https://github.com/keryell updated https://github.com/llvm/llvm-project/pull/229648
>From 35e591feadfcd121d6a3fc9920bcaa5a68eba60c Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Tue, 6 Oct 2026 18:44:34 -0700 Subject: [PATCH 01/11] [CIR] Handle module imports in emitTopLevelDecl An ImportDecl reached the "declaration of kind" NYI error, so a translation unit importing a Clang header module or a C++20 named module failed under -fclangir. Mirror classic CodeGen's handling of Decl::Import: record the imported module once; for a C++20 named module under the Itanium ABI stop there, since the module has its own initializer function; otherwise emit the module initializers of the header module and of its implicit submodules into the importing translation unit, each module once. The CXX20ModuleInits flag of classic CodeGen becomes the member cxx20ModuleInits, computed once in the constructor; release() uses it in place of its inline copy of the same check when naming the named-module initializer. Three pieces of classic CodeGen stay missing features: autolink metadata for imported modules, debug info for import declarations, and the call from the importing unit's global init function to a named module's initializer. The test covers a header module imported twice and a named module import, checking the CIR and the LLVM IR against classic CodeGen. Assisted-by: Claude Fable 5.1 (Claude Code) --- clang/include/clang/CIR/MissingFeatures.h | 2 + clang/lib/CIR/CodeGen/CIRGenModule.cpp | 64 ++++++++++++++- clang/lib/CIR/CodeGen/CIRGenModule.h | 12 +++ clang/test/CIR/CodeGen/module-import.cpp | 95 +++++++++++++++++++++++ 4 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 clang/test/CIR/CodeGen/module-import.cpp diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 68e15b84cea16..7c58080e49b3f 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -129,6 +129,8 @@ struct MissingFeatures { // Various handling of deferred processing in CIRGenModule. static bool cgmRelease() { return false; } static bool checkAliases() { return false; } + static bool emitModuleLinkOptions() { return false; } + static bool emitCXXModuleInitFunc() { return false; } static bool shouldSkipAliasEmission() { return false; } // CXXABI diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 1b17ad636829d..01f0acaf719da 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -93,6 +93,11 @@ CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext, diags(diags), target(astContext.getTargetInfo()), abi(createCXXABI(*this)), genTypes(*this), vtables(*this) { + // The name of a C++20 module initializer has only an Itanium mangling. + cxx20ModuleInits = + langOpts.CPlusPlusModules && + getCXXABI().getMangleContext().getKind() == MangleContext::MK_Itanium; + // Initialize cached types voidTy = cir::VoidType::get(&getMLIRContext()); voidPtrTy = cir::PointerType::get(voidTy); @@ -2549,6 +2554,58 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) { emitGlobal(fd); break; } + + case Decl::Import: { + auto *import = cast<ImportDecl>(decl); + clang::Module *mod = import->getImportedModule(); + + // If we've already imported this module, we're done. + if (!importedModules.insert(mod)) + break; + + assert(!cir::MissingFeatures::generateDebugInfo()); + + // A C++20 named module has its own initializer function, so its + // initializers are not emitted here. Classic CodeGen's global init + // function of the importing translation unit calls that initializer, + // which in turn covers the modules it imports itself; that call is not + // emitted yet. + if (cxx20ModuleInits && mod->isNamedModule()) { + assert(!cir::MissingFeatures::emitCXXModuleInitFunc()); + break; + } + + // For clang C++ module map modules the initializers for sub-modules are + // emitted here. + + // Find all of the submodules and emit the module initializers. + llvm::SmallPtrSet<clang::Module *, 16> visited; + SmallVector<clang::Module *, 16> stack; + visited.insert(mod); + stack.push_back(mod); + + while (!stack.empty()) { + clang::Module *cur = stack.pop_back_val(); + if (!emittedModuleInitializers.insert(cur).second) + continue; + + for (Decl *initializer : astContext.getModuleInitializers(cur)) + emitTopLevelDecl(initializer); + + // Visit the submodules of this module. + for (clang::Module *submodule : cur->submodules()) { + // Skip explicit children; they need to be explicitly imported to emit + // the initializers. + if (submodule->IsExplicit) + continue; + + if (visited.insert(submodule).second) + stack.push_back(submodule); + } + } + break; + } + case Decl::Export: emitDeclContext(cast<ExportDecl>(decl)); break; @@ -3968,9 +4025,7 @@ void CIRGenModule::release() { // Itanium mangler because no Microsoft mangling for them has been settled // on yet. Non-Itanium named modules fall back to `_GLOBAL__sub_I_` exactly // as they do in classic codegen. - if (langOpts.CPlusPlusModules && - getCXXABI().getMangleContext().getKind() == - clang::ItaniumMangleContext::MK_Itanium) { + if (cxx20ModuleInits) { if (clang::Module *primary = astContext.getCurrentNamedModule(); primary && !primary->isModuleImplementation()) { llvm::SmallString<256> fnName; @@ -3982,6 +4037,9 @@ void CIRGenModule::release() { } } + // Autolink metadata for the imported modules is not emitted yet. + assert(!cir::MissingFeatures::emitModuleLinkOptions()); + // Classic codegen calls `checkAliases` here to validate any alias // definitions emitted during codegen. assert(!cir::MissingFeatures::checkAliases()); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 05ba8b5ee212e..c8d697796e2d1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -36,6 +36,8 @@ #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" #include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallPtrSet.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/TargetParser/Triple.h" @@ -113,6 +115,16 @@ class CIRGenModule : public CIRGenTypeCache { /// Accumulated record layout entries, materialized in release(). llvm::SmallVector<mlir::NamedAttribute> recordLayoutEntries; + /// Whether C++20 named modules get their own module initializer function, + /// which requires the Itanium mangling of the initializer name. + bool cxx20ModuleInits = false; + + /// The complete set of modules that has been imported. + llvm::SetVector<clang::Module *> importedModules; + + /// The set of modules for which the module initializers have been emitted. + llvm::SmallPtrSet<clang::Module *, 16> emittedModuleInitializers; + llvm::DenseSet<clang::GlobalDecl> diagnosedConflictingDefinitions; /// ------- diff --git a/clang/test/CIR/CodeGen/module-import.cpp b/clang/test/CIR/CodeGen/module-import.cpp new file mode 100644 index 0000000000000..dbac152b7ba12 --- /dev/null +++ b/clang/test/CIR/CodeGen/module-import.cpp @@ -0,0 +1,95 @@ +// RUN: rm -rf %t +// RUN: split-file %s %t +// +// Importing a clang header module emits the initializers of the module and of +// its implicit submodules into the importing translation unit, once. +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -emit-cir %t/header-module.cpp -o - | FileCheck %t/header-module.cpp --check-prefix=CIR --implicit-check-not=c_global +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -emit-llvm %t/header-module.cpp -o - | FileCheck %t/header-module.cpp --check-prefix=LLVM --implicit-check-not=c_global +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -emit-llvm %t/header-module.cpp -o - | FileCheck %t/header-module.cpp --check-prefix=LLVM --implicit-check-not=c_global +// +// Importing a C++20 named module only records the module: its initializers +// stay in its own translation unit. The call to that module's initializer from +// the importing unit's global init function is not emitted yet. +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-module-interface %t/a.cppm -o %t/a.pcm +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-cir %t/named-module.cpp -o - | FileCheck %t/named-module.cpp --check-prefix=CIR --implicit-check-not=__cxx_global_var_init +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-llvm %t/named-module.cpp -o - | FileCheck %t/named-module.cpp --check-prefix=LLVM --implicit-check-not=__cxx_global_var_init + +//--- module.modulemap +module A { + header "a.h" + module B { header "b.h" } + explicit module C { header "c.h" } + export * +} + +//--- a.h +int side_effect(); +int a_global = side_effect(); +inline int a_func(int x) { return x + 1; } + +//--- b.h +int side_effect(); +int b_global = side_effect(); + +//--- c.h +int side_effect(); +int c_global = side_effect(); + +//--- header-module.cpp +// The second include of a modular header imports the module again and must not +// redefine its globals. +#include "a.h" +#include "a.h" + +int use() { return a_func(a_global); } + +// CIR: cir.global external {{.*}}@a_global = #cir.int<0> : !s32i +// CIR: cir.func internal private @__cxx_global_var_init() +// CIR: cir.get_global @a_global : !cir.ptr<!s32i> +// CIR: cir.call @_Z11side_effectv() +// CIR: cir.global external {{.*}}@b_global = #cir.int<0> : !s32i +// CIR: cir.func internal private @__cxx_global_var_init.1() +// CIR: cir.get_global @b_global : !cir.ptr<!s32i> +// CIR: cir.call @_Z11side_effectv() +// CIR-DAG: cir.func private @_Z11side_effectv() +// CIR-DAG: cir.func {{.*}}@_Z3usev() +// CIR-DAG: cir.func {{.*}}linkonce_odr @_Z6a_funci +// CIR: cir.func internal private @_GLOBAL__sub_I_header_module.cpp() +// CIR: cir.call @__cxx_global_var_init() +// CIR: cir.call @__cxx_global_var_init.1() + +// LLVM: @a_global = global i32 0 +// LLVM: @b_global = global i32 0 +// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_header_module.cpp, ptr null }] +// LLVM: define internal void @__cxx_global_var_init() +// LLVM: call noundef i32 @_Z11side_effectv() +// LLVM: store i32 %{{.*}}, ptr @a_global +// LLVM: define internal void @__cxx_global_var_init.1() +// LLVM: call noundef i32 @_Z11side_effectv() +// LLVM: store i32 %{{.*}}, ptr @b_global +// LLVM: define internal void @_GLOBAL__sub_I_header_module.cpp() +// LLVM: call void @__cxx_global_var_init() +// LLVM: call void @__cxx_global_var_init.1() + +//--- a.cppm +export module a; +int side_effect(); +export int a_val = side_effect(); +export inline int a_func(int x) { return x + 1; } + +//--- named-module.cpp +import a; + +int use() { return a_func(a_val); } + +// CIR: cir.global "private" external {{.*}}@_ZW1a5a_val : !s32i +// CIR: cir.func {{.*}}@_Z3usev() +// CIR: cir.get_global @_ZW1a5a_val : !cir.ptr<!s32i> +// CIR: cir.call @_ZW1a6a_funci +// CIR: cir.func {{.*}}linkonce_odr @_ZW1a6a_funci + +// LLVM: @_ZW1a5a_val = external global i32 +// LLVM: define dso_local noundef i32 @_Z3usev() +// LLVM: load i32, ptr @_ZW1a5a_val +// LLVM: call noundef i32 @_ZW1a6a_funci +// LLVM: define linkonce_odr noundef i32 @_ZW1a6a_funci >From 9717f041b041c7d46b200859bb8d84cd46e493cc Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Tue, 6 Oct 2026 21:36:10 -0700 Subject: [PATCH 02/11] [CIR] Call the initializers of imported C++20 named modules A translation unit importing a C++20 named module with a dynamic initializer never ran it: the module's initializer function existed, but nothing called it from the importing unit, where classic CodeGen's global init function calls it first. CIRGen now records, as the module attribute cir.cxx_module_imported_inits, the mangled initializer names of the imported modules to call, with classic CodeGen's selection: for a module interface or partition unit the modules it exports, imports, or imports in its global or private module fragment, otherwise the modules the unit imported; header-like modules and named modules that need no initializer are skipped. LoweringPrepare declares those functions and calls them before the unit's own initializers. The initializer of an interface unit runs its calls once behind an internal guard byte, <name>__in_chrg, since the global ctor entry and every importing unit call it; and it is emitted even with nothing to run, as classic CodeGen does, since importing units may call it. The guard check of the TLS init function is shared for this, with the thread-local access made a parameter. The test checks an importing unit, an interface unit with a dynamic initializer, one whose only initializer is an imported module's, and one with nothing to run, comparing the LLVM IR with classic CodeGen. Assisted-by: Claude Fable 5.1 (Claude Code) --- .../clang/CIR/Dialect/IR/CIRDialect.td | 4 + clang/include/clang/CIR/MissingFeatures.h | 1 - clang/lib/CIR/CodeGen/CIRGenModule.cpp | 59 ++++++--- clang/lib/CIR/CodeGen/CIRGenModule.h | 4 + .../Dialect/Transforms/LoweringPrepare.cpp | 116 +++++++++++++----- clang/test/CIR/CodeGen/module-import.cpp | 78 +++++++++++- 6 files changed, 215 insertions(+), 47 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td index 70647fa194329..c2736b73df28e 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td @@ -106,6 +106,10 @@ def CIR_Dialect : Dialect { // 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"; } + // Mangled names of the initializer functions of the C++20 named modules + // this translation unit imports, in the order the global init function + // calls them, precomputed by CIRGen for the same reason. + static llvm::StringRef getCXXModuleImportedInitsAttrName() { return "cir.cxx_module_imported_inits"; } // LangOptions facts that post-CIRGen lowering consumes, serialized by // CIRGen so a reloaded .cir module is self-describing without a live // clang::LangOptions. See #cir.lowering_lang_options. diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 7c58080e49b3f..4c59a27bdf7b9 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -130,7 +130,6 @@ struct MissingFeatures { static bool cgmRelease() { return false; } static bool checkAliases() { return false; } static bool emitModuleLinkOptions() { return false; } - static bool emitCXXModuleInitFunc() { return false; } static bool shouldSkipAliasEmission() { return false; } // CXXABI diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 01f0acaf719da..af7d678ceab25 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2566,14 +2566,11 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) { assert(!cir::MissingFeatures::generateDebugInfo()); // A C++20 named module has its own initializer function, so its - // initializers are not emitted here. Classic CodeGen's global init - // function of the importing translation unit calls that initializer, - // which in turn covers the modules it imports itself; that call is not - // emitted yet. - if (cxx20ModuleInits && mod->isNamedModule()) { - assert(!cir::MissingFeatures::emitCXXModuleInitFunc()); + // initializers are not emitted here. The global init function of this + // translation unit calls that initializer, which in turn covers the + // modules it imports itself; release() records the modules to call. + if (cxx20ModuleInits && mod->isNamedModule()) break; - } // For clang C++ module map modules the initializers for sub-modules are // emitted here. @@ -3963,6 +3960,14 @@ CIRGenModule::getMLIRVisibilityFromCIRLinkage(cir::GlobalLinkageKind glk) { llvm_unreachable("linkage should be handled above!"); } +std::string CIRGenModule::getModuleInitializerName(clang::Module *mod) { + llvm::SmallString<256> name; + llvm::raw_svector_ostream out(name); + cast<clang::ItaniumMangleContext>(getCXXABI().getMangleContext()) + .mangleModuleInitializer(mod, out); + return std::string(name); +} + void CIRGenModule::release() { emitDeferred(); emitVTablesOpportunistically(); @@ -4026,15 +4031,39 @@ void CIRGenModule::release() { // on yet. Non-Itanium named modules fall back to `_GLOBAL__sub_I_` exactly // as they do in classic codegen. if (cxx20ModuleInits) { - 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)); + clang::Module *primary = astContext.getCurrentNamedModule(); + bool isInterfaceUnit = primary && !primary->isModuleImplementation(); + if (isInterfaceUnit) + theModule->setAttr( + cir::CIRDialect::getCXXModuleInitFnNameAttrName(), + builder.getStringAttr(getModuleInitializerName(primary))); + + // The imported modules whose initializers the global init function of + // this translation unit calls first. A module interface or partition unit + // calls those of the modules it exports, imports, or imports in its + // global or private module fragment; any other translation unit calls + // those of the modules it imported. A header-like module has no + // initializer function, and a named module that needs none is skipped. + llvm::SmallSetVector<clang::Module *, 8> imports; + if (isInterfaceUnit) { + for (auto exported : primary->Exports) + imports.insert(exported.first); + imports.insert_range(primary->Imports); + for (clang::Module *sub : primary->submodules()) + imports.insert_range(sub->Imports); + } else { + imports.insert_range(importedModules); + } + llvm::SmallVector<mlir::Attribute> importedInits; + for (clang::Module *mod : imports) { + if (mod->isHeaderLikeModule() || !mod->isNamedModuleInterfaceHasInit()) + continue; + importedInits.push_back( + builder.getStringAttr(getModuleInitializerName(mod))); } + if (!importedInits.empty()) + theModule->setAttr(cir::CIRDialect::getCXXModuleImportedInitsAttrName(), + builder.getArrayAttr(importedInits)); } // Autolink metadata for the imported modules is not emitted yet. diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index c8d697796e2d1..d8a950176805b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -125,6 +125,10 @@ class CIRGenModule : public CIRGenTypeCache { /// The set of modules for which the module initializers have been emitted. llvm::SmallPtrSet<clang::Module *, 16> emittedModuleInitializers; + /// The Itanium-mangled name of the initializer function of a C++20 named + /// module. + std::string getModuleInitializerName(clang::Module *mod); + llvm::DenseSet<clang::GlobalDecl> diagnosedConflictingDefinitions; /// ------- diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index d90f65f64e61b..2ae4201d2923b 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -128,12 +128,13 @@ struct LoweringPreparePass /// Get the declaration for the 'wrapper' function for a global-TLS variable. cir::FuncOp getOrCreateThreadLocalWrapper(CIRBaseBuilderTy &builder, cir::GlobalOp op); - // Function that generates the guard global variable, get-global, and 'if' - // condition for global TLS init function generation. This inserts an 'if' - // with the store at the beginning of the 'then' region, so inserts into the - // body should happen after that. - cir::IfOp buildGlobalTlsGuardCheck(CIRBaseBuilderTy &builder, - mlir::Location loc, cir::GlobalOp guard); + // Function that generates the get-global and 'if' condition on a guard + // variable for an init function that must run once: the global TLS init + // function, or the initializer of a C++20 named-module interface unit. This + // inserts an 'if' with the store at the beginning of the 'then' region, so + // inserts into the body should happen after that. + cir::IfOp buildInitGuardCheck(CIRBaseBuilderTy &builder, mlir::Location loc, + cir::GlobalOp guard, bool threadLocal); /// Handle the dtor region by registering destructor with __cxa_atexit cir::FuncOp getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op, mlir::Region &dtorRegion, @@ -141,12 +142,16 @@ struct LoweringPreparePass /// Build a function named `fnName` with the given linkage that calls each /// of `initializers` in order, then returns, and register it in - /// `globalCtorList` under `priority`. Shared by the default - /// `_GLOBAL__sub_I_*` initializer function and the per-priority - /// `_GLOBAL__I_<priority>` initializer functions. - cir::FuncOp buildGlobalInitCallerFunc( - llvm::StringRef fnName, cir::GlobalLinkageKind linkage, - llvm::ArrayRef<cir::FuncOp> initializers, uint32_t priority); + /// `globalCtorList` under `priority`. With a `guard` byte, the calls run + /// only when it is still zero and set it first. Shared by the default + /// `_GLOBAL__sub_I_*` initializer function, the C++20 named-module + /// initializer and the per-priority `_GLOBAL__I_<priority>` initializer + /// functions. + cir::FuncOp + buildGlobalInitCallerFunc(llvm::StringRef fnName, + cir::GlobalLinkageKind linkage, + llvm::ArrayRef<cir::FuncOp> initializers, + uint32_t priority, cir::GlobalOp guard = {}); /// Build a module init function that calls all the dynamic initializers. void buildCXXGlobalInitFunc(); @@ -1313,12 +1318,13 @@ LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) { cir::IfOp guardIf; if (needsTlsGuard) { - guardIf = buildGlobalTlsGuardCheck( + guardIf = buildInitGuardCheck( builder, op.getLoc(), getOrCreateStaticLocalDeclGuardAddress( builder, op, op.getTlsRefs()->getGuardName().getValue(), /*isLocalVarDecl=*/false, - /*useInt8GuardVariable=*/op.hasInternalLinkage())); + /*useInt8GuardVariable=*/op.hasInternalLinkage()), + /*threadLocal=*/true); builder.setInsertionPointToEnd(&guardIf.getThenRegion().front()); } @@ -2015,9 +2021,11 @@ LoweringPreparePass::createGlobalThreadLocalGuard(CIRBaseBuilderTy &builder, return g; } -cir::IfOp LoweringPreparePass::buildGlobalTlsGuardCheck( - CIRBaseBuilderTy &builder, mlir::Location loc, cir::GlobalOp guard) { - cir::GetGlobalOp getGuard = builder.createGetGlobal(guard, /*tls=*/true); +cir::IfOp LoweringPreparePass::buildInitGuardCheck(CIRBaseBuilderTy &builder, + mlir::Location loc, + cir::GlobalOp guard, + bool threadLocal) { + cir::GetGlobalOp getGuard = builder.createGetGlobal(guard, threadLocal); mlir::Value getGuardValue = getGuard; // Classic codegen always just loads the first byte of the guard instead of @@ -2055,8 +2063,9 @@ void LoweringPreparePass::buildCXXGlobalTlsFunc() { mlir::Block *entryBB = tlsInit.addEntryBlock(); builder.setInsertionPointToStart(entryBB); - cir::IfOp ifOperation = buildGlobalTlsGuardCheck( - builder, loc, createGlobalThreadLocalGuard(builder, loc)); + cir::IfOp ifOperation = buildInitGuardCheck( + builder, loc, createGlobalThreadLocalGuard(builder, loc), + /*threadLocal=*/true); // Emit the body of the guarded spot. builder.setInsertionPointToEnd(&ifOperation.getThenRegion().front()); @@ -2081,16 +2090,30 @@ static std::string getPrioritySuffix(unsigned priority) { cir::FuncOp LoweringPreparePass::buildGlobalInitCallerFunc( llvm::StringRef fnName, cir::GlobalLinkageKind linkage, - llvm::ArrayRef<cir::FuncOp> initializers, uint32_t priority) { + llvm::ArrayRef<cir::FuncOp> initializers, uint32_t priority, + cir::GlobalOp guard) { CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointToEnd(&mlirModule.getBodyRegion().back()); auto fnType = cir::FuncType::get({}, builder.getVoidTy()); cir::FuncOp fn = buildRuntimeFunction(builder, fnName, mlirModule.getLoc(), fnType, linkage); builder.setInsertionPointToStart(fn.addEntryBlock()); - for (cir::FuncOp init : initializers) - builder.createCallOp(init.getLoc(), init, {}); - cir::ReturnOp::create(builder, fn.getLoc()); + mlir::Location loc = fn.getLoc(); + auto emitCalls = [&] { + for (cir::FuncOp init : initializers) + builder.createCallOp(init.getLoc(), init, {}); + }; + if (guard) { + cir::IfOp guardIf = + buildInitGuardCheck(builder, loc, guard, /*threadLocal=*/false); + builder.setInsertionPointToEnd(&guardIf.getThenRegion().front()); + emitCalls(); + cir::YieldOp::create(builder, loc); + builder.setInsertionPointAfter(guardIf); + } else { + emitCalls(); + } + cir::ReturnOp::create(builder, loc); globalCtorList.emplace_back(fnName, priority); return fn; } @@ -2112,11 +2135,36 @@ void LoweringPreparePass::buildCXXGlobalPriorityInitFuncs() { void LoweringPreparePass::buildCXXGlobalInitFunc() { buildCXXGlobalPriorityInitFuncs(); - if (dynamicInitializers.empty()) + auto moduleInitFnName = mlirModule->getAttrOfType<mlir::StringAttr>( + cir::CIRDialect::getCXXModuleInitFnNameAttrName()); + auto importedInits = mlirModule->getAttrOfType<mlir::ArrayAttr>( + cir::CIRDialect::getCXXModuleImportedInitsAttrName()); + + // As in classic CodeGen, a C++20 named-module interface unit gets its + // initializer function even with nothing to run, since importing + // translation units may call it; any other translation unit gets one only + // when there is something to call. + if (dynamicInitializers.empty() && !importedInits && !moduleInitFnName) return; + CIRBaseBuilderTy builder(getContext()); + builder.setInsertionPointToEnd(&mlirModule.getBodyRegion().back()); + mlir::Location loc = mlirModule.getLoc(); + + // The initializers of the imported C++20 named modules, recorded by CIRGen + // as the mangled names of functions this translation unit declares and + // calls before its own initializers. + llvm::SmallVector<cir::FuncOp> initializers; + if (importedInits) + for (mlir::Attribute name : importedInits) + initializers.push_back( + buildRuntimeFunction(builder, cast<mlir::StringAttr>(name).getValue(), + loc, builder.getVoidFnTy())); + initializers.append(dynamicInitializers.begin(), dynamicInitializers.end()); + SmallString<256> fnName; cir::GlobalLinkageKind linkage; + cir::GlobalOp guard; // Include the filename in the symbol name. Including "sub_" matches gcc // and makes sure these symbols appear lexicographically behind the symbols // with priority (TBD). Module implementation units behave the same @@ -2127,18 +2175,28 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { // linkage that goes with it both come from the attribute and this pass needs // no live ASTContext. Modules built directly from textual CIR can opt in to // the module-init form by setting the same attribute. - if (auto fnNameAttr = mlirModule->getAttrOfType<mlir::StringAttr>( - cir::CIRDialect::getCXXModuleInitFnNameAttrName())) { - fnName += fnNameAttr.getValue(); + if (moduleInitFnName) { + fnName += moduleInitFnName.getValue(); linkage = cir::GlobalLinkageKind::ExternalLinkage; + // The global ctor entry and every importing translation unit call this + // function, so a guard byte makes the initializers run once. There is + // none when there is nothing to run. + if (!initializers.empty()) { + cir::IntType guardTy = builder.getSIntNTy(8); + guard = + createGuardGlobalOp(builder, loc, (fnName + "__in_chrg").str(), + guardTy, cir::GlobalLinkageKind::InternalLinkage); + guard.setAlignment(clang::CharUnits::One().getAsAlign().value()); + guard.setInitialValueAttr(cir::IntAttr::get(guardTy, 0)); + } } else { fnName += "_GLOBAL__sub_I_"; fnName += getTransformedFileName(mlirModule); linkage = cir::GlobalLinkageKind::InternalLinkage; } - buildGlobalInitCallerFunc(fnName, linkage, dynamicInitializers, - cir::GlobalCtorAttr::getDefaultPriority()); + buildGlobalInitCallerFunc(fnName, linkage, initializers, + cir::GlobalCtorAttr::getDefaultPriority(), guard); } /// Lower a cir.array.ctor or cir.array.dtor into a do-while loop that diff --git a/clang/test/CIR/CodeGen/module-import.cpp b/clang/test/CIR/CodeGen/module-import.cpp index dbac152b7ba12..3e601d7c70a78 100644 --- a/clang/test/CIR/CodeGen/module-import.cpp +++ b/clang/test/CIR/CodeGen/module-import.cpp @@ -8,11 +8,25 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fmodules -fimplicit-module-maps -fmodules-cache-path=%t/cache -emit-llvm %t/header-module.cpp -o - | FileCheck %t/header-module.cpp --check-prefix=LLVM --implicit-check-not=c_global // // Importing a C++20 named module only records the module: its initializers -// stay in its own translation unit. The call to that module's initializer from -// the importing unit's global init function is not emitted yet. +// stay in its own translation unit, and the importing unit's global init +// function calls the module's initializer first. // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-module-interface %t/a.cppm -o %t/a.pcm // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-cir %t/named-module.cpp -o - | FileCheck %t/named-module.cpp --check-prefix=CIR --implicit-check-not=__cxx_global_var_init // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-llvm %t/named-module.cpp -o - | FileCheck %t/named-module.cpp --check-prefix=LLVM --implicit-check-not=__cxx_global_var_init +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fmodule-file=a=%t/a.pcm -emit-llvm %t/named-module.cpp -o - | FileCheck %t/named-module.cpp --check-prefix=LLVM --implicit-check-not=__cxx_global_var_init +// +// The interface unit's own initializer, guarded; one of a module that only +// imports a module with an initializer, which calls it; one of a module with +// nothing to run, emitted all the same. +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir %t/a.cppm -o - | FileCheck %t/a.cppm --check-prefix=CIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %t/a.cppm -o - | FileCheck %t/a.cppm --check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %t/a.cppm -o - | FileCheck %t/a.cppm --check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-cir %t/b.cppm -o - | FileCheck %t/b.cppm --check-prefix=CIR --implicit-check-not=__cxx_global_var_init +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-llvm %t/b.cppm -o - | FileCheck %t/b.cppm --check-prefix=LLVM --implicit-check-not=__cxx_global_var_init +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fmodule-file=a=%t/a.pcm -emit-llvm %t/b.cppm -o - | FileCheck %t/b.cppm --check-prefix=LLVM --implicit-check-not=__cxx_global_var_init +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir %t/c.cppm -o - | FileCheck %t/c.cppm --check-prefix=CIR --implicit-check-not=__in_chrg --implicit-check-not=cir.call +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %t/c.cppm -o - | FileCheck %t/c.cppm --check-prefix=LLVM --implicit-check-not=__in_chrg --implicit-check-not=call +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %t/c.cppm -o - | FileCheck %t/c.cppm --check-prefix=LLVM --implicit-check-not=__in_chrg --implicit-check-not=call //--- module.modulemap module A { @@ -77,19 +91,79 @@ int side_effect(); export int a_val = side_effect(); export inline int a_func(int x) { return x + 1; } +// The initializer of a module interface unit has external linkage and runs +// its initializers once, behind a guard byte, whichever caller comes first. +// CIR: cir.cxx_module_init_fn_name = "_ZGIW1a" +// CIR: cir.global {{.*}}internal @_ZGIW1a__in_chrg = #cir.int<0> : !s8i +// CIR: cir.func private @_ZGIW1a() +// CIR: cir.get_global @_ZGIW1a__in_chrg : !cir.ptr<!s8i> +// CIR: cir.if +// CIR: cir.store +// CIR: cir.call @__cxx_global_var_init() + +// LLVM: @_ZGIW1a__in_chrg = internal global i8 0 +// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_ZGIW1a, ptr null }] +// LLVM: define void @_ZGIW1a() +// LLVM: load i8, ptr @_ZGIW1a__in_chrg +// LLVM: icmp eq i8 +// LLVM: store i8 1, ptr @_ZGIW1a__in_chrg +// LLVM: call void @__cxx_global_var_init() + //--- named-module.cpp import a; int use() { return a_func(a_val); } +// CIR: cir.cxx_module_imported_inits = ["_ZGIW1a"] // CIR: cir.global "private" external {{.*}}@_ZW1a5a_val : !s32i // CIR: cir.func {{.*}}@_Z3usev() // CIR: cir.get_global @_ZW1a5a_val : !cir.ptr<!s32i> // CIR: cir.call @_ZW1a6a_funci // CIR: cir.func {{.*}}linkonce_odr @_ZW1a6a_funci +// CIR: cir.func private @_ZGIW1a() +// CIR: cir.func internal private @_GLOBAL__sub_I_named_module.cpp() +// CIR: cir.call @_ZGIW1a() // LLVM: @_ZW1a5a_val = external global i32 +// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_named_module.cpp, ptr null }] // LLVM: define dso_local noundef i32 @_Z3usev() // LLVM: load i32, ptr @_ZW1a5a_val // LLVM: call noundef i32 @_ZW1a6a_funci // LLVM: define linkonce_odr noundef i32 @_ZW1a6a_funci +// LLVM: declare void @_ZGIW1a() +// LLVM: define internal void @_GLOBAL__sub_I_named_module.cpp() +// LLVM: call void @_ZGIW1a() + +//--- b.cppm +export module b; +import a; +export int b_func() { return a_func(a_val); } + +// CIR: cir.cxx_module_imported_inits = ["_ZGIW1a"] +// CIR: cir.cxx_module_init_fn_name = "_ZGIW1b" +// CIR: cir.global {{.*}}internal @_ZGIW1b__in_chrg = #cir.int<0> : !s8i +// CIR: cir.func private @_ZGIW1a() +// CIR: cir.func private @_ZGIW1b() +// CIR: cir.get_global @_ZGIW1b__in_chrg : !cir.ptr<!s8i> +// CIR: cir.if +// CIR: cir.store +// CIR: cir.call @_ZGIW1a() + +// LLVM: @_ZGIW1b__in_chrg = internal global i8 0 +// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_ZGIW1b, ptr null }] +// LLVM: declare void @_ZGIW1a() +// LLVM: define void @_ZGIW1b() +// LLVM: load i8, ptr @_ZGIW1b__in_chrg +// LLVM: store i8 1, ptr @_ZGIW1b__in_chrg +// LLVM: call void @_ZGIW1a() + +//--- c.cppm +export module c; +export int c_func(); + +// CIR: cir.func private @_ZGIW1c() +// CIR-NEXT: cir.return + +// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_ZGIW1c, ptr null }] +// LLVM: define void @_ZGIW1c() +// LLVM: ret void >From 958a80b2d6e7f22bc005e9955c966d6490cfc6d4 Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:42:41 -0700 Subject: [PATCH 03/11] [CIR] Name the import declaration importDecl in emitTopLevelDecl `import` is a contextual keyword, highlighted as one by editors. Assisted-by: Claude Fable 5.1 (Claude Code) --- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index af7d678ceab25..4755f1ba30c83 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2556,8 +2556,8 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) { } case Decl::Import: { - auto *import = cast<ImportDecl>(decl); - clang::Module *mod = import->getImportedModule(); + auto *importDecl = cast<ImportDecl>(decl); + clang::Module *mod = importDecl->getImportedModule(); // If we've already imported this module, we're done. if (!importedModules.insert(mod)) >From 04a7856e4970e6e26a42e6b96d0e7c0d27e5bff8 Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:42:42 -0700 Subject: [PATCH 04/11] [CIR] Build the module initializer name in the returned std::string A raw_string_ostream on the result replaces the SmallString and the copy out of it. Assisted-by: Claude Fable 5.1 (Claude Code) --- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 4755f1ba30c83..43b2ba7394a0a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -3961,11 +3961,11 @@ CIRGenModule::getMLIRVisibilityFromCIRLinkage(cir::GlobalLinkageKind glk) { } std::string CIRGenModule::getModuleInitializerName(clang::Module *mod) { - llvm::SmallString<256> name; - llvm::raw_svector_ostream out(name); + std::string name; + llvm::raw_string_ostream out(name); cast<clang::ItaniumMangleContext>(getCXXABI().getMangleContext()) .mangleModuleInitializer(mod, out); - return std::string(name); + return name; } void CIRGenModule::release() { >From 8c7d706fa17275c5fe05f35abc91d03969b0f104 Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:42:43 -0700 Subject: [PATCH 05/11] [CIR] Skip an ImportDecl that names no module Classic CodeGen checks the imported module for null before using it; do the same before recording it or emitting anything. Assisted-by: Claude Fable 5.1 (Claude Code) --- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 43b2ba7394a0a..04dd517c378a8 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2559,6 +2559,10 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) { auto *importDecl = cast<ImportDecl>(decl); clang::Module *mod = importDecl->getImportedModule(); + // Nothing to record or to emit without a module. + if (!mod) + break; + // If we've already imported this module, we're done. if (!importedModules.insert(mod)) break; >From db10f16b7a8ec02e3ef81a8faf6941dd6a2149ce Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:42:43 -0700 Subject: [PATCH 06/11] [CIR][NFC] Move the CXX20ModuleInits comment to where the flag is computed The comment on the Itanium-mangler condition sat in release(), above code that no longer performs that check since cxx20ModuleInits carries it. Assisted-by: Claude Fable 5.1 (Claude Code) --- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 04dd517c378a8..aef637947285e 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -93,7 +93,10 @@ CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext, diags(diags), target(astContext.getTargetInfo()), abi(createCXXABI(*this)), genTypes(*this), vtables(*this) { - // The name of a C++20 module initializer has only an Itanium mangling. + // Classic CodeGen's CXX20ModuleInits: C++20 named modules get an + // initializer function of their own only under the Itanium mangler, since + // no Microsoft mangling for it has been settled on yet; otherwise they fall + // back to `_GLOBAL__sub_I_` as in classic CodeGen. cxx20ModuleInits = langOpts.CPlusPlusModules && getCXXABI().getMangleContext().getKind() == MangleContext::MK_Itanium; @@ -4028,12 +4031,6 @@ void CIRGenModule::release() { // and that the function needs external linkage, and its absence selects the // `_GLOBAL__sub_I_` form. Lowering therefore never has to rediscover the // module from the AST. - // - // The mangler-kind check mirrors classic codegen's `CXX20ModuleInits` (see - // CodeGenModule.cpp), which only enables C++20 module initializers for the - // Itanium mangler because no Microsoft mangling for them has been settled - // on yet. Non-Itanium named modules fall back to `_GLOBAL__sub_I_` exactly - // as they do in classic codegen. if (cxx20ModuleInits) { clang::Module *primary = astContext.getCurrentNamedModule(); bool isInterfaceUnit = primary && !primary->isModuleImplementation(); >From 81cdb7d51072794812760aee54a2c0e88ab8707f Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:42:44 -0700 Subject: [PATCH 07/11] [CIR][NFC] Add a comma to the buildGlobalInitCallerFunc comment Assisted-by: Claude Fable 5.1 (Claude Code) --- clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 2ae4201d2923b..8bc88a25889bf 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -145,7 +145,7 @@ struct LoweringPreparePass /// `globalCtorList` under `priority`. With a `guard` byte, the calls run /// only when it is still zero and set it first. Shared by the default /// `_GLOBAL__sub_I_*` initializer function, the C++20 named-module - /// initializer and the per-priority `_GLOBAL__I_<priority>` initializer + /// initializer, and the per-priority `_GLOBAL__I_<priority>` initializer /// functions. cir::FuncOp buildGlobalInitCallerFunc(llvm::StringRef fnName, >From 10cfd474ee4153e6c9338add2bfd4c2e3588f267 Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:42:45 -0700 Subject: [PATCH 08/11] [CIR][NFC] Fix the grammar of the importedModules comment Assisted-by: Claude Fable 5.1 (Claude Code) --- clang/lib/CIR/CodeGen/CIRGenModule.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index d8a950176805b..c5e705b6e6bbe 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -119,7 +119,7 @@ class CIRGenModule : public CIRGenTypeCache { /// which requires the Itanium mangling of the initializer name. bool cxx20ModuleInits = false; - /// The complete set of modules that has been imported. + /// The complete set of modules that have been imported. llvm::SetVector<clang::Module *> importedModules; /// The set of modules for which the module initializers have been emitted. >From 95fe5ae8d2410975b75f65371e7330bd647ff0c4 Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:45:52 -0700 Subject: [PATCH 09/11] [CIR] Order the imported module initializers with init_priority The imported C++20 modules' initializers were called only from the default-priority function, after the `_GLOBAL__I_<priority>` functions had run, so a prioritized initializer that depends on an imported module ran before it; and an interface unit emitted its prioritized initializers outside its own initializer and its guard. Follow classic CodeGen's EmitCXXGlobalInitFunc and EmitCXXModuleInitFunc: in a translation unit with prioritized initializers, the imported modules' initializers run from the highest-priority function, in front of its own; in a module interface or partition unit, all initializers, the imported modules' first and then the unit's own in priority order, run from the unit's initializer behind its guard, with no `_GLOBAL__I_<priority>` function. Assisted-by: Claude Fable 5.1 (Claude Code) --- .../Dialect/Transforms/LoweringPrepare.cpp | 102 ++++++++++-------- clang/test/CIR/CodeGen/module-import.cpp | 61 +++++++++++ 2 files changed, 121 insertions(+), 42 deletions(-) diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 8bc88a25889bf..c2b433f567fe7 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -157,8 +157,11 @@ struct LoweringPreparePass void buildCXXGlobalInitFunc(); /// Build one `_GLOBAL__I_<priority>` function per distinct priority found /// in `prioritizedDynamicInitializers`, in ascending priority order, and - /// register each with `globalCtorList`. - void buildCXXGlobalPriorityInitFuncs(); + /// register each with `globalCtorList`. The first of them, the highest + /// priority, calls `importedInits` before its own initializers and takes + /// them out of the vector, as classic CodeGen does. + void buildCXXGlobalPriorityInitFuncs( + llvm::SmallVectorImpl<cir::FuncOp> &importedInits); // Build an init function for all of the ordered global thread local storage // variables. void buildCXXGlobalTlsFunc(); @@ -2118,7 +2121,8 @@ cir::FuncOp LoweringPreparePass::buildGlobalInitCallerFunc( return fn; } -void LoweringPreparePass::buildCXXGlobalPriorityInitFuncs() { +void LoweringPreparePass::buildCXXGlobalPriorityInitFuncs( + llvm::SmallVectorImpl<cir::FuncOp> &importedInits) { // std::map keeps priorities in ascending order, so each group is already // ready to emit into its own function, named after its priority so that // the functions are naturally ordered relative to one another. @@ -2127,26 +2131,16 @@ void LoweringPreparePass::buildCXXGlobalPriorityInitFuncs() { fnName += "_GLOBAL__I_"; fnName += getPrioritySuffix(priority); + llvm::SmallVector<cir::FuncOp> calls(importedInits.begin(), + importedInits.end()); + calls.append(initializers.begin(), initializers.end()); + importedInits.clear(); buildGlobalInitCallerFunc(fnName, cir::GlobalLinkageKind::InternalLinkage, - initializers, priority); + calls, priority); } } void LoweringPreparePass::buildCXXGlobalInitFunc() { - buildCXXGlobalPriorityInitFuncs(); - - auto moduleInitFnName = mlirModule->getAttrOfType<mlir::StringAttr>( - cir::CIRDialect::getCXXModuleInitFnNameAttrName()); - auto importedInits = mlirModule->getAttrOfType<mlir::ArrayAttr>( - cir::CIRDialect::getCXXModuleImportedInitsAttrName()); - - // As in classic CodeGen, a C++20 named-module interface unit gets its - // initializer function even with nothing to run, since importing - // translation units may call it; any other translation unit gets one only - // when there is something to call. - if (dynamicInitializers.empty() && !importedInits && !moduleInitFnName) - return; - CIRBaseBuilderTy builder(getContext()); builder.setInsertionPointToEnd(&mlirModule.getBodyRegion().back()); mlir::Location loc = mlirModule.getLoc(); @@ -2154,33 +2148,37 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { // The initializers of the imported C++20 named modules, recorded by CIRGen // as the mangled names of functions this translation unit declares and // calls before its own initializers. - llvm::SmallVector<cir::FuncOp> initializers; - if (importedInits) - for (mlir::Attribute name : importedInits) - initializers.push_back( + llvm::SmallVector<cir::FuncOp> importedInits; + if (auto names = mlirModule->getAttrOfType<mlir::ArrayAttr>( + cir::CIRDialect::getCXXModuleImportedInitsAttrName())) + for (mlir::Attribute name : names) + importedInits.push_back( buildRuntimeFunction(builder, cast<mlir::StringAttr>(name).getValue(), loc, builder.getVoidFnTy())); - initializers.append(dynamicInitializers.begin(), dynamicInitializers.end()); - SmallString<256> fnName; - cir::GlobalLinkageKind linkage; - cir::GlobalOp guard; - // Include the filename in the symbol name. Including "sub_" matches gcc - // 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. // The C++20 named-module init function name is precomputed by CIRGen and // stored as a module-level attribute. Its presence is what marks this // module as a named-module interface unit, so the name and the external // linkage that goes with it both come from the attribute and this pass needs // no live ASTContext. Modules built directly from textual CIR can opt in to // the module-init form by setting the same attribute. - if (moduleInitFnName) { - fnName += moduleInitFnName.getValue(); - linkage = cir::GlobalLinkageKind::ExternalLinkage; - // The global ctor entry and every importing translation unit call this - // function, so a guard byte makes the initializers run once. There is - // none when there is nothing to run. + if (auto moduleInitFnName = mlirModule->getAttrOfType<mlir::StringAttr>( + cir::CIRDialect::getCXXModuleInitFnNameAttrName())) { + // As in classic CodeGen's EmitCXXModuleInitFunc, an interface unit gets + // one initializer function, even with nothing to run, since importing + // translation units may call it. It runs the imported modules' + // initializers, then the unit's own in priority order, which get no + // `_GLOBAL__I_<priority>` function of their own. The global ctor entry + // and every importing translation unit call it, so a guard byte makes + // the initializers run once; there is none when there is nothing to run. + llvm::SmallVector<cir::FuncOp> initializers(importedInits.begin(), + importedInits.end()); + for (const auto &[priority, inits] : prioritizedDynamicInitializers) + initializers.append(inits.begin(), inits.end()); + initializers.append(dynamicInitializers.begin(), dynamicInitializers.end()); + + SmallString<256> fnName(moduleInitFnName.getValue()); + cir::GlobalOp guard; if (!initializers.empty()) { cir::IntType guardTy = builder.getSIntNTy(8); guard = @@ -2189,14 +2187,34 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { guard.setAlignment(clang::CharUnits::One().getAsAlign().value()); guard.setInitialValueAttr(cir::IntAttr::get(guardTy, 0)); } - } else { - fnName += "_GLOBAL__sub_I_"; - fnName += getTransformedFileName(mlirModule); - linkage = cir::GlobalLinkageKind::InternalLinkage; + buildGlobalInitCallerFunc(fnName, cir::GlobalLinkageKind::ExternalLinkage, + initializers, + cir::GlobalCtorAttr::getDefaultPriority(), guard); + return; } - buildGlobalInitCallerFunc(fnName, linkage, initializers, - cir::GlobalCtorAttr::getDefaultPriority(), guard); + // Any other translation unit, as in classic CodeGen's + // EmitCXXGlobalInitFunc: one function per priority, the imported modules' + // initializers in front of the first, then the default-priority function + // for the rest, only when there is something left to call. + buildCXXGlobalPriorityInitFuncs(importedInits); + if (importedInits.empty() && dynamicInitializers.empty()) + return; + + llvm::SmallVector<cir::FuncOp> initializers(importedInits.begin(), + importedInits.end()); + initializers.append(dynamicInitializers.begin(), dynamicInitializers.end()); + + // Include the filename in the symbol name. Including "sub_" matches gcc + // 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. + SmallString<256> fnName; + fnName += "_GLOBAL__sub_I_"; + fnName += getTransformedFileName(mlirModule); + buildGlobalInitCallerFunc(fnName, cir::GlobalLinkageKind::InternalLinkage, + initializers, + cir::GlobalCtorAttr::getDefaultPriority()); } /// Lower a cir.array.ctor or cir.array.dtor into a do-while loop that diff --git a/clang/test/CIR/CodeGen/module-import.cpp b/clang/test/CIR/CodeGen/module-import.cpp index 3e601d7c70a78..8aa0c5ad5a58c 100644 --- a/clang/test/CIR/CodeGen/module-import.cpp +++ b/clang/test/CIR/CodeGen/module-import.cpp @@ -27,6 +27,16 @@ // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-cir %t/c.cppm -o - | FileCheck %t/c.cppm --check-prefix=CIR --implicit-check-not=__in_chrg --implicit-check-not=cir.call // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -emit-llvm %t/c.cppm -o - | FileCheck %t/c.cppm --check-prefix=LLVM --implicit-check-not=__in_chrg --implicit-check-not=call // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %t/c.cppm -o - | FileCheck %t/c.cppm --check-prefix=LLVM --implicit-check-not=__in_chrg --implicit-check-not=call +// +// init_priority: an importing unit calls the imported initializers from its +// first prioritized function; an interface unit folds its prioritized +// initializers into its own initializer, behind the guard. +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-cir %t/named-module-priority.cpp -o - | FileCheck %t/named-module-priority.cpp --check-prefix=CIR +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-llvm %t/named-module-priority.cpp -o - | FileCheck %t/named-module-priority.cpp --check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fmodule-file=a=%t/a.pcm -emit-llvm %t/named-module-priority.cpp -o - | FileCheck %t/named-module-priority.cpp --check-prefix=LLVM +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-cir %t/d.cppm -o - | FileCheck %t/d.cppm --check-prefix=CIR --implicit-check-not=_GLOBAL__I_ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fclangir -fmodule-file=a=%t/a.pcm -emit-llvm %t/d.cppm -o - | FileCheck %t/d.cppm --check-prefix=LLVM --implicit-check-not=_GLOBAL__I_ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fmodule-file=a=%t/a.pcm -emit-llvm %t/d.cppm -o - | FileCheck %t/d.cppm --check-prefix=LLVM --implicit-check-not=_GLOBAL__I_ //--- module.modulemap module A { @@ -167,3 +177,54 @@ export int c_func(); // LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_ZGIW1c, ptr null }] // LLVM: define void @_ZGIW1c() // LLVM: ret void + +//--- named-module-priority.cpp +import a; +struct S { S(); }; +int side_effect(); +[[gnu::init_priority(200)]] S p; +int q = side_effect(); + +// The imported module's initializer runs from the first prioritized function, +// before that priority's own initializers; the default-priority function keeps +// the rest. +// CIR: cir.global_ctors = [#cir.global_ctor<"_GLOBAL__I_000200", 200>, #cir.global_ctor<"_GLOBAL__sub_I_named_module_priority.cpp", 65535>] +// CIR: cir.func internal private @_GLOBAL__I_000200() +// CIR-NEXT: cir.call @_ZGIW1a() +// CIR-NEXT: cir.call @__cxx_global_var_init() +// CIR-NEXT: cir.return +// CIR: cir.func internal private @_GLOBAL__sub_I_named_module_priority.cpp() +// CIR-NEXT: cir.call @__cxx_global_var_init.1() +// CIR-NEXT: cir.return + +// LLVM: @llvm.global_ctors = appending global [2 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 200, ptr @_GLOBAL__I_000200, ptr null }, { i32, ptr, ptr } { i32 65535, ptr @_GLOBAL__sub_I_named_module_priority.cpp, ptr null }] +// LLVM: define internal void @_GLOBAL__I_000200() +// LLVM: call void @_ZGIW1a() +// LLVM-NEXT: call void @__cxx_global_var_init() +// LLVM: define internal void @_GLOBAL__sub_I_named_module_priority.cpp() +// LLVM: call void @__cxx_global_var_init.1() + +//--- d.cppm +export module d; +import a; +struct S { S(); }; +int side_effect(); +[[gnu::init_priority(200)]] S p; +int q = side_effect(); + +// An interface unit folds its prioritized initializers into its own +// initializer, after the imported modules' and behind the guard. +// CIR: cir.global_ctors = [#cir.global_ctor<"_ZGIW1d", 65535>] +// CIR: cir.func private @_ZGIW1d() +// CIR: cir.if +// CIR: cir.store +// CIR-NEXT: cir.call @_ZGIW1a() +// CIR-NEXT: cir.call @__cxx_global_var_init() +// CIR-NEXT: cir.call @__cxx_global_var_init.1() + +// LLVM: @llvm.global_ctors = appending global [1 x { i32, ptr, ptr }] [{ i32, ptr, ptr } { i32 65535, ptr @_ZGIW1d, ptr null }] +// LLVM: define void @_ZGIW1d() +// LLVM: store i8 1, ptr @_ZGIW1d__in_chrg +// LLVM-NEXT: call void @_ZGIW1a() +// LLVM-NEXT: call void @__cxx_global_var_init() +// LLVM-NEXT: call void @__cxx_global_var_init.1() >From cb4e37c51905d19a5d9b166bfd619b7f9a3b6a43 Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:48:27 -0700 Subject: [PATCH 10/11] [CIR] Locate the imported module initializers at their import The declarations of the imported C++20 modules' initializer functions, and the calls to them, carried the module's location. CIRGen now records each one as a #cir.cxx_module_init attribute: the mangled name with the location of the module's first import declaration, or of the module declaration for a module that only an interface unit's export or import lists name. LoweringPrepare declares the function at that location. Assisted-by: Claude Fable 5.1 (Claude Code) --- .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 25 +++++++++++++++++++ .../clang/CIR/Dialect/IR/CIRDialect.td | 6 ++--- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 12 +++++++-- clang/lib/CIR/CodeGen/CIRGenModule.h | 3 +++ .../Dialect/Transforms/LoweringPrepare.cpp | 12 ++++----- clang/test/CIR/CodeGen/module-import.cpp | 4 +-- 6 files changed, 49 insertions(+), 13 deletions(-) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 6cf08b5257510..c184df4b59e68 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -2509,4 +2509,29 @@ def CIR_AnnotationArrayAttr include "clang/CIR/Dialect/IR/CIROpenCLAttrs.td" include "clang/CIR/Dialect/IR/CIRCUDAAttrs.td" +//===----------------------------------------------------------------------===// +// CXXModuleInitAttr +//===----------------------------------------------------------------------===// + +def CIR_CXXModuleInitAttr : CIR_Attr<"CXXModuleInit", "cxx_module_init"> { + let summary = "Initializer function of an imported C++20 named module"; + let description = [{ + The Itanium-mangled name of the initializer function of a C++20 named + module the translation unit imports, with the location of the import + declaration. CIRGen lists one per module whose initializer the unit's + global init function must call, in call order, in the + `cir.cxx_module_imported_inits` module attribute; LoweringPrepare declares + each function at that location and calls it before the unit's own + initializers. + + Example: + ```mlir + cir.cxx_module_imported_inits = [#cir.cxx_module_init<"_ZGIW1a", loc("use.cpp":1:1)>] + ``` + }]; + let parameters = (ins "mlir::StringAttr":$name, "mlir::LocationAttr":$loc); + let assemblyFormat = [{ `<` $name `,` $loc `>` }]; + let canHaveIllegalCXXABIType = 0; +} + #endif // CLANG_CIR_DIALECT_IR_CIRATTRS_TD diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td index c2736b73df28e..13d4fbcde5cd1 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td @@ -106,9 +106,9 @@ def CIR_Dialect : Dialect { // 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"; } - // Mangled names of the initializer functions of the C++20 named modules - // this translation unit imports, in the order the global init function - // calls them, precomputed by CIRGen for the same reason. + // The initializer functions of the C++20 named modules this translation + // unit imports, as #cir.cxx_module_init attributes in the order the global + // init function calls them, precomputed by CIRGen for the same reason. static llvm::StringRef getCXXModuleImportedInitsAttrName() { return "cir.cxx_module_imported_inits"; } // LangOptions facts that post-CIRGen lowering consumes, serialized by // CIRGen so a reloaded .cir module is self-describing without a live diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index aef637947285e..c60e7458356b0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2569,6 +2569,7 @@ void CIRGenModule::emitTopLevelDecl(Decl *decl) { // If we've already imported this module, we're done. if (!importedModules.insert(mod)) break; + moduleImportLocs[mod] = importDecl->getLocation(); assert(!cir::MissingFeatures::generateDebugInfo()); @@ -4059,8 +4060,15 @@ void CIRGenModule::release() { for (clang::Module *mod : imports) { if (mod->isHeaderLikeModule() || !mod->isNamedModuleInterfaceHasInit()) continue; - importedInits.push_back( - builder.getStringAttr(getModuleInitializerName(mod))); + // A module that only the interface unit's export or import lists name + // takes the location of the module declaration. + auto importLoc = moduleImportLocs.find(mod); + mlir::Location loc = + getLoc(importLoc != moduleImportLocs.end() ? importLoc->second + : primary->DefinitionLoc); + importedInits.push_back(cir::CXXModuleInitAttr::get( + &getMLIRContext(), + builder.getStringAttr(getModuleInitializerName(mod)), loc)); } if (!importedInits.empty()) theModule->setAttr(cir::CIRDialect::getCXXModuleImportedInitsAttrName(), diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index c5e705b6e6bbe..957e26182972a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -122,6 +122,9 @@ class CIRGenModule : public CIRGenTypeCache { /// The complete set of modules that have been imported. llvm::SetVector<clang::Module *> importedModules; + /// The location of the first import declaration of each imported module. + llvm::DenseMap<clang::Module *, clang::SourceLocation> moduleImportLocs; + /// The set of modules for which the module initializers have been emitted. llvm::SmallPtrSet<clang::Module *, 16> emittedModuleInitializers; diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index c2b433f567fe7..e9e2ce0b017d8 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -2146,15 +2146,15 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { mlir::Location loc = mlirModule.getLoc(); // The initializers of the imported C++20 named modules, recorded by CIRGen - // as the mangled names of functions this translation unit declares and - // calls before its own initializers. + // as the mangled names of functions this translation unit declares, at the + // location of the import, and calls before its own initializers. llvm::SmallVector<cir::FuncOp> importedInits; - if (auto names = mlirModule->getAttrOfType<mlir::ArrayAttr>( + if (auto inits = mlirModule->getAttrOfType<mlir::ArrayAttr>( cir::CIRDialect::getCXXModuleImportedInitsAttrName())) - for (mlir::Attribute name : names) + for (auto init : inits.getAsRange<cir::CXXModuleInitAttr>()) importedInits.push_back( - buildRuntimeFunction(builder, cast<mlir::StringAttr>(name).getValue(), - loc, builder.getVoidFnTy())); + buildRuntimeFunction(builder, init.getName().getValue(), + init.getLoc(), builder.getVoidFnTy())); // The C++20 named-module init function name is precomputed by CIRGen and // stored as a module-level attribute. Its presence is what marks this diff --git a/clang/test/CIR/CodeGen/module-import.cpp b/clang/test/CIR/CodeGen/module-import.cpp index 8aa0c5ad5a58c..d0149cd3c9f7b 100644 --- a/clang/test/CIR/CodeGen/module-import.cpp +++ b/clang/test/CIR/CodeGen/module-import.cpp @@ -124,7 +124,7 @@ import a; int use() { return a_func(a_val); } -// CIR: cir.cxx_module_imported_inits = ["_ZGIW1a"] +// CIR: cir.cxx_module_imported_inits = [#cir.cxx_module_init<"_ZGIW1a", {{[^>]*}}>] // CIR: cir.global "private" external {{.*}}@_ZW1a5a_val : !s32i // CIR: cir.func {{.*}}@_Z3usev() // CIR: cir.get_global @_ZW1a5a_val : !cir.ptr<!s32i> @@ -149,7 +149,7 @@ export module b; import a; export int b_func() { return a_func(a_val); } -// CIR: cir.cxx_module_imported_inits = ["_ZGIW1a"] +// CIR: cir.cxx_module_imported_inits = [#cir.cxx_module_init<"_ZGIW1a", {{[^>]*}}>] // CIR: cir.cxx_module_init_fn_name = "_ZGIW1b" // CIR: cir.global {{.*}}internal @_ZGIW1b__in_chrg = #cir.int<0> : !s8i // CIR: cir.func private @_ZGIW1a() >From 561ed4b712fa0a9d7d0d14ac62265cf0e98f50f2 Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Wed, 7 Oct 2026 16:49:52 -0700 Subject: [PATCH 11/11] [clang] Share the selection of imported module initializers to call Classic CodeGen's EmitCXXGlobalInitFunc and EmitCXXModuleInitFunc and CIRGen's release() each selected the imported C++20 named modules whose initializer function the unit's global init function calls: for a module interface or partition unit the modules it exports, imports, or imports in its global or private module fragment, otherwise the modules the unit imported, minus header-like modules and modules that need no initializer. CodeGenUtils::importedModulesToInitialize now holds that query, and the three sites call it. Assisted-by: Claude Fable 5.1 (Claude Code) --- .../include/clang/CodeGenUtils/ModuleUtils.h | 13 ++++++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 21 ++-------- clang/lib/CodeGen/CGDeclCXX.cpp | 41 +++---------------- clang/lib/CodeGenUtils/ModuleUtils.cpp | 40 ++++++++++++++++++ 4 files changed, 61 insertions(+), 54 deletions(-) diff --git a/clang/include/clang/CodeGenUtils/ModuleUtils.h b/clang/include/clang/CodeGenUtils/ModuleUtils.h index 5c7423ad4fac5..e887069fdeee8 100644 --- a/clang/include/clang/CodeGenUtils/ModuleUtils.h +++ b/clang/include/clang/CodeGenUtils/ModuleUtils.h @@ -16,6 +16,9 @@ #define LLVM_CLANG_CODEGENUTILS_MODULEUTILS_H #include "clang/AST/ASTContext.h" +#include "clang/Basic/Module.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" namespace clang::CodeGenUtils { @@ -27,6 +30,16 @@ bool isVarDeclStrongDefinition(const ASTContext &Ctx, const VarDecl *D, /// Check whether \p D should be emitted into a COMDAT group. bool shouldBeInCOMDAT(const ASTContext &Ctx, const Decl &D); +/// The C++20 named modules whose Itanium initializer function the global init +/// function of a translation unit calls before the unit's own initializers, +/// in call order. For a module interface or partition unit \p Primary these +/// are the modules it exports, imports, or imports in its global or private +/// module fragment; for any other unit the modules in \p Imported, its import +/// declarations in order. A header-like module has no initializer function, +/// and a named module that needs none is skipped. +llvm::SmallVector<Module *, 8> +importedModulesToInitialize(Module *Primary, llvm::ArrayRef<Module *> Imported); + } // namespace clang::CodeGenUtils #endif // LLVM_CLANG_CODEGENUTILS_MODULEUTILS_H diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index c60e7458356b0..9662f06968520 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -4041,25 +4041,10 @@ void CIRGenModule::release() { builder.getStringAttr(getModuleInitializerName(primary))); // The imported modules whose initializers the global init function of - // this translation unit calls first. A module interface or partition unit - // calls those of the modules it exports, imports, or imports in its - // global or private module fragment; any other translation unit calls - // those of the modules it imported. A header-like module has no - // initializer function, and a named module that needs none is skipped. - llvm::SmallSetVector<clang::Module *, 8> imports; - if (isInterfaceUnit) { - for (auto exported : primary->Exports) - imports.insert(exported.first); - imports.insert_range(primary->Imports); - for (clang::Module *sub : primary->submodules()) - imports.insert_range(sub->Imports); - } else { - imports.insert_range(importedModules); - } + // this translation unit calls first, selected as in classic CodeGen. llvm::SmallVector<mlir::Attribute> importedInits; - for (clang::Module *mod : imports) { - if (mod->isHeaderLikeModule() || !mod->isNamedModuleInterfaceHasInit()) - continue; + for (clang::Module *mod : CodeGenUtils::importedModulesToInitialize( + primary, importedModules.getArrayRef())) { // A module that only the interface unit's export or import lists name // takes the location of the module declaration. auto importLoc = moduleImportLocs.find(mod); diff --git a/clang/lib/CodeGen/CGDeclCXX.cpp b/clang/lib/CodeGen/CGDeclCXX.cpp index a54809c48037e..566f7e5d64f3e 100644 --- a/clang/lib/CodeGen/CGDeclCXX.cpp +++ b/clang/lib/CodeGen/CGDeclCXX.cpp @@ -19,6 +19,7 @@ #include "TargetInfo.h" #include "clang/AST/Attr.h" #include "clang/Basic/LangOptions.h" +#include "clang/CodeGenUtils/ModuleUtils.h" #include "llvm/ADT/StringExtras.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/MDBuilder.h" @@ -703,34 +704,8 @@ void CodeGenModule::EmitCXXModuleInitFunc(Module *Primary) { // As noted above, we create the function, even if it is empty. // Module initializers for imported modules are emitted first. - // Collect all the modules that we import - llvm::SmallSetVector<Module *, 8> AllImports; - // Ones that we export - for (auto I : Primary->Exports) - AllImports.insert(I.first); - // Ones that we only import. - AllImports.insert_range(Primary->Imports); - // Ones that we import in the global module fragment or the private module - // fragment. - for (Module *SubM : Primary->submodules()) { - assert((SubM->isGlobalModule() || SubM->isPrivateModule()) && - "The sub modules of C++20 module unit should only be global module " - "fragments or private module framents."); - assert(SubM->Exports.empty() && - "The global mdoule fragments and the private module fragments are " - "not allowed to export import modules."); - AllImports.insert_range(SubM->Imports); - } - SmallVector<llvm::Function *, 8> ModuleInits; - for (Module *M : AllImports) { - // No Itanium initializer in header like modules. - if (M->isHeaderLikeModule()) - continue; // TODO: warn of mixed use of module map modules and C++20? - // We're allowed to skip the initialization if we are sure it doesn't - // do any thing. - if (!M->isNamedModuleInterfaceHasInit()) - continue; + for (Module *M : CodeGenUtils::importedModulesToInitialize(Primary, {})) { llvm::FunctionType *FTy = llvm::FunctionType::get(VoidTy, false); SmallString<256> FnName; { @@ -824,7 +799,6 @@ void CodeGenModule::EmitCXXModuleInitFunc(Module *Primary) { } // We are done with the inits. - AllImports.clear(); PrioritizedCXXGlobalInits.clear(); CXXGlobalInits.clear(); ModuleInits.clear(); @@ -865,14 +839,9 @@ CodeGenModule::EmitCXXGlobalInitFunc() { // When we import C++20 modules, we must run their initializers first. SmallVector<llvm::Function *, 8> ModuleInits; if (CXX20ModuleInits) - for (Module *M : ImportedModules) { - // No Itanium initializer in header like modules. - if (M->isHeaderLikeModule()) - continue; - // We're allowed to skip the initialization if we are sure it doesn't - // do any thing. - if (!M->isNamedModuleInterfaceHasInit()) - continue; + for (Module *M : CodeGenUtils::importedModulesToInitialize( + getContext().getCurrentNamedModule(), + ImportedModules.getArrayRef())) { llvm::FunctionType *FTy = llvm::FunctionType::get(VoidTy, false); SmallString<256> FnName; { diff --git a/clang/lib/CodeGenUtils/ModuleUtils.cpp b/clang/lib/CodeGenUtils/ModuleUtils.cpp index 0d4882345e303..621a3a31b1959 100644 --- a/clang/lib/CodeGenUtils/ModuleUtils.cpp +++ b/clang/lib/CodeGenUtils/ModuleUtils.cpp @@ -9,6 +9,7 @@ #include "clang/CodeGenUtils/ModuleUtils.h" #include "clang/AST/Attr.h" #include "clang/Basic/TargetInfo.h" +#include "llvm/ADT/SetVector.h" namespace clang::CodeGenUtils { @@ -111,4 +112,43 @@ bool shouldBeInCOMDAT(const ASTContext &Ctx, const Decl &D) { llvm_unreachable("No such linkage"); } +llvm::SmallVector<Module *, 8> +importedModulesToInitialize(Module *Primary, + llvm::ArrayRef<Module *> Imported) { + llvm::SmallSetVector<Module *, 8> Candidates; + if (Primary && Primary->isInterfaceOrPartition()) { + // The modules the unit exports. + for (auto I : Primary->Exports) + Candidates.insert(I.first); + // The modules it only imports. + Candidates.insert_range(Primary->Imports); + // The modules it imports in the global module fragment or the private + // module fragment. + for (Module *SubM : Primary->submodules()) { + assert( + (SubM->isGlobalModule() || SubM->isPrivateModule()) && + "The sub modules of C++20 module unit should only be global module " + "fragments or private module fragments."); + assert(SubM->Exports.empty() && + "The global module fragments and the private module fragments are " + "not allowed to export import modules."); + Candidates.insert_range(SubM->Imports); + } + } else { + Candidates.insert_range(Imported); + } + + llvm::SmallVector<Module *, 8> Result; + for (Module *M : Candidates) { + // No Itanium initializer in header like modules. + if (M->isHeaderLikeModule()) + continue; + // The initialization may be skipped when it is sure to do nothing. + if (!M->isNamedModuleInterfaceHasInit()) + continue; + Result.push_back(M); + } + return Result; +} + } // namespace clang::CodeGenUtils _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
