[llvm-branch-commits] [llvm] [BOLT] Page out .dwo files (PR #214903)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Aug 7 17:53:46 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-bolt

Author: Rafael Auler (rafaelauler)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/214903.diff


3 Files Affected:

- (modified) bolt/include/bolt/Utils/Utils.h (+11) 
- (modified) bolt/lib/Core/BinaryContext.cpp (+68-2) 
- (modified) bolt/lib/Utils/Utils.cpp (+37) 


``````````diff
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,

``````````

</details>


https://github.com/llvm/llvm-project/pull/214903


More information about the llvm-branch-commits mailing list