[llvm] [DWARFLinker] Resolve a module import through the module's anchor (PR #218575)
Jonas Devlieghere via llvm-commits
llvm-commits at lists.llvm.org
Mon Aug 24 19:59:54 PDT 2026
https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/218575
An importing compile unit emits its own DW_TAG_module skeleton, and that skeleton can name a different DW_AT_LLVM_include_path than the unit built from the .pcm. For example, a Swift unit names the .swiftmodule while the companion Clang module names the framework bundle.
The classic linker gets this from DeclContext uniquing. f03b9d709525 made the parallel linker's type pool pick the right copy, but the type pool only covers ODR languages. This addresses the non-ODR case.
Add a link-global ModulePool, using the dotted module path as the key. A module unit records where it put each DW_TAG_module, either as a type entry or as a section + offset. Because the import may not exist during cloning, we emit a placeholder and only emit it when the anchor is fully resolved.
A .pcm holds the description of the module it was built for, and also a partial copy of every module that it imports. Only the first is the canonical description, so a unit only claims itself as the target for that module, and not for the ones it imports.
The same module can also be built more than once, with different compile flags. Every .pcm gets a unit, so two units can describe the same module. To make this deterministic, the one that came earlier in the link wins, which matches what we do for identical types.
An importer whose own copy already sits in the type pool needs none of this, because the pool has already uniqued it.
rdar://185156929
Assisted-by: Claude
>From 0f248f297c8e5005ff04236c1b29faea000cfa77 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Mon, 24 Aug 2026 15:32:51 -0700
Subject: [PATCH] [DWARFLinker] Resolve a module import through the module's
anchor
An importing compile unit emits its own DW_TAG_module skeleton, and that
skeleton can name a different DW_AT_LLVM_include_path than the unit
built from the .pcm. For example, a Swift unit names the .swiftmodule
while the companion Clang module names the framework bundle.
The classic linker gets this from DeclContext uniquing. f03b9d709525
made the parallel linker's type pool pick the right copy, but the type
pool only covers ODR languages. This addresses the non-ODR case.
Add a link-global ModulePool, using the dotted module path as the key. A
module unit records where it put each DW_TAG_module, either as a type
entry or as a section + offset. Because the import may not exist during
cloning, we emit a placeholder and only emit it when the anchor is fully
resolved.
A .pcm holds the description of the module it was built for, and also a
partial copy of every module that it imports. Only the first is the
canonical description, so a unit only claims itself as the target for
that module, and not for the ones it imports.
The same module can also be built more than once, with different compile
flags. Every .pcm gets a unit, so two units can describe the same
module. To make this deterministic, the one that came earlier in the
link wins, which matches what we do for identical types.
An importer whose own copy already sits in the type pool needs none of
this, because the pool has already uniqued it.
rdar://185156929
Assisted-by: Claude
---
.../Parallel/DIEAttributeCloner.cpp | 25 +++++
.../Parallel/DWARFLinkerCompileUnit.cpp | 87 ++++++++++++++++
.../Parallel/DWARFLinkerCompileUnit.h | 10 ++
.../Parallel/DWARFLinkerGlobalData.h | 4 +
.../DWARFLinker/Parallel/DWARFLinkerImpl.cpp | 7 ++
llvm/lib/DWARFLinker/Parallel/ModulePool.h | 73 ++++++++++++++
.../DWARFLinker/Parallel/OutputSections.cpp | 29 ++++++
.../lib/DWARFLinker/Parallel/OutputSections.h | 16 +++
.../Inputs/module-import-canonical/2.ll | 36 +++++++
.../Inputs/module-import-canonical/M.ll | 25 +++++
.../debug-map-duplicate.map | 10 ++
.../module-import-canonical/debug-map.map | 10 ++
.../Inputs/module-import-canonical/dup-a.ll | 24 +++++
.../Inputs/module-import-canonical/dup-b.ll | 24 +++++
.../Inputs/module-import-canonical/dup1.ll | 33 +++++++
.../Inputs/module-import-canonical/dup2.ll | 33 +++++++
.../module-import-canonical-duplicate.test | 47 +++++++++
.../dsymutil/X86/module-import-canonical.test | 99 +++++++++++++++++++
llvm/test/tools/dsymutil/X86/modules.m | 28 ++++++
llvm/test/tools/dsymutil/X86/submodules.m | 22 ++++-
20 files changed, 639 insertions(+), 3 deletions(-)
create mode 100644 llvm/lib/DWARFLinker/Parallel/ModulePool.h
create mode 100644 llvm/test/tools/dsymutil/Inputs/module-import-canonical/2.ll
create mode 100644 llvm/test/tools/dsymutil/Inputs/module-import-canonical/M.ll
create mode 100644 llvm/test/tools/dsymutil/Inputs/module-import-canonical/debug-map-duplicate.map
create mode 100644 llvm/test/tools/dsymutil/Inputs/module-import-canonical/debug-map.map
create mode 100644 llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup-a.ll
create mode 100644 llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup-b.ll
create mode 100644 llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup1.ll
create mode 100644 llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup2.ll
create mode 100644 llvm/test/tools/dsymutil/X86/module-import-canonical-duplicate.test
create mode 100644 llvm/test/tools/dsymutil/X86/module-import-canonical.test
diff --git a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
index 93104e0ee3153..68d7b9d72cf61 100644
--- a/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DIEAttributeCloner.cpp
@@ -253,6 +253,31 @@ size_t DIEAttributeCloner::cloneDieRefAttr(
if (RefDIEInfo.needToPlaceInTypeTable())
RefTypeName = RefDiePair->CU->getDieTypeEntry(RefDiePair->DieEntry);
+ // The importing unit's DW_TAG_module skeleton can have a different
+ // DW_AT_LLVM_include_path than the unit built from the .pcm and the import
+ // must use the latter. The type pool already merges copies, so a reference
+ // with a type entry resolves correctly. Without one, the module's anchor is
+ // the only link between the two, and it is not known until the unit
+ // describing the module has been emitted.
+ if (RefDiePair->DieEntry->getTag() == dwarf::DW_TAG_module &&
+ AttrSpec.Attr == dwarf::DW_AT_import && !OutUnit.isTypeUnit() &&
+ !RefTypeName) {
+ SmallString<128> Path;
+ if (RefDiePair->CU->getModulePath(RefDiePair->DieEntry, Path)) {
+ ModuleAnchor *Anchor =
+ InUnit.getGlobalData().getModulePool().getOrCreate(Path);
+
+ DebugInfoOutputSection.notePatchWithOffsetUpdate(
+ DebugDieModuleRefPatch{
+ AttrOutOffset, RefDiePair->CU,
+ RefDiePair->CU->getDIEIndex(RefDiePair->DieEntry), Anchor},
+ PatchesOffsets);
+ return Generator
+ .addScalarAttribute(AttrSpec.Attr, dwarf::DW_FORM_ref_addr, 0xBADDEF)
+ .second;
+ }
+ }
+
if (OutUnit.isTypeUnit()) {
assert(RefTypeName && "Type name for referenced DIE is not set");
assert(InUnit.getDieTypeEntry(InputDIEIdx) &&
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
index fb938b5bffbec..c623091cc460b 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
@@ -256,6 +256,84 @@ void CompileUnit::cleanupDataAfterClonning() {
getOrigUnit().clear();
}
+bool CompileUnit::getModulePath(const DWARFDebugInfoEntry *DieEntry,
+ SmallVectorImpl<char> &Path) {
+ assert(DieEntry->getTag() == dwarf::DW_TAG_module);
+
+ SmallVector<StringRef, 4> Names;
+ for (const DWARFDebugInfoEntry *CurEntry = DieEntry;
+ CurEntry && CurEntry->getTag() == dwarf::DW_TAG_module;
+ CurEntry = getParent(CurEntry).getDebugInfoEntry()) {
+ StringRef Name = dwarf::toStringRef(find(CurEntry, dwarf::DW_AT_name));
+ if (Name.empty())
+ return false;
+ Names.push_back(Name);
+ }
+
+ for (StringRef Name : reverse(Names)) {
+ Path.append(Name.begin(), Name.end());
+ Path.push_back('\0');
+ }
+
+ return true;
+}
+
+void CompileUnit::noteModuleAnchors() {
+ // A module unit carries a skeleton for each module it imports alongside the
+ // module it was built from, and those skeletons describe their module no
+ // better than any importer's. Only the latter subtree is authoritative.
+ const DWARFDebugInfoEntry *Root = nullptr;
+ for (const DWARFDebugInfoEntry *CurChild =
+ getFirstChildEntry(getDebugInfoEntry(0));
+ CurChild && CurChild->getAbbreviationDeclarationPtr();
+ CurChild = getSiblingEntry(CurChild)) {
+ if (CurChild->getTag() == dwarf::DW_TAG_module &&
+ dwarf::toStringRef(find(CurChild, dwarf::DW_AT_name)) ==
+ getClangModuleName()) {
+ Root = CurChild;
+ break;
+ }
+ }
+ if (!Root)
+ return;
+
+ SmallString<128> Path;
+
+ // Submodules nest inside the module which declares them, so following the
+ // DW_TAG_module chain down from the root visits every module this unit
+ // describes, without walking the types they contain.
+ SmallVector<const DWARFDebugInfoEntry *, 4> WorkList = {Root};
+ while (!WorkList.empty()) {
+ const DWARFDebugInfoEntry *DieEntry = WorkList.pop_back_val();
+
+ for (const DWARFDebugInfoEntry *CurChild = getFirstChildEntry(DieEntry);
+ CurChild && CurChild->getAbbreviationDeclarationPtr();
+ CurChild = getSiblingEntry(CurChild))
+ if (CurChild->getTag() == dwarf::DW_TAG_module)
+ WorkList.push_back(CurChild);
+
+ Path.clear();
+ if (!getModulePath(DieEntry, Path))
+ continue;
+
+ ModuleAnchor Anchor;
+ Anchor.Priority = getPriority();
+ if (getDIEInfo(DieEntry).needToPlaceInTypeTable())
+ Anchor.TypeName = getDieTypeEntry(DieEntry);
+ if (!Anchor.TypeName) {
+ // Don't create a dangling reference to a DIE without a type entry or
+ // output offset.
+ uint64_t OutOffset = getDieOutOffset(DieEntry);
+ if (!OutOffset)
+ continue;
+ Anchor.Section = &getSectionDescriptor(DebugSectionKind::DebugInfo);
+ Anchor.LocalOffset = OutOffset;
+ }
+
+ getGlobalData().getModulePool().set(Path, Anchor);
+ }
+}
+
/// Collect references to parseable Swift interfaces in imported
/// DW_TAG_module blocks.
void CompileUnit::analyzeImportedModule(const DWARFDebugInfoEntry *DieEntry) {
@@ -369,6 +447,15 @@ void CompileUnit::updateDieRefPatchesWithClonedOffsets() {
Patch.RefCU.getPointer()->getDieOutOffset(
Patch.RefDieIdxOrClonedOffset);
});
+
+ (*DebugInfoSection)
+ ->ListDebugDieModuleRefPatch.forEach(
+ [&](DebugDieModuleRefPatch &Patch) {
+ /// Replace stored DIE indexes with DIE output offsets.
+ Patch.RefDieIdxOrClonedOffset =
+ Patch.RefCU.getPointer()->getDieOutOffset(
+ Patch.RefDieIdxOrClonedOffset);
+ });
}
if (std::optional<SectionDescriptor *> DebugLocSection =
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
index e8fc4b91bfd45..c8c1b8822aa64 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
@@ -114,6 +114,16 @@ class alignas(8) CompileUnit : public DwarfUnit {
/// Returns DWARFFile containing this compile unit.
const DWARFFile &getContainingFile() const { return File; }
+ /// Appends the names of the DW_TAG_module enclosing \p DieEntry, outermost
+ /// first. Returns false when one of them has no name making the module
+ /// unidentifiable across units.
+ bool getModulePath(const DWARFDebugInfoEntry *DieEntry,
+ SmallVectorImpl<char> &Path);
+
+ /// Must run while the output offsets are still available, and once they are
+ /// final.
+ void noteModuleAnchors();
+
/// Set deterministic priority for type DIE allocation ordering. Units compare
/// by \p ObjFileIdx first and by \p LocalIdx second.
/// Lower priority values win when multiple CUs race to define the same type.
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerGlobalData.h b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerGlobalData.h
index b47dd5ee58435..b65c5d809b1d1 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerGlobalData.h
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerGlobalData.h
@@ -9,6 +9,7 @@
#ifndef LLVM_LIB_DWARFLINKER_PARALLEL_DWARFLINKERGLOBALDATA_H
#define LLVM_LIB_DWARFLINKER_PARALLEL_DWARFLINKERGLOBALDATA_H
+#include "ModulePool.h"
#include "TypePool.h"
#include "llvm/DWARFLinker/Parallel/DWARFLinker.h"
#include "llvm/DWARFLinker/StringPool.h"
@@ -91,6 +92,8 @@ class LinkingGlobalData {
/// Returns global string pool.
StringPool &getStringPool() { return Strings; }
+ ModulePool &getModulePool() { return Modules; }
+
/// Returns linking options.
const DWARFLinkerOptions &getOptions() const { return Options; }
@@ -144,6 +147,7 @@ class LinkingGlobalData {
protected:
llvm::parallel::PerThreadBumpPtrAllocator Allocator;
StringPool Strings;
+ ModulePool Modules;
DWARFLinkerOptions Options;
MessageHandlerTy WarningHandler;
MessageHandlerTy ErrorHandler;
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerImpl.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerImpl.cpp
index 0999e58ef1a5d..82f912fc4eb49 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerImpl.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerImpl.cpp
@@ -718,6 +718,13 @@ void DWARFLinkerImpl::LinkContext::linkSingleCompileUnit(
case CompileUnit::Stage::Cloned:
// Update DIEs referencies.
CU.updateDieRefPatchesWithClonedOffsets();
+
+ // Later than cloning, so that the offsets are final, and no later,
+ // because a unit which got this far can no longer be skipped and have
+ // its section dropped from the output.
+ if (CU.isClangModule())
+ CU.noteModuleAnchors();
+
CU.setStage(CompileUnit::Stage::PatchesUpdated);
break;
diff --git a/llvm/lib/DWARFLinker/Parallel/ModulePool.h b/llvm/lib/DWARFLinker/Parallel/ModulePool.h
new file mode 100644
index 0000000000000..7217780e17762
--- /dev/null
+++ b/llvm/lib/DWARFLinker/Parallel/ModulePool.h
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIB_DWARFLINKER_PARALLEL_MODULEPOOL_H
+#define LLVM_LIB_DWARFLINKER_PARALLEL_MODULEPOOL_H
+
+#include "TypePool.h"
+#include "llvm/ADT/StringMap.h"
+#include <limits>
+#include <mutex>
+
+namespace llvm {
+namespace dwarf_linker {
+namespace parallel {
+
+struct SectionDescriptor;
+
+/// Where the DW_TAG_module DIE describing a clang module ended up in the
+/// output. Clang imposes a one-definition rule (ODR) on module names regardless
+/// of the source language, so a module is described once and every importer
+/// resolves to that description.
+///
+/// A module DIE lands either in the artificial type unit, or in the plain DWARF
+/// of the unit which emitted it.
+struct ModuleAnchor {
+ bool isSet() const { return TypeName != nullptr || Section != nullptr; }
+
+ TypeEntry *TypeName = nullptr;
+ SectionDescriptor *Section = nullptr;
+ uint64_t LocalOffset = 0;
+
+ /// Priority of the unit which recorded this anchor.
+ uint64_t Priority = std::numeric_limits<uint64_t>::max();
+};
+
+class ModulePool {
+public:
+ ModuleAnchor *getOrCreate(StringRef Path) {
+ std::lock_guard<std::mutex> Guard(Mutex);
+
+ // The underlying StringMap guarantees the pointer remains stable. The
+ // pointee is written while cloning, so it may only be read afterwards.
+ return &Anchors[Path];
+ }
+
+ void set(StringRef Path, const ModuleAnchor &Location) {
+ std::lock_guard<std::mutex> Guard(Mutex);
+
+ // Anchors are keyed by module name while a unit is created per .pcm, so a
+ // module built more than once has a unit for each copy. The lowest priority
+ // wins, as in the type pool, guaranteeing determinism.
+ ModuleAnchor &Anchor = Anchors[Path];
+ if (!Anchor.isSet() || Location.Priority < Anchor.Priority)
+ Anchor = Location;
+ }
+
+private:
+ /// Unlike the type pool this is not a per-DIE structure. It is accessed once
+ /// for each DW_TAG_module cloned and once for each DW_AT_import cloned.
+ std::mutex Mutex;
+ StringMap<ModuleAnchor> Anchors;
+};
+
+} // end of namespace parallel
+} // end of namespace dwarf_linker
+} // end of namespace llvm
+
+#endif // LLVM_LIB_DWARFLINKER_PARALLEL_MODULEPOOL_H
diff --git a/llvm/lib/DWARFLinker/Parallel/OutputSections.cpp b/llvm/lib/DWARFLinker/Parallel/OutputSections.cpp
index 7d9a47a48b663..4630b44cf7b24 100644
--- a/llvm/lib/DWARFLinker/Parallel/OutputSections.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/OutputSections.cpp
@@ -67,6 +67,7 @@ void SectionDescriptor::clearAllSectionData() {
ListDebugULEB128DieRefPatch.erase();
ListDebugOffsetPatch.erase();
ListDebugDieTypeRefPatch.erase();
+ ListDebugDieModuleRefPatch.erase();
ListDebugType2TypeDieRefPatch.erase();
ListDebugTypeDeclFilePatch.erase();
ListDebugTypeLineStrPatch.erase();
@@ -431,6 +432,34 @@ void OutputSections::applyPatches(
TypeEntry->getFinalDie().getOffset());
});
+ Section.ListDebugDieModuleRefPatch.forEach(
+ [&](DebugDieModuleRefPatch &Patch) {
+ const ModuleAnchor &Anchor = *Patch.Anchor;
+
+ uint64_t FinalOffset;
+ if (Anchor.TypeName) {
+ assert(TypeUnitPtr != nullptr);
+ TypeEntryBody *TypeEntry = Anchor.TypeName->getValue().load();
+ assert(TypeEntry &&
+ formatv("No data for type {0}", Anchor.TypeName->getKey())
+ .str()
+ .c_str());
+
+ FinalOffset = TypeEntry->getFinalDie().getOffset();
+ } else if (Anchor.Section) {
+ FinalOffset = Anchor.Section->StartOffset + Anchor.LocalOffset;
+ } else {
+ // No unit describes this module in full, so the importer's own
+ // skeleton is all the output has.
+ FinalOffset = Patch.RefDieIdxOrClonedOffset +
+ Patch.RefCU.getPointer()
+ ->getSectionDescriptor(DebugSectionKind::DebugInfo)
+ .StartOffset;
+ }
+
+ Section.apply(Patch.PatchOffset, dwarf::DW_FORM_ref_addr, FinalOffset);
+ });
+
Section.ListDebugType2TypeDieRefPatch.forEach(
[&](DebugType2TypeDieRefPatch &Patch) {
assert(TypeUnitPtr != nullptr);
diff --git a/llvm/lib/DWARFLinker/Parallel/OutputSections.h b/llvm/lib/DWARFLinker/Parallel/OutputSections.h
index 5043e918013e4..10488f8e9f2b6 100644
--- a/llvm/lib/DWARFLinker/Parallel/OutputSections.h
+++ b/llvm/lib/DWARFLinker/Parallel/OutputSections.h
@@ -10,6 +10,7 @@
#define LLVM_LIB_DWARFLINKER_PARALLEL_OUTPUTSECTIONS_H
#include "ArrayList.h"
+#include "ModulePool.h"
#include "StringEntryToDwarfStringPoolEntryMap.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringRef.h"
@@ -100,6 +101,19 @@ struct DebugDieTypeRefPatch : SectionPatch {
TypeEntry *RefTypeName = nullptr;
};
+/// This structure is used to update a DW_AT_import reference to a
+/// DW_TAG_module. The reference resolves to the module's anchor once the link
+/// has filled it in, and to the inherited target otherwise. The inherited
+/// target is always resolved as an inter-CU reference, so that the attribute
+/// keeps the width it was emitted with whichever of the two wins.
+struct DebugDieModuleRefPatch : DebugDieRefPatch {
+ DebugDieModuleRefPatch(uint64_t PatchOffset, CompileUnit *RefCU,
+ uint32_t RefIdx, ModuleAnchor *Anchor)
+ : DebugDieRefPatch(PatchOffset, nullptr, RefCU, RefIdx), Anchor(Anchor) {}
+
+ ModuleAnchor *Anchor = nullptr;
+};
+
/// This structure is used to update reference to the type DIE.
struct DebugType2TypeDieRefPatch : SectionPatch {
DebugType2TypeDieRefPatch(uint64_t PatchOffset, DIE *Die, TypeEntry *TypeName,
@@ -163,6 +177,7 @@ struct SectionDescriptor : SectionDescriptorBase {
ListDebugULEB128DieRefPatch(&GlobalData.getAllocator()),
ListDebugOffsetPatch(&GlobalData.getAllocator()),
ListDebugDieTypeRefPatch(&GlobalData.getAllocator()),
+ ListDebugDieModuleRefPatch(&GlobalData.getAllocator()),
ListDebugType2TypeDieRefPatch(&GlobalData.getAllocator()),
ListDebugTypeStrPatch(&GlobalData.getAllocator()),
ListDebugTypeLineStrPatch(&GlobalData.getAllocator()),
@@ -202,6 +217,7 @@ struct SectionDescriptor : SectionDescriptorBase {
ADD_PATCHES_LIST(DebugULEB128DieRefPatch)
ADD_PATCHES_LIST(DebugOffsetPatch)
ADD_PATCHES_LIST(DebugDieTypeRefPatch)
+ ADD_PATCHES_LIST(DebugDieModuleRefPatch)
ADD_PATCHES_LIST(DebugType2TypeDieRefPatch)
ADD_PATCHES_LIST(DebugTypeStrPatch)
ADD_PATCHES_LIST(DebugTypeLineStrPatch)
diff --git a/llvm/test/tools/dsymutil/Inputs/module-import-canonical/2.ll b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/2.ll
new file mode 100644
index 0000000000000..ff7187633e687
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/2.ll
@@ -0,0 +1,36 @@
+; Test input for ../X86/module-import-canonical.test.
+;
+; A second object file importing the same module M. Only the first object file
+; to reference M.pcm gets a module unit, so this one has to resolve its import
+; to a DIE another object file owns.
+
+target triple = "x86_64-apple-darwin"
+
+define void @main2() !dbg !100 { ret void }
+
+!llvm.dbg.cu = !{!0, !20}
+!llvm.module.flags = !{!10, !11}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !1,
+ producer: "test", emissionKind: FullDebug,
+ imports: !30)
+!1 = !DIFile(filename: "2.m", directory: "")
+!100 = distinct !DISubprogram(name: "main2", scope: !0, file: !1, line: 1,
+ type: !101, unit: !0,
+ spFlags: DISPFlagDefinition)
+!101 = !DISubroutineType(types: !102)
+!102 = !{null}
+!30 = !{!31}
+!31 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !0,
+ entity: !32, line: 1)
+!32 = !DIModule(scope: !0, name: "M",
+ includePath: "/tmp/M.framework/Modules/M.swiftmodule")
+
+; The skeleton unit referencing M.pcm. Its dwo id has to match M.ll's.
+!20 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !21,
+ producer: "test", emissionKind: FullDebug,
+ splitDebugFilename: "M.pcm", dwoId: 42)
+!21 = !DIFile(filename: "M", directory: "")
+
+!10 = !{i32 2, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/llvm/test/tools/dsymutil/Inputs/module-import-canonical/M.ll b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/M.ll
new file mode 100644
index 0000000000000..9c58afc215202
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/M.ll
@@ -0,0 +1,25 @@
+; Test input for ../X86/module-import-canonical.test.
+;
+; Stands in for the Clang module of a framework: one compile unit holding the
+; DW_TAG_module that describes the module itself, with the include path
+; pointing at the framework bundle.
+
+target triple = "x86_64-apple-darwin"
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!10, !11}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !1,
+ producer: "test", emissionKind: FullDebug,
+ retainedTypes: !2, dwoId: 42)
+!1 = !DIFile(filename: "M", directory: "")
+!2 = !{!3}
+!3 = !DICompositeType(tag: DW_TAG_structure_type, name: "S", scope: !4,
+ file: !1, line: 1, size: 32, elements: !5)
+!4 = !DIModule(scope: null, name: "M", includePath: "/tmp/M.framework")
+!5 = !{!6}
+!6 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !3, file: !1,
+ line: 2, baseType: !7, size: 32)
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !{i32 2, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/llvm/test/tools/dsymutil/Inputs/module-import-canonical/debug-map-duplicate.map b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/debug-map-duplicate.map
new file mode 100644
index 0000000000000..045fc36f8ea11
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/debug-map-duplicate.map
@@ -0,0 +1,10 @@
+---
+triple: 'x86_64-apple-darwin'
+objects:
+ - filename: dup1.o
+ symbols:
+ - { sym: _main, objAddr: 0x0, binAddr: 0x10000, size: 0x10 }
+ - filename: dup2.o
+ symbols:
+ - { sym: _main2, objAddr: 0x0, binAddr: 0x20000, size: 0x10 }
+...
diff --git a/llvm/test/tools/dsymutil/Inputs/module-import-canonical/debug-map.map b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/debug-map.map
new file mode 100644
index 0000000000000..f8f672a41045b
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/debug-map.map
@@ -0,0 +1,10 @@
+---
+triple: 'x86_64-apple-darwin'
+objects:
+ - filename: 1.o
+ symbols:
+ - { sym: _main, objAddr: 0x0, binAddr: 0x10000, size: 0x10 }
+ - filename: 2.o
+ symbols:
+ - { sym: _main2, objAddr: 0x0, binAddr: 0x20000, size: 0x10 }
+...
diff --git a/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup-a.ll b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup-a.ll
new file mode 100644
index 0000000000000..de5c57bbd5102
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup-a.ll
@@ -0,0 +1,24 @@
+; Test input for ../X86/module-import-canonical-duplicate.test.
+;
+; One of two builds of module M. Stands in for the copy the first object file
+; of the link was built against.
+
+target triple = "x86_64-apple-darwin"
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!10, !11}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !1,
+ producer: "test", emissionKind: FullDebug,
+ retainedTypes: !2, dwoId: 42)
+!1 = !DIFile(filename: "M", directory: "")
+!2 = !{!3}
+!3 = !DICompositeType(tag: DW_TAG_structure_type, name: "S", scope: !4,
+ file: !1, line: 1, size: 32, elements: !5)
+!4 = !DIModule(scope: null, name: "M", includePath: "/tmp/A.framework")
+!5 = !{!6}
+!6 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !3, file: !1,
+ line: 2, baseType: !7, size: 32)
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !{i32 2, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup-b.ll b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup-b.ll
new file mode 100644
index 0000000000000..4792432b98e36
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup-b.ll
@@ -0,0 +1,24 @@
+; Test input for ../X86/module-import-canonical-duplicate.test.
+;
+; The other build of module M, naming a different include path. Reached through
+; a different .pcm path, so the link gets a unit for this copy too.
+
+target triple = "x86_64-apple-darwin"
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!10, !11}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !1,
+ producer: "test", emissionKind: FullDebug,
+ retainedTypes: !2, dwoId: 43)
+!1 = !DIFile(filename: "M", directory: "")
+!2 = !{!3}
+!3 = !DICompositeType(tag: DW_TAG_structure_type, name: "S", scope: !4,
+ file: !1, line: 1, size: 32, elements: !5)
+!4 = !DIModule(scope: null, name: "M", includePath: "/tmp/B.framework")
+!5 = !{!6}
+!6 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !3, file: !1,
+ line: 2, baseType: !7, size: 32)
+!7 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!10 = !{i32 2, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup1.ll b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup1.ll
new file mode 100644
index 0000000000000..61f0137854b79
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup1.ll
@@ -0,0 +1,33 @@
+; Test input for ../X86/module-import-canonical-duplicate.test.
+;
+; The first object file of the link, built against the copy of M under a/.
+
+target triple = "x86_64-apple-darwin"
+
+define void @main() !dbg !100 { ret void }
+
+!llvm.dbg.cu = !{!0, !20}
+!llvm.module.flags = !{!10, !11}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !1,
+ producer: "test", emissionKind: FullDebug,
+ imports: !30)
+!1 = !DIFile(filename: "dup1.m", directory: "")
+!100 = distinct !DISubprogram(name: "main", scope: !0, file: !1, line: 1,
+ type: !101, unit: !0,
+ spFlags: DISPFlagDefinition)
+!101 = !DISubroutineType(types: !102)
+!102 = !{null}
+!30 = !{!31}
+!31 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !0,
+ entity: !32, line: 1)
+!32 = !DIModule(scope: !0, name: "M", includePath: "/tmp/importer1")
+
+; The skeleton unit referencing a/M.pcm. Its dwo id has to match dup-a.ll's.
+!20 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !21,
+ producer: "test", emissionKind: FullDebug,
+ splitDebugFilename: "a/M.pcm", dwoId: 42)
+!21 = !DIFile(filename: "M", directory: "")
+
+!10 = !{i32 2, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup2.ll b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup2.ll
new file mode 100644
index 0000000000000..4447c49a37845
--- /dev/null
+++ b/llvm/test/tools/dsymutil/Inputs/module-import-canonical/dup2.ll
@@ -0,0 +1,33 @@
+; Test input for ../X86/module-import-canonical-duplicate.test.
+;
+; The second object file of the link, built against the copy of M under b/.
+
+target triple = "x86_64-apple-darwin"
+
+define void @main2() !dbg !100 { ret void }
+
+!llvm.dbg.cu = !{!0, !20}
+!llvm.module.flags = !{!10, !11}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !1,
+ producer: "test", emissionKind: FullDebug,
+ imports: !30)
+!1 = !DIFile(filename: "dup2.m", directory: "")
+!100 = distinct !DISubprogram(name: "main2", scope: !0, file: !1, line: 1,
+ type: !101, unit: !0,
+ spFlags: DISPFlagDefinition)
+!101 = !DISubroutineType(types: !102)
+!102 = !{null}
+!30 = !{!31}
+!31 = !DIImportedEntity(tag: DW_TAG_imported_declaration, scope: !0,
+ entity: !32, line: 1)
+!32 = !DIModule(scope: !0, name: "M", includePath: "/tmp/importer2")
+
+; The skeleton unit referencing b/M.pcm. Its dwo id has to match dup-b.ll's.
+!20 = distinct !DICompileUnit(language: DW_LANG_ObjC, file: !21,
+ producer: "test", emissionKind: FullDebug,
+ splitDebugFilename: "b/M.pcm", dwoId: 43)
+!21 = !DIFile(filename: "M", directory: "")
+
+!10 = !{i32 2, !"Dwarf Version", i32 4}
+!11 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/llvm/test/tools/dsymutil/X86/module-import-canonical-duplicate.test b/llvm/test/tools/dsymutil/X86/module-import-canonical-duplicate.test
new file mode 100644
index 0000000000000..df611042574c6
--- /dev/null
+++ b/llvm/test/tools/dsymutil/X86/module-import-canonical-duplicate.test
@@ -0,0 +1,47 @@
+# A module can be built more than once in one link, under a different .pcm path
+# each time. A unit is created per .pcm but a module is anchored by name, so
+# both units describe module M and only one of them can be the import target.
+# The choice has to follow the link order, not the order the units happen to
+# finish cloning, or the dSYM stops being reproducible.
+
+RUN: rm -rf %t && mkdir -p %t/a %t/b
+RUN: llc -O0 -mtriple=x86_64-apple-darwin -filetype=obj \
+RUN: -o %t/a/M.pcm %p/../Inputs/module-import-canonical/dup-a.ll
+RUN: llc -O0 -mtriple=x86_64-apple-darwin -filetype=obj \
+RUN: -o %t/b/M.pcm %p/../Inputs/module-import-canonical/dup-b.ll
+RUN: llc -O0 -mtriple=x86_64-apple-darwin -filetype=obj \
+RUN: -o %t/dup1.o %p/../Inputs/module-import-canonical/dup1.ll
+RUN: llc -O0 -mtriple=x86_64-apple-darwin -filetype=obj \
+RUN: -o %t/dup2.o %p/../Inputs/module-import-canonical/dup2.ll
+
+RUN: dsymutil --linker parallel -f \
+RUN: -y %p/../Inputs/module-import-canonical/debug-map-duplicate.map \
+RUN: -oso-prepend-path %t -o %t/parallel.dwarf
+RUN: llvm-dwarfdump --verify %t/parallel.dwarf
+RUN: llvm-dwarfdump --debug-info %t/parallel.dwarf | FileCheck %s
+
+# Threading must not decide which copy wins.
+
+RUN: dsymutil --linker parallel -f --num-threads 1 \
+RUN: -y %p/../Inputs/module-import-canonical/debug-map-duplicate.map \
+RUN: -oso-prepend-path %t -o %t/serial.dwarf
+RUN: cmp %t/parallel.dwarf %t/serial.dwarf
+
+# The copy reached through the first object file of the link is the one every
+# importer resolves to, because its unit takes the lower priority.
+
+CHECK: DW_TAG_compile_unit
+CHECK: DW_AT_name {{.*}}"M"
+CHECK: 0x0[[MODULE:[0-9a-f]+]]: DW_TAG_module
+CHECK-NEXT: DW_AT_name {{.*}}"M"
+CHECK-NEXT: DW_AT_LLVM_include_path {{.*}}"/tmp/A.framework"
+
+CHECK: DW_TAG_compile_unit
+CHECK: DW_AT_name {{.*}}"dup1.m"
+CHECK: DW_TAG_imported_declaration
+CHECK: DW_AT_import {{.*}}(0x{{0*}}[[MODULE]] "M")
+
+CHECK: DW_TAG_compile_unit
+CHECK: DW_AT_name {{.*}}"dup2.m"
+CHECK: DW_TAG_imported_declaration
+CHECK: DW_AT_import {{.*}}(0x{{0*}}[[MODULE]] "M")
diff --git a/llvm/test/tools/dsymutil/X86/module-import-canonical.test b/llvm/test/tools/dsymutil/X86/module-import-canonical.test
new file mode 100644
index 0000000000000..7018064964cc5
--- /dev/null
+++ b/llvm/test/tools/dsymutil/X86/module-import-canonical.test
@@ -0,0 +1,99 @@
+# An importing compile unit emits its own DW_TAG_module skeleton for a module,
+# and that skeleton can name a different DW_AT_LLVM_include_path than the
+# module's own compile unit: a Swift unit names the .swiftmodule while the
+# companion Clang module names the framework bundle. Consumers compare the
+# search paths of two imported modules to tell whether they belong to the same
+# framework, so every importer has to resolve a module to the DIE of the unit
+# describing that module in full.
+#
+# Only the first object file which references a .pcm gets a module unit for it,
+# so the second object file below resolves its import across object file
+# boundaries.
+
+RUN: rm -rf %t && mkdir -p %t
+RUN: llc -O0 -mtriple=x86_64-apple-darwin -filetype=obj \
+RUN: -o %t/M.pcm %p/../Inputs/module-import-canonical/M.ll
+RUN: llc -O0 -mtriple=x86_64-apple-darwin -filetype=obj \
+RUN: -o %t/1.o %p/../Inputs/module-import-canonical/1.ll
+RUN: llc -O0 -mtriple=x86_64-apple-darwin -filetype=obj \
+RUN: -o %t/2.o %p/../Inputs/module-import-canonical/2.ll
+
+RUN: dsymutil --linker parallel -f \
+RUN: -y %p/../Inputs/module-import-canonical/debug-map.map \
+RUN: -oso-prepend-path %t -o %t/parallel.dwarf
+RUN: llvm-dwarfdump --verify %t/parallel.dwarf
+RUN: llvm-dwarfdump --debug-info %t/parallel.dwarf | FileCheck %s
+
+# The output must not depend on thread interleaving.
+
+RUN: dsymutil --linker parallel -f --num-threads 1 \
+RUN: -y %p/../Inputs/module-import-canonical/debug-map.map \
+RUN: -oso-prepend-path %t -o %t/serial.dwarf
+RUN: cmp %t/parallel.dwarf %t/serial.dwarf
+
+# The classic linker resolves the imports to the same DIE.
+
+RUN: dsymutil --linker classic -f \
+RUN: -y %p/../Inputs/module-import-canonical/debug-map.map \
+RUN: -oso-prepend-path %t -o %t/classic.dwarf
+RUN: llvm-dwarfdump --verify %t/classic.dwarf
+RUN: llvm-dwarfdump --debug-info %t/classic.dwarf | FileCheck %s
+
+# When the module unit is in an ODR language, the DW_TAG_module describing the
+# module lands in the type table, and an import has to resolve to that copy.
+# The classic linker has no type table, so it is not run over this variant.
+
+RUN: mkdir -p %t/odr && cp %t/1.o %t/2.o %t/odr
+RUN: llc -O0 -mtriple=x86_64-apple-darwin -filetype=obj \
+RUN: -o %t/odr/M.pcm %p/../Inputs/module-import-canonical/M-odr.ll
+
+RUN: dsymutil --linker parallel -f \
+RUN: -y %p/../Inputs/module-import-canonical/debug-map.map \
+RUN: -oso-prepend-path %t/odr -o %t/odr.dwarf
+RUN: llvm-dwarfdump --verify %t/odr.dwarf
+RUN: llvm-dwarfdump --debug-info %t/odr.dwarf | FileCheck %s --check-prefix=ODR
+
+RUN: dsymutil --linker parallel -f --num-threads 1 \
+RUN: -y %p/../Inputs/module-import-canonical/debug-map.map \
+RUN: -oso-prepend-path %t/odr -o %t/odr-serial.dwarf
+RUN: cmp %t/odr.dwarf %t/odr-serial.dwarf
+
+# The module's own compile unit holds the DW_TAG_module carrying the bundle
+# path. Capture its offset. The DIE label strips leading zeros differently from
+# DW_AT_import, so anchor on the significant digits.
+
+CHECK: DW_TAG_compile_unit
+CHECK: DW_AT_name {{.*}}"M"
+CHECK: 0x0[[MODULE:[0-9a-f]+]]: DW_TAG_module
+CHECK-NEXT: DW_AT_name {{.*}}"M"
+CHECK-NEXT: DW_AT_LLVM_include_path {{.*}}"/tmp/M.framework"
+
+# Each importing unit resolves its import to that DIE, not to any skeleton of
+# its own naming a different include path.
+
+CHECK: DW_TAG_compile_unit
+CHECK: DW_AT_name {{.*}}"1.m"
+CHECK: DW_TAG_imported_declaration
+CHECK: DW_AT_import {{.*}}(0x{{0*}}[[MODULE]] "M")
+
+CHECK: DW_TAG_compile_unit
+CHECK: DW_AT_name {{.*}}"2.m"
+CHECK: DW_TAG_imported_declaration
+CHECK: DW_AT_import {{.*}}(0x{{0*}}[[MODULE]] "M")
+
+# The type table holds the module DIE carrying the bundle path, and both
+# importing units resolve to it.
+
+ODR: DW_TAG_compile_unit
+ODR: DW_AT_name {{.*}}"__artificial_type_unit"
+ODR: 0x0[[TYPEMODULE:[0-9a-f]+]]: DW_TAG_module
+ODR-NEXT: DW_AT_name {{.*}}"M"
+ODR-NEXT: DW_AT_LLVM_include_path {{.*}}"/tmp/M.framework"
+
+ODR: DW_TAG_compile_unit
+ODR: DW_AT_name {{.*}}"1.m"
+ODR: DW_AT_import {{.*}}(0x{{0*}}[[TYPEMODULE]] "M")
+
+ODR: DW_TAG_compile_unit
+ODR: DW_AT_name {{.*}}"2.m"
+ODR: DW_AT_import {{.*}}(0x{{0*}}[[TYPEMODULE]] "M")
diff --git a/llvm/test/tools/dsymutil/X86/modules.m b/llvm/test/tools/dsymutil/X86/modules.m
index 1060154222e1d..ca1af8d8b4867 100644
--- a/llvm/test/tools/dsymutil/X86/modules.m
+++ b/llvm/test/tools/dsymutil/X86/modules.m
@@ -42,6 +42,34 @@
// RUN: llvm-dwarfdump -v --debug-info %t.parallel.dSYM \
// RUN: | FileCheck --check-prefix=ACCEL %s
+// Foo imports Bar, so Foo.pcm carries a skeleton of Bar next to the module it
+// describes. That import has to resolve to the unit built from Bar.pcm, which
+// is the only one describing Bar in full. Both units offer the parallel linker
+// an anchor for Bar, so the winner must not depend on which is cloned first.
+
+// RUN: dsymutil --linker parallel -f -oso-prepend-path=%p/../Inputs/modules \
+// RUN: -y %p/dummy-debug-map.map -o %t.threaded
+// RUN: dsymutil --linker parallel -f --num-threads 1 \
+// RUN: -oso-prepend-path=%p/../Inputs/modules \
+// RUN: -y %p/dummy-debug-map.map -o %t.serial
+// RUN: cmp %t.threaded %t.serial
+// RUN: llvm-dwarfdump --debug-info %t.threaded \
+// RUN: | FileCheck --check-prefix=MODIMPORT %s
+
+// MODIMPORT: 0x0[[BAR:[0-9a-f]+]]: DW_TAG_module
+// MODIMPORT-NEXT: DW_AT_name {{.*}}"Bar"
+// MODIMPORT: DW_TAG_structure_type
+// MODIMPORT-NEXT: DW_AT_name {{.*}}"Bar"
+// MODIMPORT: DW_AT_name {{.*}}"value"
+// MODIMPORT: DW_TAG_structure_type
+// MODIMPORT-NEXT: DW_AT_name {{.*}}"PruneMeNot"
+
+// MODIMPORT: DW_TAG_module
+// MODIMPORT-NEXT: DW_AT_name {{.*}}"Foo"
+// MODIMPORT: DW_TAG_imported_declaration
+// MODIMPORT-NOT: DW_TAG
+// MODIMPORT: DW_AT_import {{.*}}(0x{{0*}}[[BAR]] "Bar")
+
// ACCEL: DW_TAG_compile_unit
// WARN-NOT: warning: hash mismatch
diff --git a/llvm/test/tools/dsymutil/X86/submodules.m b/llvm/test/tools/dsymutil/X86/submodules.m
index 8de9d2322e945..57d1f9bd3cb89 100644
--- a/llvm/test/tools/dsymutil/X86/submodules.m
+++ b/llvm/test/tools/dsymutil/X86/submodules.m
@@ -24,7 +24,7 @@
// RUN: dsymutil --linker parallel -f -oso-prepend-path=%p/../Inputs/submodules \
// RUN: -y %p/dummy-debug-map.map -o - \
// RUN: | llvm-dwarfdump -v --debug-info - \
-// RUN: | FileCheck %s --check-prefix=CHECK
+// RUN: | FileCheck %s --check-prefixes=CHECK,PARALLEL
// ---------------------------------------------------------------------
#ifdef CHILD_H
@@ -34,7 +34,7 @@
// CHECK-NOT: DW_TAG
// CHECK: DW_TAG_module
// CHECK-NEXT: DW_AT_name{{.*}}"Parent"
-// CHECK: DW_TAG_module
+// CHECK: 0x0[[CHILD:.*]]: DW_TAG_module
// CHECK-NEXT: DW_AT_name{{.*}}"Child"
// CHECK: DW_TAG_structure_type
// CHECK-NOT: DW_TAG
@@ -51,7 +51,23 @@
// CLASSIC: 0x0[[EMPTY:.*]]: DW_TAG_module
// CLASSIC-NEXT: DW_AT_name{{.*}}"Empty"
-// CLASSIC: DW_AT_import {{.*}}0x{{0*}}[[EMPTY]]
+// Parent.pcm describes no Empty submodule, so both linkers leave that import
+// pointing at the skeleton the importing unit emitted. Only the offsets differ,
+// because the classic linker prunes the Parent and Child skeletons next to it.
+
+// PARALLEL: DW_TAG_module
+// PARALLEL-NEXT: DW_AT_name{{.*}}"Parent"
+// PARALLEL: DW_TAG_module
+// PARALLEL-NEXT: DW_AT_name{{.*}}"Child"
+// PARALLEL: 0x0[[EMPTY:.*]]: DW_TAG_module
+// PARALLEL-NEXT: DW_AT_name{{.*}}"Empty"
+
+// A submodule which is described elsewhere is imported through the module
+// unit's DIE, not through the skeleton the importing unit emits for it.
+
+// CHECK: DW_AT_import {{.*}}0x{{0*}}[[CHILD]]
+
+// CHECK: DW_AT_import {{.*}}0x{{0*}}[[EMPTY]]
@import Parent.Child;
@import Parent.Empty;
int main(int argc, char **argv) { return 0; }
More information about the llvm-commits
mailing list