https://github.com/dan0907 created https://github.com/llvm/llvm-project/pull/220820
Add the "::<!module-name>" suffix MSVC appends to externally-visible module-owned entities, and the "$$_A<module-name>" marker it adds to a module-owned record referenced as an embedded type (e.g. in RTTI or a cross-module parameter), matching MSVC's mangling. Fixes #174064 From 75f085b4959bc7a04b96b8949e209e726054dd21 Mon Sep 17 00:00:00 2001 From: Dan <[email protected]> Date: Thu, 3 Sep 2026 14:10:07 +0800 Subject: [PATCH] [clang] Mangle C++20 module ownership for the Microsoft ABI Add the "::<!module-name>" suffix MSVC appends to externally-visible module-owned entities, and the "$$_A<module-name>" marker it adds to a module-owned record referenced as an embedded type (e.g. in RTTI or a cross-module parameter), matching MSVC's mangling. Fixes #174064 --- clang/lib/AST/MicrosoftMangle.cpp | 93 ++++- .../Modules/ms-mangle-module-ownership.cppm | 393 ++++++++++++++++++ 2 files changed, 473 insertions(+), 13 deletions(-) create mode 100644 clang/test/Modules/ms-mangle-module-ownership.cppm diff --git a/clang/lib/AST/MicrosoftMangle.cpp b/clang/lib/AST/MicrosoftMangle.cpp index cc7bf2279b72e..fdd176ab7a021 100644 --- a/clang/lib/AST/MicrosoftMangle.cpp +++ b/clang/lib/AST/MicrosoftMangle.cpp @@ -27,6 +27,7 @@ #include "clang/Basic/ABI.h" #include "clang/Basic/DiagnosticAST.h" #include "clang/Basic/DiagnosticOptions.h" +#include "clang/Basic/Module.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" #include "clang/Lex/Preprocessor.h" @@ -342,6 +343,19 @@ class MicrosoftCXXNameMangler { enum QualifierMangleMode { QMM_Drop, QMM_Mangle, QMM_Escape, QMM_Result }; enum class TplArgKind { ClassNTTP, StructuralValue }; + // The module owning the entity `mangle(GlobalDecl, StringRef)` is + // currently mangling the top-level name of, or null if there is none + // (e.g. while mangling RTTI's bare QualType). mangleName consults this + // to decide whether an embedded record-type reference needs a + // "$$_A<module>" tag -- see MangleEmbeddedRecordModule. + Module *MangleContextModule = nullptr; + + // Set by mangleType(TagDecl*) immediately before it calls mangleName, to + // mark that this is an embedded record-type reference rather than a + // top-level entity's own name. Cleared the first time mangleName checks + // it. + bool MangleEmbeddedRecordModule = false; + MicrosoftCXXNameMangler(MicrosoftMangleContextImpl &C, raw_ostream &Out_) : Context(C), Out(Out_), Structor(nullptr), StructorType(-1), TemplateArgStringStorage(TemplateArgStringStorageAlloc), @@ -601,6 +615,11 @@ void MicrosoftCXXNameMangler::mangle(GlobalDecl GD, StringRef Prefix) { // default, we emit an asm marker at the start so we get the name right. // Callers can override this with a custom prefix. + // Record this entity's own module, so an embedded record-type reference + // below can compare against it -- see MangleContextModule. + if (D->isExternallyVisible()) + MangleContextModule = D->getOwningModuleForLinkage(); + // <mangled-name> ::= ? <name> <type-encoding> Out << Prefix; mangleName(GD); @@ -966,6 +985,32 @@ void MicrosoftCXXNameMangler::mangleName(GlobalDecl GD) { mangleNestedName(GD); + if (MangleEmbeddedRecordModule) { + // Fire once, for the record itself, not for enclosing-scope components + // mangleNestedName may have just recursed into. + MangleEmbeddedRecordModule = false; + // <name> gains a "$$_A<module>@@" scope component when this record's + // owning module differs from MangleContextModule (always true for + // RTTI, which has none). The module name shares NameBackReferences + // with ordinary names: a fresh name needs its own name-terminating '@' + // plus the list terminator ("@@"); a back-reference digit has no '@' + // of its own, so it needs only the list terminator ("@"). + if (const auto *ND = dyn_cast<NamedDecl>(GD.getDecl())) + if (Module *M = ND->getOwningModuleForLinkage()) + if (M != MangleContextModule) { + Out << "$$_A"; + StringRef Name = M->getPrimaryModuleInterfaceName(); + BackRefVec::iterator Found = llvm::find(NameBackReferences, Name); + if (Found == NameBackReferences.end()) { + if (NameBackReferences.size() < 10) + NameBackReferences.push_back(std::string(Name)); + Out << Name << "@@"; + } else { + Out << (Found - NameBackReferences.begin()) << '@'; + } + } + } + // Terminate the whole name with an '@'. Out << '@'; } @@ -3465,6 +3510,10 @@ void MicrosoftCXXNameMangler::mangleType(const TagDecl *TD) { const auto *Def = TD->getDefinition(); TD = Def ? Def : TD->getFirstDecl(); mangleTagTypeKind(TD->getTagKind()); + // The only place a record type is mangled as an embedded reference + // (parameter, return type, RTTI's target type, ...) rather than as a + // top-level entity's own name -- see MangleContextModule. + MangleEmbeddedRecordModule = true; mangleName(TD); } @@ -3942,22 +3991,40 @@ void MicrosoftMangleContextImpl::mangleCXXName(GlobalDecl GD, getASTContext().getSourceManager(), "Mangling declaration"); - msvc_hashing_ostream MHO(Out); - - if (auto *CD = dyn_cast<CXXConstructorDecl>(D)) { - auto Type = GD.getCtorType(); - MicrosoftCXXNameMangler mangler(*this, MHO, CD, Type); - return mangler.mangle(GD); + { + msvc_hashing_ostream MHO(Out); + + if (auto *CD = dyn_cast<CXXConstructorDecl>(D)) { + auto Type = GD.getCtorType(); + MicrosoftCXXNameMangler mangler(*this, MHO, CD, Type); + mangler.mangle(GD); + } else if (auto *DD = dyn_cast<CXXDestructorDecl>(D)) { + auto Type = GD.getDtorType(); + MicrosoftCXXNameMangler mangler(*this, MHO, DD, Type); + mangler.mangle(GD); + } else { + MicrosoftCXXNameMangler Mangler(*this, MHO); + Mangler.mangle(GD); + } + // MHO flushes here (raw, or hashed to "??@<md5>@" past the length + // threshold) before the module-ownership suffix below, since MSVC + // appends "::<!name>" after hashing rather than folding it in. } - if (auto *DD = dyn_cast<CXXDestructorDecl>(D)) { - auto Type = GD.getDtorType(); - MicrosoftCXXNameMangler mangler(*this, MHO, DD, Type); - return mangler.mangle(GD); + // <mangled-name> ::= <mangled-name> "::<!" <module-name> ">" + // + // MSVC tags externally-visible module-owned entities with this suffix so + // identically-named entities from different modules stay distinct at + // link time; a module partition's own name is dropped, only the primary + // module name is used. The vector deleting destructor is excluded -- + // unlike the base and scalar-deleting destructors, MSVC leaves it + // unmangled by module ownership. + bool IsVectorDeletingDtor = + isa<CXXDestructorDecl>(D) && GD.getDtorType() == Dtor_VectorDeleting; + if (!IsVectorDeletingDtor && D->isExternallyVisible()) { + if (Module *M = D->getOwningModuleForLinkage()) + Out << "::<!" << M->getPrimaryModuleInterfaceName() << '>'; } - - MicrosoftCXXNameMangler Mangler(*this, MHO); - return Mangler.mangle(GD); } void MicrosoftCXXNameMangler::mangleType(const BitIntType *T, Qualifiers, diff --git a/clang/test/Modules/ms-mangle-module-ownership.cppm b/clang/test/Modules/ms-mangle-module-ownership.cppm new file mode 100644 index 0000000000000..9387363a88bd8 --- /dev/null +++ b/clang/test/Modules/ms-mangle-module-ownership.cppm @@ -0,0 +1,393 @@ +// Windows ABI module-ownership mangling, for +// https://github.com/llvm/llvm-project/issues/174064. +// +// MSVC tags externally-visible module-owned entities with a +// "::<!module-name>" suffix, and a module-owned record referenced as an +// embedded type (e.g. in RTTI, or a cross-module parameter) with a +// "$$_A<module-name>" scope component. Expected encodings below are taken +// from real cl.exe output. +// +// RUN: rm -rf %t +// RUN: mkdir -p %t +// RUN: split-file %s %t +// +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/a.pcm -c %t/a.cppm -o %t/a.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/partition.pcm -c %t/partition.cppm -o %t/partition.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/long.pcm -c %t/long.cppm -o %t/long.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/hashed.pcm -c %t/hashed.cppm -o %t/hashed.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -S -emit-llvm %t/a.cppm -o - | FileCheck %t/a.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -S -emit-llvm %t/partition.cppm -o - | FileCheck %t/partition.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -S -emit-llvm %t/nonmodule.cpp -o - | FileCheck %t/nonmodule.cpp +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -S -emit-llvm %t/long.cppm -o - | FileCheck %t/long.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -S -emit-llvm %t/hashed.cppm -o - | FileCheck %t/hashed.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -S -emit-llvm %t/gmf.cppm -o - | FileCheck %t/gmf.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/modb.pcm -c %t/modb.cppm -o %t/modb.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=b=%t/modb.pcm -S -emit-llvm %t/crossmod.cppm -o - | FileCheck %t/crossmod.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=b=%t/modb.pcm -S -emit-llvm %t/crossmod_val.cppm -o - | FileCheck %t/crossmod_val.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=b=%t/modb.pcm -S -emit-llvm %t/tmplcross.cppm -o - | FileCheck %t/tmplcross.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/modc.pcm -c %t/modc.cppm -o %t/modc.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=c=%t/modc.pcm -S -emit-llvm %t/functmplcross.cppm -o - | FileCheck %t/functmplcross.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/modd.pcm -c %t/modd.cppm -o %t/modd.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=d=%t/modd.pcm -fmodule-output=%t/mode.pcm -c %t/mode.cppm -o %t/mode.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=d=%t/modd.pcm -fmodule-file=e=%t/mode.pcm -S -emit-llvm %t/reexport.cppm -o - | FileCheck %t/reexport.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/modg.pcm -c %t/modg.cppm -o %t/modg.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=g=%t/modg.pcm -S -emit-llvm %t/crossenum.cppm -o - | FileCheck %t/crossenum.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -S -emit-llvm %t/dotmod.cppm -o - | FileCheck %t/dotmod.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/modm.pcm -c %t/gmfreexport.cppm -o %t/gmfreexport.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=m=%t/modm.pcm -S -emit-llvm %t/usegmfreexport.cppm -o - | FileCheck %t/usegmfreexport.cppm +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-output=%t/modparta.pcm -c %t/modparta.cppm -o %t/modparta.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=mod2:parta=%t/modparta.pcm -fmodule-output=%t/modpartb.pcm -c %t/modpartb.cppm -o %t/modpartb.o +// RUN: %clang -std=c++20 --target=x86_64-windows-msvc -fmodule-file=mod2:parta=%t/modparta.pcm -fmodule-file=mod2:partb=%t/modpartb.pcm -S -emit-llvm %t/modaggregate.cppm -o - | FileCheck %t/modaggregate.cppm + +//--- a.cppm +export module a; + +export int func() { return 43; } + +int nonExportedFunc() { return 2; } +static int internalFunc() { return 3; } + +export int gVar = 42; + +export struct Foo { + static int sVal; + int x; + virtual int f() { return 1; } + virtual ~Foo() {} +}; +int Foo::sVal = 1; + +export int takesFoo(Foo p) { return p.x; } +export Foo makesFoo() { return Foo(); } + +// A class template instantiation is owned by the template's own module, so +// TBox<int> (same module "a" as its use here) mangles like any other +// same-module type -- unmarked when embedded, but still tagged in RTTI. +export template <typename T> +struct TBox { + T val; + T get() { return val; } + virtual ~TBox() {} +}; +export int useTBox() { + TBox<int> b; + b.val = 1; + return b.get(); +} +export TBox<int> makesTBox() { return TBox<int>(); } + +// Referencing internalFunc keeps it from being discarded as unused, without +// changing its (internal) linkage. +export int useInternal() { return internalFunc(); } + +// Module linkage (plain or non-exported) gets the suffix; true internal +// linkage (`static`) does not. A struct passed by value only tags the +// enclosing function's own name, not the embedded parameter/return type +// (same module). Base and scalar-deleting dtors get the suffix; +// vector-deleting does not. RTTI's type descriptor gets "$$_A<module>@@" +// instead of the plain suffix. +// +// CHECK-DAG lines are kept in one contiguous block (FileCheck's DAG search +// floor) with CHECK-NOTs moved after, since a CHECK-NOT between DAG groups +// would skip matches appearing before it in the IR. +// CHECK-DAG: define dso_local {{.*}} @"?func@@YAHXZ::<!a>"( +// CHECK-DAG: define dso_local {{.*}} @"?nonExportedFunc@@YAHXZ::<!a>"( +// CHECK-DAG: define internal {{.*}} @"?internalFunc@@YAHXZ"( +// CHECK-DAG: @"?gVar@@3HA::<!a>" = +// CHECK-DAG: @"?sVal@Foo@@2HA::<!a>" = +// CHECK-DAG: define {{.*}} @"?takesFoo@@YAHUFoo@@@Z::<!a>"( +// CHECK-DAG: define {{.*}} @"?makesFoo@@YA?AUFoo@@XZ::<!a>"( +// CHECK-DAG: define {{.*}} @"??1Foo@@UEAA@XZ::<!a>"( +// CHECK-DAG: define {{.*}} @"??_GFoo@@UEAAPEAXI@Z::<!a>"( +// CHECK-DAG: @"??_R0?AUFoo@$$_Aa@@@@8" = +// CHECK-DAG: define {{.*}} @"?get@?$TBox@H@@QEAAHXZ::<!a>"( +// CHECK-DAG: define {{.*}} @"??1?$TBox@H@@UEAA@XZ::<!a>"( +// CHECK-DAG: define {{.*}} @"?makesTBox@@YA?AU?$TBox@H@@XZ::<!a>"( +// CHECK-DAG: @"??_R0?AU?$TBox@H@$$_Aa@@@@8" = +// +// The vftable and other RTTI symbols (Complete Object Locator, Class +// Hierarchy Descriptor, Base Class Array, Base Class Descriptor) get no +// module suffix at all -- only the type descriptor does. +// CHECK-DAG: @"??_7Foo@@6B@" = +// CHECK-DAG: @"??_R4Foo@@6B@" = +// CHECK-DAG: @"??_R3Foo@@8" = +// CHECK-DAG: @"??_R2Foo@@8" = +// CHECK-DAG: @"??_R1A@?0A@EA@Foo@@8" = +// CHECK-NOT: internalFunc{{.*}}::<! +// CHECK-NOT: "??_EFoo@@UEAAPEAXI@Z::<! + +//--- partition.cppm +export module a:b; + +export int partFunc() { return 1; } + +export struct Foo { + virtual int f() { return 1; } + virtual ~Foo() {} +}; +Foo *make() { return new Foo(); } + +// A module partition's suffix drops the partition part entirely: only the +// primary module name is used, both for the plain suffix and for RTTI. +// CHECK-DAG: define {{.*}} @"?partFunc@@YAHXZ::<!a>"( +// CHECK-DAG: @"??_R0?AUFoo@$$_Aa@@@@8" = + +//--- nonmodule.cpp +struct Foo { + virtual int f() { return 1; } + virtual ~Foo() {} +}; +Foo *make() { return new Foo(); } + +// No module ownership at all -> ordinary, unmodified RTTI type descriptor. +// CHECK: @"??_R0?AUFoo@@@8" = +// CHECK-NOT: $$_A + +//--- long.cppm +export module abcdefgh; + +export struct Foo { + virtual int f() { return 1; } + virtual ~Foo() {} +}; +Foo *make() { return new Foo(); } + +// The RTTI marker uses the module name raw, with no length prefix. +// CHECK: @"??_R0?AUFoo@$$_Aabcdefgh@@@@8" = + +//--- hashed.cppm +module; +// Enough repeated "int" parameters (each mangles as a single 'H') to push +// wideFunc's mangled name past msvc_hashing_ostream's 4096-char threshold; +// doubling macros keep the source short while generating thousands of +// tokens in the parameter list. +#define I1 int +#define I2 I1, I1 +#define I4 I2, I2 +#define I8 I4, I4 +#define I16 I8, I8 +#define I32 I16, I16 +#define I64 I32, I32 +#define I128 I64, I64 +#define I256 I128, I128 +#define I512 I256, I256 +#define I1024 I512, I512 +#define I2048 I1024, I1024 +#define I4096 I2048, I2048 +#define I4608 I4096, I512 +export module a; + +export int wideFunc(I4608) { return 0; } + +// Past the hashing threshold the whole mangled name is replaced with +// "??@<md5>@", but the module suffix is still appended after that hash, +// not folded into it. +// CHECK: define {{.*}} @"??@{{[0-9a-f]+}}@::<!a>"( + +//--- gmf_common.h +int gmfFunc() { return 1; } +struct GmfFoo { + virtual int f() { return 1; } + virtual ~GmfFoo() {} +}; + +//--- gmf.cppm +module; +#include "gmf_common.h" +export module a; + +export int useGmf() { return gmfFunc(); } +export GmfFoo *makeGmfFoo() { return new GmfFoo(); } + +// Global-module-fragment entities (reached via #include before the module +// declaration) are not module-owned -- only the module-purview functions +// referencing them get the suffix. +// CHECK-DAG: define {{.*}} @"?useGmf@@YAHXZ::<!a>"( +// CHECK-DAG: define {{.*}} @"?makeGmfFoo@@YAPEAUGmfFoo@@XZ::<!a>"( +// CHECK-DAG: define {{.*}} @"?gmfFunc@@YAHXZ"( +// CHECK-DAG: define {{.*}} @"?f@GmfFoo@@UEAAHXZ"( +// CHECK-DAG: define {{.*}} @"??1GmfFoo@@UEAA@XZ"( +// CHECK-DAG: @"??_R0?AUGmfFoo@@@8" = +// CHECK-NOT: gmfFunc{{.*}}::<! +// CHECK-NOT: GmfFoo{{.*}}::<! +// CHECK-NOT: $$_A + +//--- modb.cppm +export module b; + +export struct BFoo { + virtual int f() { return 1; } + virtual ~BFoo() {} +}; + +export template <typename T> +struct Box { + T val; + T get() { return val; } + virtual ~Box() {} +}; + +//--- crossmod.cppm +export module a; +import b; + +export BFoo *takesB(BFoo *p) { return p; } + +// A cross-module record gets the "$$_A<module>" marker even in an ordinary +// function signature, not just RTTI, and it shares the ordinary name +// back-reference table: BFoo* appears as both return type and parameter, +// so the second occurrence is a back-reference digit ("$$_A2") rather than +// the name again -- and needs one fewer trailing '@' than a fresh name. +// CHECK: define {{.*}} @"?takesB@@YAPEAUBFoo@$$_Ab@@@PEAU1$$_A2@@@Z::<!a>"( + +//--- crossmod_val.cppm +export module a; +import b; + +export int takesB2(BFoo p, BFoo q) { return 0; } + +// The same cross-module type by value twice: MS mangling's separate +// whole-argument-type substitution table recognizes the repeat and skips +// re-mangling it (back-reference "0"), never touching the module-name +// back-reference table at all. +// CHECK: define {{.*}} @"?takesB2@@YAHUBFoo@$$_Ab@@@0@Z::<!a>"( + +//--- tmplcross.cppm +export module a; +import b; + +export int useBox() { + Box<int> b; + b.val = 1; + return b.get(); +} +export Box<int> makeBox() { return Box<int>(); } + +// Box<int> here is owned by "b" (the template's module), not "a" -- its +// member functions get "::<!b>", and referencing it from makeBox (owned by +// "a") now crosses modules, so it also gets the "$$_Ab" embedded marker +// and an "b"-tagged RTTI descriptor. +// CHECK-DAG: define {{.*}} @"?useBox@@YAHXZ::<!a>"( +// CHECK-DAG: define {{.*}} @"?get@?$Box@H@@QEAAHXZ::<!b>"( +// CHECK-DAG: define {{.*}} @"??1?$Box@H@@UEAA@XZ::<!b>"( +// CHECK-DAG: define {{.*}} @"?makeBox@@YA?AU?$Box@H@$$_Ab@@@XZ::<!a>"( +// CHECK-DAG: @"??_R0?AU?$Box@H@$$_Ab@@@@8" = + +//--- modc.cppm +export module c; + +export template <typename T> +T identity(T v) { return v; } + +//--- functmplcross.cppm +export module a2; +import c; + +export int useIdentity() { return identity(5); } + +// Function templates follow the same rule: identity<int> is owned by "c" +// (the template's module), the caller by "a2". +// CHECK-DAG: define {{.*}} @"?useIdentity@@YAHXZ::<!a2>"( +// CHECK-DAG: define {{.*}} @"??$identity@H@@YAHH@Z::<!c>"( + +//--- modd.cppm +export module d; + +export struct DFoo { + virtual int f() { return 1; } + virtual ~DFoo() {} +}; +export int dFunc() { return 1; } + +//--- mode.cppm +export module e; +export import d; + +//--- reexport.cppm +export module f; +import e; + +export DFoo *useDFoo(DFoo *p) { return p; } +export int useDFunc() { return dFunc(); } + +// "f" imports "d" only indirectly, through "e"'s "export import d;". A +// re-export never changes an entity's true owning module: DFoo and dFunc +// still mangle as owned by "d", not "e". +// CHECK-DAG: define {{.*}} @"?useDFoo@@YAPEAUDFoo@$$_Ad@@@PEAU1$$_A2@@@Z::<!f>"( +// CHECK-DAG: define {{.*}} @"?useDFunc@@YAHXZ::<!f>"( +// CHECK-DAG: declare {{.*}} @"?dFunc@@YAHXZ::<!d>"( + +//--- modg.cppm +export module g; + +export enum class Color { Red, Green, Blue }; + +//--- crossenum.cppm +export module h; +import g; + +export Color useColor(Color c) { return c; } + +// mangleType(const TagDecl*) is the shared choke point for both records +// and enums, so a cross-module enum gets the same embedded marker a struct +// would (just with the "W4" tag-kind letter instead of "U"). +// CHECK: define {{.*}} @"?useColor@@YA?AW4Color@$$_Ag@@@W41$$_A2@@@Z::<!h>"( + +//--- dotmod.cppm +export module FOO.BAR; + +export int func() { return 1; } + +// A dot in a module name has no special meaning to MSVC's mangler: unlike +// Itanium (which mangles "FOO.BAR" as two separately-substitutable +// <module-subname> components), MSVC emits the primary module name as one +// literal string. +// CHECK: define {{.*}} @"?func@@YAHXZ::<!FOO.BAR>"( + +//--- gmfreexport.cppm +module; +int gmfFunc() { return 1; } +export module m; + +export using ::gmfFunc; + +//--- usegmfreexport.cppm +export module n; +import m; + +export int useIt() { return gmfFunc(); } + +// "export using" re-exporting a GMF entity does not retroactively give it +// module ownership -- gmfFunc still mangles with no suffix, same as an +// unexported GMF reference. +// CHECK-DAG: define {{.*}} @"?useIt@@YAHXZ::<!n>"( +// CHECK-DAG: declare {{.*}} @"?gmfFunc@@YAHXZ"( + +//--- modparta.cppm +export module mod2:parta; + +export int a2 = 43; +export int foo2() { return 3 + a2; } + +//--- modpartb.cppm +module mod2:partb; + +int b2 = 43; +int bar2() { return 43 + b2; } + +//--- modaggregate.cppm +export module mod2; +import :parta; +import :partb; + +export int use2() { return foo2() + bar2() + a2 + b2; } + +// A primary module interface aggregating an exported partition (":parta") +// and an internal one (":partb") across a real multi-file build: both +// still mangle with the primary module name only, export status making no +// difference. +// CHECK-DAG: declare {{.*}} @"?foo2@@YAHXZ::<!mod2>"( +// CHECK-DAG: declare {{.*}} @"?bar2@@YAHXZ::<!mod2>"( +// CHECK-DAG: define {{.*}} @"?use2@@YAHXZ::<!mod2>"( +// CHECK-DAG: @"?a2@@3HA::<!mod2>" = +// CHECK-DAG: @"?b2@@3HA::<!mod2>" = _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
