From c55b5b2c9f49d23a6063cc6e7a756e22c9cede43 Mon Sep 17 00:00:00 2001 From: John Holdsworth Date: Sat, 5 Jul 2025 10:24:51 +0200 Subject: [PATCH 1/5] Multi-threaded disk i/o. --- lld/MachO/Config.h | 1 + lld/MachO/Driver.cpp | 111 +++++++++++++++++++++++++++++++++++++++---- lld/MachO/Options.td | 3 ++ 3 files changed, 106 insertions(+), 9 deletions(-) diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h index a01e60efbe761..92c6eb85f4123 100644 --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -186,6 +186,7 @@ struct Configuration { bool interposable = false; bool errorForArchMismatch = false; bool ignoreAutoLink = false; + int readThreads = 0; // ld64 allows invalid auto link options as long as the link succeeds. LLD // does not, but there are cases in the wild where the invalid linker options // exist. This allows users to ignore the specific invalid options in the case diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index 9eb391c4ee1b9..36626720aa252 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -44,6 +44,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/Parallel.h" #include "llvm/Support/Path.h" +#include "llvm/Support/Process.h" #include "llvm/Support/TarWriter.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Support/TimeProfiler.h" @@ -282,11 +283,11 @@ static void saveThinArchiveToRepro(ArchiveFile const *file) { ": Archive::children failed: " + toString(std::move(e))); } -static InputFile *addFile(StringRef path, LoadType loadType, - bool isLazy = false, bool isExplicit = true, - bool isBundleLoader = false, - bool isForceHidden = false) { - std::optional buffer = readFile(path); +static InputFile *deferredAddFile(std::optional buffer, + StringRef path, LoadType loadType, + bool isLazy = false, bool isExplicit = true, + bool isBundleLoader = false, + bool isForceHidden = false) { if (!buffer) return nullptr; MemoryBufferRef mbref = *buffer; @@ -441,6 +442,14 @@ static InputFile *addFile(StringRef path, LoadType loadType, return newFile; } +static InputFile *addFile(StringRef path, LoadType loadType, + bool isLazy = false, bool isExplicit = true, + bool isBundleLoader = false, + bool isForceHidden = false) { + return deferredAddFile(readFile(path), path, loadType, isLazy, isExplicit, + isBundleLoader, isForceHidden); +} + static std::vector missingAutolinkWarnings; static void addLibrary(StringRef name, bool isNeeded, bool isWeak, bool isReexport, bool isHidden, bool isExplicit, @@ -564,13 +573,23 @@ void macho::resolveLCLinkerOptions() { } } -static void addFileList(StringRef path, bool isLazy) { +typedef struct { + StringRef path; + std::optional buffer; +} DeferredFile; + +static void addFileList(StringRef path, bool isLazy, + std::vector &deferredFiles) { std::optional buffer = readFile(path); if (!buffer) return; MemoryBufferRef mbref = *buffer; for (StringRef path : args::getLines(mbref)) - addFile(rerootPath(path), LoadType::CommandLine, isLazy); + if (config->readThreads) { + StringRef rrpath = rerootPath(path); + deferredFiles.push_back({rrpath, readFile(rrpath)}); + } else + addFile(rerootPath(path), LoadType::CommandLine, isLazy); } // We expect sub-library names of the form "libfoo", which will match a dylib @@ -1215,13 +1234,68 @@ static void handleSymbolPatterns(InputArgList &args, parseSymbolPatternsFile(arg, symbolPatterns); } -static void createFiles(const InputArgList &args) { +// Most input files have been mapped but not yet paged in. +// This code forces the page-ins on multiple threads so +// the process is not stalled waiting on disk buffer i/o. +void multiThreadedPageIn(std::vector &deferred, int nthreads) { +#ifndef _WIN32 + typedef struct { + std::vector &deferred; + size_t counter, total, pageSize; + pthread_mutex_t mutex; + } PageInState; + PageInState state = {deferred, 0, 0, + llvm::sys::Process::getPageSizeEstimate(), + pthread_mutex_t()}; + pthread_mutex_init(&state.mutex, NULL); + + pthread_t running[200]; + int maxthreads = sizeof running / sizeof running[0]; + if (nthreads > maxthreads) + nthreads = maxthreads; + + for (int t = 0; t < nthreads; t++) + pthread_create( + &running[t], nullptr, + [](void *ptr) -> void * { + PageInState &state = *(PageInState *)ptr; + static int total = 0; + while (true) { + pthread_mutex_lock(&state.mutex); + if (state.counter >= state.deferred.size()) { + pthread_mutex_unlock(&state.mutex); + return nullptr; + } + DeferredFile &add = state.deferred[state.counter]; + state.counter += 1; + pthread_mutex_unlock(&state.mutex); + + int t = 0; // Reference each page to load it into memory. + for (const char *page = add.buffer->getBuffer().data(), + *end = page + add.buffer->getBuffer().size(); + page < end; page += state.pageSize) + t += *page; + state.total += t; // Avoids whole section being optimised out. + } + }, + &state); + + for (int t = 0; t < nthreads; t++) + pthread_join(running[t], nullptr); + + pthread_mutex_destroy(&state.mutex); +#endif +} + +void createFiles(const InputArgList &args) { TimeTraceScope timeScope("Load input files"); // This loop should be reserved for options whose exact ordering matters. // Other options should be handled via filtered() and/or getLastArg(). bool isLazy = false; // If we've processed an opening --start-lib, without a matching --end-lib bool inLib = false; + std::vector deferredFiles; + for (const Arg *arg : args) { const Option &opt = arg->getOption(); warnIfDeprecatedOption(opt); @@ -1229,6 +1303,11 @@ static void createFiles(const InputArgList &args) { switch (opt.getID()) { case OPT_INPUT: + if (config->readThreads) { + StringRef rrpath = rerootPath(arg->getValue()); + deferredFiles.push_back({rrpath, readFile(rrpath)}); + break; + } addFile(rerootPath(arg->getValue()), LoadType::CommandLine, isLazy); break; case OPT_needed_library: @@ -1249,7 +1328,7 @@ static void createFiles(const InputArgList &args) { dylibFile->forceWeakImport = true; break; case OPT_filelist: - addFileList(arg->getValue(), isLazy); + addFileList(arg->getValue(), isLazy, deferredFiles); break; case OPT_force_load: addFile(rerootPath(arg->getValue()), LoadType::CommandLineForce); @@ -1295,6 +1374,12 @@ static void createFiles(const InputArgList &args) { break; } } + + if (config->readThreads) { + multiThreadedPageIn(deferredFiles, config->readThreads); + for (auto &add : deferredFiles) + deferredAddFile(add.buffer, add.path, LoadType::CommandLine, isLazy); + } } static void gatherInputSections() { @@ -1687,6 +1772,14 @@ bool link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, } } + if (auto *arg = args.getLastArg(OPT_read_threads)) { + StringRef v(arg->getValue()); + unsigned threads = 0; + if (!llvm::to_integer(v, threads, 0) || threads < 0) + error(arg->getSpelling() + ": expected a positive integer, but got '" + + arg->getValue() + "'"); + config->readThreads = threads; + } if (auto *arg = args.getLastArg(OPT_threads_eq)) { StringRef v(arg->getValue()); unsigned threads = 0; diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td index 4f0602f59812b..3dc98fccc1b7b 100644 --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -396,6 +396,9 @@ def dead_strip : Flag<["-"], "dead_strip">, def interposable : Flag<["-"], "interposable">, HelpText<"Indirects access to all exported symbols in an image">, Group; +def read_threads : Joined<["--"], "read-threads=">, + HelpText<"Number of threads to use paging in files.">, + Group; def order_file : Separate<["-"], "order_file">, MetaVarName<"">, HelpText<"Layout functions and data according to specification in ">, From 3d11a33599246bbf5e358b554489aeae854ed7be Mon Sep 17 00:00:00 2001 From: John Holdsworth Date: Sun, 6 Jul 2025 10:05:38 +0200 Subject: [PATCH 2/5] Afterthoughts. --- lld/MachO/Driver.cpp | 82 +++++++++++++++++++++++--------------------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index 36626720aa252..5b9f9cc2939bd 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -283,11 +283,11 @@ static void saveThinArchiveToRepro(ArchiveFile const *file) { ": Archive::children failed: " + toString(std::move(e))); } -static InputFile *deferredAddFile(std::optional buffer, - StringRef path, LoadType loadType, - bool isLazy = false, bool isExplicit = true, - bool isBundleLoader = false, - bool isForceHidden = false) { +static InputFile *processFile(std::optional buffer, + StringRef path, LoadType loadType, + bool isLazy = false, bool isExplicit = true, + bool isBundleLoader = false, + bool isForceHidden = false) { if (!buffer) return nullptr; MemoryBufferRef mbref = *buffer; @@ -446,8 +446,24 @@ static InputFile *addFile(StringRef path, LoadType loadType, bool isLazy = false, bool isExplicit = true, bool isBundleLoader = false, bool isForceHidden = false) { - return deferredAddFile(readFile(path), path, loadType, isLazy, isExplicit, - isBundleLoader, isForceHidden); + return processFile(readFile(path), path, loadType, isLazy, isExplicit, + isBundleLoader, isForceHidden); +} + +typedef struct { + StringRef path; + LoadType loadType; + bool isLazy; + std::optional buffer; +} DeferredFile; + +static void deferFile(StringRef path, LoadType loadType, bool isLazy, + std::vector &deferred) { + std::optional buffer = readFile(path); + if (config->readThreads) + deferred.push_back({path, loadType, isLazy, buffer}); + else + processFile(buffer, path, loadType, isLazy); } static std::vector missingAutolinkWarnings; @@ -573,11 +589,6 @@ void macho::resolveLCLinkerOptions() { } } -typedef struct { - StringRef path; - std::optional buffer; -} DeferredFile; - static void addFileList(StringRef path, bool isLazy, std::vector &deferredFiles) { std::optional buffer = readFile(path); @@ -585,11 +596,7 @@ static void addFileList(StringRef path, bool isLazy, return; MemoryBufferRef mbref = *buffer; for (StringRef path : args::getLines(mbref)) - if (config->readThreads) { - StringRef rrpath = rerootPath(path); - deferredFiles.push_back({rrpath, readFile(rrpath)}); - } else - addFile(rerootPath(path), LoadType::CommandLine, isLazy); + deferFile(rerootPath(path), LoadType::CommandLine, isLazy, deferredFiles); } // We expect sub-library names of the form "libfoo", which will match a dylib @@ -1239,43 +1246,44 @@ static void handleSymbolPatterns(InputArgList &args, // the process is not stalled waiting on disk buffer i/o. void multiThreadedPageIn(std::vector &deferred, int nthreads) { #ifndef _WIN32 +#define MaxReadThreads 200 typedef struct { std::vector &deferred; - size_t counter, total, pageSize; + size_t counter, bytes, total, pageSize; pthread_mutex_t mutex; } PageInState; - PageInState state = {deferred, 0, 0, - llvm::sys::Process::getPageSizeEstimate(), - pthread_mutex_t()}; + PageInState state = { + deferred, 0, 0, 0, llvm::sys::Process::getPageSizeEstimate(), + pthread_mutex_t()}; pthread_mutex_init(&state.mutex, NULL); - pthread_t running[200]; - int maxthreads = sizeof running / sizeof running[0]; - if (nthreads > maxthreads) - nthreads = maxthreads; + pthread_t running[MaxReadThreads]; + if (nthreads > MaxReadThreads) + nthreads = MaxReadThreads; for (int t = 0; t < nthreads; t++) pthread_create( &running[t], nullptr, [](void *ptr) -> void * { PageInState &state = *(PageInState *)ptr; - static int total = 0; while (true) { pthread_mutex_lock(&state.mutex); if (state.counter >= state.deferred.size()) { pthread_mutex_unlock(&state.mutex); return nullptr; } - DeferredFile &add = state.deferred[state.counter]; + DeferredFile &file = state.deferred[state.counter]; state.counter += 1; pthread_mutex_unlock(&state.mutex); + const char *page = file.buffer->getBuffer().data(), + *end = page + file.buffer->getBuffer().size(); + state.bytes += end - page; + int t = 0; // Reference each page to load it into memory. - for (const char *page = add.buffer->getBuffer().data(), - *end = page + add.buffer->getBuffer().size(); - page < end; page += state.pageSize) + for (; page < end; page += state.pageSize) t += *page; - state.total += t; // Avoids whole section being optimised out. + state.total += t; // Avoids the loop being optimised out. } }, &state); @@ -1303,12 +1311,8 @@ void createFiles(const InputArgList &args) { switch (opt.getID()) { case OPT_INPUT: - if (config->readThreads) { - StringRef rrpath = rerootPath(arg->getValue()); - deferredFiles.push_back({rrpath, readFile(rrpath)}); - break; - } - addFile(rerootPath(arg->getValue()), LoadType::CommandLine, isLazy); + deferFile(rerootPath(arg->getValue()), LoadType::CommandLine, isLazy, + deferredFiles); break; case OPT_needed_library: if (auto *dylibFile = dyn_cast_or_null( @@ -1377,8 +1381,8 @@ void createFiles(const InputArgList &args) { if (config->readThreads) { multiThreadedPageIn(deferredFiles, config->readThreads); - for (auto &add : deferredFiles) - deferredAddFile(add.buffer, add.path, LoadType::CommandLine, isLazy); + for (auto &file : deferredFiles) + processFile(file.buffer, file.path, file.loadType, file.isLazy); } } From 02fb145b4b2f2710c59ea750e0b14292abc8c58c Mon Sep 17 00:00:00 2001 From: John Holdsworth Date: Sun, 6 Jul 2025 18:09:19 +0200 Subject: [PATCH 3/5] multiThreadedPageIn of library archives. --- lld/MachO/Driver.cpp | 149 +++++++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 70 deletions(-) diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index 5b9f9cc2939bd..bacbe24fb4434 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -283,6 +283,70 @@ static void saveThinArchiveToRepro(ArchiveFile const *file) { ": Archive::children failed: " + toString(std::move(e))); } +typedef struct { + StringRef path; + bool isLazy; + std::optional buffer; + const char *start; + size_t size; +} DeferredFile; + +// Most input files have been mapped but not yet paged in. +// This code forces the page-ins on multiple threads so +// the process is not stalled waiting on disk buffer i/o. +static void multiThreadedPageIn(std::vector &deferred) { +#ifndef _WIN32 +#define MaxReadThreads 200 + typedef struct { + std::vector &deferred; + size_t counter, total, pageSize; + pthread_mutex_t mutex; + } PageInState; + PageInState state = {deferred, 0, 0, + llvm::sys::Process::getPageSizeEstimate(), + pthread_mutex_t()}; + static size_t totalBytes; + + pthread_t running[MaxReadThreads]; + if (config->readThreads > MaxReadThreads) + config->readThreads = MaxReadThreads; + pthread_mutex_init(&state.mutex, NULL); + + for (int t = 0; t < config->readThreads; t++) + pthread_create( + &running[t], nullptr, + [](void *ptr) -> void * { + PageInState &state = *(PageInState *)ptr; + while (true) { + pthread_mutex_lock(&state.mutex); + if (state.counter >= state.deferred.size()) { + pthread_mutex_unlock(&state.mutex); + return nullptr; + } + DeferredFile &file = state.deferred[state.counter]; + state.counter += 1; + pthread_mutex_unlock(&state.mutex); + + const char *page = file.start, *end = page + file.size; + totalBytes += end - page; + + int t = 0; // Reference each page to load it into memory. + for (; page < end; page += state.pageSize) + t += *page; + state.total += t; // Avoids the loop being optimised out. + } + }, + &state); + + for (int t = 0; t < config->readThreads; t++) + pthread_join(running[t], nullptr); + + pthread_mutex_destroy(&state.mutex); + if (getenv("LLD_MULTI_THREAD_PAGE")) + printf("multiThreadedPageIn %ld/%ld\n", totalBytes, deferred.size()); +#endif +} + static InputFile *processFile(std::optional buffer, StringRef path, LoadType loadType, bool isLazy = false, bool isExplicit = true, @@ -367,6 +431,7 @@ static InputFile *processFile(std::optional buffer, // we already found that it contains an ObjC symbol. if (readFile(path)) { Error e = Error::success(); + std::vector deferredFiles; for (const object::Archive::Child &c : file->getArchive().children(e)) { Expected mb = c.getMemoryBufferRef(); if (!mb) { @@ -380,6 +445,9 @@ static InputFile *processFile(std::optional buffer, continue; } + deferredFiles.push_back({path, isLazy, std::nullopt, + mb->getBuffer().data(), + mb->getBuffer().size()}); if (!hasObjCSection(*mb)) continue; if (Error e = file->fetch(c, "-ObjC")) @@ -389,6 +457,8 @@ static InputFile *processFile(std::optional buffer, if (e) error(toString(file) + ": Archive::children failed: " + toString(std::move(e))); + if (config->readThreads && deferredFiles.size() > 1) + multiThreadedPageIn(deferredFiles); } } file->addLazySymbols(); @@ -450,20 +520,14 @@ static InputFile *addFile(StringRef path, LoadType loadType, isBundleLoader, isForceHidden); } -typedef struct { - StringRef path; - LoadType loadType; - bool isLazy; - std::optional buffer; -} DeferredFile; - -static void deferFile(StringRef path, LoadType loadType, bool isLazy, +static void deferFile(StringRef path, bool isLazy, std::vector &deferred) { std::optional buffer = readFile(path); if (config->readThreads) - deferred.push_back({path, loadType, isLazy, buffer}); + deferred.push_back({path, isLazy, buffer, buffer->getBuffer().data(), + buffer->getBuffer().size()}); else - processFile(buffer, path, loadType, isLazy); + processFile(buffer, path, LoadType::CommandLine, isLazy); } static std::vector missingAutolinkWarnings; @@ -596,7 +660,7 @@ static void addFileList(StringRef path, bool isLazy, return; MemoryBufferRef mbref = *buffer; for (StringRef path : args::getLines(mbref)) - deferFile(rerootPath(path), LoadType::CommandLine, isLazy, deferredFiles); + deferFile(rerootPath(path), isLazy, deferredFiles); } // We expect sub-library names of the form "libfoo", which will match a dylib @@ -1241,61 +1305,7 @@ static void handleSymbolPatterns(InputArgList &args, parseSymbolPatternsFile(arg, symbolPatterns); } -// Most input files have been mapped but not yet paged in. -// This code forces the page-ins on multiple threads so -// the process is not stalled waiting on disk buffer i/o. -void multiThreadedPageIn(std::vector &deferred, int nthreads) { -#ifndef _WIN32 -#define MaxReadThreads 200 - typedef struct { - std::vector &deferred; - size_t counter, bytes, total, pageSize; - pthread_mutex_t mutex; - } PageInState; - PageInState state = { - deferred, 0, 0, 0, llvm::sys::Process::getPageSizeEstimate(), - pthread_mutex_t()}; - pthread_mutex_init(&state.mutex, NULL); - - pthread_t running[MaxReadThreads]; - if (nthreads > MaxReadThreads) - nthreads = MaxReadThreads; - - for (int t = 0; t < nthreads; t++) - pthread_create( - &running[t], nullptr, - [](void *ptr) -> void * { - PageInState &state = *(PageInState *)ptr; - while (true) { - pthread_mutex_lock(&state.mutex); - if (state.counter >= state.deferred.size()) { - pthread_mutex_unlock(&state.mutex); - return nullptr; - } - DeferredFile &file = state.deferred[state.counter]; - state.counter += 1; - pthread_mutex_unlock(&state.mutex); - - const char *page = file.buffer->getBuffer().data(), - *end = page + file.buffer->getBuffer().size(); - state.bytes += end - page; - - int t = 0; // Reference each page to load it into memory. - for (; page < end; page += state.pageSize) - t += *page; - state.total += t; // Avoids the loop being optimised out. - } - }, - &state); - - for (int t = 0; t < nthreads; t++) - pthread_join(running[t], nullptr); - - pthread_mutex_destroy(&state.mutex); -#endif -} - -void createFiles(const InputArgList &args) { +static void createFiles(const InputArgList &args) { TimeTraceScope timeScope("Load input files"); // This loop should be reserved for options whose exact ordering matters. // Other options should be handled via filtered() and/or getLastArg(). @@ -1311,8 +1321,7 @@ void createFiles(const InputArgList &args) { switch (opt.getID()) { case OPT_INPUT: - deferFile(rerootPath(arg->getValue()), LoadType::CommandLine, isLazy, - deferredFiles); + deferFile(rerootPath(arg->getValue()), isLazy, deferredFiles); break; case OPT_needed_library: if (auto *dylibFile = dyn_cast_or_null( @@ -1380,9 +1389,9 @@ void createFiles(const InputArgList &args) { } if (config->readThreads) { - multiThreadedPageIn(deferredFiles, config->readThreads); + multiThreadedPageIn(deferredFiles); for (auto &file : deferredFiles) - processFile(file.buffer, file.path, file.loadType, file.isLazy); + processFile(file.buffer, file.path, LoadType::CommandLine, file.isLazy); } } From a8eeead77a34e1072d8a1f7eabcc983388e303a4 Mon Sep 17 00:00:00 2001 From: John Holdsworth Date: Tue, 8 Jul 2025 14:46:19 +0200 Subject: [PATCH 4/5] Multi-thread i/o in background. --- lld/MachO/Driver.cpp | 104 ++++++++++++++++++++++++++++++------------- 1 file changed, 73 insertions(+), 31 deletions(-) diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index bacbe24fb4434..ba5ac7eff585a 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -290,27 +290,26 @@ typedef struct { const char *start; size_t size; } DeferredFile; +typedef std::vector DeferredFiles; + +#ifndef _WIN32 +typedef struct { + DeferredFiles deferred; + size_t counter, total, pageSize; + pthread_mutex_t mutex; +} PageInState; // Most input files have been mapped but not yet paged in. // This code forces the page-ins on multiple threads so // the process is not stalled waiting on disk buffer i/o. -static void multiThreadedPageIn(std::vector &deferred) { -#ifndef _WIN32 +static void multiThreadedPageInBackground(PageInState *state) { #define MaxReadThreads 200 - typedef struct { - std::vector &deferred; - size_t counter, total, pageSize; - pthread_mutex_t mutex; - } PageInState; - PageInState state = {deferred, 0, 0, - llvm::sys::Process::getPageSizeEstimate(), - pthread_mutex_t()}; static size_t totalBytes; pthread_t running[MaxReadThreads]; if (config->readThreads > MaxReadThreads) config->readThreads = MaxReadThreads; - pthread_mutex_init(&state.mutex, NULL); + pthread_mutex_init(&state->mutex, nullptr); for (int t = 0; t < config->readThreads; t++) pthread_create( @@ -336,20 +335,49 @@ static void multiThreadedPageIn(std::vector &deferred) { state.total += t; // Avoids the loop being optimised out. } }, - &state); + state); for (int t = 0; t < config->readThreads; t++) pthread_join(running[t], nullptr); - pthread_mutex_destroy(&state.mutex); + pthread_mutex_destroy(&state->mutex); if (getenv("LLD_MULTI_THREAD_PAGE")) - printf("multiThreadedPageIn %ld/%ld\n", totalBytes, deferred.size()); + printf("multiThreadedPageIn %ld/%ld\n", totalBytes, state->deferred.size()); +} +#endif + +static void multiThreadedPageIn(DeferredFiles deferred) { +#ifndef _WIN32 + static pthread_t running; + static pthread_mutex_t busy; + + if (running) + pthread_join(running, nullptr); + else + pthread_mutex_init(&busy, nullptr); + + PageInState *state = + new PageInState{deferred, 0, 0, llvm::sys::Process::getPageSizeEstimate(), + pthread_mutex_t()}; + + pthread_mutex_lock(&busy); + pthread_create( + &running, nullptr, + [](void *ptr) -> void * { + PageInState *state = (PageInState *)ptr; + multiThreadedPageInBackground(state); + pthread_mutex_unlock(&busy); + delete state; + return nullptr; + }, + state); #endif } static InputFile *processFile(std::optional buffer, - StringRef path, LoadType loadType, - bool isLazy = false, bool isExplicit = true, + DeferredFiles *archiveContents, StringRef path, + LoadType loadType, bool isLazy = false, + bool isExplicit = true, bool isBundleLoader = false, bool isForceHidden = false) { if (!buffer) @@ -431,7 +459,6 @@ static InputFile *processFile(std::optional buffer, // we already found that it contains an ObjC symbol. if (readFile(path)) { Error e = Error::success(); - std::vector deferredFiles; for (const object::Archive::Child &c : file->getArchive().children(e)) { Expected mb = c.getMemoryBufferRef(); if (!mb) { @@ -445,9 +472,10 @@ static InputFile *processFile(std::optional buffer, continue; } - deferredFiles.push_back({path, isLazy, std::nullopt, - mb->getBuffer().data(), - mb->getBuffer().size()}); + if (archiveContents) + archiveContents->push_back({path, isLazy, std::nullopt, + mb->getBuffer().data(), + mb->getBuffer().size()}); if (!hasObjCSection(*mb)) continue; if (Error e = file->fetch(c, "-ObjC")) @@ -457,11 +485,10 @@ static InputFile *processFile(std::optional buffer, if (e) error(toString(file) + ": Archive::children failed: " + toString(std::move(e))); - if (config->readThreads && deferredFiles.size() > 1) - multiThreadedPageIn(deferredFiles); } } - file->addLazySymbols(); + if (!archiveContents || archiveContents->empty()) + file->addLazySymbols(); loadedArchives[path] = ArchiveFileInfo{file, isCommandLineLoad}; newFile = file; break; @@ -516,18 +543,17 @@ static InputFile *addFile(StringRef path, LoadType loadType, bool isLazy = false, bool isExplicit = true, bool isBundleLoader = false, bool isForceHidden = false) { - return processFile(readFile(path), path, loadType, isLazy, isExplicit, - isBundleLoader, isForceHidden); + return processFile(readFile(path), nullptr, path, loadType, isLazy, + isExplicit, isBundleLoader, isForceHidden); } -static void deferFile(StringRef path, bool isLazy, - std::vector &deferred) { +static void deferFile(StringRef path, bool isLazy, DeferredFiles &deferred) { std::optional buffer = readFile(path); if (config->readThreads) deferred.push_back({path, isLazy, buffer, buffer->getBuffer().data(), buffer->getBuffer().size()}); else - processFile(buffer, path, LoadType::CommandLine, isLazy); + processFile(buffer, nullptr, path, LoadType::CommandLine, isLazy); } static std::vector missingAutolinkWarnings; @@ -654,7 +680,7 @@ void macho::resolveLCLinkerOptions() { } static void addFileList(StringRef path, bool isLazy, - std::vector &deferredFiles) { + DeferredFiles &deferredFiles) { std::optional buffer = readFile(path); if (!buffer) return; @@ -1312,7 +1338,7 @@ static void createFiles(const InputArgList &args) { bool isLazy = false; // If we've processed an opening --start-lib, without a matching --end-lib bool inLib = false; - std::vector deferredFiles; + DeferredFiles deferredFiles; for (const Arg *arg : args) { const Option &opt = arg->getOption(); @@ -1390,8 +1416,24 @@ static void createFiles(const InputArgList &args) { if (config->readThreads) { multiThreadedPageIn(deferredFiles); + + DeferredFiles archiveContents; + std::vector archives; for (auto &file : deferredFiles) - processFile(file.buffer, file.path, LoadType::CommandLine, file.isLazy); + if (ArchiveFile *archive = dyn_cast( + processFile(file.buffer, &archiveContents, file.path, + LoadType::CommandLine, file.isLazy))) + archives.push_back(archive); + + if (!archiveContents.empty()) { + multiThreadedPageIn(archiveContents); + for (auto *archive : archives) + archive->addLazySymbols(); + } + + // flush threads + deferredFiles.clear(); + multiThreadedPageIn(deferredFiles); } } From 55e26a80be5073f2b7761cc9515cebe16e43772c Mon Sep 17 00:00:00 2001 From: John Holdsworth Date: Wed, 9 Jul 2025 07:31:49 +0200 Subject: [PATCH 5/5] Response to first review. --- lld/MachO/Driver.cpp | 138 ++++++++++++++++++------------------------- lld/MachO/Options.td | 2 +- 2 files changed, 60 insertions(+), 80 deletions(-) diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index ba5ac7eff585a..4f3b562668ab1 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -47,6 +47,7 @@ #include "llvm/Support/Process.h" #include "llvm/Support/TarWriter.h" #include "llvm/Support/TargetSelect.h" +#include "llvm/Support/Threading.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/TargetParser/Host.h" #include "llvm/TextAPI/Architecture.h" @@ -283,95 +284,75 @@ static void saveThinArchiveToRepro(ArchiveFile const *file) { ": Archive::children failed: " + toString(std::move(e))); } -typedef struct { +class DeferredFile { +public: StringRef path; bool isLazy; std::optional buffer; const char *start; size_t size; -} DeferredFile; -typedef std::vector DeferredFiles; +}; +using DeferredFiles = std::vector; -#ifndef _WIN32 -typedef struct { +class PageInState { DeferredFiles deferred; - size_t counter, total, pageSize; - pthread_mutex_t mutex; -} PageInState; - -// Most input files have been mapped but not yet paged in. -// This code forces the page-ins on multiple threads so -// the process is not stalled waiting on disk buffer i/o. -static void multiThreadedPageInBackground(PageInState *state) { -#define MaxReadThreads 200 - static size_t totalBytes; - - pthread_t running[MaxReadThreads]; - if (config->readThreads > MaxReadThreads) - config->readThreads = MaxReadThreads; - pthread_mutex_init(&state->mutex, nullptr); - - for (int t = 0; t < config->readThreads; t++) - pthread_create( - &running[t], nullptr, - [](void *ptr) -> void * { - PageInState &state = *(PageInState *)ptr; - while (true) { - pthread_mutex_lock(&state.mutex); - if (state.counter >= state.deferred.size()) { - pthread_mutex_unlock(&state.mutex); - return nullptr; - } - DeferredFile &file = state.deferred[state.counter]; - state.counter += 1; - pthread_mutex_unlock(&state.mutex); - - const char *page = file.start, *end = page + file.size; - totalBytes += end - page; - - int t = 0; // Reference each page to load it into memory. - for (; page < end; page += state.pageSize) - t += *page; - state.total += t; // Avoids the loop being optimised out. - } - }, - state); + size_t counter = 0, total = 0, pageSize; + std::mutex mutex, *busy; + +public: + PageInState(DeferredFiles &deferred, std::mutex *busy) { + this->deferred = deferred; + this->busy = busy; + pageSize = llvm::sys::Process::getPageSizeEstimate(); + } + + // Most input files have been mapped but not yet paged in. + // This code forces the page-ins on multiple threads so + // the process is not stalled waiting on disk buffer i/o. + void multiThreadedPageInBackground() { + static size_t totalBytes; + + parallelFor(0, config->readThreads, [&](size_t I) { + while (true) { + mutex.lock(); + if (counter >= deferred.size()) { + mutex.unlock(); + return; + } + DeferredFile &file = deferred[counter]; + totalBytes += file.size; + counter += 1; + mutex.unlock(); + + int t = 0; // Reference each page to load it into memory. + for (const char *page = file.start, *end = page + file.size; page < end; + page += pageSize) + t += *page; + total += t; // Avoids the loop being optimised out. + } + }); - for (int t = 0; t < config->readThreads; t++) - pthread_join(running[t], nullptr); + if (getenv("LLD_MULTI_THREAD_PAGE")) + llvm::dbgs() << "multiThreadedPageIn " << totalBytes << "/" + << deferred.size() << "\n"; - pthread_mutex_destroy(&state->mutex); - if (getenv("LLD_MULTI_THREAD_PAGE")) - printf("multiThreadedPageIn %ld/%ld\n", totalBytes, state->deferred.size()); -} -#endif + busy->unlock(); + delete this; + } +}; static void multiThreadedPageIn(DeferredFiles deferred) { -#ifndef _WIN32 - static pthread_t running; - static pthread_mutex_t busy; + static std::thread *running; + static std::mutex busy; - if (running) - pthread_join(running, nullptr); - else - pthread_mutex_init(&busy, nullptr); - - PageInState *state = - new PageInState{deferred, 0, 0, llvm::sys::Process::getPageSizeEstimate(), - pthread_mutex_t()}; - - pthread_mutex_lock(&busy); - pthread_create( - &running, nullptr, - [](void *ptr) -> void * { - PageInState *state = (PageInState *)ptr; - multiThreadedPageInBackground(state); - pthread_mutex_unlock(&busy); - delete state; - return nullptr; - }, - state); -#endif + busy.lock(); + if (running) { + running->join(); + delete running; + } + + running = new std::thread(&PageInState::multiThreadedPageInBackground, + new PageInState(deferred, &busy)); } static InputFile *processFile(std::optional buffer, @@ -1432,8 +1413,7 @@ static void createFiles(const InputArgList &args) { } // flush threads - deferredFiles.clear(); - multiThreadedPageIn(deferredFiles); + multiThreadedPageIn(DeferredFiles()); } } diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td index 3dc98fccc1b7b..2e70695868fb6 100644 --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -397,7 +397,7 @@ def interposable : Flag<["-"], "interposable">, HelpText<"Indirects access to all exported symbols in an image">, Group; def read_threads : Joined<["--"], "read-threads=">, - HelpText<"Number of threads to use paging in files.">, + HelpText<"Number of threads to use if pro-actively paging in files.">, Group; def order_file : Separate<["-"], "order_file">, MetaVarName<"">,