https://github.com/pfeodrippe updated https://github.com/llvm/llvm-project/pull/169251
>From 8eaade199ddea14510e400ba296b530e50d241ba Mon Sep 17 00:00:00 2001 From: Paulo Feodrippe <[email protected]> Date: Sun, 23 Nov 2025 21:35:08 -0500 Subject: [PATCH] Fix static const member address reference --- clang/lib/CodeGen/CGExpr.cpp | 8 ++ clang/lib/CodeGen/CGExprConstant.cpp | 6 ++ clang/lib/CodeGen/CodeGenModule.cpp | 57 +++++++++++++ clang/lib/CodeGen/CodeGenModule.h | 8 ++ .../test/Interpreter/static-const-member.cpp | 80 +++++++++++++++++++ .../unittests/Interpreter/InterpreterTest.cpp | 14 ++++ 6 files changed, 173 insertions(+) create mode 100644 clang/test/Interpreter/static-const-member.cpp diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index b33772919b8c8..10be33a2d1ba7 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -3111,6 +3111,14 @@ static LValue EmitGlobalVarDeclLValue(CodeGenFunction &CGF, return CGF.MakeAddrLValue(Addr, T, AlignmentSource::Decl); } + // In incremental mode, ensure static data members with in-class initializers + // are materialized before we request their address. This call is largely + // redundant with the CGExprConstant.cpp path (which handles most address-of + // operations), but provides defense-in-depth for lvalue references and direct + // uses that might bypass constant evaluation. + if (CGF.CGM.getLangOpts().IncrementalExtensions && VD->isStaticDataMember()) + VD = CGF.CGM.materializeStaticDataMember(VD); + llvm::Value *V = CGF.CGM.GetAddrOfGlobalVar(VD); if (VD->getTLSKind() != VarDecl::TLS_None) diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 6407afc3d9447..f75129b58e457 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -2243,6 +2243,12 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) { } if (const auto *VD = dyn_cast<VarDecl>(D)) { + // In incremental mode, ensure static data members with in-class + // initializers are materialized on first odr-use (e.g. taking address + // with &Foo::member). This is the primary path exercised by + // clang/test/Interpreter/static-const-member.cpp. + if (CGM.getLangOpts().IncrementalExtensions && VD->isStaticDataMember()) + VD = CGM.materializeStaticDataMember(VD); // We can never refer to a variable with local storage. if (!VD->hasLocalStorage()) { if (VD->isFileVarDecl() || VD->hasExternalStorage()) diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 645b78a599f89..7950b05db41c3 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -6302,6 +6302,63 @@ CodeGenModule::getLLVMLinkageVarDefinition(const VarDecl *VD) { return getLLVMLinkageForDeclarator(VD, Linkage); } +const VarDecl *CodeGenModule::materializeStaticDataMember(const VarDecl *VD) { + if (!VD) + return VD; + + const VarDecl *DefinitionVD = VD->getDefinition(); + if (!DefinitionVD) + // If we only ever saw the declaration (e.g. forward-declared class in an + // earlier partial translation unit), continue with whatever we have so the + // later guards can decide whether emission is necessary. + DefinitionVD = VD; + + // Out-of-line or non-static members have already been emitted (see Test 5/7 + // in clang/test/Interpreter/static-const-member.cpp) so there's nothing for + // the incremental runtime to synthesize here. + if (!DefinitionVD->isStaticDataMember() || DefinitionVD->isOutOfLine()) + return DefinitionVD; + + const VarDecl *InitVD = nullptr; + // Members without in-class initializers rely on the user's out-of-class + // definition (Test 8). There's no constant we can materialize. + if (!DefinitionVD->getAnyInitializer(InitVD) || !InitVD) + return DefinitionVD; + + auto NeedsMaterialization = [](llvm::GlobalValue *GV) { + if (!GV) + return true; + // Defensive check: if the JIT/incremental pipeline created a + // declaration-only global before we reach materialization, we need to emit + // the definition. (Currently untested; would require manually injecting a + // forward declaration.) + if (GV->isDeclaration()) + return true; + // Variables emitted as available_externally (e.g. inline static members + // with initializers) must be upgraded to linkonce_odr so the JIT can + // materialize them. Future-proof: in practice GV is null on first use, so + // we never reach this.) + if (auto *GVVar = llvm::dyn_cast<llvm::GlobalVariable>(GV)) + return GVVar->hasAvailableExternallyLinkage(); + return false; + }; + + GlobalDecl GD(InitVD); + StringRef MangledName = getMangledName(GD); + llvm::GlobalValue *GV = GetGlobalValue(MangledName); + // If the interpreter hasn't forced emission yet (Test 9), do it now and + // ensure we end up with linkonce_odr linkage so the JIT can materialize it. + if (NeedsMaterialization(GV)) { + EmitGlobalVarDefinition(InitVD, /*IsTentative=*/false); + GV = GetGlobalValue(MangledName); + if (auto *GVVar = llvm::dyn_cast_or_null<llvm::GlobalVariable>(GV)) + if (GVVar->hasAvailableExternallyLinkage()) + GVVar->setLinkage(llvm::GlobalValue::LinkOnceODRLinkage); + } + + return InitVD; +} + /// Replace the uses of a function that was declared with a non-proto type. /// We want to silently drop extra arguments from call sites static void replaceUsesOfNonProtoConstant(llvm::Constant *old, diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index a253bcda2d06c..1ea582c03a8c6 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1839,6 +1839,14 @@ class CodeGenModule : public CodeGenTypeCache { return TrapReasonBuilder(&getDiags(), DiagID, TR); } + /// Materialize a static data member with an in-class initializer on demand. + /// + /// In incremental contexts (e.g. clang-repl) a class can be defined in an + /// earlier partial translation unit, while the first odr-use (such as taking + /// the address) happens later. Ensure we emit a usable definition so the JIT + /// can resolve the symbol. + const VarDecl *materializeStaticDataMember(const VarDecl *VD); + private: bool shouldDropDLLAttribute(const Decl *D, const llvm::GlobalValue *GV) const; diff --git a/clang/test/Interpreter/static-const-member.cpp b/clang/test/Interpreter/static-const-member.cpp new file mode 100644 index 0000000000000..0d5553fe2448d --- /dev/null +++ b/clang/test/Interpreter/static-const-member.cpp @@ -0,0 +1,80 @@ +// RUN: cat %s | clang-repl | FileCheck %s + +extern "C" int printf(const char*, ...); + +struct Foo { static int const bar { 5 }; static int const baz { 10 }; }; + +// Test 1: Taking the address of a static const member with in-class initializer +// should materialize the symbol and allow dereferencing +int const * p = &Foo::bar; +printf("Address test: %d\n", *p); +// Guard: Exercises the !isStaticDataMember()/isOutOfLine() early-return's +// positive path (in-class static member must materialize). +// CHECK: Address test: 5 + +// Test 2: Materialize and use multiple static const members +int const * q = &Foo::baz; +printf("Second member test: %d\n", *q); +// Guard: Same positive-path guard as Test 1, but with another member in the +// same class to ensure successive members still materialize. +// CHECK: Second member test: 10 + +// Test 3: Verify the address is stable and consistent +int const * p2 = &Foo::bar; +printf("Address stability: %d\n", (*p == *p2) ? 1 : 0); +// Guard: Still the !isStaticDataMember()/isOutOfLine() path, but reuses the +// same member before it gets emitted to ensure no spurious early exit. +// CHECK: Address stability: 1 + +// Test 4: constexpr static members +struct Qux { static constexpr int val = 99; }; +int const *p3 = &Qux::val; +printf("Constexpr test: %d\n", *p3); +// Guard: Positive path again, showing constexpr inline members also bypass the +// !isStaticDataMember()/isOutOfLine() early return and reach materialization. +// CHECK: Constexpr test: 99 + +// Test 5: Non-const static member with out-of-class definition +struct NonConst { static int value; }; +int NonConst::value = 42; +int *p4 = &NonConst::value; +printf("Non-const test: %d\n", *p4); +// Guard: Triggers the getAnyInitializer/InitVD early return (no in-class +// initializer), so materialization is skipped and we use the out-of-class def. +// CHECK: Non-const test: 42 + +// Test 6: Redeclaration before definition to ensure canonical definition is used +struct ForwardDecl; +struct ForwardDecl { static const int val { 17 }; }; +int const *p5 = &ForwardDecl::val; +printf("Forward-decl test: %d\n", *p5); +// Guard: Relies on getDefinition() finding the canonical declaration even +// though we first saw a forward declaration. +// CHECK: Forward-decl test: 17 + +// Test 7: Out-of-class const definition should not be re-materialized +struct OutOfLine { static const int value; }; +const int OutOfLine::value = 23; +int const *p6 = &OutOfLine::value; +printf("Out-of-line const test: %d\n", *p6); +// Guard: Hits the isOutOfLine() half of the early return, proving we don't try +// to re-materialize members that already have an out-of-class definition. +// CHECK: Out-of-line const test: 23 + +// Test 8: Static member without in-class initializer relies solely on the out-of-class definition +struct NoInClassInit { static const int value; }; +const int NoInClassInit::value = 64; +int const *p7 = &NoInClassInit::value; +printf("No in-class init test: %d\n", *p7); +// Guard: Another getAnyInitializer/InitVD early return (const variant) showing +// we skip members that lack in-class initializers. +// CHECK: No in-class init test: 64 + +// Test 9: Repeated materialization requests reuse the emitted definition +int const *p8 = &Foo::bar; +printf("Repeat materialization test: %d\n", *p8); +// Guard: NeedsMaterialization() emits or reuses the same global instead of +// creating duplicates for repeated odr-uses. +// CHECK: Repeat materialization test: 5 + +%quit diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp index 9ff9092524d21..341e61563e6bf 100644 --- a/clang/unittests/Interpreter/InterpreterTest.cpp +++ b/clang/unittests/Interpreter/InterpreterTest.cpp @@ -443,4 +443,18 @@ TEST_F(InterpreterTest, TranslationUnit_CanonicalDecl) { sema.getASTContext().getTranslationUnitDecl()->getCanonicalDecl()); } +TEST_F(InterpreterTest, StaticConstMemberAddress) { + std::unique_ptr<Interpreter> Interp = createInterpreter(); + + llvm::cantFail( + Interp->ParseAndExecute("struct Foo { static int const bar { 5 }; };")); + + Value V; + llvm::cantFail(Interp->ParseAndExecute("int const * p = &Foo::bar; *p", &V)); + + ASSERT_TRUE(V.isValid()); + ASSERT_TRUE(V.hasValue()); + EXPECT_EQ(V.getInt(), 5); +} + } // end anonymous namespace _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
