[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