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

Reply via email to