diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index e6e4947882544..ee2f55be71713 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -3070,7 +3070,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 b44dd9ecc717e..87f47adda1203 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(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 f6f7f22a09004..fa5db085edccd 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -6228,6 +6228,35 @@ 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; + + StringRef MangledName = getMangledName(InitVD); + auto needsEmission = [](llvm::GlobalValue *GV) { + if (!GV || GV->isDeclaration()) + return true; + if (auto *GVVar = llvm::dyn_cast(GV)) + return GVVar->hasAvailableExternallyLinkage(); + return false; + }; + + llvm::GlobalValue *GV = GetGlobalValue(MangledName); + if (needsEmission(GV)) { + EmitGlobalVarDefinition(InitVD, /*IsTentative=*/false); + GV = GetGlobalValue(MangledName); + if (auto *GVVar = llvm::dyn_cast_or_null(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 3971b296b3f80..f71a3da6157a9 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -1832,6 +1832,13 @@ class CodeGenModule : public CodeGenTypeCache { return TrapReasonBuilder(&getDiags(), DiagID, TR); } + void EmitGlobalVarDefinition(const VarDecl *D, bool IsTentative = false); + +public: + /// Ensure a static data member with an in-class initializer is materialized + /// and return the declaration that owns the emitted definition. + const VarDecl *materializeStaticDataMember(const VarDecl *VD); + private: bool shouldDropDLLAttribute(const Decl *D, const llvm::GlobalValue *GV) const; @@ -1882,7 +1889,6 @@ class CodeGenModule : public CodeGenTypeCache { void EmitGlobalFunctionDefinition(GlobalDecl GD, llvm::GlobalValue *GV); void EmitMultiVersionFunctionDefinition(GlobalDecl GD, llvm::GlobalValue *GV); - void EmitGlobalVarDefinition(const VarDecl *D, bool IsTentative = false); void EmitAliasDefinition(GlobalDecl GD); void emitIFuncDefinition(GlobalDecl GD); void emitCPUDispatchDefinition(GlobalDecl GD); diff --git a/clang/test/Interpreter/static-const-member.cpp b/clang/test/Interpreter/static-const-member.cpp new file mode 100644 index 0000000000000..1577704e96365 --- /dev/null +++ b/clang/test/Interpreter/static-const-member.cpp @@ -0,0 +1,37 @@ +// Test for static const member address issue +// see https://github.com/llvm/llvm-project/issues/146956 + +// RUN: cat %s | clang-repl | FileCheck %s + +extern "C" int printf(const char*, ...); + +// Test 1: Static const member with in-class initializer +struct Foo { + static int const bar { 5 }; +}; + +// Taking the address should work +int const * p = &Foo::bar; +printf("Address test: %d\n", *p); +// CHECK: Address test: 5 + +// Test 2: Direct value access (this should already work) +int const i = Foo::bar; +printf("Value test: %d\n", i); +// CHECK-NEXT: Value test: 5 + +// Test 3: Multiple accesses to the same static member +int const * p2 = &Foo::bar; +printf("Second address test: %d\n", *p2); +// CHECK-NEXT: Second address test: 5 + +// Test 4: Different types +struct Bar { + static double const pi { 3.14159 }; +}; + +double const * pi_ptr = &Bar::pi; +printf("Pi test: %.2f\n", *pi_ptr); +// CHECK-NEXT: Pi test: 3.14 + +%quit diff --git a/clang/unittests/Interpreter/InterpreterTest.cpp b/clang/unittests/Interpreter/InterpreterTest.cpp index 9ff9092524d21..54466934ac413 100644 --- a/clang/unittests/Interpreter/InterpreterTest.cpp +++ b/clang/unittests/Interpreter/InterpreterTest.cpp @@ -443,4 +443,19 @@ TEST_F(InterpreterTest, TranslationUnit_CanonicalDecl) { sema.getASTContext().getTranslationUnitDecl()->getCanonicalDecl()); } +TEST_F(InterpreterTest, StaticConstMemberAddress) { + std::unique_ptr 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