[llvm] [DWARFLinker] Keep module type definitions in the parallel linker (PR #215435)
Jonas Devlieghere via llvm-commits
llvm-commits at lists.llvm.org
Mon Aug 10 22:51:08 PDT 2026
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/215435
>From b6baf97772d9abd4be877935aa3ef9c6acd41f16 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Mon, 10 Aug 2026 17:00:45 -0700
Subject: [PATCH] [DWARFLinker] Keep module type definitions in the parallel
linker
A Clang module compile unit carries the only definitions of the types
its module owns. No definition in it is reachable from a live address.
Similar to the classic linker, we need to mark everything as kept.
rdar://184559034
---
.../Parallel/DependencyTracker.cpp | 41 +++++++----
.../dsymutil/X86/module-type-definition.test | 70 +++++++++++++++++++
2 files changed, 97 insertions(+), 14 deletions(-)
create mode 100644 llvm/test/tools/dsymutil/X86/module-type-definition.test
diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
index 36712b9d0cfec..124e73b1a15bd 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
@@ -105,6 +105,18 @@ void DependencyTracker::verifyKeepChain() {
#endif
}
+static bool isNamespaceLikeEntry(const DWARFDebugInfoEntry *Entry) {
+ switch (Entry->getTag()) {
+ case dwarf::DW_TAG_compile_unit:
+ case dwarf::DW_TAG_module:
+ case dwarf::DW_TAG_namespace:
+ return true;
+
+ default:
+ return false;
+ }
+}
+
bool DependencyTracker::resolveDependenciesAndMarkLiveness(
bool InterCUProcessingStarted, std::atomic<bool> &HasNewInterconnectedCUs) {
RootEntriesWorkList.clear();
@@ -216,7 +228,19 @@ void DependencyTracker::collectRootsToKeep(
llvm_unreachable("Called for incorrect DIE");
} break;
default:
- // A forward-declared type nested in a DW_TAG_module is the module's
+ // A module compile unit has no relocations, so liveness analysis never
+ // reaches a type definition that nothing else in the unit references. The
+ // module owns the only copy of those definitions, so keep them.
+ if (Entry.CU->isClangModule() && isNamespaceLikeEntry(Entry.DieEntry) &&
+ dwarf::isType(CurChild->getTag())) {
+ addActionToRootEntriesWorkList(
+ LiveRootWorklistActionTy::MarkTypeEntryRec, ChildEntry,
+ ReferencedBy);
+ break;
+ }
+
+ // An importing unit emits a skeleton of the module it imports, so a
+ // forward-declared type nested in a DW_TAG_module there is the module's
// record that the name exists, even when no full definition has been
// emitted. Route it through the type pool: when another CU emits a
// real definition for the same synthetic name, the existing
@@ -224,7 +248,8 @@ void DependencyTracker::collectRootsToKeep(
// the definition and drops this declaration at emission time. For
// non-ODR languages getFinalPlacementForEntry forces PlainDwarf,
// so the forward decl is kept in place under its module.
- if (Entry.DieEntry->getTag() == dwarf::DW_TAG_module &&
+ if (!Entry.CU->isClangModule() &&
+ Entry.DieEntry->getTag() == dwarf::DW_TAG_module &&
dwarf::isType(CurChild->getTag()) &&
dwarf::toUnsigned(Entry.CU->find(CurChild, dwarf::DW_AT_declaration),
0)) {
@@ -314,18 +339,6 @@ void DependencyTracker::setPlainDwarfPlacementRec(
setPlainDwarfPlacementRec(UnitEntryPairTy{Entry.CU, CurChild});
}
-static bool isNamespaceLikeEntry(const DWARFDebugInfoEntry *Entry) {
- switch (Entry->getTag()) {
- case dwarf::DW_TAG_compile_unit:
- case dwarf::DW_TAG_module:
- case dwarf::DW_TAG_namespace:
- return true;
-
- default:
- return false;
- }
-}
-
bool isAlreadyMarked(const CompileUnit::DIEInfo &Info,
CompileUnit::DieOutputPlacement NewPlacement) {
if (!Info.getKeep())
diff --git a/llvm/test/tools/dsymutil/X86/module-type-definition.test b/llvm/test/tools/dsymutil/X86/module-type-definition.test
new file mode 100644
index 0000000000000..fa661b57cd0af
--- /dev/null
+++ b/llvm/test/tools/dsymutil/X86/module-type-definition.test
@@ -0,0 +1,70 @@
+# A Clang module compile unit has no relocations, so liveness analysis never
+# reaches the type definitions the module owns, even though its compile unit
+# holds the only copy of them. They must be kept anyway, or everything importing
+# the module is left with only forward declarations. Covered for an ODR
+# language, where the definitions land in the artificial type unit, and a
+# non-ODR one, where they stay under the module in plain DWARF.
+
+RUN: dsymutil --linker parallel -f \
+RUN: -oso-prepend-path=%p/../Inputs/modules-pruning \
+RUN: -y %p/dummy-debug-map.map -o - \
+RUN: | llvm-dwarfdump --debug-info - | FileCheck %s --check-prefix=ODR
+
+ODR: DW_TAG_compile_unit
+ODR: DW_AT_name {{.*}}"__artificial_type_unit"
+ODR: DW_TAG_module
+ODR-NEXT: DW_AT_name {{.*}}"Outer"
+ODR: DW_TAG_module
+ODR-NEXT: DW_AT_name {{.*}}"Template"
+ODR: DW_TAG_namespace
+ODR-NEXT: DW_AT_name {{.*}}"M"
+ODR: DW_TAG_structure_type
+ODR: DW_AT_name {{.*}}"false_type"
+ODR-NEXT: DW_AT_byte_size {{.*}}(0x01)
+ODR: DW_TAG_member
+ODR-NEXT: DW_AT_name {{.*}}"value"
+ODR: DW_AT_const_value {{.*}}(0)
+ODR: DW_TAG_structure_type
+ODR: DW_AT_name {{.*}}"true_type"
+ODR-NEXT: DW_AT_byte_size {{.*}}(0x01)
+ODR: DW_TAG_member
+ODR-NEXT: DW_AT_name {{.*}}"value"
+ODR: DW_AT_const_value {{.*}}(1)
+
+RUN: dsymutil --linker parallel -f \
+RUN: -oso-prepend-path=%p/../Inputs/modules \
+RUN: -y %p/dummy-debug-map.map -o - \
+RUN: | llvm-dwarfdump --debug-info - | FileCheck %s --check-prefix=NOODR
+
+NOODR: DW_TAG_module
+NOODR-NEXT: DW_AT_name {{.*}}"Bar"
+NOODR: DW_TAG_structure_type
+NOODR-NEXT: DW_AT_name {{.*}}"Bar"
+NOODR-NEXT: DW_AT_byte_size {{.*}}(0x04)
+NOODR: DW_TAG_member
+NOODR-NEXT: DW_AT_name {{.*}}"value"
+
+NOODR: DW_TAG_module
+NOODR-NEXT: DW_AT_name {{.*}}"Foo"
+NOODR: DW_TAG_typedef
+NOODR-NEXT: DW_AT_type {{.*}}"Bar::Bar"
+NOODR-NEXT: DW_AT_name {{.*}}"Bar"
+NOODR: DW_TAG_structure_type
+NOODR-NEXT: DW_AT_name {{.*}}"S"
+NOODR-NEXT: DW_AT_byte_size {{.*}}(0x00)
+NOODR: DW_TAG_structure_type
+NOODR-NEXT: DW_AT_name {{.*}}"Foo"
+NOODR-NEXT: DW_AT_byte_size {{.*}}(0x04)
+NOODR: DW_TAG_member
+NOODR-NEXT: DW_AT_name {{.*}}"ivar"
+
+# Without ODR merging there is nothing to redirect the typedef above at the
+# definition kept in the module's own unit, so it still resolves to the forward
+# declaration its importer recorded.
+
+NOODR: DW_TAG_module
+NOODR-NEXT: DW_AT_name {{.*}}"Bar"
+NOODR: DW_TAG_structure_type
+NOODR-NEXT: DW_AT_name {{.*}}"Bar"
+NOODR-NEXT: DW_AT_byte_size {{.*}}(0x04)
+NOODR-NEXT: DW_AT_declaration {{.*}}(true)
More information about the llvm-commits
mailing list