[CIR][LoweringPrepare] Emit guard variables for static local initialization (#179828)
This implements the lowering of static local variables with the Itanium C++ ABI guard variable pattern in LoweringPrepare. This is initial support, errorNYI covering all that hasn't been added just yet. 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 Once the new design doc lands I'll add more information over there.
This commit is contained in:
parent
24fbf9deb0
commit
da9c513c87
@ -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
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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();
|
||||
}]>
|
||||
];
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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 @@ public:
|
||||
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
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user