Index: include/llvm/LTO/LTO.h =================================================================== --- include/llvm/LTO/LTO.h +++ include/llvm/LTO/LTO.h @@ -85,6 +85,13 @@ namespace lto { +/// Given the original \p Path to an output file, replace any path +/// prefix matching \p OldPrefix with \p NewPrefix. Also, create the +/// resulting directory if it does not yet exist. +std::string getThinLTOOutputFile(const std::string &Path, + const std::string &OldPrefix, + const std::string &NewPrefix); + class LTO; struct SymbolResolution; class ThinBackendProc; Index: lib/Bitcode/Reader/BitcodeReader.cpp =================================================================== --- lib/Bitcode/Reader/BitcodeReader.cpp +++ lib/Bitcode/Reader/BitcodeReader.cpp @@ -6097,13 +6097,18 @@ return error("Invalid record"); break; case bitc::GLOBALVAL_SUMMARY_BLOCK_ID: - assert(VSTOffset > 0 && "Expected non-zero VST offset"); assert(!SeenValueSymbolTable && "Already read VST when parsing summary block?"); - if (std::error_code EC = - parseValueSymbolTable(VSTOffset, ValueIdToLinkageMap)) - return EC; - SeenValueSymbolTable = true; + // We might not have a VST if there were no values in the + // summary. An empty summary block generated when we are + // performing ThinLTO compiles so we don't later invoke + // the regular LTO process on them. + if (VSTOffset > 0) { + if (std::error_code EC = + parseValueSymbolTable(VSTOffset, ValueIdToLinkageMap)) + return EC; + SeenValueSymbolTable = true; + } SeenGlobalValSummary = true; if (std::error_code EC = parseEntireSummary()) return EC; Index: lib/Bitcode/Writer/BitcodeWriter.cpp =================================================================== --- lib/Bitcode/Writer/BitcodeWriter.cpp +++ lib/Bitcode/Writer/BitcodeWriter.cpp @@ -3341,13 +3341,15 @@ /// Emit the per-module summary section alongside the rest of /// the module's bitcode. void ModuleBitcodeWriter::writePerModuleGlobalValueSummary() { - if (Index->begin() == Index->end()) - return; - Stream.EnterSubblock(bitc::GLOBALVAL_SUMMARY_BLOCK_ID, 4); Stream.EmitRecord(bitc::FS_VERSION, ArrayRef<uint64_t>{INDEX_VERSION}); + if (Index->begin() == Index->end()) { + Stream.ExitBlock(); + return; + } + // Abbrev for FS_PERMODULE. BitCodeAbbrev *Abbv = new BitCodeAbbrev(); Abbv->Add(BitCodeAbbrevOp(bitc::FS_PERMODULE)); Index: lib/LTO/LTO.cpp =================================================================== --- lib/LTO/LTO.cpp +++ lib/LTO/LTO.cpp @@ -609,6 +609,26 @@ }; } +// Given the original \p Path to an output file, replace any path +// prefix matching \p OldPrefix with \p NewPrefix. Also, create the +// resulting directory if it does not yet exist. +std::string lto::getThinLTOOutputFile(const std::string &Path, + const std::string &OldPrefix, + const std::string &NewPrefix) { + if (OldPrefix.empty() && NewPrefix.empty()) + return Path; + SmallString<128> NewPath(Path); + llvm::sys::path::replace_path_prefix(NewPath, OldPrefix, NewPrefix); + StringRef ParentPath = llvm::sys::path::parent_path(NewPath.str()); + if (!ParentPath.empty()) { + // Make sure the new directory exists, creating it if necessary. + if (std::error_code EC = llvm::sys::fs::create_directories(ParentPath)) + llvm::errs() << "warning: could not create directory '" << ParentPath + << "': " << EC.message() << '\n'; + } + return NewPath.str(); +} + class WriteIndexesThinBackend : public ThinBackendProc { std::string OldPrefix, NewPrefix; bool ShouldEmitImportsFiles; @@ -627,26 +647,6 @@ ShouldEmitImportsFiles(ShouldEmitImportsFiles), LinkedObjectsFileName(LinkedObjectsFileName) {} - /// Given the original \p Path to an output file, replace any path - /// prefix matching \p OldPrefix with \p NewPrefix. Also, create the - /// resulting directory if it does not yet exist. - std::string getThinLTOOutputFile(const std::string &Path, - const std::string &OldPrefix, - const std::string &NewPrefix) { - if (OldPrefix.empty() && NewPrefix.empty()) - return Path; - SmallString<128> NewPath(Path); - llvm::sys::path::replace_path_prefix(NewPath, OldPrefix, NewPrefix); - StringRef ParentPath = llvm::sys::path::parent_path(NewPath.str()); - if (!ParentPath.empty()) { - // Make sure the new directory exists, creating it if necessary. - if (std::error_code EC = llvm::sys::fs::create_directories(ParentPath)) - llvm::errs() << "warning: could not create directory '" << ParentPath - << "': " << EC.message() << '\n'; - } - return NewPath.str(); - } - Error start( unsigned Task, MemoryBufferRef MBRef, const FunctionImporter::ImportMapTy &ImportList, @@ -713,6 +713,16 @@ ModuleToDefinedGVSummaries(ThinLTO.ModuleMap.size()); ThinLTO.CombinedIndex.collectDefinedGVSummariesPerModule( ModuleToDefinedGVSummaries); + // Create entries for any modules that didn't have any GV summaries + // (either they didn't have any GVs to start with, or we suppressed + // generation of the summaries because they e.g. had inline assembly + // uses that couldn't be promoted/renamed on export). This is so + // InProcessThinBackend::start can still launch a backend thread, which + // is passed the map of summaries for the module, without any special + // handling for this case. + for (auto &Mod : ThinLTO.ModuleMap) + if (!ModuleToDefinedGVSummaries.count(Mod.first)) + ModuleToDefinedGVSummaries.try_emplace(Mod.first); StringMap<FunctionImporter::ImportMapTy> ImportLists( ThinLTO.ModuleMap.size()); Index: test/ThinLTO/X86/Inputs/emit_imports.ll =================================================================== --- test/ThinLTO/X86/Inputs/emit_imports.ll +++ test/ThinLTO/X86/Inputs/emit_imports.ll @@ -1,3 +1,6 @@ +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + define void @g() { entry: ret void Index: test/ThinLTO/X86/Inputs/empty.ll =================================================================== --- /dev/null +++ test/ThinLTO/X86/Inputs/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: test/ThinLTO/X86/emit_imports.ll =================================================================== --- test/ThinLTO/X86/emit_imports.ll +++ test/ThinLTO/X86/emit_imports.ll @@ -1,7 +1,11 @@ ; RUN: opt -module-summary %s -o %t1.bc ; RUN: opt -module-summary %p/Inputs/emit_imports.ll -o %t2.bc -; RUN: llvm-lto -thinlto-action=thinlink -o %t.index.bc %t1.bc %t2.bc -; RUN: llvm-lto -thinlto-action=emitimports -thinlto-index %t.index.bc %t1.bc %t2.bc +; Include a file with an empty module summary index, to ensure that the expected +; output files are created regardless, for a distributed build system. +; RUN: opt -module-summary %p/Inputs/empty.ll -o %t3.bc +; RUN: rm -f %t3.bc.imports +; RUN: llvm-lto -thinlto-action=thinlink -o %t.index.bc %t1.bc %t2.bc %t3.bc +; RUN: llvm-lto -thinlto-action=emitimports -thinlto-index %t.index.bc %t1.bc %t2.bc %t3.bc ; The imports file for this module contains the bitcode file for ; Inputs/emit_imports.ll @@ -12,9 +16,13 @@ ; The imports file for Input/emit_imports.ll is empty as it does not import anything. ; RUN: cat %t2.bc.imports | count 0 +; The imports file for Input/empty.ll is empty but should exist. +; RUN: cat %t3.bc.imports | count 0 + ; RUN: rm -f %t1.thinlto.bc %t1.bc.imports ; RUN: rm -f %t2.thinlto.bc %t2.bc.imports -; RUN: llvm-lto2 %t1.bc %t2.bc -o %t.o \ +; RUN: rm -f %t3.bc.thinlto.bc %t3.bc.imports +; RUN: llvm-lto2 %t1.bc %t2.bc %t3.bc -o %t.o -save-temps \ ; RUN: -thinlto-distributed-indexes \ ; RUN: -r=%t1.bc,g, \ ; RUN: -r=%t1.bc,f,px \ @@ -26,6 +34,15 @@ ; The imports file for Input/emit_imports.ll is empty as it does not import anything. ; RUN: cat %t2.bc.imports | count 0 +; The imports file for Input/empty.ll is empty but should exist. +; RUN: cat %t3.bc.imports | count 0 + +; The index file should be created even for the input with an empty summary. +; RUN: ls %t3.bc.thinlto.bc + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + declare void @g(...) define void @f() { Index: test/tools/gold/X86/thinlto_emit_imports.ll =================================================================== --- test/tools/gold/X86/thinlto_emit_imports.ll +++ test/tools/gold/X86/thinlto_emit_imports.ll @@ -1,13 +1,17 @@ ; Generate summary sections and test gold handling. ; RUN: opt -module-summary %s -o %t.o ; RUN: opt -module-summary %p/Inputs/thinlto.ll -o %t2.o +; Include a file with an empty module summary index, to ensure that the expected +; output files are created regardless, for a distributed build system. +; RUN: opt -module-summary %p/Inputs/thinlto_empty.ll -o %t3.o ; Ensure gold generates imports files if requested for distributed backends. +; RUN: rm -f %t3.o.imports %t3.o.thinlto.bc ; RUN: %gold -plugin %llvmshlibdir/LLVMgold.so \ ; RUN: --plugin-opt=thinlto \ ; RUN: --plugin-opt=thinlto-index-only \ ; RUN: --plugin-opt=thinlto-emit-imports-files \ -; RUN: -shared %t.o %t2.o -o %t3 +; RUN: -shared %t.o %t2.o %t3.o -o %t4 ; The imports file for this module contains the bitcode file for ; Inputs/thinlto.ll @@ -18,6 +22,12 @@ ; The imports file for Input/thinlto.ll is empty as it does not import anything. ; RUN: cat %t2.o.imports | count 0 +; The imports file for Input/thinlto_empty.ll is empty but should exist. +; RUN: cat %t3.o.imports | count 0 + +; The index file should be created even for the input with an empty summary. +; RUN: ls %t3.o.thinlto.bc + declare void @g(...) define void @f() { Index: test/tools/gold/X86/v1.12/thinlto_emit_linked_objects.ll =================================================================== --- test/tools/gold/X86/v1.12/thinlto_emit_linked_objects.ll +++ test/tools/gold/X86/v1.12/thinlto_emit_linked_objects.ll @@ -9,14 +9,26 @@ ; be included in the link, and not %t2.o since it is within ; a library (--start-lib/--end-lib pair) and not strongly referenced. ; Note that the support for detecting this is in gold v1.12. +; RUN: rm -f %t.o.thinlto.bc +; RUN: rm -f %t2.o.thinlto.bc +; RUN: rm -f %t.o.imports +; RUN: rm -f %t2.o.imports ; RUN: %gold -plugin %llvmshlibdir/LLVMgold.so \ ; RUN: --plugin-opt=thinlto \ ; RUN: --plugin-opt=thinlto-index-only=%t3 \ +; RUN: --plugin-opt=thinlto-emit-imports-files \ ; RUN: -m elf_x86_64 \ ; RUN: -o %t4 \ ; RUN: %t.o \ ; RUN: --start-lib %t2.o --end-lib +; Ensure that the expected output files are created, even for the file +; the linker decided not to include in the link. +; RUN: ls %t.o.thinlto.bc +; RUN: ls %t2.o.thinlto.bc +; RUN: ls %t.o.imports +; RUN: ls %t2.o.imports + ; RUN: cat %t3 | FileCheck %s ; CHECK: thinlto_emit_linked_objects.ll.tmp.o ; CHECK-NOT: thinlto_emit_linked_objects.ll.tmp2.o Index: tools/gold/gold-plugin.cpp =================================================================== --- tools/gold/gold-plugin.cpp +++ tools/gold/gold-plugin.cpp @@ -752,6 +752,34 @@ ParallelCodeGenParallelismLevel); } +// Write empty files that may be expected by a distributed build +// system when invoked with thinlto_index_only. This is invoked when +// the linker has decided not to include the given module in the +// final link. Frequently the distributed build system will want to +// confirm that all expected outputs are created based on all of the +// modules provided to the linker. +static void writeEmptyDistributedBuildOutputs(std::string &ModulePath, + std::string &OldPrefix, + std::string &NewPrefix) { + std::string NewModulePath = + getThinLTOOutputFile(ModulePath, OldPrefix, NewPrefix); + std::error_code EC; + { + raw_fd_ostream OS(NewModulePath + ".thinlto.bc", EC, + sys::fs::OpenFlags::F_None); + if (EC) + message(LDPL_FATAL, "Failed to write '%s': %s", + (NewModulePath + ".thinlto.bc").c_str(), EC.message().c_str()); + } + if (options::thinlto_emit_imports_files) { + raw_fd_ostream OS(NewModulePath + ".imports", EC, + sys::fs::OpenFlags::F_None); + if (EC) + message(LDPL_FATAL, "Failed to write '%s': %s", + (NewModulePath + ".imports").c_str(), EC.message().c_str()); + } +} + /// gold informs us that all symbols have been read. At this point, we use /// get_symbols to see if any of our definitions have been overridden by a /// native object file. Then, perform optimization and codegen. @@ -771,13 +799,22 @@ std::unique_ptr<LTO> Lto = createLTO(); + std::string OldPrefix, NewPrefix; + if (options::thinlto_index_only) + getThinLTOOldAndNewPrefix(OldPrefix, NewPrefix); + for (claimed_file &F : Modules) { if (options::thinlto && !HandleToInputFile.count(F.leader_handle)) HandleToInputFile.insert(std::make_pair( F.leader_handle, llvm::make_unique<PluginInputFile>(F.handle))); const void *View = getSymbolsAndView(F); - if (!View) + if (!View) { + if (options::thinlto_index_only) + // Write empty output files that may be expected by the distributed + // build system. + writeEmptyDistributedBuildOutputs(F.name, OldPrefix, NewPrefix); continue; + } addModule(*Lto, F, View); }