[clang] [CIR] Fix assertion on Microsoft-ABI C++20 named module initializer (PR #225565)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Sep 22 18:14:51 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Konstantinos Parasyris (koparasy)
<details>
<summary>Changes</summary>
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`.
---
Full diff: https://github.com/llvm/llvm-project/pull/225565.diff
3 Files Affected:
- (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+13-2)
- (modified) clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp (+5-13)
- (added) clang/test/CIR/CodeGen/cxx20-module-initializer-ms-abi.cppm (+46)
``````````diff
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 5d682296a2376..2abe5a82c49e6 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()
``````````
</details>
https://github.com/llvm/llvm-project/pull/225565
More information about the cfe-commits
mailing list