https://github.com/rafaelauler created https://github.com/llvm/llvm-project/pull/207292
Summary: preprocessDWODebugInfo() eagerly force-extracted every .dwo compile unit's DIE tree (getNonSkeletonUnitDIE(false)) very early in BOLT pipeline, way before DWARFRewriter kicked in. Those vectors then sit in memory throughout the entire rewrite pipeline, directly contributing to BOLT's RSS peak. I did a fair amount of digging and didn't find any reason as to why we need to keep all DIEs of DWO CU materialized at all, since DWARFRewriter won't even read this vector (the #197359 concurrency fix did use that, but that is unnecessary). The problem is that these DIE trees are a massive contribution to RSS when processing large binaries where we have 10s of K of dwos, storing complete trees for each processed dwo. This diff changes the #197359 concurrency fix to not rely on the DIE sibling/children structure. It parses DWP type units selectively per compile unit (DIEBuilder::buildDWPTypeUnitsForUnit -> collectReferencedTypeSignatures) by finding the DW_FORM_ref_sig8 references in a unit's DIEs to decide which type units belong in that unit's output .dwo. That walk previously used DWARFDie::children(), which requires the unit's full DIE vector. Here we rewrite the walk to stream the unit's DIEs one at a time with DWARFDebugInfoEntry::extractFast (the same technique already used by DIEBuilder::constructFromUnit and DWARFRewriter::partitionCUs), reading DW_FORM_ref_sig8 attributes off a single reusable transient entry. The tree structure is irrelevant -- every DIE in the unit is visited regardless -- so no DIE vector is built. collectReferencedTypeSignatures now takes a DWARFUnit& instead of a DWARFDie. With the walk self-sufficient: - preprocessDWODebugInfo() now extracts only the .dwo CU DIE (getNonSkeletonUnitDIE(true)); nothing reads the full array off it anymore (constructFromUnit and the signature walk both stream). - BinaryContext::collectDebugScopeBoundaries() drops its split-DWARF fast-path, which called DWARFUnit::dies() (= full extraction); DWO units are now streamed like monolithic ones. The result is that .dwo DIE vectors are never materialized during BOLT processing. Expected to be a ~10% RSS win on large split-dwarf binaries. Depends on #207291 >From 475f4e821fc5cef929eb82d332c086361fdbf689 Mon Sep 17 00:00:00 2001 From: Rafael Auler <[email protected]> Date: Wed, 1 Jul 2026 19:30:18 -0700 Subject: [PATCH] [BOLT] Stop materializing .dwo DIE vectors early in the pipeline Summary: preprocessDWODebugInfo() eagerly force-extracted every .dwo compile unit's DIE tree (getNonSkeletonUnitDIE(false)) very early in BOLT pipeline, way before DWARFRewriter kicked in. Those vectors then sit in memory throughout the entire rewrite pipeline, directly contributing to BOLT's RSS peak. I did a fair amount of digging and didn't find any reason as to why we need to keep all DIEs of DWO CU materialized at all, since DWARFRewriter won't even read this vector (the #197359 concurrency fix did use that, but that is unnecessary). The problem is that these DIE trees are a massive contribution to RSS when processing large binaries where we have 10s of K of dwos, storing complete trees for each processed dwo. This diff changes the #197359 concurrency fix to not rely on the DIE sibling/children structure. It parses DWP type units selectively per compile unit (DIEBuilder::buildDWPTypeUnitsForUnit -> collectReferencedTypeSignatures) by finding the DW_FORM_ref_sig8 references in a unit's DIEs to decide which type units belong in that unit's output .dwo. That walk previously used DWARFDie::children(), which requires the unit's full DIE vector. Here we rewrite the walk to stream the unit's DIEs one at a time with DWARFDebugInfoEntry::extractFast (the same technique already used by DIEBuilder::constructFromUnit and DWARFRewriter::partitionCUs), reading DW_FORM_ref_sig8 attributes off a single reusable transient entry. The tree structure is irrelevant -- every DIE in the unit is visited regardless -- so no DIE vector is built. collectReferencedTypeSignatures now takes a DWARFUnit& instead of a DWARFDie. With the walk self-sufficient: - preprocessDWODebugInfo() now extracts only the .dwo CU DIE (getNonSkeletonUnitDIE(true)); nothing reads the full array off it anymore (constructFromUnit and the signature walk both stream). - BinaryContext::collectDebugScopeBoundaries() drops its split-DWARF fast-path, which called DWARFUnit::dies() (= full extraction); DWO units are now streamed like monolithic ones. The result is that .dwo DIE vectors are never materialized during BOLT processing. Expected to be a ~10% RSS win on large split-dwarf binaries. --- bolt/lib/Core/BinaryContext.cpp | 16 +++++++-------- bolt/lib/Core/DIEBuilder.cpp | 36 +++++++++++++++++---------------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp index 9d07775508da6..0647fda69724a 100644 --- a/bolt/lib/Core/BinaryContext.cpp +++ b/bolt/lib/Core/BinaryContext.cpp @@ -1779,8 +1779,15 @@ void BinaryContext::preprocessDWODebugInfo() { } // Prevent failures when DWOName is already an absolute path. sys::path::make_absolute(DWOCompDir, AbsolutePath); + // Extract only the .dwo CU DIE here: we just need the DWO unit pointer + // (for DWOCUs) and the isDWOUnit()/DWOId checks. The full DIE vector is + // never read off this cached array -- every consumer streams the DIEs on + // demand with DWARFDebugInfoEntry::extractFast (DIEBuilder:: + // constructFromUnit / collectReferencedTypeSignatures). DWARFUnit *DWOCU = - DwarfUnit->getNonSkeletonUnitDIE(false, AbsolutePath).getDwarfUnit(); + DwarfUnit + ->getNonSkeletonUnitDIE(/*ExtractUnitDIEOnly=*/true, AbsolutePath) + .getDwarfUnit(); if (!DWOCU->isDWOUnit()) { this->outs() << "BOLT-WARNING: Debug Fission: DWO debug information for " @@ -2008,13 +2015,6 @@ void BinaryContext::collectDebugScopeBoundaries() { continue; DWARFUnit *DIEUnit = CUDie.getDwarfUnit(); - // For split DWARF, preprocessDWODebugInfo already fully extracts the .dwo's - // DIE array. - if (DIEUnit->isDWOUnit()) { - for (const DWARFDebugInfoEntry &Entry : DIEUnit->dies()) - processScopeDie(DWARFDie(DIEUnit, &Entry)); - continue; - } // Walk the unit's DIEs by streaming them one at a time. Track nesting depth // with a counter: a DIE with children descends a level (++), a null entry // (sibling-chain terminator) ascends (--), the unit-end offset limits the diff --git a/bolt/lib/Core/DIEBuilder.cpp b/bolt/lib/Core/DIEBuilder.cpp index 9ee6059a3cc88..8326aa77a0ad0 100644 --- a/bolt/lib/Core/DIEBuilder.cpp +++ b/bolt/lib/Core/DIEBuilder.cpp @@ -283,23 +283,28 @@ void DIEBuilder::buildTypeUnits(DebugStrOffsetsWriter *StrOffsetWriter, } } -/// Recursively collects type unit signatures from the given DIE and all of its -/// children. +/// Collects the signatures of all type units referenced (via DW_FORM_ref_sig8) +/// by any DIE of \p U. +/// +/// The DIEs are streamed one at a time with DWARFDebugInfoEntry::extractFast so +/// the unit's full DIE vector is never materialized -- this keeps RSS down +/// during debug-info processing, where BOLT is already memory-heavy. /// /// Note: De-duplication of the collected signatures is handled at the outer /// level by registerUnit. -static void collectReferencedTypeSignatures(DWARFDie Die, +static void collectReferencedTypeSignatures(DWARFUnit &U, DenseSet<uint64_t> &ProcessedTU, SmallVectorImpl<uint64_t> &TUlist) { - SmallVector<DWARFDie, 8> DIElist; - DIElist.push_back(Die); - - while (!DIElist.empty()) { - DWARFDie Current = DIElist.pop_back_val(); - if (!Current) - continue; - - for (const DWARFAttribute &Attr : Current.attributes()) { + DWARFDataExtractor DebugInfoData = U.getDebugInfoExtractor(); + uint64_t DIEOffset = U.getOffset() + U.getHeaderSize(); + const uint64_t NextCUOffset = U.getNextUnitOffset(); + DWARFDebugInfoEntry DIEEntry; + while (DIEOffset < NextCUOffset && + DIEEntry.extractFast(U, &DIEOffset, DebugInfoData, NextCUOffset, 0)) { + if (!DIEEntry.getAbbreviationDeclarationPtr()) + continue; // Null entry: terminator of a sibling chain. + DWARFDie Die(&U, &DIEEntry); + for (const DWARFAttribute &Attr : Die.attributes()) { if (Attr.Value.getForm() != dwarf::DW_FORM_ref_sig8) continue; if (const std::optional<uint64_t> Signature = @@ -307,9 +312,6 @@ static void collectReferencedTypeSignatures(DWARFDie Die, if (ProcessedTU.insert(*Signature).second) TUlist.push_back(*Signature); } - - for (DWARFDie Child : Current.children()) - DIElist.push_back(Child); } } @@ -319,7 +321,7 @@ void DIEBuilder::buildDWPTypeUnitsForUnit(DWARFUnit &U) { DenseSet<uint64_t> ProcessedTU; SmallVector<uint64_t, 8> TUlist; // Collecting signatures of type units referenced by this unit. - collectReferencedTypeSignatures(U.getUnitDIE(), ProcessedTU, TUlist); + collectReferencedTypeSignatures(U, ProcessedTU, TUlist); getState().Type = U.getVersion() < 5 ? ProcessingType::DWARF4TUs : ProcessingType::DWARF5TUs; @@ -336,7 +338,7 @@ void DIEBuilder::buildDWPTypeUnitsForUnit(DWARFUnit &U) { if (!UnitId || getState().CloneUnitCtxMap[*UnitId].IsConstructed) continue; - collectReferencedTypeSignatures(TU->getUnitDIE(), ProcessedTU, TUlist); + collectReferencedTypeSignatures(*TU, ProcessedTU, TUlist); } // Ensure original order of processing type units _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
