[llvm-branch-commits] [llvm] [BOLT] Page out .dwo files (PR #214903)
Rafael Auler via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Aug 7 17:53:07 PDT 2026
https://github.com/rafaelauler created https://github.com/llvm/llvm-project/pull/214903
Split-DWARF inputs at big binaries scale ship 100+ GiB of .dwo files. BOLT opened a fair number of them during readDebugInfo, putting a lot of pressure on the OS memory management: mmap'd reads always populate the page cache; with every .dwo mapped at once those pages accumulated, refaulted, and registered as memory pressure that got the process oomd-killed.
Now, .dwo page-cache pages are reclaimed as soon as BOLT is done with each file: madvise(MADV_PAGEOUT) on the live mapping, then posix_fadvise(POSIX_FADV_DONTNEED) once it is unmapped. Controlled by -drop-dwo-page-cache, OFF by default, as it is unlikely upstream will be processing gigantic sets of dwo files.
>From f5f3c6afc62ce6d8e2e31821bcebcf83668ea3ab Mon Sep 17 00:00:00 2001
From: Rafael Auler <rafaelauler at fb.com>
Date: Fri, 31 Jul 2026 14:14:09 -0700
Subject: [PATCH] [BOLT] Page out .dwo files
Split-DWARF inputs at big binaries scale ship 100+ GiB of .dwo
files. BOLT opened a fair number of them during readDebugInfo, putting
a lot of pressure on the OS memory management: mmap'd reads always
populate the page cache; with every .dwo mapped at once those pages
accumulated, refaulted, and registered as memory pressure that got the
process oomd-killed.
Now, .dwo page-cache pages are reclaimed as soon as BOLT is done with
each file: madvise(MADV_PAGEOUT) on the live mapping, then
posix_fadvise(POSIX_FADV_DONTNEED) once it is unmapped. Controlled by
-drop-dwo-page-cache, OFF by default, as it is unlikely upstream
will be processing gigantic sets of dwo files.
---
bolt/include/bolt/Utils/Utils.h | 11 ++++++
bolt/lib/Core/BinaryContext.cpp | 70 ++++++++++++++++++++++++++++++++-
bolt/lib/Utils/Utils.cpp | 37 +++++++++++++++++
3 files changed, 116 insertions(+), 2 deletions(-)
diff --git a/bolt/include/bolt/Utils/Utils.h b/bolt/include/bolt/Utils/Utils.h
index 3b4a17e919d00..e1a10147e54a1 100644
--- a/bolt/include/bolt/Utils/Utils.h
+++ b/bolt/include/bolt/Utils/Utils.h
@@ -79,6 +79,17 @@ std::optional<uint8_t> readDWARFExpressionTargetReg(StringRef ExprBytes);
void safePWrite(raw_fd_ostream &OS, const char *Src, size_t Size,
uint64_t Offset);
+/// Ask the OS to reclaim the pages backing the mapped range starting at
+/// \p Addr and spanning \p Size bytes. Intended for data read in a streaming
+/// fashion, where keeping the pages resident only adds memory pressure. The
+/// range is trimmed to whole pages, and the request is silently ignored if
+/// \p Addr is not page-aligned. No-op on platforms without MADV_PAGEOUT.
+void pageOutMemory(const void *Addr, size_t Size);
+
+/// Evict any page-cache pages still held for the file at \p Path. Only takes
+/// effect once every mapping of the file is gone. No-op on non-Linux platforms.
+void dropFileFromPageCache(StringRef Path);
+
} // namespace bolt
bool operator==(const llvm::MCCFIInstruction &L,
diff --git a/bolt/lib/Core/BinaryContext.cpp b/bolt/lib/Core/BinaryContext.cpp
index ef3238edfc635..7cc661afaa79a 100644
--- a/bolt/lib/Core/BinaryContext.cpp
+++ b/bolt/lib/Core/BinaryContext.cpp
@@ -18,7 +18,9 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/Twine.h"
#include "llvm/DebugInfo/DWARF/DWARFCompileUnit.h"
+#include "llvm/DebugInfo/DWARF/DWARFContext.h"
#include "llvm/DebugInfo/DWARF/DWARFFormValue.h"
+#include "llvm/DebugInfo/DWARF/DWARFObject.h"
#include "llvm/DebugInfo/DWARF/DWARFUnit.h"
#include "llvm/MC/MCAssembler.h"
#include "llvm/MC/MCContext.h"
@@ -31,9 +33,11 @@
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
+#include "llvm/Object/ObjectFile.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBufferRef.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/ScopedPrinter.h"
#include <algorithm>
@@ -91,6 +95,13 @@ static cl::opt<bool>
FailOnInvalidPadding("fail-on-invalid-padding", cl::Hidden, cl::init(false),
cl::desc("treat invalid code padding as error"),
cl::ZeroOrMore, cl::cat(BoltCategory));
+
+static cl::opt<bool> DropDWOPageCache(
+ "drop-dwo-page-cache",
+ cl::desc("Treat .dwo files as streaming input: reclaim their page-cache "
+ "pages as soon as BOLT is done reading each one. Keeps the "
+ "file-backed footprint of large split-DWARF inputs bounded."),
+ cl::Hidden, cl::init(false), cl::cat(BoltCategory));
} // namespace opts
namespace llvm {
@@ -1738,6 +1749,26 @@ BinaryFunctionListType BinaryContext::getAllBinaryFunctions() {
return AllFunctions;
}
+/// Return the path of the file backing \p DWOCU's DWARF context, or "" .
+static StringRef getDWOFilePath(const DWARFUnit *DWOCU) {
+ if (!DWOCU)
+ return StringRef();
+ return DWOCU->getContext().getDWARFObj().getFileName();
+}
+
+/// Reclaim the page-cache pages of the still-mapped .dwo backing \p DWOCU.
+static void pageOutDWOMapping(const DWARFUnit *DWOCU) {
+ if (!opts::DropDWOPageCache || !DWOCU)
+ return;
+ const object::ObjectFile *File = DWOCU->getContext().getDWARFObj().getFile();
+ if (!File)
+ return;
+ // mmap-backed MemoryBuffers are page-aligned; the malloc-backed fallback
+ // used for small files is not, and pageOutMemory() ignores it.
+ MemoryBufferRef Buf = File->getMemoryBufferRef();
+ pageOutMemory(Buf.getBufferStart(), Buf.getBufferSize());
+}
+
/// 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
@@ -1799,8 +1830,16 @@ void BinaryContext::releaseDWOCU(uint64_t DWOId) {
if (UsesDWP)
return;
auto Iter = DWOIdToSkeletonCU.find(DWOId);
- if (Iter != DWOIdToSkeletonCU.end())
- Iter->second->clearDWO();
+ if (Iter == DWOIdToSkeletonCU.end())
+ return;
+ DWARFUnit *DWOCU = Iter->second->getDWO();
+ if (!DWOCU)
+ return;
+ const std::string Path = getDWOFilePath(DWOCU).str();
+ Iter->second->clearDWO();
+ // Only effective now that the last mapping of the file is gone.
+ if (opts::DropDWOPageCache)
+ dropFileFromPageCache(Path);
}
void BinaryContext::releaseAllDWOContexts() {
@@ -1808,8 +1847,25 @@ void BinaryContext::releaseAllDWOContexts() {
// it was opened (preprocessDWODebugInfo, the line-table name loop, or
// collectDebugScopeBoundaries, which opens CUs directly via
// getNonSkeletonUnitDIE).
+ std::vector<std::string> Paths;
+ if (opts::DropDWOPageCache) {
+ Paths.reserve(UsesDWP ? 1 : DWOIdToSkeletonCU.size());
+ for (auto &KV : DWOIdToSkeletonCU) {
+ DWARFUnit *DWOCU = KV.second->getDWO();
+ if (!DWOCU)
+ continue;
+ StringRef Path = getDWOFilePath(DWOCU);
+ if (!Path.empty())
+ Paths.emplace_back(Path.str());
+ if (UsesDWP)
+ break;
+ }
+ }
for (auto &KV : DWOIdToSkeletonCU)
KV.second->clearDWO();
+ // Only effective now that the last mapping of each file is gone.
+ for (const std::string &Path : Paths)
+ dropFileFromPageCache(Path);
}
bool BinaryContext::isValidDwarfUnit(DWARFUnit &DU) const {
@@ -1869,6 +1925,10 @@ void BinaryContext::preprocessDWODebugInfo() {
// Remember the skeleton CU so its DWO context can be (re-)opened lazily by
// getDWOCU() after it is released (see releaseAllDWOContexts).
DWOIdToSkeletonCU[*DWOId] = DwarfUnit;
+ // In this loop we're touching a large number of pages, in streaming fashion
+ // -- so keep OS file cache usage under control.
+ if (!UsesDWP)
+ pageOutDWOMapping(DWOCU);
}
if (!DWOIdToSkeletonCU.empty())
this->outs() << "BOLT-INFO: processing split DWARF\n";
@@ -2091,6 +2151,12 @@ void BinaryContext::collectDebugScopeBoundaries() {
// vector (as DWARFUnit's extractDIEsToVector() does): only DIE tags/ranges
// are inspected, so the tree structure is not needed.
forEachDIEInUnit(*DIEUnit, processScopeDie);
+
+ // Done streaming this .dwo's DIEs. Release it right away and give the
+ // opportunity for any used page cache to also be reclaimed by OS, if
+ // --drop-dwo-page-cache is in effect.
+ if (std::optional<uint64_t> DWOId = CU->getDWOId())
+ releaseDWOCU(*DWOId);
}
}
diff --git a/bolt/lib/Utils/Utils.cpp b/bolt/lib/Utils/Utils.cpp
index a9c8405d7f90b..81152bc79135a 100644
--- a/bolt/lib/Utils/Utils.cpp
+++ b/bolt/lib/Utils/Utils.cpp
@@ -11,11 +11,18 @@
//===----------------------------------------------------------------------===//
#include "bolt/Utils/Utils.h"
+#include "llvm/ADT/SmallString.h"
#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/MC/MCDwarf.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/raw_ostream.h"
+#if defined(__linux__)
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
namespace llvm {
namespace bolt {
@@ -119,6 +126,36 @@ void safePWrite(raw_fd_ostream &OS, const char *Src, size_t Size,
OS.seek(SavedPos);
}
+void pageOutMemory(const void *Addr, size_t Size) {
+#if defined(__linux__) && defined(MADV_PAGEOUT)
+ const uintptr_t Start = reinterpret_cast<uintptr_t>(Addr);
+ const size_t PageSize = static_cast<size_t>(::getpagesize());
+ // madvise() requires a page-aligned start.
+ if (PageSize == 0 || Start % PageSize != 0 || Size < PageSize)
+ return;
+ (void)::madvise(const_cast<void *>(Addr), Size & ~(PageSize - 1),
+ MADV_PAGEOUT);
+#else
+ (void)Addr;
+ (void)Size;
+#endif
+}
+
+void dropFileFromPageCache(StringRef Path) {
+#if defined(__linux__)
+ if (Path.empty())
+ return;
+ SmallString<128> PathStorage(Path);
+ const int FD = ::open(PathStorage.c_str(), O_RDONLY);
+ if (FD < 0)
+ return;
+ (void)::posix_fadvise(FD, 0, 0, POSIX_FADV_DONTNEED);
+ ::close(FD);
+#else
+ (void)Path;
+#endif
+}
+
} // namespace bolt
bool operator==(const llvm::MCCFIInstruction &L,
More information about the llvm-branch-commits
mailing list