Skip to content

Commit 3f29c7c

Browse files
committed
[Modules] Record whether VarDecl initializers contain side effects
Calling `DeclMustBeEmitted` should not lead to more deserialization, as it may occur before previous deserialization has finished. When passed a `VarDecl` with an initializer however, `DeclMustBeEmitted` needs to know whether that initializer contains side effects. When the `VarDecl` is deserialized but the initializer is not, this triggers deserialization of the initializer. To avoid this we add a bit to the serialization format for `VarDecl`s, indicating whether its initializer contains side effects or not, so that the `ASTReader` can query this information directly without deserializing the initializer. rdar://153085264
1 parent 93705c3 commit 3f29c7c

File tree

9 files changed

+91
-6
lines changed

9 files changed

+91
-6
lines changed

clang/include/clang/AST/Decl.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
13511351
return const_cast<VarDecl *>(this)->getInitializingDeclaration();
13521352
}
13531353

1354+
/// Checks whether this declaration has an initializer with side effects,
1355+
/// without triggering deserialization if the initializer is not yet
1356+
/// deserialized.
1357+
bool hasInitWithSideEffects() const;
1358+
13541359
/// Determine whether this variable's value might be usable in a
13551360
/// constant expression, according to the relevant language standard.
13561361
/// This only checks properties of the declaration, and does not check

clang/include/clang/AST/ExternalASTSource.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class RecordDecl;
5151
class Selector;
5252
class Stmt;
5353
class TagDecl;
54+
class VarDecl;
5455

5556
/// Abstract interface for external sources of AST nodes.
5657
///
@@ -195,6 +196,10 @@ class ExternalASTSource : public RefCountedBase<ExternalASTSource> {
195196
/// module.
196197
virtual bool wasThisDeclarationADefinition(const FunctionDecl *FD);
197198

199+
virtual bool hasInitializerWithSideEffects(const VarDecl *VD) const {
200+
return false;
201+
}
202+
198203
/// Finds all declarations lexically contained within the given
199204
/// DeclContext, after applying an optional filter predicate.
200205
///

clang/include/clang/Serialization/ASTReader.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,6 +1442,10 @@ class ASTReader
14421442
const StringRef &operator*() && = delete;
14431443
};
14441444

1445+
/// VarDecls with initializers containing side effects must be emitted,
1446+
/// but DeclMustBeEmitted is not allowed to deserialize the intializer.
1447+
llvm::SmallPtrSet<Decl *, 16> InitSideEffectVars;
1448+
14451449
public:
14461450
/// Get the buffer for resolving paths.
14471451
SmallString<0> &getPathBuf() { return PathBuf; }
@@ -2392,6 +2396,8 @@ class ASTReader
23922396

23932397
bool wasThisDeclarationADefinition(const FunctionDecl *FD) override;
23942398

2399+
bool hasInitializerWithSideEffects(const VarDecl *VD) const override;
2400+
23952401
/// Retrieve a selector from the given module with its local ID
23962402
/// number.
23972403
Selector getLocalSelector(ModuleFile &M, unsigned LocalID);

clang/lib/AST/ASTContext.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12889,9 +12889,7 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) {
1288912889
return true;
1289012890

1289112891
// Variables that have initialization with side-effects are required.
12892-
if (VD->getInit() && VD->getInit()->HasSideEffects(*this) &&
12893-
// We can get a value-dependent initializer during error recovery.
12894-
(VD->getInit()->isValueDependent() || !VD->evaluateValue()))
12892+
if (VD->hasInitWithSideEffects())
1289512893
return true;
1289612894

1289712895
// Likewise, variables with tuple-like bindings are required if their

clang/lib/AST/Decl.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,6 +2434,31 @@ VarDecl *VarDecl::getInitializingDeclaration() {
24342434
return Def;
24352435
}
24362436

2437+
bool VarDecl::hasInitWithSideEffects() const {
2438+
if (!hasInit())
2439+
return false;
2440+
2441+
// Check if we can get the initializer without deserializing
2442+
const Expr *E = nullptr;
2443+
if (auto *S = dyn_cast<Stmt *>(Init)) {
2444+
E = cast<Expr>(S);
2445+
} else {
2446+
auto *Eval = getEvaluatedStmt();
2447+
if (!Eval->Value.isOffset())
2448+
E = cast<Expr>(Eval->Value.get(nullptr));
2449+
}
2450+
2451+
if (E)
2452+
return E->HasSideEffects(getASTContext()) &&
2453+
// We can get a value-dependent initializer during error recovery.
2454+
(E->isValueDependent() || !evaluateValue());
2455+
2456+
assert(getEvaluatedStmt()->Value.isOffset());
2457+
// ASTReader tracks this without having to deserialize the initializer
2458+
return getASTContext().getExternalSource()->hasInitializerWithSideEffects(
2459+
this);
2460+
}
2461+
24372462
bool VarDecl::isOutOfLine() const {
24382463
if (Decl::isOutOfLine())
24392464
return true;

clang/lib/Serialization/ASTReader.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9712,6 +9712,10 @@ bool ASTReader::wasThisDeclarationADefinition(const FunctionDecl *FD) {
97129712
return ThisDeclarationWasADefinitionSet.contains(FD);
97139713
}
97149714

9715+
bool ASTReader::hasInitializerWithSideEffects(const VarDecl *VD) const {
9716+
return InitSideEffectVars.count(VD);
9717+
}
9718+
97159719
Selector ASTReader::getLocalSelector(ModuleFile &M, unsigned LocalID) {
97169720
return DecodeSelector(getGlobalSelectorID(M, LocalID));
97179721
}

clang/lib/Serialization/ASTReaderDecl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,10 @@ RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
16321632
VD->NonParmVarDeclBits.PreviousDeclInSameBlockScope =
16331633
VarDeclBits.getNextBit();
16341634

1635+
bool HasInitWithSideEffect = VarDeclBits.getNextBit();
1636+
if (HasInitWithSideEffect)
1637+
Reader.InitSideEffectVars.insert(VD);
1638+
16351639
VD->NonParmVarDeclBits.EscapingByref = VarDeclBits.getNextBit();
16361640
HasDeducedType = VarDeclBits.getNextBit();
16371641
VD->NonParmVarDeclBits.ImplicitParamKind =

clang/lib/Serialization/ASTWriterDecl.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,7 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
12591259
VarDeclBits.addBit(D->isConstexpr());
12601260
VarDeclBits.addBit(D->isInitCapture());
12611261
VarDeclBits.addBit(D->isPreviousDeclInSameBlockScope());
1262+
VarDeclBits.addBit(D->hasInitWithSideEffects());
12621263

12631264
VarDeclBits.addBit(D->isEscapingByref());
12641265
HasDeducedType = D->getType()->getContainedDeducedType();
@@ -1308,7 +1309,7 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
13081309
!D->hasExtInfo() && D->getFirstDecl() == D->getMostRecentDecl() &&
13091310
D->getKind() == Decl::Var && !D->isInline() && !D->isConstexpr() &&
13101311
!D->isInitCapture() && !D->isPreviousDeclInSameBlockScope() &&
1311-
!D->isEscapingByref() && !HasDeducedType &&
1312+
!D->hasInitWithSideEffects() && !D->isEscapingByref() && !HasDeducedType &&
13121313
D->getStorageDuration() != SD_Static && !D->getDescribedVarTemplate() &&
13131314
!D->getMemberSpecializationInfo() && !D->isObjCForDecl() &&
13141315
!isa<ImplicitParamDecl>(D) && !D->isEscapingByref())
@@ -2693,12 +2694,12 @@ void ASTWriter::WriteDeclAbbrevs() {
26932694
// VarDecl
26942695
Abv->Add(BitCodeAbbrevOp(
26952696
BitCodeAbbrevOp::Fixed,
2696-
21)); // Packed Var Decl bits: Linkage, ModulesCodegen,
2697+
22)); // Packed Var Decl bits: Linkage, ModulesCodegen,
26972698
// SClass, TSCSpec, InitStyle,
26982699
// isARCPseudoStrong, IsThisDeclarationADemotedDefinition,
26992700
// isExceptionVariable, isNRVOVariable, isCXXForRangeDecl,
27002701
// isInline, isInlineSpecified, isConstexpr,
2701-
// isInitCapture, isPrevDeclInSameScope,
2702+
// isInitCapture, isPrevDeclInSameScope, hasInitWithSideEffects,
27022703
// EscapingByref, HasDeducedType, ImplicitParamKind, isObjCForDecl
27032704
Abv->Add(BitCodeAbbrevOp(0)); // VarKind (local enum)
27042705
// Type Source Info
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Tests referencing variable with initializer containing side effect across module boundary
2+
//
3+
// RUN: rm -rf %t
4+
// RUN: mkdir -p %t
5+
// RUN: split-file %s %t
6+
7+
// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/Foo.cppm \
8+
// RUN: -o %t/Foo.pcm
9+
10+
// RUN: %clang_cc1 -std=c++20 -emit-module-interface \
11+
// RUN: -fmodule-file=Foo=%t/Foo.pcm \
12+
// RUN: %t/Bar.cppm \
13+
// RUN: -o %t/Bar.pcm
14+
15+
// RUN: %clang_cc1 -std=c++20 -emit-obj \
16+
// RUN: -main-file-name Bar.cppm \
17+
// RUN: -fmodule-file=Foo=%t/Foo.pcm \
18+
// RUN: -x pcm %t/Bar.pcm \
19+
// RUN: -o %t/Bar.o
20+
21+
//--- Foo.cppm
22+
export module Foo;
23+
24+
export {
25+
class S {};
26+
27+
inline S s = S{};
28+
}
29+
30+
//--- Bar.cppm
31+
export module Bar;
32+
import Foo;
33+
34+
S bar() {
35+
return s;
36+
}
37+

0 commit comments

Comments
 (0)