Skip to content

Commit 8913b25

Browse files
committed
[C++20] [Modules] [Reduced BMI] Remove unreachable decls GMF in reduced BMI
Following of #76930 This follows the idea of "only writes what we writes", which I think is the most natural and efficient way to implement this optimization. We start writing the BMI from the first declaration in module purview instead of the global module fragment, so that everything in the GMF untouched won't be written in the BMI naturally. The exception is, as I said in #76930, when we write a declaration we need to write its decl context, and when we write the decl context, we need to write everything from it. So when we see `std::vector`, we basically need to write everything under namespace std. This violates our intention. To fix this, this patch delays the writing of namespace in the GMF. From my local measurement, the size of the BMI decrease to 90M from 112M for a local modules build. I think this is significant. This feature will be covered under the experimental reduced BMI so that it won't affect any existing users. So I'd like to land this when the CI gets green. Documents will be added seperately.
1 parent 21265f6 commit 8913b25

17 files changed

+337
-33
lines changed

clang/include/clang/Serialization/ASTBitCodes.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,10 @@ enum ASTRecordTypes {
698698
/// Record code for an unterminated \#pragma clang assume_nonnull begin
699699
/// recorded in a preamble.
700700
PP_ASSUME_NONNULL_LOC = 67,
701+
702+
/// Record code for lexical and visible block for delayed namespace in
703+
/// reduced BMI.
704+
DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD = 68,
701705
};
702706

703707
/// Record types used within a source manager block.

clang/include/clang/Serialization/ASTReader.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,20 @@ class ASTReader
517517
/// in the chain.
518518
DeclUpdateOffsetsMap DeclUpdateOffsets;
519519

520+
using DelayedNamespaceOffsetMapTy = llvm::DenseMap<
521+
serialization::DeclID,
522+
std::pair</*LexicalOffset*/ uint64_t, /*VisibleOffset*/ uint64_t>>;
523+
524+
/// Mapping from global declaration IDs to the lexical and visible block
525+
/// offset for delayed namespace in reduced BMI.
526+
///
527+
/// We can't use the existing DeclUpdate mechanism since the DeclUpdate
528+
/// may only be applied in an outer most read. However, we need to know
529+
/// whether or not a DeclContext has external storage during the recursive
530+
/// reading. So we need to apply the offset immediately after we read the
531+
/// namespace as if it is not delayed.
532+
DelayedNamespaceOffsetMapTy DelayedNamespaceOffsetMap;
533+
520534
struct PendingUpdateRecord {
521535
Decl *D;
522536
serialization::GlobalDeclID ID;

clang/include/clang/Serialization/ASTWriter.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,16 @@ class ASTWriter : public ASTDeserializationListener,
201201
/// The declarations and types to emit.
202202
std::queue<DeclOrType> DeclTypesToEmit;
203203

204+
/// The delayed namespace to emit. Only meaningful for reduced BMI.
205+
///
206+
/// In reduced BMI, we want to elide the unreachable declarations in
207+
/// the global module fragment. However, in ASTWriterDecl, when we see
208+
/// a namespace, all the declarations in the namespace would be emitted.
209+
/// So the optimization become meaningless. To solve the issue, we
210+
/// delay recording all the declarations until we emit all the declarations.
211+
/// Then we can safely record the reached declarations only.
212+
llvm::SmallVector<NamespaceDecl *, 16> DelayedNamespace;
213+
204214
/// The first ID number we can use for our own declarations.
205215
serialization::DeclID FirstDeclID = serialization::NUM_PREDEF_DECL_IDS;
206216

@@ -529,7 +539,8 @@ class ASTWriter : public ASTDeserializationListener,
529539
void WriteType(QualType T);
530540

531541
bool isLookupResultExternal(StoredDeclsList &Result, DeclContext *DC);
532-
bool isLookupResultEntirelyExternal(StoredDeclsList &Result, DeclContext *DC);
542+
bool isLookupResultEntirelyExternalOrUnreachable(StoredDeclsList &Result,
543+
DeclContext *DC);
533544

534545
void GenerateNameLookupTable(const DeclContext *DC,
535546
llvm::SmallVectorImpl<char> &LookupTable);
@@ -704,6 +715,15 @@ class ASTWriter : public ASTDeserializationListener,
704715
/// declaration.
705716
serialization::DeclID getDeclID(const Decl *D);
706717

718+
/// Whether or not the declaration got emitted. If not, it wouldn't be
719+
/// emitted.
720+
///
721+
/// This may only be called after we've done the job to write the
722+
/// declarations (marked by DoneWritingDeclsAndTypes).
723+
///
724+
/// A declaration may only be omitted in reduced BMI.
725+
bool wasDeclEmitted(const Decl *D) const;
726+
707727
unsigned getAnonymousDeclarationNumber(const NamedDecl *D);
708728

709729
/// Add a string to the given record.
@@ -798,6 +818,10 @@ class ASTWriter : public ASTDeserializationListener,
798818
return WritingModule && WritingModule->isNamedModule();
799819
}
800820

821+
bool isGeneratingReducedBMI() const { return GeneratingReducedBMI; }
822+
823+
bool getDoneWritingDeclsAndTypes() const { return DoneWritingDeclsAndTypes; }
824+
801825
private:
802826
// ASTDeserializationListener implementation
803827
void ReaderInitialized(ASTReader *Reader) override;

clang/lib/Serialization/ASTReader.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3795,6 +3795,29 @@ llvm::Error ASTReader::ReadASTBlock(ModuleFile &F,
37953795
}
37963796
break;
37973797

3798+
case DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD: {
3799+
if (Record.size() % 3 != 0)
3800+
return llvm::createStringError(
3801+
std::errc::illegal_byte_sequence,
3802+
"invalid DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD block in AST "
3803+
"file");
3804+
for (unsigned I = 0, N = Record.size(); I != N; I += 3) {
3805+
GlobalDeclID ID = getGlobalDeclID(F, Record[I]);
3806+
3807+
uint64_t BaseOffset = F.DeclsBlockStartOffset;
3808+
assert(BaseOffset && "Invalid DeclsBlockStartOffset for module file!");
3809+
uint64_t LexicalOffset = Record[I + 1] ? BaseOffset + Record[I + 1] : 0;
3810+
uint64_t VisibleOffset = Record[I + 2] ? BaseOffset + Record[I + 2] : 0;
3811+
3812+
DelayedNamespaceOffsetMap[ID] = {LexicalOffset, VisibleOffset};
3813+
3814+
assert(!GetExistingDecl(ID) &&
3815+
"We shouldn't load the namespace in the front of delayed "
3816+
"namespace lexical and visible block");
3817+
}
3818+
break;
3819+
}
3820+
37983821
case OBJC_CATEGORIES_MAP:
37993822
if (F.LocalNumObjCCategoriesInMap != 0)
38003823
return llvm::createStringError(

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4124,6 +4124,15 @@ Decl *ASTReader::ReadDeclRecord(DeclID ID) {
41244124
// offsets for its tables of lexical and visible declarations.
41254125
if (auto *DC = dyn_cast<DeclContext>(D)) {
41264126
std::pair<uint64_t, uint64_t> Offsets = Reader.VisitDeclContext(DC);
4127+
4128+
// Get the lexical and visible block for the delayed namespace.
4129+
// It is sufficient to judge if ID is in DelayedNamespaceOffsetMap.
4130+
// But it may be more efficient to filter the other cases.
4131+
if (!Offsets.first && !Offsets.second && isa<NamespaceDecl>(D))
4132+
if (auto Iter = DelayedNamespaceOffsetMap.find(ID);
4133+
Iter != DelayedNamespaceOffsetMap.end())
4134+
Offsets = Iter->second;
4135+
41274136
if (Offsets.first &&
41284137
ReadLexicalDeclContextStorage(*Loc.F, DeclsCursor, Offsets.first, DC))
41294138
return nullptr;

clang/lib/Serialization/ASTWriter.cpp

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,7 @@ void ASTWriter::WriteBlockInfoBlock() {
870870
RECORD(WEAK_UNDECLARED_IDENTIFIERS);
871871
RECORD(PENDING_IMPLICIT_INSTANTIATIONS);
872872
RECORD(UPDATE_VISIBLE);
873+
RECORD(DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD);
873874
RECORD(DECL_UPDATE_OFFSETS);
874875
RECORD(DECL_UPDATES);
875876
RECORD(CUDA_SPECIAL_DECL_REFS);
@@ -3029,10 +3030,12 @@ void ASTWriter::WriteSubmodules(Module *WritingModule) {
30293030
Stream.EmitRecordWithBlob(ConfigMacroAbbrev, Record, CM);
30303031
}
30313032

3032-
// Emit the initializers, if any.
3033+
// Emit the reachable initializers.
3034+
// The initializer may only be unreachable in reduced BMI.
30333035
RecordData Inits;
30343036
for (Decl *D : Context->getModuleInitializers(Mod))
3035-
Inits.push_back(GetDeclRef(D));
3037+
if (wasDeclEmitted(D))
3038+
Inits.push_back(GetDeclRef(D));
30363039
if (!Inits.empty())
30373040
Stream.EmitRecord(SUBMODULE_INITIALIZERS, Inits);
30383041

@@ -3211,6 +3214,9 @@ uint64_t ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
32113214
uint64_t Offset = Stream.GetCurrentBitNo();
32123215
SmallVector<uint32_t, 128> KindDeclPairs;
32133216
for (const auto *D : DC->decls()) {
3217+
if (DoneWritingDeclsAndTypes && !wasDeclEmitted(D))
3218+
continue;
3219+
32143220
KindDeclPairs.push_back(D->getKind());
32153221
KindDeclPairs.push_back(GetDeclRef(D));
32163222
}
@@ -3865,8 +3871,14 @@ class ASTDeclContextNameLookupTrait {
38653871
data_type getData(const Coll &Decls) {
38663872
unsigned Start = DeclIDs.size();
38673873
for (NamedDecl *D : Decls) {
3868-
DeclIDs.push_back(
3869-
Writer.GetDeclRef(getDeclForLocalLookup(Writer.getLangOpts(), D)));
3874+
NamedDecl *DeclForLocalLookup =
3875+
getDeclForLocalLookup(Writer.getLangOpts(), D);
3876+
3877+
if (Writer.getDoneWritingDeclsAndTypes() &&
3878+
!Writer.wasDeclEmitted(DeclForLocalLookup))
3879+
continue;
3880+
3881+
DeclIDs.push_back(Writer.GetDeclRef(DeclForLocalLookup));
38703882
}
38713883
return std::make_pair(Start, DeclIDs.size());
38723884
}
@@ -3975,11 +3987,20 @@ bool ASTWriter::isLookupResultExternal(StoredDeclsList &Result,
39753987
DC->hasNeedToReconcileExternalVisibleStorage();
39763988
}
39773989

3978-
bool ASTWriter::isLookupResultEntirelyExternal(StoredDeclsList &Result,
3979-
DeclContext *DC) {
3980-
for (auto *D : Result.getLookupResult())
3981-
if (!getDeclForLocalLookup(getLangOpts(), D)->isFromASTFile())
3982-
return false;
3990+
bool ASTWriter::isLookupResultEntirelyExternalOrUnreachable(
3991+
StoredDeclsList &Result, DeclContext *DC) {
3992+
for (auto *D : Result.getLookupResult()) {
3993+
auto *LocalD = getDeclForLocalLookup(getLangOpts(), D);
3994+
if (LocalD->isFromASTFile())
3995+
continue;
3996+
3997+
// We can only be sure whether the local declaration is reachable
3998+
// after we done writing the declarations and types.
3999+
if (DoneWritingDeclsAndTypes && !wasDeclEmitted(LocalD))
4000+
continue;
4001+
4002+
return false;
4003+
}
39834004

39844005
return true;
39854006
}
@@ -4017,8 +4038,17 @@ ASTWriter::GenerateNameLookupTable(const DeclContext *ConstDC,
40174038
// don't need to write an entry for the name at all. If we can't
40184039
// write out a lookup set without performing more deserialization,
40194040
// just skip this entry.
4020-
if (isLookupResultExternal(Result, DC) &&
4021-
isLookupResultEntirelyExternal(Result, DC))
4041+
//
4042+
// Also in reduced BMI, we'd like to avoid writing unreachable
4043+
// declarations in GMF, so we need to avoid writing declarations
4044+
// that entirely external or unreachable.
4045+
//
4046+
// FIMXE: It looks sufficient to test
4047+
// isLookupResultEntirelyExternalOrUnreachable here. But due to bug we have
4048+
// to test isLookupResultExternal here. See
4049+
// https://github.com/llvm/llvm-project/issues/61065 for details.
4050+
if ((GeneratingReducedBMI || isLookupResultExternal(Result, DC)) &&
4051+
isLookupResultEntirelyExternalOrUnreachable(Result, DC))
40224052
continue;
40234053

40244054
// We also skip empty results. If any of the results could be external and
@@ -4209,9 +4239,15 @@ uint64_t ASTWriter::WriteDeclContextVisibleBlock(ASTContext &Context,
42094239
continue;
42104240
}
42114241

4212-
for (NamedDecl *ND : Result)
4213-
if (!ND->isFromASTFile())
4214-
GetDeclRef(ND);
4242+
for (NamedDecl *ND : Result) {
4243+
if (ND->isFromASTFile())
4244+
continue;
4245+
4246+
if (DoneWritingDeclsAndTypes && !wasDeclEmitted(ND))
4247+
continue;
4248+
4249+
GetDeclRef(ND);
4250+
}
42154251
}
42164252

42174253
return 0;
@@ -4979,9 +5015,18 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
49795015
const TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
49805016

49815017
// Force all top level declarations to be emitted.
4982-
for (const auto *D : TU->noload_decls())
4983-
if (!D->isFromASTFile())
4984-
GetDeclRef(D);
5018+
//
5019+
// We start emitting top level declarations from the module purview to
5020+
// implement the eliding unreachable declaration feature.
5021+
for (const auto *D : TU->noload_decls()) {
5022+
if (D->isFromASTFile())
5023+
continue;
5024+
5025+
if (GeneratingReducedBMI && D->isFromExplicitGlobalModule())
5026+
continue;
5027+
5028+
GetDeclRef(D);
5029+
}
49855030

49865031
// If the translation unit has an anonymous namespace, and we don't already
49875032
// have an update block for it, write it as an update block.
@@ -5291,24 +5336,59 @@ void ASTWriter::WriteDeclAndTypes(ASTContext &Context) {
52915336
WriteDecl(Context, DOT.getDecl());
52925337
}
52935338
} while (!DeclUpdates.empty());
5294-
Stream.ExitBlock();
52955339

52965340
DoneWritingDeclsAndTypes = true;
52975341

5342+
// DelayedNamespace is only meaningful in reduced BMI.
5343+
// See the comments of DelayedNamespace for details.
5344+
assert(DelayedNamespace.empty() || GeneratingReducedBMI);
5345+
RecordData DelayedNamespaceRecord;
5346+
for (NamespaceDecl *NS : DelayedNamespace) {
5347+
uint64_t LexicalOffset = WriteDeclContextLexicalBlock(Context, NS);
5348+
uint64_t VisibleOffset = WriteDeclContextVisibleBlock(Context, NS);
5349+
5350+
// Write the offset relative to current block.
5351+
if (LexicalOffset)
5352+
LexicalOffset -= DeclTypesBlockStartOffset;
5353+
5354+
if (VisibleOffset)
5355+
VisibleOffset -= DeclTypesBlockStartOffset;
5356+
5357+
DelayedNamespaceRecord.push_back(getDeclID(NS));
5358+
DelayedNamespaceRecord.push_back(LexicalOffset);
5359+
DelayedNamespaceRecord.push_back(VisibleOffset);
5360+
}
5361+
5362+
// The process of writing lexical and visible block for delayed namespace
5363+
// shouldn't introduce any new decls, types or update to emit.
5364+
assert(DeclTypesToEmit.empty());
5365+
assert(DeclUpdates.empty());
5366+
5367+
Stream.ExitBlock();
5368+
52985369
// These things can only be done once we've written out decls and types.
52995370
WriteTypeDeclOffsets();
53005371
if (!DeclUpdatesOffsetsRecord.empty())
53015372
Stream.EmitRecord(DECL_UPDATE_OFFSETS, DeclUpdatesOffsetsRecord);
53025373

5374+
if (!DelayedNamespaceRecord.empty())
5375+
Stream.EmitRecord(DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD,
5376+
DelayedNamespaceRecord);
5377+
53035378
const TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
53045379
// Create a lexical update block containing all of the declarations in the
53055380
// translation unit that do not come from other AST files.
53065381
SmallVector<uint32_t, 128> NewGlobalKindDeclPairs;
53075382
for (const auto *D : TU->noload_decls()) {
5308-
if (!D->isFromASTFile()) {
5309-
NewGlobalKindDeclPairs.push_back(D->getKind());
5310-
NewGlobalKindDeclPairs.push_back(GetDeclRef(D));
5311-
}
5383+
if (D->isFromASTFile())
5384+
continue;
5385+
5386+
// In reduced BMI, skip unreached declarations.
5387+
if (!wasDeclEmitted(D))
5388+
continue;
5389+
5390+
NewGlobalKindDeclPairs.push_back(D->getKind());
5391+
NewGlobalKindDeclPairs.push_back(GetDeclRef(D));
53125392
}
53135393

53145394
auto Abv = std::make_shared<llvm::BitCodeAbbrev>();
@@ -5817,6 +5897,21 @@ DeclID ASTWriter::getDeclID(const Decl *D) {
58175897
return DeclIDs[D];
58185898
}
58195899

5900+
bool ASTWriter::wasDeclEmitted(const Decl *D) const {
5901+
assert(D);
5902+
5903+
assert(DoneWritingDeclsAndTypes &&
5904+
"wasDeclEmitted should only be called after writing declarations");
5905+
5906+
if (D->isFromASTFile())
5907+
return true;
5908+
5909+
bool Emitted = DeclIDs.contains(D);
5910+
assert((Emitted || GeneratingReducedBMI) &&
5911+
"The declaration can only be omitted in reduced BMI.");
5912+
return Emitted;
5913+
}
5914+
58205915
void ASTWriter::associateDeclWithFile(const Decl *D, DeclID ID) {
58215916
assert(ID);
58225917
assert(D);

clang/lib/Serialization/ASTWriterDecl.cpp

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1719,6 +1719,15 @@ void ASTDeclWriter::VisitClassTemplateDecl(ClassTemplateDecl *D) {
17191719

17201720
if (D->isFirstDecl())
17211721
AddTemplateSpecializations(D);
1722+
1723+
// Force emitting the corresponding deduction guide in reduced BMI mode.
1724+
// Otherwise, the deduction guide may be optimized out incorrectly.
1725+
if (Writer.isGeneratingReducedBMI()) {
1726+
auto Name = Context.DeclarationNames.getCXXDeductionGuideName(D);
1727+
for (auto *DG : D->getDeclContext()->noload_lookup(Name))
1728+
Writer.GetDeclRef(DG);
1729+
}
1730+
17221731
Code = serialization::DECL_CLASS_TEMPLATE;
17231732
}
17241733

@@ -1962,8 +1971,22 @@ void ASTDeclWriter::VisitDeclContext(DeclContext *DC) {
19621971
"You need to update the serializer after you change the "
19631972
"DeclContextBits");
19641973

1965-
Record.AddOffset(Writer.WriteDeclContextLexicalBlock(Context, DC));
1966-
Record.AddOffset(Writer.WriteDeclContextVisibleBlock(Context, DC));
1974+
uint64_t LexicalOffset = 0;
1975+
uint64_t VisibleOffset = 0;
1976+
1977+
if (Writer.isGeneratingReducedBMI() && isa<NamespaceDecl>(DC) &&
1978+
cast<NamespaceDecl>(DC)->isFromExplicitGlobalModule()) {
1979+
// In reduced BMI, delay writing lexical and visible block for namespace
1980+
// in the global module fragment. See the comments of DelayedNamespace for
1981+
// details.
1982+
Writer.DelayedNamespace.push_back(cast<NamespaceDecl>(DC));
1983+
} else {
1984+
LexicalOffset = Writer.WriteDeclContextLexicalBlock(Context, DC);
1985+
VisibleOffset = Writer.WriteDeclContextVisibleBlock(Context, DC);
1986+
}
1987+
1988+
Record.AddOffset(LexicalOffset);
1989+
Record.AddOffset(VisibleOffset);
19671990
}
19681991

19691992
const Decl *ASTWriter::getFirstLocalDecl(const Decl *D) {

0 commit comments

Comments
 (0)