[clang] b5c41b4 - [CIR] Add CIRGen support for static local variables with non-constant initializers (#179827)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Feb 12 19:45:57 PST 2026
Author: Bruno Cardoso Lopes
Date: 2026-02-13T04:45:51+01:00
New Revision: b5c41b4bdef7faaaf49125b41fdee0116824e73a
URL: https://github.com/llvm/llvm-project/commit/b5c41b4bdef7faaaf49125b41fdee0116824e73a
DIFF: https://github.com/llvm/llvm-project/commit/b5c41b4bdef7faaaf49125b41fdee0116824e73a.diff
LOG: [CIR] Add CIRGen support for static local variables with non-constant initializers (#179827)
This adds CIRGen infrastructure for C++ function-local static variables
that require guarded initialization (Itanium C++ ABI).
Changes:
- Add ASTVarDeclAttr to carry VarDecl AST through the pipeline
- Add emitGuardedInit() to CIRGenCXXABI for guarded initialization
- Add emitCXXGuardedInit() to CIRGenFunction
- Replace NYI in addInitializerToStaticVarDecl() with ctor region emission
- Set static_local attribute on GlobalOp and GetGlobalOp
This doesn't unwrap the high level constructs just yet - The global's ctor region contains the initialization code, which will be lowered by LoweringPrepare to emit the actual guard variable pattern with __cxa_guard_acquire/__cxa_guard_release calls.
Added:
clang/test/CIR/CodeGen/static-local.cpp
Modified:
clang/include/clang/CIR/Dialect/IR/CIRAttrs.h
clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
clang/include/clang/CIR/Dialect/IR/CIROps.td
clang/lib/CIR/CodeGen/CIRGenCXX.cpp
clang/lib/CIR/CodeGen/CIRGenDecl.cpp
clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
clang/lib/CIR/CodeGen/CIRGenFunction.h
clang/lib/CIR/CodeGen/CIRGenModule.cpp
clang/lib/CIR/Dialect/IR/CIRDialect.cpp
clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
clang/test/CIR/CodeGen/global-array-dtor.cpp
clang/test/CIR/CodeGen/global-init.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h
index 858d4d6350bed..eb87dc083b0f5 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h
@@ -19,6 +19,7 @@
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
+#include "clang/CIR/Interfaces/ASTAttrInterfaces.h"
#include "clang/CIR/Interfaces/CIRTypeInterfaces.h"
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index d7938bc350925..47a0b3394c48e 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -19,6 +19,8 @@ include "clang/CIR/Dialect/IR/CIRAttrConstraints.td"
include "clang/CIR/Dialect/IR/CIRDialect.td"
include "clang/CIR/Dialect/IR/CIREnumAttr.td"
+include "clang/CIR/Interfaces/ASTAttrInterfaces.td"
+
//===----------------------------------------------------------------------===//
// CIR Attrs
//===----------------------------------------------------------------------===//
@@ -1286,5 +1288,44 @@ def CIR_SideEffect : CIR_I32EnumAttr<
}];
}
+//===----------------------------------------------------------------------===//
+// AST Wrappers
+//===----------------------------------------------------------------------===//
+
+class CIR_AST<string name, string prefix, list<Trait> traits = []>
+ : CIR_Attr<!strconcat("AST", name), !strconcat(prefix, ".ast"), traits> {
+ string clang_name = !strconcat("const clang::", name, " *");
+
+ let summary = !strconcat("Wraps a '", clang_name, "' AST node.");
+ let description = [{
+ Operations optionally refer to this node, they could be available depending
+ on the CIR lowering stage. Whether it's attached to the appropriate
+ CIR operation is delegated to the operation verifier.
+
+ Note: the AST pointer can be null when CIR is parsed from text, since
+ there is no serialization support for AST nodes yet.
+ }];
+ let parameters = (ins clang_name:$ast);
+
+ // Printing and parsing available in CIRDialect.cpp
+ let hasCustomAssemblyFormat = 1;
+
+ let extraClassDefinition = [{
+ ::mlir::Attribute $cppClass::parse(::mlir::AsmParser &parser,
+ ::mlir::Type type) {
+ // We cannot really parse anything AST related at this point
+ // since we have no serialization/JSON story.
+ return $cppClass::get(parser.getContext(), nullptr);
+ }
+
+ void $cppClass::print(::mlir::AsmPrinter &printer) const {
+ // Nothing to print besides the mnemonics.
+ }
+ }];
+}
+
+def CIR_ASTVarDeclAttr : CIR_AST<"VarDecl", "var.decl", [
+ ASTVarDeclInterface
+]>;
#endif // CLANG_CIR_DIALECT_IR_CIRATTRS_TD
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 0ce7420e4bc9a..25b09c9eb4ed4 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2408,7 +2408,8 @@ def CIR_GlobalOp : CIR_Op<"global", [
UnitAttr:$constant,
UnitAttr:$dso_local,
UnitAttr:$static_local,
- OptionalAttr<I64Attr>:$alignment);
+ OptionalAttr<I64Attr>:$alignment,
+ OptionalAttr<ASTVarDeclInterface>:$ast);
let regions = (region MaxSizedRegion<1>:$ctorRegion,
MaxSizedRegion<1>:$dtorRegion);
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index f8a058b521c54..c3457e40a9110 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -15,6 +15,7 @@
#include "CIRGenModule.h"
#include "clang/AST/GlobalDecl.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/Support/SaveAndRestore.h"
@@ -256,7 +257,7 @@ void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl,
CIRGenFunction::SourceLocRAIIObject fnLoc{cgf,
getLoc(varDecl->getLocation())};
- assert(!cir::MissingFeatures::astVarDeclInterface());
+ addr.setAstAttr(cir::ASTVarDeclAttr::get(&getMLIRContext(), varDecl));
if (!ty->isReferenceType()) {
assert(!cir::MissingFeatures::openMP());
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 42a7d70677b61..66bc7680965d4 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -568,13 +568,24 @@ Address CIRGenModule::createUnnamedGlobalFrom(const VarDecl &d,
cir::GlobalOp CIRGenFunction::addInitializerToStaticVarDecl(
const VarDecl &d, cir::GlobalOp gv, cir::GetGlobalOp gvAddr) {
ConstantEmitter emitter(*this);
- mlir::TypedAttr init =
- mlir::cast<mlir::TypedAttr>(emitter.tryEmitForInitializer(d));
+ mlir::TypedAttr init = mlir::dyn_cast_if_present<mlir::TypedAttr>(
+ emitter.tryEmitForInitializer(d));
// If constant emission failed, then this should be a C++ static
// initializer.
if (!init) {
- cgm.errorNYI(d.getSourceRange(), "static var without initializer");
+ if (!getLangOpts().CPlusPlus) {
+ cgm.errorNYI(d.getInit()->getSourceRange(),
+ "constant l-value expression");
+ } else if (d.hasFlexibleArrayInit(getContext())) {
+ cgm.errorNYI(d.getInit()->getSourceRange(), "flexible array initializer");
+ } else {
+ // Since we have a static initializer, this global variable can't
+ // be constant.
+ gv.setConstant(false);
+ emitCXXGuardedInit(d, gv, /*performInit*/ true);
+ gvAddr.setStaticLocal(true);
+ }
return gv;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
index d1efed80aaf0e..9c2a5fe0cda07 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
@@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//
+#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "clang/AST/Attr.h"
#include "clang/Basic/LangOptions.h"
@@ -17,6 +18,23 @@
using namespace clang;
using namespace clang::CIRGen;
+void CIRGenFunction::emitCXXGuardedInit(const VarDecl &varDecl,
+ cir::GlobalOp globalOp,
+ bool performInit) {
+ // If we've been asked to forbid guard variables, emit an error now.
+ // This diagnostic is hard-coded for Darwin's use case; we can find
+ // better phrasing if someone else needs it.
+ if (cgm.getCodeGenOpts().ForbidGuardVariables)
+ cgm.error(varDecl.getLocation(), "guard variables are forbidden");
+
+ // Emit the initializer and add a global destructor if appropriate.
+ cgm.emitCXXGlobalVarDeclInit(&varDecl, globalOp, performInit);
+
+ // Mark the global as static local. The emission of the guard/acquire
+ // is done during LoweringPrepare.
+ globalOp.setStaticLocal(true);
+}
+
void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *vd,
cir::GlobalOp addr,
bool performInit) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 3b07273f513e6..786a0f6e9e23c 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1849,6 +1849,11 @@ class CIRGenFunction : public CIRGenTypeCache {
void emitStaticVarDecl(const VarDecl &d, cir::GlobalLinkageKind linkage);
+ /// Emit a guarded initializer for a static local variable or a static
+ /// data member of a class template instantiation.
+ void emitCXXGuardedInit(const VarDecl &varDecl, cir::GlobalOp globalOp,
+ bool performInit);
+
void emitStoreOfComplex(mlir::Location loc, mlir::Value v, LValue dest,
bool isInit);
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 007d501f25014..133eb0f3dfe7e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -830,7 +830,8 @@ mlir::Value CIRGenModule::getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty,
cir::GlobalOp g = getOrCreateCIRGlobal(d, ty, isForDefinition);
mlir::Type ptrTy = builder.getPointerTo(g.getSymType());
return cir::GetGlobalOp::create(builder, getLoc(d->getSourceRange()), ptrTy,
- g.getSymNameAttr(), tlsAccess);
+ g.getSymNameAttr(), tlsAccess,
+ g.getStaticLocal());
}
cir::GlobalViewAttr CIRGenModule::getAddrOfGlobalVarAttr(const VarDecl *d) {
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 2fe84873c5cdd..c38a77a7598b8 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1855,8 +1855,9 @@ cir::GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
if (getTls() && !g.getTlsModel())
return emitOpError("access to global not marked thread local");
// Verify that the static_local attribute matches between get_global and
- // global.
- if (getStaticLocal() != g.getStaticLocal())
+ // global. Skip when inside a GlobalOp region (e.g., ctor/dtor regions).
+ if (getStaticLocal() != g.getStaticLocal() &&
+ !getOperation()->getParentOfType<cir::GlobalOp>())
return emitOpError("static_local attribute mismatch");
} else if (auto f = dyn_cast<FuncOp>(op)) {
symTy = f.getFunctionType();
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index b7cc8775d298f..5c6d40027158f 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -872,6 +872,10 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
}
void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
+ // Static locals are handled separately via guard variables.
+ if (op.getStaticLocal())
+ return;
+
mlir::Region &ctorRegion = op.getCtorRegion();
mlir::Region &dtorRegion = op.getDtorRegion();
diff --git a/clang/test/CIR/CodeGen/global-array-dtor.cpp b/clang/test/CIR/CodeGen/global-array-dtor.cpp
index 01277a3f34015..43acb5b79e5f0 100644
--- a/clang/test/CIR/CodeGen/global-array-dtor.cpp
+++ b/clang/test/CIR/CodeGen/global-array-dtor.cpp
@@ -26,7 +26,7 @@ ArrayDtor arrDtor[16];
// CIR-BEFORE-LPP: }
// CIR-BEFORE-LPP: }
-// CIR: cir.global external @arrDtor = #cir.zero : !cir.array<!rec_ArrayDtor x 16> {alignment = 16 : i64}
+// CIR: cir.global external @arrDtor = #cir.zero : !cir.array<!rec_ArrayDtor x 16>
// CIR: cir.func internal private @__cxx_global_array_dtor(%[[ARR_ARG:.*]]: !cir.ptr<!void> {{.*}}) {
// CIR: %[[CONST15:.*]] = cir.const #cir.int<15> : !u64i
// CIR: %[[BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARR_ARG]] : !cir.ptr<!void> -> !cir.ptr<!rec_ArrayDtor>
diff --git a/clang/test/CIR/CodeGen/global-init.cpp b/clang/test/CIR/CodeGen/global-init.cpp
index 3510e3e82f4e8..ecfe25eed28cc 100644
--- a/clang/test/CIR/CodeGen/global-init.cpp
+++ b/clang/test/CIR/CodeGen/global-init.cpp
@@ -187,7 +187,7 @@ ArrayDtor arrDtor[16];
// CIR-BEFORE-LPP: }
// CIR-BEFORE-LPP: }
-// CIR: cir.global external @arrDtor = #cir.zero : !cir.array<!rec_ArrayDtor x 16> {alignment = 16 : i64}
+// CIR: cir.global external @arrDtor = #cir.zero : !cir.array<!rec_ArrayDtor x 16>
// CIR: cir.func internal private @__cxx_global_array_dtor(%[[ARR_ARG:.*]]: !cir.ptr<!void> {{.*}}) {
// CIR: %[[CONST15:.*]] = cir.const #cir.int<15> : !u64i
// CIR: %[[BEGIN:.*]] = cir.cast array_to_ptrdecay %[[ARR_ARG]] : !cir.ptr<!void> -> !cir.ptr<!rec_ArrayDtor>
diff --git a/clang/test/CIR/CodeGen/static-local.cpp b/clang/test/CIR/CodeGen/static-local.cpp
new file mode 100644
index 0000000000000..0297028e96438
--- /dev/null
+++ b/clang/test/CIR/CodeGen/static-local.cpp
@@ -0,0 +1,30 @@
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2>&1 | FileCheck %s --check-prefix=CIR-BEFORE-LPP
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefix=OGCG
+
+class A {
+public:
+ A();
+};
+
+void f() {
+ static A a;
+}
+
+// CIR-BEFORE-LPP: cir.global "private" internal dso_local static_local @_ZZ1fvE1a = ctor : !rec_A {
+// CIR-BEFORE-LPP: %[[ADDR:.*]] = cir.get_global @_ZZ1fvE1a : !cir.ptr<!rec_A>
+// CIR-BEFORE-LPP: cir.call @_ZN1AC1Ev(%[[ADDR]]) : (!cir.ptr<!rec_A>) -> ()
+// CIR-BEFORE-LPP: } {alignment = 1 : i64, ast = #cir.var.decl.ast}
+
+// CIR-BEFORE-LPP: cir.func no_inline dso_local @_Z1fv()
+// CIR-BEFORE-LPP: %[[VAR:.*]] = cir.get_global static_local @_ZZ1fvE1a : !cir.ptr<!rec_A>
+// CIR-BEFORE-LPP: cir.return
+
+// OGCG: @_ZGVZ1fvE1a = internal global i64 0
+// OGCG: define{{.*}}void @_Z1fv()
+// OGCG: %[[GUARD:.*]] = load atomic i8, ptr @_ZGVZ1fvE1a acquire
+// OGCG: %[[IS_UNINIT:.*]] = icmp eq i8 %[[GUARD]], 0
+// OGCG: br i1 %[[IS_UNINIT]]
+// OGCG: call i32 @__cxa_guard_acquire
+// OGCG: call void @_ZN1AC1Ev
+// OGCG: call void @__cxa_guard_release
+// OGCG: ret void
More information about the cfe-commits
mailing list