Index: lld/ELF/Config.h =================================================================== --- lld/ELF/Config.h +++ lld/ELF/Config.h @@ -94,6 +94,8 @@ llvm::StringRef SoName; llvm::StringRef Sysroot; llvm::StringRef ThinLTOCacheDir; + llvm::StringRef ThinLTOIndexOnlyObjectsFile; + llvm::StringRef ThinLTOPrefixReplace; std::string Rpath; std::vector VersionDefinitions; std::vector AuxiliaryList; @@ -156,6 +158,7 @@ bool SysvHash = false; bool Target1Rel; bool Trace; + bool ThinLTOIndexOnly; bool UndefinedVersion; bool WarnBackrefs; bool WarnCommon; Index: lld/ELF/Driver.cpp =================================================================== --- lld/ELF/Driver.cpp +++ lld/ELF/Driver.cpp @@ -777,27 +777,37 @@ // Parse LTO plugin-related options for compatibility with gold. for (auto *Arg : Args.filtered(OPT_plugin_opt)) { StringRef S = Arg->getValue(); - if (S == "disable-verify") + if (S == "disable-verify") { Config->DisableVerify = true; - else if (S == "save-temps") + } else if (S == "save-temps") { Config->SaveTemps = true; - else if (S.startswith("O")) + } else if (S.startswith("O")) { Config->LTOO = parseInt(S.substr(1), Arg); - else if (S.startswith("lto-partitions=")) + } else if (S.startswith("lto-partitions=")) { Config->LTOPartitions = parseInt(S.substr(15), Arg); - else if (S.startswith("jobs=")) + } else if (S.startswith("jobs=")) { Config->ThinLTOJobs = parseInt(S.substr(5), Arg); - else if (S.startswith("mcpu=")) + } else if (S.startswith("mcpu=")) { parseClangOption(Saver.save("-" + S), Arg->getSpelling()); - else if (S == "new-pass-manager") + } else if (S == "new-pass-manager") { Config->LTONewPassManager = true; - else if (S == "debug-pass-manager") + } else if (S == "debug-pass-manager") { Config->LTODebugPassManager = true; - else if (S.startswith("sample-profile=")) - Config->LTOSampleProfile = S.substr(strlen("sample-profile=")); - else if (!S.startswith("/") && !S.startswith("-fresolution=") && - !S.startswith("-pass-through=") && !S.startswith("thinlto")) + } else if (S.startswith("sample-profile=")) { + Config->LTOSampleProfile = S.substr(15); + } else if (S == "thinlto-index-only") { + Config->ThinLTOIndexOnly = true; + } else if (S.startswith("thinlto-index-only=")) { + Config->ThinLTOIndexOnly = true; + Config->ThinLTOIndexOnlyObjectsFile = S.substr(19); + } else if (S.startswith("thinlto-prefix-replace=")) { + Config->ThinLTOPrefixReplace = S.substr(23); + if (!Config->ThinLTOPrefixReplace.contains(';')) + error("thinlto-prefix-replace expects 'old;new' format"); + } else if (!S.startswith("/") && !S.startswith("-fresolution=") && + !S.startswith("-pass-through=") && !S.startswith("thinlto")) { parseClangOption(S, Arg->getSpelling()); + } } // Parse -mllvm options. Index: lld/ELF/InputFiles.h =================================================================== --- lld/ELF/InputFiles.h +++ lld/ELF/InputFiles.h @@ -259,11 +259,11 @@ template void parse(); MemoryBufferRef getBuffer(); InputFile *fetch(); + bool AddedToLink = false; private: template void addElfSymbols(); - bool Seen = false; uint64_t OffsetInArchive; }; @@ -351,8 +351,13 @@ uint64_t OffsetInArchive = 0); InputFile *createSharedFile(MemoryBufferRef MB, StringRef DefaultSoName); +inline bool isBitcode(MemoryBufferRef MB) { + return identify_magic(MB.getBuffer()) == llvm::file_magic::bitcode; +} + extern std::vector BinaryFiles; extern std::vector BitcodeFiles; +extern std::vector LazyObjFiles; extern std::vector ObjectFiles; extern std::vector SharedFiles; Index: lld/ELF/InputFiles.cpp =================================================================== --- lld/ELF/InputFiles.cpp +++ lld/ELF/InputFiles.cpp @@ -42,6 +42,7 @@ uint32_t InputFile::NextGroupId; std::vector elf::BinaryFiles; std::vector elf::BitcodeFiles; +std::vector elf::LazyObjFiles; std::vector elf::ObjectFiles; std::vector elf::SharedFiles; @@ -1024,9 +1025,10 @@ // this causes a collision which result in only one of the objects being // taken into consideration at LTO time (which very likely causes undefined // symbols later in the link stage). - MemoryBufferRef MBRef(MB.getBuffer(), - Saver.save(ArchiveName + MB.getBufferIdentifier() + - utostr(OffsetInArchive))); + MemoryBufferRef MBRef( + MB.getBuffer(), + Saver.save(ArchiveName + MB.getBufferIdentifier() + + (ArchiveName.empty() ? "" : utostr(OffsetInArchive)))); Obj = CHECK(lto::InputFile::create(MBRef), this); Triple T(Obj->getTargetTriple()); @@ -1128,11 +1130,6 @@ Data.size(), 0, STB_GLOBAL, nullptr, nullptr); } -static bool isBitcode(MemoryBufferRef MB) { - using namespace sys::fs; - return identify_magic(MB.getBuffer()) == file_magic::bitcode; -} - InputFile *elf::createObjectFile(MemoryBufferRef MB, StringRef ArchiveName, uint64_t OffsetInArchive) { if (isBitcode(MB)) @@ -1168,9 +1165,9 @@ } MemoryBufferRef LazyObjFile::getBuffer() { - if (Seen) + if (AddedToLink) return MemoryBufferRef(); - Seen = true; + AddedToLink = true; return MB; } Index: lld/ELF/LTO.h =================================================================== --- lld/ELF/LTO.h +++ lld/ELF/LTO.h @@ -24,6 +24,7 @@ #include "lld/Common/LLVM.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" #include #include @@ -38,6 +39,7 @@ class BitcodeFile; class InputFile; +class LazyObjFile; class BitcodeCompiler { public: @@ -46,12 +48,14 @@ void add(BitcodeFile &F); std::vector compile(); + void addLazyObjFile(LazyObjFile *File); private: std::unique_ptr LTOObj; std::vector> Buff; std::vector> Files; llvm::DenseSet UsedStartStop; + std::unique_ptr LinkedObjects; }; } // namespace elf } // namespace lld Index: lld/ELF/LTO.cpp =================================================================== --- lld/ELF/LTO.cpp +++ lld/ELF/LTO.cpp @@ -20,6 +20,8 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/BinaryFormat/ELF.h" +#include "llvm/Bitcode/BitcodeReader.h" +#include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/IR/DiagnosticPrinter.h" #include "llvm/LTO/Caching.h" #include "llvm/LTO/Config.h" @@ -29,7 +31,6 @@ #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" -#include "llvm/Support/raw_ostream.h" #include #include #include @@ -66,7 +67,57 @@ [&](ErrorInfoBase &EIB) { error(EIB.message()); }); } -static std::unique_ptr createLTO() { +// With the ThinLTOIndexOnly option, only the thin link is performed, and will +// generate index files for the ThinLTO backends in a distributed build system. +// The distributed build system may expect that index files are created for all +// input bitcode objects provided to the linker for the thin link. However, +// index files will not normally be created for input bitcode objects that +// either aren't selected by the linker (i.e. in a static library and not +// needed), or because they don't have a summary. Therefore we need to create +// empty dummy index file outputs in those cases. +// If SkipModule is true then .thinlto.bc should contain just +// SkipModuleByDistributedBackend flag which requests distributed backend +// to skip the compilation of the corresponding module and produce an empty +// object file. +static void writeEmptyDistributedBuildOutputs(const std::string &ModulePath, + const std::string &OldPrefix, + const std::string &NewPrefix, + bool SkipModule) { + std::string NewModulePath = + lto::getThinLTOOutputFile(ModulePath, OldPrefix, NewPrefix); + std::error_code EC; + + raw_fd_ostream OS(NewModulePath + ".thinlto.bc", EC, + sys::fs::OpenFlags::F_None); + if (EC) + error("failed to write " + NewModulePath + ".thinlto.bc" + ": " + + EC.message()); + + if (SkipModule) { + ModuleSummaryIndex Index(false); + Index.setSkipModuleByDistributedBackend(); + WriteIndexToFile(Index, OS); + } +} + +// Creates and returns output stream with a list of object files for final +// linking of distributed ThinLTO. +static std::unique_ptr createLinkedObjectsFile() { + if (Config->ThinLTOIndexOnlyObjectsFile.empty()) + return nullptr; + std::error_code EC; + auto LinkedObjectsFile = llvm::make_unique( + Config->ThinLTOIndexOnlyObjectsFile, EC, sys::fs::OpenFlags::F_None); + if (EC) + error("cannot create " + Config->ThinLTOIndexOnlyObjectsFile + ": " + + EC.message()); + return LinkedObjectsFile; +} + +// Creates instance of LTO. +// LinkedObjectsFile is an output stream to write the list of object files for +// the final ThinLTO linking. Can be nullptr. +static std::unique_ptr createLTO(raw_fd_ostream *LinkedObjectsFile) { lto::Config Conf; // LLD supports the new relocations. @@ -105,6 +156,13 @@ if (Config->ThinLTOJobs != -1u) Backend = lto::createInProcessThinBackend(Config->ThinLTOJobs); + if (Config->ThinLTOIndexOnly) { + std::string OldPrefix, NewPrefix; + std::tie(OldPrefix, NewPrefix) = Config->ThinLTOPrefixReplace.split(';'); + Backend = lto::createWriteIndexesThinBackend(OldPrefix, NewPrefix, true, + LinkedObjectsFile, nullptr); + } + Conf.SampleProfile = Config->LTOSampleProfile; Conf.UseNewPM = Config->LTONewPassManager; Conf.DebugPassManager = Config->LTODebugPassManager; @@ -113,7 +171,9 @@ Config->LTOPartitions); } -BitcodeCompiler::BitcodeCompiler() : LTOObj(createLTO()) { +BitcodeCompiler::BitcodeCompiler() { + LinkedObjects = createLinkedObjectsFile(); + LTOObj = createLTO(LinkedObjects.get()); for (Symbol *Sym : Symtab->getSymbols()) { StringRef Name = Sym->getName(); for (StringRef Prefix : {"__start_", "__stop_"}) @@ -131,6 +191,13 @@ void BitcodeCompiler::add(BitcodeFile &F) { lto::InputFile &Obj = *F.Obj; + + std::string OldPrefix, NewPrefix; + std::tie(OldPrefix, NewPrefix) = Config->ThinLTOPrefixReplace.split(';'); + + // Create the empty files which, if indexed, will be overwritten later. + writeEmptyDistributedBuildOutputs(Obj.getName(), OldPrefix, NewPrefix, false); + unsigned SymNum = 0; std::vector Syms = F.getSymbols(); std::vector Resols(Syms.size()); @@ -188,6 +255,13 @@ Buff.resize(MaxTasks); Files.resize(MaxTasks); + // If LazyObjFile has not been added to link, emit empty index files + if (Config->ThinLTOIndexOnly) { + for (LazyObjFile *F : LazyObjFiles) + if (!F->AddedToLink && isBitcode(F->MB)) + addLazyObjFile(F); + } + // The --thinlto-cache-dir option specifies the path to a directory in which // to cache native object files for ThinLTO incremental builds. If a path was // specified, configure LTO to use it as the cache directory. @@ -222,9 +296,24 @@ Ret.push_back(Obj); } + // ThinLTO with index only option is required to generate only the index + // files. After that, we exit from linker and ThinLTO backend runs in a + // distributed environment. + if (Config->ThinLTOIndexOnly) + exit(0); + for (std::unique_ptr &File : Files) if (File) Ret.push_back(createObjectFile(*File)); return Ret; } + +// For lazy object files not added to link, adds empty index files +void BitcodeCompiler::addLazyObjFile(LazyObjFile *File) { + StringRef Identifier = File->getBuffer().getBufferIdentifier(); + std::string OldPrefix, NewPrefix; + std::tie(OldPrefix, NewPrefix) = Config->ThinLTOPrefixReplace.split(';'); + writeEmptyDistributedBuildOutputs(Identifier, OldPrefix, NewPrefix, + /* SkipModule */ true); +} Index: lld/ELF/Options.td =================================================================== --- lld/ELF/Options.td +++ lld/ELF/Options.td @@ -425,6 +425,10 @@ defm thinlto_cache_policy: Eq<"thinlto-cache-policy">, HelpText<"Pruning policy for the ThinLTO cache">; def thinlto_jobs: J<"thinlto-jobs=">, HelpText<"Number of ThinLTO jobs">; +def thinlto_index_only: J<"thinlto-index-only=">, + HelpText<"Write individual backend index files for LTO ">; +def thinlto_prefix_replace : F<"thinlto-prefix-replace">, + HelpText<"Controls location of distributed backend files">; // Ignore LTO plugin-related options. // clang -flto passes -plugin and -plugin-opt to the linker. This is required Index: lld/ELF/SymbolTable.cpp =================================================================== --- lld/ELF/SymbolTable.cpp +++ lld/ELF/SymbolTable.cpp @@ -82,6 +82,7 @@ // Lazy object file if (auto *F = dyn_cast(File)) { + LazyObjFiles.push_back(F); F->parse(); return; } Index: lld/test/ELF/lto/Inputs/thinlto_empty.ll =================================================================== --- /dev/null +++ lld/test/ELF/lto/Inputs/thinlto_empty.ll @@ -0,0 +1,2 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" Index: lld/test/ELF/lto/thinlto.ll =================================================================== --- lld/test/ELF/lto/thinlto.ll +++ lld/test/ELF/lto/thinlto.ll @@ -1,7 +1,37 @@ ; REQUIRES: x86 + +; First ensure that the ThinLTO handling in lld handles +; bitcode without summary sections gracefully and generates index file. +; RUN: llvm-as %s -o %t.o +; RUN: llvm-as %p/Inputs/thinlto.ll -o %t2.o +; RUN: ld.lld -m elf_x86_64 --plugin-opt=thinlto-index-only -shared %t.o %t2.o -o %t3 +; RUN: ls %t2.o.thinlto.bc +; RUN: not test -e %t3 +; RUN: ld.lld -m elf_x86_64 -shared %t.o %t2.o -o %t4 +; RUN: llvm-nm %t4 | FileCheck %s --check-prefix=NM + ; Basic ThinLTO tests. ; RUN: opt -module-summary %s -o %t.o ; RUN: opt -module-summary %p/Inputs/thinlto.ll -o %t2.o +; RUN: opt -module-summary %p/Inputs/thinlto_empty.ll -o %t4.o + +; Ensure lld generates an index and not a binary if requested. +; RUN: ld.lld -m elf_x86_64 --plugin-opt=thinlto-index-only -shared %t.o %t2.o -o %t3 +; RUN: llvm-bcanalyzer -dump %t.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND1 +; RUN: llvm-bcanalyzer -dump %t2.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND2 +; RUN: not test -e %t3 + +; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib +; RUN: rm -f %t2.o.thinlto.bc +; RUN: ld.lld -m elf_x86_64 --plugin-opt=thinlto-index-only -shared %t.o %t4.o --start-lib %t2.o --end-lib -o %t5 +; RUN: ls %t2.o.thinlto.bc + +; Ensure lld generates error if unable to write to index file +; RUN: rm -f %t4.o.thinlto.bc +; RUN: touch %t4.o.thinlto.bc +; RUN: chmod 400 %t4.o.thinlto.bc +; RUN: ld.lld -m elf_x86_64 --plugin-opt=thinlto-index-only -shared %t.o %t4.o -o %t5 2>&1 | FileCheck %s --check-prefix=ERR +; ERR: failed to write {{.*}}4.o.thinlto.bc: Permission denied ; First force single-threaded mode ; RUN: rm -f %t.lto.o %t1.lto.o @@ -15,16 +45,43 @@ ; RUN: llvm-nm %t21.lto.o | FileCheck %s --check-prefix=NM1 ; RUN: llvm-nm %t22.lto.o | FileCheck %s --check-prefix=NM2 -; NM1: T f -; NM1-NOT: U g - -; NM2: T g - ; Then check without --thinlto-jobs (which currently default to hardware_concurrency) ; We just check that we don't crash or fail (as it's not sure which tests are ; stable on the final output file itself. ; RUN: ld.lld -shared %t.o %t2.o -o %t2 +; NM: T f +; NM1: T f +; NM1-NOT: U g +; NM2: T g + +; The backend index for this module contains summaries from itself and +; Inputs/thinlto.ll, as it imports from the latter. +; BACKEND1: &1 | FileCheck %s --check-prefix=ERR +; ERR: thinlto-prefix-replace expects 'old;new' format + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +define void @f() { +entry: + ret void +}