Files
clang-p2996/clang/lib/Serialization/ASTReaderInternals.h
Chuanqi Xu 20e9049509 [Serialization] Support loading template specializations lazily (#119333)
Reland https://github.com/llvm/llvm-project/pull/83237

---

(Original comments)

Currently all the specializations of a template (including
instantiation, specialization and partial specializations) will be
loaded at once if we want to instantiate another instance for the
template, or find instantiation for the template, or just want to
complete the redecl chain.

This means basically we need to load every specializations for the
template once the template declaration got loaded. This is bad since
when we load a specialization, we need to load all of its template
arguments. Then we have to deserialize a lot of unnecessary
declarations.

For example,

```
// M.cppm
export module M;
export template <class T>
class A {};

export class ShouldNotBeLoaded {};

export class Temp {
   A<ShouldNotBeLoaded> AS;
};

// use.cpp
import M;
A<int> a;
```

We have a specialization ` A<ShouldNotBeLoaded>` in `M.cppm` and we
instantiate the template `A` in `use.cpp`. Then we will deserialize
`ShouldNotBeLoaded` surprisingly when compiling `use.cpp`. And this
patch tries to avoid that.

Given that the templates are heavily used in C++, this is a pain point
for the performance.

This patch adds MultiOnDiskHashTable for specializations in the
ASTReader. Then we will only deserialize the specializations with the
same template arguments. We made that by using ODRHash for the template
arguments as the key of the hash table.

To review this patch, I think `ASTReaderDecl::AddLazySpecializations`
may be a good entry point.
2024-12-11 09:40:47 +08:00

374 lines
11 KiB
C++

//===- ASTReaderInternals.h - AST Reader Internals --------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file provides internal definitions used in the AST reader.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_CLANG_LIB_SERIALIZATION_ASTREADERINTERNALS_H
#define LLVM_CLANG_LIB_SERIALIZATION_ASTREADERINTERNALS_H
#include "MultiOnDiskHashTable.h"
#include "clang/AST/DeclarationName.h"
#include "clang/Basic/LLVM.h"
#include "clang/Serialization/ASTBitCodes.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/OnDiskHashTable.h"
#include <ctime>
#include <utility>
namespace clang {
class ASTReader;
class FileEntry;
struct HeaderFileInfo;
class HeaderSearch;
class ObjCMethodDecl;
namespace serialization {
class ModuleFile;
namespace reader {
/// Class that performs name lookup into a DeclContext stored
/// in an AST file.
class ASTDeclContextNameLookupTrait {
ASTReader &Reader;
ModuleFile &F;
public:
// Maximum number of lookup tables we allow before condensing the tables.
static const int MaxTables = 4;
/// The lookup result is a list of global declaration IDs.
using data_type = SmallVector<GlobalDeclID, 4>;
struct data_type_builder {
data_type &Data;
llvm::DenseSet<GlobalDeclID> Found;
data_type_builder(data_type &D) : Data(D) {}
void insert(GlobalDeclID ID) {
// Just use a linear scan unless we have more than a few IDs.
if (Found.empty() && !Data.empty()) {
if (Data.size() <= 4) {
for (auto I : Found)
if (I == ID)
return;
Data.push_back(ID);
return;
}
// Switch to tracking found IDs in the set.
Found.insert(Data.begin(), Data.end());
}
if (Found.insert(ID).second)
Data.push_back(ID);
}
};
using hash_value_type = unsigned;
using offset_type = unsigned;
using file_type = ModuleFile *;
using external_key_type = DeclarationName;
using internal_key_type = DeclarationNameKey;
explicit ASTDeclContextNameLookupTrait(ASTReader &Reader, ModuleFile &F)
: Reader(Reader), F(F) {}
static bool EqualKey(const internal_key_type &a, const internal_key_type &b) {
return a == b;
}
static hash_value_type ComputeHash(const internal_key_type &Key) {
return Key.getHash();
}
static internal_key_type GetInternalKey(const external_key_type &Name) {
return Name;
}
static std::pair<unsigned, unsigned>
ReadKeyDataLength(const unsigned char *&d);
internal_key_type ReadKey(const unsigned char *d, unsigned);
void ReadDataInto(internal_key_type, const unsigned char *d,
unsigned DataLen, data_type_builder &Val);
static void MergeDataInto(const data_type &From, data_type_builder &To) {
To.Data.reserve(To.Data.size() + From.size());
for (GlobalDeclID ID : From)
To.insert(ID);
}
file_type ReadFileRef(const unsigned char *&d);
};
struct DeclContextLookupTable {
MultiOnDiskHashTable<ASTDeclContextNameLookupTrait> Table;
};
using LazySpecializationInfo = GlobalDeclID;
/// Class that performs lookup to specialized decls.
class LazySpecializationInfoLookupTrait {
ASTReader &Reader;
ModuleFile &F;
public:
// Maximum number of lookup tables we allow before condensing the tables.
static const int MaxTables = 4;
/// The lookup result is a list of global declaration IDs.
using data_type = SmallVector<LazySpecializationInfo, 4>;
struct data_type_builder {
data_type &Data;
llvm::DenseSet<LazySpecializationInfo> Found;
data_type_builder(data_type &D) : Data(D) {}
void insert(LazySpecializationInfo Info) {
// Just use a linear scan unless we have more than a few IDs.
if (Found.empty() && !Data.empty()) {
if (Data.size() <= 4) {
for (auto I : Found)
if (I == Info)
return;
Data.push_back(Info);
return;
}
// Switch to tracking found IDs in the set.
Found.insert(Data.begin(), Data.end());
}
if (Found.insert(Info).second)
Data.push_back(Info);
}
};
using hash_value_type = unsigned;
using offset_type = unsigned;
using file_type = ModuleFile *;
using external_key_type = unsigned;
using internal_key_type = unsigned;
explicit LazySpecializationInfoLookupTrait(ASTReader &Reader, ModuleFile &F)
: Reader(Reader), F(F) {}
static bool EqualKey(const internal_key_type &a, const internal_key_type &b) {
return a == b;
}
static hash_value_type ComputeHash(const internal_key_type &Key) {
return Key;
}
static internal_key_type GetInternalKey(const external_key_type &Name) {
return Name;
}
static std::pair<unsigned, unsigned>
ReadKeyDataLength(const unsigned char *&d);
internal_key_type ReadKey(const unsigned char *d, unsigned);
void ReadDataInto(internal_key_type, const unsigned char *d, unsigned DataLen,
data_type_builder &Val);
static void MergeDataInto(const data_type &From, data_type_builder &To) {
To.Data.reserve(To.Data.size() + From.size());
for (LazySpecializationInfo Info : From)
To.insert(Info);
}
file_type ReadFileRef(const unsigned char *&d);
};
struct LazySpecializationInfoLookupTable {
MultiOnDiskHashTable<LazySpecializationInfoLookupTrait> Table;
};
/// Base class for the trait describing the on-disk hash table for the
/// identifiers in an AST file.
///
/// This class is not useful by itself; rather, it provides common
/// functionality for accessing the on-disk hash table of identifiers
/// in an AST file. Different subclasses customize that functionality
/// based on what information they are interested in. Those subclasses
/// must provide the \c data_type type and the ReadData operation, only.
class ASTIdentifierLookupTraitBase {
public:
using external_key_type = StringRef;
using internal_key_type = StringRef;
using hash_value_type = unsigned;
using offset_type = unsigned;
static bool EqualKey(const internal_key_type& a, const internal_key_type& b) {
return a == b;
}
static hash_value_type ComputeHash(const internal_key_type& a);
static std::pair<unsigned, unsigned>
ReadKeyDataLength(const unsigned char*& d);
// This hopefully will just get inlined and removed by the optimizer.
static const internal_key_type&
GetInternalKey(const external_key_type& x) { return x; }
// This hopefully will just get inlined and removed by the optimizer.
static const external_key_type&
GetExternalKey(const internal_key_type& x) { return x; }
static internal_key_type ReadKey(const unsigned char* d, unsigned n);
};
/// Class that performs lookup for an identifier stored in an AST file.
class ASTIdentifierLookupTrait : public ASTIdentifierLookupTraitBase {
ASTReader &Reader;
ModuleFile &F;
// If we know the IdentifierInfo in advance, it is here and we will
// not build a new one. Used when deserializing information about an
// identifier that was constructed before the AST file was read.
IdentifierInfo *KnownII;
public:
using data_type = IdentifierInfo *;
ASTIdentifierLookupTrait(ASTReader &Reader, ModuleFile &F,
IdentifierInfo *II = nullptr)
: Reader(Reader), F(F), KnownII(II) {}
data_type ReadData(const internal_key_type& k,
const unsigned char* d,
unsigned DataLen);
IdentifierID ReadIdentifierID(const unsigned char *d);
ASTReader &getReader() const { return Reader; }
};
/// The on-disk hash table used to contain information about
/// all of the identifiers in the program.
using ASTIdentifierLookupTable =
llvm::OnDiskIterableChainedHashTable<ASTIdentifierLookupTrait>;
/// Class that performs lookup for a selector's entries in the global
/// method pool stored in an AST file.
class ASTSelectorLookupTrait {
ASTReader &Reader;
ModuleFile &F;
public:
struct data_type {
SelectorID ID;
unsigned InstanceBits;
unsigned FactoryBits;
bool InstanceHasMoreThanOneDecl;
bool FactoryHasMoreThanOneDecl;
SmallVector<ObjCMethodDecl *, 2> Instance;
SmallVector<ObjCMethodDecl *, 2> Factory;
};
using external_key_type = Selector;
using internal_key_type = external_key_type;
using hash_value_type = unsigned;
using offset_type = unsigned;
ASTSelectorLookupTrait(ASTReader &Reader, ModuleFile &F)
: Reader(Reader), F(F) {}
static bool EqualKey(const internal_key_type& a,
const internal_key_type& b) {
return a == b;
}
static hash_value_type ComputeHash(Selector Sel);
static const internal_key_type&
GetInternalKey(const external_key_type& x) { return x; }
static std::pair<unsigned, unsigned>
ReadKeyDataLength(const unsigned char*& d);
internal_key_type ReadKey(const unsigned char* d, unsigned);
data_type ReadData(Selector, const unsigned char* d, unsigned DataLen);
};
/// The on-disk hash table used for the global method pool.
using ASTSelectorLookupTable =
llvm::OnDiskChainedHashTable<ASTSelectorLookupTrait>;
/// Trait class used to search the on-disk hash table containing all of
/// the header search information.
///
/// The on-disk hash table contains a mapping from each header path to
/// information about that header (how many times it has been included, its
/// controlling macro, etc.). Note that we actually hash based on the size
/// and mtime, and support "deep" comparisons of file names based on current
/// inode numbers, so that the search can cope with non-normalized path names
/// and symlinks.
class HeaderFileInfoTrait {
ASTReader &Reader;
ModuleFile &M;
public:
using external_key_type = FileEntryRef;
struct internal_key_type {
off_t Size;
time_t ModTime;
StringRef Filename;
bool Imported;
};
using internal_key_ref = const internal_key_type &;
using data_type = HeaderFileInfo;
using hash_value_type = unsigned;
using offset_type = unsigned;
HeaderFileInfoTrait(ASTReader &Reader, ModuleFile &M)
: Reader(Reader), M(M) {}
static hash_value_type ComputeHash(internal_key_ref ikey);
internal_key_type GetInternalKey(external_key_type ekey);
bool EqualKey(internal_key_ref a, internal_key_ref b);
static std::pair<unsigned, unsigned>
ReadKeyDataLength(const unsigned char*& d);
static internal_key_type ReadKey(const unsigned char *d, unsigned);
data_type ReadData(internal_key_ref,const unsigned char *d, unsigned DataLen);
private:
OptionalFileEntryRef getFile(const internal_key_type &Key);
};
/// The on-disk hash table used for known header files.
using HeaderFileInfoLookupTable =
llvm::OnDiskChainedHashTable<HeaderFileInfoTrait>;
} // namespace reader
} // namespace serialization
} // namespace clang
#endif // LLVM_CLANG_LIB_SERIALIZATION_ASTREADERINTERNALS_H