[clang] [Clang] Fix static const member address reference (PR #169251)
Paulo Rafael Feodrippe via cfe-commits
cfe-commits at lists.llvm.org
Sun Nov 23 15:52:25 PST 2025
https://github.com/pfeodrippe updated https://github.com/llvm/llvm-project/pull/169251
>From 4cabc02917447df5064a2c6ef9ae2cf49043338a Mon Sep 17 00:00:00 2001
From: Paulo Feodrippe <pfeodrippe at gmail.com>
Date: Sun, 23 Nov 2025 17:31:37 -0500
Subject: [PATCH] Fix static const member address reference
---
clang/lib/CodeGen/CGExpr.cpp | 8 ++++-
clang/lib/CodeGen/CGExprConstant.cpp | 1 +
clang/lib/CodeGen/CodeGenModule.cpp | 35 ++++++++++++++++++
clang/lib/CodeGen/CodeGenModule.h | 13 +++++++
.../test/Interpreter/static-const-member.cpp | 36 +++++++++++++++++++
.../unittests/Interpreter/InterpreterTest.cpp | 14 ++++++++
6 files changed, 106 insertions(+), 1 deletion(-)
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..0faa312185a1a 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3111,7 +3111,13 @@ static LValue EmitGlobalVarDeclLValue(CodeGenFunction &CGF,
return CGF.MakeAddrLValue(Addr, T, AlignmentSource::Decl);
}
- llvm::Value *V = CGF.CGM.GetAddrOfGlobalVar(VD);
+ // For static data members with in-class initializers, ensure we emit a
+ // definition if one doesn't exist yet. This is necessary for interpreters
+ // where the member's address might be taken after the class definition,
+ // requiring the symbol to be materialized on demand.
+ const VarDecl *DefinitionVD = CGF.CGM.materializeStaticDataMember(VD);
+
+ llvm::Value *V = CGF.CGM.GetAddrOfGlobalVar(DefinitionVD);
if (VD->getTLSKind() != VarDecl::TLS_None)
V = CGF.Builder.CreateThreadLocalAddress(V);
diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp
index 6407afc3d9447..bb297c96566a1 100644
--- a/clang/lib/CodeGen/CGExprConstant.cpp
+++ b/clang/lib/CodeGen/CGExprConstant.cpp
@@ -2243,6 +2243,7 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) {
}
if (const auto *VD = dyn_cast<VarDecl>(D)) {
+ 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..5a1b80a383b04 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -6302,6 +6302,41 @@ CodeGenModule::getLLVMLinkageVarDefinition(const VarDecl *VD) {
return getLLVMLinkageForDeclarator(VD, Linkage);
}
+const VarDecl *CodeGenModule::materializeStaticDataMember(const VarDecl *VD) {
+ if (!VD->isStaticDataMember())
+ return VD;
+
+ const VarDecl *InitVD = nullptr;
+ if (!VD->getAnyInitializer(InitVD) || !InitVD)
+ return VD;
+
+ // Only materialize in-class initializers (constexpr or const integral types)
+ // Don't materialize out-of-class definitions
+ if (InitVD->isOutOfLine())
+ return InitVD;
+
+ // This is necessary for interpreters where the member's address might be
+ // taken after the class definition, requiring the symbol to be materialized
+ // on demand.
+ if (!getLangOpts().IncrementalExtensions)
+ return VD;
+
+ // If the variable is not defined or has available_externally linkage,
+ // we need to emit the definition and ensure it has linkonce_odr linkage.
+ auto *GV = dyn_cast<llvm::GlobalVariable>(
+ GetAddrOfGlobalVar(InitVD)->stripPointerCasts());
+ if (!GV || GV->isDeclaration() || GV->hasAvailableExternallyLinkage()) {
+ EmitGlobalVarDefinition(InitVD, /*IsTentative=*/false);
+
+ GV = dyn_cast<llvm::GlobalVariable>(
+ GetAddrOfGlobalVar(InitVD)->stripPointerCasts());
+ if (GV && GV->hasAvailableExternallyLinkage())
+ GV->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..8cdd7b9f763dd 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -1839,6 +1839,19 @@ class CodeGenModule : public CodeGenTypeCache {
return TrapReasonBuilder(&getDiags(), DiagID, TR);
}
+public:
+ /// Ensure a static data member with an in-class initializer is materialized.
+ ///
+ /// For static data members with in-class initializers, this ensures a
+ /// definition is emitted if one doesn't exist yet. This is necessary for
+ /// interpreters where the member's address might be taken after the class
+ /// definition, requiring the symbol to be materialized on demand.
+ ///
+ /// \param VD The variable declaration to materialize.
+ /// \returns The declaration that owns the emitted definition, or the
+ /// original declaration if no materialization is needed.
+ 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..f2dc24a1045db
--- /dev/null
+++ b/clang/test/Interpreter/static-const-member.cpp
@@ -0,0 +1,36 @@
+// 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);
+// 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);
+// 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);
+// 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);
+// 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);
+// CHECK: Non-const test: 42
+
+%quit
diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp
index 9ff9092524d21..176f8d427d7d0 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();
+
+ // Test taking the address of a static const member with in-class initializer
+ llvm::cantFail(
+ Interp->ParseAndExecute("struct Foo { static int const bar { 5 }; };"));
+
+ Value V;
+ llvm::cantFail(Interp->ParseAndExecute("int const * p = &Foo::bar; *p", &V));
+
+ // The value should be 5
+ EXPECT_EQ(V.getInt(), 5);
+}
+
} // end anonymous namespace
More information about the cfe-commits
mailing list