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,17 @@ getOldNewOptions(args, OPT_thinlto_object_suffix_replace_eq); config->thinLTOPrefixReplace = getOldNewOptions(args, OPT_thinlto_prefix_replace_eq); + if (config->thinLTOEmitIndexFiles && !config->thinLTOIndexOnly) { + if (config->thinLTOEmitImportsFiles) + error("--thinlto-emit-imports-files is not supported with " + "--thinlto-emit-index-files"); + else 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,16 @@ // 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); } ltoObj = std::make_unique(createConfig(), backend, @@ -224,7 +225,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 +340,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,118 @@ +; REQUIRES: x86 + +; Mostly copied from thinlto-index-only +; First ensure that the ThinLTO handling in lld handles +; bitcode without summary sections gracefully and generates index file. +; RUN: llvm-as %s -o %t1.o +; RUN: llvm-as %p/Inputs/thinlto.ll -o %t2.o +; RUN: rm -f %t3 +; RUN: ld.lld --thinlto-emit-index-files -shared %t1.o %t2.o -o %t3 +; RUN: ls %t2.o.thinlto.bc +; RUN: test -e %t3 +; RUN: ld.lld -shared %t1.o %t2.o -o %t3 +; RUN: llvm-nm %t3 | FileCheck %s --check-prefix=NM + +; Basic ThinLTO tests. +; RUN: opt -module-summary %s -o %t1.o +; RUN: opt -module-summary %p/Inputs/thinlto.ll -o %t2.o +; RUN: opt -module-summary %p/Inputs/thinlto_empty.ll -o %t3.o + +; Ensure lld generates an index and also a binary if requested. +; RUN: rm -f %t4 +; RUN: ld.lld --thinlto-emit-index-files -shared %t1.o %t2.o -o %t4 +; RUN: llvm-bcanalyzer -dump %t1.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND1 +; RUN: llvm-bcanalyzer -dump %t2.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND2 +; RUN: test -e %t4 + +; Ensure lld generates an index and not a binary if both emit-index and index-only are present. +; RUN: rm -f %t4 +; RUN: ld.lld --thinlto-emit-index-files --thinlto-index-only -shared %t1.o %t2.o -o %t4 +; RUN: not test -e %t4 + +; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib +; RUN: rm -f %t2.o.thinlto.bc %t4 +; RUN: ld.lld --thinlto-emit-index-files -shared %t1.o %t3.o --start-lib %t2.o --end-lib -o %t4 +; RUN: llvm-dis < %t2.o.thinlto.bc | grep -q '\^0 = module:' +; RUN: test -e %t4 + +; Test that LLD generates an empty index even for lazy object file that is not added to link. +; Test LLD generates empty imports file either because of thinlto-emit-imports-files option. +; RUN: rm -f %t1.o.thinlto.bc +; RUN: rm -f %t1.o.imports +; RUN: rm -f %t3 +; RUN: ld.lld --thinlto-emit-index-files -shared %t2.o --start-lib %t1.o --end-lib \ +; RUN: -o %t3 +; RUN: test -e %t3 +; RUN: ls %t1.o.thinlto.bc + +; Ensure LLD generates an empty index for each bitcode file even if all bitcode files are lazy. +; RUN: rm -f %t1.o.thinlto.bc %t4 +; RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux-gnu /dev/null -o %tdummy.o +; RUN: ld.lld --thinlto-emit-index-files -shared %tdummy.o --start-lib %t1.o --end-lib -o %t4 +; RUN: test -e %t4 +; RUN: ls %t1.o.thinlto.bc + +; Test that LLD errors out when run with imports, suffix replacement, or prefix replacement +; RUN: not ld.lld --thinlto-emit-index-files -shared %t2.o --start-lib %t1.o --end-lib \ +; RUN: --thinlto-emit-imports-files 2>&1 | FileCheck %s --check-prefix=ERR1 +; ERR1: --thinlto-emit-imports-files is not supported with --thinlto-emit-index-files + +; RUN: not ld.lld --thinlto-emit-index-files -shared %t2.o --start-lib %t1.o --end-lib \ +; RUN: --thinlto-prefix-replace="abc;xyz" 2>&1 | FileCheck %s --check-prefix=ERR2 +; ERR2: --thinlto-prefix-replace is not supported with --thinlto-emit-index-files + +; RUN: not ld.lld --thinlto-emit-index-files -shared %t2.o --start-lib %t1.o --end-lib \ +; RUN: --thinlto-object-suffix-replace="abc;xyz" 2>&1 | FileCheck %s --check-prefix=ERR3 +; ERR3: --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 %t2.o --start-lib %t1.o --end-lib \ +; RUN: --thinlto-emit-imports-files --thinlto-index-only + +; RUN: ld.lld --thinlto-emit-index-files -shared %t2.o --start-lib %t1.o --end-lib \ +; RUN: --thinlto-prefix-replace="abc;xyz" --thinlto-index-only + +; RUN: ld.lld --thinlto-emit-index-files -shared %t2.o --start-lib %t1.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); /// This ThinBackend writes individual module indexes to files, instead of /// running the individual backend jobs. This backend is for distributed builds @@ -212,7 +215,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 @@ -1160,12 +1160,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( @@ -1176,6 +1180,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 { @@ -1189,15 +1217,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) + : ThinBackendProc(Conf, CombinedIndex, ModuleToDefinedGVSummaries, + OnWrite, /*ShouldEmitImportsFiles*/ false), 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))); @@ -1226,6 +1258,10 @@ 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; })) @@ -1284,6 +1320,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(); } @@ -1301,13 +1340,15 @@ }; } // end anonymous namespace -ThinBackend lto::createInProcessThinBackend(ThreadPoolStrategy Parallelism) { +ThinBackend lto::createInProcessThinBackend(ThreadPoolStrategy Parallelism, + lto::IndexWriteCallback OnWrite, + bool ShouldEmitIndexFiles) { 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); }; } @@ -1334,9 +1375,7 @@ namespace { class WriteIndexesThinBackend : public ThinBackendProc { std::string OldPrefix, NewPrefix; - bool ShouldEmitImportsFiles; raw_fd_ostream *LinkedObjectsFile; - lto::IndexWriteCallback OnWrite; public: WriteIndexesThinBackend( @@ -1344,10 +1383,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, @@ -1362,23 +1401,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));