Skip to content

Commit 1c90be3

Browse files
erichkeanexlauko
andauthored
[CIR] Implement static-local-tls lowering (llvm#194059)
thread_local variables at function scope work very much like static-locals, except with slightly different lowering from cir-lowering-prepare. This patch implements that lowering. Global tls variables are left to a later patch. One decision I made here was that LocalInitOp lost its 'static-local'-ness, and assumes it is always static-local. Global TLS is probably just going to use Global directly, so we don't need to to permit it. I DID leave it in the printing, as it makes it more clear what is happening/for symmetry with get_global/global. --------- Co-authored-by: Henrich Lauko <henrich.lau@gmail.com>
1 parent 6226ca5 commit 1c90be3

9 files changed

Lines changed: 452 additions & 111 deletions

File tree

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3819,9 +3819,7 @@ def CIR_FuncOp : CIR_Op<"func", [
38193819
//===----------------------------------------------------------------------===//
38203820

38213821
def CIR_LocalInitOp : CIR_Op<"local_init", [
3822-
DeclareOpInterfaceMethods<SymbolUserOpInterface>, NoRegionArguments,
3823-
ParentOneOf<["cir::FuncOp"]>,
3824-
HasAtMostOneOfAttrs<["tls", "static_local"]>
3822+
DeclareOpInterfaceMethods<SymbolUserOpInterface>, NoRegionArguments
38253823
]> {
38263824
let summary = "initialize a static or thread local object";
38273825
let description = [{
@@ -3830,8 +3828,13 @@ def CIR_LocalInitOp : CIR_Op<"local_init", [
38303828
variable. This will be handled during lowering-prepare to include the
38313829
guard variables correctly for the variable.
38323830

3831+
This operation may also represent a static local thread local variable,
3832+
which would be indicated by the 'tls' flag.
3833+
38333834
Example:
38343835
```
3836+
// Note: despite this always being static_local, we print it anyway to be
3837+
// visually consistent with get_global, and as a 'counter' to 'tls'.
38353838
cir.local_init static_local @GlobalName ctor {
38363839
%4 = cir.get_global static_local @GlobalName : !cir.ptr<!rec_CtorDtor>
38373840
%5 = cir.call @_Z5get_iv() : () -> !s32i
@@ -3846,8 +3849,7 @@ def CIR_LocalInitOp : CIR_Op<"local_init", [
38463849
}];
38473850
let arguments = (ins
38483851
FlatSymbolRefAttr : $globalName,
3849-
UnitAttr : $tls,
3850-
UnitAttr : $static_local
3852+
UnitProp : $tls
38513853
);
38523854

38533855
let regions = (region
@@ -3856,8 +3858,7 @@ def CIR_LocalInitOp : CIR_Op<"local_init", [
38563858
);
38573859

38583860
let assemblyFormat = [{
3859-
(`thread_local` $tls^)?
3860-
(`static_local` $static_local^)?
3861+
(`thread_local` $tls^) : (`static_local`)?
38613862
$globalName attr-dict
38623863
(`ctor` $ctorRegion^)?
38633864
(`dtor` $dtorRegion^)?
@@ -3873,6 +3874,7 @@ def CIR_LocalInitOp : CIR_Op<"local_init", [
38733874
}
38743875
}];
38753876

3877+
let hasVerifier = 1;
38763878
let hasLLVMLowering = false;
38773879
}
38783880

clang/lib/CIR/CodeGen/CIRGenCXX.cpp

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,6 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const VarDecl *vd,
155155
// directly.
156156
cir::FuncOp fnOp;
157157
if (record && (canRegisterDestructor || cgm.getCodeGenOpts().CXAAtExit)) {
158-
if (vd->getTLSKind())
159-
cgm.errorNYI(vd->getSourceRange(), "TLS destructor");
160158
assert(!record->hasTrivialDestructor());
161159
assert(!cir::MissingFeatures::openCL());
162160
CXXDestructorDecl *dtor = record->getDestructor();
@@ -343,16 +341,11 @@ void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl,
343341
void CIRGenModule::emitCXXStaticLocalVarDeclInit(const VarDecl *varDecl,
344342
cir::GlobalOp addr,
345343
bool performInit) {
346-
assert(varDecl->isStaticLocal() ||
347-
varDecl->getTLSKind() != VarDecl::TLS_None);
344+
assert(varDecl->isStaticLocal());
348345

349-
if (varDecl->getTLSKind() != VarDecl::TLS_None)
350-
errorNYI(varDecl->getSourceRange(),
351-
"TLS not implemented for static-local init");
352-
353-
auto initOp = cir::LocalInitOp::create(
354-
builder, addr->getLoc(), addr.getSymNameAttr(),
355-
varDecl->getTLSKind() != VarDecl::TLS_None, varDecl->isStaticLocal());
346+
auto initOp =
347+
cir::LocalInitOp::create(builder, addr->getLoc(), addr.getSymNameAttr(),
348+
varDecl->getTLSKind() != VarDecl::TLS_None);
356349

357350
emitCXXSpecialVarDeclInit(varDecl, addr, performInit, initOp.getCtorRegion(),
358351
initOp.getDtorRegion());

clang/lib/CIR/CodeGen/CIRGenDecl.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &d,
459459
gv.setComdat(true);
460460

461461
if (d.getTLSKind())
462-
errorNYI(d.getSourceRange(), "getOrCreateStaticVarDecl: TLS");
462+
setTLSMode(gv, d);
463463

464464
setGVProperties(gv, &d);
465465

@@ -642,7 +642,8 @@ void CIRGenFunction::emitStaticVarDecl(const VarDecl &d,
642642
cir::GlobalOp globalOp = cgm.getOrCreateStaticVarDecl(d, linkage);
643643
// TODO(cir): we should have a way to represent global ops as values without
644644
// having to emit a get global op. Sometimes these emissions are not used.
645-
mlir::Value addr = builder.createGetGlobal(globalOp);
645+
mlir::Value addr =
646+
builder.createGetGlobal(globalOp, d.getTLSKind() != VarDecl::TLS_None);
646647
auto getAddrOp = addr.getDefiningOp<cir::GetGlobalOp>();
647648
assert(getAddrOp && "expected cir::GetGlobalOp");
648649

clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,11 +1763,6 @@ void CIRGenItaniumCXXABI::registerGlobalDtor(const VarDecl *vd,
17631763
if (vd->isNoDestroy(cgm.getASTContext()))
17641764
return;
17651765

1766-
if (vd->getTLSKind()) {
1767-
cgm.errorNYI(vd->getSourceRange(), "registerGlobalDtor: TLS");
1768-
return;
1769-
}
1770-
17711766
// HLSL doesn't support atexit.
17721767
if (cgm.getLangOpts().HLSL) {
17731768
cgm.errorNYI(vd->getSourceRange(), "registerGlobalDtor: HLSL");

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,12 @@ LogicalResult cir::BreakOp::verify() {
375375
// LocalInitOp
376376
//===----------------------------------------------------------------------===//
377377

378+
LogicalResult cir::LocalInitOp::verify() {
379+
if (!getOperation()->getParentOfType<FuncOp>())
380+
return emitOpError("must be inside of a 'cir.func'");
381+
return success();
382+
}
383+
378384
LogicalResult
379385
cir::LocalInitOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
380386
cir::GlobalOp global = getReferencedGlobal(symbolTable);
@@ -385,10 +391,7 @@ cir::LocalInitOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
385391
if (getTls() && !global.getTlsModel())
386392
return emitOpError("access to global not marked thread local");
387393

388-
bool isStaticLocal = getStaticLocal();
389-
bool globalIsStaticLocal = global.getStaticLocalGuard().has_value();
390-
391-
if (isStaticLocal != globalIsStaticLocal)
394+
if (!global.getStaticLocalGuard().has_value())
392395
return emitOpError("static_local attribute mismatch");
393396

394397
return success();

clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp

Lines changed: 50 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ struct LoweringPreparePass
183183
guard.setInitialValueAttr(cir::IntAttr::get(guardTy, 0));
184184
guard.setDSOLocal(globalOp.getDsoLocal());
185185
guard.setAlignment(guardAlignment.getAsAlign().value());
186+
guard.setTlsModel(globalOp.getTlsModel());
186187

187188
// The ABI says: "It is suggested that it be emitted in the same COMDAT
188189
// group as the associated data object." In practice, this doesn't work
@@ -241,7 +242,7 @@ struct LoweringPreparePass
241242

242243
void emitGlobalGuardedDtorRegion(CIRBaseBuilderTy &builder,
243244
cir::GlobalOp global,
244-
mlir::Region &dtorRegion,
245+
mlir::Region &dtorRegion, bool tls,
245246
mlir::Block &entryBB) {
246247
// Create a variable that binds the atexit to this shared object.
247248
builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
@@ -266,6 +267,11 @@ struct LoweringPreparePass
266267
builder.getVoidFnTy({voidFnPtrTy, voidPtrTy, handlePtrTy});
267268

268269
llvm::StringLiteral nameAtExit = "__cxa_atexit";
270+
if (tls)
271+
nameAtExit = astCtx->getTargetInfo().getTriple().isOSDarwin()
272+
? llvm::StringLiteral("_tlv_atexit")
273+
: llvm::StringLiteral("__cxa_thread_atexit");
274+
269275
cir::FuncOp fnAtExit = buildRuntimeFunction(builder, nameAtExit,
270276
global.getLoc(), fnAtExitType);
271277

@@ -317,6 +323,28 @@ struct LoweringPreparePass
317323
// initialization so that recursive accesses during initialization do not
318324
// restart initialization.
319325

326+
auto emitBody = [&]() {
327+
// Emit the initializer and add a global destructor if appropriate.
328+
mlir::Block *insertBlock = builder.getInsertionBlock();
329+
if (!ctorRegion.empty()) {
330+
assert(ctorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
331+
332+
mlir::Block &block = ctorRegion.front();
333+
insertBlock->getOperations().splice(
334+
insertBlock->end(), block.getOperations(), block.begin(),
335+
std::prev(block.end()));
336+
}
337+
338+
if (!dtorRegion.empty()) {
339+
assert(dtorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
340+
341+
emitGlobalGuardedDtorRegion(builder, globalOp, dtorRegion, !threadsafe,
342+
*insertBlock);
343+
}
344+
builder.setInsertionPointToEnd(insertBlock);
345+
ctorRegion.getBlocks().clear();
346+
};
347+
320348
// Variables used when coping with thread-safe statics and exceptions.
321349
if (threadsafe) {
322350
// Call __cxa_guard_acquire.
@@ -341,26 +369,7 @@ struct LoweringPreparePass
341369
// OG: CGF.EHStack.pushCleanup<CallGuardAbort>(EHCleanup, guard);
342370
assert(!cir::MissingFeatures::guardAbortOnException());
343371

344-
// Emit the initializer and add a global destructor if appropriate.
345-
mlir::Block *insertBlock = builder.getInsertionBlock();
346-
if (!ctorRegion.empty()) {
347-
assert(ctorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
348-
349-
mlir::Block &block = ctorRegion.front();
350-
insertBlock->getOperations().splice(
351-
insertBlock->end(), block.getOperations(), block.begin(),
352-
std::prev(block.end()));
353-
}
354-
355-
if (!dtorRegion.empty()) {
356-
assert(dtorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
357-
358-
emitGlobalGuardedDtorRegion(builder, globalOp, dtorRegion,
359-
*insertBlock);
360-
}
361-
362-
builder.setInsertionPointToEnd(insertBlock);
363-
ctorRegion.getBlocks().clear();
372+
emitBody();
364373

365374
// Pop the guard-abort cleanup if we pushed one.
366375
// OG: CGF.PopCleanupBlock();
@@ -380,11 +389,13 @@ struct LoweringPreparePass
380389
globalOp->emitError("NYI: non-threadsafe init for non-local variables");
381390
return;
382391
} else {
392+
emitBody();
383393
// For local variables, store 1 into the first byte of the guard variable
384394
// after the object initialization completes so that initialization is
385395
// retried if initialization is interrupted by an exception.
386-
globalOp->emitError("NYI: non-threadsafe init for local variables");
387-
return;
396+
builder.createStore(
397+
loc, builder.getConstantInt(loc, guardPtrTy.getPointee(), 1),
398+
guardPtr);
388399
}
389400

390401
builder.createYield(loc); // Outermost IfOp
@@ -1063,7 +1074,8 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
10631074
assert(!cir::MissingFeatures::astVarDeclInterface());
10641075
assert(!cir::MissingFeatures::opGlobalThreadLocal());
10651076

1066-
emitGlobalGuardedDtorRegion(builder, op, dtorRegion, *entryBB);
1077+
emitGlobalGuardedDtorRegion(builder, op, dtorRegion,
1078+
op.getTlsModel().has_value(), *entryBB);
10671079
}
10681080

10691081
// Replace cir.yield with cir.return
@@ -1159,31 +1171,27 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
11591171
(varDecl.isLocalVarDecl() || nonTemplateInline) &&
11601172
!varDecl.getTLSKind();
11611173

1162-
// TLS variables need special handling - the guard must also be thread-local.
1163-
if (varDecl.getTLSKind()) {
1164-
globalOp->emitError("NYI: guarded initialization for thread-local statics");
1165-
return;
1166-
}
1167-
11681174
// If we have a global variable with internal linkage and thread-safe statics
11691175
// are disabled, we can just let the guard variable be of type i8.
11701176
bool useInt8GuardVariable = !threadsafe && globalOp.hasInternalLinkage();
1171-
if (useInt8GuardVariable) {
1172-
globalOp->emitError("NYI: int8 guard variables for non-threadsafe statics");
1173-
return;
1174-
}
1175-
1177+
cir::CIRDataLayout dataLayout(mlirModule);
1178+
cir::IntType guardTy;
1179+
clang::CharUnits guardAlignment;
11761180
// Guard variables are 64 bits in the generic ABI and size width on ARM
11771181
// (i.e. 32-bit on AArch32, 64-bit on AArch64).
1178-
if (useARMGuardVarABI()) {
1182+
if (useInt8GuardVariable) {
1183+
guardTy = cir::IntType::get(&getContext(), 8, /*isSigned=*/true);
1184+
guardAlignment = clang::CharUnits::One();
1185+
} else if (useARMGuardVarABI()) {
11791186
globalOp->emitError("NYI: ARM-style guard variables for static locals");
11801187
return;
1188+
} else {
1189+
guardTy = cir::IntType::get(&getContext(), 64, /*isSigned=*/true);
1190+
guardAlignment =
1191+
clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy));
11811192
}
1182-
cir::IntType guardTy =
1183-
cir::IntType::get(&getContext(), 64, /*isSigned=*/true);
1184-
cir::CIRDataLayout dataLayout(mlirModule);
1185-
clang::CharUnits guardAlignment =
1186-
clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy));
1193+
assert(guardTy && guardAlignment.getQuantity() != 0);
1194+
11871195
auto guardPtrTy = cir::PointerType::get(guardTy);
11881196

11891197
// Create the guard variable if we don't already have it.
@@ -1195,7 +1203,7 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
11951203
return;
11961204
}
11971205

1198-
mlir::Value guardPtr = builder.createGetGlobal(guard, /*threadLocal*/ false);
1206+
mlir::Value guardPtr = builder.createGetGlobal(guard, localInitOp.getTls());
11991207

12001208
// Test whether the variable has completed initialization.
12011209
//
@@ -1292,10 +1300,6 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
12921300

12931301
void LoweringPreparePass::lowerLocalInitOp(
12941302
cir::LocalInitOp initOp, mlir::SymbolTableCollection &symbolTables) {
1295-
if (!initOp.getStaticLocal()) {
1296-
initOp->emitError("NYI: Non-static-local in lower-init-local op");
1297-
return;
1298-
}
12991303

13001304
// If we don't actually need to initialize anything anymore, we're done here.
13011305
if (initOp.getCtorRegion().empty() && initOp.getDtorRegion().empty()) {

0 commit comments

Comments
 (0)