Index: test/tools/gold/X86/Inputs/thinlto_archive1.ll =================================================================== --- /dev/null +++ test/tools/gold/X86/Inputs/thinlto_archive1.ll @@ -0,0 +1,4 @@ +define void @g() { +entry: + ret void +} Index: test/tools/gold/X86/Inputs/thinlto_archive2.ll =================================================================== --- /dev/null +++ test/tools/gold/X86/Inputs/thinlto_archive2.ll @@ -0,0 +1,4 @@ +define void @h() { +entry: + ret void +} Index: test/tools/gold/X86/thinlto_archive.ll =================================================================== --- /dev/null +++ test/tools/gold/X86/thinlto_archive.ll @@ -0,0 +1,29 @@ +; Generate summary sections +; RUN: opt -module-summary %s -o %t.o +; RUN: opt -module-summary %p/Inputs/thinlto_archive1.ll -o %t2.o +; RUN: opt -module-summary %p/Inputs/thinlto_archive2.ll -o %t3.o + +; Generate the static library +; RUN: llvm-ar r %t.a %t2.o %t3.o + +; Test importing from archive library via gold, using jobs=1 to ensure +; output messages are not interleaved. +; RUN: %gold -plugin %llvmshlibdir/LLVMgold.so \ +; RUN: --plugin-opt=thinlto \ +; RUN: --plugin-opt=-print-imports \ +; RUN: --plugin-opt=jobs=1 \ +; RUN: -shared %t.o %t.a -o %t4 2>&1 | FileCheck %s +; RUN: llvm-nm %t4 | FileCheck %s --check-prefix=NM + +; CHECK-DAG: Import g +declare void @g(...) +; CHECK-DAG: Import h +declare void @h(...) + +; NM: T f +define void @f() { +entry: + call void (...) @g() + call void (...) @h() + ret void +} Index: tools/gold/CMakeLists.txt =================================================================== --- tools/gold/CMakeLists.txt +++ tools/gold/CMakeLists.txt @@ -10,6 +10,7 @@ set(LLVM_LINK_COMPONENTS ${LLVM_TARGETS_TO_BUILD} Linker + LTO BitWriter IPO ) Index: tools/gold/gold-plugin.cpp =================================================================== --- tools/gold/gold-plugin.cpp +++ tools/gold/gold-plugin.cpp @@ -28,6 +28,7 @@ #include "llvm/IR/LegacyPassManager.h" #include "llvm/IR/Module.h" #include "llvm/IR/Verifier.h" +#include "llvm/LTO/LTO.h" #include "llvm/Linker/IRMover.h" #include "llvm/MC/SubtargetFeature.h" #include "llvm/Object/IRObjectFile.h" @@ -73,7 +74,10 @@ namespace { struct claimed_file { void *handle; + void *leader_handle; std::vector syms; + off_t filesize; + std::string name; }; /// RAII wrapper to manage opening and releasing of a ld_plugin_input_file. @@ -113,9 +117,6 @@ /// Class to own information used by a task or during its cleanup for a /// ThinLTO backend instantiation. class ThinLTOTaskInfo { - /// The input file holding the module bitcode read by the ThinLTO task. - PluginInputFile InputFile; - /// The output stream the task will codegen into. std::unique_ptr OS; @@ -127,10 +128,9 @@ bool TempOutFile; public: - ThinLTOTaskInfo(PluginInputFile InputFile, std::unique_ptr OS, - std::string Filename, bool TempOutFile) - : InputFile(std::move(InputFile)), OS(std::move(OS)), Filename(Filename), - TempOutFile(TempOutFile) {} + ThinLTOTaskInfo(std::unique_ptr OS, std::string Filename, + bool TempOutFile) + : OS(std::move(OS)), Filename(Filename), TempOutFile(TempOutFile) {} /// Performs task related cleanup activities that must be done /// single-threaded (i.e. call backs to gold). @@ -146,6 +146,7 @@ static Optional RelocationModel; static std::string output_name = ""; static std::list Modules; +static DenseMap FDToLeaderHandle; static StringMap ResInfo; static std::vector Cleanup; static llvm::TargetOptions TargetOpts; @@ -503,6 +504,23 @@ claimed_file &cf = Modules.back(); cf.handle = file->handle; + // Keep track of the first handle for each file descriptor, since there are + // multiple in the case of an archive. This is used later in the case of + // ThinLTO parallel backends to ensure that each file is only opened and + // released once. + auto LeaderHandle = + FDToLeaderHandle.insert(std::make_pair(file->fd, file->handle)).first; + cf.leader_handle = LeaderHandle->second; + // Save the filesize since for parallel ThinLTO backends we can only + // invoke get_input_file once per archive (only for the leader handle). + cf.filesize = file->filesize; + // In the case of an archive library, all but the first member must have a + // non-zero offset, which we can append to the file name to obtain a + // unique name. + cf.name = file->name; + if (file->offset) + cf.name += ".llvm." + std::to_string(file->offset) + "." + + sys::path::filename(Obj->getModule().getSourceFileName()).str(); // If we are doing ThinLTO compilation, don't need to process the symbols. // Later we simply build a combined index file after all files are claimed. @@ -643,13 +661,12 @@ } static std::unique_ptr -getModuleSummaryIndexForFile(claimed_file &F, ld_plugin_input_file &Info) { +getModuleSummaryIndexForFile(claimed_file &F) { const void *View = getSymbolsAndView(F); if (!View) return nullptr; - MemoryBufferRef BufferRef(StringRef((const char *)View, Info.filesize), - Info.name); + MemoryBufferRef BufferRef(StringRef((const char *)View, F.filesize), F.name); // Don't bother trying to build an index if there is no summary information // in this bitcode file. @@ -671,14 +688,11 @@ return Obj.takeIndex(); } -static std::unique_ptr -getModuleForFile(LLVMContext &Context, claimed_file &F, const void *View, - ld_plugin_input_file &Info, raw_fd_ostream *ApiFile, - StringSet<> &Internalize, StringSet<> &Maybe, - std::vector &Keep, - StringMap &Realign) { - MemoryBufferRef BufferRef(StringRef((const char *)View, Info.filesize), - Info.name); +static std::unique_ptr getModuleForFile( + LLVMContext &Context, claimed_file &F, const void *View, StringRef Name, + raw_fd_ostream *ApiFile, StringSet<> &Internalize, StringSet<> &Maybe, + std::vector &Keep, StringMap &Realign) { + MemoryBufferRef BufferRef(StringRef((const char *)View, F.filesize), Name); ErrorOr> ObjOrErr = object::IRObjectFile::create(BufferRef, Context); @@ -874,17 +888,23 @@ /// a unique and identifiable save-temps output file for each ThinLTO backend. std::string SaveTempsFilename; + /// Map from a module name to the corresponding buffer holding a view of the + /// bitcode provided via the get_view gold callback. + StringMap *ModuleMap; + public: /// Constructor used by full LTO. CodeGen(std::unique_ptr M) - : M(std::move(M)), OS(nullptr), TaskID(-1), CombinedIndex(nullptr) { + : M(std::move(M)), OS(nullptr), TaskID(-1), CombinedIndex(nullptr), + ModuleMap(nullptr) { initTargetMachine(); } /// Constructor used by ThinLTO. CodeGen(std::unique_ptr M, raw_fd_ostream *OS, int TaskID, - const ModuleSummaryIndex *CombinedIndex, std::string Filename) + const ModuleSummaryIndex *CombinedIndex, std::string Filename, + StringMap *ModuleMap) : M(std::move(M)), OS(OS), TaskID(TaskID), CombinedIndex(CombinedIndex), - SaveTempsFilename(Filename) { + SaveTempsFilename(Filename), ModuleMap(ModuleMap) { assert(options::thinlto == !!CombinedIndex && "Expected module summary index iff performing ThinLTO"); initTargetMachine(); @@ -968,6 +988,21 @@ void CodeGen::runLTOPasses() { M->setDataLayout(TM->createDataLayout()); + if (CombinedIndex) { + // First collect the import list. + FunctionImporter::ImportMapTy ImportList; + ComputeCrossModuleImportForModule(M->getModuleIdentifier(), *CombinedIndex, + ImportList); + + // Create a loader that will parse the bitcode from the buffers + // in the ModuleMap. + ModuleLoader Loader(M->getContext(), *ModuleMap); + + // Perform function importing. + FunctionImporter Importer(*CombinedIndex, Loader); + Importer.importFunctions(*M, ImportList); + } + legacy::PassManager passes; passes.add(createTargetTransformInfoWrapperPass(TM->getTargetIRAnalysis())); @@ -981,7 +1016,6 @@ PMB.LoopVectorize = true; PMB.SLPVectorize = true; PMB.OptLevel = options::OptLevel; - PMB.ModuleSummary = CombinedIndex; if (options::thinlto) PMB.populateThinLTOPassManager(passes); else @@ -1099,13 +1133,13 @@ /// Links the module in \p View from file \p F into the combined module /// saved in the IRMover \p L. Returns true on error, false on success. static bool linkInModule(LLVMContext &Context, IRMover &L, claimed_file &F, - const void *View, ld_plugin_input_file &File, + const void *View, StringRef Name, raw_fd_ostream *ApiFile, StringSet<> &Internalize, StringSet<> &Maybe) { std::vector Keep; StringMap Realign; std::unique_ptr M = getModuleForFile( - Context, F, View, File, ApiFile, Internalize, Maybe, Keep, Realign); + Context, F, View, Name, ApiFile, Internalize, Maybe, Keep, Realign); if (!M.get()) return false; if (!options::triple.empty()) @@ -1130,10 +1164,10 @@ /// Perform the ThinLTO backend on a single module, invoking the LTO and codegen /// pipelines. static void thinLTOBackendTask(claimed_file &F, const void *View, - ld_plugin_input_file &File, - raw_fd_ostream *ApiFile, + StringRef Name, raw_fd_ostream *ApiFile, const ModuleSummaryIndex &CombinedIndex, - raw_fd_ostream *OS, unsigned TaskID) { + raw_fd_ostream *OS, unsigned TaskID, + StringMap &ModuleMap) { // Need to use a separate context for each task LLVMContext Context; Context.setDiscardValueNames(options::TheOutputType != @@ -1141,22 +1175,24 @@ Context.enableDebugTypeODRUniquing(); // Merge debug info types. Context.setDiagnosticHandler(diagnosticHandlerForContext, nullptr, true); - std::unique_ptr NewModule(new llvm::Module(File.name, Context)); + std::unique_ptr NewModule(new llvm::Module(Name, Context)); IRMover L(*NewModule.get()); StringSet<> Dummy; - if (linkInModule(Context, L, F, View, File, ApiFile, Dummy, Dummy)) + if (linkInModule(Context, L, F, View, Name, ApiFile, Dummy, Dummy)) message(LDPL_FATAL, "Failed to rename module for ThinLTO"); if (renameModuleForThinLTO(*NewModule, CombinedIndex)) message(LDPL_FATAL, "Failed to rename module for ThinLTO"); - CodeGen codeGen(std::move(NewModule), OS, TaskID, &CombinedIndex, File.name); + CodeGen codeGen(std::move(NewModule), OS, TaskID, &CombinedIndex, Name, + &ModuleMap); codeGen.runAll(); } /// Launch each module's backend pipeline in a separate task in a thread pool. static void thinLTOBackends(raw_fd_ostream *ApiFile, - const ModuleSummaryIndex &CombinedIndex) { + const ModuleSummaryIndex &CombinedIndex, + StringMap &ModuleMap) { unsigned TaskCount = 0; std::vector Tasks; Tasks.reserve(Modules.size()); @@ -1171,7 +1207,6 @@ for (claimed_file &F : Modules) { // Do all the gold callbacks in the main thread, since gold is not thread // safe by default. - PluginInputFile InputFile(F.handle); const void *View = getSymbolsAndView(F); if (!View) continue; @@ -1183,7 +1218,7 @@ else if (options::TheOutputType == options::OT_SAVE_TEMPS) { // Use the input file name so that we get a unique and identifiable // output file for each ThinLTO backend task. - Filename = InputFile.file().name; + Filename = F.name; Filename += ".thinlto.o"; } bool TempOutFile = Filename.empty(); @@ -1198,15 +1233,14 @@ llvm::make_unique(FD, true); // Enqueue the task - ThinLTOThreadPool.async(thinLTOBackendTask, std::ref(F), View, - std::ref(InputFile.file()), ApiFile, - std::ref(CombinedIndex), OS.get(), TaskCount); + ThinLTOThreadPool.async(thinLTOBackendTask, std::ref(F), View, F.name, + ApiFile, std::ref(CombinedIndex), OS.get(), + TaskCount, std::ref(ModuleMap)); // Record the information needed by the task or during its cleanup // to a ThinLTOTaskInfo instance. For information needed by the task // the unique_ptr ownership is transferred to the ThinLTOTaskInfo. - Tasks.emplace_back(std::move(InputFile), std::move(OS), - NewFilename.c_str(), TempOutFile); + Tasks.emplace_back(std::move(OS), NewFilename.c_str(), TempOutFile); } } @@ -1249,13 +1283,32 @@ /// Also, either launch backend threads or (under thinlto-index-only) /// emit individual index files for distributed backends and exit. static ld_plugin_status thinLTOLink(raw_fd_ostream *ApiFile) { + // Map from a module name to the corresponding buffer holding a view of the + // bitcode provided via the get_view gold callback. + StringMap ModuleMap; + // Map to own RAII objects that manage the file opening and releasing + // interfaces with gold. + DenseMap> HandleToInputFile; + ModuleSummaryIndex CombinedIndex; uint64_t NextModuleId = 0; for (claimed_file &F : Modules) { - PluginInputFile InputFile(F.handle); + if (!HandleToInputFile.count(F.leader_handle)) + HandleToInputFile.insert(std::make_pair( + F.leader_handle, llvm::make_unique(F.handle))); + // Pass this into getModuleSummaryIndexForFile + const void *View = getSymbolsAndView(F); + if (!View) + continue; + + MemoryBufferRef ModuleBuffer(StringRef((const char *)View, F.filesize), + F.name); + assert(ModuleMap.find(ModuleBuffer.getBufferIdentifier()) == + ModuleMap.end() && + "Expect unique Buffer Identifier"); + ModuleMap[ModuleBuffer.getBufferIdentifier()] = ModuleBuffer; - std::unique_ptr Index = - getModuleSummaryIndexForFile(F, InputFile.file()); + std::unique_ptr Index = getModuleSummaryIndexForFile(F); // Skip files without a module summary. if (Index) @@ -1292,11 +1345,10 @@ // contains summaries only for its own global values, and for any that // should be imported. for (claimed_file &F : Modules) { - PluginInputFile InputFile(F.handle); std::error_code EC; std::string NewModulePath = - getThinLTOOutputFile(InputFile.file().name, OldPrefix, NewPrefix); + getThinLTOOutputFile(F.name, OldPrefix, NewPrefix); raw_fd_ostream OS((Twine(NewModulePath) + ".thinlto.bc").str(), EC, sys::fs::OpenFlags::F_None); if (EC) @@ -1305,16 +1357,14 @@ // Build a map of module to the GUIDs and summary objects that should // be written to its index. std::map ModuleToSummariesForIndex; - gatherImportedSummariesForModule(InputFile.file().name, - ModuleToDefinedGVSummaries, ImportLists, - ModuleToSummariesForIndex); + gatherImportedSummariesForModule(F.name, ModuleToDefinedGVSummaries, + ImportLists, ModuleToSummariesForIndex); WriteIndexToFile(CombinedIndex, OS, &ModuleToSummariesForIndex); if (options::thinlto_emit_imports_files) { - if ((EC = EmitImportsFiles( - InputFile.file().name, - (Twine(NewModulePath) + ".imports").str(), - ImportLists))) + if ((EC = EmitImportsFiles(F.name, + (Twine(NewModulePath) + ".imports").str(), + ImportLists))) message(LDPL_FATAL, "Unable to open %s.imports", NewModulePath.c_str(), EC.message().c_str()); } @@ -1335,7 +1385,7 @@ WriteIndexToFile(CombinedIndex, OS); } - thinLTOBackends(ApiFile, CombinedIndex); + thinLTOBackends(ApiFile, CombinedIndex, ModuleMap); return LDPS_OK; } @@ -1364,12 +1414,13 @@ StringSet<> Internalize; StringSet<> Maybe; for (claimed_file &F : Modules) { + // RAII object to manage the file opening and releasing interfaces with + // gold. PluginInputFile InputFile(F.handle); const void *View = getSymbolsAndView(F); if (!View) continue; - if (linkInModule(Context, L, F, View, InputFile.file(), ApiFile, - Internalize, Maybe)) + if (linkInModule(Context, L, F, View, F.name, ApiFile, Internalize, Maybe)) message(LDPL_FATAL, "Failed to link module"); }