[llvm] [AsmPrinter] Find a DIE's unit by its owner, not by its tag (PR #219660)

Arcadiy Ivanov via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 12 14:18:41 PDT 2026


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

>From e7c9e86340c915a902e9a650d4c08ef04d644082 Mon Sep 17 00:00:00 2001
From: Arcadiy Ivanov <arcadiy at ivanov.biz>
Date: Sat, 29 Aug 2026 05:23:27 -0400
Subject: [PATCH] [AsmPrinter] Find a DIE's unit by its owner

Fix DIE::getUnitDie() for DW_TAG_partial_unit. The existing parent walk
recognizes unit roots by tag but omits partial units, so a cross-unit
DW_FORM_ref_addr can reach getDebugSectionOffset() without a unit and
assert.

Have getUnit() walk the parent chain until Owner contains a DIEUnit, then
derive getUnitDie() from that unit. A DIEUnit owns its unit DIE, so this
handles every unit root tag without maintaining another tag list and
still returns null for a DIE tree that no DIEUnit owns. Use
dwarf::isUnitType for the constructor assertion as well. Update the
DIEUnit comments to describe the owner-based walk.

Add unit coverage for all four unit root tags and an unowned DIE tree.
Add a dwarfutil test for resolving DW_FORM_ref_addr into a partial unit
with and without garbage collection. Keep that test on the classic
linker and omit --verify because of #219613 and #219365.

Fixes #219650.

Assisted-by: Claude Code
---
 llvm/include/llvm/CodeGen/DIE.h               |  26 +-
 llvm/lib/CodeGen/AsmPrinter/DIE.cpp           |  27 +-
 .../ELF/X86/dwarf5-partial-unit-ref-addr.test | 303 ++++++++++++++++++
 llvm/unittests/CodeGen/DIETest.cpp            |  38 +++
 4 files changed, 361 insertions(+), 33 deletions(-)
 create mode 100644 llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ref-addr.test

diff --git a/llvm/include/llvm/CodeGen/DIE.h b/llvm/include/llvm/CodeGen/DIE.h
index bddbf9277cfbdf..376681e31884ac 100644
--- a/llvm/include/llvm/CodeGen/DIE.h
+++ b/llvm/include/llvm/CodeGen/DIE.h
@@ -937,18 +937,16 @@ class DIE : IntrusiveBackListNode, public DIEValueList {
   computeOffsetsAndAbbrevs(const dwarf::FormParams &FormParams,
                            DIEAbbrevSet &AbbrevSet, unsigned CUOffset);
 
-  /// Climb up the parent chain to get the compile unit or type unit DIE that
-  /// this DIE belongs to.
+  /// Climb up the parent chain to get the unit DIE that this DIE belongs to.
   ///
-  /// \returns the compile or type unit DIE that owns this DIE, or NULL if
-  /// this DIE hasn't been added to a unit DIE.
+  /// \returns the unit DIE that owns this DIE, or NULL if this DIE hasn't been
+  /// added to a unit DIE.
   LLVM_ABI const DIE *getUnitDie() const;
 
-  /// Climb up the parent chain to get the compile unit or type unit that this
-  /// DIE belongs to.
+  /// Climb up the parent chain to get the unit that this DIE belongs to.
   ///
-  /// \returns the DIEUnit that represents the compile or type unit that owns
-  /// this DIE, or NULL if this DIE hasn't been added to a unit DIE.
+  /// \returns the DIEUnit that represents the unit that owns this DIE, or NULL
+  /// if this DIE hasn't been added to a unit DIE.
   LLVM_ABI DIEUnit *getUnit() const;
 
   void setOffset(unsigned O) { Offset = O; }
@@ -980,13 +978,13 @@ class DIE : IntrusiveBackListNode, public DIEValueList {
 };
 
 //===--------------------------------------------------------------------===//
-/// Represents a compile or type unit.
+/// Represents a DWARF unit.
 class DIEUnit {
-  /// The compile unit or type unit DIE. This variable must be an instance of
-  /// DIE so that we can calculate the DIEUnit from any DIE by traversing the
-  /// parent backchain and getting the Unit DIE, and then casting itself to a
-  /// DIEUnit. This allows us to be able to find the DIEUnit for any DIE without
-  /// having to store a pointer to the DIEUnit in each DIE instance.
+  /// The unit DIE. This variable must be an instance of DIE so that we can
+  /// calculate the DIEUnit from any DIE by traversing the parent backchain
+  /// until we reach the DIE whose Owner is this DIEUnit. This allows us to be
+  /// able to find the DIEUnit for any DIE without having to store a pointer to
+  /// the DIEUnit in each DIE instance.
   DIE Die;
   /// The section this unit will be emitted in. This may or may not be set to
   /// a valid section depending on the client that is emitting DWARF.
diff --git a/llvm/lib/CodeGen/AsmPrinter/DIE.cpp b/llvm/lib/CodeGen/AsmPrinter/DIE.cpp
index 01e7ae434192de..899ce0c1a264a3 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DIE.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DIE.cpp
@@ -188,23 +188,16 @@ uint64_t DIE::getDebugSectionOffset() const {
   return Unit->getDebugSectionOffset() + getOffset();
 }
 
-const DIE *DIE::getUnitDie() const {
-  const DIE *p = this;
-  while (p) {
-    if (p->getTag() == dwarf::DW_TAG_compile_unit ||
-        p->getTag() == dwarf::DW_TAG_skeleton_unit ||
-        p->getTag() == dwarf::DW_TAG_type_unit)
-      return p;
-    p = p->getParent();
-  }
+DIEUnit *DIE::getUnit() const {
+  for (const DIE *P = this; P; P = P->getParent())
+    if (auto *Unit = dyn_cast_if_present<DIEUnit *>(P->Owner))
+      return Unit;
   return nullptr;
 }
 
-DIEUnit *DIE::getUnit() const {
-  const DIE *UnitDie = getUnitDie();
-  if (UnitDie)
-    return dyn_cast_if_present<DIEUnit *>(UnitDie->Owner);
-  return nullptr;
+const DIE *DIE::getUnitDie() const {
+  const DIEUnit *Unit = getUnit();
+  return Unit ? &Unit->getUnitDie() : nullptr;
 }
 
 DIEValue DIE::findAttribute(dwarf::Attribute Attribute) const {
@@ -305,11 +298,7 @@ unsigned DIE::computeOffsetsAndAbbrevs(const dwarf::FormParams &FormParams,
 //===----------------------------------------------------------------------===//
 DIEUnit::DIEUnit(dwarf::Tag UnitTag) : Die(UnitTag) {
   Die.Owner = this;
-  assert((UnitTag == dwarf::DW_TAG_compile_unit ||
-          UnitTag == dwarf::DW_TAG_skeleton_unit ||
-          UnitTag == dwarf::DW_TAG_type_unit ||
-          UnitTag == dwarf::DW_TAG_partial_unit) &&
-         "expected a unit TAG");
+  assert(dwarf::isUnitType(UnitTag) && "expected a unit TAG");
 }
 
 void DIEValue::emitValue(const AsmPrinter *AP) const {
diff --git a/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ref-addr.test b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ref-addr.test
new file mode 100644
index 00000000000000..56e01be796c78f
--- /dev/null
+++ b/llvm/test/tools/llvm-dwarfutil/ELF/X86/dwarf5-partial-unit-ref-addr.test
@@ -0,0 +1,303 @@
+## This test checks that a cross-unit DW_FORM_ref_addr reference into a
+## DW_TAG_partial_unit is rewritten to the section-absolute offset of the
+## referenced DIE, as it already is for a DW_TAG_compile_unit.
+##
+## The two documents differ only in the root tag and unit type of the unit
+## holding the referenced type, so the compile unit document is the control.
+##
+## Only the classic linker is exercised. The parallel linker asserts on a type
+## under a partial unit root, which is issue #219613, and --verify rejects the
+## output because a partial unit still ships under a DW_UT_compile header,
+## which is issue #219365.
+
+# RUN: yaml2obj --docnum=1 %s -o %t-cu.o
+# RUN: yaml2obj --docnum=2 %s -o %t-pu.o
+
+# RUN: llvm-dwarfutil %t-cu.o %t-cu.out
+# RUN: llvm-dwarfdump --debug-info %t-cu.out | FileCheck %s
+
+# RUN: llvm-dwarfutil %t-pu.o %t-pu.out
+# RUN: llvm-dwarfdump --debug-info %t-pu.out | FileCheck %s
+
+## Update mode keeps every DIE, so the same reference has to be resolved there.
+
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF \
+# RUN:   %t-cu.o %t-cu.upd
+# RUN: llvm-dwarfdump --debug-info %t-cu.upd | FileCheck %s
+
+# RUN: llvm-dwarfutil --no-garbage-collection --build-accelerator=DWARF \
+# RUN:   %t-pu.o %t-pu.upd
+# RUN: llvm-dwarfdump --debug-info %t-pu.upd | FileCheck %s
+
+## U01 owns the type, U02 names it across the unit boundary.
+# CHECK: DW_AT_name{{.*}}"U01"
+
+# CHECK: 0x[[S:[0-9a-f]*]]: DW_TAG_structure_type
+# CHECK-NEXT: DW_AT_name{{.*}}"S"
+
+# CHECK: DW_AT_name{{.*}}"U02"
+
+# CHECK: DW_TAG_subprogram
+# CHECK-NEXT: DW_AT_name{{.*}}"foo2"
+# CHECK-NEXT: DW_AT_type{{.*}}(0x00000000[[S]] "{{.*}}S")
+
+## The control: U01 is an ordinary compilation unit.
+--- !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_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_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_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_type
+            Form:      DW_FORM_ref_addr
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 5
+          Values:
+            - CStr: by_hand
+            - CStr: U01
+            - Value: 0x04
+            - Value: 0x1130
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 2
+          Values:
+            - CStr: foo1
+            - Value: 0x1130
+            - Value: 0x10
+        - AbbrCode: 3
+          Values:
+            - CStr: ns
+        - AbbrCode: 4
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 5
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1140
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 6
+          Values:
+            - CStr: foo2
+            - Value: 0x49
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 0
+
+## The case: U01 is a partial unit, and nothing else changes.
+--- !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_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_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_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_type
+            Form:      DW_FORM_ref_addr
+          - Attribute: DW_AT_low_pc
+            Form:      DW_FORM_addr
+          - Attribute: DW_AT_high_pc
+            Form:      DW_FORM_data8
+  debug_info:
+    - Version:  5
+      UnitType: DW_UT_partial
+      AbbrevTableID: 0
+      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: 3
+          Values:
+            - CStr: ns
+        - AbbrCode: 4
+          Values:
+            - CStr: S
+            - Value: 0x8
+        - AbbrCode: 0
+        - AbbrCode: 0
+    - Version:  5
+      UnitType: DW_UT_compile
+      AbbrevTableID: 0
+      Entries:
+        - AbbrCode: 5
+          Values:
+            - CStr: by_hand
+            - CStr: U02
+            - Value: 0x04
+            - Value: 0x1140
+            - Value: 0x10
+            - Value: 0x8
+        - AbbrCode: 6
+          Values:
+            - CStr: foo2
+            - Value: 0x49
+            - Value: 0x1140
+            - Value: 0x10
+        - AbbrCode: 0
+...
diff --git a/llvm/unittests/CodeGen/DIETest.cpp b/llvm/unittests/CodeGen/DIETest.cpp
index 4eb4a78f7de9d4..5311b583be058c 100644
--- a/llvm/unittests/CodeGen/DIETest.cpp
+++ b/llvm/unittests/CodeGen/DIETest.cpp
@@ -222,4 +222,42 @@ TEST(DIEValueListTest, DeleteValue) {
   EXPECT_FALSE(List.deleteValue(dwarf::DW_AT_high_pc));
 }
 
+TEST(DIETest, GetUnitDie) {
+  // A DIEUnit owns exactly one DIE, its unit DIE, whatever the root tag is, so
+  // the walk up from a nested DIE has to reach the unit for every unit root
+  // tag.
+  static const dwarf::Tag UnitTags[] = {
+      dwarf::DW_TAG_compile_unit, dwarf::DW_TAG_partial_unit,
+      dwarf::DW_TAG_type_unit, dwarf::DW_TAG_skeleton_unit};
+
+  BumpPtrAllocator Alloc;
+  for (dwarf::Tag UnitTag : UnitTags) {
+    BasicDIEUnit Unit(UnitTag);
+    Unit.setDebugSectionOffset(0x100);
+
+    DIE &Root = Unit.getUnitDie();
+    DIE &Namespace = Root.addChild(DIE::get(Alloc, dwarf::DW_TAG_namespace));
+    DIE &Struct =
+        Namespace.addChild(DIE::get(Alloc, dwarf::DW_TAG_structure_type));
+    Struct.setOffset(0x20);
+
+    EXPECT_EQ(&Root, Struct.getUnitDie());
+    EXPECT_EQ(static_cast<DIEUnit *>(&Unit), Struct.getUnit());
+    EXPECT_EQ(0x120u, Struct.getDebugSectionOffset());
+  }
+}
+
+TEST(DIETest, GetUnitDieWithoutUnit) {
+  // A tree carrying a unit root tag that was never handed to a DIEUnit belongs
+  // to no unit, so there is no absolute offset to compute for anything in it.
+  BumpPtrAllocator Alloc;
+  DIE *Root = DIE::get(Alloc, dwarf::DW_TAG_compile_unit);
+  DIE &Struct = Root->addChild(DIE::get(Alloc, dwarf::DW_TAG_structure_type));
+
+  EXPECT_EQ(nullptr, Root->getUnitDie());
+  EXPECT_EQ(nullptr, Root->getUnit());
+  EXPECT_EQ(nullptr, Struct.getUnitDie());
+  EXPECT_EQ(nullptr, Struct.getUnit());
+}
+
 } // end namespace



More information about the llvm-commits mailing list