Author: Konstantinos Parasyris Date: 2026-08-25T14:53:30-07:00 New Revision: 554dd47f288782e6e37f0c903e5851bf5d8a9228
URL: https://github.com/llvm/llvm-project/commit/554dd47f288782e6e37f0c903e5851bf5d8a9228 DIFF: https://github.com/llvm/llvm-project/commit/554dd47f288782e6e37f0c903e5851bf5d8a9228.diff LOG: [CIR] Add cir.cxx_module_init_fn_name and cir.static_local_info attributes (#215921) Adds two CIR attributes that record specific declaration facts, so passes that consume them don't have to query the AST for those particular facts. Added: clang/test/CIR/CodeGen/cxx20-module-initializer.cppm clang/test/CIR/CodeGen/static-local-info.cpp clang/test/CIR/IR/static-local-info.cir Modified: clang/include/clang/CIR/Dialect/IR/CIRAttrs.td clang/include/clang/CIR/Dialect/IR/CIRDialect.td clang/include/clang/CIR/Dialect/IR/CIROps.td clang/lib/CIR/CodeGen/CIRGenCXX.cpp clang/lib/CIR/CodeGen/CIRGenModule.cpp clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index ff66eead4a7db..4c9b41b55ee1b 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -2011,6 +2011,91 @@ def CIR_ASTVarDeclAttr : CIR_AST<"VarDecl", "var.decl", [ ASTVarDeclInterface ]>; +//===----------------------------------------------------------------------===// +// StaticLocalInfoAttr +//===----------------------------------------------------------------------===// + +// CIR-native thread-local storage kind. Cases mirror clang::VarDecl::TLSKind +// so CIRGen can map between them, but CIR does not depend on the underlying +// clang enumerator values. +def CIR_TLSKind : CIR_I32EnumAttr<"TLSKind", "thread-local storage kind", [ + I32EnumAttrCase<"None", 0, "none">, // clang::VarDecl::TLS_None + I32EnumAttrCase<"Static", 1, "static">, // clang::VarDecl::TLS_Static + I32EnumAttrCase<"Dynamic", 2, "dynamic"> // clang::VarDecl::TLS_Dynamic +]> { + let genSpecializedAttr = 0; +} + +// CIR-native template-specialization kind. Cases mirror +// clang::TemplateSpecializationKind. +def CIR_TemplateSpecializationKind + : CIR_I32EnumAttr<"TemplateSpecializationKind", + "template specialization kind", [ + // clang::TSK_Undeclared + I32EnumAttrCase<"Undeclared", 0, "undeclared">, + // clang::TSK_ImplicitInstantiation + I32EnumAttrCase<"ImplicitInstantiation", 1, "implicit_instantiation">, + // clang::TSK_ExplicitSpecialization + I32EnumAttrCase<"ExplicitSpecialization", 2, "explicit_specialization">, + // clang::TSK_ExplicitInstantiationDeclaration + I32EnumAttrCase<"ExplicitInstantiationDeclaration", 3, + "explicit_instantiation_declaration">, + // clang::TSK_ExplicitInstantiationDefinition + I32EnumAttrCase<"ExplicitInstantiationDefinition", 4, + "explicit_instantiation_definition"> +]> { + let genSpecializedAttr = 0; +} + +def CIR_StaticLocalInfoAttr + : CIR_Attr<"StaticLocalInfo", "static_local_info"> { + let summary = "Static-local variable facts needed by later lowering"; + let description = [{ + Holds the subset of a static-local variable's declaration facts that + post-CIRGen lowering needs, as plain data (no `clang::VarDecl` pointer). + + - `local`: whether the variable is a block-scope (local) declaration. + Mirrors `clang::VarDecl::isLocalVarDecl()`. The namespace-scope inline + variables that also reach guarded initialization are not local. + + - `tls`: the thread-local storage kind (`none`, `static`, or `dynamic`). + + - `is_inline`: whether the variable is inline. Mirrors + `clang::VarDecl::isInline()`. + + - `tsk`: the template-specialization kind (`undeclared`, + `implicit_instantiation`, `explicit_specialization`, + `explicit_instantiation_declaration`, or + `explicit_instantiation_definition`). + + Example: + ``` + cir.global ... @x = ... + {static_local_info = #cir.static_local_info< + local = true, tls = none, is_inline = false, tsk = undeclared>} + ``` + + Because it stores plain data rather than a `clang::VarDecl` pointer, it + round-trips through textual CIR and stays valid once the AST is gone. + This is what lets LoweringPrepare consume these facts without a live + `clang::ASTContext`; the `$ast` `ASTVarDeclAttr` remains a separate, + live-AST handle for consumers that need arbitrary AST properties. + }]; + + let parameters = (ins + "bool":$local, + EnumParameter<CIR_TLSKind>:$tls, + "bool":$is_inline, + EnumParameter<CIR_TemplateSpecializationKind>:$tsk + ); + + let assemblyFormat = [{ + `<` struct($local, $tls, $is_inline, $tsk) `>` + }]; + + let canHaveIllegalCXXABIType = 0; +} + //===----------------------------------------------------------------------===// // AnnotationAttr //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td index d974cb1fa4544..c0ece1f5ee1df 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td @@ -81,6 +81,9 @@ def CIR_Dialect : Dialect { static llvm::StringRef getTuneCPUAttrName() { return "cir.tune-cpu"; } static llvm::StringRef getTargetFeaturesAttrName() { return "cir.target-features"; } static llvm::StringRef getCUDABinaryHandleAttrName() { return "cir.cu.binary_handle"; } + // Mangled symbol name of the C++20 named-module initializer function, + // precomputed by CIRGen so later passes don't need a live ASTContext. + static llvm::StringRef getCXXModuleInitFnNameAttrName() { return "cir.cxx_module_init_fn_name"; } static llvm::StringRef getMustTailAttrName() { return "musttail"; } static llvm::StringRef getCatchCopyThunkAttrName() { return "cir.eh.catch_copy_thunk"; } diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index a52c37e4860ce..6b57604770420 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -3388,6 +3388,7 @@ def CIR_GlobalOp : CIR_Op<"global", [ OptionalAttr<CIR_StaticLocalGuardAttr>:$static_local_guard, OptionalAttr<I64Attr>:$alignment, OptionalAttr<ASTVarDeclInterface>:$ast, + OptionalAttr<CIR_StaticLocalInfoAttr>:$static_local_info, OptionalAttr<StrAttr>:$section, OptionalAttr<CIR_AnnotationArrayAttr>:$annotations, OptionalAttr<FlatSymbolRefAttr>:$aliasee, diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index 925df05de91a5..e05074699bd6e 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -237,6 +237,37 @@ cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl gd) { // initialization for each global there. In CIR, we attach a ctor // region to the global variable and insert the initialization code // into the ctor region. This will be moved into the +// Map clang's VarDecl::TLSKind onto the CIR-native enum. +static cir::TLSKind getCIRTLSKind(clang::VarDecl::TLSKind kind) { + switch (kind) { + case clang::VarDecl::TLS_None: + return cir::TLSKind::None; + case clang::VarDecl::TLS_Static: + return cir::TLSKind::Static; + case clang::VarDecl::TLS_Dynamic: + return cir::TLSKind::Dynamic; + } + llvm_unreachable("unknown TLSKind"); +} + +// Map clang's TemplateSpecializationKind onto the CIR-native enum. +static cir::TemplateSpecializationKind +getCIRTemplateSpecializationKind(clang::TemplateSpecializationKind kind) { + switch (kind) { + case clang::TSK_Undeclared: + return cir::TemplateSpecializationKind::Undeclared; + case clang::TSK_ImplicitInstantiation: + return cir::TemplateSpecializationKind::ImplicitInstantiation; + case clang::TSK_ExplicitSpecialization: + return cir::TemplateSpecializationKind::ExplicitSpecialization; + case clang::TSK_ExplicitInstantiationDeclaration: + return cir::TemplateSpecializationKind::ExplicitInstantiationDeclaration; + case clang::TSK_ExplicitInstantiationDefinition: + return cir::TemplateSpecializationKind::ExplicitInstantiationDefinition; + } + llvm_unreachable("unknown clang::TemplateSpecializationKind"); +} + // __cxx_global_var_init function during the LoweringPrepare pass. void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl, cir::GlobalOp addr, @@ -270,8 +301,20 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl, // expects "this" in the "generic" address space. assert(!cir::MissingFeatures::addressSpace()); + // Attach the AST handle for consumers that need arbitrary AST properties. addr.setAstAttr(cir::ASTVarDeclAttr::get(&getMLIRContext(), varDecl)); + // For static-local guarded globals, also materialize the specific facts + // LoweringPrepare needs into a serializable attribute, so that lowering can + // run without a live ASTContext (e.g. on serialized CIR in split-compilation + // flows). This is orthogonal to the AST handle above. + if (addr.getStaticLocalGuard().has_value()) + addr.setStaticLocalInfoAttr(cir::StaticLocalInfoAttr::get( + &getMLIRContext(), varDecl->isLocalVarDecl(), + getCIRTLSKind(varDecl->getTLSKind()), varDecl->isInline(), + getCIRTemplateSpecializationKind( + varDecl->getTemplateSpecializationKind()))); + if (!ty->isReferenceType()) { assert(!cir::MissingFeatures::openMP()); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 6c49afe243606..9db6edbc9b519 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -24,9 +24,11 @@ #include "clang/AST/DeclBase.h" #include "clang/AST/DeclOpenACC.h" #include "clang/AST/GlobalDecl.h" +#include "clang/AST/Mangle.h" #include "clang/AST/RecordLayout.h" #include "clang/AST/StmtOpenMP.h" #include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Basic/Module.h" #include "clang/Basic/SourceManager.h" #include "clang/CIR/Dialect/IR/CIRAttrs.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" @@ -3889,6 +3891,23 @@ void CIRGenModule::release() { emitLLVMUsed(); + // Precompute the mangled C++20 named-module initializer function name and + // stash it on the ModuleOp so LoweringPrepare (which may run without a live + // ASTContext in split-compilation flows) can read it back as an attribute. + if (langOpts.CPlusPlusModules && + getCXXABI().getMangleContext().getKind() == + clang::ItaniumMangleContext::MK_Itanium) { + if (clang::Module *primary = astContext.getCurrentNamedModule(); + primary && !primary->isModuleImplementation()) { + llvm::SmallString<256> fnName; + llvm::raw_svector_ostream out(fnName); + cast<clang::ItaniumMangleContext>(getCXXABI().getMangleContext()) + .mangleModuleInitializer(primary, out); + theModule->setAttr(cir::CIRDialect::getCXXModuleInitFnNameAttrName(), + builder.getStringAttr(fnName)); + } + } + // Classic codegen calls `checkAliases` here to validate any alias // definitions emitted during codegen. assert(!cir::MissingFeatures::checkAliases()); diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 5ede978c4d671..f4ea135032493 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -425,9 +425,8 @@ struct LoweringPreparePass /// following OG's ItaniumCXXABI::EmitGuardedInit skeleton. void emitCXXGuardedInitIf(CIRBaseBuilderTy &builder, cir::GlobalOp globalOp, mlir::Region &ctorRegion, mlir::Region &dtorRegion, - cir::ASTVarDeclInterface varDecl, - mlir::Value guardPtr, cir::PointerType guardPtrTy, - bool threadsafe) { + bool isLocalVarDecl, mlir::Value guardPtr, + cir::PointerType guardPtrTy, bool threadsafe) { auto loc = globalOp->getLoc(); // The semantics of dynamic initialization of variables with static or @@ -525,7 +524,7 @@ struct LoweringPreparePass mlir::ValueRange{guardPtr}); builder.createYield(loc); - } else if (!varDecl.isLocalVarDecl()) { + } else if (!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. @@ -1353,9 +1352,12 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp, cir::LocalInitOp localInitOp) { CIRBaseBuilderTy builder(getContext()); - std::optional<cir::ASTVarDeclInterface> astOption = globalOp.getAst(); - assert(astOption.has_value()); - cir::ASTVarDeclInterface varDecl = astOption.value(); + // Static-local facts are materialized into a serializable attribute by + // CIRGen, so this pass does not need a live ASTContext to read them. + std::optional<cir::StaticLocalInfoAttr> infoOption = + globalOp.getStaticLocalInfo(); + assert(infoOption.has_value()); + cir::StaticLocalInfoAttr info = infoOption.value(); builder.setInsertionPointAfter(localInitOp); mlir::Block *localInitBlock = builder.getInsertionBlock(); @@ -1369,9 +1371,13 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp, // 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()); + cir::TemplateSpecializationKind tsk = info.getTsk(); + bool isTemplateInstantiation = + tsk == cir::TemplateSpecializationKind::ImplicitInstantiation || + tsk == + cir::TemplateSpecializationKind::ExplicitInstantiationDeclaration || + tsk == cir::TemplateSpecializationKind::ExplicitInstantiationDefinition; + bool nonTemplateInline = info.getIsInline() && !isTemplateInstantiation; // Inline namespace-scope variables require guarded initialization in a // __cxx_global_var_init function. This is not yet implemented. @@ -1385,8 +1391,8 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp, // 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(); + (info.getLocal() || nonTemplateInline) && + info.getTls() == cir::TLSKind::None; // 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. @@ -1395,7 +1401,7 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp, // Create the guard variable if we don't already have it. cir::GlobalOp guard = getOrCreateStaticLocalDeclGuardAddress( builder, globalOp, globalOp.getStaticLocalGuard()->getName().getValue(), - varDecl.isLocalVarDecl(), useInt8GuardVariable); + info.getLocal(), useInt8GuardVariable); if (!guard) { // Error was already emitted, just restore the terminator and return. localInitBlock->push_back(ret); @@ -1483,10 +1489,10 @@ void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp, cir::IfOp::create( builder, globalOp.getLoc(), needsInit, /*withElseRegion=*/false, [&](mlir::OpBuilder &, mlir::Location) { - emitCXXGuardedInitIf(builder, globalOp, localInitOp.getCtorRegion(), - localInitOp.getDtorRegion(), varDecl, guardPtr, - builder.getPointerTo(guard.getSymType()), - threadsafe); + emitCXXGuardedInitIf( + builder, globalOp, localInitOp.getCtorRegion(), + localInitOp.getDtorRegion(), info.getLocal(), guardPtr, + builder.getPointerTo(guard.getSymType()), threadsafe); }); } else { // Threadsafe statics without inline atomics - call __cxa_guard_acquire @@ -1967,9 +1973,16 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { // and makes sure these symbols appear lexicographically behind the symbols // with priority (TBD). Module implementation units behave the same // way as a non-modular TU with imports. - // TODO: check CXX20ModuleInits - if (astCtx->getCurrentNamedModule() && - !astCtx->getCurrentNamedModule()->isModuleImplementation()) { + // The C++20 named-module init function name is precomputed by CIRGen and + // stored as a module-level attribute, so this pass does not need a live + // ASTContext in split-compilation flows. Fall back to the AST-based path + // only when the attribute is absent (e.g. tests that bypass CIRGen). + if (auto fnNameAttr = mlirModule->getAttrOfType<mlir::StringAttr>( + cir::CIRDialect::getCXXModuleInitFnNameAttrName())) { + fnName += fnNameAttr.getValue(); + linkage = cir::GlobalLinkageKind::ExternalLinkage; + } else if (astCtx && astCtx->getCurrentNamedModule() && + !astCtx->getCurrentNamedModule()->isModuleImplementation()) { llvm::raw_svector_ostream out(fnName); std::unique_ptr<clang::MangleContext> mangleCtx( astCtx->createMangleContext()); diff --git a/clang/test/CIR/CodeGen/cxx20-module-initializer.cppm b/clang/test/CIR/CodeGen/cxx20-module-initializer.cppm new file mode 100644 index 0000000000000..2122a5e97636d --- /dev/null +++ b/clang/test/CIR/CodeGen/cxx20-module-initializer.cppm @@ -0,0 +1,22 @@ +// RUN: %clang_cc1 -std=c++20 -triple %itanium_abi_triple -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR + +// CIRGen precomputes the mangled C++20 named-module initializer function +// name and stores it as a module-level attribute so LoweringPrepare can +// build the initializer without a live ASTContext after split-compilation. +// The dynamic initializer below forces LoweringPrepare to actually emit that +// initializer function, which must have external linkage for a named-module +// interface unit. + +export module A; + +int foo(); +int x = foo(); + +// CIR: module +// CIR-SAME: cir.cxx_module_init_fn_name = "_ZGIW1A" + +// The initializer for a named-module interface unit has external linkage. +// (Internal linkage would render as "cir.func internal private", so matching +// "cir.func private" immediately after the name asserts external linkage.) +// CIR: cir.func private @_ZGIW1A() diff --git a/clang/test/CIR/CodeGen/static-local-info.cpp b/clang/test/CIR/CodeGen/static-local-info.cpp new file mode 100644 index 0000000000000..2345703d8a875 --- /dev/null +++ b/clang/test/CIR/CodeGen/static-local-info.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir \ +// RUN: -emit-cir %s -o - | FileCheck %s + +// CIRGen attaches the VarDecl facts LoweringPrepare needs (isLocalVarDecl, +// TLSKind, isInline, TemplateSpecializationKind) to static-local guarded +// globals as a #cir.static_local_info attribute, so the facts survive without +// a live ASTContext. This is orthogonal to the #cir.var.decl AST handle, which +// is still attached for consumers that need arbitrary AST properties. + +struct HasCtor { + HasCtor(); + int x; +}; + +int regular() { + static HasCtor s; + return s.x; +} + +int tls() { + static thread_local HasCtor s; + return s.x; +} + +// The thread_local static local materializes a non-default TLS kind, alongside +// the retained AST handle. +// CHECK: @_ZZ3tlsvE1s +// CHECK-SAME: ast = #cir.var.decl.ast +// CHECK-SAME: static_local_info = #cir.static_local_info<local = true, tls = dynamic, is_inline = false, tsk = undeclared> + +// CHECK: @_ZZ7regularvE1s +// CHECK-SAME: ast = #cir.var.decl.ast +// CHECK-SAME: static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared> diff --git a/clang/test/CIR/IR/static-local-info.cir b/clang/test/CIR/IR/static-local-info.cir new file mode 100644 index 0000000000000..b5c8f8919224a --- /dev/null +++ b/clang/test/CIR/IR/static-local-info.cir @@ -0,0 +1,21 @@ +// RUN: cir-opt %s --verify-roundtrip | FileCheck %s + +// #cir.static_local_info holds plain data (no AST pointer), so it parses and +// prints without a live ASTContext. Check that each field round-trips, +// including the named tls / tsk enum keywords at non-default values. + +!s32i = !cir.int<s, 32> + +module { + cir.global "private" internal @regular = #cir.int<0> : !s32i + {static_local_info = #cir.static_local_info< + local = true, tls = none, is_inline = false, tsk = undeclared>} + // CHECK: @regular + // CHECK-SAME: static_local_info = #cir.static_local_info<local = true, tls = none, is_inline = false, tsk = undeclared> + + cir.global "private" internal @tls = #cir.int<0> : !s32i + {static_local_info = #cir.static_local_info< + local = true, tls = dynamic, is_inline = true, tsk = implicit_instantiation>} + // CHECK: @tls + // CHECK-SAME: static_local_info = #cir.static_local_info<local = true, tls = dynamic, is_inline = true, tsk = implicit_instantiation> +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
