[llvm-branch-commits] [llvm] [BOLT] Create and release .dwo DWARF contexts incrementally (PR #214900)
Rafael Auler via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Aug 7 17:46:48 PDT 2026
https://github.com/rafaelauler created https://github.com/llvm/llvm-project/pull/214900
BOLT opened a DWARFContext for every .dwo during
readDebugInfo and kept them all alive until teardown. On large split-DWARF targets that is tens of GiB held resident through emission, the point of peak RSS.
Make the DWOCUs map a lazily-populated cache instead:
* Use the newly added DWARFUnit::clearDWO()/hasDWO() to directly manage DWARFUnit's DIE caching mechanism.
* BinaryContext::getDWOCU() opens a context on demand (keyed off a stable DWOId -> skeleton CU map).
* Release contexts as soon as they are done with: all of them at the end of readDebugInfo, and per-bucket at the DWARF rewrite merge point.
* Remove DWOCUs map, which became redundant and whose purpose can now be served by the new id-to-skeleton map, and then fetching the split CU from the skeleton via getNonSkeletonUnitDIE().
Also drop DWARFRewriter::writeDWOFiles()'s use of getDWOContext(), which dereferenced an arbitrary DWOCUs entry -- with contexts now being freed concurrently that would be a use-after-free. The CU's own context answers the .dwp question identically.
>From 76e43e06c6136d968fa28075834ade01b1f389e8 Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Thu, 30 Jul 2026 18:45:01 -0700
Subject: [PATCH] [BOLT] Create and release .dwo DWARF contexts incrementally
BOLT opened a DWARFContext for every .dwo during
readDebugInfo and kept them all alive until teardown. On large
split-DWARF targets that is tens of GiB held resident through
emission, the point of peak RSS.
Make the DWOCUs map a lazily-populated cache instead:
* Use the newly added DWARFUnit::clearDWO()/hasDWO() to directly
manage DWARFUnit's DIE caching mechanism.
* BinaryContext::getDWOCU() opens a context on demand (keyed off a
stable DWOId -> skeleton CU map).
* Release contexts as soon as they are done with: all of them at the
end of readDebugInfo, and per-bucket at the DWARF rewrite merge
point.
* Remove DWOCUs map, which became redundant and whose purpose can
now be served by the new id-to-skeleton map, and then fetching
the split CU from the skeleton via getNonSkeletonUnitDIE().
Also drop DWARFRewriter::writeDWOFiles()'s use of getDWOContext(),
which dereferenced an arbitrary DWOCUs entry -- with contexts now
being freed concurrently that would be a use-after-free. The CU's own
context answers the .dwp question identically.
---
bolt/include/bolt/Core/BinaryContext.h | 41 +++++--
bolt/lib/Core/BinaryContext.cpp | 160 +++++++++++++++++--------
bolt/lib/Rewrite/DWARFRewriter.cpp | 23 ++--
bolt/lib/Rewrite/RewriteInstance.cpp | 2 +
4 files changed, 151 insertions(+), 75 deletions(-)
diff --git a/bolt/include/bolt/Core/BinaryContext.h b/bolt/include/bolt/Core/BinaryContext.h
index 240e5a75d1de5..9c4f21a335cfb 100644
--- a/bolt/include/bolt/Core/BinaryContext.h
+++ b/bolt/include/bolt/Core/BinaryContext.h
@@ -261,15 +261,18 @@ class BinaryContext {
/// The runtime library.
std::unique_ptr<RuntimeLibrary> RtLibrary;
- /// DWP Context.
- std::shared_ptr<DWARFContext> DWPContext;
-
/// Decoded pseudo probes.
std::shared_ptr<MCPseudoProbeDecoder> PseudoProbeDecoder;
- /// A map of DWO Ids to CUs.
+ /// Populated once in preprocessDWODebugInfo() and immutable thereafter; it
+ /// lets getDWOCU() (re-)open a DWO context on demand. The context is then
+ /// owned and cached by the skeleton unit itself (DWARFUnit::hasDWO()).
using DWOIdToCUMapType = std::unordered_map<uint64_t, DWARFUnit *>;
- DWOIdToCUMapType DWOCUs;
+ DWOIdToCUMapType DWOIdToSkeletonCU;
+
+ /// With a package every split CU shares one DWARFContext, which changes how
+ /// it may be released -- see releaseDWOCU().
+ bool UsesDWP{false};
bool ContainsDwarf5{false};
bool ContainsDwarfLegacy{false};
@@ -326,14 +329,32 @@ class BinaryContext {
void clearFragmentsToSkip() { FragmentsToSkip.clear(); }
- /// Given DWOId returns CU if it exists in DWOCUs.
+ /// True if split-dwarf files being processed come from a package (as
+ /// opposed to dwo files scattered on disk).
+ bool usesDWP() const { return UsesDWP; }
+
+ /// Given a DWOId, return the corresponding split CU, lazily opening its DWO
+ /// context if needed. Returns std::nullopt if the DWO could not be loaded.
+ ///
+ /// There is no shared mutable state to guard here: DWOIdToSkeletonCU is
+ /// immutable after preprocessing and the context is cached by the skeleton
+ /// unit. Callers must, however, keep a given DWOId to a single thread at a
+ /// time -- during parallel rewriting each DWOId belongs to exactly one
+ /// bucket -- because opening and releasing both mutate the skeleton unit's
+ /// DWO pointer.
std::optional<DWARFUnit *> getDWOCU(uint64_t DWOId);
- /// Returns DWOContext if it exists.
- DWARFContext *getDWOContext() const;
+ /// Release the DWO context previously opened for \p DWOId (if any), freeing
+ /// its DWARFContext and parsed unit vector. Same threading contract as
+ /// getDWOCU().
+ void releaseDWOCU(uint64_t DWOId);
+
+ /// Release all currently-open DWO contexts. Used after preprocessing, so that
+ /// contexts are re-opened lazily and during rewriting. Not thread-safe.
+ void releaseAllDWOContexts();
- /// Get Number of DWOCUs in a map.
- uint32_t getNumDWOCUs() { return DWOCUs.size(); }
+ /// Get the number of split-DWARF CUs in the binary.
+ uint32_t getNumDWOCUs() { return DWOIdToSkeletonCU.size(); }
/// Returns true if DWARF5 is used.
bool isDWARF5Used() const { return ContainsDwarf5; }
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index 13d7e4bc1a5d6..ef3238edfc635 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -1738,18 +1738,78 @@ BinaryFunctionListType BinaryContext::getAllBinaryFunctions() {
return AllFunctions;
}
+/// Compute the absolute path of the .dwo/.dwp file backing \p SkeletonCU,
+/// honoring --comp-dir-override and relative-path fallbacks. If
+/// \p FellBackToRelative is non-null, it is set to true when the compilation
+/// directory was missing and the relative path was used instead. If
+/// \p DWONameOut is non-null, it receives the raw DW_AT_dwo_name.
+static SmallString<128> getDWOAbsolutePath(DWARFUnit &SkeletonCU,
+ bool *FellBackToRelative = nullptr,
+ std::string *DWONameOut = nullptr) {
+ std::string DWOName =
+ dwarf::toString(SkeletonCU.getUnitDIE().find(
+ {dwarf::DW_AT_dwo_name, dwarf::DW_AT_GNU_dwo_name}),
+ "");
+ SmallString<128> AbsolutePath(DWOName);
+
+ std::string DWOCompDir;
+ if (!opts::CompDirOverride.empty()) {
+ DWOCompDir = opts::CompDirOverride;
+ } else {
+ DWOCompDir = SkeletonCU.getCompilationDir();
+ if (!sys::fs::exists(DWOCompDir) && sys::fs::exists(DWOName)) {
+ DWOCompDir = ".";
+ if (FellBackToRelative)
+ *FellBackToRelative = true;
+ }
+ }
+ // Prevent failures when DWOName is already an absolute path.
+ sys::path::make_absolute(DWOCompDir, AbsolutePath);
+ if (DWONameOut)
+ *DWONameOut = std::move(DWOName);
+ return AbsolutePath;
+}
+
std::optional<DWARFUnit *> BinaryContext::getDWOCU(uint64_t DWOId) {
- auto Iter = DWOCUs.find(DWOId);
- if (Iter == DWOCUs.end())
+ auto Iter = DWOIdToSkeletonCU.find(DWOId);
+ if (Iter == DWOIdToSkeletonCU.end())
+ return std::nullopt;
+ DWARFUnit &SkeletonCU = *Iter->second;
+
+ // The skeleton unit owns and caches its DWO context, so it is the cache:
+ // parseDWO() is a no-op once the context is open, and only then do we need
+ // the .dwo path -- computing it stats the filesystem, so skip that on the
+ // hot path.
+ SmallString<128> AbsolutePath;
+ if (!SkeletonCU.getDWO())
+ AbsolutePath = getDWOAbsolutePath(SkeletonCU);
+
+ DWARFUnit *DWOCU =
+ SkeletonCU
+ .getNonSkeletonUnitDIE(/*ExtractUnitDIEOnly=*/true, AbsolutePath)
+ .getDwarfUnit();
+ // On failure getNonSkeletonUnitDIE() falls back to the skeleton's own DIE.
+ if (!DWOCU || !DWOCU->isDWOUnit())
return std::nullopt;
+ return DWOCU;
+}
- return Iter->second;
+void BinaryContext::releaseDWOCU(uint64_t DWOId) {
+ // With a .dwp package every split CU aliases one shared DWARFContext
+ if (UsesDWP)
+ return;
+ auto Iter = DWOIdToSkeletonCU.find(DWOId);
+ if (Iter != DWOIdToSkeletonCU.end())
+ Iter->second->clearDWO();
}
-DWARFContext *BinaryContext::getDWOContext() const {
- if (DWOCUs.empty())
- return nullptr;
- return &DWOCUs.begin()->second->getContext();
+void BinaryContext::releaseAllDWOContexts() {
+ // Release via the skeleton map so we free every DWO context regardless of how
+ // it was opened (preprocessDWODebugInfo, the line-table name loop, or
+ // collectDebugScopeBoundaries, which opens CUs directly via
+ // getNonSkeletonUnitDIE).
+ for (auto &KV : DWOIdToSkeletonCU)
+ KV.second->clearDWO();
}
bool BinaryContext::isValidDwarfUnit(DWARFUnit &DU) const {
@@ -1772,47 +1832,45 @@ void BinaryContext::preprocessDWODebugInfo() {
DWARFUnit *const DwarfUnit = CU.get();
if (!isValidDwarfUnit(*DwarfUnit))
continue;
- if (std::optional<uint64_t> DWOId = DwarfUnit->getDWOId()) {
- std::string DWOName = dwarf::toString(
- DwarfUnit->getUnitDIE().find(
- {dwarf::DW_AT_dwo_name, dwarf::DW_AT_GNU_dwo_name}),
- "");
- SmallString<16> AbsolutePath(DWOName);
- std::string DWOCompDir = DwarfUnit->getCompilationDir();
- if (!opts::CompDirOverride.empty()) {
- DWOCompDir = opts::CompDirOverride;
- } else if (!sys::fs::exists(DWOCompDir) && sys::fs::exists(DWOName)) {
- DWOCompDir = ".";
- this->outs()
- << "BOLT-WARNING: Debug Fission: Debug Compilation Directory of "
- << DWOName
- << " does not exist. Relative path will be used to process .dwo "
- "files.\n";
- }
- // 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(/*ExtractUnitDIEOnly=*/true, AbsolutePath)
- .getDwarfUnit();
- if (!DWOCU->isDWOUnit()) {
- this->outs()
- << "BOLT-WARNING: Debug Fission: DWO debug information for "
- << DWOName
- << " was not retrieved and won't be updated. Please check "
- "relative path or use '--comp-dir-override' to specify the base "
- "location.\n";
- continue;
- }
- DWOCUs[*DWOId] = DWOCU;
+ std::optional<uint64_t> DWOId = DwarfUnit->getDWOId();
+ if (!DWOId)
+ continue;
+ std::string DWOName;
+ bool FellBackToRelative = false;
+ SmallString<128> AbsolutePath =
+ getDWOAbsolutePath(*DwarfUnit, &FellBackToRelative, &DWOName);
+ if (FellBackToRelative)
+ this->outs()
+ << "BOLT-WARNING: Debug Fission: Debug Compilation Directory of "
+ << DWOName
+ << " does not exist. Relative path will be used to process .dwo "
+ "files.\n";
+ // Extract only the .dwo CU DIE here: we just need 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(/*ExtractUnitDIEOnly=*/true, AbsolutePath)
+ .getDwarfUnit();
+ if (!DWOCU->isDWOUnit()) {
+ this->outs()
+ << "BOLT-WARNING: Debug Fission: DWO debug information for "
+ << DWOName
+ << " was not retrieved and won't be updated. Please check "
+ "relative path or use '--comp-dir-override' to specify the base "
+ "location.\n";
+ continue;
}
- }
- if (!DWOCUs.empty())
+ // Detect a .dwp package on the first split CU we manage to open.
+ if (DWOIdToSkeletonCU.empty())
+ UsesDWP = !DWOCU->getContext().getCUIndex().getRows().empty();
+ // Remember the skeleton CU so its DWO context can be (re-)opened lazily by
+ // getDWOCU() after it is released (see releaseAllDWOContexts).
+ DWOIdToSkeletonCU[*DWOId] = DwarfUnit;
+ }
+ if (!DWOIdToSkeletonCU.empty())
this->outs() << "BOLT-INFO: processing split DWARF\n";
}
@@ -1925,8 +1983,8 @@ void BinaryContext::preprocessDebugInfo() {
const char *Name =
dwarf::toString(CU->getUnitDIE().find(dwarf::DW_AT_name), nullptr);
if (std::optional<uint64_t> DWOID = CU->getDWOId()) {
- auto Iter = DWOCUs.find(*DWOID);
- if (Iter == DWOCUs.end()) {
+ std::optional<DWARFUnit *> DWOCU = getDWOCU(*DWOID);
+ if (!DWOCU) {
const char *DWOName =
dwarf::toString(CU->getUnitDIE().find(dwarf::DW_AT_dwo_name),
"<missing DW_AT_dwo_name>");
@@ -1935,8 +1993,8 @@ void BinaryContext::preprocessDebugInfo() {
NumMissingDWOs++;
continue;
}
- Name = dwarf::toString(
- Iter->second->getUnitDIE().find(dwarf::DW_AT_name), nullptr);
+ Name = dwarf::toString((*DWOCU)->getUnitDIE().find(dwarf::DW_AT_name),
+ nullptr);
}
BinaryLineTable.setRootFile(CU->getCompilationDir(), Name, Checksum,
std::nullopt);
diff --git a/bolt/lib/Rewrite/DWARFRewriter.cpp b/bolt/lib/Rewrite/DWARFRewriter.cpp
index 1c02fc2ffd38e..e416b79f6717f 100644
--- a/bolt/lib/Rewrite/DWARFRewriter.cpp
+++ b/bolt/lib/Rewrite/DWARFRewriter.cpp
@@ -1061,6 +1061,9 @@ void DWARFRewriter::updateDebugInfo() {
BucketDIEBlders[Idx].reset();
LocalWriters[Idx].RngListsWriter.reset();
LocalWriters[Idx].LegacyRangesWriter.reset();
+ for (DWARFUnit *CU : SortedCUs)
+ if (std::optional<uint64_t> DWOId = CU->getDWOId())
+ BC.releaseDWOCU(*DWOId);
};
for (size_t I = 0; I < TotalTasks; ++I) {
@@ -2250,15 +2253,7 @@ void DWARFRewriter::writeDWOFiles(
const std::string &DWOName, DebugLocWriter &LocWriter,
DebugStrOffsetsWriter &StrOffstsWriter, DebugStrWriter &StrWriter,
DebugRangesSectionWriter &TempRangesSectionWriter) {
- // Setup DWP code once.
- DWARFContext *DWOCtx = BC.getDWOContext();
const uint64_t DWOId = *CU.getDWOId();
- const DWARFUnitIndex *CUIndex = nullptr;
- bool IsDWP = false;
- if (DWOCtx) {
- CUIndex = &DWOCtx->getCUIndex();
- IsDWP = !CUIndex->getRows().empty();
- }
// Skipping CUs that we failed to load.
std::optional<DWARFUnit *> DWOCU = BC.getDWOCU(DWOId);
@@ -2284,9 +2279,9 @@ void DWARFRewriter::writeDWOFiles(
std::unique_ptr<ToolOutputFile> TempOut =
std::make_unique<ToolOutputFile>(AbsolutePath, EC, sys::fs::OF_None);
- const DWARFUnitIndex::Entry *CUDWOEntry = nullptr;
- if (IsDWP)
- CUDWOEntry = CUIndex->getFromHash(DWOId);
+ const DWARFUnitIndex::Entry *CUDWOEntry =
+ !BC.usesDWP() ? nullptr
+ : (*DWOCU)->getContext().getCUIndex().getFromHash(DWOId);
const object::ObjectFile *File =
(*DWOCU)->getContext().getDWARFObj().getFile();
@@ -2324,7 +2319,7 @@ void DWARFRewriter::writeDWOFiles(
continue;
Expected<StringRef> ContentsExp = Section.getContents();
assert(ContentsExp && "Invalid contents.");
- if (IsDWP && SectionName == "debug_str.dwo") {
+ if (BC.usesDWP() && SectionName == "debug_str.dwo") {
if (StrWriter.isInitialized())
StrDWOContent = StrWriter.getBufferStr();
else
@@ -2335,7 +2330,7 @@ void DWARFRewriter::writeDWOFiles(
(*DWOCU)->getContext(), SectionName, *ContentsExp, KnownSections,
*Streamer, *this, CUDWOEntry, DWOId, OutputData, RangeListssWriter,
LocWriter, StrOffstsWriter, StrWriter, OverridenSections)) {
- if (IsDWP && SectionName == "debug_str_offsets.dwo") {
+ if (BC.usesDWP() && SectionName == "debug_str_offsets.dwo") {
StrOffsetsContent = *OutData;
continue;
}
@@ -2343,7 +2338,7 @@ void DWARFRewriter::writeDWOFiles(
}
}
- if (IsDWP) {
+ if (BC.usesDWP()) {
// Handling both .debug_str.dwo and .debug_str_offsets.dwo concurrently. In
// the original DWP, .debug_str is a deduplicated global table, and the
// .debug_str.dwo slice for a single CU needs to be extracted according to
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index c122614a5ee0f..f7212cfeb97df 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -3779,6 +3779,8 @@ void RewriteInstance::readDebugInfo() {
TimerGroupDesc, opts::TimeRewrite);
BC->collectDebugScopeBoundaries();
}
+
+ BC->releaseAllDWOContexts();
}
void RewriteInstance::preprocessProfileData() {
More information about the llvm-branch-commits
mailing list