https://github.com/pfeodrippe updated 
https://github.com/llvm/llvm-project/pull/169251

>From b45a156af7ba7c10e997435ef1ff0b5ef9a950fa 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

Reply via email to