[clang] [CIR] Fix 'vague' linkage globals. (PR #228586)
Erich Keane via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 7 08:51:44 PDT 2026
https://github.com/erichkeane updated https://github.com/llvm/llvm-project/pull/228586
>From 6c4fe81d8d7f0658b19e00bdded3dca6de0def60 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Wed, 30 Sep 2026 11:14:07 -0700
Subject: [PATCH 1/9] [CIR] Fix 'vague' linkage globals.
Namespace scope globals with 'vague' (internal/linkonce ODR) need to be
'guarded' to prevent them from being initialized in multiple TUs, which
typically shows itself with multiple 'free' operations happening (since
it is registered to 'free' multiple times: the initial allocation just
leaks).
This patch gets that correct. Additionally, these have the idea of an
'associated variable' when added to teh Global Ctor/Dtor, so this
modifies that to make sure that is correct.
As this infrastructure reuses the
'static_local_guard'/'static_local_info', I've generalized that naming
with the help of Claude who hopefully isn't pulling a fast one on us :)
---
.../include/clang/CIR/Dialect/IR/CIRAttrs.td | 56 +++--
clang/include/clang/CIR/Dialect/IR/CIROps.td | 17 +-
.../Dialect/Transforms/CIRTransformUtils.h | 10 +
clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 14 +-
clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp | 57 ++---
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 2 +-
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 3 +-
clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 32 ++-
.../Dialect/Transforms/LoweringPrepare.cpp | 143 +++++++----
.../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp | 51 ++--
.../test/CIR/CodeGen/global-tls-templates.cpp | 8 +-
clang/test/CIR/CodeGen/static-local-info.cpp | 6 +-
clang/test/CIR/CodeGen/static-local.cpp | 30 +--
clang/test/CIR/CodeGen/static-members.cpp | 230 ++++++++++++------
.../test/CIR/CodeGen/thread-local-in-func.cpp | 12 +-
.../test/CIR/CodeGen/tls-model-func-scope.cpp | 16 +-
.../test/CIR/IR/invalid-static-local-info.cir | 10 +-
clang/test/CIR/IR/invalid-static-local.cir | 16 +-
clang/test/CIR/IR/invalid-tls.cir | 6 +-
clang/test/CIR/IR/static-local-info.cir | 14 +-
clang/test/CIR/IR/static-local.cir | 34 +--
21 files changed, 458 insertions(+), 309 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index f6aaac5ab4cab..eec487fb8ade2 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -1451,25 +1451,29 @@ def CIR_UnwindTableKindAttr : CIR_EnumAttr<CIR_UnwindTableKind, "uwtable"> {
//===----------------------------------------------------------------------===//
class CIR_GlobalCtorDtor<string name, string attrMnemonic>
- : CIR_Attr<"Global" # name, "global_" # attrMnemonic> {
- let parameters = (ins "mlir::StringAttr":$name, "int":$priority);
+ : CIR_Attr<"Global" #name, "global_" #attrMnemonic> {
+ let parameters = (ins "mlir::StringAttr":$name, "int":$priority,
+ OptionalParameter<"mlir::FlatSymbolRefAttr">:$associated);
let skipDefaultBuilders = 1;
let builders = [
AttrBuilder<(ins
"llvm::StringRef":$name,
- CArg<"int", "65535">:$priority), [{
- return $_get($_ctxt, mlir::StringAttr::get($_ctxt, name), priority);
+ CArg<"int", "65535">:$priority,
+ CArg<"mlir::FlatSymbolRefAttr", "{}">:$associated), [{
+ return $_get($_ctxt, mlir::StringAttr::get($_ctxt, name), priority,
+ associated);
}]>,
AttrBuilderWithInferredContext<(ins
"mlir::StringAttr":$name,
- CArg<"int", "65535">:$priority), [{
- return $_get(name.getContext(), name, priority);
+ CArg<"int", "65535">:$priority,
+ CArg<"mlir::FlatSymbolRefAttr", "{}">:$associated), [{
+ return $_get(name.getContext(), name, priority, associated);
}]>
];
let assemblyFormat = [{
- `<` $name `,` $priority `>`
+ `<` $name `,` $priority (`,` $associated^)? `>`
}];
let extraClassDeclaration = [{
@@ -1477,9 +1481,7 @@ class CIR_GlobalCtorDtor<string name, string attrMnemonic>
return getPriority() == getDefaultPriority();
};
- static int getDefaultPriority() {
- return 65535;
- }
+ static int getDefaultPriority() { return 65535; }
}];
let canHaveIllegalCXXABIType = 0;
}
@@ -2174,19 +2176,22 @@ def CIR_MemoryEffectsAttr : CIR_Attr<"MemoryEffects", "memory_effects"> {
}
//===----------------------------------------------------------------------===//
-// StaticLocalGuardAttr
+// DynamicInitGuardAttr
//===----------------------------------------------------------------------===//
-def CIR_StaticLocalGuardAttr : CIR_Attr<"StaticLocalGuard",
- "static_local_guard"> {
- let summary = "Guard variable name for static local variables";
+def CIR_DynamicInitGuardAttr : CIR_Attr<"DynamicInitGuard",
+ "dynamic_init_guard"> {
+ let summary = "Guard variable name for guarded dynamic initialization";
let description = [{
- Contains the mangled guard variable name for static local variable
- initialization.
+ Contains the mangled guard variable name for guarded dynamic
+ initialization. This covers both function-local static variables and
+ namespace-scope variables with vague linkage (e.g. weak/linkonce_odr
+ inline variables), which both use the Itanium `__cxa_guard_*` protocol.
+ See `DynamicInitInfoAttr` for the facts that distinguish the two.
Example:
```
- cir.global internal static_local_guard<"_ZGVZ3foovE1x"> @_ZZ3foovE1x = ...
+ cir.global internal dynamic_init_guard<"_ZGVZ3foovE1x"> @_ZZ3foovE1x = ...
```
}];
let parameters = (ins "mlir::StringAttr":$name);
@@ -2320,7 +2325,7 @@ def CIR_ASTVarDeclAttr : CIR_AST<"VarDecl", "var.decl", [
]>;
//===----------------------------------------------------------------------===//
-// StaticLocalInfoAttr
+// DynamicInitInfoAttr
//===----------------------------------------------------------------------===//
// CIR-native thread-local storage kind. Cases mirror clang::VarDecl::TLSKind
@@ -2351,12 +2356,15 @@ def CIR_TemplateSpecializationKind
"explicit_instantiation_definition">
]>;
-def CIR_StaticLocalInfoAttr
- : CIR_Attr<"StaticLocalInfo", "static_local_info"> {
- let summary = "Static-local variable facts needed by later lowering";
+def CIR_DynamicInitInfoAttr
+ : CIR_Attr<"DynamicInitInfo", "dynamic_init_info"> {
+ let summary = "Variable facts needed by guarded dynamic initialization lowering";
let description = [{
- Holds the subset of a static-local variable's declaration facts that
- post-CIRGen lowering needs, as plain data (no `clang::VarDecl` pointer).
+ Holds the subset of a guarded-dynamic-initialization variable's
+ declaration facts that post-CIRGen lowering needs, as plain data (no
+ `clang::VarDecl` pointer). Always accompanies a `DynamicInitGuardAttr`,
+ and applies equally to function-local statics and to namespace-scope
+ variables with vague linkage (e.g. weak/linkonce_odr inline variables).
- `local`: whether the variable is a block-scope (local) declaration.
Mirrors `clang::VarDecl::isLocalVarDecl()`. The namespace-scope inline
@@ -2375,7 +2383,7 @@ def CIR_StaticLocalInfoAttr
Example:
```
cir.global ... @x = ...
- static_local_info<local = true, tls = none, is_inline = false,
+ dynamic_init_info<local = true, tls = none, is_inline = false,
tsk = undeclared>
```
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index f65830ccade4f..ff83e04ce9bea 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3410,10 +3410,11 @@ def CIR_GlobalOp : CIR_RegionBranchOpBase<"global", [
Symbol visibility in `sym_visibility` is defined in terms of MLIR's visibility
and verified to be in accordance to `linkage`.
- The `static_local_guard` attribute indicates that this global represents a
- function-local static variable that requires guarded initialization
- (e.g., C++ static local variables with non-constant initializers).
- It contains the mangled name of the guard variable.
+ The `dynamic_init_guard` attribute indicates that this global requires
+ guarded dynamic initialization via the Itanium `__cxa_guard_*` protocol
+ (e.g., C++ static local variables with non-constant initializers, or
+ namespace-scope variables with vague linkage such as weak/linkonce_odr
+ inline variables). It contains the mangled name of the guard variable.
The `strictfp` attribute indicates that the dynamic initialization emitted
into the `ctorRegion`/`dtorRegion` runs under a constrained floating-point
@@ -3445,10 +3446,10 @@ def CIR_GlobalOp : CIR_RegionBranchOpBase<"global", [
OptionalAttr<StrAttr>:$comdat,
UnitProp:$constant,
UnitProp:$dso_local,
- OptionalAttr<CIR_StaticLocalGuardAttr>:$static_local_guard,
+ OptionalAttr<CIR_DynamicInitGuardAttr>:$dynamic_init_guard,
OptionalAttr<I64Attr>:$alignment,
OptionalAttr<ASTVarDeclInterface>:$ast,
- OptionalAttr<CIR_StaticLocalInfoAttr>:$static_local_info,
+ OptionalAttr<CIR_DynamicInitInfoAttr>:$dynamic_init_info,
OptionalAttr<StrAttr>:$section,
OptionalAttr<CIR_AnnotationArrayAttr>:$annotations,
OptionalAttr<FlatSymbolRefAttr>:$aliasee,
@@ -3468,7 +3469,7 @@ def CIR_GlobalOp : CIR_RegionBranchOpBase<"global", [
(`tls_model` `=` enum($tls_model)^)?
(`tls_refs` `=` $tls_refs^)?
(`dso_local` $dso_local^)?
- (`static_local_guard` `` $static_local_guard^)?
+ (`dynamic_init_guard` `` $dynamic_init_guard^)?
(` ` custom<GlobalMemorySpace>($addr_space)^ )?
$sym_name
(`alias` `(` $aliasee^ `)`)?
@@ -3477,7 +3478,7 @@ def CIR_GlobalOp : CIR_RegionBranchOpBase<"global", [
($annotations^)?
(`align` `(` $alignment^ `)`)?
(`ast` `(` $ast^ `)`)?
- (`static_local_info` `` $static_local_info^)?
+ (`dynamic_init_info` `` $dynamic_init_info^)?
(`section` `(` $section^ `)`)?
(`strictfp` $strictfp^)?
(`init_priority` `(` $init_priority^ `)`)?
diff --git a/clang/include/clang/CIR/Dialect/Transforms/CIRTransformUtils.h b/clang/include/clang/CIR/Dialect/Transforms/CIRTransformUtils.h
index b708da61bf01f..b305207a21d35 100644
--- a/clang/include/clang/CIR/Dialect/Transforms/CIRTransformUtils.h
+++ b/clang/include/clang/CIR/Dialect/Transforms/CIRTransformUtils.h
@@ -16,6 +16,16 @@
#include "llvm/ADT/SmallVector.h"
namespace cir {
+/// Helper type for lowering global ctor/dtors, that contain all the values
+/// needed for transforming the values between IRs.
+struct GlobalCtorDtorEntry {
+ GlobalCtorDtorEntry(llvm::StringRef name, uint32_t priority,
+ llvm::StringRef associated = "")
+ : name(name), priority(priority), associated(associated) {}
+ std::string name;
+ uint32_t priority;
+ std::string associated;
+};
/// Replace a `cir::CallOp` with a `cir::TryCallOp` whose unwind destination
/// is \p unwindDest. The call's parent block is split immediately after the
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index ebb89a89f000e..e796b07c2c064 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -194,7 +194,7 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
// The same applies to code above where it is calling getAddrOfGlobalVar.
mlir::Value globalVal = builder.createGetGlobal(addr);
globalVal.getDefiningOp<cir::GetGlobalOp>().setStaticLocal(
- addr.getStaticLocalGuard().has_value());
+ addr.getDynamicInitGuard().has_value());
CharUnits alignment = cgf.getContext().getDeclAlign(vd);
Address globalAddr{globalVal, cgf.convertTypeForMem(type), alignment};
cgf.emitDestroy(globalAddr, type, cgf.getDestroyer(dtorKind));
@@ -304,12 +304,12 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl,
// Attach the AST handle for consumers that need arbitrary AST properties.
addr.setAstAttr(cir::ASTVarDeclAttr::get(&getMLIRContext(), varDecl));
- // For static-local guarded globals, also materialize the specific facts
+ // For dynamic-init-guarded globals, also materialize the specific facts
// LoweringPrepare needs into a serializable attribute, so that lowering can
// run without a live ASTContext (e.g. on serialized CIR in split-compilation
// flows). This is orthogonal to the AST handle above.
- if (addr.getStaticLocalGuard().has_value())
- addr.setStaticLocalInfoAttr(cir::StaticLocalInfoAttr::get(
+ if (addr.getDynamicInitGuard().has_value())
+ addr.setDynamicInitInfoAttr(cir::DynamicInitInfoAttr::get(
&getMLIRContext(), varDecl->isLocalVarDecl(),
getCIRTLSKind(varDecl->getTLSKind()), varDecl->isInline(),
getCIRTemplateSpecializationKind(
@@ -357,10 +357,10 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl,
scope.setAsGlobalInit();
builder.setInsertionPointToStart(block);
mlir::Value getGlobal = builder.createGetGlobal(addr, varDecl->getTLSKind());
- // If we're initializing a static local with a guard variable, set the flag
- // that indicates that.
+ // If we're initializing a guarded variable, set the flag that indicates
+ // that.
getGlobal.getDefiningOp<cir::GetGlobalOp>().setStaticLocal(
- addr.getStaticLocalGuard().has_value());
+ addr.getDynamicInitGuard().has_value());
Address declAddr(getGlobal, getASTContext().getDeclAlign(varDecl));
assert(performInit && "cannot have a constant initializer which needs "
diff --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
index 2bcef48739dfa..ae3741ee90a67 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
@@ -30,33 +30,28 @@ void CIRGenFunction::emitCXXGuardedInit(const VarDecl &varDecl,
if (cgm.getCodeGenOpts().ForbidGuardVariables)
cgm.error(varDecl.getLocation(), "guard variables are forbidden");
- // Compute the mangled guard variable name and set the static_local attribute
- // BEFORE emitting initialization. This ensures that GetGlobalOps created
- // during initialization (e.g., in the ctor region) will see the attribute
- // and be marked with static_local accordingly.
+ // Compute the mangled guard variable name and set the dynamic_init_guard
+ // attribute BEFORE emitting initialization. This ensures that GetGlobalOps
+ // created during initialization (e.g., in the ctor region) will see the
+ // attribute and be marked with static_local accordingly.
llvm::SmallString<256> guardName;
{
llvm::raw_svector_ostream out(guardName);
cgm.getCXXABI().getMangleContext().mangleStaticGuardVariable(&varDecl, out);
}
- // Mark the global as static local with the guard name. The emission of the
- // guard/acquire is done during LoweringPrepare.
+ // Mark the global as requiring guarded dynamic initialization, with the
+ // guard name. The emission of the guard/acquire is done during
+ // LoweringPrepare.
auto guardAttr = mlir::StringAttr::get(&cgm.getMLIRContext(), guardName);
- if (!varDecl.isStaticLocal())
- cgm.errorNYI(
- varDecl.getSourceRange(),
- "Static local guard attr only valid on static local variables");
- globalOp.setStaticLocalGuardAttr(
- cir::StaticLocalGuardAttr::get(&cgm.getMLIRContext(), guardAttr));
-
- // Emit the initializer and add a global destructor if appropriate.
- // TODO(cir): classic codegen calls emitCXXGlobalVarDeclInit for this as well,
- // and this is meant to handle cases with weak linkage (see comment in
- // emitCXXGlobalVarDeclInitFunc). At one point we'll have to do some level of
- // split here depending on whether this is a global (which should/can have
- // ctor/dtor regions), or should have in-function initialization.
- cgm.emitCXXStaticLocalVarDeclInit(&varDecl, globalOp, performInit);
+
+ globalOp.setDynamicInitGuardAttr(
+ cir::DynamicInitGuardAttr::get(&cgm.getMLIRContext(), guardAttr));
+
+ if (varDecl.isStaticLocal())
+ cgm.emitCXXStaticLocalVarDeclInit(&varDecl, globalOp, performInit);
+ else
+ cgm.emitCXXGlobalVarDeclInit(&varDecl, globalOp, performInit);
}
void CIRGenModule::setGlobalTlsReferences(const VarDecl &vd,
@@ -104,19 +99,11 @@ void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *vd,
bool performInit) {
assert(!cir::MissingFeatures::cudaSupport());
- assert(!cir::MissingFeatures::deferredCXXGlobalInit());
-
- // TODO(cir): Classic codegen calls emitCXXGuardedInit in the following case:
- // template<typename T> struct Templ {
- // static T f;
- // };
- // template<typename T> T Templ<T>::f = get_i();
- // auto func() {
- // Templ<int> t;
- // return decltype(t)::f;
- // }
- //
- // However, at the moment it is only suitable for static-local variables, so
- // we will have to modify it to work for this case as well.
- emitCXXGlobalVarDeclInit(vd, addr, performInit);
+ if (addr.hasWeakLinkage() || addr.hasLinkOnceLinkage() ||
+ (vd->getTLSKind() == VarDecl::TLS_Dynamic &&
+ isTemplateInstantiation(vd->getTemplateSpecializationKind()))) {
+ CIRGenFunction(*this, builder).emitCXXGuardedInit(*vd, addr, performInit);
+ } else {
+ emitCXXGlobalVarDeclInit(vd, addr, performInit);
+ }
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 38f4839c1015d..193436b0daf73 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -1144,7 +1144,7 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) {
cgm.getOrCreateStaticVarDecl(*vd, cgm.getCIRLinkageVarDefinition(vd));
mlir::Value getGlobVal = builder.createGetGlobal(var);
auto getGlob = getGlobVal.getDefiningOp<cir::GetGlobalOp>();
- getGlob.setStaticLocal(var.getStaticLocalGuard().has_value());
+ getGlob.setStaticLocal(var.getDynamicInitGuard().has_value());
getGlob.setTls(vd->getTLSKind() != VarDecl::TLS_None);
addr = Address(cgm.castGlobalToDeclAddrSpace(getGlob, *vd),
convertTypeForMem(vd->getType()),
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 5063b6a4cc779..6dd5d5fb78ae7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -1447,7 +1447,8 @@ mlir::Value CIRGenModule::getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty,
mlir::Value addr = cir::GetGlobalOp::create(
builder, getLoc(d->getSourceRange()), ptrTy, g.getSymNameAttr(),
tlsAccess,
- /*static_local=*/g.getStaticLocalGuard().has_value());
+ /*static_local=*/g.getDynamicInitGuard().has_value() &&
+ d->isLocalVarDecl());
return castGlobalToDeclAddrSpace(addr, *d);
}
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 15cebd358040e..460bc8fbfe1cb 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -673,7 +673,7 @@ cir::LocalInitOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
if (getTls() && !global.getTlsModel())
return emitOpError("access to global not marked thread local");
- if (!global.getStaticLocalGuard().has_value())
+ if (!global.getDynamicInitGuard().has_value())
return emitOpError("static_local attribute mismatch");
return success();
@@ -2361,23 +2361,32 @@ mlir::LogicalResult cir::GlobalOp::verify() {
return failure();
}
- if ((getStaticLocalGuard().has_value()) &&
+ std::optional<DynamicInitInfoAttr> info = getDynamicInitInfo();
+
+ if ((getDynamicInitGuard().has_value()) && (!info || info->getLocal()) &&
(!getCtorRegion().empty() || !getDtorRegion().empty()))
return emitOpError(
"Cannot have a static-local global-op with a constructor or "
"destructor, they require in-function initialization via LocalInitOp");
- // CIRGen emits 'static_local_guard' and 'static_local_info' together and
+ // CIRGen emits 'dynamic_init_guard' and 'dynamic_init_info' together and
// they are only meaningful together: the guard drives lowering, which reads
// the info. Require both or neither so malformed .cir can carry neither a
// guard without info nor a dangling info nothing will read.
- if (getStaticLocalGuard().has_value() != getStaticLocalInfo().has_value())
- return emitOpError("'static_local_guard' and 'static_local_info' must be "
+ if (getDynamicInitGuard().has_value() != getDynamicInitInfo().has_value())
+ return emitOpError("'dynamic_init_guard' and 'dynamic_init_info' must be "
"present together");
if (getTlsRefs()) {
- if (getStaticLocalGuard().has_value())
- return emitOpError("cannot have both static local and tls references");
+ // 'Unordered' TLS globals (variable template instantiations) legitimately
+ // carry both: they can't use the shared __tls_init guard, so CIRGen also
+ // gives them their own dynamic-init guard, mangled the same as
+ // 'tls_refs's own guard name. 'Ordered' TLS globals share __tls_init and
+ // have no per-variable guard name, so a dynamic-init guard there would be
+ // a mismatch.
+ if (getDynamicInitGuard().has_value() && !getTlsRefs()->getGuardName())
+ return emitOpError("cannot have a dynamic-init guard combined with "
+ "ordered tls references");
if (!getTlsModel())
return emitOpError("'tls_refs' only valid for tls");
}
@@ -2596,10 +2605,13 @@ cir::GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
return emitOpError("access to global not marked thread local");
// Verify that the static_local attribute on GetGlobalOp matches the
- // static_local_guard attribute on GlobalOp. GetGlobalOp uses a UnitAttr,
- // GlobalOp uses StaticLocalGuardAttr. Both should be present, or neither.
+ // dynamic_init_guard attribute on GlobalOp. GetGlobalOp uses a UnitAttr,
+ // GlobalOp uses DynamicInitGuardAttr. Both should be present, or neither.
bool getGlobalIsStaticLocal = getStaticLocal();
- bool globalIsStaticLocal = g.getStaticLocalGuard().has_value();
+ bool globalIsStaticLocal =
+ g.getDynamicInitGuard().has_value() &&
+ (!g.getDynamicInitInfo() || g.getDynamicInitInfo()->getLocal());
+
if (getGlobalIsStaticLocal != globalIsStaticLocal &&
!getOperation()->getParentOfType<cir::GlobalOp>())
return emitOpError("static_local attribute mismatch");
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 23fb81e2f706d..9757d8a6b8550 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -25,6 +25,7 @@
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Dialect/IR/CIRTypes.h"
#include "clang/CIR/Dialect/Passes.h"
+#include "clang/CIR/Dialect/Transforms/CIRTransformUtils.h"
#include "clang/CIR/Interfaces/ASTAttrInterfaces.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/ADT/StringRef.h"
@@ -193,6 +194,11 @@ struct LoweringPreparePass
/// Handle static local variable initialization with guard variables.
void handleStaticLocal(cir::GlobalOp globalOp, cir::LocalInitOp localInitOp);
+ /// Handle the guard-protected init for static local/weak/linkonce-odr
+ /// variables.
+ void emitGuardedInit(CIRBaseBuilderTy &builder, cir::GlobalOp globalOp,
+ mlir::Region &ctorRegion, mlir::Region &dtorRegion);
+
/// Get or create __cxa_guard_acquire function.
cir::FuncOp getGuardAcquireFn(cir::PointerType guardPtrTy);
@@ -364,9 +370,9 @@ struct LoweringPreparePass
llvm::StringMap<llvm::SmallVector<cir::GlobalOp, 1>> constAggregateGlobals;
/// List of ctors and their priorities to be called before main()
- llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList;
+ llvm::SmallVector<cir::GlobalCtorDtorEntry, 4> globalCtorList;
/// List of dtors and their priorities to be called when unloading module.
- llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalDtorList;
+ llvm::SmallVector<cir::GlobalCtorDtorEntry, 4> globalDtorList;
/// Returns true if the target uses ARM-style guard variables for static
/// local initialization (32-bit guard, check bit 0 only).
@@ -552,10 +558,10 @@ struct LoweringPreparePass
// For non-local variables, store 1 into the first byte of the guard
// variable before the object initialization begins so that references
// to the variable during initialization don't restart initialization.
- // OG: Builder.CreateStore(llvm::ConstantInt::get(CGM.Int8Ty, 1), ...);
- // Then: CGF.EmitCXXGlobalVarDeclInit(D, var, shouldPerformInit);
- globalOp->emitError("NYI: non-threadsafe init for non-local variables");
- return;
+ builder.createStore(
+ loc, builder.getConstantInt(loc, guardPtrTy.getPointee(), 1),
+ guardPtr);
+ emitBody();
} else {
emitBody();
// For local variables, store 1 into the first byte of the guard variable
@@ -1262,6 +1268,26 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
// If this is a global TLS variable (that is, declared at namespace scope), we
// have to emit the guard variable here.
bool needsTlsGuard = op.getTlsRefs() && op.getTlsRefs()->getGuardName();
+
+ // Non-TLS globals (such as weak/linkonce_odr/inline-ns scope variables) use
+ // the __cxa_guard_acquire/release instead of the TLS guard.
+ if (!needsTlsGuard && op.getDynamicInitGuard()) {
+ if (!op.getDynamicInitInfo()) {
+ op->emitError(
+ "guarded global with a guard is missing 'dynamic_init_info'");
+ return {};
+ }
+
+ if (op.getDynamicInitInfo()->getLocal()) {
+ op->emitError("guarded global is marked local");
+ return {};
+ }
+ emitGuardedInit(builder, op, op.getCtorRegion(), op.getDtorRegion());
+ builder.setInsertionPointToEnd(entryBB);
+ cir::ReturnOp::create(builder, op.getLoc());
+ return f;
+ }
+
cir::IfOp guardIf;
if (needsTlsGuard) {
guardIf = buildGlobalTlsGuardCheck(
@@ -1377,20 +1403,6 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
cir::LocalInitOp localInitOp) {
CIRBaseBuilderTy builder(getContext());
- // Static-local facts are materialized into a serializable attribute by
- // CIRGen, so this pass does not need a live ASTContext to read them.
- std::optional<cir::StaticLocalInfoAttr> infoOption =
- globalOp.getStaticLocalInfo();
- // The GlobalOp verifier requires 'static_local_info' whenever a
- // 'static_local_guard' is present, so verified IR reaching here has it.
- // Emit an error instead of asserting so malformed .cir gets a diagnostic.
- if (!infoOption.has_value()) {
- globalOp->emitError(
- "static-local global with a guard is missing 'static_local_info'");
- return;
- }
- cir::StaticLocalInfoAttr info = infoOption.value();
-
builder.setInsertionPointAfter(localInitOp);
mlir::Block *localInitBlock = builder.getInsertionBlock();
@@ -1401,6 +1413,34 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
// operation has changed thanks to the terminator removal.
builder.setInsertionPointAfter(localInitOp);
+ emitGuardedInit(builder, globalOp, localInitOp.getCtorRegion(),
+ localInitOp.getDtorRegion());
+
+ // Insert the removed terminator back.
+ builder.getInsertionBlock()->push_back(ret);
+}
+
+void LoweringPreparePass::emitGuardedInit(CIRBaseBuilderTy &builder,
+ cir::GlobalOp globalOp,
+ mlir::Region &ctorRegion,
+ mlir::Region &dtorRegion) {
+ // Dynamic-initialization facts are materialized into a serializable
+ // attribute by CIRGen, so this pass does not need a live ASTContext to
+ // read them.
+ std::optional<cir::DynamicInitInfoAttr> infoOption =
+ globalOp.getDynamicInitInfo();
+
+ // The GlobalOp verifier requires 'dynamic_init_info' whenever a
+ // 'dynamic_init_guard' is present, so verified IR reaching here has it.
+ // Emit an error instead of asserting so malformed .cir gets a diagnostic.
+ if (!infoOption.has_value()) {
+ globalOp->emitError(
+ "dynamic-init-guarded global is missing 'dynamic_init_info'");
+ return;
+ }
+
+ cir::DynamicInitInfoAttr info = infoOption.value();
+
// Inline variables that weren't instantiated from variable templates have
// partially-ordered initialization within their translation unit.
cir::TemplateSpecializationKind tsk = info.getTsk();
@@ -1411,14 +1451,6 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
tsk == cir::TemplateSpecializationKind::ExplicitInstantiationDefinition;
bool nonTemplateInline = info.getIsInline() && !isTemplateInstantiation;
- // Inline namespace-scope variables require guarded initialization in a
- // __cxx_global_var_init function. This is not yet implemented.
- if (nonTemplateInline) {
- globalOp->emitError(
- "NYI: guarded initialization for inline namespace-scope variables");
- return;
- }
-
// We only need to use thread-safe statics for local non-TLS variables and
// inline variables; other global initialization is always single-threaded
// or (through lazy dynamic loading in multiple threads) unsequenced.
@@ -1432,15 +1464,14 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
// Create the guard variable if we don't already have it.
cir::GlobalOp guard = getOrCreateStaticLocalDeclGuardAddress(
- builder, globalOp, globalOp.getStaticLocalGuard()->getName().getValue(),
+ builder, globalOp, globalOp.getDynamicInitGuard()->getName().getValue(),
info.getLocal(), useInt8GuardVariable);
if (!guard) {
- // Error was already emitted, just restore the terminator and return.
- localInitBlock->push_back(ret);
return;
}
- mlir::Value guardPtr = builder.createGetGlobal(guard, localInitOp.getTls());
+ mlir::Value guardPtr =
+ builder.createGetGlobal(guard, info.getTls() != cir::TLSKind::None);
// Test whether the variable has completed initialization.
//
@@ -1469,7 +1500,7 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
auto bytePtrTy = cir::PointerType::get(builder.getSIntNTy(8));
mlir::Value bytePtr = builder.createBitcast(guardPtr, bytePtrTy);
mlir::Value guardLoad = builder.createAlignedLoad(
- localInitOp.getLoc(), bytePtr, *guard.getAlignment());
+ globalOp.getLoc(), bytePtr, *guard.getAlignment());
// Itanium ABI:
// An implementation supporting thread-safety on multiprocessor
@@ -1505,15 +1536,14 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
// the variable is not initialized and 1 when it is.
if (useARMGuardVarABI() && !useInt8GuardVariable) {
auto one = builder.getConstantInt(
- localInitOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()),
- 1);
- guardLoad = builder.createAnd(localInitOp.getLoc(), guardLoad, one);
+ globalOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()), 1);
+ guardLoad = builder.createAnd(globalOp.getLoc(), guardLoad, one);
}
// Check if the first byte of the guard variable is zero.
auto zero = builder.getConstantInt(
- localInitOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()), 0);
- auto needsInit = builder.createCompare(localInitOp.getLoc(),
+ globalOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()), 0);
+ auto needsInit = builder.createCompare(globalOp.getLoc(),
cir::CmpOpKind::eq, guardLoad, zero);
// Build the guarded initialization inside an if block.
@@ -1521,9 +1551,8 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
builder, globalOp.getLoc(), needsInit,
/*withElseRegion=*/false, [&](mlir::OpBuilder &, mlir::Location) {
emitCXXGuardedInitIf(
- builder, globalOp, localInitOp.getCtorRegion(),
- localInitOp.getDtorRegion(), info.getLocal(), guardPtr,
- builder.getPointerTo(guard.getSymType()), threadsafe);
+ builder, globalOp, ctorRegion, dtorRegion, info.getLocal(),
+ guardPtr, builder.getPointerTo(guard.getSymType()), threadsafe);
});
} else {
// Threadsafe statics without inline atomics - call __cxa_guard_acquire
@@ -1531,9 +1560,6 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
globalOp->emitError("NYI: guarded init without inline atomics support");
return;
}
-
- // Insert the removed terminator back.
- builder.getInsertionBlock()->push_back(ret);
}
void LoweringPreparePass::lowerLocalInitOp(cir::LocalInitOp initOp) {
@@ -1687,7 +1713,8 @@ LoweringPreparePass::defineGlobalThreadLocalInitAlias(cir::GlobalOp op,
void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
// Static locals are handled separately via guard variables.
- if (op.getStaticLocalGuard())
+ if (op.getDynamicInitGuard() &&
+ (!op.getDynamicInitInfo() || op.getDynamicInitInfo()->getLocal()))
return;
mlir::Region &ctorRegion = op.getCtorRegion();
@@ -1704,7 +1731,7 @@ void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
dtorRegion.getBlocks().clear();
assert(!cir::MissingFeatures::astVarDeclInterface());
- if (op.getTlsModel() && !op.getStaticLocalGuard().has_value()) {
+ if (op.getTlsModel()) {
// There are two types of global TLS variables: 'ordered' and 'unordered'.
// 'ordered' are the common case. A call to any of them causes all of the
// initializers for all other 'ordered' ones to be called, via a
@@ -1729,6 +1756,19 @@ void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
}
} else if (std::optional<uint32_t> priority = op.getInitPriority()) {
prioritizedDynamicInitializers[*priority].push_back(f);
+ } else if (op.getDynamicInitGuard() &&
+ getTargetInfo().getTriple().supportsCOMDAT() &&
+ !op.hasInternalLinkage()) {
+ // Vague-linkage globals (weak/linkonce_odr inline variables) get their
+ // own `llvm.global_ctors` entry keyed to the variable via COMDAT
+ // associated data, rather than being folded into `_GLOBAL__sub_I_*`.
+ // This ensures at most one initializer per DSO runs, and lets the
+ // linker drop the initializer call along with a discarded duplicate
+ // definition of the variable. Matches classic CodeGen's
+ // EmitCXXGlobalVarDeclInitFunc.
+ globalCtorList.emplace_back(f.getSymName(),
+ cir::GlobalCtorAttr::getDefaultPriority(),
+ op.getSymName());
} else {
dynamicInitializers.push_back(f);
}
@@ -1844,10 +1884,15 @@ void LoweringPreparePass::lowerThreeWayCmpOp(CmpThreeWayOp op) {
template <typename AttributeTy>
static llvm::SmallVector<mlir::Attribute>
prepareCtorDtorAttrList(mlir::MLIRContext *context,
- llvm::ArrayRef<std::pair<std::string, uint32_t>> list) {
+ llvm::ArrayRef<cir::GlobalCtorDtorEntry> list) {
llvm::SmallVector<mlir::Attribute> attrs;
- for (const auto &[name, priority] : list)
- attrs.push_back(AttributeTy::get(context, name, priority));
+ for (const auto &entry : list) {
+ mlir::FlatSymbolRefAttr associated;
+ if (!entry.associated.empty())
+ associated = mlir::FlatSymbolRefAttr::get(context, entry.associated);
+ attrs.push_back(
+ AttributeTy::get(context, entry.name, entry.priority, associated));
+ }
return attrs;
}
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 8c6679c1c5c51..fe51fa2e0ae1d 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -4158,11 +4158,10 @@ static void prepareTypeConverter(mlir::LLVMTypeConverter &converter,
return mlir::LLVM::LLVMVoidType::get(type.getContext());
});
}
-
static void buildCtorDtorList(
mlir::ModuleOp module, StringRef globalXtorName, StringRef llvmXtorName,
- llvm::function_ref<std::pair<StringRef, int>(mlir::Attribute)> createXtor) {
- llvm::SmallVector<std::pair<StringRef, int>> globalXtors;
+ llvm::function_ref<cir::GlobalCtorDtorEntry(mlir::Attribute)> createXtor) {
+ llvm::SmallVector<cir::GlobalCtorDtorEntry> globalXtors;
if (auto attr =
module->getDiscardableAttrOfType<mlir::ArrayAttr>(globalXtorName))
for (mlir::Attribute element : attr)
@@ -4198,22 +4197,26 @@ static void buildCtorDtorList(
mlir::Value result =
mlir::LLVM::UndefOp::create(builder, loc, ctorStructArrayTy);
- for (auto [index, fn] : llvm::enumerate(globalXtors)) {
+ for (auto [index, xtorInfo] : llvm::enumerate(globalXtors)) {
mlir::Value structInit =
mlir::LLVM::UndefOp::create(builder, loc, ctorStructTy);
mlir::Value initPriority = mlir::LLVM::ConstantOp::create(
- builder, loc, ctorStructFields[0], fn.second);
+ builder, loc, ctorStructFields[0], xtorInfo.priority);
mlir::Value initFuncAddr = mlir::LLVM::AddressOfOp::create(
- builder, loc, ctorStructFields[1], fn.first);
+ builder, loc, ctorStructFields[1], xtorInfo.name);
mlir::Value initAssociate =
- mlir::LLVM::ZeroOp::create(builder, loc, ctorStructFields[2]);
+ xtorInfo.associated.empty()
+ ? mlir::LLVM::ZeroOp::create(builder, loc, ctorStructFields[2])
+ .getResult()
+ : mlir::LLVM::AddressOfOp::create(builder, loc, ctorStructFields[2],
+ xtorInfo.associated)
+ .getResult();
// Literal zero makes the InsertValueOp::create ambiguous.
llvm::SmallVector<int64_t> zero{0};
structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
initPriority, zero);
structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
initFuncAddr, 1);
- // TODO: handle associated data for initializers.
structInit = mlir::LLVM::InsertValueOp::create(builder, loc, structInit,
initAssociate, 2);
result = mlir::LLVM::InsertValueOp::create(builder, loc, result, structInit,
@@ -4582,19 +4585,27 @@ void ConvertCIRToLLVMPass::runOnOperation() {
}
// Emit the llvm.global_ctors array.
- buildCtorDtorList(module, cir::CIRDialect::getGlobalCtorsAttrName(),
- "llvm.global_ctors", [](mlir::Attribute attr) {
- auto ctorAttr = mlir::cast<cir::GlobalCtorAttr>(attr);
- return std::make_pair(ctorAttr.getName(),
- ctorAttr.getPriority());
- });
+ buildCtorDtorList(
+ module, cir::CIRDialect::getGlobalCtorsAttrName(), "llvm.global_ctors",
+ [](mlir::Attribute attr) {
+ auto ctorAttr = mlir::cast<cir::GlobalCtorAttr>(attr);
+ StringRef associated;
+ if (mlir::FlatSymbolRefAttr assoc = ctorAttr.getAssociated())
+ associated = assoc.getValue();
+ return cir::GlobalCtorDtorEntry(ctorAttr.getName(),
+ ctorAttr.getPriority(), associated);
+ });
// Emit the llvm.global_dtors array.
- buildCtorDtorList(module, cir::CIRDialect::getGlobalDtorsAttrName(),
- "llvm.global_dtors", [](mlir::Attribute attr) {
- auto dtorAttr = mlir::cast<cir::GlobalDtorAttr>(attr);
- return std::make_pair(dtorAttr.getName(),
- dtorAttr.getPriority());
- });
+ buildCtorDtorList(
+ module, cir::CIRDialect::getGlobalDtorsAttrName(), "llvm.global_dtors",
+ [](mlir::Attribute attr) {
+ auto dtorAttr = mlir::cast<cir::GlobalDtorAttr>(attr);
+ StringRef associated;
+ if (mlir::FlatSymbolRefAttr assoc = dtorAttr.getAssociated())
+ associated = assoc.getValue();
+ return cir::GlobalCtorDtorEntry(dtorAttr.getName(),
+ dtorAttr.getPriority(), associated);
+ });
// Emit @llvm.global.annotations from the previously-collected entries.
buildGlobalAnnotationsVar(module);
diff --git a/clang/test/CIR/CodeGen/global-tls-templates.cpp b/clang/test/CIR/CodeGen/global-tls-templates.cpp
index a3176dbf9d516..8c896a4acf0af 100644
--- a/clang/test/CIR/CodeGen/global-tls-templates.cpp
+++ b/clang/test/CIR/CodeGen/global-tls-templates.cpp
@@ -13,13 +13,13 @@ struct CtorDtor {
template<typename T>
thread_local T tls_templ = {get_i()};
-// CIR-BEFORE-LPP-LABEL: cir.global linkonce_odr comdat tls_model = tls_dyn tls_refs = <"_ZTW9tls_templIiE", "_ZTH9tls_templIiE", "_ZGV9tls_templIiE"> @_Z9tls_templIiE = ctor : !s32i {
+// CIR-BEFORE-LPP-LABEL: cir.global linkonce_odr comdat tls_model = tls_dyn tls_refs = <"_ZTW9tls_templIiE", "_ZTH9tls_templIiE", "_ZGV9tls_templIiE"> dynamic_init_guard<"_ZGV9tls_templIiE"> @_Z9tls_templIiE = ctor : !s32i {
// CIR-BEFORE-LPP: %[[GET_GLOB:.*]] = cir.get_global thread_local @_Z9tls_templIiE : !cir.ptr<!s32i>
// CIR-BEFORE-LPP: %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i {llvm.noundef})
// CIR-BEFORE-LPP: cir.store{{.*}} %[[CALL]], %[[GET_GLOB]] : !s32i, !cir.ptr<!s32i>
// CIR-BEFORE-LPP: }
//
-// CIR-BEFORE-LPP-LABEL: cir.global linkonce_odr comdat tls_model = tls_dyn tls_refs = <"_ZTW9tls_templI8CtorDtorE", "_ZTH9tls_templI8CtorDtorE", "_ZGV9tls_templI8CtorDtorE"> @_Z9tls_templI8CtorDtorE = ctor : !rec_CtorDtor {
+// CIR-BEFORE-LPP-LABEL: cir.global linkonce_odr comdat tls_model = tls_dyn tls_refs = <"_ZTW9tls_templI8CtorDtorE", "_ZTH9tls_templI8CtorDtorE", "_ZGV9tls_templI8CtorDtorE"> dynamic_init_guard<"_ZGV9tls_templI8CtorDtorE"> @_Z9tls_templI8CtorDtorE = ctor : !rec_CtorDtor {
// CIR-BEFORE-LPP: %[[GET_GLOB:.*]] = cir.get_global thread_local @_Z9tls_templI8CtorDtorE : !cir.ptr<!rec_CtorDtor>
// CIR-BEFORE-LPP: %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i {llvm.noundef})
// CIR-BEFORE-LPP: cir.call @_ZN8CtorDtorC1Ei(%[[GET_GLOB]], %[[CALL]]) : (!cir.ptr<!rec_CtorDtor>
@@ -53,7 +53,7 @@ thread_local T tls_templ = {get_i()};
// CIR: cir.global "private" linkonce_odr comdat("_Z9tls_templIiE") tls_model = tls_dyn @_ZGV9tls_templIiE = #cir.int<0> : !s64i
// Global: int
-// CIR: cir.global linkonce_odr comdat tls_model = tls_dyn tls_refs = <"_ZTW9tls_templIiE", "_ZTH9tls_templIiE", "_ZGV9tls_templIiE"> @_Z9tls_templIiE = #cir.int<0> : !s32i
+// CIR: cir.global linkonce_odr comdat tls_model = tls_dyn tls_refs = <"_ZTW9tls_templIiE", "_ZTH9tls_templIiE", "_ZGV9tls_templIiE"> dynamic_init_guard<"_ZGV9tls_templIiE"> @_Z9tls_templIiE = #cir.int<0> : !s32i
// Init Func: int
// CIR: cir.func internal private @[[INT_INIT]]() {
@@ -74,7 +74,7 @@ thread_local T tls_templ = {get_i()};
// Global: Ctor/Dotr:
-// CIR: cir.global linkonce_odr comdat tls_model = tls_dyn tls_refs = <"_ZTW9tls_templI8CtorDtorE", "_ZTH9tls_templI8CtorDtorE", "_ZGV9tls_templI8CtorDtorE"> @_Z9tls_templI8CtorDtorE = #cir.zero : !rec_CtorDtor
+// CIR: cir.global linkonce_odr comdat tls_model = tls_dyn tls_refs = <"_ZTW9tls_templI8CtorDtorE", "_ZTH9tls_templI8CtorDtorE", "_ZGV9tls_templI8CtorDtorE"> dynamic_init_guard<"_ZGV9tls_templI8CtorDtorE"> @_Z9tls_templI8CtorDtorE = #cir.zero : !rec_CtorDtor
// Init Func: Ctor/Dtor:
// CIR: cir.func internal private @[[CTOR_DTOR_INIT]]() {
diff --git a/clang/test/CIR/CodeGen/static-local-info.cpp b/clang/test/CIR/CodeGen/static-local-info.cpp
index deb4c13e8726d..a34f69aca05c0 100644
--- a/clang/test/CIR/CodeGen/static-local-info.cpp
+++ b/clang/test/CIR/CodeGen/static-local-info.cpp
@@ -3,7 +3,7 @@
// CIRGen attaches the VarDecl facts LoweringPrepare needs (isLocalVarDecl,
// TLSKind, isInline, TemplateSpecializationKind) to static-local guarded
-// globals as a #cir.static_local_info attribute, so the facts survive without
+// globals as a #cir.dynamic_init_info attribute, so the facts survive without
// a live ASTContext. This is orthogonal to the #cir.var.decl AST handle, which
// is still attached for consumers that need arbitrary AST properties.
@@ -26,8 +26,8 @@ int tls() {
// the retained AST handle.
// CHECK: @_ZZ3tlsvE1s
// CHECK-SAME: ast(#cir.var.decl.ast)
-// CHECK-SAME: static_local_info<local = true, tls = dynamic, is_inline = false, tsk = undeclared>
+// CHECK-SAME: dynamic_init_info<local = true, tls = dynamic, is_inline = false, tsk = undeclared>
// CHECK: @_ZZ7regularvE1s
// CHECK-SAME: ast(#cir.var.decl.ast)
-// CHECK-SAME: static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+// CHECK-SAME: dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
diff --git a/clang/test/CIR/CodeGen/static-local.cpp b/clang/test/CIR/CodeGen/static-local.cpp
index f97ee53a909d3..8461a7698e51f 100644
--- a/clang/test/CIR/CodeGen/static-local.cpp
+++ b/clang/test/CIR/CodeGen/static-local.cpp
@@ -31,33 +31,33 @@
// CIR-DAG: cir.global "private" internal dso_local @_ZGVZ23array_static_local_dtorvE2sm = #cir.int<0> : !s64i
// LLVM-DAG: @_ZGVZ23array_static_local_dtorvE2sm = internal global i64 0
-// CIR-BOTH-DAG: cir.global linkonce_odr comdat static_local_guard<"_ZGVZ10getInlineAvE1a"> @_ZZ10getInlineAvE1a = #cir.zero : !rec_A
+// CIR-BOTH-DAG: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVZ10getInlineAvE1a"> @_ZZ10getInlineAvE1a = #cir.zero : !rec_A
// LLVM-DAG: @_ZZ10getInlineAvE1a = linkonce_odr global %class.A zeroinitializer, comdat
// CIR-BOTH-DAG: cir.global "private" internal dso_local @_ZZ23referenced_inside_constvE12static_local = #cir.int<42> : !s32i
// LLVM-DAG: @_ZZ23referenced_inside_constvE12static_local = internal global i32 42
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ17referenced_insidevE12static_local"> @_ZZ17referenced_insidevE12static_local = #cir.int<0> : !s32i
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ17referenced_insidevE12static_local"> @_ZZ17referenced_insidevE12static_local = #cir.int<0> : !s32i
// LLVM-DAG: @_ZZ17referenced_insidevE12static_local = internal global i32 0
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ14test_ctor_dtorvE9ctor_dtor"> @_ZZ14test_ctor_dtorvE9ctor_dtor = #cir.zero : !rec_HasCtorDtor
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ14test_ctor_dtorvE9ctor_dtor"> @_ZZ14test_ctor_dtorvE9ctor_dtor = #cir.zero : !rec_HasCtorDtor
// LLVM-DAG: @_ZZ14test_ctor_dtorvE9ctor_dtor = internal global %struct.HasCtorDtor zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ9test_dtorvE4dtor"> @_ZZ9test_dtorvE4dtor = #cir.zero : !rec_HasDtor
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ9test_dtorvE4dtor"> @_ZZ9test_dtorvE4dtor = #cir.zero : !rec_HasDtor
// LLVM-DAG: @_ZZ9test_dtorvE4dtor = internal global %struct.HasDtor zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ8self_refiE12magic_static"> @_ZZ8self_refiE12magic_static = #cir.int<0> : !s32i
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ8self_refiE12magic_static"> @_ZZ8self_refiE12magic_static = #cir.int<0> : !s32i
// LLVM-DAG: @_ZZ8self_refiE12magic_static = internal global i32 0
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZN8InMember8mem_funcEiiiiE12magic_static"> @_ZZN8InMember8mem_funcEiiiiE12magic_static = #cir.int<0> : !s32i
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZN8InMember8mem_funcEiiiiE12magic_static"> @_ZZN8InMember8mem_funcEiiiiE12magic_static = #cir.int<0> : !s32i
// LLVM-DAG: @_ZZN8InMember8mem_funcEiiiiE12magic_static = internal global i32 0
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ10multi_refsiiiiiiiE17refs_magic_static"> @_ZZ10multi_refsiiiiiiiE17refs_magic_static = #cir.zero : !rec_A
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ10multi_refsiiiiiiiE17refs_magic_static"> @_ZZ10multi_refsiiiiiiiE17refs_magic_static = #cir.zero : !rec_A
// LLVM-DAG: @_ZZ10multi_refsiiiiiiiE17refs_magic_static = internal global %class.A zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ10multi_refsiiiiiiiE12magic_static"> @_ZZ10multi_refsiiiiiiiE12magic_static = #cir.zero : !rec_A
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ10multi_refsiiiiiiiE12magic_static"> @_ZZ10multi_refsiiiiiiiE12magic_static = #cir.zero : !rec_A
// LLVM-DAG: @_ZZ10multi_refsiiiiiiiE12magic_static = internal global %class.A zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ29references_param_and_previousiE17refs_magic_static"> @_ZZ29references_param_and_previousiE17refs_magic_static = #cir.int<0> : !s32i
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ29references_param_and_previousiE17refs_magic_static"> @_ZZ29references_param_and_previousiE17refs_magic_static = #cir.int<0> : !s32i
// LLVM-DAG: @_ZZ29references_param_and_previousiE17refs_magic_static = internal global i32 0
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ29references_param_and_previousiE12magic_static"> @_ZZ29references_param_and_previousiE12magic_static = #cir.int<0> : !s32i
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ29references_param_and_previousiE12magic_static"> @_ZZ29references_param_and_previousiE12magic_static = #cir.int<0> : !s32i
// LLVM-DAG: @_ZZ29references_param_and_previousiE12magic_static = internal global i32 0
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ1fvE1a"> @_ZZ1fvE1a = #cir.zero : !rec_A
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ1fvE1a"> @_ZZ1fvE1a = #cir.zero : !rec_A
// LLVM-DAG: @_ZZ1fvE1a = internal global %class.A zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ8ref_initvE1y"> @_ZZ8ref_initvE1y = #cir.ptr<null> : !cir.ptr<!s32i>
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ8ref_initvE1y"> @_ZZ8ref_initvE1y = #cir.ptr<null> : !cir.ptr<!s32i>
// LLVM-DAG: @_ZZ8ref_initvE1y = internal global ptr null
-// CIR-BOTH-DAG: cir.global "private" internal dso_local static_local_guard<"_ZGVZ23array_static_local_dtorvE2sm"> @_ZZ23array_static_local_dtorvE2sm = #cir.zero : !cir.array<!rec_HasCtorDtor x 2>
+// CIR-BOTH-DAG: cir.global "private" internal dso_local dynamic_init_guard<"_ZGVZ23array_static_local_dtorvE2sm"> @_ZZ23array_static_local_dtorvE2sm = #cir.zero : !cir.array<!rec_HasCtorDtor x 2>
// LLVM-DAG: @_ZZ23array_static_local_dtorvE2sm = internal global [2 x %struct.HasCtorDtor] zeroinitializer
// CIR-BOTH-DAG: cir.global "private" internal dso_local @_ZZ15use_static_declvE1p = #cir.global_view<@_ZZ15use_static_declvE1x> : !cir.ptr<!s32i>
// LLVM-DAG: @_ZZ15use_static_declvE1p = internal global ptr @_ZZ15use_static_declvE1x
@@ -752,7 +752,7 @@ int referenced_inside_const() {
// Reference-typed static local with a non-constant initializer. The
// cir.get_global emitted inside the initializer region for the reference
// must carry the static_local marker so that it matches the
-// static_local_guard attribute on the corresponding cir.global. Otherwise
+// dynamic_init_guard attribute on the corresponding cir.global. Otherwise
// the cir.get_global verifier rejects the IR with
// "static_local attribute mismatch".
int g = 5;
@@ -792,7 +792,7 @@ int &ref_init() {
// Static local array with a non-trivial destructor. The cir.get_global
// emitted inside the dtor region for the array must carry the static_local
-// marker so that it matches the static_local_guard attribute on the
+// marker so that it matches the dynamic_init_guard attribute on the
// corresponding cir.global. Otherwise the cir.get_global verifier rejects
// the IR with "static_local attribute mismatch".
void array_static_local_dtor() {
diff --git a/clang/test/CIR/CodeGen/static-members.cpp b/clang/test/CIR/CodeGen/static-members.cpp
index 4b6bed6a0d1cd..2ebd3dc1933c3 100644
--- a/clang/test/CIR/CodeGen/static-members.cpp
+++ b/clang/test/CIR/CodeGen/static-members.cpp
@@ -3,7 +3,7 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck %s -check-prefix=LLVM --input-file=%t-cir.ll
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
-// RUN: FileCheck %s -check-prefix=OGCG --input-file=%t.ll
+// RUN: FileCheck %s -check-prefix=LLVM --input-file=%t.ll
struct HasDtor {
~HasDtor();
@@ -12,87 +12,161 @@ struct S {
static inline HasDtor hd;
};
-// CIR: cir.global linkonce_odr comdat @_ZN1S2hdE = #cir.zero : !rec_HasDtor
-
-// CIR: cir.func internal private @__cxx_global_var_init() {
-// CIR: %[[HD:.*]] = cir.get_global @_ZN1S2hdE : !cir.ptr<!rec_HasDtor>
-// CIR: %[[DTOR:.*]] = cir.get_global @_ZN7HasDtorD1Ev : !cir.ptr<!cir.func<(!cir.ptr<!rec_HasDtor>)>>
-// CIR: %[[DTOR_CAST:.*]] = cir.cast bitcast %[[DTOR]] : !cir.ptr<!cir.func<(!cir.ptr<!rec_HasDtor>)>> -> !cir.ptr<!cir.func<(!cir.ptr<!void>)>>
-// CIR: %[[HD_CAST:.*]] = cir.cast bitcast %[[HD]] : !cir.ptr<!rec_HasDtor> -> !cir.ptr<!void>
-// CIR: %[[HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr<!u8i>
-// CIR: cir.call @__cxa_atexit(%[[DTOR_CAST]], %[[HD_CAST]], %[[HANDLE]])
-
-// LLVM: @_ZN1S2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat
-// LLVM: @_ZN5Outer5Inner2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat
-
-// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_static_members.cpp, ptr null }]
-// LLVM: define internal void @__cxx_global_var_init()
-// LLVM: call i32 @__cxa_atexit(ptr @_ZN7HasDtorD1Ev, ptr @_ZN1S2hdE, ptr @__dso_handle)
-
-// FIXME(cir): OGCG has a guard variable for this case that we don't generate in CIR.
-// This is needed because the variable linkonce_odr linkage.
-
-// OGCG: @_ZN1S2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat
-// OGCG: @_ZGVN1S2hdE = linkonce_odr global i64 0, comdat($_ZN1S2hdE)
-// OGCG: @_ZN5Outer5Inner2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat
-// OGCG: @_ZGVN5Outer5Inner2hdE = linkonce_odr global i64 0, comdat($_ZN5Outer5Inner2hdE)
-// OGCG: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [
-// OGCG-SAME: { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @_ZN1S2hdE },
-// OGCG-SAME: { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.1, ptr @_ZN5Outer5Inner2hdE }]
-
-// OGCG: define internal void @__cxx_global_var_init() {{.*}} section ".text.startup" comdat($_ZN1S2hdE) {
-// OGCG: %[[GUARD:.*]] = load atomic i8, ptr @_ZGVN1S2hdE acquire
-// OGCG: %[[UNINIT:.*]] = icmp eq i8 %[[GUARD]], 0
-// OGCG: br i1 %[[UNINIT]], label %[[INIT_CHECK:.*]], label %[[INIT_END:.*]]
-// OGCG: [[INIT_CHECK:.*]]:
-// OGCG: %[[GUARD_ACQUIRE:.*]] = call i32 @__cxa_guard_acquire(ptr @_ZGVN1S2hdE)
-// OGCG: %[[TOBOOL:.*]] = icmp ne i32 %[[GUARD_ACQUIRE]], 0
-// OGCG: br i1 %[[TOBOOL]], label %[[INIT:.*]], label %[[INIT_END]]
-// OGCG: [[INIT:.*]]:
-// OGCG: %[[ATEXIT:.*]] = call i32 @__cxa_atexit(ptr @_ZN7HasDtorD1Ev, ptr @_ZN1S2hdE, ptr @__dso_handle)
-// OGCG: call void @__cxa_guard_release(ptr @_ZGVN1S2hdE)
-// OGCG: br label %[[INIT_END]]
-// OGCG: [[INIT_END]]:
+// CIR: module @
+// CIR-SAME: cir.global_ctors = [#cir.global_ctor<"__cxx_global_var_init", 65535, @_ZN1S2hdE>, #cir.global_ctor<"__cxx_global_var_init.1", 65535, @_ZN5Outer5Inner2hdE>, #cir.global_ctor<"__cxx_global_var_init.2", 65535, @_ZN13NonThreadSafeIiE1fE>]
+
+// Guard variables.
+// CIR-DAG: cir.global "private" linkonce_odr comdat("_ZN1S2hdE") @_ZGVN1S2hdE = #cir.int<0> : !s64i
+// LLVM-DAG: @_ZGVN1S2hdE = linkonce_odr global i64 0, comdat($_ZN1S2hdE), align 8
+// CIR-DAG: cir.global "private" linkonce_odr comdat("_ZN5Outer5Inner2hdE") @_ZGVN5Outer5Inner2hdE = #cir.int<0> : !s64i
+// LLVM-DAG: @_ZGVN5Outer5Inner2hdE = linkonce_odr global i64 0, comdat($_ZN5Outer5Inner2hdE), align 8
+// CIR-DAG: cir.global "private" linkonce_odr comdat("_ZN13NonThreadSafeIiE1fE") @_ZGVN13NonThreadSafeIiE1fE = #cir.int<0> : !s64i
+// LLVM-DAG: @_ZGVN13NonThreadSafeIiE1fE = linkonce_odr global i64 0, comdat($_ZN13NonThreadSafeIiE1fE), align 8
+
+// LLVM-DAG: @_ZN1S2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat, align 1
+// LLVM-DAG: @_ZN5Outer5Inner2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat, align 1
+// LLVM-DAG: @_ZN13NonThreadSafeIiE1fE = linkonce_odr global i32 0, comdat, align 4
+// LLVM: @llvm.global_ctors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @_ZN1S2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.1, ptr @_ZN5Outer5Inner2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.2, ptr @_ZN13NonThreadSafeIiE1fE }]
+
+
+// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN1S2hdE"> @_ZN1S2hdE = #cir.zero : !rec_HasDtor align(1) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
+// CIR-LABEL: cir.func internal private @__cxx_global_var_init() {
+// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN1S2hdE : !cir.ptr<!s64i>
+// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
+// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) syncscope(system) atomic(acquire) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
+// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s8i
+// CIR: %[[CMP:.*]] = cir.cmp eq %[[LOAD_GUARD]], %[[ZERO]] : !s8i
+// CIR: cir.if %[[CMP]] {
+// CIR: %[[ACQUIRE_GUARD:.*]] = cir.call @__cxa_guard_acquire(%[[GET_GUARD]]) : (!cir.ptr<!s64i>) -> !s32i
+// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
+// CIR: %[[CMP:.*]] = cir.cmp ne %[[ACQUIRE_GUARD]], %[[ZERO]] : !s32i
+// CIR: cir.if %[[CMP]] {
+// CIR: %[[GET_HD:.*]] = cir.get_global @_ZN1S2hdE : !cir.ptr<!rec_HasDtor>
+// CIR: %[[GET_DTOR:.*]] = cir.get_global @_ZN7HasDtorD1Ev : !cir.ptr<!cir.func<(!cir.ptr<!rec_HasDtor>)>>
+// CIR: %[[CAST_DTOR:.*]] = cir.cast bitcast %[[GET_DTOR]] : !cir.ptr<!cir.func<(!cir.ptr<!rec_HasDtor>)>> -> !cir.ptr<!cir.func<(!cir.ptr<!void>)>>
+// CIR: %[[CAST_HD:.*]] = cir.cast bitcast %[[GET_HD]] : !cir.ptr<!rec_HasDtor> -> !cir.ptr<!void>
+// CIR: %[[DSO_HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr<!u8i>
+// CIR: %[[AT_EXIT:.*]] = cir.call @__cxa_atexit(%[[CAST_DTOR]], %[[CAST_HD]], %[[DSO_HANDLE]]) : (!cir.ptr<!cir.func<(!cir.ptr<!void>)>>, !cir.ptr<!void>, !cir.ptr<!u8i>) -> !s32i
+// CIR: cir.call @__cxa_guard_release(%[[GET_GUARD]]) : (!cir.ptr<!s64i>) -> ()
+// CIR: }
+// CIR: }
+// CIR: cir.return
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init()
+// LLVM: %[[LOAD_GUARD:.*]] = load atomic i8, ptr @_ZGVN1S2hdE acquire, align 8
+// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
+// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET:.*]]
+
+// LLVM: [[UNINIT]]:
+// LLVM: %[[ACQUIRE_GUARD:.*]] = call i32 @__cxa_guard_acquire(ptr @_ZGVN1S2hdE)
+// LLVM: %[[CMP:.*]] = icmp ne i32 %[[ACQUIRE_GUARD]], 0
+// CIR leaves an extra 'block' for this target, but both go to 'ret' via only
+// empty blocks.
+// LLVM: br i1 %[[CMP]], label %[[DO_INIT:.*]], label %{{.*}}
+
+// LLVM: [[DO_INIT]]:
+// LLVM: %[[AT_EXIT:.*]] = call i32 @__cxa_atexit(ptr @_ZN7HasDtorD1Ev, ptr @_ZN1S2hdE, ptr @__dso_handle)
+// LLVM: call void @__cxa_guard_release(ptr @_ZGVN1S2hdE)
+// CIR leaves an extra 'block' for this target, but both go to 'ret' via only
+// empty blocks.
+// LLVM: br label %{{.*}}
+
+// LLVM: [[RET]]:
+// LLVM: ret void
struct Outer {
struct Inner {
static inline HasDtor hd;
};
};
+// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN5Outer5Inner2hdE"> @_ZN5Outer5Inner2hdE = #cir.zero : !rec_HasDtor align(1) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
+// CIR-LABEL: cir.func internal private @__cxx_global_var_init.1() {
+// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN5Outer5Inner2hdE : !cir.ptr<!s64i>
+// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
+// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) syncscope(system) atomic(acquire) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
+// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s8i
+// CIR: %[[CMP:.*]] = cir.cmp eq %[[LOAD_GUARD]], %[[ZERO]] : !s8i
+// CIR: cir.if %[[CMP]] {
+// CIR: %[[ACQUIRE_GUARD:.*]] = cir.call @__cxa_guard_acquire(%[[GET_GUARD]]) : (!cir.ptr<!s64i>) -> !s32i
+// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
+// CIR: %[[CMP:.*]] = cir.cmp ne %[[ACQUIRE_GUARD]], %[[ZERO]] : !s32i
+// CIR: cir.if %[[CMP]] {
+// CIR: %[[GET_HD:.*]] = cir.get_global @_ZN5Outer5Inner2hdE : !cir.ptr<!rec_HasDtor>
+// CIR: %[[GET_DTOR:.*]] = cir.get_global @_ZN7HasDtorD1Ev : !cir.ptr<!cir.func<(!cir.ptr<!rec_HasDtor>)>>
+// CIR: %[[CAST_DTOR:.*]] = cir.cast bitcast %[[GET_DTOR]] : !cir.ptr<!cir.func<(!cir.ptr<!rec_HasDtor>)>> -> !cir.ptr<!cir.func<(!cir.ptr<!void>)>>
+// CIR: %[[CAST_HD:.*]] = cir.cast bitcast %[[GET_HD]] : !cir.ptr<!rec_HasDtor> -> !cir.ptr<!void>
+// CIR: %[[DSO_HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr<!u8i>
+// CIR: %[[AT_EXIT:.*]] = cir.call @__cxa_atexit(%[[CAST_DTOR]], %[[CAST_HD]], %[[DSO_HANDLE]]) : (!cir.ptr<!cir.func<(!cir.ptr<!void>)>>, !cir.ptr<!void>, !cir.ptr<!u8i>) -> !s32i
+// CIR: cir.call @__cxa_guard_release(%[[GET_GUARD]]) : (!cir.ptr<!s64i>) -> ()
+// CIR: }
+// CIR: }
+// CIR: cir.return
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init.1()
+// LLVM: %[[LOAD_GUARD:.*]] = load atomic i8, ptr @_ZGVN5Outer5Inner2hdE acquire, align 8
+// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
+// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET:.*]]
+
+// LLVM: [[UNINIT]]:
+// LLVM: %[[ACQUIRE_GUARD:.*]] = call i32 @__cxa_guard_acquire(ptr @_ZGVN5Outer5Inner2hdE)
+// LLVM: %[[CMP:.*]] = icmp ne i32 %[[ACQUIRE_GUARD]], 0
+// CIR leaves an extra 'block' for this target, but both go to 'ret' via only
+// empty blocks.
+// LLVM: br i1 %[[CMP]], label %[[DO_INIT:.*]], label %{{.*}}
+
+// LLVM: [[DO_INIT]]:
+// LLVM: %[[AT_EXIT:.*]] = call i32 @__cxa_atexit(ptr @_ZN7HasDtorD1Ev, ptr @_ZN5Outer5Inner2hdE, ptr @__dso_handle)
+// LLVM: call void @__cxa_guard_release(ptr @_ZGVN5Outer5Inner2hdE)
+// CIR leaves an extra 'block' for this target, but both go to 'ret' via only
+// empty blocks.
+// LLVM: br label %{{.*}}
+
+// LLVM: [[RET]]:
+// LLVM: ret void
+
+// Not thread-safe example(because not inline), so doesn't have guard/release.
+int get_i();
+template <typename T> struct NonThreadSafe {
+ static T f;
+};
+
+template <typename T> T NonThreadSafe<T>::f = get_i();
+
+int useNonThreadSafe() {
+ return NonThreadSafe<int>::f;
+}
+
+// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN13NonThreadSafeIiE1fE"> @_ZN13NonThreadSafeIiE1fE = #cir.int<0> : !s32i align(4) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = false, tsk = implicit_instantiation>
+
+// CIR-LABEL: cir.func internal private @__cxx_global_var_init.2() {
+// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN13NonThreadSafeIiE1fE : !cir.ptr<!s64i>
+// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
+// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
+// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s8i
+// CIR: %[[CMP:.*]] = cir.cmp eq %[[LOAD_GUARD]], %[[ZERO]] : !s8i
+// CIR: cir.if %[[CMP]] {
+// CIR: %[[ONE:.*]] = cir.const #cir.int<1> : !s64i
+// CIR: cir.store %[[ONE]], %[[GET_GUARD]] : !s64i, !cir.ptr<!s64i>
+// CIR: %[[GET_F:.*]] = cir.get_global @_ZN13NonThreadSafeIiE1fE : !cir.ptr<!s32i>
+// CIR: %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i {llvm.noundef})
+// CIR: cir.store align(4) %[[CALL]], %[[GET_F]] : !s32i, !cir.ptr<!s32i>
+// CIR: }
+// CIR: cir.return
+// CIR: }
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init.2()
+// LLVM: %[[LOAD_GUARD:.*]] = load i8, ptr @_ZGVN13NonThreadSafeIiE1fE, align 8
+// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
+// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET2:.*]]
+
+// LLVM: [[UNINIT]]:
+// LLVM-NOT: call {{.*}}@__cxa_guard_acquire
+// LLVM: store i{{.*}} 1, ptr @_ZGVN13NonThreadSafeIiE1fE
+// LLVM: %[[CALL:.*]] = call noundef i32 @_Z5get_iv()
+// LLVM: store i32 %[[CALL]], ptr @_ZN13NonThreadSafeIiE1fE
+// LLVM-NOT: call {{.*}}@__cxa_guard_release
+// LLVM: br label %{{.*}}
+
+// LLVM: [[RET2]]:
+// LLVM: ret void
-// CIR: cir.global linkonce_odr comdat @_ZN5Outer5Inner2hdE = #cir.zero : !rec_HasDtor
-// CIR: cir.func internal private @__cxx_global_var_init.1()
-// CIR: %[[HD:.*]] = cir.get_global @_ZN5Outer5Inner2hdE : !cir.ptr<!rec_HasDtor>
-// CIR: %[[DTOR:.*]] = cir.get_global @_ZN7HasDtorD1Ev : !cir.ptr<!cir.func<(!cir.ptr<!rec_HasDtor>)>>
-// CIR: %[[DTOR_CAST:.*]] = cir.cast bitcast %[[DTOR]] : !cir.ptr<!cir.func<(!cir.ptr<!rec_HasDtor>)>> -> !cir.ptr<!cir.func<(!cir.ptr<!void>)>>
-// CIR: %[[HD_CAST:.*]] = cir.cast bitcast %[[HD]] : !cir.ptr<!rec_HasDtor> -> !cir.ptr<!void>
-// CIR: %[[HANDLE:.*]] = cir.get_global @__dso_handle : !cir.ptr<!u8i>
-// CIR: cir.call @__cxa_atexit(%[[DTOR_CAST]], %[[HD_CAST]], %[[HANDLE]]) : (!cir.ptr<!cir.func<(!cir.ptr<!void>)>>, !cir.ptr<!void>, !cir.ptr<!u8i>) -> !s32i
-
-// LLVM: define internal void @__cxx_global_var_init.1()
-// LLVM: call i32 @__cxa_atexit(ptr @_ZN7HasDtorD1Ev, ptr @_ZN5Outer5Inner2hdE, ptr @__dso_handle)
-
-// OGCG: define internal void @__cxx_global_var_init.1() {{.*}} section ".text.startup" comdat($_ZN5Outer5Inner2hdE) {
-// OGCG: %[[GUARD:.*]] = load atomic i8, ptr @_ZGVN5Outer5Inner2hdE acquire
-// OGCG: %[[UNINIT:.*]] = icmp eq i8 %[[GUARD]], 0
-// OGCG: br i1 %[[UNINIT]], label %[[INIT_CHECK:.*]], label %[[INIT_END:.*]]
-// OGCG: [[INIT_CHECK:.*]]:
-// OGCG: %[[GUARD_ACQUIRE:.*]] = call i32 @__cxa_guard_acquire(ptr @_ZGVN5Outer5Inner2hdE)
-// OGCG: %[[TOBOOL:.*]] = icmp ne i32 %[[GUARD_ACQUIRE]], 0
-// OGCG: br i1 %[[TOBOOL]], label %[[INIT:.*]], label %[[INIT_END]]
-// OGCG: [[INIT:.*]]:
-// OGCG: %[[ATEXIT:.*]] = call i32 @__cxa_atexit(ptr @_ZN7HasDtorD1Ev, ptr @_ZN5Outer5Inner2hdE, ptr @__dso_handle)
-// OGCG: call void @__cxa_guard_release(ptr @_ZGVN5Outer5Inner2hdE)
-// OGCG: br label %[[INIT_END]]
-// OGCG: [[INIT_END]]:
-
-
-// CIR: cir.func internal private @_GLOBAL__sub_I_static_members.cpp()
-// CIR: cir.call @__cxx_global_var_init()
-
-// LLVM: define internal void @_GLOBAL__sub_I_static_members.cpp()
-// LLVM: call void @__cxx_global_var_init()
-
-// Note: OGCG doesn't actually generate this function, and just adds these to
-// the `llvm.global_ctors` instead. It isn't clear whether that is meaningful
-// or important.
diff --git a/clang/test/CIR/CodeGen/thread-local-in-func.cpp b/clang/test/CIR/CodeGen/thread-local-in-func.cpp
index 003d8176257b7..3faeb1b8bf2d4 100644
--- a/clang/test/CIR/CodeGen/thread-local-in-func.cpp
+++ b/clang/test/CIR/CodeGen/thread-local-in-func.cpp
@@ -17,17 +17,17 @@
// CIR-DAG: cir.global "private" internal tls_model = tls_dyn dso_local @_ZGVZ8test_intiE4init = #cir.int<0> : !s8i
// LLVM-BOTH-DAG: @_ZGVZ8test_intiE4init = internal thread_local global i8 0
-// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local static_local_guard<"_ZGVZ13test_ctordtoriE4init"> @_ZZ13test_ctordtoriE4init = #cir.zero : !rec_CtorDtor
+// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local dynamic_init_guard<"_ZGVZ13test_ctordtoriE4init"> @_ZZ13test_ctordtoriE4init = #cir.zero : !rec_CtorDtor
// LLVM-BOTH-DAG: @_ZZ13test_ctordtoriE4init = internal thread_local global %struct.CtorDtor zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local static_local_guard<"_ZGVZ13test_ctordtoriE10const_init"> @_ZZ13test_ctordtoriE10const_init = #cir.zero : !rec_CtorDtor
+// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local dynamic_init_guard<"_ZGVZ13test_ctordtoriE10const_init"> @_ZZ13test_ctordtoriE10const_init = #cir.zero : !rec_CtorDtor
// LLVM-BOTH-DAG: @_ZZ13test_ctordtoriE10const_init = internal thread_local global %struct.CtorDtor zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local static_local_guard<"_ZGVZ9test_dtoriE10const_init"> @_ZZ9test_dtoriE10const_init = #cir.zero : !rec_Dtor
+// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local dynamic_init_guard<"_ZGVZ9test_dtoriE10const_init"> @_ZZ9test_dtoriE10const_init = #cir.zero : !rec_Dtor
// LLVM-BOTH-DAG: @_ZZ9test_dtoriE10const_init = internal thread_local global %struct.Dtor zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local static_local_guard<"_ZGVZ9test_ctoriE4init"> @_ZZ9test_ctoriE4init = #cir.zero : !rec_Ctor
+// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local dynamic_init_guard<"_ZGVZ9test_ctoriE4init"> @_ZZ9test_ctoriE4init = #cir.zero : !rec_Ctor
// LLVM-BOTH-DAG: @_ZZ9test_ctoriE4init = internal thread_local global %struct.Ctor zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local static_local_guard<"_ZGVZ9test_ctoriE10const_init"> @_ZZ9test_ctoriE10const_init = #cir.zero : !rec_Ctor
+// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local dynamic_init_guard<"_ZGVZ9test_ctoriE10const_init"> @_ZZ9test_ctoriE10const_init = #cir.zero : !rec_Ctor
// LLVM-BOTH-DAG: @_ZZ9test_ctoriE10const_init = internal thread_local global %struct.Ctor zeroinitializer
-// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local static_local_guard<"_ZGVZ8test_intiE4init"> @_ZZ8test_intiE4init = #cir.int<0> : !s32i
+// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local dynamic_init_guard<"_ZGVZ8test_intiE4init"> @_ZZ8test_intiE4init = #cir.int<0> : !s32i
// LLVM-BOTH-DAG: @_ZZ8test_intiE4init = internal thread_local global i32 0
// CIR-BOTH-DAG: cir.global "private" internal tls_model = tls_dyn dso_local @_ZZ8test_intiE10const_init = #cir.int<5> : !s32i
// LLVM-BOTH-DAG: @_ZZ8test_intiE10const_init = internal thread_local global i32 5
diff --git a/clang/test/CIR/CodeGen/tls-model-func-scope.cpp b/clang/test/CIR/CodeGen/tls-model-func-scope.cpp
index e5662acd00df9..99b2e7fdb5049 100644
--- a/clang/test/CIR/CodeGen/tls-model-func-scope.cpp
+++ b/clang/test/CIR/CodeGen/tls-model-func-scope.cpp
@@ -34,8 +34,8 @@ void func() {
// CIR-GD-SAME: cir.default_tls_model = #cir.tls_model<tls_dyn>
// CIR-GD: cir.global "private" internal tls_model = tls_init_exec dso_local @_ZGVZ4funcvE17override_tls_mode = #cir.int<0> : !s8i
// CIR-GD: cir.global "private" internal tls_model = tls_dyn dso_local @_ZGVZ4funcvE16default_tls_mode = #cir.int<0> : !s8i
-// CIR-GD: cir.global "private" internal tls_model = tls_init_exec dso_local static_local_guard<"_ZGVZ4funcvE17override_tls_mode"> @_ZZ4funcvE17override_tls_mode = #cir.int<0> : !s32i
-// CIR-GD: cir.global "private" internal tls_model = tls_dyn dso_local static_local_guard<"_ZGVZ4funcvE16default_tls_mode"> @_ZZ4funcvE16default_tls_mode = #cir.int<0> : !s32i
+// CIR-GD: cir.global "private" internal tls_model = tls_init_exec dso_local dynamic_init_guard<"_ZGVZ4funcvE17override_tls_mode"> @_ZZ4funcvE17override_tls_mode = #cir.int<0> : !s32i
+// CIR-GD: cir.global "private" internal tls_model = tls_dyn dso_local dynamic_init_guard<"_ZGVZ4funcvE16default_tls_mode"> @_ZZ4funcvE16default_tls_mode = #cir.int<0> : !s32i
// LLVM-GD-DAG: @_ZGVZ4funcvE17override_tls_mode = internal thread_local(initialexec) global i8 0
// LLVM-GD-DAG: @_ZGVZ4funcvE16default_tls_mode = internal thread_local global i8 0
@@ -46,8 +46,8 @@ void func() {
// CIR-LD-SAME: cir.default_tls_model = #cir.tls_model<tls_local_dyn>
// CIR-LD: cir.global "private" internal tls_model = tls_init_exec dso_local @_ZGVZ4funcvE17override_tls_mode = #cir.int<0> : !s8i
// CIR-LD: cir.global "private" internal tls_model = tls_local_dyn dso_local @_ZGVZ4funcvE16default_tls_mode = #cir.int<0> : !s8i
-// CIR-LD: cir.global "private" internal tls_model = tls_init_exec dso_local static_local_guard<"_ZGVZ4funcvE17override_tls_mode"> @_ZZ4funcvE17override_tls_mode = #cir.int<0> : !s32i
-// CIR-LD: cir.global "private" internal tls_model = tls_local_dyn dso_local static_local_guard<"_ZGVZ4funcvE16default_tls_mode"> @_ZZ4funcvE16default_tls_mode = #cir.int<0> : !s32i
+// CIR-LD: cir.global "private" internal tls_model = tls_init_exec dso_local dynamic_init_guard<"_ZGVZ4funcvE17override_tls_mode"> @_ZZ4funcvE17override_tls_mode = #cir.int<0> : !s32i
+// CIR-LD: cir.global "private" internal tls_model = tls_local_dyn dso_local dynamic_init_guard<"_ZGVZ4funcvE16default_tls_mode"> @_ZZ4funcvE16default_tls_mode = #cir.int<0> : !s32i
// LLVM-LD-DAG: @_ZGVZ4funcvE17override_tls_mode = internal thread_local(initialexec) global i8 0
// LLVM-LD-DAG: @_ZGVZ4funcvE16default_tls_mode = internal thread_local(localdynamic) global i8 0
@@ -58,8 +58,8 @@ void func() {
// CIR-IE-SAME: cir.default_tls_model = #cir.tls_model<tls_init_exec>
// CIR-IE: cir.global "private" internal tls_model = tls_init_exec dso_local @_ZGVZ4funcvE17override_tls_mode = #cir.int<0> : !s8i
// CIR-IE: cir.global "private" internal tls_model = tls_init_exec dso_local @_ZGVZ4funcvE16default_tls_mode = #cir.int<0> : !s8i
-// CIR-IE: cir.global "private" internal tls_model = tls_init_exec dso_local static_local_guard<"_ZGVZ4funcvE17override_tls_mode"> @_ZZ4funcvE17override_tls_mode = #cir.int<0> : !s32i
-// CIR-IE: cir.global "private" internal tls_model = tls_init_exec dso_local static_local_guard<"_ZGVZ4funcvE16default_tls_mode"> @_ZZ4funcvE16default_tls_mode = #cir.int<0> : !s32i
+// CIR-IE: cir.global "private" internal tls_model = tls_init_exec dso_local dynamic_init_guard<"_ZGVZ4funcvE17override_tls_mode"> @_ZZ4funcvE17override_tls_mode = #cir.int<0> : !s32i
+// CIR-IE: cir.global "private" internal tls_model = tls_init_exec dso_local dynamic_init_guard<"_ZGVZ4funcvE16default_tls_mode"> @_ZZ4funcvE16default_tls_mode = #cir.int<0> : !s32i
// LLVM-IE-DAG: @_ZGVZ4funcvE17override_tls_mode = internal thread_local(initialexec) global i8 0
// LLVM-IE-DAG: @_ZGVZ4funcvE16default_tls_mode = internal thread_local(initialexec) global i8 0
@@ -70,8 +70,8 @@ void func() {
// CIR-LE-SAME: cir.default_tls_model = #cir.tls_model<tls_local_exec>
// CIR-LE: cir.global "private" internal tls_model = tls_init_exec dso_local @_ZGVZ4funcvE17override_tls_mode = #cir.int<0> : !s8i
// CIR-LE: cir.global "private" internal tls_model = tls_local_exec dso_local @_ZGVZ4funcvE16default_tls_mode = #cir.int<0> : !s8i
-// CIR-LE: cir.global "private" internal tls_model = tls_init_exec dso_local static_local_guard<"_ZGVZ4funcvE17override_tls_mode"> @_ZZ4funcvE17override_tls_mode = #cir.int<0> : !s32i
-// CIR-LE: cir.global "private" internal tls_model = tls_local_exec dso_local static_local_guard<"_ZGVZ4funcvE16default_tls_mode"> @_ZZ4funcvE16default_tls_mode = #cir.int<0> : !s32i
+// CIR-LE: cir.global "private" internal tls_model = tls_init_exec dso_local dynamic_init_guard<"_ZGVZ4funcvE17override_tls_mode"> @_ZZ4funcvE17override_tls_mode = #cir.int<0> : !s32i
+// CIR-LE: cir.global "private" internal tls_model = tls_local_exec dso_local dynamic_init_guard<"_ZGVZ4funcvE16default_tls_mode"> @_ZZ4funcvE16default_tls_mode = #cir.int<0> : !s32i
// LLVM-LE-DAG: @_ZGVZ4funcvE17override_tls_mode = internal thread_local(initialexec) global i8 0
// LLVM-LE-DAG: @_ZGVZ4funcvE16default_tls_mode = internal thread_local(localexec) global i8 0
diff --git a/clang/test/CIR/IR/invalid-static-local-info.cir b/clang/test/CIR/IR/invalid-static-local-info.cir
index 1a2047aec2e45..0caf9e18982e3 100644
--- a/clang/test/CIR/IR/invalid-static-local-info.cir
+++ b/clang/test/CIR/IR/invalid-static-local-info.cir
@@ -1,6 +1,6 @@
// RUN: cir-opt %s -verify-diagnostics -split-input-file
-// 'static_local_guard' and 'static_local_info' are only meaningful together:
+// 'dynamic_init_guard' and 'dynamic_init_info' are only meaningful together:
// the guard drives lowering, which reads the info. The GlobalOp verifier
// rejects either without the other, guarding the static-local lowering path
// against hand-written or serialized .cir that would otherwise crash lowering
@@ -9,8 +9,8 @@
!s32i = !cir.int<s, 32>
module {
- // expected-error at +1 {{'static_local_guard' and 'static_local_info' must be present together}}
- cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
+ // expected-error at +1 {{'dynamic_init_guard' and 'dynamic_init_info' must be present together}}
+ cir.global "private" internal dynamic_init_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
}
// -----
@@ -18,7 +18,7 @@ module {
!s32i = !cir.int<s, 32>
module {
- // expected-error at +1 {{'static_local_guard' and 'static_local_info' must be present together}}
+ // expected-error at +1 {{'dynamic_init_guard' and 'dynamic_init_info' must be present together}}
cir.global "private" internal @_ZZ1fvE1x : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
}
diff --git a/clang/test/CIR/IR/invalid-static-local.cir b/clang/test/CIR/IR/invalid-static-local.cir
index fef64ccac5077..31a7b1178d92e 100644
--- a/clang/test/CIR/IR/invalid-static-local.cir
+++ b/clang/test/CIR/IR/invalid-static-local.cir
@@ -4,7 +4,7 @@
module {
-// Global is not marked static_local_guard, but get_global is static_local
+// Global is not marked dynamic_init_guard, but get_global is static_local
cir.global "private" internal @_ZZ1fvE1x : !s32i
cir.func @test_static_local_mismatch() {
@@ -21,9 +21,9 @@ cir.func @test_static_local_mismatch() {
module {
-// Global is marked static_local_guard, but get_global is not static_local
-cir.global "private" internal static_local_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+// Global is marked dynamic_init_guard, but get_global is not static_local
+cir.global "private" internal dynamic_init_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
cir.func @test_static_local_mismatch_reverse() {
// expected-error @below {{static_local attribute mismatch}}
@@ -40,8 +40,8 @@ cir.func @test_static_local_mismatch_reverse() {
module {
// local_init is both static_local and thread_local
-cir.global "private" internal static_local_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+cir.global "private" internal dynamic_init_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
cir.func @test_static_local_and_tls() {
%0 = cir.get_global static_local @_ZZ1fvE1y : !cir.ptr<!s32i>
@@ -64,8 +64,8 @@ cir.func @test_static_local_and_tls() {
module {
// local_init not at function scope.
-cir.global "private" internal static_local_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+cir.global "private" internal dynamic_init_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
cir.global "private" internal @_AnotherGlobal = ctor : !s32i {
// expected-error @below {{'cir.local_init' op expects ancestor op 'cir.func'}}
diff --git a/clang/test/CIR/IR/invalid-tls.cir b/clang/test/CIR/IR/invalid-tls.cir
index a7146c0bde737..9b2b3b1a9af65 100644
--- a/clang/test/CIR/IR/invalid-tls.cir
+++ b/clang/test/CIR/IR/invalid-tls.cir
@@ -16,9 +16,9 @@ module {
!s32i = !cir.int<s, 32>
module {
- // expected-error at +1{{op cannot have both static local and tls references}}
-cir.global "private" internal tls_model = tls_dyn tls_refs = <"asdf", "asdf", "asdf"> static_local_guard<"asdf"> @_ZZ1fvE1y : !s32i
- static_local_info<local = true, tls = dynamic, is_inline = false, tsk = undeclared>
+ // expected-error at +1{{op cannot have a dynamic-init guard combined with ordered tls references}}
+cir.global "private" internal tls_model = tls_dyn tls_refs = <"asdf", "asdf"> dynamic_init_guard<"asdf"> @_ZZ1fvE1y : !s32i
+ dynamic_init_info<local = true, tls = dynamic, is_inline = false, tsk = undeclared>
}
// -----
diff --git a/clang/test/CIR/IR/static-local-info.cir b/clang/test/CIR/IR/static-local-info.cir
index 74d5494cac64b..0ad5eba219c8a 100644
--- a/clang/test/CIR/IR/static-local-info.cir
+++ b/clang/test/CIR/IR/static-local-info.cir
@@ -1,21 +1,21 @@
// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
-// #cir.static_local_info holds plain data (no AST pointer), so it parses and
+// #cir.dynamic_init_info holds plain data (no AST pointer), so it parses and
// prints without a live ASTContext. Check that each field round-trips,
// including the named tls / tsk enum keywords at non-default values.
!s32i = !cir.int<s, 32>
module {
- cir.global "private" internal static_local_guard<"_ZGV7regular"> @regular = #cir.int<0> : !s32i
- static_local_info<
+ cir.global "private" internal dynamic_init_guard<"_ZGV7regular"> @regular = #cir.int<0> : !s32i
+ dynamic_init_info<
local = true, tls = none, is_inline = false, tsk = undeclared>
// CHECK: @regular
- // CHECK-SAME: static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+ // CHECK-SAME: dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
- cir.global "private" internal static_local_guard<"_ZGV3tls"> @tls = #cir.int<0> : !s32i
- static_local_info<
+ cir.global "private" internal dynamic_init_guard<"_ZGV3tls"> @tls = #cir.int<0> : !s32i
+ dynamic_init_info<
local = true, tls = dynamic, is_inline = true, tsk = implicit_instantiation>
// CHECK: @tls
- // CHECK-SAME: static_local_info<local = true, tls = dynamic, is_inline = true, tsk = implicit_instantiation>
+ // CHECK-SAME: dynamic_init_info<local = true, tls = dynamic, is_inline = true, tsk = implicit_instantiation>
}
diff --git a/clang/test/CIR/IR/static-local.cir b/clang/test/CIR/IR/static-local.cir
index b398326a84be6..e5a5733605681 100644
--- a/clang/test/CIR/IR/static-local.cir
+++ b/clang/test/CIR/IR/static-local.cir
@@ -4,26 +4,26 @@
module {
-// Test static_local_guard attribute on global and static_local on get_global.
-// The GlobalOp verifier requires static_local_guard and static_local_info to
+// Test dynamic_init_guard attribute on global and static_local on get_global.
+// The GlobalOp verifier requires dynamic_init_guard and dynamic_init_info to
// be paired, matching what CIRGen emits, so each guarded global also carries
// the info attribute here.
-cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
-// CHECK: cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
+cir.global "private" internal dynamic_init_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+// CHECK: cir.global "private" internal dynamic_init_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i
-cir.global "private" internal static_local_guard<"_HasInitGuard"> @_HasInit : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
-// CHECK: cir.global "private" internal static_local_guard<"_HasInitGuard"> @_HasInit : !s32i
-cir.global "private" internal static_local_guard<"_HasInitGuard2"> @_HasInit2 : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
-// CHECK: cir.global "private" internal static_local_guard<"_HasInitGuard2"> @_HasInit2 : !s32i
-cir.global "private" internal static_local_guard<"_HasInitGuard3"> @_HasInit3 : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
-// CHECK: cir.global "private" internal static_local_guard<"_HasInitGuard3"> @_HasInit3 : !s32i
-cir.global "private" internal static_local_guard<"_HasInitGuard4"> @_HasInit4 : !s32i
- static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared>
-// CHECK: cir.global "private" internal static_local_guard<"_HasInitGuard4"> @_HasInit4 : !s32i
+cir.global "private" internal dynamic_init_guard<"_HasInitGuard"> @_HasInit : !s32i
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+// CHECK: cir.global "private" internal dynamic_init_guard<"_HasInitGuard"> @_HasInit : !s32i
+cir.global "private" internal dynamic_init_guard<"_HasInitGuard2"> @_HasInit2 : !s32i
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+// CHECK: cir.global "private" internal dynamic_init_guard<"_HasInitGuard2"> @_HasInit2 : !s32i
+cir.global "private" internal dynamic_init_guard<"_HasInitGuard3"> @_HasInit3 : !s32i
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+// CHECK: cir.global "private" internal dynamic_init_guard<"_HasInitGuard3"> @_HasInit3 : !s32i
+cir.global "private" internal dynamic_init_guard<"_HasInitGuard4"> @_HasInit4 : !s32i
+ dynamic_init_info<local = true, tls = none, is_inline = false, tsk = undeclared>
+// CHECK: cir.global "private" internal dynamic_init_guard<"_HasInitGuard4"> @_HasInit4 : !s32i
cir.func @test_static_local() {
%0 = cir.get_global static_local @_ZZ1fvE1x : !cir.ptr<!s32i>
>From c4067ba10b23f20575e29a91edfadd2a2d2b0f51 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Tue, 6 Oct 2026 10:33:52 -0700
Subject: [PATCH 2/9] Make sure we add the comdat values to the 'llvm.used'
---
.../Dialect/Transforms/LoweringPrepare.cpp | 49 +++++++++++++++++++
clang/test/CIR/CodeGen/static-members.cpp | 6 ++-
2 files changed, 54 insertions(+), 1 deletion(-)
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 9757d8a6b8550..871a69a468a39 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -221,6 +221,10 @@ struct LoweringPreparePass
cir::IntType guardTy,
cir::GlobalLinkageKind linkage);
+ /// Append 'global' to the module's llvm.used list to make sure it doesn't get
+ /// garbage-collected by the linker.
+ void addUsedGlobal(cir::GlobalOp global);
+
/// Get the guard variable for a static local declaration.
cir::GlobalOp getStaticLocalDeclGuardAddress(llvm::StringRef globalSymName) {
auto it = staticLocalDeclGuardMap.find(globalSymName);
@@ -1399,6 +1403,44 @@ cir::GlobalOp LoweringPreparePass::createGuardGlobalOp(
return g;
}
+void LoweringPreparePass::addUsedGlobal(cir::GlobalOp global) {
+ CIRBaseBuilderTy builder(getContext());
+ mlir::Attribute entry = cir::GlobalViewAttr::get(
+ builder.getVoidPtrTy(),
+ mlir::FlatSymbolRefAttr::get(global.getSymNameAttr()));
+
+ mlir::StringAttr usedName = builder.getStringAttr("llvm.used");
+ cir::GlobalOp used =
+ symbolTables.lookupSymbolIn<cir::GlobalOp>(mlirModule, usedName);
+
+ llvm::SmallVector<mlir::Attribute, 4> elements;
+ if (used) {
+ auto existing = mlir::cast<cir::ConstArrayAttr>(*used.getInitialValue());
+ llvm::append_range(elements,
+ mlir::cast<mlir::ArrayAttr>(existing.getElts()));
+ }
+ elements.push_back(entry);
+
+ cir::ArrayType arrayTy =
+ cir::ArrayType::get(builder.getVoidPtrTy(), elements.size());
+ cir::ConstArrayAttr initAttr = cir::ConstArrayAttr::get(
+ arrayTy, mlir::ArrayAttr::get(&getContext(), elements));
+
+ if (used) {
+ used.setSymType(arrayTy);
+ used.setInitialValueAttr(initAttr);
+ return;
+ }
+
+ builder.setInsertionPointToStart(mlirModule.getBody());
+ used = cir::GlobalOp::create(builder, mlirModule.getLoc(),
+ usedName.getValue(), arrayTy);
+ used.setLinkage(cir::GlobalLinkageKind::AppendingLinkage);
+ used.setInitialValueAttr(initAttr);
+ used.setSectionAttr(builder.getStringAttr("llvm.metadata"));
+ symbolTables.getSymbolTable(mlirModule).insert(used);
+}
+
void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
cir::LocalInitOp localInitOp) {
CIRBaseBuilderTy builder(getContext());
@@ -1769,6 +1811,13 @@ void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
globalCtorList.emplace_back(f.getSymName(),
cir::GlobalCtorAttr::getDefaultPriority(),
op.getSymName());
+ // When COMDAT is used on ELF or in the MS C++ ABI, the key must be in
+ // `llvm.used` to prevent the linker from garbage-collecting it (and,
+ // with it, the global ctor entry keyed to it). Matches classic
+ // CodeGen's EmitCXXGlobalVarDeclInitFunc.
+ if (getTargetInfo().getTriple().isOSBinFormatELF() ||
+ getTargetInfo().getCXXABI().isMicrosoft())
+ addUsedGlobal(op);
} else {
dynamicInitializers.push_back(f);
}
diff --git a/clang/test/CIR/CodeGen/static-members.cpp b/clang/test/CIR/CodeGen/static-members.cpp
index 2ebd3dc1933c3..40ee13e42c18a 100644
--- a/clang/test/CIR/CodeGen/static-members.cpp
+++ b/clang/test/CIR/CodeGen/static-members.cpp
@@ -26,7 +26,11 @@ struct S {
// LLVM-DAG: @_ZN1S2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat, align 1
// LLVM-DAG: @_ZN5Outer5Inner2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat, align 1
// LLVM-DAG: @_ZN13NonThreadSafeIiE1fE = linkonce_odr global i32 0, comdat, align 4
-// LLVM: @llvm.global_ctors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @_ZN1S2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.1, ptr @_ZN5Outer5Inner2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.2, ptr @_ZN13NonThreadSafeIiE1fE }]
+
+// The COMDAT keys must be kept in `llvm.used` so the linker doesn't
+// garbage-collect them (and, with them, their `llvm.global_ctors` entries).
+// LLVM-DAG: @llvm.used = appending global [3 x ptr] [ptr @_ZN1S2hdE, ptr @_ZN5Outer5Inner2hdE, ptr @_ZN13NonThreadSafeIiE1fE], section "llvm.metadata"
+// LLVM-DAG: @llvm.global_ctors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @_ZN1S2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.1, ptr @_ZN5Outer5Inner2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.2, ptr @_ZN13NonThreadSafeIiE1fE }]
// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN1S2hdE"> @_ZN1S2hdE = #cir.zero : !rec_HasDtor align(1) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
>From a35c45e7711ef81c73fdadf2338beda56cc4aad6 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Tue, 6 Oct 2026 11:20:50 -0700
Subject: [PATCH 3/9] Fixup example from review of global static-local not
getting right guard
---
clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 9 +--
clang/test/CIR/CodeGen/static-members.cpp | 68 +++++++++++++++++++++--
2 files changed, 68 insertions(+), 9 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index e796b07c2c064..fca30e5c31257 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -194,7 +194,7 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
// The same applies to code above where it is calling getAddrOfGlobalVar.
mlir::Value globalVal = builder.createGetGlobal(addr);
globalVal.getDefiningOp<cir::GetGlobalOp>().setStaticLocal(
- addr.getDynamicInitGuard().has_value());
+ addr.getDynamicInitGuard().has_value() && vd->isLocalVarDecl());
CharUnits alignment = cgf.getContext().getDeclAlign(vd);
Address globalAddr{globalVal, cgf.convertTypeForMem(type), alignment};
cgf.emitDestroy(globalAddr, type, cgf.getDestroyer(dtorKind));
@@ -357,10 +357,11 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl,
scope.setAsGlobalInit();
builder.setInsertionPointToStart(block);
mlir::Value getGlobal = builder.createGetGlobal(addr, varDecl->getTLSKind());
- // If we're initializing a guarded variable, set the flag that indicates
- // that.
+ // If we're initializing a guarded local static, set the flag that
+ // indicates that. Namespace-scope vague-linkage globals also get a
+ // dynamic-init guard, but aren't "static locals".
getGlobal.getDefiningOp<cir::GetGlobalOp>().setStaticLocal(
- addr.getDynamicInitGuard().has_value());
+ addr.getDynamicInitGuard().has_value() && varDecl->isLocalVarDecl());
Address declAddr(getGlobal, getASTContext().getDeclAlign(varDecl));
assert(performInit && "cannot have a constant initializer which needs "
diff --git a/clang/test/CIR/CodeGen/static-members.cpp b/clang/test/CIR/CodeGen/static-members.cpp
index 40ee13e42c18a..f1276476ed58f 100644
--- a/clang/test/CIR/CodeGen/static-members.cpp
+++ b/clang/test/CIR/CodeGen/static-members.cpp
@@ -13,24 +13,27 @@ struct S {
};
// CIR: module @
-// CIR-SAME: cir.global_ctors = [#cir.global_ctor<"__cxx_global_var_init", 65535, @_ZN1S2hdE>, #cir.global_ctor<"__cxx_global_var_init.1", 65535, @_ZN5Outer5Inner2hdE>, #cir.global_ctor<"__cxx_global_var_init.2", 65535, @_ZN13NonThreadSafeIiE1fE>]
+// CIR-SAME: cir.global_ctors = [#cir.global_ctor<"__cxx_global_var_init", 65535, @_ZN1S2hdE>, #cir.global_ctor<"__cxx_global_var_init.1", 65535, @_ZN5Outer5Inner2hdE>, #cir.global_ctor<"__cxx_global_var_init.2", 65535, @_ZN9RefMember3refE>, #cir.global_ctor<"__cxx_global_var_init.3", 65535, @_ZN13NonThreadSafeIiE1fE>]
// Guard variables.
// CIR-DAG: cir.global "private" linkonce_odr comdat("_ZN1S2hdE") @_ZGVN1S2hdE = #cir.int<0> : !s64i
// LLVM-DAG: @_ZGVN1S2hdE = linkonce_odr global i64 0, comdat($_ZN1S2hdE), align 8
// CIR-DAG: cir.global "private" linkonce_odr comdat("_ZN5Outer5Inner2hdE") @_ZGVN5Outer5Inner2hdE = #cir.int<0> : !s64i
// LLVM-DAG: @_ZGVN5Outer5Inner2hdE = linkonce_odr global i64 0, comdat($_ZN5Outer5Inner2hdE), align 8
+// CIR-DAG: cir.global "private" linkonce_odr comdat("_ZN9RefMember3refE") @_ZGVN9RefMember3refE = #cir.int<0> : !s64i
+// LLVM-DAG: @_ZGVN9RefMember3refE = linkonce_odr global i64 0, comdat($_ZN9RefMember3refE), align 8
// CIR-DAG: cir.global "private" linkonce_odr comdat("_ZN13NonThreadSafeIiE1fE") @_ZGVN13NonThreadSafeIiE1fE = #cir.int<0> : !s64i
// LLVM-DAG: @_ZGVN13NonThreadSafeIiE1fE = linkonce_odr global i64 0, comdat($_ZN13NonThreadSafeIiE1fE), align 8
// LLVM-DAG: @_ZN1S2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat, align 1
// LLVM-DAG: @_ZN5Outer5Inner2hdE = linkonce_odr global %struct.HasDtor zeroinitializer, comdat, align 1
+// LLVM-DAG: @_ZN9RefMember3refE = linkonce_odr global ptr null, comdat, align 8
// LLVM-DAG: @_ZN13NonThreadSafeIiE1fE = linkonce_odr global i32 0, comdat, align 4
// The COMDAT keys must be kept in `llvm.used` so the linker doesn't
// garbage-collect them (and, with them, their `llvm.global_ctors` entries).
-// LLVM-DAG: @llvm.used = appending global [3 x ptr] [ptr @_ZN1S2hdE, ptr @_ZN5Outer5Inner2hdE, ptr @_ZN13NonThreadSafeIiE1fE], section "llvm.metadata"
-// LLVM-DAG: @llvm.global_ctors = appending global [3 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @_ZN1S2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.1, ptr @_ZN5Outer5Inner2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.2, ptr @_ZN13NonThreadSafeIiE1fE }]
+// LLVM-DAG: @llvm.used = appending global [4 x ptr] [ptr @_ZN1S2hdE, ptr @_ZN5Outer5Inner2hdE, ptr @_ZN9RefMember3refE, ptr @_ZN13NonThreadSafeIiE1fE], section "llvm.metadata"
+// LLVM-DAG: @llvm.global_ctors = appending global [4 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @_ZN1S2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.1, ptr @_ZN5Outer5Inner2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.2, ptr @_ZN9RefMember3refE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.3, ptr @_ZN13NonThreadSafeIiE1fE }]
// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN1S2hdE"> @_ZN1S2hdE = #cir.zero : !rec_HasDtor align(1) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
@@ -128,6 +131,61 @@ struct Outer {
// LLVM: [[RET]]:
// LLVM: ret void
+int &get_ref();
+struct RefMember {
+ static inline int &ref = get_ref();
+};
+
+int useRefMember() {
+ return RefMember::ref;
+}
+
+// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN9RefMember3refE"> @_ZN9RefMember3refE = #cir.ptr<null> : !cir.ptr<!s32i> align(8) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
+// CIR-LABEL: cir.func internal private @__cxx_global_var_init.2() {
+// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN9RefMember3refE : !cir.ptr<!s64i>
+// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
+// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) syncscope(system) atomic(acquire) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
+// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s8i
+// CIR: %[[CMP:.*]] = cir.cmp eq %[[LOAD_GUARD]], %[[ZERO]] : !s8i
+// CIR: cir.if %[[CMP]] {
+// CIR: %[[ACQUIRE_GUARD:.*]] = cir.call @__cxa_guard_acquire(%[[GET_GUARD]]) : (!cir.ptr<!s64i>) -> !s32i
+// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i
+// CIR: %[[CMP:.*]] = cir.cmp ne %[[ACQUIRE_GUARD]], %[[ZERO]] : !s32i
+// CIR: cir.if %[[CMP]] {
+// Note the lack of a `static_local` attribute here: unlike a true function-
+// local static, this get_global must not be marked static_local even though
+// the global it refers to carries a dynamic_init_guard.
+// CIR: %[[GET_REF:.*]] = cir.get_global @_ZN9RefMember3refE : !cir.ptr<!cir.ptr<!s32i>>
+// CIR: %[[CALL:.*]] = cir.call @_Z7get_refv() : () -> (!cir.ptr<!s32i> {{.*}})
+// CIR: cir.store align(8) %[[CALL]], %[[GET_REF]] : !cir.ptr<!s32i>, !cir.ptr<!cir.ptr<!s32i>>
+// CIR: cir.call @__cxa_guard_release(%[[GET_GUARD]]) : (!cir.ptr<!s64i>) -> ()
+// CIR: }
+// CIR: }
+// CIR: cir.return
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init.2()
+// LLVM: %[[LOAD_GUARD:.*]] = load atomic i8, ptr @_ZGVN9RefMember3refE acquire, align 8
+// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
+// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET:.*]]
+
+// LLVM: [[UNINIT]]:
+// LLVM: %[[ACQUIRE_GUARD:.*]] = call i32 @__cxa_guard_acquire(ptr @_ZGVN9RefMember3refE)
+// LLVM: %[[CMP:.*]] = icmp ne i32 %[[ACQUIRE_GUARD]], 0
+// CIR leaves an extra 'block' for this target, but both go to 'ret' via only
+// empty blocks.
+// LLVM: br i1 %[[CMP]], label %[[DO_INIT:.*]], label %{{.*}}
+
+// LLVM: [[DO_INIT]]:
+// LLVM: %[[CALL:.*]] = call {{.*}}ptr @_Z7get_refv()
+// LLVM: store ptr %[[CALL]], ptr @_ZN9RefMember3refE
+// LLVM: call void @__cxa_guard_release(ptr @_ZGVN9RefMember3refE)
+// CIR leaves an extra 'block' for this target, but both go to 'ret' via only
+// empty blocks.
+// LLVM: br label %{{.*}}
+
+// LLVM: [[RET]]:
+// LLVM: ret void
+
// Not thread-safe example(because not inline), so doesn't have guard/release.
int get_i();
template <typename T> struct NonThreadSafe {
@@ -142,7 +200,7 @@ int useNonThreadSafe() {
// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN13NonThreadSafeIiE1fE"> @_ZN13NonThreadSafeIiE1fE = #cir.int<0> : !s32i align(4) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = false, tsk = implicit_instantiation>
-// CIR-LABEL: cir.func internal private @__cxx_global_var_init.2() {
+// CIR-LABEL: cir.func internal private @__cxx_global_var_init.3() {
// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN13NonThreadSafeIiE1fE : !cir.ptr<!s64i>
// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
@@ -158,7 +216,7 @@ int useNonThreadSafe() {
// CIR: cir.return
// CIR: }
-// LLVM-LABEL: define internal void @__cxx_global_var_init.2()
+// LLVM-LABEL: define internal void @__cxx_global_var_init.3()
// LLVM: %[[LOAD_GUARD:.*]] = load i8, ptr @_ZGVN13NonThreadSafeIiE1fE, align 8
// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET2:.*]]
>From 3c66f2a7a3c37785d96dfef73ad6c92e0d9f61b6 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Tue, 6 Oct 2026 11:24:27 -0700
Subject: [PATCH 4/9] add MissingFeature for the MSABI difference
---
clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
index ae3741ee90a67..86c48c1373aa2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
@@ -99,6 +99,13 @@ void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *vd,
bool performInit) {
assert(!cir::MissingFeatures::cudaSupport());
+ // Classic CodeGen dispatches guarded initialization through
+ // CGCXXABI::EmitGuardedInit, which the Microsoft ABI overrides with a
+ // completely different (bitmask-based "magic statics") guard scheme
+ // instead of the Itanium __cxa_guard_acquire/__cxa_guard_release calls
+ // that emitCXXGuardedInit/LoweringPrepare unconditionally assume here.
+ assert(!cir::MissingFeatures::msabi());
+
if (addr.hasWeakLinkage() || addr.hasLinkOnceLinkage() ||
(vd->getTLSKind() == VarDecl::TLS_Dynamic &&
isTemplateInstantiation(vd->getTemplateSpecializationKind()))) {
>From 72e3f958ddd6014adb40b88aa4589a78c9695ae7 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Tue, 6 Oct 2026 11:42:38 -0700
Subject: [PATCH 5/9] Make sure we cast so that big-endian works right
---
.../Dialect/Transforms/LoweringPrepare.cpp | 16 +++++++++-----
.../CIR/CodeGen/static-local-arm-guard.cpp | 22 +++++++++----------
clang/test/CIR/CodeGen/static-members.cpp | 5 +++--
3 files changed, 23 insertions(+), 20 deletions(-)
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 871a69a468a39..d126fd697def1 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -562,18 +562,22 @@ struct LoweringPreparePass
// For non-local variables, store 1 into the first byte of the guard
// variable before the object initialization begins so that references
// to the variable during initialization don't restart initialization.
- builder.createStore(
- loc, builder.getConstantInt(loc, guardPtrTy.getPointee(), 1),
- guardPtr);
+ mlir::Value byteGuardPtr = builder.createBitcast(
+ guardPtr, cir::PointerType::get(builder.getSIntNTy(8)));
+ builder.createStore(loc,
+ builder.getConstantInt(loc, builder.getSIntNTy(8), 1),
+ byteGuardPtr);
emitBody();
} else {
emitBody();
// For local variables, store 1 into the first byte of the guard variable
// after the object initialization completes so that initialization is
// retried if initialization is interrupted by an exception.
- builder.createStore(
- loc, builder.getConstantInt(loc, guardPtrTy.getPointee(), 1),
- guardPtr);
+ mlir::Value byteGuardPtr = builder.createBitcast(
+ guardPtr, cir::PointerType::get(builder.getSIntNTy(8)));
+ builder.createStore(loc,
+ builder.getConstantInt(loc, builder.getSIntNTy(8), 1),
+ byteGuardPtr);
}
builder.createYield(loc); // Outermost IfOp
diff --git a/clang/test/CIR/CodeGen/static-local-arm-guard.cpp b/clang/test/CIR/CodeGen/static-local-arm-guard.cpp
index 156c6f70c8b8c..a1cfc07116a2b 100644
--- a/clang/test/CIR/CodeGen/static-local-arm-guard.cpp
+++ b/clang/test/CIR/CodeGen/static-local-arm-guard.cpp
@@ -1,16 +1,14 @@
// RUN: %clang_cc1 -std=c++17 -triple aarch64-unknown-linux-gnu -fno-threadsafe-statics -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR
// RUN: %clang_cc1 -std=c++17 -triple aarch64-unknown-linux-gnu -fno-threadsafe-statics -fclangir -emit-llvm %s -o %t-cir.ll
-// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM,LLVM-CIR
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
// RUN: %clang_cc1 -std=c++17 -triple aarch64-unknown-linux-gnu -fno-threadsafe-statics -emit-llvm %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM,LLVM-OGCG
+// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM
// On ARM-style targets (here, AArch64), function-scope static locals use the
// ARM ABI rule that only bit 0 of the guard is checked. The guard size depends
// on classic codegen's `useInt8GuardVariable = !threadsafe && hasInternalLinkage`
-// (see ItaniumCXXABI::EmitGuardedInit). Both branches below are NYI in the CIR
-// LoweringPrepare pass and currently fail; fill in the expected output once
-// support is added.
+// (see ItaniumCXXABI::EmitGuardedInit).
int bar();
@@ -69,17 +67,18 @@ inline int word_guard() {
// CIR: %[[B_TOO:.*]] = cir.get_global static_local @_ZZ10word_guardvE1b : !cir.ptr<!s32i>
// CIR: %[[BAR:.*]] = cir.call @_Z3barv()
// CIR: cir.store{{.*}} %[[BAR]], %[[B_TOO]] : !s32i, !cir.ptr<!s32i>
-// CIR: %[[ONE:.*]] = cir.const #cir.int<1> : !s64i
-// CIR: cir.store %[[ONE]], %[[GUARD]] : !s64i, !cir.ptr<!s64i>
+// CIR: %[[GUARD_BYTE_PTR2:.*]] = cir.cast bitcast %[[GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
+// CIR: %[[ONE:.*]] = cir.const #cir.int<1> : !s8i
+// CIR: cir.store %[[ONE]], %[[GUARD_BYTE_PTR2]] : !s8i, !cir.ptr<!s8i>
// CIR: }
// CIR: %[[LOAD_B:.*]] = cir.load{{.*}} %[[B]] : !cir.ptr<!s32i>, !s32i
// CIR: cir.store %[[LOAD_B]], %[[RETVAL]] : !s32i, !cir.ptr<!s32i>
// CIR: %[[LOAD_RETVAL:.*]] = cir.load %[[RETVAL]] : !cir.ptr<!s32i>, !s32i
// CIR: cir.return %[[LOAD_RETVAL]] : !s32i
-// Note: The guard variable is a 64-bit value, but the load and truncate of
-// that value gets folded to an 8-byte-aligned i8 load on both LLVM via
-// CIR and OGCG, while the store of the value only gets folded in OGCG.
+// Note: The guard variable is a 64-bit value, but both the load/truncate and
+// the mark-initialized store get folded to an i8 access on both LLVM
+// via CIR and OGCG.
// LLVM: define {{.*}} i32 @_Z10word_guardv()
// LLVM: %[[GUARD:.*]] = load i8, ptr @_ZGVZ10word_guardvE1b, align 8
@@ -89,8 +88,7 @@ inline int word_guard() {
// LLVM: [[DO_INIT]]:
// LLVM: %[[BAR:.*]] = call {{.*}} i32 @_Z3barv()
// LLVM: store i32 %[[BAR]], ptr @_ZZ10word_guardvE1b
-// LLVM-CIR: store i64 1, ptr @_ZGVZ10word_guardvE1b, align 8
-// LLVM-OGCG: store i8 1, ptr @_ZGVZ10word_guardvE1b, align 8
+// LLVM: store i8 1, ptr @_ZGVZ10word_guardvE1b
// LLVM: br label %[[DONE]]
// LLVM: [[DONE]]:
// LLVM: %[[LOAD_B:.*]] = load i32, ptr @_ZZ10word_guardvE1b
diff --git a/clang/test/CIR/CodeGen/static-members.cpp b/clang/test/CIR/CodeGen/static-members.cpp
index f1276476ed58f..529fcf0dc8a6e 100644
--- a/clang/test/CIR/CodeGen/static-members.cpp
+++ b/clang/test/CIR/CodeGen/static-members.cpp
@@ -207,8 +207,9 @@ int useNonThreadSafe() {
// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> : !s8i
// CIR: %[[CMP:.*]] = cir.cmp eq %[[LOAD_GUARD]], %[[ZERO]] : !s8i
// CIR: cir.if %[[CMP]] {
-// CIR: %[[ONE:.*]] = cir.const #cir.int<1> : !s64i
-// CIR: cir.store %[[ONE]], %[[GET_GUARD]] : !s64i, !cir.ptr<!s64i>
+// CIR: %[[GUARD_BYTE:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
+// CIR: %[[ONE:.*]] = cir.const #cir.int<1> : !s8i
+// CIR: cir.store %[[ONE]], %[[GUARD_BYTE]] : !s8i, !cir.ptr<!s8i>
// CIR: %[[GET_F:.*]] = cir.get_global @_ZN13NonThreadSafeIiE1fE : !cir.ptr<!s32i>
// CIR: %[[CALL:.*]] = cir.call @_Z5get_iv() : () -> (!s32i {llvm.noundef})
// CIR: cir.store align(4) %[[CALL]], %[[GET_F]] : !s32i, !cir.ptr<!s32i>
>From ebe5f0f54a4e3a880c79aa24f8b24c5428c597b2 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Tue, 6 Oct 2026 14:25:05 -0700
Subject: [PATCH 6/9] set global-visibility for the guard
---
.../lib/CIR/Dialect/Transforms/LoweringPrepare.cpp | 1 +
clang/test/CIR/CodeGen/static-local.cpp | 14 ++++++++++++++
2 files changed, 15 insertions(+)
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index d126fd697def1..87bfa87d6ab86 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -277,6 +277,7 @@ struct LoweringPreparePass
guard.setDSOLocal(globalOp.getDsoLocal());
guard.setAlignment(guardAlignment.getAsAlign().value());
guard.setTlsModel(globalOp.getTlsModel());
+ guard.setGlobalVisibility(globalOp.getGlobalVisibility());
// The ABI says: "It is suggested that it be emitted in the same COMDAT
// group as the associated data object." In practice, this doesn't work
diff --git a/clang/test/CIR/CodeGen/static-local.cpp b/clang/test/CIR/CodeGen/static-local.cpp
index 8461a7698e51f..bc2d172be1657 100644
--- a/clang/test/CIR/CodeGen/static-local.cpp
+++ b/clang/test/CIR/CodeGen/static-local.cpp
@@ -26,6 +26,8 @@
// LLVM-DAG: @_ZGVZ1fvE1a = internal global i64 0
// CIR-DAG: cir.global "private" linkonce_odr comdat @_ZGVZ10getInlineAvE1a = #cir.int<0> : !s64i
// LLVM-DAG: @_ZGVZ10getInlineAvE1a = linkonce_odr global i64 0, comdat
+// CIR-DAG: cir.global "private" hidden linkonce_odr comdat @_ZGVZ16getHiddenInlineAvE1a = #cir.int<0> : !s64i
+// LLVM-DAG: @_ZGVZ16getHiddenInlineAvE1a = linkonce_odr hidden global i64 0, comdat
// CIR-DAG: cir.global "private" internal dso_local @_ZGVZ8ref_initvE1y = #cir.int<0> : !s64i
// LLVM-DAG: @_ZGVZ8ref_initvE1y = internal global i64 0
// CIR-DAG: cir.global "private" internal dso_local @_ZGVZ23array_static_local_dtorvE2sm = #cir.int<0> : !s64i
@@ -181,6 +183,18 @@ void call_inline() {
// LLVM: call void @_ZN1AC1Ev(ptr {{.*}}@_ZZ10getInlineAvE1a)
// LLVM: call void @__cxa_guard_release(ptr @_ZGVZ10getInlineAvE1a)
+__attribute__((visibility("hidden")))
+inline const A &getHiddenInlineA() {
+ static A a;
+ return a;
+}
+
+void call_hidden_inline() {
+ use(&getHiddenInlineA());
+}
+
+// CIR-BOTH-LABEL: cir.func no_inline comdat linkonce_odr hidden @_Z16getHiddenInlineAv()
+
int bar();
void references_param_and_previous(int param) {
>From be7ec944583ea1b9ed283d71409eb7c3ad22ba91 Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Wed, 7 Oct 2026 07:07:27 -0700
Subject: [PATCH 7/9] set the init function to also be in the variable's comdat
section
---
clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp | 7 +++++++
clang/test/CIR/CodeGen/static-members.cpp | 12 ++++++++----
2 files changed, 15 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 87bfa87d6ab86..ee9ba6388e2f8 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -1823,6 +1823,13 @@ void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
if (getTargetInfo().getTriple().isOSBinFormatELF() ||
getTargetInfo().getCXXABI().isMicrosoft())
addUsedGlobal(op);
+
+ // Since the global ctor entry is keyed to the variable, the init function
+ // can be discarded along with it, so put it in the variable's COMDAT.
+ std::optional<llvm::StringRef> comdat = op.getComdat();
+ if (comdat && (getTargetInfo().getTriple().isOSBinFormatELF() ||
+ getTargetInfo().getTriple().isOSBinFormatWasm()))
+ f.setComdat(comdat->empty() ? op.getSymName() : *comdat);
} else {
dynamicInitializers.push_back(f);
}
diff --git a/clang/test/CIR/CodeGen/static-members.cpp b/clang/test/CIR/CodeGen/static-members.cpp
index 529fcf0dc8a6e..459892a71fd0d 100644
--- a/clang/test/CIR/CodeGen/static-members.cpp
+++ b/clang/test/CIR/CodeGen/static-members.cpp
@@ -37,7 +37,7 @@ struct S {
// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN1S2hdE"> @_ZN1S2hdE = #cir.zero : !rec_HasDtor align(1) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
-// CIR-LABEL: cir.func internal private @__cxx_global_var_init() {
+// CIR-LABEL: cir.func comdat("_ZN1S2hdE") internal private @__cxx_global_var_init() {
// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN1S2hdE : !cir.ptr<!s64i>
// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) syncscope(system) atomic(acquire) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
@@ -60,6 +60,7 @@ struct S {
// CIR: cir.return
// LLVM-LABEL: define internal void @__cxx_global_var_init()
+// LLVM-SAME: comdat($_ZN1S2hdE)
// LLVM: %[[LOAD_GUARD:.*]] = load atomic i8, ptr @_ZGVN1S2hdE acquire, align 8
// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET:.*]]
@@ -87,7 +88,7 @@ struct Outer {
};
};
// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN5Outer5Inner2hdE"> @_ZN5Outer5Inner2hdE = #cir.zero : !rec_HasDtor align(1) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
-// CIR-LABEL: cir.func internal private @__cxx_global_var_init.1() {
+// CIR-LABEL: cir.func comdat("_ZN5Outer5Inner2hdE") internal private @__cxx_global_var_init.1() {
// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN5Outer5Inner2hdE : !cir.ptr<!s64i>
// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) syncscope(system) atomic(acquire) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
@@ -110,6 +111,7 @@ struct Outer {
// CIR: cir.return
// LLVM-LABEL: define internal void @__cxx_global_var_init.1()
+// LLVM-SAME: comdat($_ZN5Outer5Inner2hdE)
// LLVM: %[[LOAD_GUARD:.*]] = load atomic i8, ptr @_ZGVN5Outer5Inner2hdE acquire, align 8
// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET:.*]]
@@ -141,7 +143,7 @@ int useRefMember() {
}
// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN9RefMember3refE"> @_ZN9RefMember3refE = #cir.ptr<null> : !cir.ptr<!s32i> align(8) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
-// CIR-LABEL: cir.func internal private @__cxx_global_var_init.2() {
+// CIR-LABEL: cir.func comdat("_ZN9RefMember3refE") internal private @__cxx_global_var_init.2() {
// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN9RefMember3refE : !cir.ptr<!s64i>
// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) syncscope(system) atomic(acquire) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
@@ -164,6 +166,7 @@ int useRefMember() {
// CIR: cir.return
// LLVM-LABEL: define internal void @__cxx_global_var_init.2()
+// LLVM-SAME: comdat($_ZN9RefMember3refE)
// LLVM: %[[LOAD_GUARD:.*]] = load atomic i8, ptr @_ZGVN9RefMember3refE acquire, align 8
// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET:.*]]
@@ -200,7 +203,7 @@ int useNonThreadSafe() {
// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN13NonThreadSafeIiE1fE"> @_ZN13NonThreadSafeIiE1fE = #cir.int<0> : !s32i align(4) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = false, tsk = implicit_instantiation>
-// CIR-LABEL: cir.func internal private @__cxx_global_var_init.3() {
+// CIR-LABEL: cir.func comdat("_ZN13NonThreadSafeIiE1fE") internal private @__cxx_global_var_init.3() {
// CIR: %[[GET_GUARD:.*]] = cir.get_global @_ZGVN13NonThreadSafeIiE1fE : !cir.ptr<!s64i>
// CIR: %[[TO_CHAR:.*]] = cir.cast bitcast %[[GET_GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i>
// CIR: %[[LOAD_GUARD:.*]] = cir.load align(8) %[[TO_CHAR]] : !cir.ptr<!s8i>, !s8i
@@ -218,6 +221,7 @@ int useNonThreadSafe() {
// CIR: }
// LLVM-LABEL: define internal void @__cxx_global_var_init.3()
+// LLVM-SAME: comdat($_ZN13NonThreadSafeIiE1fE)
// LLVM: %[[LOAD_GUARD:.*]] = load i8, ptr @_ZGVN13NonThreadSafeIiE1fE, align 8
// LLVM: %[[CMP:.*]] = icmp eq i8 %[[LOAD_GUARD]], 0
// LLVM: br i1 %[[CMP]], label %[[UNINIT:.*]], label %[[RET2:.*]]
>From ae310cebbbe89582250a91049f0a82a42bb57baa Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Wed, 7 Oct 2026 08:01:18 -0700
Subject: [PATCH 8/9] Make sure globals only create a single block in the
ctor/dtor regions.
---
clang/include/clang/CIR/Dialect/IR/CIROps.td | 4 +-
clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 39 ++++-
.../CIR/Dialect/Transforms/CXXABILowering.cpp | 9 +-
clang/test/CIR/CodeGen/throws.cpp | 139 ++++++++++++++++++
4 files changed, 185 insertions(+), 6 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index ff83e04ce9bea..0b8f7ab85ee83 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3457,8 +3457,8 @@ def CIR_GlobalOp : CIR_RegionBranchOpBase<"global", [
OptionalAttr<I32Attr>:$init_priority
);
- let regions = (region AnyRegion:$ctorRegion,
- AnyRegion:$dtorRegion);
+ let regions = (region MaxSizedRegion<1>:$ctorRegion,
+ MaxSizedRegion<1>:$dtorRegion);
let assemblyFormat = [{
($sym_visibility^)?
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index fca30e5c31257..07eb4039a2ef7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -51,6 +51,34 @@ void CIRGenFunction::emitInvariantStart(CharUnits size, mlir::Value addr,
mlir::ValueRange{sizeValue, addr});
}
+/// The ctor and dtor regions of a global (or static local) hold a single block,
+/// but an initializer can have control flow: a `throw` or a trap ends the block
+/// it is in, and the rest of the initializer goes in a new, unreachable one.
+/// If emitting into \p region produced more than one block, move them all into
+/// a cir.scope, leaving the region with one block that holds the scope.
+static void wrapMultiBlockRegionInScope(CIRGenBuilderTy &builder,
+ mlir::Region ®ion,
+ mlir::Location loc) {
+ if (region.empty() || region.hasOneBlock())
+ return;
+
+ mlir::OpBuilder::InsertionGuard guard(builder);
+ mlir::Block *wrapper = builder.createBlock(®ion, region.begin());
+ builder.setInsertionPointToStart(wrapper);
+ auto scope = cir::ScopeOp::create(builder, loc,
+ [](mlir::OpBuilder &, mlir::Location) {});
+
+ // Swap the scope's empty block for the original blocks, which already end in
+ // the cir.yield that terminates the scope.
+ mlir::Region &scopeRegion = scope.getRegion();
+ scopeRegion.getBlocks().clear();
+ scopeRegion.getBlocks().splice(scopeRegion.end(), region.getBlocks(),
+ std::next(region.begin()), region.end());
+
+ builder.setInsertionPointToEnd(wrapper);
+ cir::YieldOp::create(builder, loc);
+}
+
static void emitDeclInit(CIRGenFunction &cgf, const VarDecl *varDecl,
cir::GlobalOp globalOp, mlir::Region &ctorRegion) {
assert((varDecl->hasGlobalStorage() ||
@@ -95,7 +123,7 @@ static void emitDeclInit(CIRGenFunction &cgf, const VarDecl *varDecl,
}
// Finish the ctor region.
- builder.setInsertionPointToEnd(block);
+ builder.setInsertionPointToEnd(builder.getInsertionBlock());
cir::YieldOp::create(builder, globalOp.getLoc());
}
@@ -325,6 +353,8 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl,
// PerformInit, constant store invariant / destroy handled below.
if (performInit) {
emitDeclInit(cgf, varDecl, addr, ctorRegion);
+ // Do this before anything else is added to the end of the region.
+ wrapMultiBlockRegionInScope(cgf.getBuilder(), ctorRegion, addr.getLoc());
// For constant storage, emit invariant.start in the ctor region after
// initialization but before the yield.
if (isConstantStorage) {
@@ -345,8 +375,10 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl,
emitDeclInvariant(cgf, varDecl);
}
- if (!isConstantStorage)
+ if (!isConstantStorage) {
emitDeclDestroy(cgf, varDecl, addr, dtorRegion);
+ wrapMultiBlockRegionInScope(cgf.getBuilder(), dtorRegion, addr.getLoc());
+ }
return;
}
@@ -383,8 +415,9 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl,
LValueBaseInfo{});
}
- builder.setInsertionPointToEnd(block);
+ builder.setInsertionPointToEnd(builder.getInsertionBlock());
cir::YieldOp::create(builder, addr->getLoc());
+ wrapMultiBlockRegionInScope(builder, ctorRegion, addr.getLoc());
}
void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl,
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index 9a0e40bd4f506..b99123cd8806a 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -615,9 +615,16 @@ mlir::LogicalResult CIRGlobalOpABILowering::matchAndRewrite(
mlir::Attribute loweredInit = lowerInitialValue(
lowerModule, layout, *getTypeConverter(), ty, op.getInitialValueAttr());
- auto newOp = mlir::cast<cir::GlobalOp>(rewriter.clone(*op.getOperation()));
+ cir::GlobalOp newOp = rewriter.cloneWithoutRegions(op);
newOp.setInitialValueAttr(loweredInit);
newOp.setSymType(loweredTy);
+ // Regions have to be separately moved(rather than cloned), else we cause
+ // multi-block regions/eh stuff to be double-referenced, and thus can't be
+ // removed properly during collectUnreachable.
+ rewriter.inlineRegionBefore(op.getCtorRegion(), newOp.getCtorRegion(),
+ newOp.getCtorRegion().end());
+ rewriter.inlineRegionBefore(op.getDtorRegion(), newOp.getDtorRegion(),
+ newOp.getDtorRegion().end());
rewriter.replaceOp(op, newOp);
return mlir::success();
}
diff --git a/clang/test/CIR/CodeGen/throws.cpp b/clang/test/CIR/CodeGen/throws.cpp
index 5a2d5ccf62e0a..f9249b6235327 100644
--- a/clang/test/CIR/CodeGen/throws.cpp
+++ b/clang/test/CIR/CodeGen/throws.cpp
@@ -5,6 +5,145 @@
// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fcxx-exceptions -fexceptions -emit-llvm %s -o %t.ll
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
+int throw_in_global_init = (throw 1, 0);
+
+// CIR-LABEL: cir.func internal private @__cxx_global_var_init() {
+// CIR-NEXT: cir.scope {
+// CIR: cir.throw
+// CIR-NEXT: cir.unreachable
+// CIR: ^bb1:
+// CIR: cir.store {{.*}} : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: cir.return
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init()
+// LLVM: call void @__cxa_throw
+// LLVM-NEXT: unreachable
+// LLVM: store i32 0, ptr @throw_in_global_init
+// LLVM: ret void
+
+// OGCG-LABEL: define internal void @__cxx_global_var_init()
+// OGCG: call void @__cxa_throw
+// OGCG-NEXT: unreachable
+// OGCG: store i32 0, ptr @throw_in_global_init
+// OGCG: ret void
+
+int trap_in_global_init = (__builtin_trap(), 1);
+
+// CIR-LABEL: cir.func internal private @__cxx_global_var_init.1() {
+// CIR-NEXT: cir.scope {
+// CIR: cir.trap
+// CIR: ^bb1:
+// CIR: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: cir.return
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init.1()
+// LLVM: call void @llvm.trap()
+// LLVM-NEXT: unreachable
+// LLVM: store i32 1, ptr @trap_in_global_init
+// LLVM: ret void
+
+// OGCG-LABEL: define internal void @__cxx_global_var_init.1()
+// OGCG: call void @llvm.trap()
+// OGCG-NEXT: unreachable
+// OGCG: store i32 1, ptr @trap_in_global_init
+// OGCG: ret void
+
+struct ThrowInGlobalCtorArg {
+ ThrowInGlobalCtorArg(int);
+};
+ThrowInGlobalCtorArg throw_in_global_ctor_arg = ThrowInGlobalCtorArg((throw 1, 0));
+
+// CIR-LABEL: cir.func internal private @__cxx_global_var_init.2() {
+// CIR-NEXT: cir.scope {
+// CIR: cir.throw
+// CIR-NEXT: cir.unreachable
+// CIR: ^bb1:
+// CIR: cir.call @_ZN20ThrowInGlobalCtorArgC1Ei
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: cir.return
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init.2()
+// LLVM: call void @__cxa_throw
+// LLVM-NEXT: unreachable
+// LLVM: call void @_ZN20ThrowInGlobalCtorArgC1Ei
+// LLVM: ret void
+
+// OGCG-LABEL: define internal void @__cxx_global_var_init.2()
+// OGCG: call void @__cxa_throw
+// OGCG-NEXT: unreachable
+// OGCG: call void @_ZN20ThrowInGlobalCtorArgC1Ei
+// OGCG: ret void
+
+template <class X> struct ThrowInTemplateStatic {
+ static inline int w = (throw 1, 0);
+};
+int use_throw_in_template_static() { return ThrowInTemplateStatic<int>::w; }
+
+// CIR-LABEL: cir.func comdat("_ZN21ThrowInTemplateStaticIiE1wE") internal private @__cxx_global_var_init.3() {
+// CIR: cir.if {{.*}} {
+// CIR: cir.scope {
+// CIR: cir.throw
+// CIR-NEXT: cir.unreachable
+// CIR: ^bb1:
+// CIR: cir.store {{.*}} : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: }
+// CIR-NEXT: cir.return
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init.3() comdat($_ZN21ThrowInTemplateStaticIiE1wE)
+// LLVM: call void @__cxa_throw
+// LLVM-NEXT: unreachable
+// LLVM: store i32 0, ptr @_ZN21ThrowInTemplateStaticIiE1wE
+
+inline int throw_in_inline_global = (throw 1, 0);
+int use_throw_in_inline_global() { return throw_in_inline_global; }
+
+// CIR-LABEL: cir.func comdat("throw_in_inline_global") internal private @__cxx_global_var_init.4() {
+// CIR: cir.call @__cxa_guard_acquire
+// CIR: cir.cleanup.scope {
+// CIR-NEXT: cir.scope {
+// CIR: cir.throw
+// CIR-NEXT: cir.unreachable
+// CIR: ^bb1:
+// CIR: cir.store {{.*}} : !s32i, !cir.ptr<!s32i>
+// CIR-NEXT: cir.yield
+// CIR-NEXT: }
+// CIR-NEXT: cir.yield
+// CIR-NEXT: } cleanup eh {
+// CIR-NEXT: cir.call @__cxa_guard_abort
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init.4() comdat($throw_in_inline_global)
+// LLVM: call i32 @__cxa_guard_acquire
+// LLVM: invoke void @__cxa_throw
+// LLVM: call void @__cxa_guard_abort
+
+int throw_in_static_local_init() {
+ static int s = (throw 1, 0);
+ return s;
+}
+
+// CIR-LABEL: cir.func {{.*}} @_Z26throw_in_static_local_initv
+// CIR: cir.scope {
+// CIR: cir.throw
+// CIR-NEXT: cir.unreachable
+// CIR: ^bb1:
+// CIR: cir.store {{.*}} : !s32i, !cir.ptr<!s32i>
+
+// LLVM-LABEL: define {{.*}} @_Z26throw_in_static_local_initv
+// LLVM: call i32 @__cxa_guard_acquire
+// LLVM: invoke void @__cxa_throw
+// LLVM: call void @__cxa_guard_abort
+
+// OGCG-LABEL: define {{.*}} @_Z26throw_in_static_local_initv
+// OGCG: call i32 @__cxa_guard_acquire
+// OGCG: invoke void @__cxa_throw
+// OGCG: call void @__cxa_guard_abort
+
void rethrow() {
throw;
}
>From 781ff69fbda22f823f1f9f3390b08d17280a59db Mon Sep 17 00:00:00 2001
From: erichkeane <ekeane at nvidia.com>
Date: Wed, 7 Oct 2026 08:49:40 -0700
Subject: [PATCH 9/9] Fix behavior of non-TLS guarded variable
---
.../Dialect/Transforms/LoweringPrepare.cpp | 7 +++++-
clang/test/CIR/CodeGen/static-members.cpp | 22 ++++++++++++++++---
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index ee9ba6388e2f8..909bc26c3b5a0 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -495,7 +495,12 @@ struct LoweringPreparePass
if (!dtorRegion.empty()) {
assert(dtorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
- emitGlobalGuardedDtorRegion(builder, globalOp, dtorRegion, !threadsafe,
+ // Whether the destructor is registered with __cxa_thread_atexit depends
+ // on the variable being thread_local, not on the guard being
+ // thread-safe, so make sure we use getTlsModel here instead of
+ // threadsafe.
+ emitGlobalGuardedDtorRegion(builder, globalOp, dtorRegion,
+ globalOp.getTlsModel().has_value(),
*insertBlock);
}
builder.setInsertionPointToEnd(insertBlock);
diff --git a/clang/test/CIR/CodeGen/static-members.cpp b/clang/test/CIR/CodeGen/static-members.cpp
index 459892a71fd0d..cc3e1ab6614bc 100644
--- a/clang/test/CIR/CodeGen/static-members.cpp
+++ b/clang/test/CIR/CodeGen/static-members.cpp
@@ -13,7 +13,7 @@ struct S {
};
// CIR: module @
-// CIR-SAME: cir.global_ctors = [#cir.global_ctor<"__cxx_global_var_init", 65535, @_ZN1S2hdE>, #cir.global_ctor<"__cxx_global_var_init.1", 65535, @_ZN5Outer5Inner2hdE>, #cir.global_ctor<"__cxx_global_var_init.2", 65535, @_ZN9RefMember3refE>, #cir.global_ctor<"__cxx_global_var_init.3", 65535, @_ZN13NonThreadSafeIiE1fE>]
+// CIR-SAME: cir.global_ctors = [#cir.global_ctor<"__cxx_global_var_init", 65535, @_ZN1S2hdE>, #cir.global_ctor<"__cxx_global_var_init.1", 65535, @_ZN5Outer5Inner2hdE>, #cir.global_ctor<"__cxx_global_var_init.2", 65535, @_ZN9RefMember3refE>, #cir.global_ctor<"__cxx_global_var_init.3", 65535, @_ZN13NonThreadSafeIiE1fE>, #cir.global_ctor<"__cxx_global_var_init.4", 65535, @_ZN18TemplateStaticDtorIiE1dE>]
// Guard variables.
// CIR-DAG: cir.global "private" linkonce_odr comdat("_ZN1S2hdE") @_ZGVN1S2hdE = #cir.int<0> : !s64i
@@ -32,8 +32,8 @@ struct S {
// The COMDAT keys must be kept in `llvm.used` so the linker doesn't
// garbage-collect them (and, with them, their `llvm.global_ctors` entries).
-// LLVM-DAG: @llvm.used = appending global [4 x ptr] [ptr @_ZN1S2hdE, ptr @_ZN5Outer5Inner2hdE, ptr @_ZN9RefMember3refE, ptr @_ZN13NonThreadSafeIiE1fE], section "llvm.metadata"
-// LLVM-DAG: @llvm.global_ctors = appending global [4 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @_ZN1S2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.1, ptr @_ZN5Outer5Inner2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.2, ptr @_ZN9RefMember3refE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.3, ptr @_ZN13NonThreadSafeIiE1fE }]
+// LLVM-DAG: @llvm.used = appending global [5 x ptr] [ptr @_ZN1S2hdE, ptr @_ZN5Outer5Inner2hdE, ptr @_ZN9RefMember3refE, ptr @_ZN13NonThreadSafeIiE1fE, ptr @_ZN18TemplateStaticDtorIiE1dE], section "llvm.metadata"
+// LLVM-DAG: @llvm.global_ctors = appending global [5 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init, ptr @_ZN1S2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.1, ptr @_ZN5Outer5Inner2hdE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.2, ptr @_ZN9RefMember3refE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.3, ptr @_ZN13NonThreadSafeIiE1fE }, { i32, ptr, ptr } { i32 65535, ptr @__cxx_global_var_init.4, ptr @_ZN18TemplateStaticDtorIiE1dE }]
// CIR: cir.global linkonce_odr comdat dynamic_init_guard<"_ZGVN1S2hdE"> @_ZN1S2hdE = #cir.zero : !rec_HasDtor align(1) ast(#cir.var.decl.ast) dynamic_init_info<local = false, tls = none, is_inline = true, tsk = undeclared>
@@ -237,3 +237,19 @@ int useNonThreadSafe() {
// LLVM: [[RET2]]:
// LLVM: ret void
+
+struct TemplateHasDtor {
+ ~TemplateHasDtor();
+};
+template <typename T> struct TemplateStaticDtor {
+ static TemplateHasDtor d;
+};
+template <typename T> TemplateHasDtor TemplateStaticDtor<T>::d;
+TemplateHasDtor *useTemplateStaticDtor() { return &TemplateStaticDtor<int>::d; }
+
+// LLVM-LABEL: define internal void @__cxx_global_var_init.4()
+// LLVM-SAME: comdat($_ZN18TemplateStaticDtorIiE1dE)
+// LLVM-NOT: __cxa_thread_atexit
+// LLVM: call i32 @__cxa_atexit(ptr @_ZN15TemplateHasDtorD1Ev, ptr @_ZN18TemplateStaticDtorIiE1dE, ptr @__dso_handle)
+// LLVM-NOT: __cxa_thread_atexit
+// LLVM: ret void
More information about the cfe-commits
mailing list