Skip to content

Commit 0977736

Browse files
committed
Fix static const member address reference
1 parent e6f2fbb commit 0977736

File tree

6 files changed

+136
-0
lines changed

6 files changed

+136
-0
lines changed

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3111,6 +3111,12 @@ static LValue EmitGlobalVarDeclLValue(CodeGenFunction &CGF,
31113111
return CGF.MakeAddrLValue(Addr, T, AlignmentSource::Decl);
31123112
}
31133113

3114+
// In incremental mode, ensure static data members with in-class initializers
3115+
// are materialized before use. This handles lvalue references that bypass
3116+
// constant evaluation (e.g., passing by reference to a function).
3117+
if (CGF.CGM.getLangOpts().IncrementalExtensions && VD->isStaticDataMember())
3118+
VD = CGF.CGM.materializeStaticDataMember(VD);
3119+
31143120
llvm::Value *V = CGF.CGM.GetAddrOfGlobalVar(VD);
31153121

31163122
if (VD->getTLSKind() != VarDecl::TLS_None)

clang/lib/CodeGen/CGExprConstant.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,6 +2243,10 @@ ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) {
22432243
}
22442244

22452245
if (const auto *VD = dyn_cast<VarDecl>(D)) {
2246+
// In incremental mode, ensure static data members with in-class
2247+
// initializers are materialized on first odr-use (e.g., &Foo::member).
2248+
if (CGM.getLangOpts().IncrementalExtensions && VD->isStaticDataMember())
2249+
VD = CGM.materializeStaticDataMember(VD);
22462250
// We can never refer to a variable with local storage.
22472251
if (!VD->hasLocalStorage()) {
22482252
if (VD->isFileVarDecl() || VD->hasExternalStorage())

clang/lib/CodeGen/CodeGenModule.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6302,6 +6302,51 @@ CodeGenModule::getLLVMLinkageVarDefinition(const VarDecl *VD) {
63026302
return getLLVMLinkageForDeclarator(VD, Linkage);
63036303
}
63046304

6305+
const VarDecl *CodeGenModule::materializeStaticDataMember(const VarDecl *VD) {
6306+
if (!VD)
6307+
return VD;
6308+
6309+
const VarDecl *DefinitionVD = VD->getDefinition();
6310+
if (!DefinitionVD)
6311+
DefinitionVD = VD;
6312+
6313+
// Only in-class static data members need materialization. Out-of-line
6314+
// definitions are emitted normally.
6315+
if (!DefinitionVD->isStaticDataMember() || DefinitionVD->isOutOfLine())
6316+
return DefinitionVD;
6317+
6318+
const VarDecl *InitVD = nullptr;
6319+
// Members without in-class initializers rely on out-of-class definitions.
6320+
if (!DefinitionVD->getAnyInitializer(InitVD) || !InitVD)
6321+
return DefinitionVD;
6322+
6323+
auto NeedsMaterialization = [](llvm::GlobalValue *GV) {
6324+
if (!GV)
6325+
return true;
6326+
if (GV->isDeclaration())
6327+
return true;
6328+
// Inline static members may be emitted as available_externally; upgrade
6329+
// to linkonce_odr so the JIT can resolve them.
6330+
if (auto *GVVar = llvm::dyn_cast<llvm::GlobalVariable>(GV))
6331+
return GVVar->hasAvailableExternallyLinkage();
6332+
return false;
6333+
};
6334+
6335+
GlobalDecl GD(InitVD);
6336+
StringRef MangledName = getMangledName(GD);
6337+
llvm::GlobalValue *GV = GetGlobalValue(MangledName);
6338+
6339+
if (NeedsMaterialization(GV)) {
6340+
EmitGlobalVarDefinition(InitVD, /*IsTentative=*/false);
6341+
GV = GetGlobalValue(MangledName);
6342+
if (auto *GVVar = llvm::dyn_cast_or_null<llvm::GlobalVariable>(GV))
6343+
if (GVVar->hasAvailableExternallyLinkage())
6344+
GVVar->setLinkage(llvm::GlobalValue::LinkOnceODRLinkage);
6345+
}
6346+
6347+
return InitVD;
6348+
}
6349+
63056350
/// Replace the uses of a function that was declared with a non-proto type.
63066351
/// We want to silently drop extra arguments from call sites
63076352
static void replaceUsesOfNonProtoConstant(llvm::Constant *old,

clang/lib/CodeGen/CodeGenModule.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1839,6 +1839,14 @@ class CodeGenModule : public CodeGenTypeCache {
18391839
return TrapReasonBuilder(&getDiags(), DiagID, TR);
18401840
}
18411841

1842+
/// Materialize a static data member with an in-class initializer on demand.
1843+
///
1844+
/// In incremental contexts (e.g. clang-repl) a class can be defined in an
1845+
/// earlier partial translation unit, while the first odr-use (such as taking
1846+
/// the address) happens later. Ensure we emit a usable definition so the JIT
1847+
/// can resolve the symbol.
1848+
const VarDecl *materializeStaticDataMember(const VarDecl *VD);
1849+
18421850
private:
18431851
bool shouldDropDLLAttribute(const Decl *D, const llvm::GlobalValue *GV) const;
18441852

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: cat %s | clang-repl | FileCheck %s
2+
// Tests for static const member materialization in clang-repl.
3+
// See https://github.com/llvm/llvm-project/issues/146956
4+
5+
extern "C" int printf(const char*, ...);
6+
7+
struct Foo { static int const bar { 5 }; static int const baz { 10 }; };
8+
9+
// Test 1: Taking address of static const member
10+
int const * p = &Foo::bar;
11+
printf("Address test: %d\n", *p);
12+
// CHECK: Address test: 5
13+
14+
// Test 2: Multiple static const members in same class
15+
int const * q = &Foo::baz;
16+
printf("Second member test: %d\n", *q);
17+
// CHECK: Second member test: 10
18+
19+
// Test 3: static constexpr member (variant of in-class init)
20+
struct Qux { static constexpr int val = 99; };
21+
int const *p3 = &Qux::val;
22+
printf("Constexpr test: %d\n", *p3);
23+
// CHECK: Constexpr test: 99
24+
25+
// Test 4: Passing static const member by reference (exercises CGExpr.cpp path)
26+
// NOTE: Uses a separate struct to ensure this is the first odr-use of RefOnly::val
27+
struct RefOnly { static int const val { 77 }; };
28+
void useRef(int const &x) { printf("Ref test: %d\n", x); }
29+
useRef(RefOnly::val);
30+
// CHECK: Ref test: 77
31+
32+
// ============================================================================
33+
// Negative tests - cases that should NOT trigger materialization
34+
// ============================================================================
35+
36+
// Test 5: Out-of-class definition (no need to materialize - already defined)
37+
struct OutOfLine { static const int value; };
38+
const int OutOfLine::value = 23;
39+
int const *p5 = &OutOfLine::value;
40+
printf("Out-of-line test: %d\n", *p5);
41+
// CHECK: Out-of-line test: 23
42+
43+
// Test 6: Non-const static member (normal code gen path)
44+
struct NonConst { static int value; };
45+
int NonConst::value = 42;
46+
int *p6 = &NonConst::value;
47+
printf("Non-const test: %d\n", *p6);
48+
// CHECK: Non-const test: 42
49+
50+
// ============================================================================
51+
// Edge case tests
52+
// ============================================================================
53+
54+
// Test 7: Repeated address-of reuses same definition
55+
int const *p7 = &Foo::bar;
56+
printf("Reuse test: %d\n", (*p == *p7) ? 1 : 0);
57+
// CHECK: Reuse test: 1
58+
59+
%quit

clang/unittests/Interpreter/InterpreterTest.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,4 +443,18 @@ TEST_F(InterpreterTest, TranslationUnit_CanonicalDecl) {
443443
sema.getASTContext().getTranslationUnitDecl()->getCanonicalDecl());
444444
}
445445

446+
TEST_F(InterpreterTest, StaticConstMemberAddress) {
447+
std::unique_ptr<Interpreter> Interp = createInterpreter();
448+
449+
llvm::cantFail(
450+
Interp->ParseAndExecute("struct Foo { static int const bar { 5 }; };"));
451+
452+
Value V;
453+
llvm::cantFail(Interp->ParseAndExecute("int const * p = &Foo::bar; *p", &V));
454+
455+
ASSERT_TRUE(V.isValid());
456+
ASSERT_TRUE(V.hasValue());
457+
EXPECT_EQ(V.getInt(), 5);
458+
}
459+
446460
} // end anonymous namespace

0 commit comments

Comments
 (0)