https://github.com/koparasy created https://github.com/llvm/llvm-project/pull/225565
Compiling a C++20 named module interface unit for the Microsoft C++ ABI asserts in `LoweringPrepare`, because the AST fallback for the initializer name does an unguarded `cast<ItaniumMangleContext>` ([LoweringPrepare.cpp#L2006-L2013](https://github.com/llvm/llvm-project/blob/97458f644132d62bb938823394b716b684956f49/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp#L2006-L2013)). That fallback is reachable because CIRGen only emits `cir.cxx_module_init_fn_name` for the Itanium mangler ([CIRGenModule.cpp#L3985-L3996](https://github.com/llvm/llvm-project/blob/97458f644132d62bb938823394b716b684956f49/clang/lib/CIR/CodeGen/CIRGenModule.cpp#L3985-L3996)), mirroring classic codegen's `CXX20ModuleInits` ([CodeGenModule.cpp#L596-L601](https://github.com/llvm/llvm-project/blob/97458f644132d62bb938823394b716b684956f49/clang/lib/CodeGen/CodeGenModule.cpp#L596-L601)). This deletes the fallback so the attribute is the only channel: absent means `_GLOBAL__sub_I_` with internal linkage, which is exactly what classic codegen does for this ABI ([CodeGenModule.cpp#L1205-L1208](https://github.com/llvm/llvm-project/blob/97458f644132d62bb938823394b716b684956f49/clang/lib/CodeGen/CodeGenModule.cpp#L1205-L1208)). The new test pins that lowering and fails with the same assertion without the fix. It checks CIR rather than LLVM IR because Microsoft-ABI CIR cannot reach LLVM IR yet (`EH ABI lowering is not yet implemented for the Microsoft ABI`). This also drops the last `ASTContext` use in `buildCXXGlobalInitFunc`. >From 6b5cc699e370080b1588b2a20980cec4e68c7ad2 Mon Sep 17 00:00:00 2001 From: Konstantinos Parasyris <[email protected]> Date: Tue, 22 Sep 2026 17:32:52 -0700 Subject: [PATCH] [CIR] Fix assertion on Microsoft-ABI C++20 named module initializer --- clang/lib/CIR/CodeGen/CIRGenModule.cpp | 15 +++++- .../Dialect/Transforms/LoweringPrepare.cpp | 18 ++------ .../cxx20-module-initializer-ms-abi.cppm | 46 +++++++++++++++++++ 3 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 clang/test/CIR/CodeGen/cxx20-module-initializer-ms-abi.cppm diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index c644edda897a3..61d5b03f393b0 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -3980,8 +3980,19 @@ 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. + // stash it on the ModuleOp so LoweringPrepare (which runs without a live + // ASTContext) can read it back as an attribute. This attribute is the only + // channel through which the named-module initializer reaches lowering: its + // presence tells LoweringPrepare both what to call the global-init function + // 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 (langOpts.CPlusPlusModules && getCXXABI().getMangleContext().getKind() == clang::ItaniumMangleContext::MK_Itanium) { diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index 2ab71608329ab..bca424b4a1411 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -14,9 +14,7 @@ #include "mlir/IR/Location.h" #include "mlir/IR/Value.h" #include "clang/AST/ASTContext.h" -#include "clang/AST/Mangle.h" #include "clang/Basic/Cuda.h" -#include "clang/Basic/Module.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" #include "clang/Basic/TargetCXXABI.h" @@ -1996,21 +1994,15 @@ void LoweringPreparePass::buildCXXGlobalInitFunc() { // 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, 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). + // 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 (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()); - cast<clang::ItaniumMangleContext>(*mangleCtx) - .mangleModuleInitializer(astCtx->getCurrentNamedModule(), out); - linkage = cir::GlobalLinkageKind::ExternalLinkage; } else { fnName += "_GLOBAL__sub_I_"; fnName += getTransformedFileName(mlirModule); diff --git a/clang/test/CIR/CodeGen/cxx20-module-initializer-ms-abi.cppm b/clang/test/CIR/CodeGen/cxx20-module-initializer-ms-abi.cppm new file mode 100644 index 0000000000000..56df8dcb6c9f8 --- /dev/null +++ b/clang/test/CIR/CodeGen/cxx20-module-initializer-ms-abi.cppm @@ -0,0 +1,46 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-windows-msvc -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --input-file=%t.cir %s --check-prefix=CIR --implicit-check-not=_ZGIW + +// Classic codegen only enables C++20 module initializers for the Itanium +// mangler, because no Microsoft mangling for them has been settled on yet (see +// CXX20ModuleInits in CodeGenModule.cpp). A named module built for the +// Microsoft C++ ABI therefore gets the ordinary `_GLOBAL__sub_I_` initializer +// with internal linkage, exactly as a non-modular translation unit would, and +// CIRGen matches that by only emitting the cir.cxx_module_init_fn_name +// attribute for the Itanium mangler. +// +// LoweringPrepare used to rediscover the named module from the AST whenever +// that attribute was absent, and mangled the name with an unguarded +// cast<ItaniumMangleContext>, which asserted for this target. The attribute is +// now the only channel through which the named-module initializer reaches +// lowering, so this compiles rather than crashing. +// +// For reference, classic codegen emits for this input: +// @llvm.global_ctors = ... { i32 65535, ptr @_GLOBAL__sub_I_<file>, ptr null } +// define internal void @"??__Ex@@YAXXZ"() { %call = call @"?foo@@YAHXZ"() +// store i32 %call, ptr @"?x@@3HA" } +// define internal void @_GLOBAL__sub_I_<file>() { call void @"??__Ex@@YAXXZ"() } +// CIR does not yet apply the Microsoft dynamic-initializer mangling to the +// per-variable initializer, naming it __cxx_global_var_init instead; that gap +// is unrelated to named modules and reproduces for a non-modular TU too. + +export module A; + +int foo(); +int x = foo(); + +// The Itanium-only attribute must not be emitted for the Microsoft mangler. +// CIR-NOT: cir.cxx_module_init_fn_name + +// CIR: cir.global_ctors = [#cir.global_ctor<"_GLOBAL__sub_I_{{.*}}", 65535>] + +// CIR: cir.func internal private @__cxx_global_var_init() +// CIR: %[[X:.*]] = cir.get_global @"?x@@3HA" +// CIR: %[[CALL:.*]] = cir.call @"?foo@@YAHXZ"() +// CIR: cir.store align(4) %[[CALL]], %[[X]] + +// The fallback initializer has internal linkage, unlike the external-linkage +// initializer a named-module interface unit gets under the Itanium mangler, and +// it just calls the per-variable initializer. +// CIR: cir.func internal private @_GLOBAL__sub_I_ +// CIR-NEXT: cir.call @__cxx_global_var_init() _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
