https://github.com/bcardosolopes updated https://github.com/llvm/llvm-project/pull/179828
>From 1324b35c85d9d4cc5425fa6eda7183e0cdb2a30b Mon Sep 17 00:00:00 2001 From: Bruno Cardoso Lopes <[email protected]> Date: Fri, 30 Jan 2026 22:49:28 -0800 Subject: [PATCH] [CIR][LoweringPrepare] Emit guard variables for static local initialization This implements the lowering of static local variables with the Itanium C++ ABI guard variable pattern in LoweringPrepare. When a GlobalOp has the static_local attribute and a ctor region, this pass: 1. Creates a guard variable global (mangled name from AST) 2. Inserts the guard check pattern at each GetGlobalOp use site: - Load guard byte with acquire ordering - If zero, call __cxa_guard_acquire - If acquire returns non-zero, inline the ctor region code - Call __cxa_guard_release 3. Clears the static_local attribute and ctor region from the GlobalOp --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 20 + clang/include/clang/CIR/Dialect/IR/CIROps.td | 7 +- .../clang/CIR/Interfaces/ASTAttrInterfaces.h | 2 - .../clang/CIR/Interfaces/ASTAttrInterfaces.td | 12 - clang/include/clang/CIR/MissingFeatures.h | 1 + clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp | 23 +- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 7 +- clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 10 +- .../Dialect/Transforms/LoweringPrepare.cpp | 407 +++++++++++++++++- clang/test/CIR/CodeGen/static-local.cpp | 37 +- clang/test/CIR/IR/invalid-static-local.cir | 6 +- clang/test/CIR/IR/static-local.cir | 6 +- 12 files changed, 501 insertions(+), 37 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 47a0b3394c48e..15c2c6e034af9 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -1288,6 +1288,26 @@ def CIR_SideEffect : CIR_I32EnumAttr< }]; } +//===----------------------------------------------------------------------===// +// StaticLocalGuardAttr +//===----------------------------------------------------------------------===// + +def CIR_StaticLocalGuardAttr : CIR_Attr<"StaticLocalGuard", + "static_local_guard"> { + let summary = "Guard variable name for static local variables"; + let description = [{ + Contains the mangled guard variable name for static local variable + initialization. + + Example: + ```mlir + cir.global internal static_local_guard<"_ZGVZ3foovE1x"> @_ZZ3foovE1x = ... + ``` + }]; + let parameters = (ins "mlir::StringAttr":$name); + let assemblyFormat = "`<` $name `>`"; +} + //===----------------------------------------------------------------------===// // AST Wrappers //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 25b09c9eb4ed4..f2c1578235107 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -2386,9 +2386,10 @@ def CIR_GlobalOp : CIR_Op<"global", [ Symbol visibility in `sym_visibility` is defined in terms of MLIR's visibility and verified to be in accordance to `linkage`. - The `static_local` attribute indicates that this global represents a + The `static_local_guard` attribute indicates that this global represents a function-local static variable that requires guarded initialization (e.g., C++ static local variables with non-constant initializers). + It contains the mangled name of the guard variable. }]; // Note that both sym_name and sym_visibility are tied to Symbol trait. @@ -2407,7 +2408,7 @@ def CIR_GlobalOp : CIR_Op<"global", [ UnitAttr:$comdat, UnitAttr:$constant, UnitAttr:$dso_local, - UnitAttr:$static_local, + OptionalAttr<CIR_StaticLocalGuardAttr>:$static_local_guard, OptionalAttr<I64Attr>:$alignment, OptionalAttr<ASTVarDeclInterface>:$ast); @@ -2422,7 +2423,7 @@ def CIR_GlobalOp : CIR_Op<"global", [ (`comdat` $comdat^)? ($tls_model^)? (`dso_local` $dso_local^)? - (`static_local` $static_local^)? + (`static_local_guard` `` $static_local_guard^)? $sym_name custom<GlobalOpTypeAndInitialValue>($sym_type, $initial_value, $ctorRegion, $dtorRegion) diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h index 12a6bfa8691cc..4bfa8df7ad55a 100644 --- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h +++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h @@ -10,11 +10,9 @@ #define CLANG_CIR_INTERFACES_ASTATTRINTERFACES_H #include "mlir/IR/Attributes.h" -#include "llvm/Support/raw_ostream.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclTemplate.h" -#include "clang/AST/Mangle.h" /// Include the generated interface declarations. #include "clang/CIR/Interfaces/ASTAttrInterfaces.h.inc" diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td index c0d4da81cb979..53909063abbab 100644 --- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td +++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td @@ -14,13 +14,6 @@ include "mlir/IR/OpBase.td" let cppNamespace = "::cir" in { def ASTVarDeclInterface : AttrInterface<"ASTVarDeclInterface"> { let methods = [ - InterfaceMethod<"", "void", "mangleStaticGuardVariable", - (ins "llvm::raw_ostream&":$out), [{}], - /*defaultImplementation=*/[{ - std::unique_ptr<clang::MangleContext> mangleCtx( - $_attr.getAst()->getASTContext().createMangleContext()); - mangleCtx->mangleStaticGuardVariable($_attr.getAst(), out); - }]>, InterfaceMethod<"", "bool", "isLocalVarDecl", (ins), [{}], /*defaultImplementation=*/[{ return $_attr.getAst()->isLocalVarDecl(); @@ -38,11 +31,6 @@ let cppNamespace = "::cir" in { "getTemplateSpecializationKind", (ins), [{}], /*defaultImplementation=*/[{ return $_attr.getAst()->getTemplateSpecializationKind(); - }]>, - InterfaceMethod<"Get the underlying VarDecl pointer.", - "const clang::VarDecl *", "getVarDecl", (ins), [{}], - /*defaultImplementation=*/[{ - return $_attr.getAst(); }]> ]; } diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 5cb0991326a3c..132616df531c5 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -286,6 +286,7 @@ struct MissingFeatures { static bool getRuntimeFunctionDecl() { return false; } static bool globalViewIndices() { return false; } static bool globalViewIntLowering() { return false; } + static bool guardAbortOnException() { return false; } static bool handleBuiltinICEArguments() { return false; } static bool hip() { return false; } static bool incrementProfileCounter() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp index 9c2a5fe0cda07..255e5ead77072 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp @@ -10,10 +10,13 @@ // //===----------------------------------------------------------------------===// +#include "CIRGenCXXABI.h" #include "CIRGenFunction.h" #include "CIRGenModule.h" #include "clang/AST/Attr.h" +#include "clang/AST/Mangle.h" #include "clang/Basic/LangOptions.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" using namespace clang; using namespace clang::CIRGen; @@ -27,12 +30,24 @@ void CIRGenFunction::emitCXXGuardedInit(const VarDecl &varDecl, if (cgm.getCodeGenOpts().ForbidGuardVariables) cgm.error(varDecl.getLocation(), "guard variables are forbidden"); + // Compute the mangled guard variable name and set the static_local attribute + // BEFORE emitting initialization. This ensures that GetGlobalOps created + // during initialization (e.g., in the ctor region) will see the attribute + // and be marked with static_local accordingly. + llvm::SmallString<256> guardName; + { + llvm::raw_svector_ostream out(guardName); + cgm.getCXXABI().getMangleContext().mangleStaticGuardVariable(&varDecl, out); + } + + // Mark the global as static local with the guard name. The emission of the + // guard/acquire is done during LoweringPrepare. + auto guardAttr = mlir::StringAttr::get(&cgm.getMLIRContext(), guardName); + globalOp.setStaticLocalGuardAttr( + cir::StaticLocalGuardAttr::get(&cgm.getMLIRContext(), guardAttr)); + // 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, diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 133eb0f3dfe7e..69b8c95331360 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -829,9 +829,10 @@ mlir::Value CIRGenModule::getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty, bool tlsAccess = d->getTLSKind() != VarDecl::TLS_None; 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.getStaticLocal()); + return cir::GetGlobalOp::create( + builder, getLoc(d->getSourceRange()), ptrTy, g.getSymNameAttr(), + tlsAccess, + /*static_local=*/g.getStaticLocalGuard().has_value()); } 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 c38a77a7598b8..3f6c8489e6896 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1854,9 +1854,13 @@ cir::GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) { // be marked with tls bits. 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. Skip when inside a GlobalOp region (e.g., ctor/dtor regions). - if (getStaticLocal() != g.getStaticLocal() && + + // Verify that the static_local attribute on GetGlobalOp matches the + // static_local_guard attribute on GlobalOp. GetGlobalOp uses a UnitAttr, + // GlobalOp uses StaticLocalGuardAttr. Both should be present, or neither. + bool getGlobalIsStaticLocal = getStaticLocal(); + bool globalIsStaticLocal = g.getStaticLocalGuard().has_value(); + if (getGlobalIsStaticLocal != globalIsStaticLocal && !getOperation()->getParentOfType<cir::GlobalOp>()) return emitOpError("static_local attribute mismatch"); } else if (auto f = dyn_cast<FuncOp>(op)) { diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 5c6d40027158f..688dfdc29e850 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -9,13 +9,18 @@ #include "PassDetail.h" #include "mlir/IR/Attributes.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Mangle.h" #include "clang/Basic/Module.h" +#include "clang/Basic/Specifiers.h" +#include "clang/Basic/TargetCXXABI.h" #include "clang/Basic/TargetInfo.h" #include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h" #include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDataLayout.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/Dialect/IR/CIROpsEnums.h" #include "clang/CIR/Dialect/Passes.h" +#include "clang/CIR/Interfaces/ASTAttrInterfaces.h" #include "clang/CIR/MissingFeatures.h" #include "llvm/Support/Path.h" @@ -99,6 +104,74 @@ struct LoweringPreparePass cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind visibility = cir::VisibilityKind::Default); + /// Handle static local variable initialization with guard variables. + void handleStaticLocal(cir::GlobalOp globalOp, cir::GetGlobalOp getGlobalOp); + + /// Get or create __cxa_guard_acquire function. + cir::FuncOp getGuardAcquireFn(cir::PointerType guardPtrTy); + + /// Get or create __cxa_guard_release function. + cir::FuncOp getGuardReleaseFn(cir::PointerType guardPtrTy); + + /// Create a guard global variable for a static local. + cir::GlobalOp createGuardGlobalOp(CIRBaseBuilderTy &builder, + mlir::Location loc, llvm::StringRef name, + cir::IntType guardTy, + cir::GlobalLinkageKind linkage); + + /// Get the guard variable for a static local declaration. + cir::GlobalOp getStaticLocalDeclGuardAddress(llvm::StringRef globalSymName) { + auto it = staticLocalDeclGuardMap.find(globalSymName); + if (it != staticLocalDeclGuardMap.end()) + return it->second; + return nullptr; + } + + /// Set the guard variable for a static local declaration. + void setStaticLocalDeclGuardAddress(llvm::StringRef globalSymName, + cir::GlobalOp guard) { + staticLocalDeclGuardMap[globalSymName] = guard; + } + + /// Get or create the guard variable for a static local declaration. + cir::GlobalOp getOrCreateStaticLocalDeclGuardAddress( + CIRBaseBuilderTy &builder, cir::GlobalOp globalOp, + cir::ASTVarDeclInterface varDecl, cir::IntType guardTy, + clang::CharUnits guardAlignment) { + llvm::StringRef globalSymName = globalOp.getSymName(); + cir::GlobalOp guard = getStaticLocalDeclGuardAddress(globalSymName); + if (!guard) { + // Get the guard name from the static_local attribute. + llvm::StringRef guardName = + globalOp.getStaticLocalGuard()->getName().getValue(); + + // Create the guard variable with a zero-initializer. + guard = createGuardGlobalOp(builder, globalOp->getLoc(), guardName, + guardTy, globalOp.getLinkage()); + guard.setInitialValueAttr(cir::IntAttr::get(guardTy, 0)); + guard.setDSOLocal(globalOp.getDsoLocal()); + guard.setAlignment(guardAlignment.getAsAlign().value()); + + // The ABI says: "It is suggested that it be emitted in the same COMDAT + // group as the associated data object." In practice, this doesn't work + // for non-ELF and non-Wasm object formats, so only do it for ELF and + // Wasm. + bool hasComdat = globalOp.getComdat(); + const llvm::Triple &triple = astCtx->getTargetInfo().getTriple(); + if (!varDecl.isLocalVarDecl() && hasComdat && + (triple.isOSBinFormatELF() || triple.isOSBinFormatWasm())) { + globalOp->emitError("NYI: guard COMDAT for non-local variables"); + return {}; + } else if (hasComdat && globalOp.isWeakForLinker()) { + globalOp->emitError("NYI: guard COMDAT for weak linkage"); + return {}; + } + + setStaticLocalDeclGuardAddress(globalSymName, guard); + } + return guard; + } + /// /// AST related /// ----------- @@ -112,11 +185,119 @@ struct LoweringPreparePass llvm::StringMap<uint32_t> dynamicInitializerNames; llvm::SmallVector<cir::FuncOp> dynamicInitializers; + /// Tracks guard variables for static locals (keyed by global symbol name). + llvm::StringMap<cir::GlobalOp> staticLocalDeclGuardMap; + /// List of ctors and their priorities to be called before main() llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList; /// List of dtors and their priorities to be called when unloading module. llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalDtorList; + /// Returns true if the target uses ARM-style guard variables for static + /// local initialization (32-bit guard, check bit 0 only). + bool useARMGuardVarABI() const { + switch (astCtx->getCXXABIKind()) { + case clang::TargetCXXABI::GenericARM: + case clang::TargetCXXABI::iOS: + case clang::TargetCXXABI::WatchOS: + case clang::TargetCXXABI::GenericAArch64: + case clang::TargetCXXABI::WebAssembly: + return true; + default: + return false; + } + } + + /// Emit the guarded initialization for a static local variable. + /// This handles the if/else structure after the guard byte check, + /// following OG's ItaniumCXXABI::EmitGuardedInit skeleton. + void emitCXXGuardedInitIf(CIRBaseBuilderTy &builder, cir::GlobalOp globalOp, + cir::ASTVarDeclInterface varDecl, + mlir::Value guardPtr, cir::PointerType guardPtrTy, + bool threadsafe) { + auto loc = globalOp->getLoc(); + + // The semantics of dynamic initialization of variables with static or + // thread storage duration depends on whether they are declared at + // block-scope. The initialization of such variables at block-scope can be + // aborted with an exception and later retried (per C++20 [stmt.dcl]p4), + // and recursive entry to their initialization has undefined behavior (also + // per C++20 [stmt.dcl]p4). For such variables declared at non-block scope, + // exceptions lead to termination (per C++20 [except.terminate]p1), and + // recursive references to the variables are governed only by the lifetime + // rules (per C++20 [class.cdtor]p2), which means such references are + // perfectly fine as long as they avoid touching memory. As a result, + // block-scope variables must not be marked as initialized until after + // initialization completes (unless the mark is reverted following an + // exception), but non-block-scope variables must be marked prior to + // initialization so that recursive accesses during initialization do not + // restart initialization. + + // Variables used when coping with thread-safe statics and exceptions. + if (threadsafe) { + // Call __cxa_guard_acquire. + cir::CallOp acquireCall = builder.createCallOp( + loc, getGuardAcquireFn(guardPtrTy), mlir::ValueRange{guardPtr}); + mlir::Value acquireResult = acquireCall.getResult(); + + auto acquireZero = builder.getConstantInt( + loc, mlir::cast<cir::IntType>(acquireResult.getType()), 0); + auto shouldInit = builder.createCompare(loc, cir::CmpOpKind::ne, + acquireResult, acquireZero); + + // Create the IfOp for the shouldInit check. + // Pass an empty callback to avoid auto-creating a yield terminator. + auto ifOp = + cir::IfOp::create(builder, loc, shouldInit, /*withElseRegion=*/false, + [](mlir::OpBuilder &, mlir::Location) {}); + mlir::OpBuilder::InsertionGuard insertGuard(builder); + builder.setInsertionPointToStart(&ifOp.getThenRegion().front()); + + // Call __cxa_guard_abort along the exceptional edge. + // OG: CGF.EHStack.pushCleanup<CallGuardAbort>(EHCleanup, guard); + assert(!cir::MissingFeatures::guardAbortOnException()); + + // Emit the initializer and add a global destructor if appropriate. + auto &ctorRegion = globalOp.getCtorRegion(); + assert(!ctorRegion.empty() && "This should never be empty here."); + if (!ctorRegion.hasOneBlock()) + llvm_unreachable("Multiple blocks NYI"); + mlir::Block &block = ctorRegion.front(); + mlir::Block *insertBlock = builder.getInsertionBlock(); + insertBlock->getOperations().splice(insertBlock->end(), + block.getOperations(), block.begin(), + std::prev(block.end())); + builder.setInsertionPointToEnd(insertBlock); + ctorRegion.getBlocks().clear(); + + // Pop the guard-abort cleanup if we pushed one. + // OG: CGF.PopCleanupBlock(); + assert(!cir::MissingFeatures::guardAbortOnException()); + + // Call __cxa_guard_release. This cannot throw. + builder.createCallOp(loc, getGuardReleaseFn(guardPtrTy), + mlir::ValueRange{guardPtr}); + + builder.createYield(loc); + } else if (!varDecl.isLocalVarDecl()) { + // For non-local variables, store 1 into the first byte of the guard + // variable before the object initialization begins so that references + // to the variable during initialization don't restart initialization. + // OG: Builder.CreateStore(llvm::ConstantInt::get(CGM.Int8Ty, 1), ...); + // Then: CGF.EmitCXXGlobalVarDeclInit(D, var, shouldPerformInit); + globalOp->emitError("NYI: non-threadsafe init for non-local variables"); + return; + } else { + // For local variables, store 1 into the first byte of the guard variable + // after the object initialization completes so that initialization is + // retried if initialization is interrupted by an exception. + globalOp->emitError("NYI: non-threadsafe init for local variables"); + return; + } + + builder.createYield(loc); // Outermost IfOp + } + void setASTContext(clang::ASTContext *c) { astCtx = c; } }; @@ -871,9 +1052,213 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { return f; } +cir::FuncOp +LoweringPreparePass::getGuardAcquireFn(cir::PointerType guardPtrTy) { + // int __cxa_guard_acquire(__guard *guard_object); + CIRBaseBuilderTy builder(getContext()); + mlir::OpBuilder::InsertionGuard ipGuard{builder}; + builder.setInsertionPointToStart(mlirModule.getBody()); + mlir::Location loc = mlirModule.getLoc(); + cir::IntType intTy = cir::IntType::get(&getContext(), 32, /*isSigned=*/true); + auto fnType = cir::FuncType::get({guardPtrTy}, intTy); + return buildRuntimeFunction(builder, "__cxa_guard_acquire", loc, fnType); +} + +cir::FuncOp +LoweringPreparePass::getGuardReleaseFn(cir::PointerType guardPtrTy) { + // void __cxa_guard_release(__guard *guard_object); + CIRBaseBuilderTy builder(getContext()); + mlir::OpBuilder::InsertionGuard ipGuard{builder}; + builder.setInsertionPointToStart(mlirModule.getBody()); + mlir::Location loc = mlirModule.getLoc(); + cir::VoidType voidTy = cir::VoidType::get(&getContext()); + auto fnType = cir::FuncType::get({guardPtrTy}, voidTy); + return buildRuntimeFunction(builder, "__cxa_guard_release", loc, fnType); +} + +cir::GlobalOp LoweringPreparePass::createGuardGlobalOp( + CIRBaseBuilderTy &builder, mlir::Location loc, llvm::StringRef name, + cir::IntType guardTy, cir::GlobalLinkageKind linkage) { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToStart(mlirModule.getBody()); + cir::GlobalOp g = cir::GlobalOp::create(builder, loc, name, guardTy); + g.setLinkageAttr( + cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage)); + mlir::SymbolTable::setSymbolVisibility( + g, mlir::SymbolTable::Visibility::Private); + return g; +} + +void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp, + cir::GetGlobalOp getGlobalOp) { + CIRBaseBuilderTy builder(getContext()); + + std::optional<cir::ASTVarDeclInterface> astOption = globalOp.getAst(); + assert(astOption.has_value()); + cir::ASTVarDeclInterface varDecl = astOption.value(); + + builder.setInsertionPointAfter(getGlobalOp); + mlir::Block *getGlobalOpBlock = builder.getInsertionBlock(); + + // Remove the terminator temporarily - we'll add it back at the end. + mlir::Operation *ret = getGlobalOpBlock->getTerminator(); + ret->remove(); + builder.setInsertionPointAfter(getGlobalOp); + + // Inline variables that weren't instantiated from variable templates have + // partially-ordered initialization within their translation unit. + bool nonTemplateInline = + varDecl.isInline() && + !clang::isTemplateInstantiation(varDecl.getTemplateSpecializationKind()); + + // Inline namespace-scope variables require guarded initialization in a + // __cxx_global_var_init function. This is not yet implemented. + if (nonTemplateInline) { + globalOp->emitError( + "NYI: guarded initialization for inline namespace-scope variables"); + return; + } + + // We only need to use thread-safe statics for local non-TLS variables and + // inline variables; other global initialization is always single-threaded + // or (through lazy dynamic loading in multiple threads) unsequenced. + bool threadsafe = astCtx->getLangOpts().ThreadsafeStatics && + (varDecl.isLocalVarDecl() || nonTemplateInline) && + !varDecl.getTLSKind(); + + // TLS variables need special handling - the guard must also be thread-local. + if (varDecl.getTLSKind()) { + globalOp->emitError("NYI: guarded initialization for thread-local statics"); + return; + } + + // If we have a global variable with internal linkage and thread-safe statics + // are disabled, we can just let the guard variable be of type i8. + bool useInt8GuardVariable = !threadsafe && globalOp.hasInternalLinkage(); + if (useInt8GuardVariable) { + globalOp->emitError("NYI: int8 guard variables for non-threadsafe statics"); + return; + } + + // Guard variables are 64 bits in the generic ABI and size width on ARM + // (i.e. 32-bit on AArch32, 64-bit on AArch64). + if (useARMGuardVarABI()) { + globalOp->emitError("NYI: ARM-style guard variables for static locals"); + return; + } + cir::IntType guardTy = + cir::IntType::get(&getContext(), 64, /*isSigned=*/true); + cir::CIRDataLayout dataLayout(mlirModule); + clang::CharUnits guardAlignment = + clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy)); + auto guardPtrTy = cir::PointerType::get(guardTy); + + // Create the guard variable if we don't already have it. + cir::GlobalOp guard = getOrCreateStaticLocalDeclGuardAddress( + builder, globalOp, varDecl, guardTy, guardAlignment); + if (!guard) { + // Error was already emitted, just restore the terminator and return. + getGlobalOpBlock->push_back(ret); + return; + } + + mlir::Value guardPtr = builder.createGetGlobal(guard, /*threadLocal*/ false); + + // Test whether the variable has completed initialization. + // + // Itanium C++ ABI 3.3.2: + // The following is pseudo-code showing how these functions can be used: + // if (obj_guard.first_byte == 0) { + // if ( __cxa_guard_acquire (&obj_guard) ) { + // try { + // ... initialize the object ...; + // } catch (...) { + // __cxa_guard_abort (&obj_guard); + // throw; + // } + // ... queue object destructor with __cxa_atexit() ...; + // __cxa_guard_release (&obj_guard); + // } + // } + // + // If threadsafe statics are enabled, but we don't have inline atomics, just + // call __cxa_guard_acquire unconditionally. The "inline" check isn't + // actually inline, and the user might not expect calls to __atomic libcalls. + unsigned maxInlineWidthInBits = + astCtx->getTargetInfo().getMaxAtomicInlineWidth(); + + if (!threadsafe || maxInlineWidthInBits) { + // Load the first byte of the guard variable. + auto bytePtrTy = cir::PointerType::get(builder.getSIntNTy(8)); + mlir::Value bytePtr = builder.createBitcast(guardPtr, bytePtrTy); + mlir::Value guardLoad = builder.createAlignedLoad( + getGlobalOp.getLoc(), bytePtr, guardAlignment.getAsAlign().value()); + + // Itanium ABI: + // An implementation supporting thread-safety on multiprocessor + // systems must also guarantee that references to the initialized + // object do not occur before the load of the initialization flag. + // + // In LLVM, we do this by marking the load Acquire. + if (threadsafe) { + auto loadOp = mlir::cast<cir::LoadOp>(guardLoad.getDefiningOp()); + loadOp.setMemOrder(cir::MemOrder::Acquire); + loadOp.setSyncScope(cir::SyncScopeKind::System); + } + + // For ARM, we should only check the first bit, rather than the entire byte: + // + // ARM C++ ABI 3.2.3.1: + // To support the potential use of initialization guard variables + // as semaphores that are the target of ARM SWP and LDREX/STREX + // synchronizing instructions we define a static initialization + // guard variable to be a 4-byte aligned, 4-byte word with the + // following inline access protocol. + // #define INITIALIZED 1 + // if ((obj_guard & INITIALIZED) != INITIALIZED) { + // if (__cxa_guard_acquire(&obj_guard)) + // ... + // } + // + // and similarly for ARM64: + // + // ARM64 C++ ABI 3.2.2: + // This ABI instead only specifies the value bit 0 of the static guard + // variable; all other bits are platform defined. Bit 0 shall be 0 when + // the variable is not initialized and 1 when it is. + if (useARMGuardVarABI()) { + globalOp->emitError( + "NYI: ARM-style guard variable check (bit 0 only) for static locals"); + return; + } + + // Check if the first byte of the guard variable is zero. + auto zero = builder.getConstantInt( + getGlobalOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()), 0); + auto needsInit = builder.createCompare(getGlobalOp.getLoc(), + cir::CmpOpKind::eq, guardLoad, zero); + + // Build the guarded initialization inside an if block. + cir::IfOp::create(builder, globalOp.getLoc(), needsInit, + /*withElseRegion=*/false, + [&](mlir::OpBuilder &, mlir::Location) { + emitCXXGuardedInitIf(builder, globalOp, varDecl, + guardPtr, guardPtrTy, threadsafe); + }); + } else { + // Threadsafe statics without inline atomics - call __cxa_guard_acquire + // unconditionally without the initial guard byte check. + globalOp->emitError("NYI: guarded init without inline atomics support"); + return; + } + + // Insert the removed terminator back. + builder.getInsertionBlock()->push_back(ret); +} + void LoweringPreparePass::lowerGlobalOp(GlobalOp op) { // Static locals are handled separately via guard variables. - if (op.getStaticLocal()) + if (op.getStaticLocalGuard()) return; mlir::Region &ctorRegion = op.getCtorRegion(); @@ -1089,6 +1474,23 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) { lowerComplexMulOp(complexMul); } else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op)) { lowerGlobalOp(glob); + } else if (auto getGlobal = mlir::dyn_cast<cir::GetGlobalOp>(op)) { + // Handle static local variables with guard variables. + // Only process GetGlobalOps inside function bodies, not in GlobalOp + // regions. + if (getGlobal.getStaticLocal() && + getGlobal->getParentOfType<cir::FuncOp>()) { + auto globalOp = mlir::dyn_cast_or_null<cir::GlobalOp>( + mlir::SymbolTable::lookupNearestSymbolFrom(getGlobal, + getGlobal.getNameAttr())); + // Only process if the GlobalOp has static_local and the ctor region is + // not empty. After handleStaticLocal processes a static local, the ctor + // region is cleared. GetGlobalOps that were spliced from the ctor region + // into the function will be skipped on subsequent iterations. + if (globalOp && globalOp.getStaticLocalGuard() && + !globalOp.getCtorRegion().empty()) + handleStaticLocal(globalOp, getGlobal); + } } else if (auto unary = mlir::dyn_cast<cir::UnaryOp>(op)) { lowerUnaryOp(unary); } else if (auto callOp = dyn_cast<cir::CallOp>(op)) { @@ -1111,7 +1513,8 @@ void LoweringPreparePass::runOnOperation() { op->walk([&](mlir::Operation *op) { if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp, cir::ComplexMulOp, cir::ComplexDivOp, cir::DynamicCastOp, - cir::FuncOp, cir::CallOp, cir::GlobalOp, cir::UnaryOp>(op)) + cir::FuncOp, cir::CallOp, cir::GetGlobalOp, cir::GlobalOp, + cir::UnaryOp>(op)) opsToTransform.push_back(op); }); diff --git a/clang/test/CIR/CodeGen/static-local.cpp b/clang/test/CIR/CodeGen/static-local.cpp index 0297028e96438..4bfba7efe4806 100644 --- a/clang/test/CIR/CodeGen/static-local.cpp +++ b/clang/test/CIR/CodeGen/static-local.cpp @@ -1,4 +1,6 @@ // 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 -fclangir -emit-cir %s -o - | FileCheck %s --check-prefix=CIR +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - | FileCheck %s --check-prefix=LLVM // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefix=OGCG class A { @@ -6,19 +8,49 @@ class A { A(); }; +void use(A*); void f() { static A a; + use(&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.global "private" internal dso_local static_local_guard<"_ZGVZ1fvE1a"> @_ZZ1fvE1a = ctor : !rec_A { +// CIR-BEFORE-LPP: %[[ADDR:.*]] = cir.get_global static_local @_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.call @_Z3useP1A(%[[VAR]]) // CIR-BEFORE-LPP: cir.return +// CIR: cir.global "private" internal dso_local @_ZGVZ1fvE1a = #cir.int<0> : !s64i +// CIR: cir.func{{.*}}@_Z1fv() +// CIR: %[[ADDR:.*]] = cir.get_global static_local @_ZZ1fvE1a : !cir.ptr<!rec_A> +// CIR: %[[GUARD:.*]] = cir.get_global @_ZGVZ1fvE1a : !cir.ptr<!s64i> +// CIR: %[[GUARD_BYTE_PTR:.*]] = cir.cast bitcast %[[GUARD]] : !cir.ptr<!s64i> -> !cir.ptr<!s8i> +// CIR: %[[GUARD_LOAD:.*]] = cir.load{{.*}}%[[GUARD_BYTE_PTR]] +// CIR: %[[ZERO:.*]] = cir.const #cir.int<0> +// CIR: %[[IS_UNINIT:.*]] = cir.cmp(eq, %[[GUARD_LOAD]], %[[ZERO]]) +// CIR: cir.if %[[IS_UNINIT]] +// CIR: cir.call @__cxa_guard_acquire +// CIR: cir.if +// CIR: cir.call @_ZN1AC1Ev +// CIR: cir.call @__cxa_guard_release +// CIR: cir.call @_Z3useP1A(%[[ADDR]]) +// CIR: cir.return + +// LLVM: @_ZGVZ1fvE1a = internal global i64 0 +// LLVM: define{{.*}}void @_Z1fv() +// LLVM: %[[GUARD:.*]] = load atomic i8, ptr @_ZGVZ1fvE1a acquire +// LLVM: %[[IS_UNINIT:.*]] = icmp eq i8 %[[GUARD]], 0 +// LLVM: br i1 %[[IS_UNINIT]], label %[[IF_THEN:.*]], label %[[IF_END:.*]] +// LLVM: call i32 @__cxa_guard_acquire +// LLVM: call void @_ZN1AC1Ev +// LLVM: call void @__cxa_guard_release +// LLVM: call void @_Z3useP1A(ptr @_ZZ1fvE1a) +// LLVM: ret void + // OGCG: @_ZGVZ1fvE1a = internal global i64 0 // OGCG: define{{.*}}void @_Z1fv() // OGCG: %[[GUARD:.*]] = load atomic i8, ptr @_ZGVZ1fvE1a acquire @@ -27,4 +59,5 @@ void f() { // OGCG: call i32 @__cxa_guard_acquire // OGCG: call void @_ZN1AC1Ev // OGCG: call void @__cxa_guard_release +// OGCG: call void @_Z3useP1A(ptr {{.*}}@_ZZ1fvE1a) // OGCG: ret void diff --git a/clang/test/CIR/IR/invalid-static-local.cir b/clang/test/CIR/IR/invalid-static-local.cir index 13ef4f63a8288..7a6b245d97eb7 100644 --- a/clang/test/CIR/IR/invalid-static-local.cir +++ b/clang/test/CIR/IR/invalid-static-local.cir @@ -4,7 +4,7 @@ module { -// Global is not marked static_local, but get_global is +// Global is not marked static_local_guard, but get_global is static_local cir.global "private" internal @_ZZ1fvE1x : !s32i cir.func @test_static_local_mismatch() { @@ -21,8 +21,8 @@ cir.func @test_static_local_mismatch() { module { -// Global is marked static_local, but get_global is not -cir.global "private" internal static_local @_ZZ1fvE1y : !s32i +// Global is marked static_local_guard, but get_global is not static_local +cir.global "private" internal static_local_guard<"_ZGVZ1fvE1y"> @_ZZ1fvE1y : !s32i cir.func @test_static_local_mismatch_reverse() { // expected-error @below {{static_local attribute mismatch}} diff --git a/clang/test/CIR/IR/static-local.cir b/clang/test/CIR/IR/static-local.cir index d4c5a743358de..b1f4a6a937bb2 100644 --- a/clang/test/CIR/IR/static-local.cir +++ b/clang/test/CIR/IR/static-local.cir @@ -4,9 +4,9 @@ module { -// Test static_local attribute on global and get_global -cir.global "private" internal static_local @_ZZ1fvE1x : !s32i -// CHECK: cir.global "private" internal static_local @_ZZ1fvE1x : !s32i +// Test static_local_guard attribute on global and static_local on get_global +cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i +// CHECK: cir.global "private" internal static_local_guard<"_ZGVZ1fvE1x"> @_ZZ1fvE1x : !s32i cir.func @test_static_local() { %0 = cir.get_global static_local @_ZZ1fvE1x : !cir.ptr<!s32i> _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
