[llvm-branch-commits] [clang] [CIR] Add CIRGen support for static local variables with non-constant initializers (PR #179827)
Bruno Cardoso Lopes via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Tue Feb 10 17:51:43 PST 2026
https://github.com/bcardosolopes updated https://github.com/llvm/llvm-project/pull/179827
>From 5f91b8cb9f7be4033e426544a17b5afe8014fb8b Mon Sep 17 00:00:00 2001
From: Bruno Cardoso Lopes <bruno.cardoso at gmail.com>
Date: Fri, 30 Jan 2026 22:48:48 -0800
Subject: [PATCH] [CIR] Add CIRGen support for static local variables with
non-constant initializers
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
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.
---
clang/include/clang/CIR/Dialect/IR/CIRAttrs.h | 1 +
.../include/clang/CIR/Dialect/IR/CIRAttrs.td | 49 +++++++++++++++++++
clang/include/clang/CIR/Dialect/IR/CIROps.td | 3 +-
clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 3 +-
clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 7 +++
clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 16 ++++--
clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp | 14 ++++++
clang/lib/CIR/CodeGen/CIRGenFunction.h | 5 ++
clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 15 ++++++
clang/test/CIR/CodeGen/global-array-dtor.cpp | 2 +-
clang/test/CIR/CodeGen/global-init.cpp | 2 +-
clang/test/CIR/CodeGen/static-local.cpp | 30 ++++++++++++
12 files changed, 140 insertions(+), 7 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/static-local.cpp
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..0d2648e8c82d7 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,52 @@ 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 appropriated
+ CIR operation is delegated to the operation verifier.
+
+ This always implies a non-null AST reference (verified).
+ }];
+ let parameters = (ins clang_name:$ast);
+
+ // Printing and parsing available in CIRDialect.cpp
+ let hasCustomAssemblyFormat = 1;
+
+ // Enable verifier.
+ let genVerifyDecl = 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.
+ }
+
+ llvm::LogicalResult $cppClass::verify(
+ ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError,
+ }] # clang_name # [{ decl) {
+ return mlir::success();
+ }
+ }];
+}
+
+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 c37764ed70202..282733f0045bf 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -2358,7 +2358,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/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
index 27d48bfabeb38..3d6db471f0c99 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
+++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h
@@ -188,6 +188,13 @@ class CIRGenCXXABI {
bool forVirtualBase, bool delegating,
Address thisAddr, QualType thisTy) = 0;
+ /// Emit a guarded initializer for a static local variable or a static
+ /// data member. This handles:
+ /// - a static local variable
+ /// - a static data member of a class template instantiation
+ virtual void emitGuardedInit(CIRGenFunction &cgf, const VarDecl &varDecl,
+ cir::GlobalOp globalOp, bool performInit) = 0;
+
/// Emit code to force the execution of a destructor during global
/// teardown. The default implementation of this uses atexit.
///
diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
index 42a7d70677b61..8fa38b093e958 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp
@@ -568,13 +568,23 @@ 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.errorUnsupported(d.getInit(), "constant l-value expression");
+ else if (d.hasFlexibleArrayInit(getContext()))
+ cgm.errorUnsupported(d.getInit(), "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..dcc27006cb87e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
@@ -10,6 +10,8 @@
//
//===----------------------------------------------------------------------===//
+#include "CIRGenCXXABI.h"
+#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "clang/AST/Attr.h"
#include "clang/Basic/LangOptions.h"
@@ -17,6 +19,18 @@
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.errorNYI(varDecl.getLocation(), "guard variables are forbidden");
+
+ cgm.getCXXABI().emitGuardedInit(*this, varDecl, globalOp, performInit);
+}
+
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 adcf4d56e3892..551dc6303d723 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -1842,6 +1842,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/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index aca2278c3876c..1c1a87a673e25 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -74,6 +74,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
QualType thisTy) override;
void registerGlobalDtor(const VarDecl *vd, cir::FuncOp dtor,
mlir::Value addr) override;
+ void emitGuardedInit(CIRGenFunction &cgf, const VarDecl &varDecl,
+ cir::GlobalOp globalOp, bool performInit) override;
void emitVirtualObjectDelete(CIRGenFunction &cgf, const CXXDeleteExpr *de,
Address ptr, QualType elementType,
const CXXDestructorDecl *dtor) override;
@@ -1592,6 +1594,19 @@ void CIRGenItaniumCXXABI::registerGlobalDtor(const VarDecl *vd,
// prepare. Nothing to be done for CIR here.
}
+void CIRGenItaniumCXXABI::emitGuardedInit(CIRGenFunction &cgf,
+ const VarDecl &varDecl,
+ cir::GlobalOp globalOp,
+ bool performInit) {
+ // Emit the initializer and add a global destructor if appropriate.
+ cgf.cgm.emitCXXGlobalVarDeclInit(&varDecl, globalOp, performInit);
+
+ // CIR diverges from IRGen here by emitting the init into the ctor region and
+ // marking the global as static local. The emission of the guard/acquire walk
+ // is done during LoweringPrepare.
+ globalOp.setStaticLocal(true);
+}
+
mlir::Value CIRGenItaniumCXXABI::getCXXDestructorImplicitParam(
CIRGenFunction &cgf, const CXXDestructorDecl *dd, CXXDtorType type,
bool forVirtualBase, bool delegating) {
diff --git a/clang/test/CIR/CodeGen/global-array-dtor.cpp b/clang/test/CIR/CodeGen/global-array-dtor.cpp
index 01277a3f34015..b39373c0d61bb 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> {alignment = 16 : i64{{.*}}}
// 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..51246cbe1c86a 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> {alignment = 16 : i64{{.*}}}
// 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 llvm-branch-commits
mailing list