[llvm] [DWARFLinker][Parallel] Keep every DIE with --no-garbage-collection (PR #219768)

Arcadiy Ivanov via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 3 10:27:50 PDT 2026


https://github.com/arcivanov updated https://github.com/llvm/llvm-project/pull/219768

>From 3a7b517ac2921ca286ef71f383fdceb897bb290f Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Fri, 28 Aug 2026 22:31:55 -0400
Subject: [PATCH 1/3] [DWARFLinker][Parallel] Identify a unit root by position,
 not by tag

Fix CompileUnit::cloneDIE() for a DW_TAG_partial_unit root. cloneDIE()
recognizes the unit root by DW_TAG_compile_unit, both to keep it out of
the artificial type unit and to recurse into its type children anyway.
A type moved to the artificial type unit sets KeepTypeChildren on its
ancestors, the unit root included, so a partial unit root is let
through and createTypeDIEandCloneAttributes() asserts on its missing
type entry. Fixing only that check is not enough: a partial unit that
holds nothing but types then loses them, and patch application asserts
with "No data for type".

Recognize the unit root by DIE index zero instead, which is the root
whatever its tag.

The tests link a type in a namespace under a partial unit root and a
partial unit that holds only types, each with a compile unit control.

Fixes #219613.

Assisted-by: Claude Code
---
 .../Parallel/DWARFLinkerCompileUnit.cpp       |   6 +-
 .../Parallel/DWARFLinkerCompileUnit.h         |   3 +
 .../X86/dwarf5-partial-unit-namespace.test    | 165 ++++++++++++++++++
 .../X86/dwarf5-partial-unit-types-only.test   | 149 ++++++++++++++++
 4 files changed, 319 insertions(+), 4 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-namespace.test
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test

diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
index 4138a9fd69aca8..126cac340a49d2 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.cpp
@@ -1419,8 +1419,7 @@ std::pair<DIE *, TypeEntry *> CompileUnit::cloneDIE(
 
   bool NeedToClonePlainDIE = Info.needToKeepInPlainDwarf();
   bool NeedToCloneTypeDIE =
-      (InputDieEntry->getTag() != dwarf::DW_TAG_compile_unit) &&
-      Info.needToPlaceInTypeTable();
+      !isUnitRootDIE(InputDieIdx) && Info.needToPlaceInTypeTable();
   std::pair<DIE *, TypeEntry *> ClonedDIE;
 
   DIEGenerator PlainDIEGenerator(Allocator, *this);
@@ -1449,8 +1448,7 @@ std::pair<DIE *, TypeEntry *> CompileUnit::cloneDIE(
       (ClonedDIE.first && Info.getKeepPlainChildren());
 
   bool HasTypeChildrenToClone =
-      ((ClonedDIE.second ||
-        InputDieEntry->getTag() == dwarf::DW_TAG_compile_unit) &&
+      ((ClonedDIE.second || isUnitRootDIE(InputDieIdx)) &&
        Info.getKeepTypeChildren());
 
   // Recursively clone children.
diff --git a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
index c8c1b8822aa642..ccde7e1fe5af88 100644
--- a/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
+++ b/llvm/lib/DWARFLinker/Parallel/DWARFLinkerCompileUnit.h
@@ -335,6 +335,9 @@ class alignas(8) CompileUnit : public DwarfUnit {
   ///
   /// @{
 
+  /// \returns true if the DIE at \p DieIdx is the unit root, whatever its tag.
+  static bool isUnitRootDIE(uint32_t DieIdx) { return DieIdx == 0; }
+
   /// \p Idx index of the DIE.
   /// \returns DieInfo descriptor.
   DIEInfo &getDIEInfo(unsigned Idx) { return DieInfoArray[Idx]; }
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-namespace.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-namespace.test
new file mode 100644
index 00000000000000..3b16ba5417dca5
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-namespace.test
@@ -0,0 +1,165 @@
+## Check that the parallel linker keeps a DW_TAG_partial_unit root out of the
+## artificial type unit when a type under it moves there. Without the namespace,
+## S is never a deduplication candidate under a partial unit root. The
+## DW_TAG_compile_unit run is the control.
+##
+## namespace ns {
+##   struct S { int m1; };
+## }
+##
+## ns::S foo1() { ... }
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -o %t.cu.o
+# RUN: llvm-dwarfutil %t.cu.o %t.cu.classic
+# RUN: llvm-dwarfdump --debug-info %t.cu.classic | FileCheck %s --check-prefixes=CLASSIC,CLASSIC-CU
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu.parallel | FileCheck %s --check-prefixes=PARALLEL,PARALLEL-CU \
+# RUN:   --implicit-check-not='DW_AT_name ("U01")'
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -o %t.pu.o
+# RUN: llvm-dwarfutil %t.pu.o %t.pu.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu.classic | FileCheck %s --check-prefixes=CLASSIC,CLASSIC-PU
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu.parallel | FileCheck %s --check-prefixes=PARALLEL,PARALLEL-PU \
+# RUN:   --implicit-check-not=DW_TAG_partial_unit --implicit-check-not='DW_AT_name ("U01")'
+
+## FIXME: llvm-dwarfdump prefixes the partial unit's name to the type (#219635).
+# CLASSIC-CU:     DW_TAG_compile_unit
+# CLASSIC-PU:     DW_TAG_partial_unit
+# CLASSIC:        DW_TAG_subprogram
+# CLASSIC-NEXT:     DW_AT_name ("foo1")
+# CLASSIC-CU:       DW_AT_type (0x00000000[[S:[0-9a-f]{8}]] "ns::S")
+# CLASSIC-PU:       DW_AT_type (0x00000000[[S:[0-9a-f]{8}]] "U01::ns::S")
+# CLASSIC:        DW_TAG_namespace
+# CLASSIC-NEXT:     DW_AT_name ("ns")
+# CLASSIC:      0x[[S]]:     DW_TAG_structure_type
+# CLASSIC-NEXT:   DW_AT_name ("S")
+# CLASSIC:          DW_TAG_member
+# CLASSIC-NEXT:       DW_AT_name ("m1")
+
+## The unit root is output once, as the root of its own unit.
+# PARALLEL:      DW_AT_name ("__artificial_type_unit")
+# PARALLEL:      DW_TAG_namespace
+# PARALLEL-NEXT:   DW_AT_name ("ns")
+# PARALLEL:      0x[[S:[0-9a-f]{8}]]:     DW_TAG_structure_type
+# PARALLEL-NEXT:   DW_AT_name ("S")
+# PARALLEL:          DW_TAG_member
+# PARALLEL-NEXT:       DW_AT_name ("m1")
+# PARALLEL-CU:   DW_TAG_compile_unit
+# PARALLEL-PU:   DW_TAG_partial_unit
+# PARALLEL-NEXT:   DW_AT_producer ("by_hand")
+# PARALLEL-NEXT:   DW_AT_name ("U01")
+# PARALLEL:      DW_TAG_subprogram
+# PARALLEL-NEXT:   DW_AT_name ("foo1")
+# PARALLEL:        DW_AT_type (0x00000000[[S]] "ns::S")
+
+## The root carries DW_AT_str_offsets_base, which the linker adds only to a
+## DW_TAG_compile_unit root (#219365).
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1130
+    Size:            0x10
+  - Name:            .debug_str_offsets
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0400000005000000"
+DWARF:
+  debug_abbrev:
+    - Table:
+      - Tag:      [[ROOT]]
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_namespace
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_structure_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_byte_size
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_data_member_location
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+  debug_info:
+    - Version:  5
+      UnitType: [[UNITTYPE]]
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x4d
+        - AbbrCode: 3
+          Values:
+            - CStr: ns
+        - AbbrCode: 4
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 5
+          Values:
+            - CStr: m1
+            - Value: 0x5c
+            - Value: 0x0
+        - AbbrCode: 0
+        - AbbrCode: 0
+        - AbbrCode: 6
+          Values:
+            - CStr: int
+        - AbbrCode: 0
+...
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test
new file mode 100644
index 00000000000000..263589c59cfaf5
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test
@@ -0,0 +1,149 @@
+## Check that the parallel linker descends into a DW_TAG_partial_unit root whose
+## only kept contents are types bound for the artificial type unit. The
+## DW_TAG_compile_unit run is the control.
+##
+## U01:  namespace ns { struct S {}; }
+## U02:  ns::S foo1() { ... }   // DW_FORM_ref_addr reference into U01
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -o %t.cu.o
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.out
+# RUN: llvm-dwarfdump --debug-info %t.cu.out | FileCheck %s --implicit-check-not='DW_AT_name ("U01")' \
+# RUN:   --implicit-check-not=DW_TAG_structure_type
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -o %t.pu.o
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.out
+# RUN: llvm-dwarfdump --debug-info %t.pu.out | FileCheck %s --check-prefixes=CHECK,CHECK-PU \
+# RUN:   --implicit-check-not=DW_TAG_structure_type
+
+## S is output only in the type unit, and foo1's reference resolves there.
+# CHECK:           DW_AT_name ("__artificial_type_unit")
+# CHECK:           DW_TAG_namespace
+# CHECK-NEXT:        DW_AT_name ("ns")
+# CHECK:         0x[[S:[0-9a-f]{8}]]:     DW_TAG_structure_type
+# CHECK-NEXT:        DW_AT_name ("S")
+# CHECK-NEXT:        DW_AT_byte_size (0x08)
+
+## FIXME: the empty partial unit root is still emitted, because marking S's
+## parents does not stop at it as at a compile unit root (#219393).
+# CHECK-PU:        DW_TAG_partial_unit
+# CHECK-PU-NEXT:     DW_AT_producer ("by_hand")
+# CHECK-PU-NEXT:     DW_AT_name ("U01")
+# CHECK:           DW_TAG_compile_unit
+# CHECK-NEXT:        DW_AT_producer ("by_hand")
+# CHECK-NEXT:        DW_AT_name ("U02")
+# CHECK:           DW_TAG_subprogram
+# CHECK-NEXT:        DW_AT_name ("foo1")
+# CHECK:             DW_AT_type (0x00000000[[S]] "ns::S")
+
+## S has no members, whose DW_TAG_base_type would stay in U01 as a plain child.
+## The roots carry DW_AT_str_offsets_base, which the linker adds only to a
+## DW_TAG_compile_unit root (#219365).
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1130
+    Size:            0x10
+  - Name:            .debug_str_offsets
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0400000005000000"
+DWARF:
+  debug_abbrev:
+    - ID:    0
+      Table:
+      - Tag:      [[ROOT]]
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_namespace
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_structure_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_byte_size
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+  debug_info:
+    - Version:  5
+      UnitType: [[UNITTYPE]]
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: ns
+        - AbbrCode: 3
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 4
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 5
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x23
+        - AbbrCode: 0
+...

>From ce45a704d2718a6c5085fbb29e13dfdaed9bca63 Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Fri, 28 Aug 2026 23:37:48 -0400
Subject: [PATCH 2/3] [DWARFLinker] Scope type uniquing on the unit root, not
 on its tag

Make ODR deduplication consider types under a DW_TAG_partial_unit root.
Both linkers recognized only a DW_TAG_compile_unit root, so each
partial unit kept its own copy of every type. The classic linker gave a
partial unit root a null declaration context, which took the whole unit
out of uniquing. The parallel linker's search for a type's outermost
enclosing DIE did not stop at a partial unit root, which is never a
candidate, and the same predicate kept every type under that root in
plain DWARF. Both now recognize the root by DIE index zero.

isNamespaceLikeEntry() itself is unchanged, because a
DW_TAG_imported_unit must keep the contents of the partial unit it
imports. That used to be done by marking the whole unit live, so once
its types became candidates, a type both imported and referenced was
emitted twice. The parallel linker now treats the import as a reference
to each entity in the partial unit, looking through namespaces.

The tests deduplicate a unit-scope and a namespace-scope struct across
two partial units with both linkers, with a compile unit control and a
--no-odr-deduplication run, and check that an imported unit keeps the
contents of the unit it imports (parallel only, because of #219621).

Fixes #219393.

Assisted-by: Claude Code
---
 .../Classic/DWARFLinkerDeclContext.cpp        |   6 +-
 .../Parallel/DependencyTracker.cpp            |  69 +++-
 .../dwarf5-partial-unit-imported-unit.test    | 300 +++++++++++++++++
 .../ELF/X86/dwarf5-partial-unit-odr.test      | 311 ++++++++++++++++++
 .../X86/dwarf5-partial-unit-types-only.test   |  62 ++--
 5 files changed, 722 insertions(+), 26 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-imported-unit.test
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-odr.test

diff --git a/llvm/lib/DWARFLinker/Classic/DWARFLinkerDeclContext.cpp b/llvm/lib/DWARFLinker/Classic/DWARFLinkerDeclContext.cpp
index bfec342526ea43..410ef8421fca63 100644
--- a/llvm/lib/DWARFLinker/Classic/DWARFLinkerDeclContext.cpp
+++ b/llvm/lib/DWARFLinker/Classic/DWARFLinkerDeclContext.cpp
@@ -66,14 +66,16 @@ DeclContextTree::getChildDeclContext(DeclContext &Context, const DWARFDie &DIE,
   // have a specification or an abstract_origin. We will get the
   // parent context wrong here.
 
+  // The unit root, whatever its tag, passes the root context to its children.
+  if (U.getOrigUnit().getDIEIndex(DIE) == 0)
+    return PointerIntPair<DeclContext *, 1>(&Context);
+
   switch (Tag) {
   default:
     // By default stop gathering child contexts.
     return PointerIntPair<DeclContext *, 1>(nullptr);
   case dwarf::DW_TAG_module:
     break;
-  case dwarf::DW_TAG_compile_unit:
-    return PointerIntPair<DeclContext *, 1>(&Context);
   case dwarf::DW_TAG_subprogram:
     // Do not unique anything inside CU local functions.
     if ((Context.getTag() == dwarf::DW_TAG_namespace ||
diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
index 669ea2ceb16c9f..a521f587c7c42a 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
@@ -106,6 +106,8 @@ void DependencyTracker::verifyKeepChain() {
 #endif
 }
 
+/// Must not match DW_TAG_partial_unit: a DW_TAG_imported_unit needs the whole
+/// partial unit it imports.
 static bool isNamespaceLikeEntry(const DWARFDebugInfoEntry *Entry) {
   switch (Entry->getTag()) {
   case dwarf::DW_TAG_compile_unit:
@@ -118,6 +120,13 @@ static bool isNamespaceLikeEntry(const DWARFDebugInfoEntry *Entry) {
   }
 }
 
+/// A unit root or namespace-like entry ends the search for a type's outermost
+/// enclosing DIE, and keeping one of its children does not keep the rest.
+static bool isUnitRootOrNamespaceLikeEntry(uint32_t Idx,
+                                           const DWARFDebugInfoEntry *Entry) {
+  return CompileUnit::isUnitRootDIE(Idx) || isNamespaceLikeEntry(Entry);
+}
+
 bool DependencyTracker::resolveDependenciesAndMarkLiveness(
     bool InterCUProcessingStarted, std::atomic<bool> &HasNewInterconnectedCUs) {
   RootEntriesWorkList.clear();
@@ -482,7 +491,8 @@ void DependencyTracker::markParentsAsKeepingChildren(
         bool AddToWorklist = !isAlreadyMarked(
             ParentInfo, CompileUnit::DieOutputPlacement::TypeTable);
         ParentInfo.setKeepTypeChildren();
-        if (AddToWorklist && !isNamespaceLikeEntry(ParentEntry)) {
+        if (AddToWorklist &&
+            !isUnitRootOrNamespaceLikeEntry(*ParentIdx, ParentEntry)) {
           addActionToRootEntriesWorkList(
               LiveRootWorklistActionTy::MarkTypeChildrenRec,
               UnitEntryPairTy{Entry.CU, ParentEntry}, std::nullopt);
@@ -497,7 +507,8 @@ void DependencyTracker::markParentsAsKeepingChildren(
         bool AddToWorklist = !isAlreadyMarked(
             ParentInfo, CompileUnit::DieOutputPlacement::PlainDwarf);
         ParentInfo.setKeepPlainChildren();
-        if (AddToWorklist && !isNamespaceLikeEntry(ParentEntry)) {
+        if (AddToWorklist &&
+            !isUnitRootOrNamespaceLikeEntry(*ParentIdx, ParentEntry)) {
           addActionToRootEntriesWorkList(
               LiveRootWorklistActionTy::MarkLiveChildrenRec,
               UnitEntryPairTy{Entry.CU, ParentEntry}, std::nullopt);
@@ -846,6 +857,48 @@ bool DependencyTracker::maybeAddReferencedRoots(
     llvm_unreachable("Unknown TreeWalkKindTy enum");
   };
 
+  // Adds a root for each entity in Scope of an imported partial unit, looking
+  // through namespaces. Entities the unit keeps by itself are skipped.
+  auto AddImportedScopeRoots = [&](auto &Self,
+                                   const UnitEntryPairTy &Scope) -> void {
+    for (const DWARFDebugInfoEntry *CurChild =
+             Scope.CU->getFirstChildEntry(Scope.DieEntry);
+         CurChild && CurChild->getAbbreviationDeclarationPtr();
+         CurChild = Scope.CU->getSiblingEntry(CurChild)) {
+      UnitEntryPairTy ChildEntry(Scope.CU, CurChild);
+      CompileUnit::DIEInfo &ChildInfo = Scope.CU->getDIEInfo(CurChild);
+
+      switch (CurChild->getTag()) {
+      case dwarf::DW_TAG_subprogram:
+      case dwarf::DW_TAG_variable:
+      case dwarf::DW_TAG_constant:
+      case dwarf::DW_TAG_label:
+        // Code is kept or dropped by its address.
+        if (ChildInfo.getHasAnAddress())
+          continue;
+        break;
+      case dwarf::DW_TAG_base_type:
+      case dwarf::DW_TAG_imported_module:
+      case dwarf::DW_TAG_imported_declaration:
+      case dwarf::DW_TAG_imported_unit:
+        // collectRootsToKeep() keeps these whatever refers to them.
+        continue;
+      default:
+        break;
+      }
+
+      if (isNamespaceLikeEntry(CurChild)) {
+        Self(Self, ChildEntry);
+        continue;
+      }
+
+      AddRoot(ChildInfo.getODRAvailable()
+                  ? LiveRootWorklistActionTy::MarkTypeEntryRec
+                  : LiveRootWorklistActionTy::MarkLiveEntryRec,
+              ChildEntry, CurChild);
+    }
+  };
+
   DWARFUnit &Unit = Entry.CU->getOrigUnit();
   DWARFDataExtractor Data = Unit.getDebugInfoExtractor();
   uint64_t Offset =
@@ -913,6 +966,16 @@ bool DependencyTracker::maybeAddReferencedRoots(
         continue;
       }
 
+      // Treat a DW_TAG_imported_unit as a reference to each entity in the
+      // imported unit, so that a type there is not also kept in plain DWARF.
+      if (CompileUnit::isUnitRootDIE(
+              RefDie->CU->getDIEIndex(RefDie->DieEntry))) {
+        AddRoot(LiveRootWorklistActionTy::MarkSingleLiveEntry, *RefDie,
+                nullptr);
+        AddImportedScopeRoots(AddImportedScopeRoots, *RefDie);
+        continue;
+      }
+
       AddRoot(Action, *RefDie, nullptr);
       continue;
     }
@@ -952,7 +1015,7 @@ DependencyTracker::getRootForSpecifiedEntry(UnitEntryPairTy Entry) {
 
     const DWARFDebugInfoEntry *ParentEntry =
         Result.CU->getDebugInfoEntry(*ParentIdx);
-    if (isNamespaceLikeEntry(ParentEntry))
+    if (isUnitRootOrNamespaceLikeEntry(*ParentIdx, ParentEntry))
       break;
     Result.DieEntry = ParentEntry;
   } while (true);
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-imported-unit.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-imported-unit.test
new file mode 100644
index 00000000000000..1bfdaece56eb43
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-imported-unit.test
@@ -0,0 +1,300 @@
+## Check that a DW_TAG_imported_unit keeps everything in the partial unit it
+## imports, and that S is output once when foo1 also references it.
+##
+## U01:  namespace ns { struct S {}; }
+## U02:  // DW_TAG_imported_unit of U01
+##       void foo1() { ... }     // first input
+##       ns::S foo1() { ... }    // second input, DW_FORM_ref_addr into U01
+##
+## FIXME: Also link the classic linker once #219621 is fixed.
+
+# RUN: yaml2obj --docnum=1 %s -o %t.import.o
+# RUN: llvm-dwarfutil --linker parallel %t.import.o %t.import.odr
+# RUN: llvm-dwarfdump --debug-info %t.import.odr | FileCheck %s --check-prefix=ODR \
+# RUN:   --implicit-check-not=DW_TAG_structure_type
+# RUN: llvm-dwarfutil --linker parallel --no-odr-deduplication %t.import.o %t.import.noodr
+# RUN: llvm-dwarfdump --debug-info %t.import.noodr | FileCheck %s --check-prefix=NOODR \
+# RUN:   --implicit-check-not=DW_TAG_structure_type --implicit-check-not=__artificial_type_unit
+
+# RUN: yaml2obj --docnum=2 %s -o %t.ref.o
+# RUN: llvm-dwarfutil --linker parallel %t.ref.o %t.ref.odr
+# RUN: llvm-dwarfdump --debug-info %t.ref.odr | FileCheck %s --check-prefixes=ODR,REF-ODR \
+# RUN:   --implicit-check-not=DW_TAG_structure_type
+# RUN: llvm-dwarfutil --linker parallel --no-odr-deduplication %t.ref.o %t.ref.noodr
+# RUN: llvm-dwarfdump --debug-info %t.ref.noodr | FileCheck %s --check-prefixes=NOODR,REF-NOODR \
+# RUN:   --implicit-check-not=DW_TAG_structure_type --implicit-check-not=__artificial_type_unit
+
+## With ODR deduplication S moves to the type unit, and U01 stays as an empty
+## root for the import to name.
+# ODR:          DW_AT_name ("__artificial_type_unit")
+# ODR:          DW_TAG_namespace
+# ODR-NEXT:       DW_AT_name ("ns")
+# ODR:        0x[[S:[0-9a-f]{8}]]: DW_TAG_structure_type
+# ODR-NEXT:       DW_AT_name ("S")
+# ODR:        0x[[U01:[0-9a-f]{8}]]: DW_TAG_partial_unit
+# ODR-NEXT:     DW_AT_producer ("by_hand")
+# ODR-NEXT:     DW_AT_name ("U01")
+# ODR-NOT:      DW_TAG
+# ODR:          DW_TAG_compile_unit
+# ODR-NEXT:     DW_AT_producer ("by_hand")
+# ODR-NEXT:     DW_AT_name ("U02")
+# ODR:          DW_TAG_imported_unit
+# ODR-NEXT:       DW_AT_import (0x00000000[[U01]] "U01")
+# ODR:          DW_TAG_subprogram
+# ODR-NEXT:       DW_AT_name ("foo1")
+# REF-ODR:        DW_AT_type (0x00000000[[S]] "ns::S")
+
+## Without it S stays in U01. FIXME: llvm-dwarfdump prefixes the partial unit's
+## name to the type (#219635).
+# NOODR:      0x[[U01:[0-9a-f]{8}]]: DW_TAG_partial_unit
+# NOODR-NEXT:   DW_AT_producer ("by_hand")
+# NOODR-NEXT:   DW_AT_name ("U01")
+# NOODR:        DW_TAG_namespace
+# NOODR-NEXT:     DW_AT_name ("ns")
+# NOODR:      0x[[S:[0-9a-f]{8}]]: DW_TAG_structure_type
+# NOODR-NEXT:     DW_AT_name ("S")
+# NOODR:      DW_TAG_compile_unit
+# NOODR-NEXT:   DW_AT_producer ("by_hand")
+# NOODR-NEXT:   DW_AT_name ("U02")
+# NOODR:        DW_TAG_imported_unit
+# NOODR-NEXT:     DW_AT_import (0x00000000[[U01]] "U01")
+# NOODR:        DW_TAG_subprogram
+# NOODR-NEXT:     DW_AT_name ("foo1")
+# REF-NOODR:      DW_AT_type (0x00000000[[S]] "U01::ns::S")
+
+## U01 holds no code. The roots carry DW_AT_str_offsets_base, which the linker
+## adds only to a DW_TAG_compile_unit root (#219365).
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1130
+    Size:            0x10
+  - Name:            .debug_str_offsets
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0400000005000000"
+DWARF:
+  debug_abbrev:
+    - ID:    0
+      Table:
+      - Tag:      DW_TAG_partial_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_namespace
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_structure_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_byte_size
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_imported_unit
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_import
+            Form:      DW_FORM_ref_addr
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_partial
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: ns
+        - AbbrCode: 3
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 4
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 6
+          Values:
+            - Value: 0x0c
+        - AbbrCode: 5
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+        - AbbrCode: 0
+...
+
+## The same units, with foo1 returning S through a DW_FORM_ref_addr to 0x23,
+## the offset of S in U01.
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1130
+    Size:            0x10
+  - Name:            .debug_str_offsets
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0400000005000000"
+DWARF:
+  debug_abbrev:
+    - ID:    0
+      Table:
+      - Tag:      DW_TAG_partial_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_namespace
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_structure_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_byte_size
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref_addr
+      - Tag:      DW_TAG_imported_unit
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_import
+            Form:      DW_FORM_ref_addr
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_partial
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: ns
+        - AbbrCode: 3
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 4
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 6
+          Values:
+            - Value: 0x0c
+        - AbbrCode: 5
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x23
+        - AbbrCode: 0
+...
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-odr.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-odr.test
new file mode 100644
index 00000000000000..43454234c73646
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-odr.test
@@ -0,0 +1,311 @@
+## Check that ODR deduplication treats a type under a DW_TAG_partial_unit root
+## as one under a DW_TAG_compile_unit root, which is the control. Each unit
+## defines a struct S at unit scope and an unrelated ns::S, so a correct link
+## keeps two definitions, one that merged the scopes keeps one, and one that
+## deduplicated nothing keeps four.
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -o %t.cu.o
+# RUN: llvm-dwarfutil %t.cu.o %t.cu.classic
+# RUN: llvm-dwarfdump --debug-info %t.cu.classic | FileCheck %s --check-prefixes=CLASSIC,CLASSIC-CU --implicit-check-not=DW_TAG_structure_type
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu.parallel | FileCheck %s --check-prefix=PARALLEL --implicit-check-not=DW_TAG_structure_type
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -o %t.pu.o
+# RUN: llvm-dwarfutil %t.pu.o %t.pu.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu.classic | FileCheck %s --check-prefixes=CLASSIC,CLASSIC-PU --implicit-check-not=DW_TAG_structure_type
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu.parallel | FileCheck %s --check-prefix=PARALLEL --implicit-check-not=DW_TAG_structure_type
+
+## Deduplication off tells a deduplicated link from one that dropped types.
+# RUN: llvm-dwarfutil --no-odr-deduplication %t.pu.o %t.pu-noodr.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu-noodr.classic | FileCheck %s --check-prefix=NOODR \
+# RUN:   --implicit-check-not=__artificial_type_unit --implicit-check-not=DW_TAG_structure_type
+# RUN: llvm-dwarfutil --linker parallel --no-odr-deduplication %t.pu.o %t.pu-noodr.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu-noodr.parallel | FileCheck %s --check-prefix=NOODR \
+# RUN:   --implicit-check-not=__artificial_type_unit --implicit-check-not=DW_TAG_structure_type
+
+## The classic linker keeps both definitions in U01. FIXME: llvm-dwarfdump
+## prefixes the partial unit's name to the type (#219635).
+# CLASSIC:        DW_AT_name ("U01")
+# CLASSIC:        DW_TAG_subprogram
+# CLASSIC-NEXT:     DW_AT_name ("foo1")
+# CLASSIC-CU:       DW_AT_type (0x00000000[[S:[0-9a-f]{8}]] "S")
+# CLASSIC-PU:       DW_AT_type (0x00000000[[S:[0-9a-f]{8}]] "U01::S")
+# CLASSIC:        DW_TAG_subprogram
+# CLASSIC-NEXT:     DW_AT_name ("foo2")
+# CLASSIC-CU:       DW_AT_type (0x00000000[[NSS:[0-9a-f]{8}]] "ns::S")
+# CLASSIC-PU:       DW_AT_type (0x00000000[[NSS:[0-9a-f]{8}]] "U01::ns::S")
+# CLASSIC:      0x[[S]]:   DW_TAG_structure_type
+# CLASSIC-NEXT:   DW_AT_name ("S")
+# CLASSIC:        DW_TAG_member
+# CLASSIC-NEXT:     DW_AT_name ("m1")
+# CLASSIC:        DW_TAG_member
+# CLASSIC-NEXT:     DW_AT_name ("m2")
+# CLASSIC:        DW_TAG_namespace
+# CLASSIC-NEXT:     DW_AT_name ("ns")
+# CLASSIC:      0x[[NSS]]:   DW_TAG_structure_type
+# CLASSIC-NEXT:   DW_AT_name ("S")
+# CLASSIC:        DW_TAG_member
+# CLASSIC-NEXT:     DW_AT_name ("m1")
+# CLASSIC:        DW_TAG_member
+# CLASSIC-NEXT:     DW_AT_name ("m2")
+# CLASSIC:        DW_AT_name ("U02")
+# CLASSIC:        DW_TAG_subprogram
+# CLASSIC-NEXT:     DW_AT_name ("bar1")
+# CLASSIC-CU:       DW_AT_type (0x00000000[[S]] "S")
+# CLASSIC-PU:       DW_AT_type (0x00000000[[S]] "U01::S")
+# CLASSIC:        DW_TAG_subprogram
+# CLASSIC-NEXT:     DW_AT_name ("bar2")
+# CLASSIC-CU:       DW_AT_type (0x00000000[[NSS]] "ns::S")
+# CLASSIC-PU:       DW_AT_type (0x00000000[[NSS]] "U01::ns::S")
+
+## The parallel linker moves both types into the artificial type unit.
+# PARALLEL:      DW_AT_name ("__artificial_type_unit")
+# PARALLEL:      DW_TAG_namespace
+# PARALLEL-NEXT:   DW_AT_name ("ns")
+# PARALLEL:      0x[[NSS:[0-9a-f]{8}]]:   DW_TAG_structure_type
+# PARALLEL-NEXT:   DW_AT_name ("S")
+# PARALLEL:        DW_TAG_member
+# PARALLEL-NEXT:     DW_AT_name ("m1")
+# PARALLEL:        DW_TAG_member
+# PARALLEL-NEXT:     DW_AT_name ("m2")
+# PARALLEL:      0x[[S:[0-9a-f]{8}]]:   DW_TAG_structure_type
+# PARALLEL-NEXT:   DW_AT_name ("S")
+# PARALLEL:        DW_TAG_member
+# PARALLEL-NEXT:     DW_AT_name ("m1")
+# PARALLEL:        DW_TAG_member
+# PARALLEL-NEXT:     DW_AT_name ("m2")
+# PARALLEL:      DW_AT_name ("U01")
+# PARALLEL:      DW_TAG_subprogram
+# PARALLEL-NEXT:   DW_AT_name ("foo1")
+# PARALLEL:        DW_AT_type (0x00000000[[S]] "S")
+# PARALLEL:      DW_TAG_subprogram
+# PARALLEL-NEXT:   DW_AT_name ("foo2")
+# PARALLEL:        DW_AT_type (0x00000000[[NSS]] "ns::S")
+# PARALLEL:      DW_AT_name ("U02")
+# PARALLEL:      DW_TAG_subprogram
+# PARALLEL-NEXT:   DW_AT_name ("bar1")
+# PARALLEL:        DW_AT_type (0x00000000[[S]] "S")
+# PARALLEL:      DW_TAG_subprogram
+# PARALLEL-NEXT:   DW_AT_name ("bar2")
+# PARALLEL:        DW_AT_type (0x00000000[[NSS]] "ns::S")
+
+## Without deduplication each unit keeps its own definitions.
+# NOODR:      DW_AT_name ("U01")
+# NOODR:      DW_TAG_structure_type
+# NOODR-NEXT:   DW_AT_name ("S")
+# NOODR:      DW_TAG_namespace
+# NOODR-NEXT:   DW_AT_name ("ns")
+# NOODR:      DW_TAG_structure_type
+# NOODR-NEXT:   DW_AT_name ("S")
+# NOODR:      DW_AT_name ("U02")
+# NOODR:      DW_TAG_structure_type
+# NOODR-NEXT:   DW_AT_name ("S")
+# NOODR:      DW_TAG_namespace
+# NOODR-NEXT:   DW_AT_name ("ns")
+# NOODR:      DW_TAG_structure_type
+# NOODR-NEXT:   DW_AT_name ("S")
+
+## The units cover disjoint halves of .text so that both are kept. The roots
+## carry DW_AT_str_offsets_base, which the linker adds only to a
+## DW_TAG_compile_unit root (#219365).
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1130
+    Size:            0x60
+  - Name:            .debug_str_offsets
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0400000005000000"
+DWARF:
+  debug_abbrev:
+    - ID:    0
+      Table:
+      - Tag:      [[ROOT]]
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+      - Tag:      DW_TAG_structure_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_byte_size
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_data_member_location
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_namespace
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+  debug_info:
+    - Version:  5
+      UnitType: [[UNITTYPE]]
+      AbbrevTableID: 0
+      AbbrOffset: 0x0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x30
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x63
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value: 0x1150
+            - Value: 0x10
+            - Value: 0x7e
+        - AbbrCode: 3
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 4
+          Values:
+            - CStr: m1
+            - Value: 0x96
+            - Value: 0x0
+        - AbbrCode: 4
+          Values:
+            - CStr: m2
+            - Value: 0x96
+            - Value: 0x4
+        - AbbrCode: 0
+        - AbbrCode: 6
+          Values:
+            - CStr: ns
+        - AbbrCode: 3
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 4
+          Values:
+            - CStr: m1
+            - Value: 0x96
+            - Value: 0x0
+        - AbbrCode: 4
+          Values:
+            - CStr: m2
+            - Value: 0x96
+            - Value: 0x4
+        - AbbrCode: 0
+        - AbbrCode: 0
+        - AbbrCode: 5
+          Values:
+            - CStr: int
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: [[UNITTYPE]]
+      AbbrevTableID: 0
+      AbbrOffset: 0x0
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1160
+            - Value: 0x30
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: bar1
+            - Value: 0x1160
+            - Value: 0x10
+            - Value: 0x63
+        - AbbrCode: 2
+          Values:
+            - CStr: bar2
+            - Value: 0x1180
+            - Value: 0x10
+            - Value: 0x7e
+        - AbbrCode: 3
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 4
+          Values:
+            - CStr: m1
+            - Value: 0x96
+            - Value: 0x0
+        - AbbrCode: 4
+          Values:
+            - CStr: m2
+            - Value: 0x96
+            - Value: 0x4
+        - AbbrCode: 0
+        - AbbrCode: 6
+          Values:
+            - CStr: ns
+        - AbbrCode: 3
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 4
+          Values:
+            - CStr: m1
+            - Value: 0x96
+            - Value: 0x0
+        - AbbrCode: 4
+          Values:
+            - CStr: m2
+            - Value: 0x96
+            - Value: 0x4
+        - AbbrCode: 0
+        - AbbrCode: 0
+        - AbbrCode: 5
+          Values:
+            - CStr: int
+        - AbbrCode: 0
+...
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test
index 263589c59cfaf5..25c1cb1f9adee3 100644
--- a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-types-only.test
@@ -6,34 +6,54 @@
 ## U02:  ns::S foo1() { ... }   // DW_FORM_ref_addr reference into U01
 
 # RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -o %t.cu.o
-# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.out
-# RUN: llvm-dwarfdump --debug-info %t.cu.out | FileCheck %s --implicit-check-not='DW_AT_name ("U01")' \
+# RUN: llvm-dwarfutil %t.cu.o %t.cu.classic
+# RUN: llvm-dwarfdump --debug-info %t.cu.classic | FileCheck %s --check-prefixes=CLASSIC,CLASSIC-CU
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu.parallel | FileCheck %s --check-prefix=PARALLEL --implicit-check-not='DW_AT_name ("U01")' \
 # RUN:   --implicit-check-not=DW_TAG_structure_type
 
 # RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -o %t.pu.o
-# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.out
-# RUN: llvm-dwarfdump --debug-info %t.pu.out | FileCheck %s --check-prefixes=CHECK,CHECK-PU \
+# RUN: llvm-dwarfutil %t.pu.o %t.pu.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu.classic | FileCheck %s --check-prefixes=CLASSIC,CLASSIC-PU
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu.parallel | FileCheck %s --check-prefix=PARALLEL --implicit-check-not='DW_AT_name ("U01")' \
 # RUN:   --implicit-check-not=DW_TAG_structure_type
 
+## The two parallel links agree byte for byte.
+# RUN: cmp %t.cu.parallel %t.pu.parallel
+
+## FIXME: llvm-dwarfdump prefixes the partial unit's name to the type (#219635).
+# CLASSIC-CU:     DW_TAG_compile_unit
+# CLASSIC-PU:     DW_TAG_partial_unit
+# CLASSIC-NEXT:     DW_AT_producer ("by_hand")
+# CLASSIC-NEXT:     DW_AT_name ("U01")
+# CLASSIC:        DW_TAG_namespace
+# CLASSIC-NEXT:     DW_AT_name ("ns")
+# CLASSIC:      0x[[S:[0-9a-f]{8}]]:     DW_TAG_structure_type
+# CLASSIC-NEXT:   DW_AT_name ("S")
+# CLASSIC:        DW_TAG_compile_unit
+# CLASSIC-NEXT:     DW_AT_producer ("by_hand")
+# CLASSIC-NEXT:     DW_AT_name ("U02")
+# CLASSIC:        DW_TAG_subprogram
+# CLASSIC-NEXT:     DW_AT_name ("foo1")
+# CLASSIC-CU:       DW_AT_type (0x00000000[[S]] "ns::S")
+# CLASSIC-PU:       DW_AT_type (0x00000000[[S]] "U01::ns::S")
+
 ## S is output only in the type unit, and foo1's reference resolves there.
-# CHECK:           DW_AT_name ("__artificial_type_unit")
-# CHECK:           DW_TAG_namespace
-# CHECK-NEXT:        DW_AT_name ("ns")
-# CHECK:         0x[[S:[0-9a-f]{8}]]:     DW_TAG_structure_type
-# CHECK-NEXT:        DW_AT_name ("S")
-# CHECK-NEXT:        DW_AT_byte_size (0x08)
+# PARALLEL:      DW_AT_name ("__artificial_type_unit")
+# PARALLEL:      DW_TAG_namespace
+# PARALLEL-NEXT:   DW_AT_name ("ns")
+# PARALLEL:      0x[[S:[0-9a-f]{8}]]:     DW_TAG_structure_type
+# PARALLEL-NEXT:   DW_AT_name ("S")
+# PARALLEL-NEXT:   DW_AT_byte_size (0x08)
 
-## FIXME: the empty partial unit root is still emitted, because marking S's
-## parents does not stop at it as at a compile unit root (#219393).
-# CHECK-PU:        DW_TAG_partial_unit
-# CHECK-PU-NEXT:     DW_AT_producer ("by_hand")
-# CHECK-PU-NEXT:     DW_AT_name ("U01")
-# CHECK:           DW_TAG_compile_unit
-# CHECK-NEXT:        DW_AT_producer ("by_hand")
-# CHECK-NEXT:        DW_AT_name ("U02")
-# CHECK:           DW_TAG_subprogram
-# CHECK-NEXT:        DW_AT_name ("foo1")
-# CHECK:             DW_AT_type (0x00000000[[S]] "ns::S")
+## U01, left empty, is dropped whatever its root tag.
+# PARALLEL:      DW_TAG_compile_unit
+# PARALLEL-NEXT:   DW_AT_producer ("by_hand")
+# PARALLEL-NEXT:   DW_AT_name ("U02")
+# PARALLEL:      DW_TAG_subprogram
+# PARALLEL-NEXT:   DW_AT_name ("foo1")
+# PARALLEL:        DW_AT_type (0x00000000[[S]] "ns::S")
 
 ## S has no members, whose DW_TAG_base_type would stay in U01 as a plain child.
 ## The roots carry DW_AT_str_offsets_base, which the linker adds only to a

>From aa4bff038b57a8387567c91fe433177b7419b0b4 Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Sun, 30 Aug 2026 01:46:33 -0400
Subject: [PATCH 3/3] [DWARFLinker][Parallel] Keep every DIE with
 --no-garbage-collection

The parallel linker still collected garbage under
--no-garbage-collection. The option arrives as UpdateIndexTablesOnly,
which only makes subprograms, variables and labels unconditionally
live. collectRootsToKeep() still selects roots by tag, so an
unreferenced namespace or type and everything beneath it is dropped,
under any root tag. The classic linker keeps everything in this mode
with markEverythingAsKept().

With UpdateIndexTablesOnly, also queue a recursive live mark on the
unit root. The walk skips subprograms, variables, constants and labels
that have an address, since collectRootsToKeep() already schedules them
as roots of their own.

The test links a compile unit root and a partial unit root with both
linkers, with collection enabled and disabled, against one set of
expectations. The parallel empty-CU.test for dsymutil --update now
expects the empty unit the classic linker emits, and the FIXME in its
classic twin is dropped.

Fixes #219652.

Assisted-by: Claude Code
---
 .../Parallel/DependencyTracker.cpp            |  12 +-
 .../X86/DWARFLinkerParallel/empty-CU.test     |   6 +-
 llvm/test/tools/dsymutil/X86/empty-CU.test    |   2 -
 ...warf5-partial-unit-garbage-collection.test | 208 ++++++++++++++++++
 4 files changed, 222 insertions(+), 6 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-garbage-collection.test

diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
index a521f587c7c42a..eed6df975666da 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
@@ -141,10 +141,16 @@ bool DependencyTracker::resolveDependenciesAndMarkLiveness(
   InterCUProcessingWasStarted = InterCUProcessingStarted;
 
   // Search for live root DIEs.
-  CompileUnit::DIEInfo &CUInfo = CU.getDIEInfo(CU.getDebugInfoEntry(0));
+  UnitEntryPairTy UnitRootEntry{&CU, CU.getDebugInfoEntry(0)};
+  CompileUnit::DIEInfo &CUInfo = CU.getDIEInfo(UnitRootEntry.DieEntry);
   CUInfo.setPlacement(CompileUnit::PlainDwarf);
-  collectRootsToKeep(UnitEntryPairTy{&CU, CU.getDebugInfoEntry(0)},
-                     std::nullopt, false);
+  collectRootsToKeep(UnitRootEntry, std::nullopt, false);
+
+  // With UpdateIndexTablesOnly nothing is garbage collected, so mark the whole
+  // unit live, as the classic linker's markEverythingAsKept() does.
+  if (CU.getGlobalData().getOptions().UpdateIndexTablesOnly)
+    addActionToRootEntriesWorkList(LiveRootWorklistActionTy::MarkLiveEntryRec,
+                                   UnitRootEntry, std::nullopt);
 
   // Mark live DIEs as kept.
   return markCollectedLiveRootsAsKept(InterCUProcessingStarted,
diff --git a/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/empty-CU.test b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/empty-CU.test
index f47e5775ad7812..515ea4b2603174 100644
--- a/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/empty-CU.test
+++ b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/empty-CU.test
@@ -1,5 +1,9 @@
 RUN: llvm-mc %p/../../Inputs/empty-CU.s -filetype obj -triple x86_64-apple-darwin -o %t.o
 RUN: dsymutil --linker parallel --update -f %t.o -o - | llvm-dwarfdump -v - -debug-info | FileCheck %s
 
+## --update keeps the empty unit, as the classic linker does.
+
 CHECK: .debug_info contents:
-CHECK-NOT: DW_TAG_compile_unit
+CHECK: 0x00000000: Compile Unit: length = 0x00000008, format = DWARF32, version = 0x0003, abbr_offset = 0x0000, addr_size = 0x04 (next unit at 0x0000000c)
+
+CHECK: 0x0000000b: DW_TAG_compile_unit [1]
diff --git a/llvm/test/tools/dsymutil/X86/empty-CU.test b/llvm/test/tools/dsymutil/X86/empty-CU.test
index b0d5d093a90a86..acf4a00f2f769c 100644
--- a/llvm/test/tools/dsymutil/X86/empty-CU.test
+++ b/llvm/test/tools/dsymutil/X86/empty-CU.test
@@ -5,5 +5,3 @@ CHECK: .debug_info contents:
 CHECK: 0x00000000: Compile Unit: length = 0x00000008, format = DWARF32, version = 0x0003, abbr_offset = 0x0000, addr_size = 0x04 (next unit at 0x0000000c)
 
 CHECK: 0x0000000b: DW_TAG_compile_unit [1]
-
-## FIXME: Support --linker parallel
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-garbage-collection.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-garbage-collection.test
new file mode 100644
index 00000000000000..383421eabcf25e
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-garbage-collection.test
@@ -0,0 +1,208 @@
+## Check that --garbage-collection alone decides which DIEs are kept: neither
+## the root tag of the unit nor the linker backend may change the result.
+##
+## U02:  void foo2();                             // the only live code
+##       namespace ns { struct S { int m1; }; }   // nothing refers to it
+##       struct Outer { int m2; };                // nor to this one
+##
+## Outer survives collection: it holds the "int" DW_TAG_base_type, which is
+## always kept, and keeping one child of a structure keeps all of it.
+
+# RUN: yaml2obj %s -DROOT=DW_TAG_compile_unit -DUNITTYPE=DW_UT_compile -o %t.cu.o
+# RUN: yaml2obj %s -DROOT=DW_TAG_partial_unit -DUNITTYPE=DW_UT_partial -o %t.pu.o
+
+## Collection enabled: ns, S and m1 go, Outer stays.
+# RUN: llvm-dwarfutil %t.cu.o %t.cu.gc.classic
+# RUN: llvm-dwarfdump --debug-info %t.cu.gc.classic | FileCheck %s --check-prefixes=CHECK,CU --implicit-check-not='DW_AT_name ("{{(ns|S|m1)}}")'
+# RUN: llvm-dwarfutil %t.pu.o %t.pu.gc.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu.gc.classic | FileCheck %s --check-prefixes=CHECK,PU --implicit-check-not='DW_AT_name ("{{(ns|S|m1)}}")'
+# RUN: llvm-dwarfutil --linker parallel %t.cu.o %t.cu.gc.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu.gc.parallel | FileCheck %s --check-prefixes=CHECK,CU --implicit-check-not='DW_AT_name ("{{(ns|S|m1)}}")'
+# RUN: llvm-dwarfutil --linker parallel %t.pu.o %t.pu.gc.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu.gc.parallel | FileCheck %s --check-prefixes=CHECK,PU --implicit-check-not='DW_AT_name ("{{(ns|S|m1)}}")'
+
+## Collection disabled: every input DIE is kept.
+## Without --build-accelerator, --no-garbage-collection does not run the linker.
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF %t.cu.o %t.cu.nogc.classic
+# RUN: llvm-dwarfdump --debug-info %t.cu.nogc.classic | FileCheck %s --check-prefixes=CHECK,NOGC,CU
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF %t.pu.o %t.pu.nogc.classic
+# RUN: llvm-dwarfdump --debug-info %t.pu.nogc.classic | FileCheck %s --check-prefixes=CHECK,NOGC,PU
+# RUN: llvm-dwarfutil --linker parallel --no-garbage-collection --build-accelerator=DWARF %t.cu.o %t.cu.nogc.parallel
+# RUN: llvm-dwarfdump --debug-info %t.cu.nogc.parallel | FileCheck %s --check-prefixes=CHECK,NOGC,CU
+# RUN: llvm-dwarfutil --linker parallel --no-garbage-collection --build-accelerator=DWARF %t.pu.o %t.pu.nogc.parallel
+# RUN: llvm-dwarfdump --debug-info %t.pu.nogc.parallel | FileCheck %s --check-prefixes=CHECK,NOGC,PU
+
+## NOGC adds the DIEs kept only with collection disabled.
+# CHECK:           DW_AT_name ("U01")
+# CHECK:           DW_TAG_subprogram
+# CHECK-NEXT:        DW_AT_name ("foo1")
+# CU:              DW_TAG_compile_unit
+# PU:              DW_TAG_partial_unit
+# CHECK:           DW_AT_name ("U02")
+# CHECK:           DW_TAG_subprogram
+# CHECK-NEXT:        DW_AT_name ("foo2")
+# NOGC:            DW_TAG_namespace
+# NOGC-NEXT:         DW_AT_name ("ns")
+# NOGC:            DW_TAG_structure_type
+# NOGC-NEXT:         DW_AT_name ("S")
+# NOGC:            DW_TAG_member
+# NOGC-NEXT:         DW_AT_name ("m1")
+# CHECK:           DW_TAG_structure_type
+# CHECK-NEXT:        DW_AT_name ("Outer")
+# CHECK:           DW_TAG_member
+# CHECK-NEXT:        DW_AT_name ("m2")
+# CHECK:           DW_TAG_base_type
+# CHECK-NEXT:        DW_AT_name ("int")
+
+## Only U02's root tag varies. The roots carry DW_AT_str_offsets_base, which
+## the linker adds only to a DW_TAG_compile_unit root (#219365).
+--- !ELF
+FileHeader:
+  Class:    ELFCLASS64
+  Data:     ELFDATA2LSB
+  Type:     ET_REL
+  Machine:  EM_X86_64
+Sections:
+  - Name:            .text
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_EXECINSTR ]
+    Address:         0x1130
+    Size:            0x20
+  - Name:            .debug_str_offsets
+    Type:            SHT_PROGBITS
+    Flags:           [  ]
+    Content:        "0400000005000000"
+DWARF:
+  debug_abbrev:
+    - ID: 0
+      Table:
+      - Tag:      DW_TAG_compile_unit
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+      - Tag:      DW_TAG_subprogram
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+      - Tag:      DW_TAG_namespace
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      DW_TAG_structure_type
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_byte_size
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_member
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_type
+            Form:      DW_FORM_ref4
+          - Attribute: DW_AT_data_member_location
+            Form:      DW_FORM_data1
+      - Tag:      DW_TAG_base_type
+        Children: DW_CHILDREN_no
+        Attributes:
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+      - Tag:      [[ROOT]]
+        Children: DW_CHILDREN_yes
+        Attributes:
+          - Attribute: DW_AT_producer
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_name
+            Form:      DW_FORM_string
+          - Attribute: DW_AT_language
+            Form:      DW_FORM_data2
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+          - Attribute: DW_AT_str_offsets_base
+            Form:      DW_FORM_sec_offset
+  debug_info:
+    - Version:  5
+      AbbrevTableID: 0
+      UnitType: DW_UT_compile
+      Entries:
+        - AbbrCode: 1
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+        - AbbrCode: 0
+    - Version:  5
+      AbbrevTableID: 0
+      UnitType: [[UNITTYPE]]
+      Entries:
+        - AbbrCode: 7
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1140
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo2
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 3
+          Values:
+            - CStr: ns
+        - AbbrCode: 4
+          Values:
+            - CStr: S
+            - Value: 0x4
+        - AbbrCode: 5
+          Values:
+            - CStr: m1
+            - Value: 0x69
+            - Value: 0x0
+        - AbbrCode: 0
+        - AbbrCode: 0
+        - AbbrCode: 4
+          Values:
+            - CStr: Outer
+            - Value: 0x4
+        - AbbrCode: 5
+          Values:
+            - CStr: m2
+            - Value: 0x69
+            - Value: 0x0
+        - AbbrCode: 6
+          Values:
+            - CStr: int
+        - AbbrCode: 0
+        - AbbrCode: 0
+...



More information about the llvm-commits mailing list