[clang] [clang][codegen] Prevent inline weak static locals from being placed in .rodata if not formally constant-initialized (PR #226645)
Divyansh Yadav via cfe-commits
cfe-commits at lists.llvm.org
Sun Sep 27 01:33:49 PDT 2026
https://github.com/schizophrenicmaniac updated https://github.com/llvm/llvm-project/pull/226645
>From 045a77f1533f3a4659c21565015753e3d8631ebc Mon Sep 17 00:00:00 2001
From: Divyansh <anshmcs at gmail.com>
Date: Sat, 26 Sep 2026 11:56:14 +0530
Subject: [PATCH] [clang][CodeGen] Use guarded init for weak static locals
without constant init (#226631)
---
clang/lib/CodeGen/CGDecl.cpp | 22 ++++--
.../static-local-inline-non-constant-init.cpp | 77 +++++++++++++++++++
2 files changed, 94 insertions(+), 5 deletions(-)
create mode 100644 clang/test/CodeGenCXX/static-local-inline-non-constant-init.cpp
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index e1ed66ae71243..bbf94e8c4ecce 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -224,10 +224,6 @@ void CodeGenFunction::EmitVarDecl(const VarDecl &D) {
llvm::GlobalValue::LinkageTypes Linkage =
CGM.getLLVMLinkageVarDefinition(&D);
- // FIXME: We need to force the emission/use of a guard variable for
- // some variables even if we can constant-evaluate them because
- // we can't guarantee every translation unit will constant-evaluate them.
-
return EmitStaticVarDecl(D, Linkage);
}
@@ -360,8 +356,24 @@ llvm::Constant *CodeGenModule::getOrCreateStaticVarDecl(
llvm::GlobalVariable *
CodeGenFunction::AddInitializerToStaticVarDecl(const VarDecl &D,
llvm::GlobalVariable *GV) {
+ // A static local with vague linkage (e.g. one in an inline function) is
+ // shared by every translation unit that emits it, but only a constant
+ // initializer is guaranteed to be emitted as a constant in all of them. If
+ // we were to fold an initializer that another translation unit can't fold
+ // (say, because it reads a variable whose initializer is only visible here),
+ // that translation unit would perform a guarded initialization which could
+ // store to our copy of the variable (possibly placed in read-only memory) or
+ // race with our unguarded reads of it. Use a guarded initialization here as
+ // well, so that every translation unit agrees on how the variable is
+ // initialized.
+ bool NeedsGuardedInit =
+ getLangOpts().CPlusPlus && !getLangOpts().CUDAIsDevice &&
+ GV->isWeakForLinker() && !D.hasConstantInitialization() &&
+ !D.hasFlexibleArrayInit(getContext());
+
ConstantEmitter emitter(*this);
- llvm::Constant *Init = emitter.tryEmitForInitializer(D);
+ llvm::Constant *Init =
+ NeedsGuardedInit ? nullptr : emitter.tryEmitForInitializer(D);
// If constant emission failed, then this should be a C++ static
// initializer.
diff --git a/clang/test/CodeGenCXX/static-local-inline-non-constant-init.cpp b/clang/test/CodeGenCXX/static-local-inline-non-constant-init.cpp
new file mode 100644
index 0000000000000..ec78d2d90e360
--- /dev/null
+++ b/clang/test/CodeGenCXX/static-local-inline-non-constant-init.cpp
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -emit-llvm -o - %s | FileCheck %s --check-prefix=ITANIUM
+// RUN: %clang_cc1 -triple x86_64-windows-msvc -std=c++17 -emit-llvm -o - %s | FileCheck %s --check-prefix=MSVC
+
+// A static local in an inline function is shared by every translation unit
+// that uses the function. If its initializer isn't a constant initializer,
+// another translation unit might not be able to fold it (here, one that can't
+// see the initializer of kSize) and will initialize the variable dynamically,
+// behind a guard variable. So it must not be emitted as a (possibly read-only)
+// constant without a guard in this translation unit either.
+// See https://github.com/llvm/llvm-project/issues/226631.
+
+extern const int kSize;
+
+inline const int &instance() {
+ static const int meta = kSize;
+ return meta;
+}
+
+// Constant initializers are still emitted as constants, without a guard.
+inline const int &constant() {
+ static const int c = 42;
+ return c;
+}
+
+// A static local that isn't shared with other translation units can still be
+// folded, even if its initializer isn't a constant initializer.
+static const int &internal() {
+ static const int i = kSize;
+ return i;
+}
+
+extern const int kSize = 3;
+
+const int *a() { return &instance(); }
+const int *b() { return &constant(); }
+const int *c() { return &internal(); }
+
+// ITANIUM-DAG: @_ZZ8instancevE4meta = linkonce_odr global i32 0, comdat, align 4
+// ITANIUM-DAG: @_ZGVZ8instancevE4meta = linkonce_odr global i64 0, comdat, align 8
+// ITANIUM-DAG: @_ZZ8constantvE1c = linkonce_odr constant i32 42, comdat, align 4
+// ITANIUM-DAG: @_ZZL8internalvE1i = internal constant i32 3, align 4
+// ITANIUM-NOT: @_ZGVZ8constantvE1c
+// ITANIUM-NOT: @_ZGVZL8internalvE1i
+
+// ITANIUM-LABEL: define linkonce_odr {{.*}} ptr @_Z8instancev()
+// ITANIUM: load atomic i8, ptr @_ZGVZ8instancevE4meta acquire
+// ITANIUM: call i32 @__cxa_guard_acquire(ptr @_ZGVZ8instancevE4meta)
+// ITANIUM: store i32 3, ptr @_ZZ8instancevE4meta
+// ITANIUM: call void @__cxa_guard_release(ptr @_ZGVZ8instancevE4meta)
+// ITANIUM: ret ptr @_ZZ8instancevE4meta
+
+// ITANIUM-LABEL: define linkonce_odr {{.*}} ptr @_Z8constantv()
+// ITANIUM-NEXT: entry:
+// ITANIUM-NEXT: ret ptr @_ZZ8constantvE1c
+
+// ITANIUM-LABEL: define internal {{.*}} ptr @_ZL8internalv()
+// ITANIUM-NEXT: entry:
+// ITANIUM-NEXT: ret ptr @_ZZL8internalvE1i
+
+// MSVC-DAG: @"?meta@?1??instance@@YAAEBHXZ at 4HB" = linkonce_odr dso_local global i32 0, comdat, align 4
+// MSVC-DAG: @"?$TSS0@?1??instance@@YAAEBHXZ at 4HA" = linkonce_odr global i32 0, comdat, align 4
+// MSVC-DAG: @"?c@?1??constant@@YAAEBHXZ at 4HB" = linkonce_odr dso_local constant i32 42, comdat, align 4
+// MSVC-DAG: @"?i@?1??internal@@YAAEBHXZ at 4HB" = internal constant i32 3, align 4
+
+// MSVC-LABEL: define linkonce_odr {{.*}} ptr @"?instance@@YAAEBHXZ"()
+// MSVC: call void @_Init_thread_header(ptr @"?$TSS0@?1??instance@@YAAEBHXZ at 4HA")
+// MSVC: store i32 3, ptr @"?meta@?1??instance@@YAAEBHXZ at 4HB"
+// MSVC: call void @_Init_thread_footer(ptr @"?$TSS0@?1??instance@@YAAEBHXZ at 4HA")
+// MSVC: ret ptr @"?meta@?1??instance@@YAAEBHXZ at 4HB"
+
+// MSVC-LABEL: define linkonce_odr {{.*}} ptr @"?constant@@YAAEBHXZ"()
+// MSVC-NEXT: entry:
+// MSVC-NEXT: ret ptr @"?c@?1??constant@@YAAEBHXZ at 4HB"
+
+// MSVC-LABEL: define internal {{.*}} ptr @"?internal@@YAAEBHXZ"()
+// MSVC-NEXT: entry:
+// MSVC-NEXT: ret ptr @"?i@?1??internal@@YAAEBHXZ at 4HB"
More information about the cfe-commits
mailing list