https://github.com/koparasy updated https://github.com/llvm/llvm-project/pull/215921
>From c4371f6db5f1f8afeb6b8bcf72bf02b307363f87 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Wed, 12 Aug 2026 16:23:27 -0700 Subject: [PATCH 1/3] [CIR] Materialize C++20 module init fn name as module attribute --- .../clang/CIR/Dialect/IR/CIRDialect.td | 3 +++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 20 +++++++++++++++++ .../Dialect/Transforms/LoweringPrepare.cpp | 13 ++++++++--- .../CIR/CodeGen/cxx20-module-initializer.cppm | 22 +++++++++++++++++++ 4 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 clang/test/CIR/CodeGen/cxx20-module-initializer.cppm diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td index de90fed45f170..c546e9535a50f 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td @@ -78,6 +78,9 @@ def CIR_Dialect : Dialect { static llvm::StringRef getArgAttrsAttrName() { return "arg_attrs"; } static llvm::StringRef getRecordLayoutsAttrName() { return "cir.record_layouts"; } 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/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index ae9cad0b7c30f..a5fc3a9bae7cb 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" @@ -3750,6 +3752,24 @@ 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 12d544fecbcc9..8ec9f442bc892 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -1866,9 +1866,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() >From 2a56c8598030bb845b0cfe3786fff8776afa613e Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Wed, 12 Aug 2026 16:23:28 -0700 Subject: [PATCH 2/3] [CIR] Emit StaticLocalInfoAttr for static-local guarded globals --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 46 +++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 13 +++++- clang/test/CIR/CodeGen/static-local-info.cpp | 35 ++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 clang/test/CIR/CodeGen/static-local-info.cpp diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 253421ab764ff..43c3b8d13de97 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -1935,6 +1935,52 @@ def CIR_ASTVarDeclAttr : CIR_AST<"VarDecl", "var.decl", [ ASTVarDeclInterface ]>; +//===----------------------------------------------------------------------===// +// StaticLocalInfoAttr +//===----------------------------------------------------------------------===// + +def CIR_StaticLocalInfoAttr + : CIR_Attr<"StaticLocalInfo", "static_local_info", + [ASTVarDeclInterface]> { + let summary = "AST-free cache of static-local VarDecl facts."; + let description = [{ + Materialized form of the facts `cir::ASTVarDeclInterface` exposes, so + that post-CIRGen passes (notably LoweringPrepare) can query them without + a live `clang::ASTContext`. Emitted by CIRGen for static-local guarded + globals in place of the AST-backed `ASTVarDeclAttr`. + + The TLS and template-specialization kinds are encoded as plain integers + matching the underlying `clang::VarDecl::TLSKind` and + `clang::TemplateSpecializationKind` enumerators. + }]; + + let parameters = (ins + "bool":$is_local_var_decl, + "uint32_t":$tls, + "bool":$is_inline, + "uint32_t":$tsk + ); + + let assemblyFormat = [{ + `<` struct($is_local_var_decl, $tls, $is_inline, $tsk) `>` + }]; + + let extraClassDeclaration = [{ + // Satisfy ASTVarDeclInterface by reading cached fields instead of an + // AST-backed VarDecl pointer. + bool isLocalVarDecl() const { return getIsLocalVarDecl(); } + clang::VarDecl::TLSKind getTLSKind() const { + return static_cast<clang::VarDecl::TLSKind>(getTls()); + } + bool isInline() const { return getIsInline(); } + clang::TemplateSpecializationKind getTemplateSpecializationKind() const { + return static_cast<clang::TemplateSpecializationKind>(getTsk()); + } + }]; + + let canHaveIllegalCXXABIType = 0; +} + //===----------------------------------------------------------------------===// // AnnotationAttr //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index 812ecc1016dcc..50f0b5ac3e003 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -265,7 +265,18 @@ void CIRGenModule::emitCXXSpecialVarDeclInit(const VarDecl *varDecl, // expects "this" in the "generic" address space. assert(!cir::MissingFeatures::addressSpace()); - addr.setAstAttr(cir::ASTVarDeclAttr::get(&getMLIRContext(), varDecl)); + // LoweringPrepare reads VarDecl facts back through ASTVarDeclInterface, but + // only for static-local guarded globals. For those, emit the materialized + // StaticLocalInfoAttr so the facts survive without a live ASTContext (e.g. + // serialized CIR in split-compilation flows). Other globals keep the + // AST-backed attribute; their $ast is never queried after CIRGen. + if (addr.getStaticLocalGuard().has_value()) + addr.setAstAttr(cir::StaticLocalInfoAttr::get( + &getMLIRContext(), varDecl->isLocalVarDecl(), + static_cast<uint32_t>(varDecl->getTLSKind()), varDecl->isInline(), + static_cast<uint32_t>(varDecl->getTemplateSpecializationKind()))); + else + addr.setAstAttr(cir::ASTVarDeclAttr::get(&getMLIRContext(), varDecl)); if (!ty->isReferenceType()) { assert(!cir::MissingFeatures::openMP()); 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..87d198d1111f0 --- /dev/null +++ b/clang/test/CIR/CodeGen/static-local-info.cpp @@ -0,0 +1,35 @@ +// 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 materialized #cir.static_local_info attribute, so the facts +// survive without a live ASTContext. The attribute is emitted directly at +// CIRGen time; there is no separate materialization pass and no AST-backed +// #cir.var.decl placeholder is left on these globals. + +struct HasCtor { + HasCtor(); + int x; +}; + +int regular() { + static HasCtor s; + return s.x; +} + +int tls() { + static thread_local HasCtor s; + return s.x; +} + +// A static local is always a local var decl; the guarded global therefore +// carries is_local_var_decl = true and never the AST-backed placeholder. +// CHECK-NOT: #cir.var.decl + +// The thread_local static local materializes a non-default TLS kind. +// CHECK: @_ZZ3tlsvE1s +// CHECK-SAME: ast = #cir.static_local_info<is_local_var_decl = true, tls = 2, is_inline = false, tsk = 0> + +// CHECK: @_ZZ7regularvE1s +// CHECK-SAME: ast = #cir.static_local_info<is_local_var_decl = true, tls = 0, is_inline = false, tsk = 0> >From f20a01eb7b04b2bfef04ff5b6ec8adaf3e98565c Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Wed, 12 Aug 2026 16:44:12 -0700 Subject: [PATCH 3/3] Format --- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index a5fc3a9bae7cb..989c0176390b7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -3764,9 +3764,8 @@ void CIRGenModule::release() { llvm::raw_svector_ostream out(fnName); cast<clang::ItaniumMangleContext>(getCXXABI().getMangleContext()) .mangleModuleInitializer(primary, out); - theModule->setAttr( - cir::CIRDialect::getCXXModuleInitFnNameAttrName(), - builder.getStringAttr(fnName)); + theModule->setAttr(cir::CIRDialect::getCXXModuleInitFnNameAttrName(), + builder.getStringAttr(fnName)); } } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
