From 0f8d66f0ea1062715cebfd877f167d34a0fcf70a Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Wed, 3 Dec 2025 01:48:26 -0800 Subject: [PATCH 1/3] Update [ghstack-poisoned] --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 13 +- clang/include/clang/CIR/Dialect/IR/CIROps.td | 48 +- .../clang/CIR/Interfaces/ASTAttrInterfaces.td | 48 +- clang/include/clang/CIR/MissingFeatures.h | 7 + clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 13 + clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 24 +- clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp | 23 + clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 7 +- clang/lib/CIR/CodeGen/CIRGenFunction.h | 13 + clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 28 + .../Dialect/Transforms/LoweringPrepare.cpp | 551 +++++++++++++++++- clang/test/CIR/CodeGen/static-local.cpp | 112 ++++ clang/test/CIR/crashes/dyncast-assertion.cpp | 23 - .../CIR/crashes/static-init-recursion.cpp | 12 - .../test/CIR/crashes/static-var-dyn-cast.cpp | 19 - .../CIR/divergences/static-local-noundef.cpp | 24 + 16 files changed, 861 insertions(+), 104 deletions(-) create mode 100644 clang/test/CIR/CodeGen/static-local.cpp delete mode 100644 clang/test/CIR/crashes/dyncast-assertion.cpp delete mode 100644 clang/test/CIR/crashes/static-init-recursion.cpp delete mode 100644 clang/test/CIR/crashes/static-var-dyn-cast.cpp create mode 100644 clang/test/CIR/divergences/static-local-noundef.cpp diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index 512e33c567e2..4f14f95b5130 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -223,6 +223,18 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return createCompare(loc, cir::CmpOpKind::ne, operand, operand); } + /// Return a boolean value testing if \p arg == 0. + mlir::Value createIsNull(mlir::Location loc, mlir::Value arg) { + return createCompare(loc, cir::CmpOpKind::eq, arg, + getNullValue(arg.getType(), loc)); + } + + /// Return a boolean value testing if \p arg != 0. + mlir::Value createIsNotNull(mlir::Location loc, mlir::Value arg) { + return createCompare(loc, cir::CmpOpKind::ne, arg, + getNullValue(arg.getType(), loc)); + } + mlir::Value createUnaryOp(mlir::Location loc, cir::UnaryOpKind kind, mlir::Value operand) { return cir::UnaryOp::create(*this, loc, kind, operand); @@ -399,7 +411,6 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { /*tbaa=*/cir::TBAAAttr{}); } - mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType, mlir::Type type, llvm::StringRef name, mlir::IntegerAttr alignment, diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 2d2dd4415edc..7c3149b350f3 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -2600,6 +2600,10 @@ def CIR_GlobalOp : CIR_Op<"global", [ `visibility_attr` is defined in terms of CIR's visibility. + The `static_local` 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). + Example: ```mlir @@ -2611,27 +2615,18 @@ def CIR_GlobalOp : CIR_Op<"global", [ // Note that both sym_name and sym_visibility are tied to Symbol trait. // TODO: sym_visibility can possibly be represented by implementing the // necessary Symbol's interface in terms of linkage instead. - let arguments = (ins - SymbolNameAttr:$sym_name, - DefaultValuedAttr< - CIR_VisibilityAttr, - "VisibilityKind::Default" - >:$global_visibility, - OptionalAttr:$sym_visibility, - TypeAttr:$sym_type, - CIR_GlobalLinkageKind:$linkage, - OptionalAttr:$addr_space, - OptionalAttr:$tls_model, - // Note this can also be a FlatSymbolRefAttr - OptionalAttr:$initial_value, - UnitAttr:$comdat, - UnitAttr:$constant, - UnitAttr:$dso_local, - OptionalAttr:$alignment, - OptionalAttr:$ast, - OptionalAttr:$section, - OptionalAttr:$annotations - ); + let arguments = (ins SymbolNameAttr:$sym_name, + DefaultValuedAttr:$global_visibility, + OptionalAttr:$sym_visibility, TypeAttr:$sym_type, + CIR_GlobalLinkageKind:$linkage, + OptionalAttr:$addr_space, + OptionalAttr:$tls_model, + // Note this can also be a FlatSymbolRefAttr + OptionalAttr:$initial_value, UnitAttr:$comdat, + UnitAttr:$constant, UnitAttr:$dso_local, UnitAttr:$static_local, + OptionalAttr:$alignment, OptionalAttr:$ast, + OptionalAttr:$section, OptionalAttr:$annotations); let regions = (region AnyRegion:$ctorRegion, AnyRegion:$dtorRegion); @@ -2643,6 +2638,7 @@ def CIR_GlobalOp : CIR_Op<"global", [ (`comdat` $comdat^)? ($tls_model^)? (`dso_local` $dso_local^)? + (`static_local` $static_local^)? (` ` custom($addr_space)^ )? $sym_name custom($sym_type, $initial_value, $ctorRegion, $dtorRegion) @@ -2696,6 +2692,10 @@ def CIR_GetGlobalOp : CIR_Op<"get_global", [ Addresses of thread local globals can only be retrieved if this operation is marked `thread_local`, which indicates the address isn't constant. + The `static_local` attribute indicates that this global is a function-local + static variable that requires guarded initialization (e.g., C++ static + local variables with non-constant initializers). + Example: ```mlir %x = cir.get_global @foo : !cir.ptr @@ -2704,14 +2704,18 @@ def CIR_GetGlobalOp : CIR_Op<"get_global", [ ... cir.global external addrspace(offload_global) @gv = #cir.int<0> : !s32i %z = cir.get_global @gv : !cir.ptr + ... + %w = cir.get_global static_local @func_static : !cir.ptr ``` }]; - let arguments = (ins FlatSymbolRefAttr:$name, UnitAttr:$tls); + let arguments = (ins FlatSymbolRefAttr:$name, UnitAttr:$tls, + UnitAttr:$static_local); let results = (outs Res:$addr); let assemblyFormat = [{ (`thread_local` $tls^)? + (`static_local` $static_local^)? $name `:` qualified(type($addr)) attr-dict }]; } diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td index babeb0bb37f0..75d0274b81fa 100644 --- a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td +++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td @@ -63,20 +63,48 @@ let cppNamespace = "::cir" in { def ASTVarDeclInterface : AttrInterface<"ASTVarDeclInterface", [ASTDeclaratorDeclInterface]> { - let methods = [ - InterfaceMethod<"", "void", "mangleDynamicInitializer", (ins "llvm::raw_ostream&":$Out), [{}], - /*defaultImplementation=*/ [{ + let methods = [InterfaceMethod<"", "void", "mangleDynamicInitializer", + (ins "llvm::raw_ostream&":$Out), [{}], + /*defaultImplementation=*/[{ std::unique_ptr MangleCtx( $_attr.getAst()->getASTContext().createMangleContext()); MangleCtx->mangleDynamicInitializer($_attr.getAst(), Out); - }] - >, - InterfaceMethod<"", "clang::VarDecl::TLSKind", "getTLSKind", (ins), [{}], - /*defaultImplementation=*/ [{ + }]>, + InterfaceMethod<"", "void", "mangleStaticGuardVariable", + (ins "llvm::raw_ostream&":$Out), [{}], + /*defaultImplementation=*/[{ + std::unique_ptr mangleCtx( + $_attr.getAst()->getASTContext().createMangleContext()); + mangleCtx->mangleStaticGuardVariable($_attr.getAst(), Out); + }]>, + InterfaceMethod<"", "clang::VarDecl::TLSKind", "getTLSKind", + (ins), [{}], + /*defaultImplementation=*/[{ return $_attr.getAst()->getTLSKind(); - }] - > - ]; + }]>, + InterfaceMethod<"", "bool", "isInline", (ins), [{}], + /*defaultImplementation=*/[{ + return $_attr.getAst()->isInline(); + }]>, + InterfaceMethod<"", "clang::TemplateSpecializationKind", + "getTemplateSpecializationKind", (ins), [{}], + /*defaultImplementation=*/[{ + return $_attr.getAst()->getTemplateSpecializationKind(); + }]>, + InterfaceMethod<"", "bool", "isLocalVarDecl", (ins), [{}], + /*defaultImplementation=*/[{ + return $_attr.getAst()->isLocalVarDecl(); + }]>, + InterfaceMethod<"", "clang::SourceLocation", "getLocation", + (ins), [{}], + /*defaultImplementation=*/[{ + return $_attr.getAst()->getLocation(); + }]>, + InterfaceMethod<"", "const clang::VarDecl *", "getRawDecl", + (ins), [{}], + /*defaultImplementation=*/[{ + return $_attr.getAst(); + }]>]; } def ASTFunctionDeclInterface : AttrInterface<"ASTFunctionDeclInterface", diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index a7a6e2f03f51..2fdb69c48185 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -127,6 +127,8 @@ struct MissingFeatures { static bool setFunctionAttributes() { return false; } static bool attributeBuiltin() { return false; } static bool attributeNoBuiltin() { return false; } + static bool functionIndexAttribute() { return false; } + static bool noUnwindAttribute() { return false; } static bool parameterAttributes() { return false; } static bool minLegalVectorWidthAttr() { return false; } static bool vscaleRangeAttr() { return false; } @@ -160,6 +162,7 @@ struct MissingFeatures { // Folding methods. static bool foldBinOpFMF() { return false; } + static bool folder() { return false; } // Fast math. static bool fastMathGuard() { return false; } @@ -485,6 +488,10 @@ struct MissingFeatures { static bool dataLayoutPtrHandlingBasedOnLangAS() { return false; } static bool tailCall() { return false; } + + static bool addressSpaceInGlobalVar() { return false; } + + static bool useARMGuardVarABI() { return false; } }; } // namespace cir diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index 9a82ea046935..7cad7d4cef72 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -174,6 +174,19 @@ class CIRGenCXXABI { bool ForVirtualBase, bool Delegating, Address This, QualType ThisTy) = 0; + /*************************** Static local guards ****************************/ + + /// Emits the guarded initializer and destructor setup for the given + /// variable, given that it couldn't be emitted as a constant. + /// If \p PerformInit is false, the initialization has been folded to a + /// constant and should not be performed. + /// + /// The variable may be: + /// - a static local variable + /// - a static data member of a class template instantiation + virtual void emitGuardedInit(CIRGenFunction &cgf, const VarDecl &varDecl, + cir::GlobalOp globalOp, bool performInit) = 0; + /// Emit code to force the execution of a destructor during global /// teardown. The default implementation of this uses atexit. /// diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index 54de09995afa..4e156c36afa2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -636,8 +636,9 @@ CIRGenFunction::addInitializerToStaticVarDecl(const VarDecl &varDecl, else { // Since we have a static initializer, this global variable can't // be constant. - llvm_unreachable("C++ guarded init it NYI"); globalOp.setConstant(false); + emitCXXGuardedInit(varDecl, globalOp, /*performInit*/ true); + getGlobalOp.setStaticLocal(true); } return globalOp; } @@ -660,6 +661,21 @@ CIRGenFunction::addInitializerToStaticVarDecl(const VarDecl &varDecl, if (globalOp.getSymType() != typedInit.getType()) { globalOp.setSymType(typedInit.getType()); + cir::GlobalOp oldGlobalOp = globalOp; + globalOp = + builder.createGlobal(CGM.getModule(), getLoc(varDecl.getSourceRange()), + oldGlobalOp.getName(), typedInit.getType(), + oldGlobalOp.getConstant(), globalOp.getLinkage()); + // FIXME(cir): OG codegen inserts new GV before old one, we probably don't + // need that? + globalOp.setVisibility(oldGlobalOp.getVisibility()); + globalOp.setGlobalVisibilityAttr(oldGlobalOp.getGlobalVisibilityAttr()); + globalOp.setInitialValueAttr(typedInit); + globalOp.setTlsModelAttr(oldGlobalOp.getTlsModelAttr()); + globalOp.setDSOLocal(oldGlobalOp.getDsoLocal()); + assert(!cir::MissingFeatures::setComdat()); + assert(!cir::MissingFeatures::addressSpaceInGlobalVar()); + // Normally this should be done with a call to CGM.replaceGlobal(OldGV, GV), // but since at this point the current block hasn't been really attached, // there's no visibility into the GetGlobalOp corresponding to this Global. @@ -667,6 +683,12 @@ CIRGenFunction::addInitializerToStaticVarDecl(const VarDecl &varDecl, // directly. getGlobalOp.getAddr().setType(getBuilder().getPointerTo( typedInit.getType(), globalOp.getAddrSpaceAttr())); + + // Replace all uses of the old global with the new global + oldGlobalOp->replaceAllUsesWith(globalOp); + + // Erase the old global, since it is no longer used. + oldGlobalOp->erase(); } bool needsDtor = diff --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp index 0b9fa80536de..430550979aa3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "CIRGenCXXABI.h" #include "CIRGenFunction.h" #include "CIRGenModule.h" #include "TargetInfo.h" @@ -51,3 +52,25 @@ void CIRGenModule::emitCXXGlobalVarDeclInitFunc(const VarDecl *D, emitCXXGlobalVarDeclInit(D, Addr, PerformInit); } + +void CIRGenFunction::emitCXXGuardedInit(const VarDecl &varDecl, + cir::GlobalOp globalOp, + bool performInit) { + // If we've been asked to forbid guard variables, emit an error now. This + // diagnostic is hard-coded for Darwin's use case; we can find better phrasing + // if someone else needs it. + if (CGM.getCodeGenOpts().ForbidGuardVariables) + llvm_unreachable("NYI"); + + CGM.getCXXABI().emitGuardedInit(*this, varDecl, globalOp, performInit); +} + +void CIRGenFunction::emitCXXGlobalVarDeclInit(const VarDecl &varDecl, + cir::GlobalOp globalOp, + bool performInit) { + // TODO(CIR): We diverge from CodeGen here via having this in CIRGenModule + // instead. This is necessary due to the way we are constructing global inits + // at the moment. With LoweringPrepare being moved to CIRGen we should + // refactor this to live here. + llvm_unreachable("NYI"); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 3930c3717e71..99e1d426d59c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -438,7 +438,7 @@ void CIRGenFunction::LexicalScope::cleanup() { // An empty non-entry block has nothing to offer, and since this is // synthetic, losing information does not affect anything. bool entryBlock = builder.getInsertionBlock()->isEntryBlock(); - if (!entryBlock && currBlock->empty()) { + if (!entryBlock && currBlock->empty() && currBlock->hasNoPredecessors()) { currBlock->erase(); // Remove unused cleanup blocks. if (cleanupBlock && cleanupBlock->hasNoPredecessors()) @@ -1379,9 +1379,8 @@ void CIRGenFunction::StartFunction(GlobalDecl gd, QualType retTy, } assert(builder.getInsertionBlock() && "Should be valid"); - auto fnEndLoc = (fd && fd->getBody()) - ? getLoc(fd->getBody()->getEndLoc()) - : getLoc(Loc); + auto fnEndLoc = (fd && fd->getBody()) ? getLoc(fd->getBody()->getEndLoc()) + : getLoc(Loc); // When the current function is not void, create an address to store the // result value. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 3ff8d647e384..8aaf53de0a78 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2243,6 +2243,19 @@ class CIRGenFunction : public CIRGenTypeCache { void emitInvariantStart(CharUnits Size); + /// Emit code in this function to perform a guarded variable + /// initialization. Guarded initializations are used when it's not + /// possible to prove that an initialization will be done exactly + /// once, e.g. with a static local variable or a static data member + /// of a class template. + void emitCXXGuardedInit(const VarDecl &varDecl, cir::GlobalOp globalOp, + bool performInit); + + /// EmitCXXGlobalVarDeclInit - Create the initializer for a C++ + /// variable with global storage. + void emitCXXGlobalVarDeclInit(const VarDecl &varDecl, cir::GlobalOp globalOp, + bool performInit); + mlir::LogicalResult emitLabel(const clang::LabelDecl *D); mlir::LogicalResult emitLabelStmt(const clang::LabelStmt &S); diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index cd597fcc7572..3460f5f51b25 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -17,17 +17,27 @@ // //===----------------------------------------------------------------------===// +#include "CIRGenBuilder.h" #include "CIRGenCXXABI.h" #include "CIRGenCleanup.h" +#include "CIRGenFunction.h" #include "CIRGenFunctionInfo.h" +#include "CIRGenModule.h" #include "ConstantInitBuilder.h" +#include "mlir/IR/Block.h" +#include "mlir/IR/BuiltinAttributes.h" #include "clang/AST/GlobalDecl.h" #include "clang/AST/Mangle.h" #include "clang/AST/VTableBuilder.h" #include "clang/Basic/Linkage.h" +#include "clang/Basic/Specifiers.h" #include "clang/Basic/TargetInfo.h" #include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "clang/CIR/MissingFeatures.h" #include "llvm/Support/ErrorHandling.h" using namespace clang; @@ -192,6 +202,8 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI { CXXDtorType Type, bool ForVirtualBase, bool Delegating, Address This, QualType ThisTy) override; + void emitGuardedInit(CIRGenFunction &cgf, const VarDecl &varDecl, + cir::GlobalOp globalOp, bool performInit) override; void registerGlobalDtor(CIRGenFunction &CGF, const VarDecl *D, cir::FuncOp dtor, mlir::Value Addr) override; void emitVirtualObjectDelete(CIRGenFunction &CGF, const CXXDeleteExpr *DE, @@ -3019,3 +3031,19 @@ LValue CIRGenItaniumCXXABI::emitThreadLocalVarDeclLValue(CIRGenFunction &cgf, lv = cgf.makeAddrLValue(addr, lvalType, AlignmentSource::Decl); return lv; } + +/// The ARM code here follows the Itanium code closely enough that we just +/// special-case it at particular places. +void CIRGenItaniumCXXABI::emitGuardedInit(CIRGenFunction &cgf, + const VarDecl &varDecl, + cir::GlobalOp globalOp, + bool performInit) { + + // Emit the initializer and add a global destructor if appropriate. + cgf.CGM.emitCXXGlobalVarDeclInit(&varDecl, globalOp, performInit); + + // CIR diverges from IRGen here by emitting the init into the ctor region and + // marking the global as static local. The emission of the guard/acquire walk + // is done during LoweringPrepare. + globalOp.setStaticLocal(true); +} diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index d7359bf960e4..c2b4960d0ba0 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -8,6 +8,7 @@ #include "LoweringPrepareCXXABI.h" #include "PassDetail.h" +#include "mlir/Dialect/Ptr/IR/MemorySpaceInterfaces.h" #include "mlir/IR/BuiltinAttributes.h" #include "mlir/IR/Region.h" #include "clang/AST/ASTContext.h" @@ -18,10 +19,12 @@ #include "clang/Basic/Module.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/Passes.h" #include "clang/CIR/Interfaces/ASTAttrInterfaces.h" +#include "clang/CIR/MissingFeatures.h" #include "llvm/ADT/APFloat.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" @@ -82,6 +85,7 @@ struct LoweringPreparePass : public LoweringPrepareBase { void lowerVAArgOp(VAArgOp op); void lowerDeleteArrayOp(DeleteArrayOp op); void lowerGlobalOp(GlobalOp op); + void lowerGetGlobalOp(GetGlobalOp op); void lowerDynamicCastOp(DynamicCastOp op); void lowerStdFindOp(StdFindOp op); void lowerIterBeginOp(IterBeginOp op); @@ -92,6 +96,9 @@ struct LoweringPreparePass : public LoweringPrepareBase { void lowerThrowOp(ThrowOp op); void lowerTrivialConstructorCall(cir::CallOp op); + void handleStaticLocal(GlobalOp globalOp, GetGlobalOp getGlobalOp); + void handleGlobalOpCtorDtor(GlobalOp globalOp); + /// Collect annotations of global values in the module void addGlobalAnnotations(mlir::Operation *op, mlir::ArrayAttr annotations); @@ -107,6 +114,16 @@ struct LoweringPreparePass : public LoweringPrepareBase { /// Build attribute of global annotation values void buildGlobalAnnotationValues(); + cir::GlobalOp + getStaticLocalDeclGuardAddress(cir::ASTVarDeclInterface varDecl) { + return staticLocalDeclGuardMap[varDecl]; + } + + void setStaticLocalDeclGuardAddress(cir::ASTVarDeclInterface varDecl, + cir::GlobalOp globalOp) { + staticLocalDeclGuardMap[varDecl] = globalOp; + } + FuncOp buildRuntimeFunction( mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, cir::FuncType type, @@ -194,6 +211,9 @@ struct LoweringPreparePass : public LoweringPrepareBase { llvm::SmallVector, 4> globalDtorList; /// List of annotations in the module llvm::SmallVector globalAnnotations; + + llvm::DenseMap + staticLocalDeclGuardMap; }; std::string getCUDAPrefix(clang::ASTContext *astCtx) { @@ -397,7 +417,6 @@ void LoweringPreparePass::lowerVAArgOp(VAArgOp op) { op.replaceAllUsesWith(res); op.erase(); } - return; } void LoweringPreparePass::lowerDeleteArrayOp(DeleteArrayOp op) { @@ -895,29 +914,535 @@ void LoweringPreparePass::lowerThreeWayCmpOp(CmpThreeWayOp op) { op.erase(); } -void LoweringPreparePass::lowerGlobalOp(GlobalOp op) { - auto &ctorRegion = op.getCtorRegion(); - auto &dtorRegion = op.getDtorRegion(); +void LoweringPreparePass::handleGlobalOpCtorDtor(GlobalOp globalOp) { + auto &ctorRegion = globalOp.getCtorRegion(); + auto &dtorRegion = globalOp.getDtorRegion(); if (!ctorRegion.empty() || !dtorRegion.empty()) { // Build a variable initialization function and move the initialzation code // in the ctor region over. - auto f = buildCXXGlobalVarDeclInitFunc(op); + auto f = buildCXXGlobalVarDeclInitFunc(globalOp); // Clear the ctor and dtor region ctorRegion.getBlocks().clear(); dtorRegion.getBlocks().clear(); - auto astDecl = mlir::cast(*op.getAst()); + auto astDecl = mlir::cast(*globalOp.getAst()); if (astDecl.hasInitPriorityAttr()) f.setGlobalCtorPriority(astDecl.getInitPriorityAttr()->getPriority()); dynamicInitializers.push_back(f); } - std::optional annotations = op.getAnnotations(); - if (annotations) { - addGlobalAnnotations(op, annotations.value()); + std::optional annotations = globalOp.getAnnotations(); + if (annotations) + addGlobalAnnotations(globalOp, annotations.value()); +} + +void LoweringPreparePass::lowerGetGlobalOp(GetGlobalOp getGlobalOp) { + if (!getGlobalOp.getStaticLocal()) + return; + + auto globalOp = mlir::cast( + mlir::SymbolTable::lookupSymbolIn(theModule, getGlobalOp.getName())); + + handleStaticLocal(globalOp, getGlobalOp); +} + +void LoweringPreparePass::lowerGlobalOp(GlobalOp globalOp) { + if (!globalOp.getStaticLocal()) + handleGlobalOpCtorDtor(globalOp); +} + +static cir::GlobalOp +createGuardGlobalOp(::cir::CIRBaseBuilderTy &builder, mlir::Location loc, + StringRef name, mlir::Type type, bool isConstant, + mlir::ptr::MemorySpaceAttrInterface addrSpace, + cir::GlobalLinkageKind linkage, cir::FuncOp curFn) { + + cir::GlobalOp g; + { + mlir::OpBuilder::InsertionGuard guard(builder); + + // insert before the Fn requiring the guard var here + builder.setInsertionPoint(curFn); + + g = cir::GlobalOp::create(builder, loc, name, type, isConstant, linkage, + addrSpace); + + // Default to private until we can judge based on the initializer, + // since MLIR doesn't allow public declarations. + mlir::SymbolTable::setSymbolVisibility( + g, mlir::SymbolTable::Visibility::Private); + } + return g; +} + +static mlir::Operation *getGlobalValue(mlir::ModuleOp theModule, + StringRef name) { + auto *global = mlir::SymbolTable::lookupSymbolIn(theModule, name); + if (!global) + return {}; + return global; +} + +static cir::FuncOp createCIRFunction(::cir::CIRBaseBuilderTy &builder, + mlir::MLIRContext &mlirContext, + mlir::ModuleOp theModule, + mlir::Location loc, StringRef name, + cir::FuncType type) { + // At the point we need to create the function, the insertion point + // could be anywhere (e.g. callsite). Do not rely on whatever it might + // be, properly save, find the appropriate place and restore. + FuncOp f; + { + mlir::OpBuilder::InsertionGuard guard(builder); + + // Get the first function in the module as the location to insert the new + // function. + Operation *firstFn = &theModule->getRegion(0).getBlocks().front().front(); + builder.setInsertionPoint(firstFn); + + f = builder.create(loc, name, type); + + assert(f.isDeclaration() && "expected empty body"); + + // A declaration gets private visibility by default, but external linkage + // as the default linkage. + f.setLinkageAttr(cir::GlobalLinkageKindAttr::get( + &mlirContext, cir::GlobalLinkageKind::ExternalLinkage)); + mlir::SymbolTable::setSymbolVisibility( + f, mlir::SymbolTable::Visibility::Private); + + // Initialize with empty dict of extra attributes. + f.setExtraAttrsAttr(cir::ExtraFuncAttributesAttr::get( + &mlirContext, builder.getDictionaryAttr({}))); + } + return f; +} + +/// If the specified mangled name is not in the module, +/// create and return a CIR Function with the specified type. If there is +/// something in the module with the specified name, return it potentially +/// bitcasted to the right type. +/// +/// If D is non-null, it specifies a decl that corresponded to this. This is +/// used to set the attributes on the function when it is first created. +static cir::FuncOp getOrCreateCIRFunctionForRuntimeFunction( + clang::ASTContext &astContext, mlir::MLIRContext &mlirContext, + mlir::ModuleOp theModule, ::cir::CIRBaseBuilderTy &builder, + StringRef mangledName, mlir::Type type) { + // Lookup the entry, lazily creating it if necessary. + mlir::Operation *entry = getGlobalValue(theModule, mangledName); + if (entry) { + assert(isa(entry) && + "not implemented, only supports FuncOp for now"); + + // If there are two attempts to define the same mangled name, issue an + // error. + auto fn = cast(entry); + + if (fn && fn.getFunctionType() == type) { + return fn; + } + } + + // This function doesn't have a complete type (for example, the return type is + // an incomplete struct). Use a fake type instead, and make sure not to try to + // set attributes. + bool isIncompleteFunction = false; + + cir::FuncType fTy; + if (mlir::isa(type)) { + fTy = mlir::cast(type); + } else { + assert(false && "NYI"); + // FTy = mlir::FunctionType::get(VoidTy, false); + isIncompleteFunction = true; + } + + // TODO: CodeGen includeds the linkage (ExternalLinkage) and only passes the + // mangledname if Entry is nullptr + auto func = createCIRFunction(builder, mlirContext, theModule, + theModule.getLoc(), mangledName, fTy); + + // If we already created a function with the same mangled name (but different + // type) before, take its name and add it to the list of functions to be + // replaced with F at the end of CodeGen. + // + // This happens if there is a prototype for a function (e.g. "int f()") and + // then a definition of a different type (e.g. "int f(int x)"). + if (entry) { + // Fetch a generic symbol-defining operation and its uses. + auto symbolOp = dyn_cast(entry); + assert(symbolOp && "Expected a symbol-defining operation"); + + // TODO(cir): When can this symbol be something other than a function? + assert(isa(entry) && "NYI"); + + // Obliterate no-proto declaration. + entry->erase(); + } + + if (!isIncompleteFunction) { + assert(func.getFunctionType() == type); + return func; + } + + // TODO(cir): Might need bitcast to different address space. + assert(!cir::MissingFeatures::addressSpace()); + return func; +} + +static cir::FuncOp createRuntimeFunction( + clang::ASTContext &astContext, mlir::MLIRContext &mlirContext, + mlir::ModuleOp theModule, ::cir::CIRBaseBuilderTy &builder, + + cir::FuncType type, StringRef name, mlir::ArrayAttr = {}, + [[maybe_unused]] bool local = false, bool assumeConvergent = false) { + if (assumeConvergent) { + llvm_unreachable("NYI"); + } + if (local) + llvm_unreachable("NYI"); + + auto entry = getOrCreateCIRFunctionForRuntimeFunction( + astContext, mlirContext, theModule, builder, name, type); + + // Traditional codegen checks for a valid dyn_cast llvm::Function for `entry`, + // no testcase that cover this path just yet though. + if (!entry) { + // Setup runtime CC, DLL support for windows and set dso local. + llvm_unreachable("NYI"); + } + + return entry; +} + +static cir::FuncOp getGuardAbortFn(clang::ASTContext &astContext, + mlir::MLIRContext &mlirContext, + mlir::ModuleOp theModule, + ::cir::CIRBaseBuilderTy &builder, + cir::PointerType guardPtrTy) { + // void __cxa_guard_abort(__guard *guard_object); + cir::FuncType fTy = cir::FuncType::get({guardPtrTy}, builder.getVoidTy(), + /*isVarArg=*/false); + assert(!cir::MissingFeatures::functionIndexAttribute()); + assert(!cir::MissingFeatures::noUnwindAttribute()); + return createRuntimeFunction(astContext, mlirContext, theModule, builder, fTy, + "__cxa_guard_abort"); +} + +static cir::FuncOp getGuardAcquireFn(clang::ASTContext &astContext, + mlir::MLIRContext &mlirContext, + mlir::ModuleOp theModule, + ::cir::CIRBaseBuilderTy &builder, + cir::PointerType guardPtrTy) { + // int __cxa_guard_acquire(__guard *guard_object); + // TODO(CIR): The hardcoded getSIntNTy(32) is wrong here. CodeGen uses + // CodeGenTypes.convertType but we don't have access to the CGM. + cir::FuncType fTy = cir::FuncType::get({guardPtrTy}, builder.getSIntNTy(32), + /*isVarArg=*/false); + assert(!cir::MissingFeatures::functionIndexAttribute()); + assert(!cir::MissingFeatures::noUnwindAttribute()); + return createRuntimeFunction(astContext, mlirContext, theModule, builder, fTy, + "__cxa_guard_acquire"); +} + +static cir::FuncOp getGuardReleaseFn(clang::ASTContext &astContext, + mlir::MLIRContext &mlirContext, + mlir::ModuleOp theModule, + ::cir::CIRBaseBuilderTy &builder, + cir::PointerType guardPtrTy) { + // void __cxa_guard_release(__guard *guard_object); + cir::FuncType fTy = cir::FuncType::get({guardPtrTy}, builder.getVoidTy(), + /*isVarArg=*/false); + assert(!cir::MissingFeatures::functionIndexAttribute()); + assert(!cir::MissingFeatures::noUnwindAttribute()); + return createRuntimeFunction(astContext, mlirContext, theModule, builder, fTy, + "__cxa_guard_release"); +} + +static mlir::Value emitRuntimeCall(::cir::CIRBaseBuilderTy &builder, + mlir::Location loc, cir::FuncOp callee, + ArrayRef args) { + // TODO(cir): set the calling convention to this runtime call. + assert(!cir::MissingFeatures::setCallingConv()); + + auto call = builder.createCallOp(loc, callee, args); + assert(call->getNumResults() <= 1 && + "runtime functions have at most 1 result"); + + if (call->getNumResults() == 0) + return nullptr; + + return call->getResult(0); +} + +static mlir::Value emitNounwindRuntimeCall(::cir::CIRBaseBuilderTy &builder, + mlir::Location loc, + cir::FuncOp callee, + ArrayRef args) { + mlir::Value call = emitRuntimeCall(builder, loc, callee, args); + assert(!cir::MissingFeatures::noUnwindAttribute()); + return call; +} + +void LoweringPreparePass::handleStaticLocal(GlobalOp globalOp, + GetGlobalOp getGlobalOp) { + CIRBaseBuilderTy builder(getContext()); + + std::optional astOption = globalOp.getAst(); + assert(astOption.has_value()); + cir::ASTVarDeclInterface varDecl = astOption.value(); + + builder.setInsertionPointAfter(getGlobalOp); + Block *getGlobalOpBlock = builder.getInsertionBlock(); + // TODO(CIR): This is too simple at the moment. This is only tested on a + // simple test case with only the static local var decl and thus we only have + // the return. For less trivial examples we'll have to handle shuffling the + // contents of this block more carefully. + 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() && + !isTemplateInstantiation(varDecl.getTemplateSpecializationKind()); + + // 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(); + + // 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(); + + cir::IntType guardTy; + clang::CharUnits guardAlignment; + if (useInt8GuardVariable) { + guardTy = cir::IntType::get(&getContext(), 8, /*isSigned=*/true); + guardAlignment = clang::CharUnits::One(); + } else { + // 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 (::cir::MissingFeatures::useARMGuardVarABI()) { + llvm_unreachable("NYI"); + } else { + guardTy = cir::IntType::get(&getContext(), 64, /*isSigned=*/true); + cir::CIRDataLayout dataLayout(theModule); + guardAlignment = + clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy)); + } + } + auto guardPtrTy = cir::PointerType::get(guardTy); + + // Create the guard variable if we don't already have it (as we might if + // we're double-emitting this function body). + cir::GlobalOp guard = getStaticLocalDeclGuardAddress(varDecl); + if (!guard) { + // Mangle the name for the guard. + SmallString<256> guardName; + { + llvm::raw_svector_ostream out(guardName); + varDecl.mangleStaticGuardVariable(out); + } + + // Create the guard variable with a zero-initializer. + // Just absorb linkage, visibility and dll storage class from the guarded + // variable. + guard = createGuardGlobalOp(builder, globalOp->getLoc(), guardName, guardTy, + /*isConstant=*/false, /*addrSpace=*/{}, + globalOp.getLinkage(), + getGlobalOp->getParentOfType()); + guard.setInitialValueAttr(cir::IntAttr::get(guardTy, 0)); + guard.setDSOLocal(globalOp.isDSOLocal()); + guard.setVisibility(globalOp.getVisibility()); + assert(!::cir::MissingFeatures::setDLLStorageClass()); + // guard.setDLLStorageClass(globalOp.getDLLStorageClass()); + // If the variable is thread-local, so is its guard variable. + assert(!::cir::MissingFeatures::threadLocal()); + // guard.setThreadLocalMode(globalOp.getThreadLocalMode()); + 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. + assert(!::cir::MissingFeatures::setComdat()); + + setStaticLocalDeclGuardAddress(varDecl, guard); + } + + 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(); + + auto initBlock = [&]() { + // CIR: Move the initializer from the globalOp's ctor region into the + // current block. + // TODO(CIR): Once we support exceptions we'll need to walk the ctor region + // to change calls to invokes. + auto &ctorRegion = globalOp.getCtorRegion(); + assert(!ctorRegion.empty() && "This should never be empty here."); + if (!ctorRegion.hasOneBlock()) + llvm_unreachable("Multiple blocks NYI"); + Block &block = ctorRegion.front(); + Block *insertBlock = builder.getInsertionBlock(); + insertBlock->getOperations().splice(insertBlock->end(), + block.getOperations(), block.begin(), + std::prev(block.end())); + builder.setInsertionPointToEnd(insertBlock); + + ctorRegion.getBlocks().clear(); + + if (threadsafe) { + // NOTE(CIR): CodeGen clears the above pushed CallGuardAbort here and thus + // the __guard_abort gets inserted. We'll have to figure out how to + // properly handle this when supporting static locals with exceptions. + + // Call __cxa_guard_release. This cannot throw. + emitNounwindRuntimeCall(builder, globalOp->getLoc(), + getGuardReleaseFn(*astCtx, getContext(), + theModule, builder, guardPtrTy), + guardPtr); + } else if (varDecl.isLocalVarDecl()) { + llvm_unreachable("NYI"); + } + }; + + // 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. + auto guardAcquireBlock = [&]() { + if (threadsafe) { + auto loc = globalOp->getLoc(); + // Call __cxa_guard_acquire. + mlir::Value value = emitNounwindRuntimeCall( + builder, loc, + getGuardAcquireFn(*astCtx, getContext(), theModule, builder, + guardPtrTy), + guardPtr); + + auto isNotNull = builder.createIsNotNull(loc, value); + builder.create(globalOp.getLoc(), isNotNull, + /*=withElseRegion*/ false, + [&](mlir::OpBuilder &, mlir::Location) { + initBlock(); + builder.createYield(getGlobalOp->getLoc()); + }); + + // NOTE(CIR): CodeGen pushes a CallGuardAbort cleanup here, but we are + // synthesizing the outcome via walking the CIR in the ctor region and + // changing calls to invokes. + + } else if (!varDecl.isLocalVarDecl()) { + llvm_unreachable("NYI"); + } + }; + + if (!threadsafe || maxInlineWidthInbits) { + // Load the first byte of the guard variable. + // Cast guard pointer to byte pointer for loading just the first byte. + auto bytePtrTy = cir::PointerType::get(builder.getSIntNTy(8)); + mlir::Value bytePtr = builder.createBitcast(guardPtr, bytePtrTy); + mlir::Value load = builder.createAlignedLoad( + getGlobalOp.getLoc(), bytePtr, guardAlignment.getAsAlign().value()); + + // Itanium ABI: + // An implementation supporting thread-safety on multiprocesor 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) + cast(load.getDefiningOp()) + .setAtomic(cir::MemOrder::Acquire, 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 (MissingFeatures::useARMGuardVarABI() && !useInt8GuardVariable) + llvm_unreachable("NYI"); + mlir::Value constOne = builder.getConstAPSInt( + getGlobalOp->getLoc(), llvm::APSInt(llvm::APInt(8, 1), + /*isUnsigned=*/false)); + mlir::Value value = + (!cir::MissingFeatures::useARMGuardVarABI() && !useInt8GuardVariable) + ? builder.createAnd(load, constOne) + : load; + mlir::Value needsInit = builder.createIsNull(globalOp.getLoc(), value); + + builder.create(globalOp.getLoc(), needsInit, + /*=withElseRegion*/ false, + [&](mlir::OpBuilder &, mlir::Location) { + if (MissingFeatures::metaDataNode()) + llvm_unreachable("NYI"); + guardAcquireBlock(); + builder.createYield(getGlobalOp->getLoc()); + }); } + + builder.setInsertionPointToEnd(getGlobalOpBlock); + builder.insert(ret); } template @@ -1805,6 +2330,8 @@ void LoweringPreparePass::runOnOp(Operation *op) { std::string deviceSideName = shadowNameAttr.getDeviceSideName(); cudaVarMap[deviceSideName] = global; } + } else if (auto getGlobal = dyn_cast(op)) { + lowerGetGlobalOp(getGlobal); } else if (auto dynamicCast = dyn_cast(op)) { lowerDynamicCastOp(dynamicCast); } else if (auto stdFind = dyn_cast(op)) { @@ -1854,8 +2381,9 @@ void LoweringPreparePass::runOnOperation() { op->walk([&](Operation *op) { if (isa(op)) + GlobalOp, GetGlobalOp, DynamicCastOp, StdFindOp, IterEndOp, + IterBeginOp, ArrayCtor, ArrayDtor, cir::FuncOp, StoreOp, ThrowOp, + CallOp>(op)) opsToTransform.push_back(op); }); @@ -1874,7 +2402,6 @@ void LoweringPreparePass::runOnOperation() { std::unique_ptr mlir::createLoweringPreparePass() { return std::make_unique(); } - std::unique_ptr mlir::createLoweringPreparePass(clang::ASTContext *astCtx) { auto pass = std::make_unique(); diff --git a/clang/test/CIR/CodeGen/static-local.cpp b/clang/test/CIR/CodeGen/static-local.cpp new file mode 100644 index 000000000000..ce7034227748 --- /dev/null +++ b/clang/test/CIR/CodeGen/static-local.cpp @@ -0,0 +1,112 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -clangir-disable-passes %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIRGEN +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s --check-prefix=OGCG + +int fnA(); + +void foo() { + static int val = fnA(); +} + + +// CIRGEN: cir.func private @_Z3fnAv() -> !s32i +// CIRGEN: cir.global "private" internal dso_local static_local @_ZZ3foovE3val = ctor : !s32i { +// CIRGEN-NEXT: %0 = cir.get_global @_ZZ3foovE3val : !cir.ptr +// CIRGEN-NEXT: %1 = cir.call @_Z3fnAv() : () -> !s32i +// CIRGEN-NEXT: cir.store align(4) %1, %0 : !s32i, !cir.ptr +// CIRGEN-NEXT: } {alignment = 4 : i64, ast = #cir.var.decl.ast} +// CIRGEN-NEXT: cir.func +// CIRGEN: %0 = cir.get_global static_local @_ZZ3foovE3val : !cir.ptr +// CIRGEN-NEXT: cir.return +// CIRGEN-NEXT: } + +// CIR: cir.func private @__cxa_guard_release(!cir.ptr) +// CIR: cir.func private @__cxa_guard_acquire(!cir.ptr) -> !s32i +// CIR: cir.func private @_Z3fnAv() -> !s32i +// CIR: cir.global "private" internal dso_local static_local @_ZZ3foovE3val = #cir.int<0> : !s32i {alignment = 4 : i64, ast = #cir.var.decl.ast} +// CIR: cir.global "private" internal dso_local @_ZGVZ3foovE3val = #cir.int<0> : !s64i {alignment = 8 : i64} +// CIR: cir.func {{.*}} @_Z3foov() extra(#fn_attr) { +// CIR-NEXT: %0 = cir.get_global static_local @_ZZ3foovE3val : !cir.ptr +// CIR-NEXT: %1 = cir.get_global @_ZGVZ3foovE3val : !cir.ptr +// CIR-NEXT: %2 = cir.cast bitcast %1 : !cir.ptr -> !cir.ptr +// CIR-NEXT: %3 = cir.load align(8) syncscope(system) atomic(acquire) %2 : !cir.ptr, !s8i +// CIR-NEXT: %4 = cir.const #cir.int<1> : !s8i +// CIR-NEXT: %5 = cir.binop(and, %3, %4) : !s8i +// CIR-NEXT: %6 = cir.const #cir.int<0> : !s8i +// CIR-NEXT: %7 = cir.cmp(eq, %5, %6) : !s8i, !cir.bool +// CIR-NEXT: cir.if %7 { +// CIR-NEXT: %8 = cir.call @__cxa_guard_acquire(%1) : (!cir.ptr) -> !s32i +// CIR-NEXT: %9 = cir.const #cir.int<0> : !s32i +// CIR-NEXT: %10 = cir.cmp(ne, %8, %9) : !s32i, !cir.bool +// CIR-NEXT: cir.if %10 { +// CIR-NEXT: %11 = cir.get_global @_ZZ3foovE3val : !cir.ptr +// CIR-NEXT: %12 = cir.call @_Z3fnAv() : () -> !s32i +// CIR-NEXT: cir.store align(4) %12, %11 : !s32i, !cir.ptr +// CIR-NEXT: cir.call @__cxa_guard_release(%1) : (!cir.ptr) -> () +// CIR-NEXT: } +// CIR-NEXT: } +// CIR-NEXT: cir.return +// CIR-NEXT: } + +// LLVM: @_ZZ3foovE3val = internal global i32 0, align 4 +// LLVM: @_ZGVZ3foovE3val = internal global i64 0, align 8 +// LLVM: declare void @__cxa_guard_release(ptr) +// LLVM: declare i32 @__cxa_guard_acquire(ptr) +// LLVM: declare i32 @_Z3fnAv() + +// LLVM: define dso_local void @_Z3foov() +// LLVM-NEXT: %1 = load atomic i8, ptr @_ZGVZ3foovE3val acquire, align 8 +// LLVM-NEXT: %2 = and i8 %1, 1 +// LLVM-NEXT: %3 = icmp eq i8 %2, 0 +// LLVM-NEXT: br i1 %3, label %4, label %10 + +// LLVM-DAG: 4: +// LLVM-NEXT: %5 = call i32 @__cxa_guard_acquire(ptr @_ZGVZ3foovE3val) +// LLVM-NEXT: %6 = icmp ne i32 %5, 0 +// LLVM-NEXT: br i1 %6, label %7, label %9 + +// LLVM-DAG: 7: +// LLVM-NEXT: %8 = call i32 @_Z3fnAv() +// LLVM-NEXT: store i32 %8, ptr @_ZZ3foovE3val, align 4 +// LLVM-NEXT: call void @__cxa_guard_release(ptr @_ZGVZ3foovE3val) +// LLVM-NEXT: br label %9 + +// LLVM-DAG: 9: +// LLVM-NEXT: br label %10 + +// LLVM-DAG: 10: +// LLVM-NEXT: ret void +// LLVM-NEXT: } + +// OGCG: @_ZZ3foovE3val = internal global i32 0, align 4 +// OGCG: @_ZGVZ3foovE3val = internal global i64 0, align 8 + +// OGCG: define dso_local void @_Z3foov() +// OGCG: entry: +// OGCG-NEXT: %[[GUARD_LOAD:.*]] = load atomic i8, ptr @_ZGVZ3foovE3val acquire, align 8 +// OGCG-NEXT: %[[GUARD_UNINIT:.*]] = icmp eq i8 %[[GUARD_LOAD]], 0 +// OGCG-NEXT: br i1 %[[GUARD_UNINIT]], label %[[INIT_CHECK:.*]], label %[[INIT_END:.*]], + +// OGCG-DAG: [[INIT_CHECK]]: +// OGCG-NEXT: %[[GUARD_ACQ:.*]] = call i32 @__cxa_guard_acquire(ptr @_ZGVZ3foovE3val) +// OGCG-NEXT: %[[TOBOOL:.*]] = icmp ne i32 %[[GUARD_ACQ]], 0 +// OGCG-NEXT: br i1 %[[TOBOOL]], label %[[INIT:.*]], label %[[INIT_END2:.*]] + +// OGCG-DAG: [[INIT]]: +// OGCG-NEXT: %[[CALL:.*]] = call noundef i32 @_Z3fnAv() +// OGCG-NEXT: store i32 %[[CALL]], ptr @_ZZ3foovE3val, align 4 +// OGCG-NEXT: call void @__cxa_guard_release(ptr @_ZGVZ3foovE3val) +// OGCG-NEXT: br label %[[INIT_END3:.*]] + +// OGCG-DAG: [[INIT_END3]]: +// OGCG-NEXT: ret void +// OGCG-NEXT: } + +// OGCG: declare i32 @__cxa_guard_acquire(ptr) +// OGCG: declare noundef i32 @_Z3fnAv() +// OGCG: declare void @__cxa_guard_release(ptr) diff --git a/clang/test/CIR/crashes/dyncast-assertion.cpp b/clang/test/CIR/crashes/dyncast-assertion.cpp deleted file mode 100644 index ae2359a27622..000000000000 --- a/clang/test/CIR/crashes/dyncast-assertion.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -// -// XFAIL: * -// -// Crash from LLVM build with ClangIR. Creduced from llvm::formLCSSAForInstructions -// -// Issue: dyn_cast assertion failure -// Location: Casting.h:644 -// Error: Assertion `isa(Val) && "cast() argument of incompatible type!"` -// -// When initializing aggregate members in a constructor with template parameters, -// CIR attempts an invalid cast operation. - -struct a { - template a(b, c); -}; -class d { - a e; - -public: - d(int) : e(0, 0) {} -}; -void f() { static d g(0); } diff --git a/clang/test/CIR/crashes/static-init-recursion.cpp b/clang/test/CIR/crashes/static-init-recursion.cpp deleted file mode 100644 index 49811b1a8254..000000000000 --- a/clang/test/CIR/crashes/static-init-recursion.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -// -// XFAIL: * -// -// Issue: Static local variable with recursive initialization -// -// When a static local variable is initialized by calling the function that -// contains it, CIR fails during initialization code generation. This pattern -// requires special guard variable handling to prevent infinite recursion at -// runtime and detect the recursion during initialization. - -int a() { static int b = a(); } diff --git a/clang/test/CIR/crashes/static-var-dyn-cast.cpp b/clang/test/CIR/crashes/static-var-dyn-cast.cpp deleted file mode 100644 index 9ca2c610b43f..000000000000 --- a/clang/test/CIR/crashes/static-var-dyn-cast.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir -// XFAIL: * -// -// dyn_cast on non-existent value - assertion failure -// Location: Casting.h:644 -// -// Original failure: assertion_dyncast from LLVM build -// Reduced from /tmp/FormattedStream-a19c5f.cpp - -struct a { - template a(b, c); -}; -class d { - a e; - -public: - d(int) : e(0, 0) {} -}; -void f() { static d g(0); } diff --git a/clang/test/CIR/divergences/static-local-noundef.cpp b/clang/test/CIR/divergences/static-local-noundef.cpp new file mode 100644 index 000000000000..ea6cb7fd95c9 --- /dev/null +++ b/clang/test/CIR/divergences/static-local-noundef.cpp @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.cir.ll | FlileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll | FileCheck %s +// +// XFAIL: * +// +// Static local initialization divergence: +// 1. Missing noundef attribute on function declaration and call +// +// CodeGen: +// declare noundef i32 @_Z3fnAv() +// %call = call noundef i32 @_Z3fnAv() +// +// CIR: +// declare i32 @_Z3fnAv() (missing noundef) +// %8 = call i32 @_Z3fnAv() (missing noundef) + +// DIFF-DAG: declare noundef i32 @_Z3fnAv() +// DIFF-DAG %call = call noundef i32 @_Z3fnAv() + +int fnA(); + +void foo() { + static int val = fnA(); +} From 0e55e5de2fe656eeb6de21fff671c38650f31c54 Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Wed, 3 Dec 2025 01:59:30 -0800 Subject: [PATCH 2/3] Update [ghstack-poisoned] --- clang/include/clang/CIR/MissingFeatures.h | 3 ++ .../Dialect/Transforms/LoweringPrepare.cpp | 52 ++++++++++++------- .../CIR/divergences/static-local-noundef.cpp | 12 ++--- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 2fdb69c48185..d93d3914e502 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -492,6 +492,9 @@ struct MissingFeatures { static bool addressSpaceInGlobalVar() { return false; } static bool useARMGuardVarABI() { return false; } + + // Static local variable guard + static bool guardAbortOnException() { return false; } }; } // namespace cir diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index c2b4960d0ba0..061e768fc920 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -281,7 +281,7 @@ FuncOp LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(GlobalOp op) { FuncOp f = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType, cir::GlobalLinkageKind::InternalLinkage); - // Move over the initialzation code of the ctor region. + // Move over the initialization code of the ctor region. mlir::Block *entryBB = f.addEntryBlock(); if (!op.getCtorRegion().empty()) { auto &block = op.getCtorRegion().front(); @@ -919,7 +919,7 @@ void LoweringPreparePass::handleGlobalOpCtorDtor(GlobalOp globalOp) { auto &dtorRegion = globalOp.getDtorRegion(); if (!ctorRegion.empty() || !dtorRegion.empty()) { - // Build a variable initialization function and move the initialzation code + // Build a variable initialization function and move the initialization code // in the ctor region over. auto f = buildCXXGlobalVarDeclInitFunc(globalOp); @@ -1201,6 +1201,14 @@ void LoweringPreparePass::handleStaticLocal(GlobalOp globalOp, // simple test case with only the static local var decl and thus we only have // the return. For less trivial examples we'll have to handle shuffling the // contents of this block more carefully. + // + // Assert that we only have GetGlobalOp + terminator in the block for now. + // When we support more complex cases, we'll need proper block splitting. + { + size_t opCount = + std::distance(getGlobalOpBlock->begin(), getGlobalOpBlock->end()); + assert(opCount == 2 && "NYI: static local in block with other operations"); + } Operation *ret = getGlobalOpBlock->getTerminator(); ret->remove(); builder.setInsertionPointAfter(getGlobalOp); @@ -1307,8 +1315,17 @@ void LoweringPreparePass::handleStaticLocal(GlobalOp globalOp, auto initBlock = [&]() { // CIR: Move the initializer from the globalOp's ctor region into the // current block. - // TODO(CIR): Once we support exceptions we'll need to walk the ctor region - // to change calls to invokes. + // + // NOTE: Exception handling is NYI. When we support exceptions, we'll need + // to: + // 1. Walk the ctor region to change calls to invokes + // 2. Push a CallGuardAbort cleanup to call __cxa_guard_abort on exception + // 3. Clear the cleanup after successful initialization + // + // Per Itanium ABI 3.3.2, if initialization throws, __cxa_guard_abort must + // be called to reset the guard, allowing retry on subsequent calls. + assert(!cir::MissingFeatures::guardAbortOnException()); + auto &ctorRegion = globalOp.getCtorRegion(); assert(!ctorRegion.empty() && "This should never be empty here."); if (!ctorRegion.hasOneBlock()) @@ -1323,17 +1340,15 @@ void LoweringPreparePass::handleStaticLocal(GlobalOp globalOp, ctorRegion.getBlocks().clear(); if (threadsafe) { - // NOTE(CIR): CodeGen clears the above pushed CallGuardAbort here and thus - // the __guard_abort gets inserted. We'll have to figure out how to - // properly handle this when supporting static locals with exceptions. - // Call __cxa_guard_release. This cannot throw. emitNounwindRuntimeCall(builder, globalOp->getLoc(), getGuardReleaseFn(*astCtx, getContext(), theModule, builder, guardPtrTy), guardPtr); } else if (varDecl.isLocalVarDecl()) { - llvm_unreachable("NYI"); + // For non-threadsafe local variables, no guard release is needed + // as we use a simple byte guard that's already been set. + llvm_unreachable("NYI: non-threadsafe local static with exceptions"); } }; @@ -1358,13 +1373,13 @@ void LoweringPreparePass::handleStaticLocal(GlobalOp globalOp, if (threadsafe) { auto loc = globalOp->getLoc(); // Call __cxa_guard_acquire. - mlir::Value value = emitNounwindRuntimeCall( + mlir::Value acquireResult = emitNounwindRuntimeCall( builder, loc, getGuardAcquireFn(*astCtx, getContext(), theModule, builder, guardPtrTy), guardPtr); - auto isNotNull = builder.createIsNotNull(loc, value); + auto isNotNull = builder.createIsNotNull(loc, acquireResult); builder.create(globalOp.getLoc(), isNotNull, /*=withElseRegion*/ false, [&](mlir::OpBuilder &, mlir::Location) { @@ -1372,12 +1387,11 @@ void LoweringPreparePass::handleStaticLocal(GlobalOp globalOp, builder.createYield(getGlobalOp->getLoc()); }); - // NOTE(CIR): CodeGen pushes a CallGuardAbort cleanup here, but we are - // synthesizing the outcome via walking the CIR in the ctor region and - // changing calls to invokes. - } else if (!varDecl.isLocalVarDecl()) { - llvm_unreachable("NYI"); + // For non-local (namespace-scope) variables without thread-safety, + // we need to mark as initialized BEFORE running the initializer to + // handle recursive initialization correctly. + llvm_unreachable("NYI: non-local static without thread-safety"); } }; @@ -1425,11 +1439,11 @@ void LoweringPreparePass::handleStaticLocal(GlobalOp globalOp, mlir::Value constOne = builder.getConstAPSInt( getGlobalOp->getLoc(), llvm::APSInt(llvm::APInt(8, 1), /*isUnsigned=*/false)); - mlir::Value value = + mlir::Value guardValue = (!cir::MissingFeatures::useARMGuardVarABI() && !useInt8GuardVariable) ? builder.createAnd(load, constOne) : load; - mlir::Value needsInit = builder.createIsNull(globalOp.getLoc(), value); + mlir::Value needsInit = builder.createIsNull(globalOp.getLoc(), guardValue); builder.create(globalOp.getLoc(), needsInit, /*=withElseRegion*/ false, @@ -1480,7 +1494,7 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { return; for (auto &f : dynamicInitializers) { - // TODO: handle globals with a user-specified initialzation priority. + // TODO: handle globals with a user-specified initialization priority. // TODO: handle defaule priority more nicely. globalCtorList.emplace_back( f.getName(), diff --git a/clang/test/CIR/divergences/static-local-noundef.cpp b/clang/test/CIR/divergences/static-local-noundef.cpp index ea6cb7fd95c9..3799f2d69ad6 100644 --- a/clang/test/CIR/divergences/static-local-noundef.cpp +++ b/clang/test/CIR/divergences/static-local-noundef.cpp @@ -1,10 +1,9 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.cir.ll | FlileCheck %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s // -// XFAIL: * // // Static local initialization divergence: -// 1. Missing noundef attribute on function declaration and call +// Missing noundef attribute on function declaration and call // // CodeGen: // declare noundef i32 @_Z3fnAv() @@ -14,9 +13,8 @@ // declare i32 @_Z3fnAv() (missing noundef) // %8 = call i32 @_Z3fnAv() (missing noundef) -// DIFF-DAG: declare noundef i32 @_Z3fnAv() -// DIFF-DAG %call = call noundef i32 @_Z3fnAv() - +// CHECK-DAG: %{{.*}} = call noundef i32 @_Z3fnAv() +// CHECK-DAG: declare noundef i32 @_Z3fnAv() int fnA(); void foo() { From eb0c21924884dfb1d302119f2a0f8c2e3d097774 Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Wed, 3 Dec 2025 02:00:11 -0800 Subject: [PATCH 3/3] Update [ghstack-poisoned] --- clang/test/CIR/divergences/static-local-noundef.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/clang/test/CIR/divergences/static-local-noundef.cpp b/clang/test/CIR/divergences/static-local-noundef.cpp index 3799f2d69ad6..25cd1a309b19 100644 --- a/clang/test/CIR/divergences/static-local-noundef.cpp +++ b/clang/test/CIR/divergences/static-local-noundef.cpp @@ -1,6 +1,7 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - | FileCheck %s // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | FileCheck %s // +// XFAIL: * // // Static local initialization divergence: // Missing noundef attribute on function declaration and call