Skip to content

Commit 1430c52

Browse files
committed
[CIR] Implement static variable initialization with guarded init
Add support for C++ static local variables with non-trivial destructors and dynamic initialization. This implements the guarded initialization pattern required by the C++ standard for one-time initialization. **New CIR Operation: GuardedInitOp** - Defined in CIROps.td as `cir.guarded.init` - Represents the guarded initialization pattern for static locals - Contains an `init_region` for initialization code - Attributes: `thread_safe`, `perform_init` - Parameters: guard variable (pointer), static variable (symbol) **CIRGen Changes:** - CIRGenFunction: Added `emitCXXGuardedInit()` and `emitCXXGlobalVarDeclInit()` - CIRGenCXXABI: Added virtual `emitGuardedInit()` method for ABI abstraction - CIRGenItaniumCXXABI: Core implementation with guard variable creation - Follows Itanium C++ ABI specification - Guard variable mangling: `_ZGVZ{function}E{variable}` - Guard type: i64 (generic ABI) or i8 (internal linkage) - Zero-initialized with same linkage as guarded variable - CIRGenDecl: Integration at two call sites - Dynamic initialization (constant emission fails) - Constant init with non-trivial destructor **Lowering Implementation:** - LoweringPrepare: Converts GuardedInitOp to control flow - Pattern: `if (!guard) { init_code; guard = 1; }` - Thread-safe guards marked as NYI (requires __cxa_guard_* runtime calls) **Verifier:** - Validates guard variable is pointer type - Ensures init_region has exactly one block - Confirms termination with cir.yield Follows ClangIR's deferred lowering principle: - CIRGen emits high-level GuardedInitOp with initialization semantics - LoweringPrepare handles platform-specific lowering - Enables future optimizations (e.g., guard elision) Copies CodeGen structure from: - `clang/lib/CodeGen/CGDecl.cpp:353-402` (AddInitializerToStaticVarDecl) - `clang/lib/CodeGen/CGDeclCXX.cpp:176-232` (EmitCXXGlobalVarDeclInit) - `clang/lib/CodeGen/ItaniumCXXABI.cpp:2691-2906` (EmitGuardedInit) **New Test:** - `clang/test/CIR/CodeGen/static-var-init.cpp` - Tests CIR emission and LLVM lowering - Covers: destructor-only, dynamic init, constant init cases - Validates guard variable creation and initialization pattern **Crash Tests Fixed:** - `static-var-guarded-init.cpp` - Static with non-trivial destructor - `static-var-dyn-cast.cpp` - Static with template constructor - Both updated with CHECK directives, XFAIL removed ✅ Static variables with non-trivial destructors ✅ Static variables with dynamic initialization ✅ Static variables with constant init + non-trivial destructor ✅ Reference static variables ⏳ Thread-safe guards (NYI, marked for future work) Implements Section 3.3.2 "One-time construction API": - Guard variable semantics (first byte = initialization status) - Proper mangling for guard variables - Destructor registration via existing __cxa_atexit mechanism All code follows ClangIR standards: - CIRGen: camelBack for members/parameters/variables - Formatted with clang-format - Includes comprehensive comments explaining design Thread-safe guards (Phase 2): - Implement __cxa_guard_acquire/release/abort calls - Add atomic operations for guard variable access - Handle exception cleanup during initialization Potential optimizations: - Guard elision when initialization proven safe - Constant folding for always-initialized cases - TLS optimization for thread-local statics ghstack-source-id: 55a883a Pull-Request: #2045
1 parent 51870fc commit 1430c52

File tree

13 files changed

+1320
-1029
lines changed

13 files changed

+1320
-1029
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 829 additions & 1012 deletions
Large diffs are not rendered by default.

clang/lib/CIR/CodeGen/CIRGenCXX.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,30 @@ static void emitDeclDestroy(CIRGenFunction &CGF, const VarDecl *D) {
282282
CGM.getCXXABI().registerGlobalDtor(CGF, D, fnOp, nullptr);
283283
}
284284

285+
/// Emit the code to initialize a static local variable from within a function
286+
/// context. This is called from guarded init for static locals.
287+
void CIRGenFunction::emitCXXGlobalVarDeclInit(const VarDecl &D,
288+
Address DeclAddr,
289+
bool PerformInit) {
290+
const Expr *Init = D.getInit();
291+
QualType T = D.getType();
292+
293+
if (!T->isReferenceType()) {
294+
bool NeedsDtor =
295+
D.needsDestruction(getContext()) == QualType::DK_cxx_destructor;
296+
if (PerformInit)
297+
emitDeclInit(*this, &D, DeclAddr);
298+
if (NeedsDtor)
299+
emitDeclDestroy(*this, &D);
300+
return;
301+
}
302+
303+
assert(PerformInit && "cannot have constant initializer which needs "
304+
"destruction for reference");
305+
RValue RV = emitReferenceBindingToExpr(Init);
306+
emitStoreThroughLValue(RV, makeAddrLValue(DeclAddr, T));
307+
}
308+
285309
cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl GD) {
286310
const auto &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD);
287311
auto Fn = getAddrOfCXXStructor(GD, &FnInfo, /*FnType=*/nullptr,

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,19 @@ class CIRGenCXXABI {
182182
virtual void registerGlobalDtor(CIRGenFunction &CGF, const VarDecl *D,
183183
cir::FuncOp dtor, mlir::Value Addr) = 0;
184184

185+
/// Emit the guarded initialization code for static local variables with
186+
/// non-trivial initialization or destruction. This implements the C++
187+
/// standard's requirements for thread-safe static initialization.
188+
///
189+
/// \param CGF - The code generation function context
190+
/// \param D - The variable being initialized
191+
/// \param DeclPtr - The global variable representing the static local
192+
/// \param PerformInit - Whether to perform initialization (true) or just
193+
/// register the destructor (false, for constant-init
194+
/// variables with non-trivial destructors)
195+
virtual void emitGuardedInit(CIRGenFunction &CGF, const VarDecl &D,
196+
cir::GlobalOp DeclPtr, bool PerformInit) = 0;
197+
185198
virtual void emitVirtualObjectDelete(CIRGenFunction &CGF,
186199
const CXXDeleteExpr *DE, Address Ptr,
187200
QualType ElementType,

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,8 @@ cir::GlobalOp CIRGenFunction::addInitializerToStaticVarDecl(
635635
// Since we have a static initializer, this global variable can't
636636
// be constant.
637637
GV.setConstant(false);
638-
llvm_unreachable("C++ guarded init it NYI");
638+
639+
emitCXXGuardedInit(D, GV, /*PerformInit=*/true);
639640
}
640641
return GV;
641642
}
@@ -678,7 +679,7 @@ cir::GlobalOp CIRGenFunction::addInitializerToStaticVarDecl(
678679
// We have a constant initializer, but a nontrivial destructor. We still
679680
// need to perform a guarded "initialization" in order to register the
680681
// destructor.
681-
llvm_unreachable("C++ guarded init is NYI");
682+
emitCXXGuardedInit(D, GV, /*PerformInit=*/false);
682683
}
683684

684685
return GV;

clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include "CIRGenCXXABI.h"
1314
#include "CIRGenFunction.h"
1415
#include "CIRGenModule.h"
1516
#include "TargetInfo.h"
@@ -51,3 +52,15 @@ void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *D,
5152

5253
emitCXXGlobalVarDeclInit(D, Addr, PerformInit);
5354
}
55+
56+
void CIRGenFunction::emitCXXGuardedInit(const VarDecl &D, cir::GlobalOp DeclPtr,
57+
bool PerformInit) {
58+
// Darwin kernel configurations forbid guard variables to reduce overhead.
59+
// See CodeGen: CGDeclCXX.cpp:390-396
60+
if (CGM.getCodeGenOpts().ForbidGuardVariables)
61+
CGM.Error(D.getLocation(),
62+
"this initialization requires a guard variable, which "
63+
"the kernel does not support");
64+
65+
CGM.getCXXABI().emitGuardedInit(*this, D, DeclPtr, PerformInit);
66+
}

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2428,6 +2428,14 @@ class CIRGenFunction : public CIRGenTypeCache {
24282428

24292429
void emitStaticVarDecl(const VarDecl &D, cir::GlobalLinkageKind Linkage);
24302430

2431+
void emitCXXGuardedInit(const VarDecl &D, cir::GlobalOp DeclPtr,
2432+
bool PerformInit);
2433+
2434+
/// Emit code to perform initialization/destruction for a static local
2435+
/// variable from within a function. This is used by guarded init.
2436+
void emitCXXGlobalVarDeclInit(const VarDecl &D, Address DeclAddr,
2437+
bool PerformInit);
2438+
24312439
// Build CIR for a statement. useCurrentScope should be true if no
24322440
// new scopes need be created when finding a compound statement.
24332441
mlir::LogicalResult emitStmt(const clang::Stmt *S, bool useCurrentScope,

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
194194
QualType ThisTy) override;
195195
void registerGlobalDtor(CIRGenFunction &CGF, const VarDecl *D,
196196
cir::FuncOp dtor, mlir::Value Addr) override;
197+
void emitGuardedInit(CIRGenFunction &CGF, const VarDecl &D,
198+
cir::GlobalOp DeclPtr, bool PerformInit) override;
197199
void emitVirtualObjectDelete(CIRGenFunction &CGF, const CXXDeleteExpr *DE,
198200
Address Ptr, QualType ElementType,
199201
const CXXDestructorDecl *Dtor) override;
@@ -2375,6 +2377,145 @@ void CIRGenItaniumCXXABI::registerGlobalDtor(CIRGenFunction &CGF,
23752377
// prepare. Nothing to be done for CIR here.
23762378
}
23772379

2380+
void CIRGenItaniumCXXABI::emitGuardedInit(CIRGenFunction &CGF, const VarDecl &D,
2381+
cir::GlobalOp declPtr,
2382+
bool performInit) {
2383+
auto &builder = CGF.getBuilder();
2384+
mlir::Location loc = CGF.getLoc(D.getLocation());
2385+
2386+
// Inline variables that weren't instantiated from variable templates have
2387+
// partially-ordered initialization within their translation unit.
2388+
bool nonTemplateInline =
2389+
D.isInline() &&
2390+
!isTemplateInstantiation(D.getTemplateSpecializationKind());
2391+
2392+
// We only need to use thread-safe statics for local non-TLS variables and
2393+
// inline variables; other global initialization is always single-threaded
2394+
// or (through lazy dynamic loading in multiple threads) unsequenced.
2395+
bool threadSafe = getContext().getLangOpts().ThreadsafeStatics &&
2396+
(D.isLocalVarDecl() || nonTemplateInline) &&
2397+
!D.getTLSKind();
2398+
2399+
// If we have a global variable with internal linkage and thread-safe statics
2400+
// are disabled, we can just let the guard variable be of type i8.
2401+
bool useInt8GuardVariable =
2402+
!threadSafe &&
2403+
declPtr.getLinkage() == cir::GlobalLinkageKind::InternalLinkage;
2404+
2405+
// Determine the guard variable type.
2406+
mlir::Type guardTy;
2407+
if (useInt8GuardVariable) {
2408+
guardTy = builder.getUInt8Ty();
2409+
} else {
2410+
// Guard variables are 64 bits in the generic ABI and size width on ARM
2411+
// (i.e. 32-bit on AArch32, 64-bit on AArch64).
2412+
if (UseARMGuardVarABI) {
2413+
llvm_unreachable("NYI: ARM guard variable ABI");
2414+
} else {
2415+
guardTy = builder.getUInt64Ty();
2416+
}
2417+
}
2418+
2419+
// Create the guard variable if we don't already have it (as we
2420+
// might if we're double-emitting this function body).
2421+
// See CodeGen: ItaniumCXXABI.cpp:2735-2772
2422+
cir::GlobalOp guardGlobal = CGM.getStaticLocalDeclGuardAddress(&D);
2423+
if (!guardGlobal) {
2424+
// Mangle the name for the guard variable.
2425+
SmallString<256> guardName;
2426+
{
2427+
llvm::raw_svector_ostream out(guardName);
2428+
getMangleContext().mangleStaticGuardVariable(&D, out);
2429+
}
2430+
2431+
// Create the guard variable with a zero-initializer.
2432+
// Just absorb linkage, visibility and dll storage class from the guarded
2433+
// variable.
2434+
guardGlobal = CIRGenModule::createGlobalOp(
2435+
CGM, loc, guardName.str(), guardTy, /*isConstant=*/false,
2436+
{}, /*insertPoint=*/nullptr,
2437+
declPtr.getLinkage());
2438+
guardGlobal.setVisibility(declPtr.getVisibility());
2439+
guardGlobal.setDSOLocal(declPtr.isDSOLocal());
2440+
2441+
// DLL storage class should match the guarded variable.
2442+
// See CodeGen: ItaniumCXXABI.cpp:2755
2443+
assert(!cir::MissingFeatures::setDLLStorageClass());
2444+
2445+
// Set alignment for the guard variable based on its type.
2446+
// See CodeGen: ItaniumCXXABI.cpp:2716-2728, 2758
2447+
CharUnits guardAlignment;
2448+
if (useInt8GuardVariable) {
2449+
guardAlignment = CharUnits::One();
2450+
} else {
2451+
if (UseARMGuardVarABI) {
2452+
llvm_unreachable("NYI: ARM guard variable ABI alignment");
2453+
} else {
2454+
guardAlignment = CharUnits::fromQuantity(
2455+
CGM.getDataLayout().getABITypeAlign(guardTy));
2456+
}
2457+
}
2458+
guardGlobal.setAlignment(guardAlignment.getQuantity());
2459+
2460+
// Set the initial value to zero.
2461+
guardGlobal.setInitialValueAttr(builder.getZeroInitAttr(guardTy));
2462+
2463+
// The ABI says: "It is suggested that it be emitted in the same COMDAT
2464+
// group as the associated data object." In practice, this doesn't work for
2465+
// non-ELF and non-Wasm object formats, so only do it for ELF and Wasm.
2466+
// See CodeGen: ItaniumCXXABI.cpp:2760-2770
2467+
bool declHasComdat = declPtr.getComdat();
2468+
if (!D.isLocalVarDecl() && declHasComdat &&
2469+
(CGM.getTarget().getTriple().isOSBinFormatELF() ||
2470+
CGM.getTarget().getTriple().isOSBinFormatWasm())) {
2471+
guardGlobal.setComdat(true);
2472+
} else if (CGM.supportsCOMDAT() && guardGlobal.isWeakForLinker()) {
2473+
guardGlobal.setComdat(true);
2474+
}
2475+
2476+
// Register the guard variable so we don't create it again.
2477+
CGM.setStaticLocalDeclGuardAddress(&D, guardGlobal);
2478+
}
2479+
2480+
// If the variable is thread-local, so is its guard variable.
2481+
if (D.getTLSKind())
2482+
llvm_unreachable("NYI: thread-local guard variables");
2483+
2484+
// Create a pointer to the guard variable for the GuardedInitOp.
2485+
auto guardAddr = builder.createGetGlobal(guardGlobal);
2486+
2487+
// Get a symbol reference to the static variable.
2488+
auto staticVarSymbol =
2489+
mlir::FlatSymbolRefAttr::get(builder.getContext(), declPtr.getSymName());
2490+
2491+
// Create the GuardedInitOp.
2492+
bool isLocalVar = D.isLocalVarDecl();
2493+
mlir::UnitAttr threadSafeAttr = threadSafe ? builder.getUnitAttr() : nullptr;
2494+
mlir::UnitAttr performInitAttr =
2495+
performInit ? builder.getUnitAttr() : nullptr;
2496+
mlir::UnitAttr isLocalVarAttr = isLocalVar ? builder.getUnitAttr() : nullptr;
2497+
auto guardedInitOp = builder.create<cir::GuardedInitOp>(
2498+
loc, guardAddr, staticVarSymbol, threadSafeAttr, performInitAttr,
2499+
isLocalVarAttr);
2500+
2501+
// Build the init region.
2502+
mlir::Region &initRegion = guardedInitOp.getInitRegion();
2503+
mlir::OpBuilder::InsertionGuard guard(builder);
2504+
mlir::Block *initBlock = builder.createBlock(&initRegion);
2505+
builder.setInsertionPointToStart(initBlock);
2506+
2507+
// Emit the initialization code if performInit is true.
2508+
if (performInit) {
2509+
// Get the address of the static variable for initialization.
2510+
Address declAddr(CGF.getBuilder().createGetGlobal(declPtr),
2511+
declPtr.getSymType(), getContext().getDeclAlign(&D));
2512+
CGF.emitCXXGlobalVarDeclInit(D, declAddr, /*performInit=*/true);
2513+
}
2514+
2515+
// Terminate the init region with a yield.
2516+
builder.create<cir::YieldOp>(loc);
2517+
}
2518+
23782519
mlir::Value CIRGenItaniumCXXABI::getCXXDestructorImplicitParam(
23792520
CIRGenFunction &CGF, const CXXDestructorDecl *DD, CXXDtorType Type,
23802521
bool ForVirtualBase, bool Delegating) {

clang/lib/CIR/CodeGen/CIRGenModule.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,9 @@ class CIRGenModule : public CIRGenTypeCache {
239239
llvm::DenseMap<const Decl *, cir::GlobalOp> StaticLocalDeclMap;
240240
llvm::DenseMap<const Decl *, cir::GlobalOp> staticLocalDeclGuardMap;
241241
llvm::DenseMap<const VarDecl *, cir::GlobalOp> initializerConstants;
242+
243+
llvm::DenseMap<const Decl *, cir::GlobalOp> StaticLocalDeclGuardMap;
244+
242245
llvm::DenseMap<llvm::StringRef, mlir::Value> Globals;
243246
mlir::Operation *getGlobalValue(llvm::StringRef Ref);
244247
mlir::Value getGlobalValue(const clang::Decl *D);
@@ -258,6 +261,14 @@ class CIRGenModule : public CIRGenTypeCache {
258261
StaticLocalDeclMap[D] = C;
259262
}
260263

264+
cir::GlobalOp getStaticLocalDeclGuardAddress(const VarDecl *D) {
265+
return StaticLocalDeclGuardMap[D];
266+
}
267+
268+
void setStaticLocalDeclGuardAddress(const VarDecl *D, cir::GlobalOp C) {
269+
StaticLocalDeclGuardMap[D] = C;
270+
}
271+
261272
cir::GlobalOp getOrCreateStaticVarDecl(const VarDecl &D,
262273
cir::GlobalLinkageKind Linkage);
263274

clang/lib/CIR/Dialect/IR/CIRDialect.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2570,6 +2570,28 @@ cir::GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
25702570
return success();
25712571
}
25722572

2573+
//===----------------------------------------------------------------------===//
2574+
// GuardedInitOp
2575+
//===----------------------------------------------------------------------===//
2576+
2577+
LogicalResult cir::GuardedInitOp::verify() {
2578+
// Verify that the guard variable is a pointer type.
2579+
auto guardPtrTy = mlir::dyn_cast<PointerType>(getGuardVar().getType());
2580+
if (!guardPtrTy)
2581+
return emitOpError("guard_var must be a pointer type");
2582+
2583+
// Verify that the init region has exactly one block.
2584+
if (getInitRegion().getBlocks().size() != 1)
2585+
return emitOpError("init_region must have exactly one block");
2586+
2587+
// Verify that the init region terminates with a yield.
2588+
auto &block = getInitRegion().front();
2589+
if (block.empty() || !isa<YieldOp>(block.back()))
2590+
return emitOpError("init_region must terminate with cir.yield");
2591+
2592+
return success();
2593+
}
2594+
25732595
//===----------------------------------------------------------------------===//
25742596
// VTableAddrPointOp
25752597
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)