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..4f3b562668ab1 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -44,8 +44,10 @@ #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/Threading.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/TargetParser/Host.h" #include "llvm/TextAPI/Architecture.h" @@ -282,11 +284,83 @@ 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); +class DeferredFile { +public: + StringRef path; + bool isLazy; + std::optional buffer; + const char *start; + size_t size; +}; +using DeferredFiles = std::vector; + +class PageInState { + DeferredFiles deferred; + 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. + } + }); + + if (getenv("LLD_MULTI_THREAD_PAGE")) + llvm::dbgs() << "multiThreadedPageIn " << totalBytes << "/" + << deferred.size() << "\n"; + + busy->unlock(); + delete this; + } +}; + +static void multiThreadedPageIn(DeferredFiles deferred) { + static std::thread *running; + static std::mutex busy; + + busy.lock(); + if (running) { + running->join(); + delete running; + } + + running = new std::thread(&PageInState::multiThreadedPageInBackground, + new PageInState(deferred, &busy)); +} + +static InputFile *processFile(std::optional buffer, + DeferredFiles *archiveContents, StringRef path, + LoadType loadType, bool isLazy = false, + bool isExplicit = true, + bool isBundleLoader = false, + bool isForceHidden = false) { if (!buffer) return nullptr; MemoryBufferRef mbref = *buffer; @@ -379,6 +453,10 @@ static InputFile *addFile(StringRef path, LoadType loadType, continue; } + 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")) @@ -390,7 +468,8 @@ static InputFile *addFile(StringRef path, LoadType loadType, ": Archive::children failed: " + toString(std::move(e))); } } - file->addLazySymbols(); + if (!archiveContents || archiveContents->empty()) + file->addLazySymbols(); loadedArchives[path] = ArchiveFileInfo{file, isCommandLineLoad}; newFile = file; break; @@ -441,6 +520,23 @@ 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 processFile(readFile(path), nullptr, path, loadType, isLazy, + isExplicit, isBundleLoader, isForceHidden); +} + +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, nullptr, path, LoadType::CommandLine, isLazy); +} + static std::vector missingAutolinkWarnings; static void addLibrary(StringRef name, bool isNeeded, bool isWeak, bool isReexport, bool isHidden, bool isExplicit, @@ -564,13 +660,14 @@ void macho::resolveLCLinkerOptions() { } } -static void addFileList(StringRef path, bool isLazy) { +static void addFileList(StringRef path, bool isLazy, + DeferredFiles &deferredFiles) { std::optional buffer = readFile(path); if (!buffer) return; MemoryBufferRef mbref = *buffer; for (StringRef path : args::getLines(mbref)) - addFile(rerootPath(path), LoadType::CommandLine, isLazy); + deferFile(rerootPath(path), isLazy, deferredFiles); } // We expect sub-library names of the form "libfoo", which will match a dylib @@ -1222,6 +1319,8 @@ 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; + DeferredFiles deferredFiles; + for (const Arg *arg : args) { const Option &opt = arg->getOption(); warnIfDeprecatedOption(opt); @@ -1229,7 +1328,7 @@ static void createFiles(const InputArgList &args) { switch (opt.getID()) { case OPT_INPUT: - addFile(rerootPath(arg->getValue()), LoadType::CommandLine, isLazy); + deferFile(rerootPath(arg->getValue()), isLazy, deferredFiles); break; case OPT_needed_library: if (auto *dylibFile = dyn_cast_or_null( @@ -1249,7 +1348,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 +1394,27 @@ static void createFiles(const InputArgList &args) { break; } } + + if (config->readThreads) { + multiThreadedPageIn(deferredFiles); + + DeferredFiles archiveContents; + std::vector archives; + for (auto &file : deferredFiles) + 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 + multiThreadedPageIn(DeferredFiles()); + } } static void gatherInputSections() { @@ -1687,6 +1807,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..2e70695868fb6 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 if pro-actively paging in files.">, + Group; def order_file : Separate<["-"], "order_file">, MetaVarName<"">, HelpText<"Layout functions and data according to specification in ">,