Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,12 @@ 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 use. This handles lvalue references that bypass
// constant evaluation (e.g., passing by reference to a function).
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)
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/CGExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2243,6 +2243,10 @@ 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., &Foo::member).
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())
Expand Down
45 changes: 45 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6302,6 +6302,51 @@ 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)
DefinitionVD = VD;

// Only in-class static data members need materialization. Out-of-line
// definitions are emitted normally.
if (!DefinitionVD->isStaticDataMember() || DefinitionVD->isOutOfLine())
return DefinitionVD;

const VarDecl *InitVD = nullptr;
// Members without in-class initializers rely on out-of-class definitions.
if (!DefinitionVD->getAnyInitializer(InitVD) || !InitVD)
return DefinitionVD;

auto NeedsMaterialization = [](llvm::GlobalValue *GV) {
if (!GV)
return true;
if (GV->isDeclaration())
return true;
// Inline static members may be emitted as available_externally; upgrade
// to linkonce_odr so the JIT can resolve them.
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 (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,
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
59 changes: 59 additions & 0 deletions clang/test/Interpreter/static-const-member.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// RUN: cat %s | clang-repl | FileCheck %s
// Tests for static const member materialization in clang-repl.
// See https://github.com/llvm/llvm-project/issues/146956

extern "C" int printf(const char*, ...);

struct Foo { static int const bar { 5 }; static int const baz { 10 }; };

// Test 1: Taking address of static const member
int const * p = &Foo::bar;
printf("Address test: %d\n", *p);
// CHECK: Address test: 5

// Test 2: Multiple static const members in same class
int const * q = &Foo::baz;
printf("Second member test: %d\n", *q);
// CHECK: Second member test: 10

// Test 3: static constexpr member (variant of in-class init)
struct Qux { static constexpr int val = 99; };
int const *p3 = &Qux::val;
printf("Constexpr test: %d\n", *p3);
// CHECK: Constexpr test: 99

// Test 4: Passing static const member by reference (exercises CGExpr.cpp path)
// NOTE: Uses a separate struct to ensure this is the first odr-use of RefOnly::val
struct RefOnly { static int const val { 77 }; };
void useRef(int const &x) { printf("Ref test: %d\n", x); }
useRef(RefOnly::val);
// CHECK: Ref test: 77

// ============================================================================
// Negative tests - cases that should NOT trigger materialization
// ============================================================================

// Test 5: Out-of-class definition (no need to materialize - already defined)
struct OutOfLine { static const int value; };
const int OutOfLine::value = 23;
int const *p5 = &OutOfLine::value;
printf("Out-of-line test: %d\n", *p5);
// CHECK: Out-of-line test: 23

// Test 6: Non-const static member (normal code gen path)
struct NonConst { static int value; };
int NonConst::value = 42;
int *p6 = &NonConst::value;
printf("Non-const test: %d\n", *p6);
// CHECK: Non-const test: 42

// ============================================================================
// Edge case tests
// ============================================================================

// Test 7: Repeated address-of reuses same definition
int const *p7 = &Foo::bar;
printf("Reuse test: %d\n", (*p == *p7) ? 1 : 0);
// CHECK: Reuse test: 1

%quit
14 changes: 14 additions & 0 deletions clang/unittests/Interpreter/InterpreterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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