[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 17:02:59 PDT 2026


https://github.com/JDevlieghere created https://github.com/llvm/llvm-project/pull/215435

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

>From d051f6275d523015fa124593e3823141e643e0cd 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            | 35 ++++++++----
 .../dsymutil/X86/module-type-definition.test  | 56 +++++++++++++++++++
 2 files changed, 79 insertions(+), 12 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..325895ce02f1f 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,6 +228,17 @@ void DependencyTracker::collectRootsToKeep(
       llvm_unreachable("Called for incorrect DIE");
     } break;
     default:
+      // Nothing in a Clang module compile unit is reachable from a live
+      // address, so liveness analysis alone keeps none of the type definitions
+      // it owns. Keep every DIE of a module unit.
+      if (Entry.CU->isClangModule() && isNamespaceLikeEntry(Entry.DieEntry) &&
+          dwarf::isType(CurChild->getTag())) {
+        addActionToRootEntriesWorkList(
+            LiveRootWorklistActionTy::MarkTypeEntryRec, ChildEntry,
+            ReferencedBy);
+        break;
+      }
+
       // A forward-declared type nested in a DW_TAG_module 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
@@ -314,18 +337,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..d602ae9af7a0f
--- /dev/null
+++ b/llvm/test/tools/dsymutil/X86/module-type-definition.test
@@ -0,0 +1,56 @@
+# The definitions of the types a Clang module owns live only in the module's
+# compile unit, which describes no code, so nothing there is reachable from a
+# live address. 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 -verify \
+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_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 -verify \
+RUN:     -oso-prepend-path=%p/../Inputs/modules \
+RUN:     -y %p/dummy-debug-map.map -o - 2>/dev/null \
+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:          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"



More information about the llvm-commits mailing list