[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