diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -221,6 +221,7 @@ bool target1Rel; bool trace; bool thinLTOEmitImportsFiles; + bool thinLTOEmitIndexFiles; bool thinLTOIndexOnly; bool timeTraceEnabled; bool tocOptimize; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1175,6 +1175,9 @@ parseCachePruningPolicy(args.getLastArgValue(OPT_thinlto_cache_policy)), "--thinlto-cache-policy: invalid cache policy"); config->thinLTOEmitImportsFiles = args.hasArg(OPT_thinlto_emit_imports_files); + config->thinLTOEmitIndexFiles = args.hasArg(OPT_thinlto_emit_index_files) || + args.hasArg(OPT_thinlto_index_only) || + args.hasArg(OPT_thinlto_index_only_eq); config->thinLTOIndexOnly = args.hasArg(OPT_thinlto_index_only) || args.hasArg(OPT_thinlto_index_only_eq); config->thinLTOIndexOnlyArg = args.getLastArgValue(OPT_thinlto_index_only_eq); @@ -1182,6 +1185,14 @@ getOldNewOptions(args, OPT_thinlto_object_suffix_replace_eq); config->thinLTOPrefixReplace = getOldNewOptions(args, OPT_thinlto_prefix_replace_eq); + if (config->thinLTOEmitIndexFiles && !config->thinLTOIndexOnly) { + if (args.hasArg(OPT_thinlto_object_suffix_replace_eq)) + error("--thinlto-object-suffix-replace is not supported with " + "--thinlto-emit-index-files"); + else if (args.hasArg(OPT_thinlto_prefix_replace_eq)) + error("--thinlto-prefix-replace is not supported with " + "--thinlto-emit-index-files"); + } config->thinLTOModulesToCompile = args::getStrings(args, OPT_thinlto_single_module_eq); config->timeTraceEnabled = args.hasArg(OPT_time_trace); diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -191,15 +191,17 @@ // Initialize ltoObj. lto::ThinBackend backend; + auto onIndexWrite = [&](StringRef s) { thinIndices.erase(s); }; if (config->thinLTOIndexOnly) { - auto onIndexWrite = [&](StringRef s) { thinIndices.erase(s); }; backend = lto::createWriteIndexesThinBackend( std::string(config->thinLTOPrefixReplace.first), std::string(config->thinLTOPrefixReplace.second), config->thinLTOEmitImportsFiles, indexFile.get(), onIndexWrite); } else { backend = lto::createInProcessThinBackend( - llvm::heavyweight_hardware_concurrency(config->thinLTOJobs)); + llvm::heavyweight_hardware_concurrency(config->thinLTOJobs), + onIndexWrite, config->thinLTOEmitIndexFiles, + config->thinLTOEmitImportsFiles); } ltoObj = std::make_unique(createConfig(), backend, @@ -224,7 +226,7 @@ lto::InputFile &obj = *f.obj; bool isExec = !config->shared && !config->relocatable; - if (config->thinLTOIndexOnly) + if (config->thinLTOEmitIndexFiles) thinIndices.insert(obj.getName()); ArrayRef syms = f.getSymbols(); @@ -339,9 +341,10 @@ } } - if (config->thinLTOIndexOnly) { + if (config->thinLTOEmitIndexFiles) thinLTOCreateEmptyIndexFiles(); + if (config->thinLTOIndexOnly) { if (!config->ltoObjPath.empty()) saveBuffer(buf[0], config->ltoObjPath); diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -596,6 +596,7 @@ HelpText<"Path to ThinLTO cached object file directory">; defm thinlto_cache_policy: EEq<"thinlto-cache-policy", "Pruning policy for the ThinLTO cache">; def thinlto_emit_imports_files: FF<"thinlto-emit-imports-files">; +def thinlto_emit_index_files: FF<"thinlto-emit-index-files">; def thinlto_index_only: FF<"thinlto-index-only">; def thinlto_index_only_eq: JJ<"thinlto-index-only=">; def thinlto_jobs: JJ<"thinlto-jobs=">, diff --git a/lld/test/ELF/lto/thinlto-emit-index.ll b/lld/test/ELF/lto/thinlto-emit-index.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/thinlto-emit-index.ll @@ -0,0 +1,107 @@ +; REQUIRES: x86 + +;; Mostly copied/updated from thinlto-index-only.ll +;; First ensure that the ThinLTO handling in lld handles +;; bitcode without summary sections gracefully and generates index file. +; RUN: rm -rf %t.dir && mkdir %t.dir && cd %t.dir +; RUN: llvm-as %s -o 1.o +; RUN: llvm-as %p/Inputs/thinlto.ll -o 2.o +; RUN: ld.lld --thinlto-emit-index-files -shared 1.o 2.o -o 3 +; RUN: ls 2.o.thinlto.bc +; RUN: ls 3 +; RUN: ld.lld -shared 1.o 2.o -o 3 +; RUN: llvm-nm 3 | FileCheck %s --check-prefix=NM + +;; Basic ThinLTO tests. +; RUN: opt -module-summary %s -o 1.o +; RUN: opt -module-summary %p/Inputs/thinlto.ll -o 2.o +; RUN: opt -module-summary %p/Inputs/thinlto_empty.ll -o 3.o + +;; Ensure lld generates an index and also a binary if requested. +; RUN: ld.lld --thinlto-emit-index-files -shared 1.o 2.o -o 4 +; RUN: llvm-bcanalyzer -dump 1.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND1 +; RUN: llvm-bcanalyzer -dump 2.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND2 +; RUN: ls 4 + +;; Ensure lld generates an index and not a binary if both emit-index and index-only are present. +; RUN: ld.lld --thinlto-emit-index-files --thinlto-index-only -shared 1.o 2.o -o 5 +; RUN: not ls 5 + +;; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib +; RUN: rm -f 2.o.thinlto.bc +; RUN: ld.lld --thinlto-emit-index-files -shared 1.o 3.o --start-lib 2.o --end-lib -o 6 +; RUN: llvm-dis < 2.o.thinlto.bc | grep -q '\^0 = module:' +; RUN: ls 6 + +;; Test that LLD generates an empty index even for lazy object file that is not added to link. +;; Test that LLD also generates empty imports file with the --thinlto-emit-imports-files option. +; RUN: rm -f 1.o.thinlto.bc 1.o.imports +; RUN: ld.lld --thinlto-emit-index-files -shared 2.o --start-lib 1.o --end-lib \ +; RUN: --thinlto-emit-imports-files -o 7 +; RUN: ls 7 +; RUN: ls 1.o.thinlto.bc +; RUN: ls 1.o.imports + +;; Ensure LLD generates an empty index for each bitcode file even if all bitcode files are lazy. +; RUN: rm -f 1.o.thinlto.bc +; RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux-gnu /dev/null -o dummy.o +; RUN: ld.lld --thinlto-emit-index-files -shared dummy.o --start-lib 1.o --end-lib -o 8 +; RUN: ls 8 +; RUN: ls 1.o.thinlto.bc + +;; Test that LLD errors out when run with suffix replacement, or prefix replacement +; RUN: not ld.lld --thinlto-emit-index-files -shared 2.o --start-lib 1.o --end-lib \ +; RUN: --thinlto-prefix-replace="abc;xyz" 2>&1 | FileCheck %s --check-prefix=ERR1 +; ERR1: --thinlto-prefix-replace is not supported with --thinlto-emit-index-files + +; RUN: not ld.lld --thinlto-emit-index-files -shared 2.o --start-lib 1.o --end-lib \ +; RUN: --thinlto-object-suffix-replace="abc;xyz" 2>&1 | FileCheck %s --check-prefix=ERR2 +; ERR2: --thinlto-object-suffix-replace is not supported with --thinlto-emit-index-files + +;; But not when passed with index only as well +; RUN: ld.lld --thinlto-emit-index-files -shared 2.o --start-lib 1.o --end-lib \ +; RUN: --thinlto-prefix-replace="abc;xyz" --thinlto-index-only + +; RUN: ld.lld --thinlto-emit-index-files -shared 2.o --start-lib 1.o --end-lib \ +; RUN: --thinlto-object-suffix-replace="abc;xyz" --thinlto-index-only + +; NM: T f + +;; The backend index for this module contains summaries from itself and +;; Inputs/thinlto.ll, as it imports from the latter. +; BACKEND1: +; BACKEND2-NEXT: ; +ThinBackend createInProcessThinBackend(ThreadPoolStrategy Parallelism, + IndexWriteCallback OnWrite = nullptr, + bool ShouldEmitIndexFiles = false, + bool ShouldEmitImportsFiles = false); /// This ThinBackend writes individual module indexes to files, instead of /// running the individual backend jobs. This backend is for distributed builds @@ -212,7 +222,6 @@ /// the final ThinLTO linking. Can be nullptr. /// OnWrite is callback which receives module identifier and notifies LTO user /// that index file for the module (and optionally imports file) was created. -using IndexWriteCallback = std::function; ThinBackend createWriteIndexesThinBackend(std::string OldPrefix, std::string NewPrefix, bool ShouldEmitImportsFiles, diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp --- a/llvm/lib/LTO/LTO.cpp +++ b/llvm/lib/LTO/LTO.cpp @@ -1161,12 +1161,16 @@ const Config &Conf; ModuleSummaryIndex &CombinedIndex; const StringMap &ModuleToDefinedGVSummaries; + lto::IndexWriteCallback OnWrite; + bool ShouldEmitImportsFiles; public: ThinBackendProc(const Config &Conf, ModuleSummaryIndex &CombinedIndex, - const StringMap &ModuleToDefinedGVSummaries) + const StringMap &ModuleToDefinedGVSummaries, + lto::IndexWriteCallback OnWrite, bool ShouldEmitImportsFiles) : Conf(Conf), CombinedIndex(CombinedIndex), - ModuleToDefinedGVSummaries(ModuleToDefinedGVSummaries) {} + ModuleToDefinedGVSummaries(ModuleToDefinedGVSummaries), + OnWrite(OnWrite), ShouldEmitImportsFiles(ShouldEmitImportsFiles) {} virtual ~ThinBackendProc() = default; virtual Error start( @@ -1177,6 +1181,30 @@ MapVector &ModuleMap) = 0; virtual Error wait() = 0; virtual unsigned getThreadCount() = 0; + + // Write sharded indices and (optionally) imports to disk + Error emitFiles(const FunctionImporter::ImportMapTy &ImportList, + llvm::StringRef ModulePath, + const std::string &NewModulePath) { + std::map ModuleToSummariesForIndex; + std::error_code EC; + gatherImportedSummariesForModule(ModulePath, ModuleToDefinedGVSummaries, + ImportList, ModuleToSummariesForIndex); + + raw_fd_ostream OS(NewModulePath + ".thinlto.bc", EC, + sys::fs::OpenFlags::OF_None); + if (EC) + return errorCodeToError(EC); + writeIndexToFile(CombinedIndex, OS, &ModuleToSummariesForIndex); + + if (ShouldEmitImportsFiles) { + EC = EmitImportsFiles(ModulePath, NewModulePath + ".imports", + ModuleToSummariesForIndex); + if (EC) + return errorCodeToError(EC); + } + return Error::success(); + } }; namespace { @@ -1190,15 +1218,19 @@ Optional Err; std::mutex ErrMu; + bool ShouldEmitIndexFiles; + public: InProcessThinBackend( const Config &Conf, ModuleSummaryIndex &CombinedIndex, ThreadPoolStrategy ThinLTOParallelism, const StringMap &ModuleToDefinedGVSummaries, - AddStreamFn AddStream, FileCache Cache) - : ThinBackendProc(Conf, CombinedIndex, ModuleToDefinedGVSummaries), + AddStreamFn AddStream, FileCache Cache, lto::IndexWriteCallback OnWrite, + bool ShouldEmitIndexFiles, bool ShouldEmitImportsFiles) + : ThinBackendProc(Conf, CombinedIndex, ModuleToDefinedGVSummaries, + OnWrite, ShouldEmitImportsFiles), BackendThreadPool(ThinLTOParallelism), AddStream(std::move(AddStream)), - Cache(std::move(Cache)) { + Cache(std::move(Cache)), ShouldEmitIndexFiles(ShouldEmitIndexFiles) { for (auto &Name : CombinedIndex.cfiFunctionDefs()) CfiFunctionDefs.insert( GlobalValue::getGUID(GlobalValue::dropLLVMManglingEscape(Name))); @@ -1227,6 +1259,11 @@ auto ModuleID = BM.getModuleIdentifier(); + if (ShouldEmitIndexFiles) { + if (auto E = emitFiles(ImportList, ModuleID, ModuleID.str())) + return E; + } + if (!Cache || !CombinedIndex.modulePaths().count(ModuleID) || all_of(CombinedIndex.getModuleHash(ModuleID), [](uint32_t V) { return V == 0; })) @@ -1285,6 +1322,9 @@ }, BM, std::ref(CombinedIndex), std::ref(ImportList), std::ref(ExportList), std::ref(ResolvedODR), std::ref(DefinedGlobals), std::ref(ModuleMap)); + + if (OnWrite) + OnWrite(std::string(ModulePath)); return Error::success(); } @@ -1302,13 +1342,16 @@ }; } // end anonymous namespace -ThinBackend lto::createInProcessThinBackend(ThreadPoolStrategy Parallelism) { +ThinBackend lto::createInProcessThinBackend(ThreadPoolStrategy Parallelism, + lto::IndexWriteCallback OnWrite, + bool ShouldEmitIndexFiles, + bool ShouldEmitImportsFiles) { return [=](const Config &Conf, ModuleSummaryIndex &CombinedIndex, const StringMap &ModuleToDefinedGVSummaries, AddStreamFn AddStream, FileCache Cache) { return std::make_unique( Conf, CombinedIndex, Parallelism, ModuleToDefinedGVSummaries, AddStream, - Cache); + Cache, OnWrite, ShouldEmitIndexFiles, ShouldEmitImportsFiles); }; } @@ -1335,9 +1378,7 @@ namespace { class WriteIndexesThinBackend : public ThinBackendProc { std::string OldPrefix, NewPrefix; - bool ShouldEmitImportsFiles; raw_fd_ostream *LinkedObjectsFile; - lto::IndexWriteCallback OnWrite; public: WriteIndexesThinBackend( @@ -1345,10 +1386,10 @@ const StringMap &ModuleToDefinedGVSummaries, std::string OldPrefix, std::string NewPrefix, bool ShouldEmitImportsFiles, raw_fd_ostream *LinkedObjectsFile, lto::IndexWriteCallback OnWrite) - : ThinBackendProc(Conf, CombinedIndex, ModuleToDefinedGVSummaries), + : ThinBackendProc(Conf, CombinedIndex, ModuleToDefinedGVSummaries, + OnWrite, ShouldEmitImportsFiles), OldPrefix(OldPrefix), NewPrefix(NewPrefix), - ShouldEmitImportsFiles(ShouldEmitImportsFiles), - LinkedObjectsFile(LinkedObjectsFile), OnWrite(OnWrite) {} + LinkedObjectsFile(LinkedObjectsFile) {} Error start( unsigned Task, BitcodeModule BM, @@ -1363,23 +1404,8 @@ if (LinkedObjectsFile) *LinkedObjectsFile << NewModulePath << '\n'; - std::map ModuleToSummariesForIndex; - gatherImportedSummariesForModule(ModulePath, ModuleToDefinedGVSummaries, - ImportList, ModuleToSummariesForIndex); - - std::error_code EC; - raw_fd_ostream OS(NewModulePath + ".thinlto.bc", EC, - sys::fs::OpenFlags::OF_None); - if (EC) - return errorCodeToError(EC); - writeIndexToFile(CombinedIndex, OS, &ModuleToSummariesForIndex); - - if (ShouldEmitImportsFiles) { - EC = EmitImportsFiles(ModulePath, NewModulePath + ".imports", - ModuleToSummariesForIndex); - if (EC) - return errorCodeToError(EC); - } + if (auto E = emitFiles(ImportList, ModulePath, NewModulePath)) + return E; if (OnWrite) OnWrite(std::string(ModulePath));