Skip to content

Commit 8eaade1

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

File tree

6 files changed

+173
-0
lines changed

6 files changed

+173
-0
lines changed

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3111,6 +3111,14 @@ 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 we request their address. This call is largely
3116+
// redundant with the CGExprConstant.cpp path (which handles most address-of
3117+
// operations), but provides defense-in-depth for lvalue references and direct
3118+
// uses that might bypass constant evaluation.
3119+
if (CGF.CGM.getLangOpts().IncrementalExtensions && VD->isStaticDataMember())
3120+
VD = CGF.CGM.materializeStaticDataMember(VD);
3121+
31143122
llvm::Value *V = CGF.CGM.GetAddrOfGlobalVar(VD);
31153123

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

clang/lib/CodeGen/CGExprConstant.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,6 +2243,12 @@ 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. taking address
2248+
// with &Foo::member). This is the primary path exercised by
2249+
// clang/test/Interpreter/static-const-member.cpp.
2250+
if (CGM.getLangOpts().IncrementalExtensions && VD->isStaticDataMember())
2251+
VD = CGM.materializeStaticDataMember(VD);
22462252
// We can never refer to a variable with local storage.
22472253
if (!VD->hasLocalStorage()) {
22482254
if (VD->isFileVarDecl() || VD->hasExternalStorage())

clang/lib/CodeGen/CodeGenModule.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6302,6 +6302,63 @@ 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+
// If we only ever saw the declaration (e.g. forward-declared class in an
6312+
// earlier partial translation unit), continue with whatever we have so the
6313+
// later guards can decide whether emission is necessary.
6314+
DefinitionVD = VD;
6315+
6316+
// Out-of-line or non-static members have already been emitted (see Test 5/7
6317+
// in clang/test/Interpreter/static-const-member.cpp) so there's nothing for
6318+
// the incremental runtime to synthesize here.
6319+
if (!DefinitionVD->isStaticDataMember() || DefinitionVD->isOutOfLine())
6320+
return DefinitionVD;
6321+
6322+
const VarDecl *InitVD = nullptr;
6323+
// Members without in-class initializers rely on the user's out-of-class
6324+
// definition (Test 8). There's no constant we can materialize.
6325+
if (!DefinitionVD->getAnyInitializer(InitVD) || !InitVD)
6326+
return DefinitionVD;
6327+
6328+
auto NeedsMaterialization = [](llvm::GlobalValue *GV) {
6329+
if (!GV)
6330+
return true;
6331+
// Defensive check: if the JIT/incremental pipeline created a
6332+
// declaration-only global before we reach materialization, we need to emit
6333+
// the definition. (Currently untested; would require manually injecting a
6334+
// forward declaration.)
6335+
if (GV->isDeclaration())
6336+
return true;
6337+
// Variables emitted as available_externally (e.g. inline static members
6338+
// with initializers) must be upgraded to linkonce_odr so the JIT can
6339+
// materialize them. Future-proof: in practice GV is null on first use, so
6340+
// we never reach this.)
6341+
if (auto *GVVar = llvm::dyn_cast<llvm::GlobalVariable>(GV))
6342+
return GVVar->hasAvailableExternallyLinkage();
6343+
return false;
6344+
};
6345+
6346+
GlobalDecl GD(InitVD);
6347+
StringRef MangledName = getMangledName(GD);
6348+
llvm::GlobalValue *GV = GetGlobalValue(MangledName);
6349+
// If the interpreter hasn't forced emission yet (Test 9), do it now and
6350+
// ensure we end up with linkonce_odr linkage so the JIT can materialize it.
6351+
if (NeedsMaterialization(GV)) {
6352+
EmitGlobalVarDefinition(InitVD, /*IsTentative=*/false);
6353+
GV = GetGlobalValue(MangledName);
6354+
if (auto *GVVar = llvm::dyn_cast_or_null<llvm::GlobalVariable>(GV))
6355+
if (GVVar->hasAvailableExternallyLinkage())
6356+
GVVar->setLinkage(llvm::GlobalValue::LinkOnceODRLinkage);
6357+
}
6358+
6359+
return InitVD;
6360+
}
6361+
63056362
/// Replace the uses of a function that was declared with a non-proto type.
63066363
/// We want to silently drop extra arguments from call sites
63076364
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: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: cat %s | clang-repl | FileCheck %s
2+
3+
extern "C" int printf(const char*, ...);
4+
5+
struct Foo { static int const bar { 5 }; static int const baz { 10 }; };
6+
7+
// Test 1: Taking the address of a static const member with in-class initializer
8+
// should materialize the symbol and allow dereferencing
9+
int const * p = &Foo::bar;
10+
printf("Address test: %d\n", *p);
11+
// Guard: Exercises the !isStaticDataMember()/isOutOfLine() early-return's
12+
// positive path (in-class static member must materialize).
13+
// CHECK: Address test: 5
14+
15+
// Test 2: Materialize and use multiple static const members
16+
int const * q = &Foo::baz;
17+
printf("Second member test: %d\n", *q);
18+
// Guard: Same positive-path guard as Test 1, but with another member in the
19+
// same class to ensure successive members still materialize.
20+
// CHECK: Second member test: 10
21+
22+
// Test 3: Verify the address is stable and consistent
23+
int const * p2 = &Foo::bar;
24+
printf("Address stability: %d\n", (*p == *p2) ? 1 : 0);
25+
// Guard: Still the !isStaticDataMember()/isOutOfLine() path, but reuses the
26+
// same member before it gets emitted to ensure no spurious early exit.
27+
// CHECK: Address stability: 1
28+
29+
// Test 4: constexpr static members
30+
struct Qux { static constexpr int val = 99; };
31+
int const *p3 = &Qux::val;
32+
printf("Constexpr test: %d\n", *p3);
33+
// Guard: Positive path again, showing constexpr inline members also bypass the
34+
// !isStaticDataMember()/isOutOfLine() early return and reach materialization.
35+
// CHECK: Constexpr test: 99
36+
37+
// Test 5: Non-const static member with out-of-class definition
38+
struct NonConst { static int value; };
39+
int NonConst::value = 42;
40+
int *p4 = &NonConst::value;
41+
printf("Non-const test: %d\n", *p4);
42+
// Guard: Triggers the getAnyInitializer/InitVD early return (no in-class
43+
// initializer), so materialization is skipped and we use the out-of-class def.
44+
// CHECK: Non-const test: 42
45+
46+
// Test 6: Redeclaration before definition to ensure canonical definition is used
47+
struct ForwardDecl;
48+
struct ForwardDecl { static const int val { 17 }; };
49+
int const *p5 = &ForwardDecl::val;
50+
printf("Forward-decl test: %d\n", *p5);
51+
// Guard: Relies on getDefinition() finding the canonical declaration even
52+
// though we first saw a forward declaration.
53+
// CHECK: Forward-decl test: 17
54+
55+
// Test 7: Out-of-class const definition should not be re-materialized
56+
struct OutOfLine { static const int value; };
57+
const int OutOfLine::value = 23;
58+
int const *p6 = &OutOfLine::value;
59+
printf("Out-of-line const test: %d\n", *p6);
60+
// Guard: Hits the isOutOfLine() half of the early return, proving we don't try
61+
// to re-materialize members that already have an out-of-class definition.
62+
// CHECK: Out-of-line const test: 23
63+
64+
// Test 8: Static member without in-class initializer relies solely on the out-of-class definition
65+
struct NoInClassInit { static const int value; };
66+
const int NoInClassInit::value = 64;
67+
int const *p7 = &NoInClassInit::value;
68+
printf("No in-class init test: %d\n", *p7);
69+
// Guard: Another getAnyInitializer/InitVD early return (const variant) showing
70+
// we skip members that lack in-class initializers.
71+
// CHECK: No in-class init test: 64
72+
73+
// Test 9: Repeated materialization requests reuse the emitted definition
74+
int const *p8 = &Foo::bar;
75+
printf("Repeat materialization test: %d\n", *p8);
76+
// Guard: NeedsMaterialization() emits or reuses the same global instead of
77+
// creating duplicates for repeated odr-uses.
78+
// CHECK: Repeat materialization test: 5
79+
80+
%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)