[lld] [llvm] [LLD][COFF] Prefetch inputs early-on to improve link times (PR #169224)

Alexandre Ganea via llvm-commits llvm-commits at lists.llvm.org
Fri Jan 9 06:33:40 PST 2026


https://github.com/aganea updated https://github.com/llvm/llvm-project/pull/169224

>From 4206ff60ee2255fa099a29cedd006b33aedead0b Mon Sep 17 00:00:00 2001
From: Alexandre Ganea <aganea at havenstudios.com>
Date: Fri, 3 Oct 2025 17:14:25 -0400
Subject: [PATCH 1/5] [LLD][COFF] Prefetch inputs early on to improve link
 times

---
 lld/COFF/Driver.cpp                      |  8 +++++++-
 llvm/include/llvm/Support/FileSystem.h   |  2 ++
 llvm/include/llvm/Support/MemoryBuffer.h |  5 +++++
 llvm/lib/Support/MemoryBuffer.cpp        |  1 +
 llvm/lib/Support/Unix/Path.inc           | 13 ++++++++++++
 llvm/lib/Support/Windows/Path.inc        | 26 ++++++++++++++++++++++++
 6 files changed, 54 insertions(+), 1 deletion(-)

diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index a5ce5829eadab..c3c1a84b1c111 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -165,6 +165,8 @@ static std::future<MBErrPair> createFutureForFile(std::string path) {
                                          /*RequiresNullTerminator=*/false);
     if (!mbOrErr)
       return MBErrPair{nullptr, mbOrErr.getError()};
+    // Prefetch memory pages in the background as we will need them soon enough.
+    (*mbOrErr)->willNeedIfMmap();
     return MBErrPair{std::move(*mbOrErr), std::error_code()};
   });
 }
@@ -378,8 +380,12 @@ void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputOpt inputOpt) {
         auto retryMb = MemoryBuffer::getFile(*retryPath, /*IsText=*/false,
                                              /*RequiresNullTerminator=*/false);
         ec = retryMb.getError();
-        if (!ec)
+        if (!ec) {
           mb = std::move(*retryMb);
+          // Prefetch memory pages in the background as we will need them soon
+          // enough.
+          mb->willNeedIfMmap();
+        }
       } else {
         // We've already handled this file.
         return;
diff --git a/llvm/include/llvm/Support/FileSystem.h b/llvm/include/llvm/Support/FileSystem.h
index cf2a8104ac813..547d732dc3053 100644
--- a/llvm/include/llvm/Support/FileSystem.h
+++ b/llvm/include/llvm/Support/FileSystem.h
@@ -1310,6 +1310,7 @@ class mapped_file_region {
 
   LLVM_ABI void unmapImpl();
   LLVM_ABI void dontNeedImpl();
+  LLVM_ABI void willNeedImpl();
 
   LLVM_ABI std::error_code init(sys::fs::file_t FD, uint64_t Offset,
                                 mapmode Mode);
@@ -1341,6 +1342,7 @@ class mapped_file_region {
     copyFrom(mapped_file_region());
   }
   void dontNeed() { dontNeedImpl(); }
+  void willNeed() { willNeedImpl(); }
 
   LLVM_ABI size_t size() const;
   LLVM_ABI char *data() const;
diff --git a/llvm/include/llvm/Support/MemoryBuffer.h b/llvm/include/llvm/Support/MemoryBuffer.h
index f092c67265a31..9b0f1f0f7d34e 100644
--- a/llvm/include/llvm/Support/MemoryBuffer.h
+++ b/llvm/include/llvm/Support/MemoryBuffer.h
@@ -83,6 +83,11 @@ class LLVM_ABI MemoryBuffer {
   /// function should not be called on a writable buffer.
   virtual void dontNeedIfMmap() {}
 
+  /// Mark the buffer as to-be-used in a near future. This shall trigger OS
+  /// prefetching from the storage device and into memory, if possible.
+  /// This should be use purely as an read optimization.
+  virtual void willNeedIfMmap() {}
+
   /// Open the specified file as a MemoryBuffer, returning a new MemoryBuffer
   /// if successful, otherwise returning null.
   ///
diff --git a/llvm/lib/Support/MemoryBuffer.cpp b/llvm/lib/Support/MemoryBuffer.cpp
index 6ee2d05eaafd0..8491421762d1d 100644
--- a/llvm/lib/Support/MemoryBuffer.cpp
+++ b/llvm/lib/Support/MemoryBuffer.cpp
@@ -248,6 +248,7 @@ class MemoryBufferMMapFile : public MB {
   }
 
   void dontNeedIfMmap() override { MFR.dontNeed(); }
+  void willNeedIfMmap() override { MFR.willNeed(); }
 };
 } // namespace
 
diff --git a/llvm/lib/Support/Unix/Path.inc b/llvm/lib/Support/Unix/Path.inc
index e7929af029785..fc71ca46d823a 100644
--- a/llvm/lib/Support/Unix/Path.inc
+++ b/llvm/lib/Support/Unix/Path.inc
@@ -925,6 +925,19 @@ void mapped_file_region::dontNeedImpl() {
 #endif
 }
 
+void mapped_file_region::willNeedImpl() {
+  assert(Mode == mapped_file_region::readonly);
+  if (!Mapping)
+    return;
+#if defined(__MVS__) || defined(_AIX)
+  // If we don't have madvise, or it isn't beneficial, treat this as a no-op.
+#elif defined(POSIX_MADV_WILLNEED)
+  ::posix_madvise(Mapping, Size, POSIX_MADV_WILLNEED);
+#else
+  ::madvise(Mapping, Size, MADV_WILLNEED);
+#endif
+}
+
 int mapped_file_region::alignment() { return Process::getPageSizeEstimate(); }
 
 std::error_code detail::directory_iterator_construct(detail::DirIterState &it,
diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc
index ca60a5b86ba7b..e149077c7d21a 100644
--- a/llvm/lib/Support/Windows/Path.inc
+++ b/llvm/lib/Support/Windows/Path.inc
@@ -1051,6 +1051,32 @@ void mapped_file_region::unmapImpl() {
 
 void mapped_file_region::dontNeedImpl() {}
 
+void mapped_file_region::willNeedImpl() {
+#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
+  typedef struct _WIN32_MEMORY_RANGE_ENTRY {
+    PVOID VirtualAddress;
+    SIZE_T NumberOfBytes;
+  } WIN32_MEMORY_RANGE_ENTRY, *PWIN32_MEMORY_RANGE_ENTRY;
+#endif
+
+  HMODULE kernelM = llvm::sys::windows::loadSystemModuleSecure(L"kernel32.dll");
+  if (kernelM) {
+    // PrefetchVirtualMemory is only available on Windows 8 and later. Since we
+    // still support compilation on Windows 7, we load the function dynamically.
+    typedef BOOL(WINAPI * PrefetchVirtualMemory_t)(
+        HANDLE hProcess, ULONG_PTR NumberOfEntries,
+        _In_reads_(NumberOfEntries) PWIN32_MEMORY_RANGE_ENTRY VirtualAddresses,
+        ULONG Flags);
+    static const auto pfnPrefetchVirtualMemory =
+        (PrefetchVirtualMemory_t)::GetProcAddress(kernelM,
+                                                  "PrefetchVirtualMemory");
+    if (pfnPrefetchVirtualMemory) {
+      WIN32_MEMORY_RANGE_ENTRY Range{Mapping, Size};
+      pfnPrefetchVirtualMemory(::GetCurrentProcess(), 1, &Range, 0);
+    }
+  }
+}
+
 std::error_code mapped_file_region::sync() const {
   if (!::FlushViewOfFile(Mapping, Size))
     return mapWindowsError(GetLastError());

>From 50ed5604bab81d75dca357e59387c3f0b44c8002 Mon Sep 17 00:00:00 2001
From: Alexandre Ganea <aganea at havenstudios.com>
Date: Fri, 28 Nov 2025 10:12:17 -0500
Subject: [PATCH 2/5] Unconditionally define struct

---
 llvm/lib/Support/Windows/Path.inc | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc
index e149077c7d21a..f9b7d26479c5d 100644
--- a/llvm/lib/Support/Windows/Path.inc
+++ b/llvm/lib/Support/Windows/Path.inc
@@ -1052,26 +1052,24 @@ void mapped_file_region::unmapImpl() {
 void mapped_file_region::dontNeedImpl() {}
 
 void mapped_file_region::willNeedImpl() {
-#if (_WIN32_WINNT < _WIN32_WINNT_WIN8)
-  typedef struct _WIN32_MEMORY_RANGE_ENTRY {
-    PVOID VirtualAddress;
-    SIZE_T NumberOfBytes;
-  } WIN32_MEMORY_RANGE_ENTRY, *PWIN32_MEMORY_RANGE_ENTRY;
-#endif
-
   HMODULE kernelM = llvm::sys::windows::loadSystemModuleSecure(L"kernel32.dll");
   if (kernelM) {
+    struct MEMORY_RANGE_ENTRY {
+      PVOID VirtualAddress;
+      SIZE_T NumberOfBytes;
+    };
+
     // PrefetchVirtualMemory is only available on Windows 8 and later. Since we
     // still support compilation on Windows 7, we load the function dynamically.
     typedef BOOL(WINAPI * PrefetchVirtualMemory_t)(
         HANDLE hProcess, ULONG_PTR NumberOfEntries,
-        _In_reads_(NumberOfEntries) PWIN32_MEMORY_RANGE_ENTRY VirtualAddresses,
+        _In_reads_(NumberOfEntries) MEMORY_RANGE_ENTRY * VirtualAddresses,
         ULONG Flags);
     static const auto pfnPrefetchVirtualMemory =
         (PrefetchVirtualMemory_t)::GetProcAddress(kernelM,
                                                   "PrefetchVirtualMemory");
     if (pfnPrefetchVirtualMemory) {
-      WIN32_MEMORY_RANGE_ENTRY Range{Mapping, Size};
+      MEMORY_RANGE_ENTRY Range{Mapping, Size};
       pfnPrefetchVirtualMemory(::GetCurrentProcess(), 1, &Range, 0);
     }
   }

>From bd3b4dcc86b6f7c771ebad4c350738d64ea356f7 Mon Sep 17 00:00:00 2001
From: Alexandre Ganea <aganea at havenstudios.com>
Date: Mon, 1 Dec 2025 10:27:37 -0500
Subject: [PATCH 3/5] loadSystemModuleSecure and GetProcAddress made static in
 a lambda

---
 llvm/lib/Support/Windows/Path.inc | 41 ++++++++++++++++---------------
 1 file changed, 21 insertions(+), 20 deletions(-)

diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc
index f9b7d26479c5d..c03b85b2f4bb3 100644
--- a/llvm/lib/Support/Windows/Path.inc
+++ b/llvm/lib/Support/Windows/Path.inc
@@ -1052,26 +1052,27 @@ void mapped_file_region::unmapImpl() {
 void mapped_file_region::dontNeedImpl() {}
 
 void mapped_file_region::willNeedImpl() {
-  HMODULE kernelM = llvm::sys::windows::loadSystemModuleSecure(L"kernel32.dll");
-  if (kernelM) {
-    struct MEMORY_RANGE_ENTRY {
-      PVOID VirtualAddress;
-      SIZE_T NumberOfBytes;
-    };
-
-    // PrefetchVirtualMemory is only available on Windows 8 and later. Since we
-    // still support compilation on Windows 7, we load the function dynamically.
-    typedef BOOL(WINAPI * PrefetchVirtualMemory_t)(
-        HANDLE hProcess, ULONG_PTR NumberOfEntries,
-        _In_reads_(NumberOfEntries) MEMORY_RANGE_ENTRY * VirtualAddresses,
-        ULONG Flags);
-    static const auto pfnPrefetchVirtualMemory =
-        (PrefetchVirtualMemory_t)::GetProcAddress(kernelM,
-                                                  "PrefetchVirtualMemory");
-    if (pfnPrefetchVirtualMemory) {
-      MEMORY_RANGE_ENTRY Range{Mapping, Size};
-      pfnPrefetchVirtualMemory(::GetCurrentProcess(), 1, &Range, 0);
-    }
+  struct MEMORY_RANGE_ENTRY {
+    PVOID VirtualAddress;
+    SIZE_T NumberOfBytes;
+  };
+  // PrefetchVirtualMemory is only available on Windows 8 and later. Since we
+  // still support compilation on Windows 7, we load the function dynamically.
+  typedef BOOL(WINAPI * PrefetchVirtualMemory_t)(
+      HANDLE hProcess, ULONG_PTR NumberOfEntries,
+      MEMORY_RANGE_ENTRY * VirtualAddresses, ULONG Flags);
+
+  static auto pfnPrefetchVirtualMemory = []() -> PrefetchVirtualMemory_t {
+    HMODULE kernelM =
+        llvm::sys::windows::loadSystemModuleSecure(L"kernel32.dll");
+    if (!kernelM)
+      return nullptr;
+    return (PrefetchVirtualMemory_t)::GetProcAddress(kernelM,
+                                                     "PrefetchVirtualMemory");
+  }();
+  if (pfnPrefetchVirtualMemory) {
+    MEMORY_RANGE_ENTRY Range{Mapping, Size};
+    pfnPrefetchVirtualMemory(::GetCurrentProcess(), 1, &Range, 0);
   }
 }
 

>From 355e6b899df27687661639dab96bf14228aeefc3 Mon Sep 17 00:00:00 2001
From: Alexandre Ganea <alex_toresh at yahoo.fr>
Date: Sat, 20 Dec 2025 11:12:12 -0500
Subject: [PATCH 4/5] Add flag -prefetch-inputs and update release notes.

---
 lld/COFF/Config.h         |  1 +
 lld/COFF/Driver.cpp       | 19 +++++++++++++------
 lld/COFF/Options.td       |  5 +++++
 lld/docs/ReleaseNotes.rst |  1 +
 4 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index c5c0338c99e57..2ee60aca116d6 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -349,6 +349,7 @@ struct Configuration {
   bool pseudoRelocs = false;
   bool stdcallFixup = false;
   bool writeCheckSum = false;
+  bool prefetchInputs = false;
   EmitKind emit = EmitKind::Obj;
   bool allowDuplicateWeak = false;
   BuildIDHash buildIDHash = BuildIDHash::None;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index c3c1a84b1c111..73c3e545bf24e 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -151,7 +151,8 @@ using MBErrPair = std::pair<std::unique_ptr<MemoryBuffer>, std::error_code>;
 
 // Create a std::future that opens and maps a file using the best strategy for
 // the host platform.
-static std::future<MBErrPair> createFutureForFile(std::string path) {
+static std::future<MBErrPair> createFutureForFile(std::string path,
+                                                  bool prefetchInputs) {
 #if _WIN64
   // On Windows, file I/O is relatively slow so it is best to do this
   // asynchronously.  But 32-bit has issues with potentially launching tons
@@ -166,7 +167,8 @@ static std::future<MBErrPair> createFutureForFile(std::string path) {
     if (!mbOrErr)
       return MBErrPair{nullptr, mbOrErr.getError()};
     // Prefetch memory pages in the background as we will need them soon enough.
-    (*mbOrErr)->willNeedIfMmap();
+    if (prefetchInputs)
+      (*mbOrErr)->willNeedIfMmap();
     return MBErrPair{std::move(*mbOrErr), std::error_code()};
   });
 }
@@ -363,7 +365,7 @@ void LinkerDriver::handleReproFile(StringRef path, InputOpt inputOpt) {
 
 void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputOpt inputOpt) {
   auto future = std::make_shared<std::future<MBErrPair>>(
-      createFutureForFile(std::string(path)));
+      createFutureForFile(std::string(path), ctx.config.prefetchInputs));
   std::string pathStr = std::string(path);
   enqueueTask([=]() {
     llvm::TimeTraceScope timeScope("File: ", path);
@@ -384,7 +386,8 @@ void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputOpt inputOpt) {
           mb = std::move(*retryMb);
           // Prefetch memory pages in the background as we will need them soon
           // enough.
-          mb->willNeedIfMmap();
+          if (ctx.config.prefetchInputs)
+            mb->willNeedIfMmap();
         }
       } else {
         // We've already handled this file.
@@ -482,8 +485,8 @@ void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
       CHECK(c.getFullName(),
             "could not get the filename for the member defining symbol " +
                 toCOFFString(ctx, sym));
-  auto future =
-      std::make_shared<std::future<MBErrPair>>(createFutureForFile(childName));
+  auto future = std::make_shared<std::future<MBErrPair>>(
+      createFutureForFile(childName, ctx.config.prefetchInputs));
   enqueueTask([=]() {
     auto mbOrErr = future->get();
     if (mbOrErr.second)
@@ -2275,6 +2278,10 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
     config->incremental = false;
   }
 
+  // Handle -prefetch-inputs
+  if (args.hasFlag(OPT_prefetch_inputs, OPT_prefetch_inputs_no, false))
+    config->prefetchInputs = true;
+
   if (errCount(ctx))
     return;
 
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index 32e55f57fb696..fb762b880c2cb 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -354,6 +354,11 @@ defm build_id: B<
 def incl_glob : Joined<["/", "-", "/?", "-?"], "includeglob:">,
     HelpText<"Force symbol to be added to symbol table as undefined one using a glob pattern">;
 
+defm prefetch_inputs : B<"prefetch-inputs",
+                         "Request the OS to prefetch input files as early as "
+                         "possible, to improve link times",
+                         "Do not prefetch input files (default)">;
+
 // Flags for debugging
 def lldmap : F<"lldmap">;
 def lldmap_file : P_priv<"lldmap">;
diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst
index b65bcca07b143..27d61dae07814 100644
--- a/lld/docs/ReleaseNotes.rst
+++ b/lld/docs/ReleaseNotes.rst
@@ -42,6 +42,7 @@ COFF Improvements
   (`#165529 <https://github.com/llvm/llvm-project/pull/165529>`_)
 * ``/linkreprofullpathrsp`` prints the full path to each object passed to the link line to a file.
   (`#174971 <https://github.com/llvm/llvm-project/pull/165449>`_)
+* New flag ``-prefetch-inputs`` can improve link times if (some) input files were not in the Windows cache.
 
 MinGW Improvements
 ------------------

>From fe52c0177edfb119a92f09a03e4867b2413b1545 Mon Sep 17 00:00:00 2001
From: Alexandre Ganea <aganea at havenstudios.com>
Date: Thu, 8 Jan 2026 20:18:28 -0500
Subject: [PATCH 5/5] Address review comments.

---
 lld/COFF/Driver.cpp       | 1 -
 lld/docs/ReleaseNotes.rst | 7 ++++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 73c3e545bf24e..b1337ea8157ab 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2278,7 +2278,6 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
     config->incremental = false;
   }
 
-  // Handle -prefetch-inputs
   if (args.hasFlag(OPT_prefetch_inputs, OPT_prefetch_inputs_no, false))
     config->prefetchInputs = true;
 
diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst
index 27d61dae07814..ea749a9255551 100644
--- a/lld/docs/ReleaseNotes.rst
+++ b/lld/docs/ReleaseNotes.rst
@@ -42,7 +42,12 @@ COFF Improvements
   (`#165529 <https://github.com/llvm/llvm-project/pull/165529>`_)
 * ``/linkreprofullpathrsp`` prints the full path to each object passed to the link line to a file.
   (`#174971 <https://github.com/llvm/llvm-project/pull/165449>`_)
-* New flag ``-prefetch-inputs`` can improve link times if (some) input files were not in the Windows cache.
+* ``-prefetch-inputs`` can improve link times by asynchronously loading input files in RAM.
+  This will dampen the effect of input file I/O latency on link times.
+  However this flag can have an adverse effect when linking a large number of inputs files, or if all
+  inputs do not fit in RAM at once. For those cases, linking might be a bit slower since the inputs
+  will be streamed into RAM upfront, only to be evicted later by swapping.
+  (`#169224 https://github.com/llvm/llvm-project/pull/169224`_)
 
 MinGW Improvements
 ------------------



More information about the llvm-commits mailing list