llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-modules
Author: Timm Baeder (tbaederr)
<details>
<summary>Changes</summary>
The problem looks something like this:
```c++
extern const int m;
constexpr int getm() { return m; }
const int m = 12;
static_assert(getm() == 12);
```
The generated bytecode for `getm()` references a global variable stored in
`interp::Program`. This variable is uninitialized when the bytecode is
generated, but since it's extern, that's not diagnosed (because it might be
initialized later).
When we parse the redeclaration of `m` on line 4, we evaluate its initializer
via `Expr::EvaluateAsInitializer`, which makes the bytecode interpreter aware
that `m` has been redeclared. It will create a new global for the new
declaration and make the previous global point to the new one. When the
bytecode is then evaluated in the `static_assert`, there is no problem.
However, when the redeclaration isn't part of the TU but gets de-serialized via
a module (which is the case in `test/Modules/redecl-add-after-load.cpp`), we
don't call `EvaluateAsInitializer`, we don't even necessarily de-serialize the
initializer expression at all. So the bytecode interpreter doesn't know about
the new declaration and the static_assert ultimately fails because `m` is not
initialized.
Marking this as a draft PR since I'm not sure this is the best way to do this
and if bytecode-specific things in other parts of the code base are tolerated.
---
Full diff: https://github.com/llvm/llvm-project/pull/198062.diff
15 Files Affected:
- (modified) clang/lib/AST/ByteCode/Compiler.cpp (+19)
- (modified) clang/lib/AST/ByteCode/Compiler.h (+2)
- (modified) clang/lib/AST/ByteCode/Context.cpp (+7)
- (modified) clang/lib/AST/ByteCode/Context.h (+1)
- (modified) clang/lib/AST/ByteCode/EvalEmitter.cpp (+4)
- (modified) clang/lib/AST/ByteCode/EvalEmitter.h (+2)
- (modified) clang/lib/AST/ByteCode/InterpState.cpp (+12)
- (modified) clang/lib/AST/ByteCode/InterpState.h (+2)
- (modified) clang/lib/AST/ByteCode/Program.cpp (+21-3)
- (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+11-1)
- (added) clang/test/AST/ByteCode/Inputs/module.modulemap (+1)
- (added) clang/test/AST/ByteCode/Inputs/redecl1.h (+6)
- (added) clang/test/AST/ByteCode/module-redecl1.cpp (+25)
- (modified) clang/test/Modules/pr102360.cppm (+8)
- (modified) clang/test/Modules/redecl-add-after-load.cpp (+4)
``````````diff
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp
b/clang/lib/AST/ByteCode/Compiler.cpp
index 46762ed8291f9..e5bc3167f8789 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -6003,6 +6003,25 @@ bool Compiler<Emitter>::visitAPValueInitializer(const
APValue &Val,
return false;
}
+template <class Emitter>
+bool Compiler<Emitter>::registerRedecl(const VarDecl *VD, const APValue &Val) {
+ if (P.getGlobal(VD))
+ return true;
+
+ UnsignedOrNone GlobalIndex = P.createGlobal(VD, /*Init=*/nullptr);
+ if (!GlobalIndex) {
+ llvm_unreachable("Why didn't that work?");
+ }
+
+ assert(canClassify(VD->getType()) &&
+ "registerRedecl should only be called with primitive values");
+
+ PrimType T = classifyPrim(VD->getType());
+ if (!visitAPValue(Val, T, VD))
+ return false;
+ return this->emitInitGlobal(T, *GlobalIndex, {});
+}
+
template <class Emitter>
bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
unsigned BuiltinID) {
diff --git a/clang/lib/AST/ByteCode/Compiler.h
b/clang/lib/AST/ByteCode/Compiler.h
index a93fdd20b712f..6777610b2bb45 100644
--- a/clang/lib/AST/ByteCode/Compiler.h
+++ b/clang/lib/AST/ByteCode/Compiler.h
@@ -251,6 +251,8 @@ class Compiler : public ConstStmtVisitor<Compiler<Emitter>,
bool>,
bool visitAttributedStmt(const AttributedStmt *S);
bool visitCXXTryStmt(const CXXTryStmt *S);
+ bool registerRedecl(const VarDecl *VD, const APValue &V);
+
protected:
bool visitStmt(const Stmt *S);
bool visitExpr(const Expr *E, bool DestroyToplevelScope) override;
diff --git a/clang/lib/AST/ByteCode/Context.cpp
b/clang/lib/AST/ByteCode/Context.cpp
index 4ca76ce3669d3..2df01d4595bd3 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -179,6 +179,13 @@ bool Context::evaluateDestruction(State &Parent, const
VarDecl *VD,
return true;
}
+void Context::registerRedecl(const VarDecl *VD, const APValue &V) {
+ Expr::EvalStatus Status;
+ Compiler<EvalEmitter> C(*this, *P, Status, Stk);
+
+ C.registerRedecl(VD, V);
+}
+
template <typename ResultT>
bool Context::evaluateStringRepr(State &Parent, const Expr *SizeExpr,
const Expr *PtrExpr, ResultT &Result) {
diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h
index 47821a3e3a7f3..1e045484f8490 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -67,6 +67,7 @@ class Context final {
/// Evaluates a toplevel initializer.
bool evaluateAsInitializer(State &Parent, const VarDecl *VD, const Expr
*Init,
APValue &Result);
+ void registerRedecl(const VarDecl *VD, const APValue &V);
/// Evaluates the destruction of a variable.
bool evaluateDestruction(State &Parent, const VarDecl *VD, APValue Value);
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.cpp
b/clang/lib/AST/ByteCode/EvalEmitter.cpp
index 34c85c7c66bdd..db929968e9b3e 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.cpp
+++ b/clang/lib/AST/ByteCode/EvalEmitter.cpp
@@ -21,6 +21,10 @@ EvalEmitter::EvalEmitter(Context &Ctx, Program &P, State
&Parent,
InterpStack &Stk)
: Ctx(Ctx), P(P), S(Parent, P, Stk, Ctx, this), EvalResult(&Ctx) {}
+EvalEmitter::EvalEmitter(Context &Ctx, Program &P, Expr::EvalStatus &Status,
+ InterpStack &Stk)
+ : Ctx(Ctx), P(P), S(Status, P, Stk, Ctx, this), EvalResult(&Ctx) {}
+
EvalEmitter::~EvalEmitter() {
for (auto &V : Locals) {
Block *B = reinterpret_cast<Block *>(V.get());
diff --git a/clang/lib/AST/ByteCode/EvalEmitter.h
b/clang/lib/AST/ByteCode/EvalEmitter.h
index f939ef4839a19..b83613cb4813d 100644
--- a/clang/lib/AST/ByteCode/EvalEmitter.h
+++ b/clang/lib/AST/ByteCode/EvalEmitter.h
@@ -59,6 +59,8 @@ class EvalEmitter : public SourceMapper {
protected:
EvalEmitter(Context &Ctx, Program &P, State &Parent, InterpStack &Stk);
+ EvalEmitter(Context &Ctx, Program &P, Expr::EvalStatus &Status,
+ InterpStack &Stk);
virtual ~EvalEmitter();
diff --git a/clang/lib/AST/ByteCode/InterpState.cpp
b/clang/lib/AST/ByteCode/InterpState.cpp
index f6338cda56e34..6aee438b4a4ca 100644
--- a/clang/lib/AST/ByteCode/InterpState.cpp
+++ b/clang/lib/AST/ByteCode/InterpState.cpp
@@ -30,6 +30,18 @@ InterpState::InterpState(const State &Parent, Program &P,
InterpStack &Stk,
EvalMode = Parent.EvalMode;
}
+InterpState::InterpState(Expr::EvalStatus &Status, Program &P, InterpStack
&Stk,
+ Context &Ctx, SourceMapper *M)
+ : State(Ctx.getASTContext(), Status), M(M), P(P), Stk(Stk), Ctx(Ctx),
+ BottomFrame(*this), Current(&BottomFrame),
+ StepsLeft(Ctx.getLangOpts().ConstexprStepLimit),
+ InfiniteSteps(StepsLeft == 0), EvalID(Ctx.getEvalID()) {
+ InConstantContext = true;
+ CheckingPotentialConstantExpression = false;
+ CheckingForUndefinedBehavior = true;
+ EvalMode = EvaluationMode::ConstantExpression;
+}
+
InterpState::InterpState(const State &Parent, Program &P, InterpStack &Stk,
Context &Ctx, const Function *Func)
: State(Ctx.getASTContext(), Parent.getEvalStatus()), M(nullptr), P(P),
diff --git a/clang/lib/AST/ByteCode/InterpState.h
b/clang/lib/AST/ByteCode/InterpState.h
index 050fa4c77cd2f..e30dd976746c3 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -44,6 +44,8 @@ class InterpState final : public State, public SourceMapper {
public:
InterpState(const State &Parent, Program &P, InterpStack &Stk, Context &Ctx,
SourceMapper *M = nullptr);
+ InterpState(Expr::EvalStatus &Status, Program &P, InterpStack &Stk,
+ Context &Ctx, SourceMapper *M = nullptr);
InterpState(const State &Parent, Program &P, InterpStack &Stk, Context &Ctx,
const Function *Func);
diff --git a/clang/lib/AST/ByteCode/Program.cpp
b/clang/lib/AST/ByteCode/Program.cpp
index d73011a8d8dc4..a3655823465b5 100644
--- a/clang/lib/AST/ByteCode/Program.cpp
+++ b/clang/lib/AST/ByteCode/Program.cpp
@@ -83,7 +83,26 @@ unsigned Program::createGlobalString(const StringLiteral *S,
const Expr *Base) {
Pointer Program::getPtrGlobal(unsigned Idx) const {
assert(Idx < Globals.size());
- return Pointer(Globals[Idx]->block());
+
+ Block *B = Globals[Idx]->block();
+
+ // Force de-serialization of a redeclaration that might initialize this
+ // global.
+ if (B->getDescriptor()->getMetadataSize() != 0 &&
+ B->getBlockDesc<GlobalInlineDescriptor>().InitState !=
+ GlobalInitState::Initialized) {
+ if (const VarDecl *VD = B->getDescriptor()->asVarDecl()) {
+ const VarDecl *MD = VD->getMostRecentDecl();
+ if (MD != VD && MD->hasInit() && !MD->getInit()->isValueDependent()) {
+ MD->evaluateValue();
+ // Note that we need to get Globals[Idx] here again since the code
block
+ // above might've actually changed what global Idx points to.
+ return Pointer(Globals[Idx]->block());
+ }
+ }
+ }
+
+ return Pointer(B);
}
UnsignedOrNone Program::getGlobal(const ValueDecl *VD) {
@@ -201,8 +220,7 @@ UnsignedOrNone Program::createGlobal(const ValueDecl *VD,
const Expr *Init,
Global *NewGlobal = Globals[*Idx];
// Note that this loop has one iteration where Redecl == VD.
- for (const Decl *Redecl : VD->redecls()) {
-
+ for (const Decl *Redecl = VD; Redecl; Redecl = Redecl->getPreviousDecl()) {
// If this redecl was registered as a dummy variable, it is now a proper
// global variable and points to the block we just created.
if (auto DummyIt = DummyVariables.find(Redecl);
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp
b/clang/lib/Serialization/ASTReaderDecl.cpp
index bd684b89e194f..ebecf1f9dc584 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -11,6 +11,7 @@
//
//===----------------------------------------------------------------------===//
+#include "../AST/ByteCode/Context.h"
#include "ASTCommon.h"
#include "ASTReaderInternals.h"
#include "clang/AST/ASTConcept.h"
@@ -1701,6 +1702,7 @@ RedeclarableResult
ASTDeclReader::VisitVarDeclImpl(VarDecl *VD) {
void ASTDeclReader::ReadVarDeclInit(VarDecl *VD) {
if (uint64_t Val = Record.readInt()) {
+ ASTContext &Context = Reader.getContext();
EvaluatedStmt *Eval = VD->ensureEvaluatedStmt();
Eval->HasConstantInitialization = (Val & 2) != 0;
Eval->HasConstantDestruction = (Val & 4) != 0;
@@ -1710,7 +1712,15 @@ void ASTDeclReader::ReadVarDeclInit(VarDecl *VD) {
if (Eval->WasEvaluated) {
Eval->Evaluated = Record.readAPValue();
if (Eval->Evaluated.needsCleanup())
- Reader.getContext().addDestruction(&Eval->Evaluated);
+ Context.addDestruction(&Eval->Evaluated);
+
+ // The bytecode interpreter has its own internal representation of global
+ // variables. Notify it that we just deserialized one and what its value
+ // is. This is important because this declaration might initialize a
+ // previously declared global (e.g. because that one is extern).
+ if (Context.getLangOpts().EnableNewConstInterp &&
+ !VD->getType().isNull() && VD->getPreviousDecl() != nullptr)
+ Context.getInterpContext().registerRedecl(VD, Eval->Evaluated);
}
// Store the offset of the initializer. Don't deserialize it yet: it might
diff --git a/clang/test/AST/ByteCode/Inputs/module.modulemap
b/clang/test/AST/ByteCode/Inputs/module.modulemap
new file mode 100644
index 0000000000000..6ba8d2e8a184b
--- /dev/null
+++ b/clang/test/AST/ByteCode/Inputs/module.modulemap
@@ -0,0 +1 @@
+module redecl1 { header "redecl1.h" }
diff --git a/clang/test/AST/ByteCode/Inputs/redecl1.h
b/clang/test/AST/ByteCode/Inputs/redecl1.h
new file mode 100644
index 0000000000000..6e02cab6b3133
--- /dev/null
+++ b/clang/test/AST/ByteCode/Inputs/redecl1.h
@@ -0,0 +1,6 @@
+
+extern const int variable = 120;
+
+
+struct S { int a; };
+extern constexpr S vars = {12};
diff --git a/clang/test/AST/ByteCode/module-redecl1.cpp
b/clang/test/AST/ByteCode/module-redecl1.cpp
new file mode 100644
index 0000000000000..90961eeae4aff
--- /dev/null
+++ b/clang/test/AST/ByteCode/module-redecl1.cpp
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -cc1 -xobjective-c++ %s -fmodules -fimplicit-module-maps
-fmodules-cache-path=Inputs/ -I %S/Inputs -verify -std=c++11
+// RUN: %clang_cc1 -cc1 -xobjective-c++ %s -fmodules -fimplicit-module-maps
-fmodules-cache-path=Inputs/ -I %S/Inputs -verify -std=c++11
-fexperimental-new-constant-interpreter
+
+// expected-no-diagnostics
+
+struct S { int a; };
+
+extern const int variable;
+extern const S vars;
+
+constexpr int test() { return variable; }
+constexpr int test2() { return vars.a; }
+
+struct C {
+ static const int variable;
+ static const S vars;
+};
+
+
+
+/// The module contains a definition for 'variable', so the function call below
+/// should work and return the correct value.
+@import redecl1;
+static_assert(test() == 120, "");
+static_assert(test2() == 12, "");
diff --git a/clang/test/Modules/pr102360.cppm b/clang/test/Modules/pr102360.cppm
index e0dab1a031801..31109d6959512 100644
--- a/clang/test/Modules/pr102360.cppm
+++ b/clang/test/Modules/pr102360.cppm
@@ -10,6 +10,14 @@
// RUN: %clang_cc1 -std=c++20 %t/d.cpp -fsyntax-only -verify \
// RUN: -fprebuilt-module-path=%t
+// RUN: %clang_cc1 -std=c++20 %t/a.cppm -emit-module-interface -o %t/a.pcm
-fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++20 %t/b.cppm -emit-module-interface -o %t/b.pcm \
+// RUN: -fprebuilt-module-path=%t -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++20 %t/c.cppm -emit-module-interface -o %t/c.pcm \
+// RUN: -fprebuilt-module-path=%t -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -std=c++20 %t/d.cpp -fsyntax-only -verify \
+// RUN: -fprebuilt-module-path=%t -fexperimental-new-constant-interpreter
+
//--- a.cppm
export module a;
diff --git a/clang/test/Modules/redecl-add-after-load.cpp
b/clang/test/Modules/redecl-add-after-load.cpp
index f888460f297e7..101be0b1fde4e 100644
--- a/clang/test/Modules/redecl-add-after-load.cpp
+++ b/clang/test/Modules/redecl-add-after-load.cpp
@@ -2,6 +2,10 @@
// RUN: %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps
-fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -verify
-std=c++11
// RUN: %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps
-fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -verify
-std=c++11 -DIMPORT_DECLS
+// RUN: %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps
-fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -verify
-std=c++11 -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -x objective-c++ -fmodules -fimplicit-module-maps
-fno-modules-error-recovery -fmodules-cache-path=%t -I %S/Inputs %s -verify
-std=c++11 -DIMPORT_DECLS -fexperimental-new-constant-interpreter
+
+
// expected-no-diagnostics
#ifdef IMPORT_DECLS
``````````
</details>
https://github.com/llvm/llvm-project/pull/198062
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits