[llvm] [DWARFLinker] Fix RefTypeName crash and type-table non-determinism (PR #209033)
Jonas Devlieghere via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 17 08:03:56 PDT 2026
https://github.com/JDevlieghere updated https://github.com/llvm/llvm-project/pull/209033
>From 4fe4414e05b391b34d9293c72f896d05ec5caaf3 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Fri, 10 Jul 2026 17:08:30 -0700
Subject: [PATCH 1/2] [DWARFLinker] Fix RefTypeName crash and type-table
non-determinism
The parallel linker computes DIE placement (artificial type unit vs
plain DWARF) concurrently across compile units.
1. updateDependenciesCompleteness checked the placement of the enclosing
root type (getRootForSpecifiedEntry) instead of the actual referenced
DIE. A nested type can be demoted to plain DWARF while its root stays
in the type table, leaving a type-unit DIE that references a plain
DIE and tripping the RefTypeName assertion in
DIEAttributeCloner::cloneDieRefAttr. Carry the actual referenced DIE
on the worklist item and check its placement.
2. The completeness dependency set was recorded as a side effect of the
concurrent marking traversal, which short-circuits on already-marked
DIEs (isAlreadyMarked) before maybeAddReferencedRoots. When a shared
cross-CU DIE was already marked by a racing CU, the current
referencing root's outgoing dependencies were dropped, so the
demotion fixpoint missed demotions and whole type subtrees were left
in the artificial type unit non-deterministically. Add a
RecordDepsOnly mode that, on the short-circuit, re-walks the
already-marked subtree and records the dependencies directly without
marking, scheduling, or following references (so no cycles). This
makes the recorded dependency set complete and order-independent; the
demoted-DIE set now matches the single-threaded result on every run.
rdar://180584698
---
.../Parallel/DependencyTracker.cpp | 102 ++++--
.../DWARFLinker/Parallel/DependencyTracker.h | 54 +++-
.../odr-deterministic-nested-type.test | 294 ++++++++++++++++++
3 files changed, 414 insertions(+), 36 deletions(-)
create mode 100644 llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-deterministic-nested-type.test
diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
index e2998738c0d7b..d80ab2dbacbb2 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
@@ -122,9 +122,11 @@ bool DependencyTracker::resolveDependenciesAndMarkLiveness(
void DependencyTracker::addActionToRootEntriesWorkList(
LiveRootWorklistActionTy Action, const UnitEntryPairTy &Entry,
- std::optional<UnitEntryPairTy> ReferencedBy) {
+ std::optional<UnitEntryPairTy> ReferencedBy,
+ const DWARFDebugInfoEntry *ReferencedTypeDieEntry) {
if (ReferencedBy) {
- RootEntriesWorkList.emplace_back(Action, Entry, *ReferencedBy);
+ RootEntriesWorkList.emplace_back(Action, Entry, *ReferencedBy,
+ ReferencedTypeDieEntry);
return;
}
@@ -264,8 +266,18 @@ bool DependencyTracker::updateDependenciesCompleteness() {
"Root entry without dependency inside the dependencies list");
UnitEntryPairTy RootEntry = Root.getRootEntry();
+
+ // Completeness must be checked against the actual referenced DIE, not its
+ // enclosing root. A nested type can be demoted to plain DWARF while its
+ // root stays in the type table, and a type-table DIE may only reference
+ // DIEs that are themselves in the type table. Checking the root instead
+ // leaves such a DIE in the type table, later tripping the type-unit
+ // reference assertion in DIEAttributeCloner::cloneDieRefAttr.
+ const DWARFDebugInfoEntry *ReferencedDieEntry =
+ Root.getReferencedTypeDieEntry() ? Root.getReferencedTypeDieEntry()
+ : RootEntry.DieEntry;
CompileUnit::DIEInfo &RootInfo =
- RootEntry.CU->getDIEInfo(RootEntry.DieEntry);
+ RootEntry.CU->getDIEInfo(ReferencedDieEntry);
UnitEntryPairTy ReferencedByEntry = Root.getReferencedByEntry();
CompileUnit::DIEInfo &ReferencedByInfo =
@@ -469,7 +481,7 @@ getFinalPlacementForEntry(const UnitEntryPairTy &Entry,
bool DependencyTracker::markDIEEntryAsKeptRec(
LiveRootWorklistActionTy Action, const UnitEntryPairTy &RootEntry,
const UnitEntryPairTy &Entry, bool InterCUProcessingStarted,
- std::atomic<bool> &HasNewInterconnectedCUs) {
+ std::atomic<bool> &HasNewInterconnectedCUs, bool RecordDepsOnly) {
if (Entry.DieEntry->getAbbreviationDeclarationPtr() == nullptr)
return true;
@@ -483,16 +495,31 @@ bool DependencyTracker::markDIEEntryAsKeptRec(
Placement == CompileUnit::PlainDwarf) &&
"Wrong kind of placement for ODR unavailable entry");
- if (!isChildrenAction(Action))
- if (isAlreadyMarked(Entry, Placement))
- return true;
+ if (!RecordDepsOnly && !isChildrenAction(Action) &&
+ isAlreadyMarked(Entry, Placement)) {
+ // Entry (and its subtree) were already marked, possibly by a racing CU or
+ // another referencing root, and which one wins is non-deterministic. Skip
+ // the redundant marking, but re-walk the subtree in record-deps-only mode
+ // so this referencing root still contributes its outgoing completeness
+ // dependencies. Otherwise the recorded dependency set depends on thread
+ // interleaving, the demotion fixpoint misses demotions, and whole type
+ // subtrees are left in the artificial type unit non-deterministically.
+ // Recording extra dependencies is harmless: a dependency only triggers a
+ // demotion when the referenced type is actually placed in plain DWARF.
+ return markDIEEntryAsKeptRec(Action, RootEntry, Entry,
+ InterCUProcessingStarted,
+ HasNewInterconnectedCUs,
+ /*RecordDepsOnly=*/true);
+ }
- // Mark current DIE as kept.
- Info.setKeep();
- Info.setPlacement(Placement);
+ if (!RecordDepsOnly) {
+ // Mark current DIE as kept.
+ Info.setKeep();
+ Info.setPlacement(Placement);
- // Set keep children property for parents.
- markParentsAsKeepingChildren(Entry);
+ // Set keep children property for parents.
+ markParentsAsKeepingChildren(Entry);
+ }
UnitEntryPairTy FinalRootEntry =
Entry.DieEntry->getTag() == dwarf::DW_TAG_subprogram ? Entry : RootEntry;
@@ -501,7 +528,7 @@ bool DependencyTracker::markDIEEntryAsKeptRec(
bool Res = true;
if (!maybeAddReferencedRoots(Action, FinalRootEntry, Entry,
InterCUProcessingStarted,
- HasNewInterconnectedCUs))
+ HasNewInterconnectedCUs, RecordDepsOnly))
Res = false;
// Return if we do not need to process children.
@@ -566,9 +593,10 @@ bool DependencyTracker::markDIEEntryAsKeptRec(
} break;
}
- if (!markDIEEntryAsKeptRec(
- Action, FinalRootEntry, UnitEntryPairTy{Entry.CU, CurChild},
- InterCUProcessingStarted, HasNewInterconnectedCUs))
+ if (!markDIEEntryAsKeptRec(Action, FinalRootEntry,
+ UnitEntryPairTy{Entry.CU, CurChild},
+ InterCUProcessingStarted,
+ HasNewInterconnectedCUs, RecordDepsOnly))
Res = false;
}
@@ -595,7 +623,7 @@ bool DependencyTracker::markDIEEntryAsKeptRec(
if (!markDIEEntryAsKeptRec(
Action, FinalRootEntry, UnitEntryPairTy{Entry.CU, CurChild},
- InterCUProcessingStarted, HasNewInterconnectedCUs))
+ InterCUProcessingStarted, HasNewInterconnectedCUs, RecordDepsOnly))
Res = false;
}
@@ -653,11 +681,26 @@ bool DependencyTracker::isTypeTableCandidate(
bool DependencyTracker::maybeAddReferencedRoots(
LiveRootWorklistActionTy Action, const UnitEntryPairTy &RootEntry,
const UnitEntryPairTy &Entry, bool InterCUProcessingStarted,
- std::atomic<bool> &HasNewInterconnectedCUs) {
+ std::atomic<bool> &HasNewInterconnectedCUs, bool RecordDepsOnly) {
const auto *Abbrev = Entry.DieEntry->getAbbreviationDeclarationPtr();
if (Abbrev == nullptr)
return true;
+ // In record-deps-only mode the referenced root is not scheduled for marking.
+ // The completeness dependency is appended directly so it participates in the
+ // demotion fixpoint without triggering any reference-following recursion.
+ auto AddRoot = [&](LiveRootWorklistActionTy RootAction,
+ const UnitEntryPairTy &Root,
+ const DWARFDebugInfoEntry *ReferencedTypeDieEntry) {
+ if (RecordDepsOnly) {
+ Dependencies.emplace_back(RootAction, Root, RootEntry,
+ ReferencedTypeDieEntry);
+ return;
+ }
+ addActionToRootEntriesWorkList(RootAction, Root, RootEntry,
+ ReferencedTypeDieEntry);
+ };
+
DWARFUnit &Unit = Entry.CU->getOrigUnit();
DWARFDataExtractor Data = Unit.getDebugInfoExtractor();
uint64_t Offset =
@@ -685,6 +728,12 @@ bool DependencyTracker::maybeAddReferencedRoots(
}
if (!RefDie->DieEntry) {
+ // The reference could not be resolved yet. Recording dependencies
+ // happens only after marking has fully resolved interconnections, so skip
+ // it here. The scheduling path below handles the delayed-resolution case.
+ if (RecordDepsOnly)
+ continue;
+
// Delay resolving reference.
RefDie->CU->setInterconnectedCU();
Entry.CU->setInterconnectedCU();
@@ -712,20 +761,23 @@ bool DependencyTracker::maybeAddReferencedRoots(
if (AttrSpec.Attr == dwarf::DW_AT_import) {
if (isNamespaceLikeEntry(RefDie->DieEntry)) {
- addActionToRootEntriesWorkList(
- isTypeAction(Action)
- ? LiveRootWorklistActionTy::MarkSingleTypeEntry
- : LiveRootWorklistActionTy::MarkSingleLiveEntry,
- *RefDie, RootEntry);
+ AddRoot(isTypeAction(Action)
+ ? LiveRootWorklistActionTy::MarkSingleTypeEntry
+ : LiveRootWorklistActionTy::MarkSingleLiveEntry,
+ *RefDie, nullptr);
continue;
}
- addActionToRootEntriesWorkList(Action, *RefDie, RootEntry);
+ AddRoot(Action, *RefDie, nullptr);
continue;
}
+ // Mark the enclosing root type as kept, but also record the actual
+ // referenced DIE: a nested type can be demoted to plain DWARF independently
+ // of its root, in which case ReferencedBy must be demoted too (see
+ // updateDependenciesCompleteness).
UnitEntryPairTy RootForReferencedDie = getRootForSpecifiedEntry(*RefDie);
- addActionToRootEntriesWorkList(Action, RootForReferencedDie, RootEntry);
+ AddRoot(Action, RootForReferencedDie, RefDie->DieEntry);
}
return true;
diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.h b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.h
index 4a0d985c8aaa6..abf3ada446173 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.h
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.h
@@ -141,15 +141,18 @@ class DependencyTracker {
RootDieEntry = RootEntry.DieEntry;
}
- LiveRootWorklistItemTy(LiveRootWorklistActionTy Action,
- UnitEntryPairTy RootEntry,
- UnitEntryPairTy ReferencedBy) {
+ LiveRootWorklistItemTy(
+ LiveRootWorklistActionTy Action, UnitEntryPairTy RootEntry,
+ UnitEntryPairTy ReferencedBy,
+ const DWARFDebugInfoEntry *ReferencedTypeDieEntry = nullptr) {
RootCU.setPointer(RootEntry.CU);
RootCU.setInt(Action);
RootDieEntry = RootEntry.DieEntry;
ReferencedByCU = ReferencedBy.CU;
ReferencedByDieEntry = ReferencedBy.DieEntry;
+
+ this->ReferencedTypeDieEntry = ReferencedTypeDieEntry;
}
UnitEntryPairTy getRootEntry() const {
@@ -168,6 +171,15 @@ class DependencyTracker {
return UnitEntryPairTy{ReferencedByCU, ReferencedByDieEntry};
}
+ /// \returns the DIE actually referenced by ReferencedByDieEntry, whose
+ /// placement (rather than the enclosing RootDieEntry's) determines whether
+ /// ReferencedByDieEntry may remain in the type table. Null when the
+ /// referenced DIE is RootDieEntry itself, in which case RootDieEntry's
+ /// placement is used instead.
+ const DWARFDebugInfoEntry *getReferencedTypeDieEntry() const {
+ return ReferencedTypeDieEntry;
+ }
+
LiveRootWorklistActionTy getAction() const {
return static_cast<LiveRootWorklistActionTy>(RootCU.getInt());
}
@@ -200,6 +212,14 @@ class DependencyTracker {
/// of ReferencedByDieEntry then it should be updated.
CompileUnit *ReferencedByCU = nullptr;
const DWARFDebugInfoEntry *ReferencedByDieEntry = nullptr;
+
+ /// The DIE actually referenced by ReferencedByDieEntry. It lives in the
+ /// same CU as RootDieEntry, but its placement can differ: RootDieEntry is
+ /// the enclosing root that is marked as kept, whereas this DIE may be a
+ /// nested type demoted independently. That placement, not RootDieEntry's,
+ /// determines whether ReferencedByDieEntry may remain in the type table.
+ /// Null when RootDieEntry is the referenced DIE itself.
+ const DWARFDebugInfoEntry *ReferencedTypeDieEntry = nullptr;
};
using RootEntriesListTy = SmallVector<LiveRootWorklistItemTy>;
@@ -225,12 +245,16 @@ class DependencyTracker {
bool markCollectedLiveRootsAsKept(bool InterCUProcessingStarted,
std::atomic<bool> &HasNewInterconnectedCUs);
- /// Mark whole DIE tree as kept recursively.
+ /// Mark whole DIE tree as kept recursively. When \p RecordDepsOnly is set the
+ /// tree is not marked. Instead its completeness dependencies are recorded
+ /// (see maybeAddReferencedRoots). This is used to re-walk an already-marked
+ /// subtree so a racing referencing root still contributes its dependencies.
bool markDIEEntryAsKeptRec(LiveRootWorklistActionTy Action,
const UnitEntryPairTy &RootEntry,
const UnitEntryPairTy &Entry,
bool InterCUProcessingStarted,
- std::atomic<bool> &HasNewInterconnectedCUs);
+ std::atomic<bool> &HasNewInterconnectedCUs,
+ bool RecordDepsOnly = false);
/// Mark parents as keeping children.
void markParentsAsKeepingChildren(const UnitEntryPairTy &Entry);
@@ -238,12 +262,20 @@ class DependencyTracker {
/// Mark whole DIE tree as placed in "PlainDwarf".
void setPlainDwarfPlacementRec(const UnitEntryPairTy &Entry);
- /// Check referenced DIEs and add them into the worklist.
+ /// Check referenced DIEs and add them into the worklist. When \p
+ /// RecordDepsOnly is set, the referenced roots are not scheduled for marking
+ /// (no new worklist items, hence no reference-following recursion). Instead
+ /// each completeness dependency is appended directly to \c Dependencies. This
+ /// is used when \p Entry was already marked by a racing CU/root: the marking
+ /// and subtree are handled elsewhere, but this referencing root's
+ /// dependencies must still be recorded so the completeness fixpoint sees a
+ /// complete, order-independent dependency set.
bool maybeAddReferencedRoots(LiveRootWorklistActionTy Action,
const UnitEntryPairTy &RootEntry,
const UnitEntryPairTy &Entry,
bool InterCUProcessingStarted,
- std::atomic<bool> &HasNewInterconnectedCUs);
+ std::atomic<bool> &HasNewInterconnectedCUs,
+ bool RecordDepsOnly = false);
/// \returns true if \p DIEEntry can possibly be put into the artificial type
/// unit.
@@ -253,10 +285,10 @@ class DependencyTracker {
UnitEntryPairTy getRootForSpecifiedEntry(UnitEntryPairTy Entry);
/// Add action item to the work list.
- void
- addActionToRootEntriesWorkList(LiveRootWorklistActionTy Action,
- const UnitEntryPairTy &Entry,
- std::optional<UnitEntryPairTy> ReferencedBy);
+ void addActionToRootEntriesWorkList(
+ LiveRootWorklistActionTy Action, const UnitEntryPairTy &Entry,
+ std::optional<UnitEntryPairTy> ReferencedBy,
+ const DWARFDebugInfoEntry *ReferencedTypeDieEntry = nullptr);
CompileUnit &CU;
diff --git a/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-deterministic-nested-type.test b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-deterministic-nested-type.test
new file mode 100644
index 0000000000000..6819f76930b75
--- /dev/null
+++ b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-deterministic-nested-type.test
@@ -0,0 +1,294 @@
+# RUN: yaml2obj %s -o %t.o
+
+## Build a debug map that links the same two-CU object eight times, so the
+## parallel linker processes many compile units that share the same nested
+## types and cross-CU references concurrently.
+# RUN: echo '---' > %t.map
+# RUN: echo "triple: 'x86_64-apple-darwin'" >> %t.map
+# RUN: echo 'objects:' >> %t.map
+# RUN: echo " - filename: '%t.o'" >> %t.map
+# RUN: echo ' symbols:' >> %t.map
+# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10100, size: 0x10 }' >> %t.map
+# RUN: echo " - filename: '%t.o'" >> %t.map
+# RUN: echo ' symbols:' >> %t.map
+# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10200, size: 0x10 }' >> %t.map
+# RUN: echo " - filename: '%t.o'" >> %t.map
+# RUN: echo ' symbols:' >> %t.map
+# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10300, size: 0x10 }' >> %t.map
+# RUN: echo " - filename: '%t.o'" >> %t.map
+# RUN: echo ' symbols:' >> %t.map
+# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10400, size: 0x10 }' >> %t.map
+# RUN: echo " - filename: '%t.o'" >> %t.map
+# RUN: echo ' symbols:' >> %t.map
+# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10500, size: 0x10 }' >> %t.map
+# RUN: echo " - filename: '%t.o'" >> %t.map
+# RUN: echo ' symbols:' >> %t.map
+# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10600, size: 0x10 }' >> %t.map
+# RUN: echo " - filename: '%t.o'" >> %t.map
+# RUN: echo ' symbols:' >> %t.map
+# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10700, size: 0x10 }' >> %t.map
+# RUN: echo " - filename: '%t.o'" >> %t.map
+# RUN: echo ' symbols:' >> %t.map
+# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10800, size: 0x10 }' >> %t.map
+# RUN: echo '...' >> %t.map
+
+## Link the same input several times with multiple threads and require the
+## output to be byte-identical every run.
+# RUN: dsymutil --linker=parallel -y %t.map -f -o %t1.out --num-threads 4
+# RUN: dsymutil --linker=parallel -y %t.map -f -o %t2.out --num-threads 4
+# RUN: dsymutil --linker=parallel -y %t.map -f -o %t3.out --num-threads 4
+# RUN: diff %t1.out %t2.out
+# RUN: diff %t1.out %t3.out
+
+## The parallel linker computes DIE placement (artificial type unit vs plain
+## DWARF) concurrently across compile units. The input (from odr-nested-types1)
+## has a nested type demoted to plain DWARF while its root stays in the type
+## table, with cross-CU references to it. That placement must not depend on
+## thread interleaving, so the output has to be byte-identical every run.
+
+--- !mach-o
+FileHeader:
+ magic: 0xFEEDFACF
+ cputype: 0x01000007
+ cpusubtype: 0x00000003
+ filetype: 0x00000001
+ ncmds: 2
+ sizeofcmds: 376
+ flags: 0x00002000
+ reserved: 0x00000000
+LoadCommands:
+ - cmd: LC_SEGMENT_64
+ cmdsize: 232
+ segname: ''
+ vmaddr: 0x00
+ vmsize: 0x300
+ fileoff: 0x300
+ filesize: 0x300
+ maxprot: 7
+ initprot: 7
+ nsects: 2
+ flags: 0
+ Sections:
+ - sectname: __debug_abbrev
+ segname: __DWARF
+ addr: 0x000000000000000F
+ size: 0x76
+ offset: 0x00000380
+ align: 0
+ reloff: 0x00000000
+ nreloc: 0
+ flags: 0x02000000
+ reserved1: 0x00000000
+ reserved2: 0x00000000
+ reserved3: 0x00000000
+ - sectname: __debug_info
+ segname: __DWARF
+ addr: 0x000000000000100
+ size: 0xa7
+ offset: 0x000003f6
+ align: 0
+ reloff: 0x00000600
+ nreloc: 1
+ flags: 0x02000000
+ reserved1: 0x00000000
+ reserved2: 0x00000000
+ reserved3: 0x00000000
+ relocations:
+ - address: 0x0
+ symbolnum: 1
+ pcrel: true
+ length: 3
+ extern: true
+ type: 0
+ scattered: false
+ value: 0
+ - cmd: LC_SYMTAB
+ cmdsize: 24
+ symoff: 0x700
+ nsyms: 2
+ stroff: 0x720
+ strsize: 10
+LinkEditData:
+ NameList:
+ - n_strx: 1
+ n_type: 0x0F
+ n_sect: 1
+ n_desc: 0
+ n_value: 0
+ - n_strx: 1
+ n_type: 0x0F
+ n_sect: 1
+ n_desc: 0
+ n_value: 0
+ StringTable:
+ - ''
+ - '__Z3foov'
+ - ''
+DWARF:
+ debug_abbrev:
+ - Table:
+ - Tag: DW_TAG_compile_unit
+ Children: DW_CHILDREN_yes
+ Attributes:
+ - Attribute: DW_AT_producer
+ Form: DW_FORM_string
+ - Attribute: DW_AT_language
+ Form: DW_FORM_data2
+ - 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
+ - Tag: DW_TAG_structure_type
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Tag: DW_TAG_member
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Attribute: DW_AT_type
+ Form: DW_FORM_ref_addr
+ - Tag: DW_TAG_base_type
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Tag: DW_TAG_pointer_type
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_type
+ Form: DW_FORM_ref_addr
+ - Tag: DW_TAG_namespace
+ Children: DW_CHILDREN_yes
+ - Tag: DW_TAG_variable
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Attribute: DW_AT_const_value
+ Form: DW_FORM_data4
+ - Attribute: DW_AT_type
+ Form: DW_FORM_ref_addr
+ - Table:
+ - Tag: DW_TAG_compile_unit
+ Children: DW_CHILDREN_yes
+ Attributes:
+ - Attribute: DW_AT_producer
+ Form: DW_FORM_string
+ - Attribute: DW_AT_language
+ Form: DW_FORM_data2
+ - 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
+ - Tag: DW_TAG_structure_type
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Attribute: DW_AT_declaration
+ Form: DW_FORM_flag_present
+ - Tag: DW_TAG_base_type
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Tag: DW_TAG_pointer_type
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_type
+ Form: DW_FORM_ref_addr
+ - Tag: DW_TAG_variable
+ Children: DW_CHILDREN_no
+ Attributes:
+ - Attribute: DW_AT_name
+ Form: DW_FORM_string
+ - Attribute: DW_AT_const_value
+ Form: DW_FORM_data4
+ - Attribute: DW_AT_type
+ Form: DW_FORM_ref_addr
+ debug_info:
+ - Version: 4
+ Entries:
+ - AbbrCode: 1
+ Values:
+ - CStr: by_hand
+ - Value: 0x04
+ - CStr: CU1
+ - AbbrCode: 2
+ Values:
+ - CStr: s1
+ - AbbrCode: 2
+ Values:
+ - CStr: s2
+ - AbbrCode: 4
+ Values:
+ - CStr: m
+ - Value: 0x0000002c
+ - AbbrCode: 0
+ - AbbrCode: 0
+ - AbbrCode: 7
+ - AbbrCode: 3
+ Values:
+ - CStr: s3
+ - AbbrCode: 0
+ - AbbrCode: 5
+ Values:
+ - CStr: int
+ - AbbrCode: 6
+ Values:
+ - Value: 0x0000001e
+ - AbbrCode: 6
+ Values:
+ - Value: 0x00000022
+ - AbbrCode: 8
+ Values:
+ - CStr: var1
+ - Value: 0x00000000
+ - Value: 0x00000036
+ - AbbrCode: 8
+ Values:
+ - CStr: var2
+ - Value: 0x00000000
+ - Value: 0x0000003b
+ - AbbrCode: 0
+ - Version: 4
+ Entries:
+ - AbbrCode: 1
+ Values:
+ - CStr: by_hand
+ - Value: 0x04
+ - CStr: CU2
+ - AbbrCode: 2
+ Values:
+ - CStr: s1
+ - AbbrCode: 3
+ Values:
+ - CStr: s2
+ - AbbrCode: 0
+ - AbbrCode: 4
+ Values:
+ - CStr: int
+ - AbbrCode: 5
+ Values:
+ - Value: 0x0000007b
+ - AbbrCode: 6
+ Values:
+ - CStr: var3
+ - Value: 0x00000000
+ - Value: 0x00000085
+ - AbbrCode: 6
+ Values:
+ - CStr: var4
+ - Value: 0x00000000
+ - Value: 0x00000085
+ - AbbrCode: 0
+...
>From f47b5a79a698c39d58698741795af64b0040ff06 Mon Sep 17 00:00:00 2001
From: Jonas Devlieghere <jonas at devlieghere.com>
Date: Thu, 16 Jul 2026 16:19:42 -0700
Subject: [PATCH 2/2] Use split file
---
.../odr-deterministic-nested-type.test | 90 ++++++++++---------
1 file changed, 47 insertions(+), 43 deletions(-)
diff --git a/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-deterministic-nested-type.test b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-deterministic-nested-type.test
index 6819f76930b75..eec48b5759125 100644
--- a/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-deterministic-nested-type.test
+++ b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-deterministic-nested-type.test
@@ -1,51 +1,55 @@
-# RUN: yaml2obj %s -o %t.o
+# RUN: rm -rf %t && mkdir -p %t
+# RUN: split-file %s %t
+# RUN: yaml2obj %t/foo.o.yaml -o %t/foo.o
-## Build a debug map that links the same two-CU object eight times, so the
-## parallel linker processes many compile units that share the same nested
-## types and cross-CU references concurrently.
-# RUN: echo '---' > %t.map
-# RUN: echo "triple: 'x86_64-apple-darwin'" >> %t.map
-# RUN: echo 'objects:' >> %t.map
-# RUN: echo " - filename: '%t.o'" >> %t.map
-# RUN: echo ' symbols:' >> %t.map
-# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10100, size: 0x10 }' >> %t.map
-# RUN: echo " - filename: '%t.o'" >> %t.map
-# RUN: echo ' symbols:' >> %t.map
-# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10200, size: 0x10 }' >> %t.map
-# RUN: echo " - filename: '%t.o'" >> %t.map
-# RUN: echo ' symbols:' >> %t.map
-# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10300, size: 0x10 }' >> %t.map
-# RUN: echo " - filename: '%t.o'" >> %t.map
-# RUN: echo ' symbols:' >> %t.map
-# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10400, size: 0x10 }' >> %t.map
-# RUN: echo " - filename: '%t.o'" >> %t.map
-# RUN: echo ' symbols:' >> %t.map
-# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10500, size: 0x10 }' >> %t.map
-# RUN: echo " - filename: '%t.o'" >> %t.map
-# RUN: echo ' symbols:' >> %t.map
-# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10600, size: 0x10 }' >> %t.map
-# RUN: echo " - filename: '%t.o'" >> %t.map
-# RUN: echo ' symbols:' >> %t.map
-# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10700, size: 0x10 }' >> %t.map
-# RUN: echo " - filename: '%t.o'" >> %t.map
-# RUN: echo ' symbols:' >> %t.map
-# RUN: echo ' - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10800, size: 0x10 }' >> %t.map
-# RUN: echo '...' >> %t.map
+## The debug map links the same two-CU object eight times, so the parallel
+## linker processes many compile units that share the same nested types and
+## cross-CU references concurrently. -oso-prepend-path resolves the relative
+## object filename against the temporary directory.
## Link the same input several times with multiple threads and require the
-## output to be byte-identical every run.
-# RUN: dsymutil --linker=parallel -y %t.map -f -o %t1.out --num-threads 4
-# RUN: dsymutil --linker=parallel -y %t.map -f -o %t2.out --num-threads 4
-# RUN: dsymutil --linker=parallel -y %t.map -f -o %t3.out --num-threads 4
-# RUN: diff %t1.out %t2.out
-# RUN: diff %t1.out %t3.out
+## output to be byte-identical every run. The parallel linker computes DIE
+## placement (artificial type unit vs plain DWARF) concurrently across compile
+## units. The input (from odr-nested-types1) has a nested type demoted to plain
+## DWARF while its root stays in the type table, with cross-CU references to it.
+## That placement must not depend on thread interleaving.
+# RUN: dsymutil --linker=parallel -oso-prepend-path=%t -y %t/link.map -f -o %t/1.out --num-threads 4
+# RUN: dsymutil --linker=parallel -oso-prepend-path=%t -y %t/link.map -f -o %t/2.out --num-threads 4
+# RUN: dsymutil --linker=parallel -oso-prepend-path=%t -y %t/link.map -f -o %t/3.out --num-threads 4
+# RUN: diff %t/1.out %t/2.out
+# RUN: diff %t/1.out %t/3.out
-## The parallel linker computes DIE placement (artificial type unit vs plain
-## DWARF) concurrently across compile units. The input (from odr-nested-types1)
-## has a nested type demoted to plain DWARF while its root stays in the type
-## table, with cross-CU references to it. That placement must not depend on
-## thread interleaving, so the output has to be byte-identical every run.
+#--- link.map
+---
+triple: 'x86_64-apple-darwin'
+objects:
+ - filename: 'foo.o'
+ symbols:
+ - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10100, size: 0x10 }
+ - filename: 'foo.o'
+ symbols:
+ - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10200, size: 0x10 }
+ - filename: 'foo.o'
+ symbols:
+ - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10300, size: 0x10 }
+ - filename: 'foo.o'
+ symbols:
+ - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10400, size: 0x10 }
+ - filename: 'foo.o'
+ symbols:
+ - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10500, size: 0x10 }
+ - filename: 'foo.o'
+ symbols:
+ - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10600, size: 0x10 }
+ - filename: 'foo.o'
+ symbols:
+ - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10700, size: 0x10 }
+ - filename: 'foo.o'
+ symbols:
+ - { sym: __Z3foov, objAddr: 0x0, binAddr: 0x10800, size: 0x10 }
+...
+#--- foo.o.yaml
--- !mach-o
FileHeader:
magic: 0xFEEDFACF
More information about the llvm-commits
mailing list