Skip to content

Commit f21ead0

Browse files
authored
[C++20] [Modules] [Reduced BMI] Remove unreachable decls GMF in redued BMI (#88359)
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 505a9ae commit f21ead0

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
@@ -4125,6 +4125,15 @@ Decl *ASTReader::ReadDeclRecord(DeclID ID) {
41254125
// offsets for its tables of lexical and visible declarations.
41264126
if (auto *DC = dyn_cast<DeclContext>(D)) {
41274127
std::pair<uint64_t, uint64_t> Offsets = Reader.VisitDeclContext(DC);
4128+
4129+
// Get the lexical and visible block for the delayed namespace.
4130+
// It is sufficient to judge if ID is in DelayedNamespaceOffsetMap.
4131+
// But it may be more efficient to filter the other cases.
4132+
if (!Offsets.first && !Offsets.second && isa<NamespaceDecl>(D))
4133+
if (auto Iter = DelayedNamespaceOffsetMap.find(ID);
4134+
Iter != DelayedNamespaceOffsetMap.end())
4135+
Offsets = Iter->second;
4136+
41284137
if (Offsets.first &&
41294138
ReadLexicalDeclContextStorage(*Loc.F, DeclsCursor, Offsets.first, DC))
41304139
return nullptr;

clang/lib/Serialization/ASTWriter.cpp

Lines changed: 117 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -869,6 +869,7 @@ void ASTWriter::WriteBlockInfoBlock() {
869869
RECORD(WEAK_UNDECLARED_IDENTIFIERS);
870870
RECORD(PENDING_IMPLICIT_INSTANTIATIONS);
871871
RECORD(UPDATE_VISIBLE);
872+
RECORD(DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD);
872873
RECORD(DECL_UPDATE_OFFSETS);
873874
RECORD(DECL_UPDATES);
874875
RECORD(CUDA_SPECIAL_DECL_REFS);
@@ -3026,10 +3027,12 @@ void ASTWriter::WriteSubmodules(Module *WritingModule) {
30263027
Stream.EmitRecordWithBlob(ConfigMacroAbbrev, Record, CM);
30273028
}
30283029

3029-
// Emit the initializers, if any.
3030+
// Emit the reachable initializers.
3031+
// The initializer may only be unreachable in reduced BMI.
30303032
RecordData Inits;
30313033
for (Decl *D : Context->getModuleInitializers(Mod))
3032-
Inits.push_back(GetDeclRef(D));
3034+
if (wasDeclEmitted(D))
3035+
Inits.push_back(GetDeclRef(D));
30333036
if (!Inits.empty())
30343037
Stream.EmitRecord(SUBMODULE_INITIALIZERS, Inits);
30353038

@@ -3208,6 +3211,9 @@ uint64_t ASTWriter::WriteDeclContextLexicalBlock(ASTContext &Context,
32083211
uint64_t Offset = Stream.GetCurrentBitNo();
32093212
SmallVector<uint32_t, 128> KindDeclPairs;
32103213
for (const auto *D : DC->decls()) {
3214+
if (DoneWritingDeclsAndTypes && !wasDeclEmitted(D))
3215+
continue;
3216+
32113217
KindDeclPairs.push_back(D->getKind());
32123218
KindDeclPairs.push_back(GetDeclRef(D));
32133219
}
@@ -3862,8 +3868,14 @@ class ASTDeclContextNameLookupTrait {
38623868
data_type getData(const Coll &Decls) {
38633869
unsigned Start = DeclIDs.size();
38643870
for (NamedDecl *D : Decls) {
3865-
DeclIDs.push_back(
3866-
Writer.GetDeclRef(getDeclForLocalLookup(Writer.getLangOpts(), D)));
3871+
NamedDecl *DeclForLocalLookup =
3872+
getDeclForLocalLookup(Writer.getLangOpts(), D);
3873+
3874+
if (Writer.getDoneWritingDeclsAndTypes() &&
3875+
!Writer.wasDeclEmitted(DeclForLocalLookup))
3876+
continue;
3877+
3878+
DeclIDs.push_back(Writer.GetDeclRef(DeclForLocalLookup));
38673879
}
38683880
return std::make_pair(Start, DeclIDs.size());
38693881
}
@@ -3972,11 +3984,20 @@ bool ASTWriter::isLookupResultExternal(StoredDeclsList &Result,
39723984
DC->hasNeedToReconcileExternalVisibleStorage();
39733985
}
39743986

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

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

40214051
// We also skip empty results. If any of the results could be external and
@@ -4206,9 +4236,15 @@ uint64_t ASTWriter::WriteDeclContextVisibleBlock(ASTContext &Context,
42064236
continue;
42074237
}
42084238

4209-
for (NamedDecl *ND : Result)
4210-
if (!ND->isFromASTFile())
4211-
GetDeclRef(ND);
4239+
for (NamedDecl *ND : Result) {
4240+
if (ND->isFromASTFile())
4241+
continue;
4242+
4243+
if (DoneWritingDeclsAndTypes && !wasDeclEmitted(ND))
4244+
continue;
4245+
4246+
GetDeclRef(ND);
4247+
}
42124248
}
42134249

42144250
return 0;
@@ -4976,9 +5012,18 @@ ASTFileSignature ASTWriter::WriteASTCore(Sema &SemaRef, StringRef isysroot,
49765012
const TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
49775013

49785014
// Force all top level declarations to be emitted.
4979-
for (const auto *D : TU->noload_decls())
4980-
if (!D->isFromASTFile())
4981-
GetDeclRef(D);
5015+
//
5016+
// We start emitting top level declarations from the module purview to
5017+
// implement the eliding unreachable declaration feature.
5018+
for (const auto *D : TU->noload_decls()) {
5019+
if (D->isFromASTFile())
5020+
continue;
5021+
5022+
if (GeneratingReducedBMI && D->isFromExplicitGlobalModule())
5023+
continue;
5024+
5025+
GetDeclRef(D);
5026+
}
49825027

49835028
// If the translation unit has an anonymous namespace, and we don't already
49845029
// have an update block for it, write it as an update block.
@@ -5288,24 +5333,59 @@ void ASTWriter::WriteDeclAndTypes(ASTContext &Context) {
52885333
WriteDecl(Context, DOT.getDecl());
52895334
}
52905335
} while (!DeclUpdates.empty());
5291-
Stream.ExitBlock();
52925336

52935337
DoneWritingDeclsAndTypes = true;
52945338

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

5371+
if (!DelayedNamespaceRecord.empty())
5372+
Stream.EmitRecord(DELAYED_NAMESPACE_LEXICAL_VISIBLE_RECORD,
5373+
DelayedNamespaceRecord);
5374+
53005375
const TranslationUnitDecl *TU = Context.getTranslationUnitDecl();
53015376
// Create a lexical update block containing all of the declarations in the
53025377
// translation unit that do not come from other AST files.
53035378
SmallVector<uint32_t, 128> NewGlobalKindDeclPairs;
53045379
for (const auto *D : TU->noload_decls()) {
5305-
if (!D->isFromASTFile()) {
5306-
NewGlobalKindDeclPairs.push_back(D->getKind());
5307-
NewGlobalKindDeclPairs.push_back(GetDeclRef(D));
5308-
}
5380+
if (D->isFromASTFile())
5381+
continue;
5382+
5383+
// In reduced BMI, skip unreached declarations.
5384+
if (!wasDeclEmitted(D))
5385+
continue;
5386+
5387+
NewGlobalKindDeclPairs.push_back(D->getKind());
5388+
NewGlobalKindDeclPairs.push_back(GetDeclRef(D));
53095389
}
53105390

53115391
auto Abv = std::make_shared<llvm::BitCodeAbbrev>();
@@ -5814,6 +5894,21 @@ DeclID ASTWriter::getDeclID(const Decl *D) {
58145894
return DeclIDs[D];
58155895
}
58165896

5897+
bool ASTWriter::wasDeclEmitted(const Decl *D) const {
5898+
assert(D);
5899+
5900+
assert(DoneWritingDeclsAndTypes &&
5901+
"wasDeclEmitted should only be called after writing declarations");
5902+
5903+
if (D->isFromASTFile())
5904+
return true;
5905+
5906+
bool Emitted = DeclIDs.contains(D);
5907+
assert((Emitted || GeneratingReducedBMI) &&
5908+
"The declaration can only be omitted in reduced BMI.");
5909+
return Emitted;
5910+
}
5911+
58175912
void ASTWriter::associateDeclWithFile(const Decl *D, DeclID ID) {
58185913
assert(ID);
58195914
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

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

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

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

0 commit comments

Comments
 (0)