diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -140,6 +140,7 @@ bool asNeeded = false; bool bsymbolic; bool bsymbolicFunctions; + bool cacheIRSymTab; bool callGraphProfileSort; bool checkSections; bool compressDebugSections; diff --git a/lld/ELF/Driver.h b/lld/ELF/Driver.h --- a/lld/ELF/Driver.h +++ b/lld/ELF/Driver.h @@ -14,13 +14,25 @@ #include "lld/Common/LLVM.h" #include "lld/Common/Reproduce.h" #include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/Option/ArgList.h" #include "llvm/Support/raw_ostream.h" +namespace llvm { +namespace lto { +class IRSymtabFileCache; +} +} // namespace llvm + namespace lld { +extern llvm::lto::IRSymtabFileCache *irSymTabCache; + namespace elf { +void addFileToIrSymTabCache(StringRef path, + llvm::StringMap &LoadedFiles, + std::vector &ToCache); extern class LinkerDriver *driver; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -47,6 +47,7 @@ #include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/Bitcode/BitcodeReader.h" #include "llvm/LTO/LTO.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Compression.h" @@ -69,6 +70,8 @@ using namespace lld; using namespace lld::elf; +llvm::lto::IRSymtabFileCache *lld::irSymTabCache; + Configuration *elf::config; LinkerDriver *elf::driver; @@ -193,9 +196,14 @@ void LinkerDriver::addFile(StringRef path, bool withLOption) { using namespace sys::fs; - Optional buffer = readFile(path); + Optional buffer; + if (irSymTabCache) + buffer = irSymTabCache->getMemBufferForPath(path); + if (!buffer.hasValue()) + buffer = readFile(path); if (!buffer.hasValue()) return; + MemoryBufferRef mbref = *buffer; if (config->formatBinary) { @@ -495,6 +503,9 @@ if (config->timeTraceEnabled) timeTraceProfilerInitialize(config->timeTraceGranularity, config->progName); + if (config->cacheIRSymTab) + irSymTabCache = new lto::IRSymtabFileCache(); + { llvm::TimeTraceScope timeScope("ExecuteLinker"); @@ -890,6 +901,8 @@ args.hasFlag(OPT_eh_frame_hdr, OPT_no_eh_frame_hdr, false); config->emitLLVM = args.hasArg(OPT_plugin_opt_emit_llvm, false); config->emitRelocs = args.hasArg(OPT_emit_relocs); + config->cacheIRSymTab = + args.hasFlag(OPT_cache_ir_symtab, OPT_no_cache_ir_symtab, false); config->callGraphProfileSort = args.hasFlag( OPT_call_graph_profile_sort, OPT_no_call_graph_profile_sort, true); config->enableNewDtags = @@ -1243,10 +1256,54 @@ return false; } +void lld::elf::addFileToIrSymTabCache( + StringRef path, llvm::StringMap &LoadedFiles, + std::vector &ToCache) { + using namespace sys::fs; + + Optional buffer = readFile(path); + if (!buffer.hasValue()) + return; + MemoryBufferRef mbref = *buffer; + LoadedFiles.try_emplace(path, mbref); + + switch (identify_magic(mbref.getBuffer())) { + case file_magic::archive: { + for (const std::pair &p : + getArchiveMembers(mbref)) + if (identify_magic(p.first.getBuffer()) == file_magic::bitcode) + ToCache.push_back(p.first); + break; + } + + case file_magic::bitcode: + ToCache.push_back(mbref); + break; + default: + break; + } +} + void LinkerDriver::createFiles(opt::InputArgList &args) { // For --{push,pop}-state. std::vector> stack; + if (irSymTabCache) { + std::vector Files; + for (auto *arg : args) { + switch (arg->getOption().getID()) { + case OPT_library: + if (Optional path = searchLibrary(arg->getValue())) + Files.push_back(*path); + break; + case OPT_INPUT: + Files.push_back(arg->getValue()); + break; + } + } + irSymTabCache->upgrade(Files, addFileToIrSymTabCache); + } + // Iterate over argv to process input files and positional arguments. for (auto *arg : args) { switch (arg->getOption().getID()) { @@ -1850,6 +1907,11 @@ parseFile(files[i]); } + if (irSymTabCache) { + delete irSymTabCache; + irSymTabCache = nullptr; + } + // Now that we have every file, we can decide if we will need a // dynamic symbol table. // We need one if we were asked to export dynamic symbols or if we are diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -486,6 +486,16 @@ this->sections.resize(sections.size()); } +static Optional> getPath(StringRef specifier) { + if (fs::exists(specifier)) + return std::make_pair(specifier.str(), false); + else if (Optional s = findFromSearchPaths(specifier)) + return std::make_pair(*s, true); + else if (Optional s = searchLibraryBaseName(specifier)) + return std::make_pair(*s, true); + return None; +} + // An ELF object file may contain a `.deplibs` section. If it exists, the // section contains a list of library specifiers such as `m` for libm. This // function resolves a given name by finding the first matching library checking @@ -495,12 +505,8 @@ static void addDependentLibrary(StringRef specifier, const InputFile *f) { if (!config->dependentLibraries) return; - if (fs::exists(specifier)) - driver->addFile(specifier, /*withLOption=*/false); - else if (Optional s = findFromSearchPaths(specifier)) - driver->addFile(*s, /*withLOption=*/true); - else if (Optional s = searchLibraryBaseName(specifier)) - driver->addFile(*s, /*withLOption=*/true); + if (Optional> path = getPath(specifier)) + driver->addFile(path->first, /*withLOption=*/path->second); else error(toString(f) + ": unable to find library from dependent library specifier: " + @@ -1443,6 +1449,11 @@ if (config->thinLTOIndexOnly) path = replaceThinLTOSuffix(mb.getBufferIdentifier()); + if (irSymTabCache) + obj = CHECK(lto::InputFile::create(mb, *irSymTabCache), this); + else + obj = CHECK(lto::InputFile::create(mb), this); + // ThinLTO assumes that all MemoryBufferRefs given to it have a unique // name. If two archives define two members with the same name, this // causes a collision which result in only one of the objects being taken @@ -1454,9 +1465,7 @@ ? saver.save(path) : saver.save(archiveName + "(" + path::filename(path) + " at " + utostr(offsetInArchive) + ")"); - MemoryBufferRef mbref(mb.getBuffer(), name); - - obj = CHECK(lto::InputFile::create(mbref), this); + obj->setName(name); Triple t(obj->getTargetTriple()); ekind = getBitcodeELFKind(t); @@ -1515,6 +1524,14 @@ for (const lto::InputFile::Symbol &objSym : obj->symbols()) symbols.push_back(createBitcodeSymbol(keptComdats, objSym, *this)); + if (config->dependentLibraries && irSymTabCache) { + std::vector filesToUpgrade; + for (auto l : obj->getDependentLibraries()) + if (Optional> path = getPath(l)) + filesToUpgrade.push_back(path->first); + irSymTabCache->upgrade(filesToUpgrade, addFileToIrSymTabCache); + } + for (auto l : obj->getDependentLibraries()) addDependentLibrary(l, this); } diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -103,6 +103,10 @@ "Only set DT_NEEDED for shared libraries if used", "Always set DT_NEEDED for shared libraries (default)">; +defm cache_ir_symtab: BB<"cache-ir-symtab", + "Cache IR symbol table before parsing input files", + "Do not cache IR symbol table before parsing input files (default)">; + defm call_graph_ordering_file: Eq<"call-graph-ordering-file", "Layout sections to optimize the given callgraph">; diff --git a/lld/test/ELF/deplibs.s b/lld/test/ELF/deplibs.s --- a/lld/test/ELF/deplibs.s +++ b/lld/test/ELF/deplibs.s @@ -31,6 +31,9 @@ # RUN: ld.lld %t.o libcmdline.a -o /dev/null -L %t.dir --trace 2>&1 | \ # RUN: FileCheck %s -DOBJ=%t.o -DSO=%t.dir --check-prefix CMDLINE \ # RUN: --implicit-check-not=foo.a --implicit-check-not=libbar.so +# RUN: ld.lld %t.o libcmdline.a -o /dev/null -L %t.dir --trace --cache-ir-symtab 2>&1 | \ +# RUN: FileCheck %s -DOBJ=%t.o -DSO=%t.dir --check-prefix CMDLINE \ +# RUN: --implicit-check-not=foo.a --implicit-check-not=libbar.so # CMDLINE: [[OBJ]] # CMDLINE-NEXT: {{^libcmdline\.a}} diff --git a/llvm/include/llvm/Bitcode/BitcodeReader.h b/llvm/include/llvm/Bitcode/BitcodeReader.h --- a/llvm/include/llvm/Bitcode/BitcodeReader.h +++ b/llvm/include/llvm/Bitcode/BitcodeReader.h @@ -93,6 +93,7 @@ StringRef getStrtab() const { return Strtab; } StringRef getModuleIdentifier() const { return ModuleIdentifier; } + void setModuleIdentifier(StringRef ModId) { ModuleIdentifier = ModId; } /// Read the bitcode module and prepare for lazy deserialization of function /// bodies. If ShouldLazyLoadMetadata is true, lazily load metadata as well. diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h --- a/llvm/include/llvm/LTO/LTO.h +++ b/llvm/include/llvm/LTO/LTO.h @@ -17,10 +17,13 @@ #include "llvm/ADT/MapVector.h" #include "llvm/ADT/StringMap.h" +#include "llvm/Bitcode/BitcodeReader.h" #include "llvm/IR/ModuleSummaryIndex.h" #include "llvm/LTO/Config.h" +#include "llvm/Object/IRObjectFile.h" #include "llvm/Object/IRSymtab.h" #include "llvm/Support/Error.h" +#include "llvm/Support/ThreadPool.h" #include "llvm/Support/thread.h" #include "llvm/Transforms/IPO/FunctionImport.h" @@ -95,6 +98,25 @@ struct SymbolResolution; class ThinBackendProc; +// For parallelizing IR symtab upgrading. +class IRSymtabFileCache { + ThreadPool BackendThreadPool; + using CacheType = + DenseMap>; + CacheType Cache; + StringMap LoadedFiles; + + using AddFileFn = std::function &LoadedFiles, + std::vector &ToLoad)>; + +public: + IRSymtabFileCache() : BackendThreadPool(), Cache(), LoadedFiles() {} + void upgrade(const std::vector &Files, AddFileFn AFF); + object::IRSymtabFile *get(const MemoryBufferRef &M); + Optional getMemBufferForPath(StringRef Path); +}; + /// An input file. This is a symbol table wrapper that only exposes the /// information that an LTO client should need in order to do symbol resolution. class InputFile { @@ -105,6 +127,7 @@ // FIXME: Remove LTO class friendship once we have bitcode symbol tables. friend LTO; InputFile() = default; + InputFile(object::IRSymtabFile &SF); std::vector Mods; SmallVector Strtab; @@ -122,6 +145,8 @@ /// Create an InputFile. static Expected> create(MemoryBufferRef Object); + static Expected> + create(MemoryBufferRef Object, IRSymtabFileCache &IRSymTabCache); /// The purpose of this class is to only expose the symbol information that an /// LTO client should need in order to do symbol resolution. @@ -161,6 +186,9 @@ /// Returns the path to the InputFile. StringRef getName() const; + /// Set the path to the InputFile. + void setName(StringRef Name); + /// Returns the input file's target triple. StringRef getTargetTriple() const { return TargetTriple; } diff --git a/llvm/include/llvm/Object/IRObjectFile.h b/llvm/include/llvm/Object/IRObjectFile.h --- a/llvm/include/llvm/Object/IRObjectFile.h +++ b/llvm/include/llvm/Object/IRObjectFile.h @@ -83,7 +83,7 @@ /// Reads a bitcode file, creating its irsymtab if necessary. Expected readIRSymtab(MemoryBufferRef MBRef); - +bool needToUpgradeIRSymtab(MemoryBufferRef MBRef); } } diff --git a/llvm/include/llvm/Object/IRSymtab.h b/llvm/include/llvm/Object/IRSymtab.h --- a/llvm/include/llvm/Object/IRSymtab.h +++ b/llvm/include/llvm/Object/IRSymtab.h @@ -367,6 +367,7 @@ /// Reads the contents of a bitcode file, creating its irsymtab if necessary. Expected readBitcode(const BitcodeFileContents &BFC); +bool needUpgrade(const BitcodeFileContents &BFC); } // end namespace irsymtab } // end namespace llvm diff --git a/llvm/include/llvm/Support/MemoryBuffer.h b/llvm/include/llvm/Support/MemoryBuffer.h --- a/llvm/include/llvm/Support/MemoryBuffer.h +++ b/llvm/include/llvm/Support/MemoryBuffer.h @@ -15,6 +15,7 @@ #include "llvm-c/Types.h" #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/Twine.h" #include "llvm/Support/CBindingWrapping.h" @@ -283,6 +284,28 @@ // Create wrappers for C Binding types (see CBindingWrapping.h). DEFINE_SIMPLE_CONVERSION_FUNCTIONS(MemoryBuffer, LLVMMemoryBufferRef) +// Support MemoryBufferRef as DenseMap keys. +template <> struct DenseMapInfo { + static inline MemoryBufferRef getEmptyKey() { + static MemoryBufferRef EmptyKey(DenseMapInfo::getEmptyKey(), + DenseMapInfo::getEmptyKey()); + return EmptyKey; + } + static inline MemoryBufferRef getTombstoneKey() { + static MemoryBufferRef TombstoneKey( + DenseMapInfo::getTombstoneKey(), + DenseMapInfo::getTombstoneKey()); + return TombstoneKey; + } + static unsigned getHashValue(const MemoryBufferRef &mbref) { + return hash_combine(mbref.getBuffer().data(), mbref.getBuffer().size()); + } + static bool isEqual(const MemoryBufferRef &LHS, const MemoryBufferRef &RHS) { + return LHS.getBuffer().data() == RHS.getBuffer().data() && + LHS.getBuffer().size() == RHS.getBuffer().size(); + } +}; + } // end namespace llvm #endif // LLVM_SUPPORT_MEMORYBUFFER_H 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 @@ -32,13 +32,13 @@ #include "llvm/Object/IRObjectFile.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/SHA1.h" #include "llvm/Support/SourceMgr.h" #include "llvm/Support/TargetRegistry.h" -#include "llvm/Support/ThreadPool.h" #include "llvm/Support/Threading.h" #include "llvm/Support/TimeProfiler.h" #include "llvm/Support/VCSRevision.h" @@ -420,39 +420,50 @@ // Requires a destructor for std::vector. InputFile::~InputFile() = default; -Expected> InputFile::create(MemoryBufferRef Object) { - std::unique_ptr File(new InputFile); - - Expected FOrErr = readIRSymtab(Object); - if (!FOrErr) - return FOrErr.takeError(); +InputFile::InputFile(IRSymtabFile &SF) { + TargetTriple = SF.TheReader.getTargetTriple(); + SourceFileName = SF.TheReader.getSourceFileName(); + COFFLinkerOpts = SF.TheReader.getCOFFLinkerOpts(); + DependentLibraries = SF.TheReader.getDependentLibraries(); + ComdatTable = SF.TheReader.getComdatTable(); - File->TargetTriple = FOrErr->TheReader.getTargetTriple(); - File->SourceFileName = FOrErr->TheReader.getSourceFileName(); - File->COFFLinkerOpts = FOrErr->TheReader.getCOFFLinkerOpts(); - File->DependentLibraries = FOrErr->TheReader.getDependentLibraries(); - File->ComdatTable = FOrErr->TheReader.getComdatTable(); - - for (unsigned I = 0; I != FOrErr->Mods.size(); ++I) { - size_t Begin = File->Symbols.size(); + for (unsigned I = 0; I != SF.Mods.size(); ++I) { + size_t Begin = Symbols.size(); for (const irsymtab::Reader::SymbolRef &Sym : - FOrErr->TheReader.module_symbols(I)) + SF.TheReader.module_symbols(I)) // Skip symbols that are irrelevant to LTO. Note that this condition needs // to match the one in Skip() in LTO::addRegularLTO(). if (Sym.isGlobal() && !Sym.isFormatSpecific()) - File->Symbols.push_back(Sym); - File->ModuleSymIndices.push_back({Begin, File->Symbols.size()}); + Symbols.push_back(Sym); + ModuleSymIndices.push_back({Begin, Symbols.size()}); } - File->Mods = FOrErr->Mods; - File->Strtab = std::move(FOrErr->Strtab); - return std::move(File); + Mods = SF.Mods; + Strtab = std::move(SF.Strtab); +} + +Expected> InputFile::create(MemoryBufferRef Object) { + Expected FOrErr = readIRSymtab(Object); + if (!FOrErr) + return FOrErr.takeError(); + return std::unique_ptr(new InputFile(*FOrErr)); +} + +Expected> +InputFile::create(MemoryBufferRef Object, IRSymtabFileCache &IRSymTabCache) { + if (IRSymtabFile *FOrErr = IRSymTabCache.get(Object)) + return std::unique_ptr(new InputFile(*FOrErr)); + return InputFile::create(Object); } StringRef InputFile::getName() const { return Mods[0].getModuleIdentifier(); } +void InputFile::setName(StringRef Name) { + return Mods[0].setModuleIdentifier(Name); +} + BitcodeModule &InputFile::getSingleBitcodeModule() { assert(Mods.size() == 1 && "Expect only one bitcode module"); return Mods[0]; @@ -1409,6 +1420,77 @@ return BackendProc->wait(); } +IRSymtabFile *IRSymtabFileCache::get(const MemoryBufferRef &M) { + auto Iter = Cache.find(M); + if (Iter != Cache.end()) + return Iter->second.get(); + return nullptr; +} + +void IRSymtabFileCache::upgrade(const std::vector &Files, + AddFileFn AFF) { + std::vector MBrefs; + for (auto &F : Files) + AFF(F, LoadedFiles, MBrefs); + + // Cache IR SymTab only if it needs to be upgraded. Also try to balance the + // amount work each thread will do based on aggregate MemoryBufferRef size. + std::vector ToCache; + unsigned TotalBufSize = 0; + for (auto &M : MBrefs) + if (Cache.count(M) == 0 && needToUpgradeIRSymtab(M)) { + ToCache.push_back(M); + TotalBufSize += M.getBufferSize(); + Cache.try_emplace(M, nullptr); + } + + if (ToCache.size() == 0) + return; + + // If too few symtabs to cache, assign one to each thread. + const unsigned ThinLTOParallelismLevel = BackendThreadPool.getThreadCount(); + const unsigned ThreadLocal = ToCache.size() >= ThinLTOParallelismLevel + ? TotalBufSize / ThinLTOParallelismLevel + : 0; + + auto PreLoad = [&](std::vector::iterator II, + std::vector::iterator IE) { + for (; II != IE; ++II) { + Expected FOrErr = readIRSymtab(*II); + if (FOrErr) + Cache.find(*II)->second.reset(new IRSymtabFile(std::move(*FOrErr))); + else + consumeError(FOrErr.takeError()); + } + }; + + auto CI = ToCache.begin(); + unsigned CurTotal = 0; + unsigned NumThread = 0; + for (auto I = ToCache.begin(), E = ToCache.end(); I != E; ++I) { + CurTotal += I->getBufferSize(); + if (CurTotal < ThreadLocal) + continue; + + CurTotal = 0; + BackendThreadPool.async(PreLoad, CI, I + 1); + CI = I + 1; + if (++NumThread == (ThinLTOParallelismLevel - 1)) { + BackendThreadPool.async(PreLoad, CI, E); + break; + } + } + BackendThreadPool.wait(); +} + +Optional +IRSymtabFileCache::getMemBufferForPath(StringRef Path) { + auto I = LoadedFiles.find(Path); + if (I != LoadedFiles.end()) + return I->second; + return None; +} + Expected> lto::setupLLVMOptimizationRemarks( LLVMContext &Context, StringRef RemarksFilename, StringRef RemarksPasses, StringRef RemarksFormat, bool RemarksWithHotness, int Count) { diff --git a/llvm/lib/Object/IRObjectFile.cpp b/llvm/lib/Object/IRObjectFile.cpp --- a/llvm/lib/Object/IRObjectFile.cpp +++ b/llvm/lib/Object/IRObjectFile.cpp @@ -133,6 +133,18 @@ new IRObjectFile(*BCOrErr, std::move(Mods))); } +bool object::needToUpgradeIRSymtab(MemoryBufferRef MBRef) { + Expected BCOrErr = + IRObjectFile::findBitcodeInMemBuffer(MBRef); + if (!BCOrErr) + return false; + + Expected BFCOrErr = getBitcodeFileContents(*BCOrErr); + if (!BFCOrErr) + return false; + return irsymtab::needUpgrade(*BFCOrErr); +} + Expected object::readIRSymtab(MemoryBufferRef MBRef) { IRSymtabFile F; Expected BCOrErr = diff --git a/llvm/lib/Object/IRSymtab.cpp b/llvm/lib/Object/IRSymtab.cpp --- a/llvm/lib/Object/IRSymtab.cpp +++ b/llvm/lib/Object/IRSymtab.cpp @@ -309,7 +309,10 @@ assert(!IRMods.empty()); Hdr.Version = storage::Header::kCurrentVersion; - setStr(Hdr.Producer, kExpectedProducerName); + if (getenv("LLVM_REEVAL_IR_SYMTAB_PRODUCER")) + setStr(Hdr.Producer, getExpectedProducerName()); + else + setStr(Hdr.Producer, kExpectedProducerName); setStr(Hdr.TargetTriple, IRMods[0]->getTargetTriple()); setStr(Hdr.SourceFileName, IRMods[0]->getSourceFileName()); TT = Triple(IRMods[0]->getTargetTriple()); @@ -374,14 +377,13 @@ return std::move(FC); } -Expected irsymtab::readBitcode(const BitcodeFileContents &BFC) { +bool irsymtab::needUpgrade(const BitcodeFileContents &BFC) { if (BFC.Mods.empty()) - return make_error("Bitcode file does not contain any modules", - inconvertibleErrorCode()); + return false; if (BFC.StrtabForSymtab.empty() || BFC.Symtab.size() < sizeof(storage::Header)) - return upgrade(BFC.Mods); + return true; // We cannot use the regular reader to read the version and producer, because // it will expect the header to be in the current format. The only thing we @@ -392,7 +394,7 @@ StringRef Producer = Hdr->Producer.get(BFC.StrtabForSymtab); if (Version != storage::Header::kCurrentVersion || Producer != kExpectedProducerName) - return upgrade(BFC.Mods); + return true; FileContents FC; FC.TheReader = {{BFC.Symtab.data(), BFC.Symtab.size()}, @@ -403,7 +405,20 @@ // the bitcode file was created by binary concatenation, so we need to create // a new symbol table from scratch. if (FC.TheReader.getNumModules() != BFC.Mods.size()) - return upgrade(std::move(BFC.Mods)); + return true; + + return false; +} + +Expected irsymtab::readBitcode(const BitcodeFileContents &BFC) { + if (BFC.Mods.empty()) + return make_error("Bitcode file does not contain any modules", + inconvertibleErrorCode()); + if (needUpgrade(BFC)) + return upgrade(BFC.Mods); + FileContents FC; + FC.TheReader = {{BFC.Symtab.data(), BFC.Symtab.size()}, + {BFC.StrtabForSymtab.data(), BFC.StrtabForSymtab.size()}}; return std::move(FC); } diff --git a/llvm/unittests/CMakeLists.txt b/llvm/unittests/CMakeLists.txt --- a/llvm/unittests/CMakeLists.txt +++ b/llvm/unittests/CMakeLists.txt @@ -41,6 +41,7 @@ add_subdirectory(TableGen) add_subdirectory(Target) add_subdirectory(TextAPI) +add_subdirectory(ThinLTO) add_subdirectory(Transforms) add_subdirectory(XRay) add_subdirectory(tools) diff --git a/llvm/unittests/ThinLTO/CMakeLists.txt b/llvm/unittests/ThinLTO/CMakeLists.txt new file mode 100644 --- /dev/null +++ b/llvm/unittests/ThinLTO/CMakeLists.txt @@ -0,0 +1,11 @@ +set(LLVM_LINK_COMPONENTS + AsmParser + BinaryFormat + Core + LTO + Support + ) + +add_llvm_unittest(ThinLTOTests + PreUpgradeIRSymbolTable.cpp + ) diff --git a/llvm/unittests/ThinLTO/PreUpgradeIRSymbolTable.cpp b/llvm/unittests/ThinLTO/PreUpgradeIRSymbolTable.cpp new file mode 100644 --- /dev/null +++ b/llvm/unittests/ThinLTO/PreUpgradeIRSymbolTable.cpp @@ -0,0 +1,172 @@ +//===- llvm/unittest/ThinLTO/PreUpgradeIRSymbolTable.cpp ------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/Analysis/ModuleSummaryAnalysis.h" +#include "llvm/Analysis/ProfileSummaryInfo.h" +#include "llvm/AsmParser/Parser.h" +#include "llvm/BinaryFormat/Magic.h" +#include "llvm/Bitcode/BitcodeWriter.h" +#include "llvm/Config/config.h" +#include "llvm/IR/Module.h" +#include "llvm/LTO/LTO.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/ToolOutputFile.h" +#include "gtest/gtest.h" + +using namespace llvm; + +static ::testing::AssertionResult NoError(std::error_code EC) { + if (!EC) + return ::testing::AssertionSuccess(); + return ::testing::AssertionFailure() + << "error " << EC.value() << ": " << EC.message(); +} + +std::vector> AllBuffers; + +static void +addFileToIrSymTabCache(StringRef path, + llvm::StringMap &LoadedFiles, + std::vector &ToCache) { + auto mbOrErr = MemoryBuffer::getFile(path, -1, false); + if (!mbOrErr) + return; + std::unique_ptr &mb = *mbOrErr; + MemoryBufferRef mbref = mb->getMemBufferRef(); + AllBuffers.push_back(std::move(*mbOrErr)); + + LoadedFiles.try_emplace(path, mbref); + ToCache.push_back(mbref); +} + +namespace { +#ifdef _MSC_VER +#define setenv(name, var, ignore) _putenv_s(name, var) +#endif + +#if HAVE_SETENV || _MSC_VER +struct PreUpgradeIRSymbolTableTest : public ::testing::Test { + LLVMContext Context; + SMDiagnostic Err; + std::unique_ptr ValidModule; + std::unique_ptr InvalidModule; + + PreUpgradeIRSymbolTableTest() + : ValidModule(parseAssemblyString( + "target datalayout = \"e-m:e-i64:64-f80:128-n8:16:32:64-S128\"\n" + "define void @f() {\n" + " ret void\n" + "}\n", + Err, Context)), + InvalidModule(parseAssemblyString("define void @g() {\n" + " ret void\n" + "}\n", + Err, Context)) {} + + enum class InputKind { + ValidAndUpgradable, + ValidAndNonUpgradable, + Invalid, + }; + + void createInputFiles(std::vector &InputFiles, + const std::vector Inputs) { + for (auto Kind : Inputs) { + SmallString<64> TmpName; + sys::fs::createUniquePath("thinlto-preload-ir-symtab-%%%.o", TmpName, + /*MakeAbsolute=*/false); + + std::error_code EC; + std::unique_ptr Out = + std::make_unique(TmpName, EC, + llvm::sys::fs::OF_None); + ASSERT_TRUE(NoError(EC)); + Module &M = Kind == InputKind::Invalid ? *InvalidModule : *ValidModule; + ProfileSummaryInfo PSI(M); + auto Index = buildModuleSummaryIndex(M, nullptr, &PSI); + if (Kind == InputKind::ValidAndUpgradable) { + setenv("LLVM_OVERRIDE_PRODUCER", "old-producer", 1); + setenv("LLVM_REEVAL_IR_SYMTAB_PRODUCER", "1", 1); + } + WriteBitcodeToFile(M, Out->os(), true, &Index); + if (Kind == InputKind::ValidAndUpgradable) { + unsetenv("LLVM_REEVAL_IR_SYMTAB_PRODUCER"); + unsetenv("LLVM_OVERRIDE_PRODUCER"); + } + Out->keep(); + InputFiles.push_back(TmpName.c_str()); + } + } + + void testPreUpgradeForInputFiles(const std::vector Inputs) { + std::vector InputFiles; + createInputFiles(InputFiles, Inputs); + + // Upgrade the symbol tables of input files. + AllBuffers.clear(); + std::unique_ptr irSymTabCache = + std::make_unique(); + irSymTabCache->upgrade(InputFiles, addFileToIrSymTabCache); + + // * If InputKind == ValidAndUpgradable, verify the input files are cached + // and their symbol tables are upgraded. + // * If InputKind != ValidAndUpgradable, verify the input files are cached + // and their symbol tables are not upgraded. + for (unsigned i = 0; i < Inputs.size(); ++i) { + // Verify the input is cached. + Optional Object = + irSymTabCache->getMemBufferForPath(InputFiles[i]); + ASSERT_TRUE(Object.hasValue()); + + // Verify the input symbol table is upgraded. + object::IRSymtabFile *SymtabFile = irSymTabCache->get(*Object); + if (Inputs[i] == InputKind::ValidAndUpgradable) + ASSERT_TRUE(SymtabFile); + else + ASSERT_FALSE(SymtabFile); + (void)SymtabFile; + } + + // Clean up. + for (auto &F : InputFiles) { + std::error_code EC = sys::fs::remove(F); + ASSERT_TRUE(NoError(EC)); + } + } +}; + +TEST_F(PreUpgradeIRSymbolTableTest, AllUpgrade) { + const std::vector Inputs(10, InputKind::ValidAndUpgradable); + testPreUpgradeForInputFiles(Inputs); +} + +TEST_F(PreUpgradeIRSymbolTableTest, AllNonUpgrade) { + const std::vector Inputs(10, InputKind::ValidAndNonUpgradable); + testPreUpgradeForInputFiles(Inputs); +} + +TEST_F(PreUpgradeIRSymbolTableTest, AllInvalid) { + const std::vector Inputs(10, InputKind::Invalid); + testPreUpgradeForInputFiles(Inputs); +} + +TEST_F(PreUpgradeIRSymbolTableTest, MixedInputs) { + const std::vector Inputs1(5, InputKind::ValidAndUpgradable); + const std::vector Inputs2(5, InputKind::ValidAndNonUpgradable); + const std::vector Inputs3(5, InputKind::Invalid); + std::vector MixedInputs; + MixedInputs.insert(MixedInputs.end(), Inputs1.begin(), Inputs1.end()); + MixedInputs.insert(MixedInputs.end(), Inputs2.begin(), Inputs2.end()); + MixedInputs.insert(MixedInputs.end(), Inputs3.begin(), Inputs3.end()); + testPreUpgradeForInputFiles(MixedInputs); +} + +#endif + +} // End anonymous namespace.