Skip to content

Commit b7f6a08

Browse files
committed
[CIR] Implement static local variable initialization with guard variables
This implements thread-safe initialization of function-local static variables with dynamic initializers, following the Itanium C++ ABI. The implementation: 1. Adds `static_local` attribute to GlobalOp and GetGlobalOp to mark function-local statics requiring guarded initialization 2. Implements guarded initialization in LoweringPrepare pass using __cxa_guard_acquire/__cxa_guard_release for thread safety 3. Adds helper methods createIsNull/createIsNotNull to CIRBaseBuilder 4. Implements emitGuardedInit in CIRGenItaniumCXXABI to emit the ctor region and mark the global as static_local 5. Adds mangleStaticGuardVariable to ASTAttrInterfaces for mangling The guard variable pattern follows Itanium ABI 3.3.2: - First byte of guard checked with acquire load for fast path - __cxa_guard_acquire called if uninitialized - __cxa_guard_release called after successful initialization ghstack-source-id: 29329f1 Pull-Request: #2046
1 parent e0e0345 commit b7f6a08

File tree

16 files changed

+878
-103
lines changed

16 files changed

+878
-103
lines changed

clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,18 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
223223
return createCompare(loc, cir::CmpOpKind::ne, operand, operand);
224224
}
225225

226+
/// Return a boolean value testing if \p arg == 0.
227+
mlir::Value createIsNull(mlir::Location loc, mlir::Value arg) {
228+
return createCompare(loc, cir::CmpOpKind::eq, arg,
229+
getNullValue(arg.getType(), loc));
230+
}
231+
232+
/// Return a boolean value testing if \p arg != 0.
233+
mlir::Value createIsNotNull(mlir::Location loc, mlir::Value arg) {
234+
return createCompare(loc, cir::CmpOpKind::ne, arg,
235+
getNullValue(arg.getType(), loc));
236+
}
237+
226238
mlir::Value createUnaryOp(mlir::Location loc, cir::UnaryOpKind kind,
227239
mlir::Value operand) {
228240
return cir::UnaryOp::create(*this, loc, kind, operand);
@@ -399,7 +411,6 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
399411
/*tbaa=*/cir::TBAAAttr{});
400412
}
401413

402-
403414
mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType,
404415
mlir::Type type, llvm::StringRef name,
405416
mlir::IntegerAttr alignment,

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

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2600,6 +2600,10 @@ def CIR_GlobalOp : CIR_Op<"global", [
26002600

26012601
`visibility_attr` is defined in terms of CIR's visibility.
26022602

2603+
The `static_local` attribute indicates that this global represents a
2604+
function-local static variable that requires guarded initialization
2605+
(e.g., C++ static local variables with non-constant initializers).
2606+
26032607
Example:
26042608

26052609
```mlir
@@ -2611,27 +2615,18 @@ def CIR_GlobalOp : CIR_Op<"global", [
26112615
// Note that both sym_name and sym_visibility are tied to Symbol trait.
26122616
// TODO: sym_visibility can possibly be represented by implementing the
26132617
// necessary Symbol's interface in terms of linkage instead.
2614-
let arguments = (ins
2615-
SymbolNameAttr:$sym_name,
2616-
DefaultValuedAttr<
2617-
CIR_VisibilityAttr,
2618-
"VisibilityKind::Default"
2619-
>:$global_visibility,
2620-
OptionalAttr<StrAttr>:$sym_visibility,
2621-
TypeAttr:$sym_type,
2622-
CIR_GlobalLinkageKind:$linkage,
2623-
OptionalAttr<MemorySpaceAttrInterface>:$addr_space,
2624-
OptionalAttr<CIR_TLSModel>:$tls_model,
2625-
// Note this can also be a FlatSymbolRefAttr
2626-
OptionalAttr<AnyAttr>:$initial_value,
2627-
UnitAttr:$comdat,
2628-
UnitAttr:$constant,
2629-
UnitAttr:$dso_local,
2630-
OptionalAttr<I64Attr>:$alignment,
2631-
OptionalAttr<ASTVarDeclInterface>:$ast,
2632-
OptionalAttr<StrAttr>:$section,
2633-
OptionalAttr<ArrayAttr>:$annotations
2634-
);
2618+
let arguments = (ins SymbolNameAttr:$sym_name,
2619+
DefaultValuedAttr<CIR_VisibilityAttr,
2620+
"VisibilityKind::Default">:$global_visibility,
2621+
OptionalAttr<StrAttr>:$sym_visibility, TypeAttr:$sym_type,
2622+
CIR_GlobalLinkageKind:$linkage,
2623+
OptionalAttr<MemorySpaceAttrInterface>:$addr_space,
2624+
OptionalAttr<CIR_TLSModel>:$tls_model,
2625+
// Note this can also be a FlatSymbolRefAttr
2626+
OptionalAttr<AnyAttr>:$initial_value, UnitAttr:$comdat,
2627+
UnitAttr:$constant, UnitAttr:$dso_local, UnitAttr:$static_local,
2628+
OptionalAttr<I64Attr>:$alignment, OptionalAttr<ASTVarDeclInterface>:$ast,
2629+
OptionalAttr<StrAttr>:$section, OptionalAttr<ArrayAttr>:$annotations);
26352630

26362631
let regions = (region AnyRegion:$ctorRegion, AnyRegion:$dtorRegion);
26372632

@@ -2643,6 +2638,7 @@ def CIR_GlobalOp : CIR_Op<"global", [
26432638
(`comdat` $comdat^)?
26442639
($tls_model^)?
26452640
(`dso_local` $dso_local^)?
2641+
(`static_local` $static_local^)?
26462642
(` ` custom<GlobalAddressSpaceValue>($addr_space)^ )?
26472643
$sym_name
26482644
custom<GlobalOpTypeAndInitialValue>($sym_type, $initial_value, $ctorRegion, $dtorRegion)
@@ -2696,6 +2692,10 @@ def CIR_GetGlobalOp : CIR_Op<"get_global", [
26962692
Addresses of thread local globals can only be retrieved if this operation
26972693
is marked `thread_local`, which indicates the address isn't constant.
26982694

2695+
The `static_local` attribute indicates that this global is a function-local
2696+
static variable that requires guarded initialization (e.g., C++ static
2697+
local variables with non-constant initializers).
2698+
26992699
Example:
27002700
```mlir
27012701
%x = cir.get_global @foo : !cir.ptr<i32>
@@ -2704,14 +2704,18 @@ def CIR_GetGlobalOp : CIR_Op<"get_global", [
27042704
...
27052705
cir.global external addrspace(offload_global) @gv = #cir.int<0> : !s32i
27062706
%z = cir.get_global @gv : !cir.ptr<!s32i, addrspace(offload_global)>
2707+
...
2708+
%w = cir.get_global static_local @func_static : !cir.ptr<i32>
27072709
```
27082710
}];
27092711

2710-
let arguments = (ins FlatSymbolRefAttr:$name, UnitAttr:$tls);
2712+
let arguments = (ins FlatSymbolRefAttr:$name, UnitAttr:$tls,
2713+
UnitAttr:$static_local);
27112714
let results = (outs Res<CIR_PointerType, "", []>:$addr);
27122715

27132716
let assemblyFormat = [{
27142717
(`thread_local` $tls^)?
2718+
(`static_local` $static_local^)?
27152719
$name `:` qualified(type($addr)) attr-dict
27162720
}];
27172721
}

clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,20 +63,48 @@ let cppNamespace = "::cir" in {
6363

6464
def ASTVarDeclInterface : AttrInterface<"ASTVarDeclInterface",
6565
[ASTDeclaratorDeclInterface]> {
66-
let methods = [
67-
InterfaceMethod<"", "void", "mangleDynamicInitializer", (ins "llvm::raw_ostream&":$Out), [{}],
68-
/*defaultImplementation=*/ [{
66+
let methods = [InterfaceMethod<"", "void", "mangleDynamicInitializer",
67+
(ins "llvm::raw_ostream&":$Out), [{}],
68+
/*defaultImplementation=*/[{
6969
std::unique_ptr<clang::MangleContext> MangleCtx(
7070
$_attr.getAst()->getASTContext().createMangleContext());
7171
MangleCtx->mangleDynamicInitializer($_attr.getAst(), Out);
72-
}]
73-
>,
74-
InterfaceMethod<"", "clang::VarDecl::TLSKind", "getTLSKind", (ins), [{}],
75-
/*defaultImplementation=*/ [{
72+
}]>,
73+
InterfaceMethod<"", "void", "mangleStaticGuardVariable",
74+
(ins "llvm::raw_ostream&":$Out), [{}],
75+
/*defaultImplementation=*/[{
76+
std::unique_ptr<clang::MangleContext> mangleCtx(
77+
$_attr.getAst()->getASTContext().createMangleContext());
78+
mangleCtx->mangleStaticGuardVariable($_attr.getAst(), Out);
79+
}]>,
80+
InterfaceMethod<"", "clang::VarDecl::TLSKind", "getTLSKind",
81+
(ins), [{}],
82+
/*defaultImplementation=*/[{
7683
return $_attr.getAst()->getTLSKind();
77-
}]
78-
>
79-
];
84+
}]>,
85+
InterfaceMethod<"", "bool", "isInline", (ins), [{}],
86+
/*defaultImplementation=*/[{
87+
return $_attr.getAst()->isInline();
88+
}]>,
89+
InterfaceMethod<"", "clang::TemplateSpecializationKind",
90+
"getTemplateSpecializationKind", (ins), [{}],
91+
/*defaultImplementation=*/[{
92+
return $_attr.getAst()->getTemplateSpecializationKind();
93+
}]>,
94+
InterfaceMethod<"", "bool", "isLocalVarDecl", (ins), [{}],
95+
/*defaultImplementation=*/[{
96+
return $_attr.getAst()->isLocalVarDecl();
97+
}]>,
98+
InterfaceMethod<"", "clang::SourceLocation", "getLocation",
99+
(ins), [{}],
100+
/*defaultImplementation=*/[{
101+
return $_attr.getAst()->getLocation();
102+
}]>,
103+
InterfaceMethod<"", "const clang::VarDecl *", "getRawDecl",
104+
(ins), [{}],
105+
/*defaultImplementation=*/[{
106+
return $_attr.getAst();
107+
}]>];
80108
}
81109

82110
def ASTFunctionDeclInterface : AttrInterface<"ASTFunctionDeclInterface",

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ struct MissingFeatures {
127127
static bool setFunctionAttributes() { return false; }
128128
static bool attributeBuiltin() { return false; }
129129
static bool attributeNoBuiltin() { return false; }
130+
static bool functionIndexAttribute() { return false; }
131+
static bool noUnwindAttribute() { return false; }
130132
static bool parameterAttributes() { return false; }
131133
static bool minLegalVectorWidthAttr() { return false; }
132134
static bool vscaleRangeAttr() { return false; }
@@ -160,6 +162,7 @@ struct MissingFeatures {
160162

161163
// Folding methods.
162164
static bool foldBinOpFMF() { return false; }
165+
static bool folder() { return false; }
163166

164167
// Fast math.
165168
static bool fastMathGuard() { return false; }
@@ -484,6 +487,13 @@ struct MissingFeatures {
484487
static bool dataLayoutPtrHandlingBasedOnLangAS() { return false; }
485488

486489
static bool tailCall() { return false; }
490+
491+
static bool addressSpaceInGlobalVar() { return false; }
492+
493+
static bool useARMGuardVarABI() { return false; }
494+
495+
// Static local variable guard
496+
static bool guardAbortOnException() { return false; }
487497
};
488498

489499
} // namespace cir

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,19 @@ class CIRGenCXXABI {
174174
bool ForVirtualBase, bool Delegating,
175175
Address This, QualType ThisTy) = 0;
176176

177+
/*************************** Static local guards ****************************/
178+
179+
/// Emits the guarded initializer and destructor setup for the given
180+
/// variable, given that it couldn't be emitted as a constant.
181+
/// If \p PerformInit is false, the initialization has been folded to a
182+
/// constant and should not be performed.
183+
///
184+
/// The variable may be:
185+
/// - a static local variable
186+
/// - a static data member of a class template instantiation
187+
virtual void emitGuardedInit(CIRGenFunction &cgf, const VarDecl &varDecl,
188+
cir::GlobalOp globalOp, bool performInit) = 0;
189+
177190
/// Emit code to force the execution of a destructor during global
178191
/// teardown. The default implementation of this uses atexit.
179192
///

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,8 +636,9 @@ CIRGenFunction::addInitializerToStaticVarDecl(const VarDecl &varDecl,
636636
else {
637637
// Since we have a static initializer, this global variable can't
638638
// be constant.
639-
llvm_unreachable("C++ guarded init it NYI");
640639
globalOp.setConstant(false);
640+
emitCXXGuardedInit(varDecl, globalOp, /*performInit*/ true);
641+
getGlobalOp.setStaticLocal(true);
641642
}
642643
return globalOp;
643644
}
@@ -660,13 +661,34 @@ CIRGenFunction::addInitializerToStaticVarDecl(const VarDecl &varDecl,
660661
if (globalOp.getSymType() != typedInit.getType()) {
661662
globalOp.setSymType(typedInit.getType());
662663

664+
cir::GlobalOp oldGlobalOp = globalOp;
665+
globalOp =
666+
builder.createGlobal(CGM.getModule(), getLoc(varDecl.getSourceRange()),
667+
oldGlobalOp.getName(), typedInit.getType(),
668+
oldGlobalOp.getConstant(), globalOp.getLinkage());
669+
// FIXME(cir): OG codegen inserts new GV before old one, we probably don't
670+
// need that?
671+
globalOp.setVisibility(oldGlobalOp.getVisibility());
672+
globalOp.setGlobalVisibilityAttr(oldGlobalOp.getGlobalVisibilityAttr());
673+
globalOp.setInitialValueAttr(typedInit);
674+
globalOp.setTlsModelAttr(oldGlobalOp.getTlsModelAttr());
675+
globalOp.setDSOLocal(oldGlobalOp.getDsoLocal());
676+
assert(!cir::MissingFeatures::setComdat());
677+
assert(!cir::MissingFeatures::addressSpaceInGlobalVar());
678+
663679
// Normally this should be done with a call to CGM.replaceGlobal(OldGV, GV),
664680
// but since at this point the current block hasn't been really attached,
665681
// there's no visibility into the GetGlobalOp corresponding to this Global.
666682
// Given those constraints, thread in the GetGlobalOp and update it
667683
// directly.
668684
getGlobalOp.getAddr().setType(getBuilder().getPointerTo(
669685
typedInit.getType(), globalOp.getAddrSpaceAttr()));
686+
687+
// Replace all uses of the old global with the new global
688+
oldGlobalOp->replaceAllUsesWith(globalOp);
689+
690+
// Erase the old global, since it is no longer used.
691+
oldGlobalOp->erase();
670692
}
671693

672694
bool needsDtor =

clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp

Lines changed: 21 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,23 @@ void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *D,
5152

5253
emitCXXGlobalVarDeclInit(D, Addr, PerformInit);
5354
}
55+
56+
void CIRGenFunction::emitCXXGuardedInit(const VarDecl &varDecl,
57+
cir::GlobalOp globalOp,
58+
bool performInit) {
59+
// If we've been asked to forbid guard variables, emit an error now. This
60+
// diagnostic is hard-coded for Darwin's use case; we can find better phrasing
61+
// if someone else needs it.
62+
if (CGM.getCodeGenOpts().ForbidGuardVariables)
63+
llvm_unreachable("NYI");
64+
65+
CGM.getCXXABI().emitGuardedInit(*this, varDecl, globalOp, performInit);
66+
}
67+
68+
void CIRGenFunction::emitCXXGlobalVarDeclInit(const VarDecl &varDecl,
69+
cir::GlobalOp globalOp,
70+
bool performInit) {
71+
// TODO(CIR): We diverge from CodeGen here via having this in CIRGenModule
72+
// instead. This is necessary due to the way we are constructing global inits.
73+
llvm_unreachable("NYI");
74+
}

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ void CIRGenFunction::LexicalScope::cleanup() {
438438
// An empty non-entry block has nothing to offer, and since this is
439439
// synthetic, losing information does not affect anything.
440440
bool entryBlock = builder.getInsertionBlock()->isEntryBlock();
441-
if (!entryBlock && currBlock->empty()) {
441+
if (!entryBlock && currBlock->empty() && currBlock->hasNoPredecessors()) {
442442
currBlock->erase();
443443
// Remove unused cleanup blocks.
444444
if (cleanupBlock && cleanupBlock->hasNoPredecessors())

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,6 +2243,19 @@ class CIRGenFunction : public CIRGenTypeCache {
22432243

22442244
void emitInvariantStart(CharUnits Size);
22452245

2246+
/// Emit code in this function to perform a guarded variable
2247+
/// initialization. Guarded initializations are used when it's not
2248+
/// possible to prove that an initialization will be done exactly
2249+
/// once, e.g. with a static local variable or a static data member
2250+
/// of a class template.
2251+
void emitCXXGuardedInit(const VarDecl &varDecl, cir::GlobalOp globalOp,
2252+
bool performInit);
2253+
2254+
/// EmitCXXGlobalVarDeclInit - Create the initializer for a C++
2255+
/// variable with global storage.
2256+
void emitCXXGlobalVarDeclInit(const VarDecl &varDecl, cir::GlobalOp globalOp,
2257+
bool performInit);
2258+
22462259
mlir::LogicalResult emitLabel(const clang::LabelDecl *D);
22472260
mlir::LogicalResult emitLabelStmt(const clang::LabelStmt &S);
22482261

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,27 @@
1717
//
1818
//===----------------------------------------------------------------------===//
1919

20+
#include "CIRGenBuilder.h"
2021
#include "CIRGenCXXABI.h"
2122
#include "CIRGenCleanup.h"
23+
#include "CIRGenFunction.h"
2224
#include "CIRGenFunctionInfo.h"
25+
#include "CIRGenModule.h"
2326
#include "ConstantInitBuilder.h"
27+
#include "mlir/IR/Block.h"
28+
#include "mlir/IR/BuiltinAttributes.h"
2429

2530
#include "clang/AST/GlobalDecl.h"
2631
#include "clang/AST/Mangle.h"
2732
#include "clang/AST/VTableBuilder.h"
2833
#include "clang/Basic/Linkage.h"
34+
#include "clang/Basic/Specifiers.h"
2935
#include "clang/Basic/TargetInfo.h"
3036
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
37+
#include "clang/CIR/Dialect/IR/CIRDialect.h"
38+
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
39+
#include "clang/CIR/Dialect/IR/CIRTypes.h"
40+
#include "clang/CIR/MissingFeatures.h"
3141
#include "llvm/Support/ErrorHandling.h"
3242

3343
using namespace clang;
@@ -192,6 +202,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI {
192202
CXXDtorType Type, bool ForVirtualBase,
193203
bool Delegating, Address This,
194204
QualType ThisTy) override;
205+
void emitGuardedInit(CIRGenFunction &cgf, const VarDecl &varDecl,
206+
cir::GlobalOp globalOp, bool performInit) override;
195207
void registerGlobalDtor(CIRGenFunction &CGF, const VarDecl *D,
196208
cir::FuncOp dtor, mlir::Value Addr) override;
197209
void emitVirtualObjectDelete(CIRGenFunction &CGF, const CXXDeleteExpr *DE,
@@ -3019,3 +3031,21 @@ LValue CIRGenItaniumCXXABI::emitThreadLocalVarDeclLValue(CIRGenFunction &cgf,
30193031
lv = cgf.makeAddrLValue(addr, lvalType, AlignmentSource::Decl);
30203032
return lv;
30213033
}
3034+
3035+
/// The ARM code here follows the Itanium code closely enough that we just
3036+
/// special-case it at particular places.
3037+
void CIRGenItaniumCXXABI::emitGuardedInit(CIRGenFunction &cgf,
3038+
const VarDecl &varDecl,
3039+
cir::GlobalOp globalOp,
3040+
bool performInit) {
3041+
// NOTE(CIR): this diverges from CodeGen. We handle all these considerations
3042+
// in LoweringPrepare::handleStaticLocal
3043+
3044+
// Emit the initializer and add a global destructor if appropriate.
3045+
cgf.CGM.emitCXXGlobalVarDeclInit(&varDecl, globalOp, performInit);
3046+
3047+
// CIR diverges from IRGen here by emitting the init into the ctor region and
3048+
// marking the global as static local. The emission of the guard/acquire walk
3049+
// is done during LoweringPrepare.
3050+
globalOp.setStaticLocal(true);
3051+
}

0 commit comments

Comments
 (0)