[llvm-branch-commits] [llvm] cefac32 - [DWARFLinker] Walk each shared subtree's dependencies once (#218072)
Tobias Hieta via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Sat Aug 29 02:08:46 PDT 2026
Author: Jonas Devlieghere
Date: 2026-08-29T11:08:33+02:00
New Revision: cefac32eb6ab00f02b7dd38f62b0f92c2a05a21b
URL: https://github.com/llvm/llvm-project/commit/cefac32eb6ab00f02b7dd38f62b0f92c2a05a21b
DIFF: https://github.com/llvm/llvm-project/commit/cefac32eb6ab00f02b7dd38f62b0f92c2a05a21b.diff
LOG: [DWARFLinker] Walk each shared subtree's dependencies once (#218072)
cdc31cfa66f0 made every root that references an already-marked subtree
re-walk that subtree to record the completeness dependencies it
contributes, which is what makes the recorded dependency set complete
and independent of thread interleaving. That walk replaced the
isAlreadyMarked short-circuit which had kept marking linear, so a widely
shared subtree is re-parsed and re-resolved once per referencing root.
Linking a RelWithDebInfo clang went from 27s to 67s of wall time and
from 242s to 1447s of CPU, peak memory grew from 35GB to 63GB, and 7.0
billion dependencies were recorded for an unchanged dSYM.
Record only that a root carries the subtree's dependencies and walk each
distinct subtree once. All of a subtree's dependencies demote the same
root, so the expansion stops at the first one that does.
A subtree contributes two kinds of dependency. One kind is recorded
under the root referencing the subtree, varies with that root, and is
summarized so it can be applied to each referencing root in turn. The
other is recorded under a subprogram nested inside the subtree, names
that subprogram, and is therefore the same for every referencing root,
so recording it once is enough. Which kind applies is carried explicitly
through the walk. Only a subprogram re-anchors the root, so a subtree
that is itself a subprogram is already anchored to itself, and
recovering the split by comparing root entries would make it depend on
which reference of the subtree happens to be walked first.
The walk has to happen when completeness is checked rather than during
marking. Marking consults the keep and placement bits through
isAlreadyMarked while sibling units are still raising them, so a subtree
walked then yields a result that depends on how the units interleave and
cannot stand in for the rest. A walk that only records dependencies
never consults those bits. It reads a DIE's ODR availability and whether
it has an address, both of which settle before marking begins.
Every DWARF section stays byte identical to cdc31cfa66f0 for dsymutil
runs over llvm-cxxfilt, llvm-dwarfdump and llvm-mc, single threaded and
across repeated runs at eight threads. Ordering inside __debug_names
still varies between runs, as it does before this change.
Assisted-by: Claude
(cherry picked from commit b704e514dca4860cb436b0c47f1748255a0f82af)
Added:
llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-shared-subtree-demotion.s
Modified:
llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
llvm/lib/DWARFLinker/Parallel/DependencyTracker.h
Removed:
################################################################################
diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
index 124e73b1a15bd..e2b63f2b29c01 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.cpp
@@ -8,6 +8,7 @@
#include "DependencyTracker.h"
#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/SaveAndRestore.h"
using namespace llvm;
using namespace dwarf_linker;
@@ -121,6 +122,15 @@ bool DependencyTracker::resolveDependenciesAndMarkLiveness(
bool InterCUProcessingStarted, std::atomic<bool> &HasNewInterconnectedCUs) {
RootEntriesWorkList.clear();
+ // The recorded subtrees are walked after marking, and need to resolve
+ // references the same way marking did. A unit whose references could not all
+ // be resolved is reset to its loaded stage and marked again from scratch, so
+ // no reference recorded under one resolution mode survives into another.
+ assert((SubtreeDependencyRefs.empty() ||
+ InterCUProcessingWasStarted == InterCUProcessingStarted) &&
+ "recorded subtrees would be walked in a
diff erent resolution mode");
+ InterCUProcessingWasStarted = InterCUProcessingStarted;
+
// Search for live root DIEs.
CompileUnit::DIEInfo &CUInfo = CU.getDIEInfo(CU.getDebugInfoEntry(0));
CUInfo.setPlacement(CompileUnit::PlainDwarf);
@@ -284,40 +294,120 @@ bool DependencyTracker::markCollectedLiveRootsAsKept(
return Res;
}
+void DependencyTracker::recordSubtreeDependencies(
+ LiveRootWorklistActionTy Action, const UnitEntryPairTy &RootEntry,
+ const UnitEntryPairTy &Entry) {
+ SubtreeDependencyRefs.push_back({Entry, Action, RootEntry});
+}
+
+void DependencyTracker::materializeSubtreeSummaries() {
+ // Walking a subtree appends the dependencies that belong to a subprogram
+ // nested inside it, so all walking has to finish before the dependency list
+ // is traversed.
+ for (size_t Idx = MaterializedRefs; Idx != SubtreeDependencyRefs.size();
+ ++Idx) {
+ // Copied rather than referenced so that the loop does not depend on the
+ // walk below leaving the vector alone.
+ const SubtreeDependencyRefTy Ref = SubtreeDependencyRefs[Idx];
+ SubtreeDependenciesKeyTy Key{Ref.Subtree.CU, Ref.Subtree.DieEntry,
+ Ref.Action};
+ if (SubtreeSummaries.contains(Key))
+ continue;
+
+ // Collected separately so that growing the map cannot invalidate the sink.
+ SubtreeDependenciesTy SubtreeDeps;
+ {
+ SaveAndRestore<SubtreeDependenciesTy *> CollectInto(CollectedSubtreeDeps,
+ &SubtreeDeps);
+
+ // A walk that only records dependencies neither marks nor follows
+ // references, so it cannot discover a new interconnection and cannot
+ // fail.
+ std::atomic<bool> HasNewInterconnectedCUs = false;
+ [[maybe_unused]] bool Res = markDIEEntryAsKeptRec(
+ Ref.Action, Ref.ReferencedBy, Ref.Subtree,
+ InterCUProcessingWasStarted, HasNewInterconnectedCUs,
+ TreeWalkKindTy::RecordSubtreeDeps);
+ assert(Res && !HasNewInterconnectedCUs && "record-deps-only walk failed");
+ }
+
+ SubtreeSummaries[Key] = std::move(SubtreeDeps);
+ }
+
+ MaterializedRefs = SubtreeDependencyRefs.size();
+}
+
+bool DependencyTracker::demoteIfIncomplete(
+ const UnitEntryPairTy &Root,
+ const DWARFDebugInfoEntry *ReferencedTypeDieEntry,
+ const UnitEntryPairTy &ReferencedBy) {
+ // 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 =
+ ReferencedTypeDieEntry ? ReferencedTypeDieEntry : Root.DieEntry;
+ CompileUnit::DIEInfo &RootInfo = Root.CU->getDIEInfo(ReferencedDieEntry);
+ CompileUnit::DIEInfo &ReferencedByInfo =
+ ReferencedBy.CU->getDIEInfo(ReferencedBy.DieEntry);
+
+ if (RootInfo.needToPlaceInTypeTable() ||
+ !ReferencedByInfo.needToPlaceInTypeTable())
+ return false;
+
+ setPlainDwarfPlacementRec(ReferencedBy);
+
+ // FIXME: we probably need to update getKeepTypeChildren status for
+ // parents of ReferencedBy.
+ return true;
+}
+
+bool DependencyTracker::applySubtreeSummaries() {
+ bool HasNewDependency = false;
+ for (const SubtreeDependencyRefTy &Ref : SubtreeDependencyRefs) {
+ CompileUnit::DIEInfo &ReferencedByInfo =
+ Ref.ReferencedBy.CU->getDIEInfo(Ref.ReferencedBy.DieEntry);
+ if (!ReferencedByInfo.needToPlaceInTypeTable())
+ continue;
+
+ SubtreeDependenciesKeyTy Key{Ref.Subtree.CU, Ref.Subtree.DieEntry,
+ Ref.Action};
+ auto Summary = SubtreeSummaries.find(Key);
+ assert(Summary != SubtreeSummaries.end() && "subtree was not summarized");
+
+ // Demotion takes the root out of the type table, so no further dependency
+ // of the same subtree can demote it again.
+ for (const SubtreeDependencyTy &Dep : Summary->second) {
+ if (demoteIfIncomplete(Dep.Root, Dep.ReferencedTypeDieEntry,
+ Ref.ReferencedBy)) {
+ HasNewDependency = true;
+ break;
+ }
+ }
+ }
+
+ return HasNewDependency;
+}
+
bool DependencyTracker::updateDependenciesCompleteness() {
+ materializeSubtreeSummaries();
+
bool HasNewDependency = false;
for (LiveRootWorklistItemTy &Root : Dependencies) {
assert(Root.hasReferencedByOtherEntry() &&
"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(ReferencedDieEntry);
-
- UnitEntryPairTy ReferencedByEntry = Root.getReferencedByEntry();
- CompileUnit::DIEInfo &ReferencedByInfo =
- ReferencedByEntry.CU->getDIEInfo(ReferencedByEntry.DieEntry);
-
- if (!RootInfo.needToPlaceInTypeTable() &&
- ReferencedByInfo.needToPlaceInTypeTable()) {
+ if (demoteIfIncomplete(Root.getRootEntry(),
+ Root.getReferencedTypeDieEntry(),
+ Root.getReferencedByEntry()))
HasNewDependency = true;
- setPlainDwarfPlacementRec(ReferencedByEntry);
-
- // FIXME: we probably need to update getKeepTypeChildren status for
- // parents of *Root.ReferencedBy.
- }
}
+ if (applySubtreeSummaries())
+ HasNewDependency = true;
+
return HasNewDependency;
}
@@ -503,7 +593,7 @@ getFinalPlacementForEntry(const UnitEntryPairTy &Entry,
bool DependencyTracker::markDIEEntryAsKeptRec(
LiveRootWorklistActionTy Action, const UnitEntryPairTy &RootEntry,
const UnitEntryPairTy &Entry, bool InterCUProcessingStarted,
- std::atomic<bool> &HasNewInterconnectedCUs, bool RecordDepsOnly) {
+ std::atomic<bool> &HasNewInterconnectedCUs, TreeWalkKindTy Kind) {
if (Entry.DieEntry->getAbbreviationDeclarationPtr() == nullptr)
return true;
@@ -518,24 +608,20 @@ bool DependencyTracker::markDIEEntryAsKeptRec(
Placement == CompileUnit::PlainDwarf) &&
"Wrong kind of placement for ODR unavailable entry");
- if (!RecordDepsOnly && !isChildrenAction(Action) &&
+ if (!recordsDepsOnly(Kind) && !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);
+ // the redundant marking, but still record that this root carries the
+ // dependencies the subtree contributes. 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.
+ recordSubtreeDependencies(Action, RootEntry, Entry);
+ return true;
}
- if (!RecordDepsOnly) {
+ if (!recordsDepsOnly(Kind)) {
// Mark current DIE as kept.
Info.setKeep();
// Marks compose monotonically so no interleaving loses an update: a general
@@ -557,14 +643,22 @@ bool DependencyTracker::markDIEEntryAsKeptRec(
markParentsAsKeepingChildren(Entry);
}
- UnitEntryPairTy FinalRootEntry =
- Entry.DieEntry->getTag() == dwarf::DW_TAG_subprogram ? Entry : RootEntry;
+ bool IsSubprogram = Entry.DieEntry->getTag() == dwarf::DW_TAG_subprogram;
+ UnitEntryPairTy FinalRootEntry = IsSubprogram ? Entry : RootEntry;
+
+ // A subprogram becomes the root of everything found below it, so from here on
+ // the dependencies name the subprogram instead of the root referencing the
+ // walked subtree, and are the same for every such root.
+ TreeWalkKindTy FinalKind =
+ IsSubprogram && Kind == TreeWalkKindTy::RecordSubtreeDeps
+ ? TreeWalkKindTy::RecordNestedSubprogramDeps
+ : Kind;
// Analyse referenced DIEs.
bool Res = true;
if (!maybeAddReferencedRoots(Action, FinalRootEntry, Entry,
InterCUProcessingStarted,
- HasNewInterconnectedCUs, RecordDepsOnly))
+ HasNewInterconnectedCUs, FinalKind))
Res = false;
// Return if we do not need to process children.
@@ -629,10 +723,9 @@ bool DependencyTracker::markDIEEntryAsKeptRec(
} break;
}
- if (!markDIEEntryAsKeptRec(Action, FinalRootEntry,
- UnitEntryPairTy{Entry.CU, CurChild},
- InterCUProcessingStarted,
- HasNewInterconnectedCUs, RecordDepsOnly))
+ if (!markDIEEntryAsKeptRec(
+ Action, FinalRootEntry, UnitEntryPairTy{Entry.CU, CurChild},
+ InterCUProcessingStarted, HasNewInterconnectedCUs, FinalKind))
Res = false;
}
@@ -659,7 +752,7 @@ bool DependencyTracker::markDIEEntryAsKeptRec(
if (!markDIEEntryAsKeptRec(
Action, FinalRootEntry, UnitEntryPairTy{Entry.CU, CurChild},
- InterCUProcessingStarted, HasNewInterconnectedCUs, RecordDepsOnly))
+ InterCUProcessingStarted, HasNewInterconnectedCUs, FinalKind))
Res = false;
}
@@ -717,24 +810,40 @@ bool DependencyTracker::isTypeTableCandidate(
bool DependencyTracker::maybeAddReferencedRoots(
LiveRootWorklistActionTy Action, const UnitEntryPairTy &RootEntry,
const UnitEntryPairTy &Entry, bool InterCUProcessingStarted,
- std::atomic<bool> &HasNewInterconnectedCUs, bool RecordDepsOnly) {
+ std::atomic<bool> &HasNewInterconnectedCUs, TreeWalkKindTy Kind) {
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.
+ // A walk that only records dependencies does not schedule the referenced root
+ // for marking. The completeness dependency is collected instead, 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) {
+ switch (Kind) {
+ case TreeWalkKindTy::MarkTree:
+ addActionToRootEntriesWorkList(RootAction, Root, RootEntry,
+ ReferencedTypeDieEntry);
+ return;
+
+ case TreeWalkKindTy::RecordSubtreeDeps:
+ // The dependency belongs to whichever root references this subtree, so it
+ // is summarized and applied to each of them in turn.
+ assert(CollectedSubtreeDeps && "record-deps-only walk without a sink");
+ CollectedSubtreeDeps->push_back(
+ {RootAction, Root, ReferencedTypeDieEntry});
+ return;
+
+ case TreeWalkKindTy::RecordNestedSubprogramDeps:
+ // The dependency belongs to a subprogram nested inside the subtree, so it
+ // is the same for every referencing root and recording it once is enough.
Dependencies.emplace_back(RootAction, Root, RootEntry,
ReferencedTypeDieEntry);
return;
}
- addActionToRootEntriesWorkList(RootAction, Root, RootEntry,
- ReferencedTypeDieEntry);
+ llvm_unreachable("Unknown TreeWalkKindTy enum");
};
DWARFUnit &Unit = Entry.CU->getOrigUnit();
@@ -767,7 +876,7 @@ bool DependencyTracker::maybeAddReferencedRoots(
// 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)
+ if (recordsDepsOnly(Kind))
continue;
// Delay resolving reference.
diff --git a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.h b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.h
index abf3ada446173..dbf67cce3365c 100644
--- a/llvm/lib/DWARFLinker/Parallel/DependencyTracker.h
+++ b/llvm/lib/DWARFLinker/Parallel/DependencyTracker.h
@@ -10,6 +10,7 @@
#define LLVM_LIB_DWARFLINKER_PARALLEL_DEPENDENCYTRACKER_H
#include "DWARFLinkerCompileUnit.h"
+#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/PointerIntPair.h"
#include "llvm/ADT/SmallVector.h"
@@ -129,6 +130,31 @@ class DependencyTracker {
}
}
+ /// What a tree walk does, and for a walk that only records dependencies,
+ /// which root the dependencies it finds are recorded under. Only a
+ /// DW_TAG_subprogram re-anchors that root, so the distinction cannot be
+ /// recovered by comparing root entries: a walk of a subprogram subtree starts
+ /// out anchored to the subprogram itself.
+ enum class TreeWalkKindTy : uint8_t {
+ /// Mark the tree as kept and schedule the roots it references.
+ MarkTree,
+
+ /// Do not mark. Record the dependencies as belonging to whichever root
+ /// references the walked subtree.
+ RecordSubtreeDeps,
+
+ /// Do not mark. Record the dependencies as belonging to a subprogram inside
+ /// the walked subtree, which makes them the same for every referencing
+ /// root.
+ RecordNestedSubprogramDeps,
+ };
+
+ /// \returns true if the specified walk records dependencies instead of
+ /// marking the tree.
+ static bool recordsDepsOnly(TreeWalkKindTy Kind) {
+ return Kind != TreeWalkKindTy::MarkTree;
+ }
+
/// Class keeping live worklist item data.
class LiveRootWorklistItemTy {
public:
@@ -224,6 +250,40 @@ class DependencyTracker {
using RootEntriesListTy = SmallVector<LiveRootWorklistItemTy>;
+ /// A completeness dependency of a subtree that belongs to whichever root
+ /// references the subtree, which is not known while the subtree is walked.
+ struct SubtreeDependencyTy {
+ LiveRootWorklistActionTy Action;
+ UnitEntryPairTy Root;
+ const DWARFDebugInfoEntry *ReferencedTypeDieEntry;
+ };
+
+ using SubtreeDependenciesTy = SmallVector<SubtreeDependencyTy>;
+
+ /// A subtree paired with the action it is walked with, which selects both the
+ /// visited children and the action recorded for a reference.
+ using SubtreeDependenciesKeyTy =
+ std::tuple<CompileUnit *, const DWARFDebugInfoEntry *,
+ LiveRootWorklistActionTy>;
+
+ /// A root referencing an already-marked subtree, standing in for all of that
+ /// subtree's dependencies. The subtree is walked when completeness is
+ /// checked, once per subtree rather than once per referencing root, which is
+ /// what keeps recording linear in the number of shared subtrees.
+ ///
+ /// Deferring the walk out of marking also keeps it off the state marking is
+ /// still mutating. A walk that only records dependencies reads a DIE's ODR
+ /// availability and whether it has an address, both settled before marking
+ /// begins, and never the keep and placement bits that sibling units raise as
+ /// they mark. Walking during marking would consult those bits through
+ /// isAlreadyMarked and yield a result that depends on how the units
+ /// interleave.
+ struct SubtreeDependencyRefTy {
+ UnitEntryPairTy Subtree;
+ LiveRootWorklistActionTy Action;
+ UnitEntryPairTy ReferencedBy;
+ };
+
/// This function navigates DIEs tree starting from specified \p Entry.
/// It puts found 'root DIE' into the worklist. The \p CollectLiveEntries
/// instructs to collect either live roots(like subprograms having live
@@ -245,16 +305,43 @@ class DependencyTracker {
bool markCollectedLiveRootsAsKept(bool InterCUProcessingStarted,
std::atomic<bool> &HasNewInterconnectedCUs);
- /// 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.
+ /// Mark whole DIE tree as kept recursively. A walk that only records
+ /// dependencies (see \p Kind) does not mark the tree. Instead its
+ /// completeness dependencies are collected (see maybeAddReferencedRoots) so
+ /// they can be applied to every root referencing the tree.
+ /// \see materializeSubtreeSummaries.
bool markDIEEntryAsKeptRec(LiveRootWorklistActionTy Action,
const UnitEntryPairTy &RootEntry,
const UnitEntryPairTy &Entry,
bool InterCUProcessingStarted,
std::atomic<bool> &HasNewInterconnectedCUs,
- bool RecordDepsOnly = false);
+ TreeWalkKindTy Kind = TreeWalkKindTy::MarkTree);
+
+ /// Record that \p RootEntry references the already-marked subtree \p Entry,
+ /// and therefore carries the completeness dependencies of that subtree. The
+ /// subtree itself is walked later, by materializeSubtreeSummaries().
+ void recordSubtreeDependencies(LiveRootWorklistActionTy Action,
+ const UnitEntryPairTy &RootEntry,
+ const UnitEntryPairTy &Entry);
+
+ /// Walk every subtree that a recorded reference stands for, once per subtree
+ /// and action, and summarize the dependencies it contributes. Called when
+ /// completeness is checked, so that liveness marking and inter-unit reference
+ /// resolution have settled and the summary no longer depends on the order the
+ /// units were processed in.
+ void materializeSubtreeSummaries();
+
+ /// Apply each summarized subtree's dependencies to every root recorded as
+ /// referencing it.
+ /// \returns true if any placement was updated.
+ bool applySubtreeSummaries();
+
+ /// Demote \p ReferencedBy to plain DWARF if it may not stay in the type table
+ /// while the DIE it references through \p Root is not placed there.
+ /// \returns true if the placement was updated.
+ bool demoteIfIncomplete(const UnitEntryPairTy &Root,
+ const DWARFDebugInfoEntry *ReferencedTypeDieEntry,
+ const UnitEntryPairTy &ReferencedBy);
/// Mark parents as keeping children.
void markParentsAsKeepingChildren(const UnitEntryPairTy &Entry);
@@ -262,20 +349,21 @@ class DependencyTracker {
/// Mark whole DIE tree as placed in "PlainDwarf".
void setPlainDwarfPlacementRec(const UnitEntryPairTy &Entry);
- /// 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.
+ /// Check referenced DIEs and add them into the worklist. A walk that only
+ /// records dependencies (see \p Kind) schedules nothing, so it triggers no
+ /// reference-following recursion. Each dependency it finds is instead
+ /// collected for the root that carries it, which is either whichever root
+ /// references the walked subtree or a subprogram nested inside it. This is
+ /// used when \p Entry was already marked by a racing CU/root: the marking and
+ /// subtree are handled elsewhere, but the 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,
- bool RecordDepsOnly = false);
+ TreeWalkKindTy Kind = TreeWalkKindTy::MarkTree);
/// \returns true if \p DIEEntry can possibly be put into the artificial type
/// unit.
@@ -297,6 +385,25 @@ class DependencyTracker {
/// List of entries dependencies.
RootEntriesListTy Dependencies;
+
+ /// Dependency summaries of already-marked subtrees, keyed by subtree and
+ /// action. Filled once, when completeness is first checked.
+ DenseMap<SubtreeDependenciesKeyTy, SubtreeDependenciesTy> SubtreeSummaries;
+
+ /// Roots referencing an already-marked subtree.
+ SmallVector<SubtreeDependencyRefTy> SubtreeDependencyRefs;
+
+ /// Number of leading SubtreeDependencyRefs whose subtree is summarized.
+ size_t MaterializedRefs = 0;
+
+ /// Where the walk in progress collects the dependencies that belong to the
+ /// root referencing the walked subtree, or null when no such walk is in
+ /// progress. Scoped by materializeSubtreeSummaries().
+ SubtreeDependenciesTy *CollectedSubtreeDeps = nullptr;
+
+ /// Whether inter-unit references could be resolved during marking. Reused
+ /// when the recorded subtrees are walked, which happens outside of marking.
+ bool InterCUProcessingWasStarted = false;
};
} // end of namespace parallel
diff --git a/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-shared-subtree-demotion.s b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-shared-subtree-demotion.s
new file mode 100644
index 0000000000000..e3773da0b28f3
--- /dev/null
+++ b/llvm/test/tools/dsymutil/X86/DWARFLinkerParallel/odr-shared-subtree-demotion.s
@@ -0,0 +1,298 @@
+# Two compile units reference the same pointer type, so whichever unit marks it
+# first leaves the other holding a reference to an already-marked subtree. The
+# dependencies of such a subtree are summarized once and applied to every unit
+# that references it. This exercises that path: the pointer names a nested type
+# that has to leave the artificial type unit, because its member has a type from
+# an anonymous namespace and so cannot be ODR deduplicated, while the type
+# enclosing it stays in the type unit.
+#
+# A type-unit DIE may only reference DIEs that are themselves in the type unit,
+# so leaving the pointer behind in the type unit produces a dangling type-unit
+# reference.
+
+# RUN: llvm-mc -triple x86_64-apple-darwin -filetype=obj %s -o %t.o
+# RUN: llvm-dwarfdump --verify %t.o
+
+# 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: __Z2f1v, objAddr: 0x0, binAddr: 0x10000, size: 0x1 }' >> %t.map
+# RUN: echo ' - { sym: __Z2f2v, objAddr: 0x1, binAddr: 0x10010, size: 0x1 }' >> %t.map
+# RUN: echo '...' >> %t.map
+
+# RUN: dsymutil --linker=parallel -y %t.map -f -o %t.dSYM
+# RUN: llvm-dwarfdump --verify %t.dSYM
+# RUN: llvm-dwarfdump -debug-info %t.dSYM | FileCheck %s
+
+## Only the deduplicated base type reaches the type unit. Neither the nested
+## type, nor the type enclosing it, nor the pointer to it may be placed there.
+# CHECK: DW_AT_name{{.*}}"__artificial_type_unit"
+# CHECK-NOT: DW_TAG_structure_type
+# CHECK-NOT: DW_TAG_pointer_type
+
+# CHECK: DW_AT_name{{.*}}"CU1"
+# CHECK: DW_TAG_namespace
+# CHECK: DW_AT_name{{.*}}"Hidden"
+# CHECK: DW_TAG_structure_type
+# CHECK: DW_AT_name{{.*}}"Outer"
+# CHECK: DW_TAG_structure_type
+# CHECK: DW_AT_name{{.*}}"Inner"
+# CHECK: DW_TAG_pointer_type
+# CHECK-NEXT: DW_AT_type{{.*}}"Outer::Inner"
+
+## The unit that found the pointer already marked has to reach the same
+## placement, so its reference resolves into the compile unit as well.
+# CHECK: DW_AT_name{{.*}}"CU2"
+# CHECK: DW_AT_name{{.*}}"f2"
+# CHECK: DW_TAG_formal_parameter
+# CHECK-NEXT: DW_AT_type{{.*}}"Outer::Inner *"
+
+## Placement must not depend on how the units interleave.
+# RUN: dsymutil --linker=parallel -y %t.map -f -o %t.1.dSYM --num-threads 1
+# RUN: dsymutil --linker=parallel -y %t.map -f -o %t.4.dSYM --num-threads 4
+# RUN: dsymutil --linker=parallel -y %t.map -f -o %t.4b.dSYM --num-threads 4
+## The first line of the dump names the input file, which
diff ers by design.
+# RUN: llvm-dwarfdump -debug-info %t.1.dSYM | sed 1d > %t.1.txt
+# RUN: llvm-dwarfdump -debug-info %t.4.dSYM | sed 1d > %t.4.txt
+# RUN: llvm-dwarfdump -debug-info %t.4b.dSYM | sed 1d > %t.4b.txt
+# RUN:
diff %t.1.txt %t.4.txt
+# RUN:
diff %t.4.txt %t.4b.txt
+
+ .section __TEXT,__text,regular,pure_instructions
+ .globl __Z2f1v
+__Z2f1v:
+Lfunc_begin0:
+ retq
+Lfunc_end0:
+ .globl __Z2f2v
+__Z2f2v:
+Lfunc_begin1:
+ retq
+Lfunc_end1:
+
+ .section __DWARF,__debug_abbrev,regular,debug
+Lsection_abbrev:
+## Abbreviations for CU1.
+Labbrev_cu1:
+ .byte 1 ## Abbreviation Code
+ .byte 17 ## DW_TAG_compile_unit
+ .byte 1 ## DW_CHILDREN_yes
+ .byte 37 ## DW_AT_producer
+ .byte 8 ## DW_FORM_string
+ .byte 19 ## DW_AT_language
+ .byte 5 ## DW_FORM_data2
+ .byte 3 ## DW_AT_name
+ .byte 8 ## DW_FORM_string
+ .byte 0, 0
+
+ .byte 2 ## Abbreviation Code
+ .byte 46 ## DW_TAG_subprogram
+ .byte 1 ## DW_CHILDREN_yes
+ .byte 3 ## DW_AT_name
+ .byte 8 ## DW_FORM_string
+ .byte 0x87, 0x40 ## DW_AT_MIPS_linkage_name (0x2007)
+ .byte 8 ## DW_FORM_string
+ .byte 17 ## DW_AT_low_pc
+ .byte 1 ## DW_FORM_addr
+ .byte 18 ## DW_AT_high_pc
+ .byte 1 ## DW_FORM_addr
+ .byte 63 ## DW_AT_external
+ .byte 12 ## DW_FORM_flag
+ .byte 0, 0
+
+ .byte 3 ## Abbreviation Code
+ .byte 5 ## DW_TAG_formal_parameter
+ .byte 0 ## DW_CHILDREN_no
+ .byte 73 ## DW_AT_type
+ .byte 19 ## DW_FORM_ref4
+ .byte 0, 0
+
+ .byte 4 ## Abbreviation Code
+ .byte 57 ## DW_TAG_namespace
+ .byte 1 ## DW_CHILDREN_yes
+ .byte 0, 0
+
+ .byte 5 ## Abbreviation Code
+ .byte 19 ## DW_TAG_structure_type
+ .byte 1 ## DW_CHILDREN_yes
+ .byte 3 ## DW_AT_name
+ .byte 8 ## DW_FORM_string
+ .byte 11 ## DW_AT_byte_size
+ .byte 11 ## DW_FORM_data1
+ .byte 0, 0
+
+ .byte 6 ## Abbreviation Code
+ .byte 13 ## DW_TAG_member
+ .byte 0 ## DW_CHILDREN_no
+ .byte 3 ## DW_AT_name
+ .byte 8 ## DW_FORM_string
+ .byte 73 ## DW_AT_type
+ .byte 19 ## DW_FORM_ref4
+ .byte 56 ## DW_AT_data_member_location
+ .byte 11 ## DW_FORM_data1
+ .byte 0, 0
+
+ .byte 7 ## Abbreviation Code
+ .byte 15 ## DW_TAG_pointer_type
+ .byte 0 ## DW_CHILDREN_no
+ .byte 73 ## DW_AT_type
+ .byte 19 ## DW_FORM_ref4
+ .byte 0, 0
+
+ .byte 8 ## Abbreviation Code
+ .byte 36 ## DW_TAG_base_type
+ .byte 0 ## DW_CHILDREN_no
+ .byte 3 ## DW_AT_name
+ .byte 8 ## DW_FORM_string
+ .byte 11 ## DW_AT_byte_size
+ .byte 11 ## DW_FORM_data1
+ .byte 62 ## DW_AT_encoding
+ .byte 11 ## DW_FORM_data1
+ .byte 0, 0
+
+ .byte 0 ## EOM(3)
+
+## Abbreviations for CU2. The formal parameter references a DIE in CU1, so it
+## uses DW_FORM_ref_addr rather than the unit-relative DW_FORM_ref4.
+Labbrev_cu2:
+ .byte 1 ## Abbreviation Code
+ .byte 17 ## DW_TAG_compile_unit
+ .byte 1 ## DW_CHILDREN_yes
+ .byte 37 ## DW_AT_producer
+ .byte 8 ## DW_FORM_string
+ .byte 19 ## DW_AT_language
+ .byte 5 ## DW_FORM_data2
+ .byte 3 ## DW_AT_name
+ .byte 8 ## DW_FORM_string
+ .byte 0, 0
+
+ .byte 2 ## Abbreviation Code
+ .byte 46 ## DW_TAG_subprogram
+ .byte 1 ## DW_CHILDREN_yes
+ .byte 3 ## DW_AT_name
+ .byte 8 ## DW_FORM_string
+ .byte 0x87, 0x40 ## DW_AT_MIPS_linkage_name (0x2007)
+ .byte 8 ## DW_FORM_string
+ .byte 17 ## DW_AT_low_pc
+ .byte 1 ## DW_FORM_addr
+ .byte 18 ## DW_AT_high_pc
+ .byte 1 ## DW_FORM_addr
+ .byte 63 ## DW_AT_external
+ .byte 12 ## DW_FORM_flag
+ .byte 0, 0
+
+ .byte 3 ## Abbreviation Code
+ .byte 5 ## DW_TAG_formal_parameter
+ .byte 0 ## DW_CHILDREN_no
+ .byte 73 ## DW_AT_type
+ .byte 16 ## DW_FORM_ref_addr
+ .byte 0, 0
+
+ .byte 0 ## EOM(3)
+
+ .section __DWARF,__debug_info,regular,debug
+Lsection_info:
+Lcu1_begin:
+ .long Lcu1_end - Lcu1_start ## Length of Unit
+Lcu1_start:
+ .short 4 ## DWARF version number
+ .long Labbrev_cu1 - Lsection_abbrev ## Offset Into Abbrev. Section
+ .byte 8 ## Address Size (in bytes)
+
+ .byte 1 ## Abbrev [1] DW_TAG_compile_unit
+ .asciz "hand-written" ## DW_AT_producer
+ .short 0x0004 ## DW_AT_language (DW_LANG_C_plus_plus)
+ .asciz "CU1" ## DW_AT_name
+
+ .byte 2 ## Abbrev [2] DW_TAG_subprogram
+ .asciz "f1" ## DW_AT_name
+ .asciz "__Z2f1v" ## DW_AT_MIPS_linkage_name
+ .quad Lfunc_begin0 ## DW_AT_low_pc
+ .quad Lfunc_end0 ## DW_AT_high_pc
+ .byte 1 ## DW_AT_external
+
+ .byte 3 ## Abbrev [3] DW_TAG_formal_parameter
+ .long Linner_ptr - Lcu1_begin ## DW_AT_type
+
+ .byte 0 ## End Of Children Mark (f1)
+
+ .byte 4 ## Abbrev [4] DW_TAG_namespace (anonymous)
+
+Lhidden:
+ .byte 5 ## Abbrev [5] DW_TAG_structure_type
+ .asciz "Hidden" ## DW_AT_name
+ .byte 4 ## DW_AT_byte_size
+
+ .byte 6 ## Abbrev [6] DW_TAG_member
+ .asciz "x" ## DW_AT_name
+ .long Lint - Lcu1_begin ## DW_AT_type
+ .byte 0 ## DW_AT_data_member_location
+
+ .byte 0 ## End Of Children Mark (Hidden)
+
+ .byte 0 ## End Of Children Mark (namespace)
+
+## Outer is ODR deduplicated into the artificial type unit, but Inner has a
+## member of a type from the anonymous namespace, which cannot be deduplicated.
+## Inner is therefore demoted to plain DWARF independently of Outer, and the
+## pointer to Inner has to follow it.
+Louter:
+ .byte 5 ## Abbrev [5] DW_TAG_structure_type
+ .asciz "Outer" ## DW_AT_name
+ .byte 4 ## DW_AT_byte_size
+
+Linner:
+ .byte 5 ## Abbrev [5] DW_TAG_structure_type
+ .asciz "Inner" ## DW_AT_name
+ .byte 4 ## DW_AT_byte_size
+
+ .byte 6 ## Abbrev [6] DW_TAG_member
+ .asciz "h" ## DW_AT_name
+ .long Lhidden - Lcu1_begin ## DW_AT_type
+ .byte 0 ## DW_AT_data_member_location
+
+ .byte 0 ## End Of Children Mark (Inner)
+
+ .byte 0 ## End Of Children Mark (Outer)
+
+Linner_ptr:
+ .byte 7 ## Abbrev [7] DW_TAG_pointer_type
+ .long Linner - Lcu1_begin ## DW_AT_type
+
+Lint:
+ .byte 8 ## Abbrev [8] DW_TAG_base_type
+ .asciz "int" ## DW_AT_name
+ .byte 4 ## DW_AT_byte_size
+ .byte 5 ## DW_AT_encoding (DW_ATE_signed)
+
+ .byte 0 ## End Of Children Mark (CU1)
+Lcu1_end:
+
+Lcu2_begin:
+ .long Lcu2_end - Lcu2_start ## Length of Unit
+Lcu2_start:
+ .short 4 ## DWARF version number
+ .long Labbrev_cu2 - Lsection_abbrev ## Offset Into Abbrev. Section
+ .byte 8 ## Address Size (in bytes)
+
+ .byte 1 ## Abbrev [1] DW_TAG_compile_unit
+ .asciz "hand-written" ## DW_AT_producer
+ .short 0x0004 ## DW_AT_language (DW_LANG_C_plus_plus)
+ .asciz "CU2" ## DW_AT_name
+
+ .byte 2 ## Abbrev [2] DW_TAG_subprogram
+ .asciz "f2" ## DW_AT_name
+ .asciz "__Z2f2v" ## DW_AT_MIPS_linkage_name
+ .quad Lfunc_begin1 ## DW_AT_low_pc
+ .quad Lfunc_end1 ## DW_AT_high_pc
+ .byte 1 ## DW_AT_external
+
+ .byte 3 ## Abbrev [3] DW_TAG_formal_parameter
+ .long Linner_ptr - Lsection_info ## DW_AT_type
+
+ .byte 0 ## End Of Children Mark (f2)
+
+ .byte 0 ## End Of Children Mark (CU2)
+Lcu2_end:
More information about the llvm-branch-commits
mailing list