[clang] [Modules] fix assert in hasInitWithSideEffects (PR #146468)
Henrik G. Olsson via cfe-commits
cfe-commits at lists.llvm.org
Thu Jul 3 10:31:20 PDT 2025
https://github.com/hnrklssn updated https://github.com/llvm/llvm-project/pull/146468
>From 39fa1a4610cf14d6288abab07f25c7f735c62508 Mon Sep 17 00:00:00 2001
From: "Henrik G. Olsson" <h_olsson at apple.com>
Date: Mon, 30 Jun 2025 21:26:19 -0700
Subject: [PATCH] [Modules] Record side effect info in EvaluatedStmt
All deserialized VarDecl initializers are EvaluatedStmt, but not all
EvaluatedStmt initializers are from a PCH. Calling
`VarDecl::hasInitWithSideEffects` can trigger constant evaluation, but
it's hard to know ahead of time whether that will trigger
deserialization - even if the initializer is fully deserialized, it may
contain a call to a constructor whose body is not deserialized. By
caching the result of `VarDecl::hasInitWithSideEffects` and populating
that cache during deserialization we can guarantee that calling it won't
trigger deserialization regardless of the state of the initializer.
This also reduces memory usage by removing the `InitSideEffectVars` set
in `ASTReader`.
rdar://154717930
---
clang/include/clang/AST/Decl.h | 14 +++--
clang/include/clang/AST/ExternalASTSource.h | 15 ------
.../clang/Sema/MultiplexExternalSemaSource.h | 2 -
clang/include/clang/Serialization/ASTReader.h | 8 ---
clang/lib/AST/Decl.cpp | 27 ++++------
.../lib/Sema/MultiplexExternalSemaSource.cpp | 8 ---
clang/lib/Serialization/ASTReader.cpp | 4 --
clang/lib/Serialization/ASTReaderDecl.cpp | 5 +-
clang/lib/Serialization/ASTWriter.cpp | 4 ++
clang/lib/Serialization/ASTWriterDecl.cpp | 1 -
.../var-init-side-effects-modulemap.cpp | 51 +++++++++++++++++++
11 files changed, 76 insertions(+), 63 deletions(-)
create mode 100644 clang/test/Modules/var-init-side-effects-modulemap.cpp
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index c4202f1f3d07e..f70a039bf3517 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -888,13 +888,17 @@ struct EvaluatedStmt {
bool HasICEInit : 1;
bool CheckedForICEInit : 1;
+ bool HasSideEffects : 1;
+ bool CheckedForSideEffects : 1;
+
LazyDeclStmtPtr Value;
APValue Evaluated;
EvaluatedStmt()
: WasEvaluated(false), IsEvaluating(false),
HasConstantInitialization(false), HasConstantDestruction(false),
- HasICEInit(false), CheckedForICEInit(false) {}
+ HasICEInit(false), CheckedForICEInit(false), HasSideEffects(false),
+ CheckedForSideEffects(false) {}
};
/// Represents a variable declaration or definition.
@@ -1353,9 +1357,11 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
return const_cast<VarDecl *>(this)->getInitializingDeclaration();
}
- /// Checks whether this declaration has an initializer with side effects,
- /// without triggering deserialization if the initializer is not yet
- /// deserialized.
+ /// Checks whether this declaration has an initializer with side effects.
+ /// The result is cached. If the result hasn't been computed this can trigger
+ /// deserialization and constant evaluation. By running this during
+ /// serialization and serializing the result all clients can safely call this
+ /// without triggering further deserialization.
bool hasInitWithSideEffects() const;
/// Determine whether this variable's value might be usable in a
diff --git a/clang/include/clang/AST/ExternalASTSource.h b/clang/include/clang/AST/ExternalASTSource.h
index e91d5132da10f..8fc490b1d8471 100644
--- a/clang/include/clang/AST/ExternalASTSource.h
+++ b/clang/include/clang/AST/ExternalASTSource.h
@@ -196,10 +196,6 @@ class ExternalASTSource : public RefCountedBase<ExternalASTSource> {
/// module.
virtual bool wasThisDeclarationADefinition(const FunctionDecl *FD);
- virtual bool hasInitializerWithSideEffects(const VarDecl *VD) const {
- return false;
- }
-
/// Finds all declarations lexically contained within the given
/// DeclContext, after applying an optional filter predicate.
///
@@ -434,17 +430,6 @@ struct LazyOffsetPtr {
return GetPtr();
}
- /// Retrieve the pointer to the AST node that this lazy pointer points to,
- /// if it can be done without triggering deserialization.
- ///
- /// \returns a pointer to the AST node, or null if not yet deserialized.
- T *getWithoutDeserializing() const {
- if (isOffset()) {
- return nullptr;
- }
- return GetPtr();
- }
-
/// Retrieve the address of the AST node pointer. Deserializes the pointee if
/// necessary.
T **getAddressOfPointer(ExternalASTSource *Source) const {
diff --git a/clang/include/clang/Sema/MultiplexExternalSemaSource.h b/clang/include/clang/Sema/MultiplexExternalSemaSource.h
index 7c66c26a17a13..391c2177d75ec 100644
--- a/clang/include/clang/Sema/MultiplexExternalSemaSource.h
+++ b/clang/include/clang/Sema/MultiplexExternalSemaSource.h
@@ -94,8 +94,6 @@ class MultiplexExternalSemaSource : public ExternalSemaSource {
bool wasThisDeclarationADefinition(const FunctionDecl *FD) override;
- bool hasInitializerWithSideEffects(const VarDecl *VD) const override;
-
/// Find all declarations with the given name in the
/// given context.
bool FindExternalVisibleDeclsByName(const DeclContext *DC,
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index 7d4b4467eb97d..6e64a435af3de 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -1453,12 +1453,6 @@ class ASTReader
const StringRef &operator*() && = delete;
};
- /// VarDecls with initializers containing side effects must be emitted,
- /// but DeclMustBeEmitted is not allowed to deserialize the intializer.
- /// FIXME: Lower memory usage by removing VarDecls once the initializer
- /// is deserialized.
- llvm::SmallPtrSet<Decl *, 16> InitSideEffectVars;
-
public:
/// Get the buffer for resolving paths.
SmallString<0> &getPathBuf() { return PathBuf; }
@@ -2410,8 +2404,6 @@ class ASTReader
bool wasThisDeclarationADefinition(const FunctionDecl *FD) override;
- bool hasInitializerWithSideEffects(const VarDecl *VD) const override;
-
/// Retrieve a selector from the given module with its local ID
/// number.
Selector getLocalSelector(ModuleFile &M, unsigned LocalID);
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 5cdf75d71e4d7..a22721d7e5277 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2444,24 +2444,15 @@ bool VarDecl::hasInitWithSideEffects() const {
if (!hasInit())
return false;
- // Check if we can get the initializer without deserializing
- const Expr *E = nullptr;
- if (auto *S = dyn_cast<Stmt *>(Init)) {
- E = cast<Expr>(S);
- } else {
- E = cast_or_null<Expr>(getEvaluatedStmt()->Value.getWithoutDeserializing());
- }
-
- if (E)
- return E->HasSideEffects(getASTContext()) &&
- // We can get a value-dependent initializer during error recovery.
- (E->isValueDependent() || !evaluateValue());
-
- assert(getEvaluatedStmt()->Value.isOffset());
- // ASTReader tracks this without having to deserialize the initializer
- if (auto Source = getASTContext().getExternalSource())
- return Source->hasInitializerWithSideEffects(this);
- return false;
+ EvaluatedStmt *ES = ensureEvaluatedStmt();
+ if (!ES->CheckedForSideEffects) {
+ const Expr *E = getInit();
+ ES->HasSideEffects = E->HasSideEffects(getASTContext()) &&
+ // We can get a value-dependent initializer during error recovery.
+ (E->isValueDependent() || !evaluateValue());
+ ES->CheckedForSideEffects = true;
+ }
+ return ES->HasSideEffects;
}
bool VarDecl::isOutOfLine() const {
diff --git a/clang/lib/Sema/MultiplexExternalSemaSource.cpp b/clang/lib/Sema/MultiplexExternalSemaSource.cpp
index 9f19f13592e86..fbfb242598c24 100644
--- a/clang/lib/Sema/MultiplexExternalSemaSource.cpp
+++ b/clang/lib/Sema/MultiplexExternalSemaSource.cpp
@@ -115,14 +115,6 @@ bool MultiplexExternalSemaSource::wasThisDeclarationADefinition(
return false;
}
-bool MultiplexExternalSemaSource::hasInitializerWithSideEffects(
- const VarDecl *VD) const {
- for (const auto &S : Sources)
- if (S->hasInitializerWithSideEffects(VD))
- return true;
- return false;
-}
-
bool MultiplexExternalSemaSource::FindExternalVisibleDeclsByName(
const DeclContext *DC, DeclarationName Name,
const DeclContext *OriginalDC) {
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 486971818f109..156b5bfdaddf6 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -9725,10 +9725,6 @@ bool ASTReader::wasThisDeclarationADefinition(const FunctionDecl *FD) {
return ThisDeclarationWasADefinitionSet.contains(FD);
}
-bool ASTReader::hasInitializerWithSideEffects(const VarDecl *VD) const {
- return InitSideEffectVars.count(VD);
-}
-
Selector ASTReader::getLocalSelector(ModuleFile &M, unsigned LocalID) {
return DecodeSelector(getGlobalSelectorID(M, LocalID));
}
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 0ffd78424be0d..8bde213dc4cb3 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -1628,9 +1628,6 @@ RedeclarableResult ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
VD->NonParmVarDeclBits.PreviousDeclInSameBlockScope =
VarDeclBits.getNextBit();
- if (VarDeclBits.getNextBit())
- Reader.InitSideEffectVars.insert(VD);
-
VD->NonParmVarDeclBits.EscapingByref = VarDeclBits.getNextBit();
HasDeducedType = VarDeclBits.getNextBit();
VD->NonParmVarDeclBits.ImplicitParamKind =
@@ -1701,6 +1698,8 @@ void ASTDeclReader::ReadVarDeclInit(VarDecl *VD) {
Eval->HasConstantInitialization = (Val & 2) != 0;
Eval->HasConstantDestruction = (Val & 4) != 0;
Eval->WasEvaluated = (Val & 8) != 0;
+ Eval->HasSideEffects = (Val & 16) != 0;
+ Eval->CheckedForSideEffects = true;
if (Eval->WasEvaluated) {
Eval->Evaluated = Record.readAPValue();
if (Eval->Evaluated.needsCleanup())
diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp
index 06cd6c7305114..dc4b2f475c323 100644
--- a/clang/lib/Serialization/ASTWriter.cpp
+++ b/clang/lib/Serialization/ASTWriter.cpp
@@ -7320,6 +7320,10 @@ void ASTRecordWriter::AddVarDeclInit(const VarDecl *VD) {
uint64_t Val = 1;
if (EvaluatedStmt *ES = VD->getEvaluatedStmt()) {
+ // This may trigger evaluation, so run it first
+ if (VD->hasInitWithSideEffects())
+ Val |= 16;
+ assert(ES->CheckedForSideEffects);
Val |= (ES->HasConstantInitialization ? 2 : 0);
Val |= (ES->HasConstantDestruction ? 4 : 0);
APValue *Evaluated = VD->getEvaluatedValue();
diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp
index 7f1b39c242e01..a4474bbd37690 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1305,7 +1305,6 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
VarDeclBits.addBit(D->isConstexpr());
VarDeclBits.addBit(D->isInitCapture());
VarDeclBits.addBit(D->isPreviousDeclInSameBlockScope());
- VarDeclBits.addBit(D->hasInitWithSideEffects());
VarDeclBits.addBit(D->isEscapingByref());
HasDeducedType = D->getType()->getContainedDeducedType();
diff --git a/clang/test/Modules/var-init-side-effects-modulemap.cpp b/clang/test/Modules/var-init-side-effects-modulemap.cpp
new file mode 100644
index 0000000000000..750a6f1731405
--- /dev/null
+++ b/clang/test/Modules/var-init-side-effects-modulemap.cpp
@@ -0,0 +1,51 @@
+// RUN: rm -rf %t
+// RUN: mkdir -p %t
+// RUN: split-file %s %t
+
+// RUN: %clang_cc1 -fsyntax-only -fmodules -fmodules-cache-path=%t -fmodule-map-file=%t/module.modulemap %t/test.cppm -I%t
+//
+
+//--- test.cppm
+#pragma clang module import Baz
+
+//--- Foo.h
+#pragma once
+class foo {
+ char dummy = 1;
+
+public:
+ static foo var;
+
+};
+
+inline foo foo::var;
+
+//--- Bar.h
+#pragma once
+#include <Foo.h>
+
+void bar() {
+ (void) foo::var;
+}
+
+//--- Baz.h
+#pragma once
+#include <Foo.h>
+
+void baz() {
+ (void) foo::var;
+}
+
+#include <Bar.h>
+
+//--- module.modulemap
+module Foo {
+ header "Foo.h"
+}
+module Bar {
+ header "Bar.h"
+}
+module Baz {
+ header "Baz.h"
+}
+
More information about the cfe-commits
mailing list