https://github.com/keryell updated https://github.com/llvm/llvm-project/pull/229648
>From e99e8bd7cea83fc7b36f92708d65b6f83b5feaff Mon Sep 17 00:00:00 2001 From: Ronan Keryell <[email protected]> Date: Tue, 6 Oct 2026 18:44:34 -0700 Subject: [PATCH] [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 68e15b84cea1658..7c58080e49b3fc1 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 5e1a98a15ad40c3..daca056079abe13 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); @@ -2542,6 +2547,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; @@ -3961,9 +4018,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; @@ -3975,6 +4030,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 d17dcb472f30715..d6974c65fd1cdbe 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 000000000000000..dbac152b7ba129f --- /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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
