diff options
Diffstat (limited to 'contrib/llvm-project/llvm/tools/llvm-lto2/llvm-lto2.cpp')
| -rw-r--r-- | contrib/llvm-project/llvm/tools/llvm-lto2/llvm-lto2.cpp | 560 |
1 files changed, 560 insertions, 0 deletions
diff --git a/contrib/llvm-project/llvm/tools/llvm-lto2/llvm-lto2.cpp b/contrib/llvm-project/llvm/tools/llvm-lto2/llvm-lto2.cpp new file mode 100644 index 000000000000..81c97a994038 --- /dev/null +++ b/contrib/llvm-project/llvm/tools/llvm-lto2/llvm-lto2.cpp @@ -0,0 +1,560 @@ +//===-- llvm-lto2: test harness for the resolution-based LTO interface ----===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This program takes in a list of bitcode files, links them and performs +// link-time optimization according to the provided symbol resolutions using the +// resolution-based LTO interface, and outputs one or more object files. +// +// This program is intended to eventually replace llvm-lto which uses the legacy +// LTO interface. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Bitcode/BitcodeReader.h" +#include "llvm/CodeGen/CommandFlags.h" +#include "llvm/IR/DiagnosticPrinter.h" +#include "llvm/LTO/LTO.h" +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Remarks/HotnessThresholdParser.h" +#include "llvm/Support/Caching.h" +#include "llvm/Support/CommandLine.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/InitLLVM.h" +#include "llvm/Support/PluginLoader.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Support/Threading.h" +#include <atomic> + +using namespace llvm; +using namespace lto; + +static codegen::RegisterCodeGenFlags CGF; + +static cl::opt<char> + OptLevel("O", + cl::desc("Optimization level. [-O0, -O1, -O2, or -O3] " + "(default = '-O2')"), + cl::Prefix, cl::init('2')); + +static cl::opt<char> CGOptLevel( + "cg-opt-level", + cl::desc("Codegen optimization level (0, 1, 2 or 3, default = '2')"), + cl::init('2')); + +static cl::list<std::string> InputFilenames(cl::Positional, cl::OneOrMore, + cl::desc("<input bitcode files>")); + +static cl::opt<std::string> OutputFilename("o", cl::Required, + cl::desc("Output filename"), + cl::value_desc("filename")); + +static cl::opt<std::string> CacheDir("cache-dir", cl::desc("Cache Directory"), + cl::value_desc("directory")); + +static cl::opt<std::string> OptPipeline("opt-pipeline", + cl::desc("Optimizer Pipeline"), + cl::value_desc("pipeline")); + +static cl::opt<std::string> AAPipeline("aa-pipeline", + cl::desc("Alias Analysis Pipeline"), + cl::value_desc("aapipeline")); + +static cl::opt<bool> SaveTemps("save-temps", cl::desc("Save temporary files")); + +static cl::list<std::string> SelectSaveTemps( + "select-save-temps", + cl::value_desc("One, or multiple of: " + "resolution,preopt,promote,internalize,import,opt,precodegen" + ",combinedindex"), + cl::desc("Save selected temporary files. Cannot be specified together with " + "-save-temps"), + cl::CommaSeparated); + +constexpr const char *SaveTempsValues[] = { + "resolution", "preopt", "promote", "internalize", + "import", "opt", "precodegen", "combinedindex"}; + +static cl::opt<bool> + ThinLTODistributedIndexes("thinlto-distributed-indexes", + cl::desc("Write out individual index and " + "import files for the " + "distributed backend case")); + +static cl::opt<bool> + ThinLTOEmitIndexes("thinlto-emit-indexes", + cl::desc("Write out individual index files via " + "InProcessThinLTO")); + +static cl::opt<bool> + ThinLTOEmitImports("thinlto-emit-imports", + cl::desc("Write out individual imports files via " + "InProcessThinLTO. Has no effect unless " + "specified with -thinlto-emit-indexes or " + "-thinlto-distributed-indexes")); + +// Default to using all available threads in the system, but using only one +// thread per core (no SMT). +// Use -thinlto-threads=all to use hardware_concurrency() instead, which means +// to use all hardware threads or cores in the system. +static cl::opt<std::string> Threads("thinlto-threads"); + +static cl::list<std::string> SymbolResolutions( + "r", + cl::desc("Specify a symbol resolution: filename,symbolname,resolution\n" + "where \"resolution\" is a sequence (which may be empty) of the\n" + "following characters:\n" + " p - prevailing: the linker has chosen this definition of the\n" + " symbol\n" + " l - local: the definition of this symbol is unpreemptable at\n" + " runtime and is known to be in this linkage unit\n" + " x - externally visible: the definition of this symbol is\n" + " visible outside of the LTO unit\n" + "A resolution for each symbol must be specified")); + +static cl::opt<std::string> OverrideTriple( + "override-triple", + cl::desc("Replace target triples in input files with this triple")); + +static cl::opt<std::string> DefaultTriple( + "default-triple", + cl::desc( + "Replace unspecified target triples in input files with this triple")); + +static cl::opt<bool> RemarksWithHotness( + "pass-remarks-with-hotness", + cl::desc("With PGO, include profile count in optimization remarks"), + cl::Hidden); + +cl::opt<std::optional<uint64_t>, false, remarks::HotnessThresholdParser> + RemarksHotnessThreshold( + "pass-remarks-hotness-threshold", + cl::desc("Minimum profile count required for an " + "optimization remark to be output." + " Use 'auto' to apply the threshold from profile summary."), + cl::value_desc("uint or 'auto'"), cl::init(0), cl::Hidden); + +static cl::opt<std::string> + RemarksFilename("pass-remarks-output", + cl::desc("Output filename for pass remarks"), + cl::value_desc("filename")); + +static cl::opt<std::string> + RemarksPasses("pass-remarks-filter", + cl::desc("Only record optimization remarks from passes whose " + "names match the given regular expression"), + cl::value_desc("regex")); + +static cl::opt<std::string> RemarksFormat( + "pass-remarks-format", + cl::desc("The format used for serializing remarks (default: YAML)"), + cl::value_desc("format"), cl::init("yaml")); + +static cl::opt<std::string> + SamplePGOFile("lto-sample-profile-file", + cl::desc("Specify a SamplePGO profile file")); + +static cl::opt<std::string> + CSPGOFile("lto-cspgo-profile-file", + cl::desc("Specify a context sensitive PGO profile file")); + +static cl::opt<bool> + RunCSIRInstr("lto-cspgo-gen", + cl::desc("Run PGO context sensitive IR instrumentation"), + cl::Hidden); + +static cl::opt<bool> + DebugPassManager("debug-pass-manager", cl::Hidden, + cl::desc("Print pass management debugging information")); + +static cl::opt<std::string> + StatsFile("stats-file", cl::desc("Filename to write statistics to")); + +static cl::list<std::string> + PassPlugins("load-pass-plugin", + cl::desc("Load passes from plugin library")); + +static cl::opt<std::string> UnifiedLTOMode("unified-lto", cl::Optional, + cl::desc("Set LTO mode"), + cl::value_desc("mode")); + +static cl::opt<bool> EnableFreestanding( + "lto-freestanding", + cl::desc("Enable Freestanding (disable builtins / TLI) during LTO"), + cl::Hidden); + +static void check(Error E, std::string Msg) { + if (!E) + return; + handleAllErrors(std::move(E), [&](ErrorInfoBase &EIB) { + errs() << "llvm-lto2: " << Msg << ": " << EIB.message().c_str() << '\n'; + }); + exit(1); +} + +template <typename T> static T check(Expected<T> E, std::string Msg) { + if (E) + return std::move(*E); + check(E.takeError(), Msg); + return T(); +} + +static void check(std::error_code EC, std::string Msg) { + check(errorCodeToError(EC), Msg); +} + +template <typename T> static T check(ErrorOr<T> E, std::string Msg) { + if (E) + return std::move(*E); + check(E.getError(), Msg); + return T(); +} + +static int usage() { + errs() << "Available subcommands: dump-symtab run\n"; + return 1; +} + +static int run(int argc, char **argv) { + cl::ParseCommandLineOptions(argc, argv, "Resolution-based LTO test harness"); + + // FIXME: Workaround PR30396 which means that a symbol can appear + // more than once if it is defined in module-level assembly and + // has a GV declaration. We allow (file, symbol) pairs to have multiple + // resolutions and apply them in the order observed. + std::map<std::pair<std::string, std::string>, std::list<SymbolResolution>> + CommandLineResolutions; + for (std::string R : SymbolResolutions) { + StringRef Rest = R; + StringRef FileName, SymbolName; + std::tie(FileName, Rest) = Rest.split(','); + if (Rest.empty()) { + llvm::errs() << "invalid resolution: " << R << '\n'; + return 1; + } + std::tie(SymbolName, Rest) = Rest.split(','); + SymbolResolution Res; + for (char C : Rest) { + if (C == 'p') + Res.Prevailing = true; + else if (C == 'l') + Res.FinalDefinitionInLinkageUnit = true; + else if (C == 'x') + Res.VisibleToRegularObj = true; + else if (C == 'r') + Res.LinkerRedefined = true; + else { + llvm::errs() << "invalid character " << C << " in resolution: " << R + << '\n'; + return 1; + } + } + CommandLineResolutions[{std::string(FileName), std::string(SymbolName)}] + .push_back(Res); + } + + std::vector<std::unique_ptr<MemoryBuffer>> MBs; + + Config Conf; + + Conf.CPU = codegen::getMCPU(); + Conf.Options = codegen::InitTargetOptionsFromCodeGenFlags(Triple()); + Conf.MAttrs = codegen::getMAttrs(); + if (auto RM = codegen::getExplicitRelocModel()) + Conf.RelocModel = *RM; + Conf.CodeModel = codegen::getExplicitCodeModel(); + + Conf.DebugPassManager = DebugPassManager; + + if (SaveTemps && !SelectSaveTemps.empty()) { + llvm::errs() << "-save-temps cannot be specified with -select-save-temps\n"; + return 1; + } + if (SaveTemps || !SelectSaveTemps.empty()) { + DenseSet<StringRef> SaveTempsArgs; + for (auto &S : SelectSaveTemps) + if (is_contained(SaveTempsValues, S)) + SaveTempsArgs.insert(S); + else { + llvm::errs() << ("invalid -select-save-temps argument: " + S) << '\n'; + return 1; + } + check(Conf.addSaveTemps(OutputFilename + ".", false, SaveTempsArgs), + "Config::addSaveTemps failed"); + } + + // Optimization remarks. + Conf.RemarksFilename = RemarksFilename; + Conf.RemarksPasses = RemarksPasses; + Conf.RemarksWithHotness = RemarksWithHotness; + Conf.RemarksHotnessThreshold = RemarksHotnessThreshold; + Conf.RemarksFormat = RemarksFormat; + + Conf.SampleProfile = SamplePGOFile; + Conf.CSIRProfile = CSPGOFile; + Conf.RunCSIRInstr = RunCSIRInstr; + + // Run a custom pipeline, if asked for. + Conf.OptPipeline = OptPipeline; + Conf.AAPipeline = AAPipeline; + + Conf.OptLevel = OptLevel - '0'; + Conf.Freestanding = EnableFreestanding; + for (auto &PluginFN : PassPlugins) + Conf.PassPlugins.push_back(PluginFN); + if (auto Level = CodeGenOpt::parseLevel(CGOptLevel)) { + Conf.CGOptLevel = *Level; + } else { + llvm::errs() << "invalid cg optimization level: " << CGOptLevel << '\n'; + return 1; + } + + if (auto FT = codegen::getExplicitFileType()) + Conf.CGFileType = *FT; + + Conf.OverrideTriple = OverrideTriple; + Conf.DefaultTriple = DefaultTriple; + Conf.StatsFile = StatsFile; + Conf.PTO.LoopVectorization = Conf.OptLevel > 1; + Conf.PTO.SLPVectorization = Conf.OptLevel > 1; + + ThinBackend Backend; + if (ThinLTODistributedIndexes) + Backend = createWriteIndexesThinBackend(/*OldPrefix=*/"", + /*NewPrefix=*/"", + /*NativeObjectPrefix=*/"", + ThinLTOEmitImports, + /*LinkedObjectsFile=*/nullptr, + /*OnWrite=*/{}); + else + Backend = createInProcessThinBackend( + llvm::heavyweight_hardware_concurrency(Threads), + /* OnWrite */ {}, ThinLTOEmitIndexes, ThinLTOEmitImports); + + // Track whether we hit an error; in particular, in the multi-threaded case, + // we can't exit() early because the rest of the threads wouldn't have had a + // change to be join-ed, and that would result in a "terminate called without + // an active exception". Altogether, this results in nondeterministic + // behavior. Instead, we don't exit in the multi-threaded case, but we make + // sure to report the error and then at the end (after joining cleanly) + // exit(1). + std::atomic<bool> HasErrors; + std::atomic_init(&HasErrors, false); + Conf.DiagHandler = [&](const DiagnosticInfo &DI) { + DiagnosticPrinterRawOStream DP(errs()); + DI.print(DP); + errs() << '\n'; + if (DI.getSeverity() == DS_Error) + HasErrors = true; + }; + + LTO::LTOKind LTOMode = LTO::LTOK_Default; + + if (UnifiedLTOMode == "full") { + LTOMode = LTO::LTOK_UnifiedRegular; + } else if (UnifiedLTOMode == "thin") { + LTOMode = LTO::LTOK_UnifiedThin; + } else if (UnifiedLTOMode == "default") { + LTOMode = LTO::LTOK_Default; + } else if (!UnifiedLTOMode.empty()) { + llvm::errs() << "invalid LTO mode\n"; + return 1; + } + + LTO Lto(std::move(Conf), std::move(Backend), 1, LTOMode); + + for (std::string F : InputFilenames) { + std::unique_ptr<MemoryBuffer> MB = check(MemoryBuffer::getFile(F), F); + std::unique_ptr<InputFile> Input = + check(InputFile::create(MB->getMemBufferRef()), F); + + std::vector<SymbolResolution> Res; + for (const InputFile::Symbol &Sym : Input->symbols()) { + auto I = CommandLineResolutions.find({F, std::string(Sym.getName())}); + // If it isn't found, look for ".", which would have been added + // (followed by a hash) when the symbol was promoted during module + // splitting if it was defined in one part and used in the other. + // Try looking up the symbol name before the suffix. + if (I == CommandLineResolutions.end()) { + auto SplitName = Sym.getName().rsplit("."); + I = CommandLineResolutions.find({F, std::string(SplitName.first)}); + } + if (I == CommandLineResolutions.end()) { + llvm::errs() << argv[0] << ": missing symbol resolution for " << F + << ',' << Sym.getName() << '\n'; + HasErrors = true; + } else { + Res.push_back(I->second.front()); + I->second.pop_front(); + if (I->second.empty()) + CommandLineResolutions.erase(I); + } + } + + if (HasErrors) + continue; + + MBs.push_back(std::move(MB)); + check(Lto.add(std::move(Input), Res), F); + } + + if (!CommandLineResolutions.empty()) { + HasErrors = true; + for (auto UnusedRes : CommandLineResolutions) + llvm::errs() << argv[0] << ": unused symbol resolution for " + << UnusedRes.first.first << ',' << UnusedRes.first.second + << '\n'; + } + if (HasErrors) + return 1; + + auto AddStream = + [&](size_t Task, + const Twine &ModuleName) -> std::unique_ptr<CachedFileStream> { + std::string Path = OutputFilename + "." + utostr(Task); + + std::error_code EC; + auto S = std::make_unique<raw_fd_ostream>(Path, EC, sys::fs::OF_None); + check(EC, Path); + return std::make_unique<CachedFileStream>(std::move(S), Path); + }; + + auto AddBuffer = [&](size_t Task, const Twine &ModuleName, + std::unique_ptr<MemoryBuffer> MB) { + *AddStream(Task, ModuleName)->OS << MB->getBuffer(); + }; + + FileCache Cache; + if (!CacheDir.empty()) + Cache = check(localCache("ThinLTO", "Thin", CacheDir, AddBuffer), + "failed to create cache"); + + check(Lto.run(AddStream, Cache), "LTO::run failed"); + return static_cast<int>(HasErrors); +} + +static int dumpSymtab(int argc, char **argv) { + for (StringRef F : make_range(argv + 1, argv + argc)) { + std::unique_ptr<MemoryBuffer> MB = + check(MemoryBuffer::getFile(F), std::string(F)); + BitcodeFileContents BFC = + check(getBitcodeFileContents(*MB), std::string(F)); + + if (BFC.Symtab.size() >= sizeof(irsymtab::storage::Header)) { + auto *Hdr = reinterpret_cast<const irsymtab::storage::Header *>( + BFC.Symtab.data()); + outs() << "version: " << Hdr->Version << '\n'; + if (Hdr->Version == irsymtab::storage::Header::kCurrentVersion) + outs() << "producer: " << Hdr->Producer.get(BFC.StrtabForSymtab) + << '\n'; + } + + std::unique_ptr<InputFile> Input = + check(InputFile::create(MB->getMemBufferRef()), std::string(F)); + + outs() << "target triple: " << Input->getTargetTriple() << '\n'; + Triple TT(Input->getTargetTriple()); + + outs() << "source filename: " << Input->getSourceFileName() << '\n'; + + if (TT.isOSBinFormatCOFF()) + outs() << "linker opts: " << Input->getCOFFLinkerOpts() << '\n'; + + if (TT.isOSBinFormatELF()) { + outs() << "dependent libraries:"; + for (auto L : Input->getDependentLibraries()) + outs() << " \"" << L << "\""; + outs() << '\n'; + } + + ArrayRef<std::pair<StringRef, Comdat::SelectionKind>> ComdatTable = + Input->getComdatTable(); + for (const InputFile::Symbol &Sym : Input->symbols()) { + switch (Sym.getVisibility()) { + case GlobalValue::HiddenVisibility: + outs() << 'H'; + break; + case GlobalValue::ProtectedVisibility: + outs() << 'P'; + break; + case GlobalValue::DefaultVisibility: + outs() << 'D'; + break; + } + + auto PrintBool = [&](char C, bool B) { outs() << (B ? C : '-'); }; + PrintBool('U', Sym.isUndefined()); + PrintBool('C', Sym.isCommon()); + PrintBool('W', Sym.isWeak()); + PrintBool('I', Sym.isIndirect()); + PrintBool('O', Sym.canBeOmittedFromSymbolTable()); + PrintBool('T', Sym.isTLS()); + PrintBool('X', Sym.isExecutable()); + outs() << ' ' << Sym.getName() << '\n'; + + if (Sym.isCommon()) + outs() << " size " << Sym.getCommonSize() << " align " + << Sym.getCommonAlignment() << '\n'; + + int Comdat = Sym.getComdatIndex(); + if (Comdat != -1) { + outs() << " comdat "; + switch (ComdatTable[Comdat].second) { + case Comdat::Any: + outs() << "any"; + break; + case Comdat::ExactMatch: + outs() << "exactmatch"; + break; + case Comdat::Largest: + outs() << "largest"; + break; + case Comdat::NoDeduplicate: + outs() << "nodeduplicate"; + break; + case Comdat::SameSize: + outs() << "samesize"; + break; + } + outs() << ' ' << ComdatTable[Comdat].first << '\n'; + } + + if (TT.isOSBinFormatCOFF() && Sym.isWeak() && Sym.isIndirect()) + outs() << " fallback " << Sym.getCOFFWeakExternalFallback() << '\n'; + + if (!Sym.getSectionName().empty()) + outs() << " section " << Sym.getSectionName() << "\n"; + } + + outs() << '\n'; + } + + return 0; +} + +int main(int argc, char **argv) { + InitLLVM X(argc, argv); + InitializeAllTargets(); + InitializeAllTargetMCs(); + InitializeAllAsmPrinters(); + InitializeAllAsmParsers(); + + // FIXME: This should use llvm::cl subcommands, but it isn't currently + // possible to pass an argument not associated with a subcommand to a + // subcommand (e.g. -use-new-pm). + if (argc < 2) + return usage(); + + StringRef Subcommand = argv[1]; + // Ensure that argv[0] is correct after adjusting argv/argc. + argv[1] = argv[0]; + if (Subcommand == "dump-symtab") + return dumpSymtab(argc - 1, argv + 1); + if (Subcommand == "run") + return run(argc - 1, argv + 1); + return usage(); +} |
