diff --git a/include/eld/Config/GeneralOptions.h b/include/eld/Config/GeneralOptions.h index 66dcee806..0d8418ba2 100644 --- a/include/eld/Config/GeneralOptions.h +++ b/include/eld/Config/GeneralOptions.h @@ -410,6 +410,15 @@ class GeneralOptions { return UnparsedLTOOptions; } + const std::optional &getLTOObjPath() const { return LTOObjPath; } + void setLTOObjPath(const std::string &V) { LTOObjPath = V; } + + void setThinLTOJobs(llvm::StringRef V) { ThinLTOJobs = V; } + llvm::StringRef getThinLTOJobs() const { return ThinLTOJobs; } + + void setLTOPartitions(unsigned V) { LTOPartitions = V; } + unsigned getLTOPartitions() const { return LTOPartitions; } + void getSymbolsFromFile(llvm::StringRef Filename, std::vector &); void setCopyFarCallsFromFile(std::string File) { @@ -1214,9 +1223,12 @@ class GeneralOptions { bool Savetemps = false; // -save-temps std::optional SaveTempsDir; // -save-temps= bool Rosegment = false; // merge read only with readonly/execute segments. + std::optional LTOObjPath; // --lto-obj-path= std::vector UnparsedLTOOptions; // Unparsed -flto-options, to pass to plugin. uint32_t LTOOptions = 0; // -flto-options + llvm::StringRef ThinLTOJobs; // --thinlto-jobs= + unsigned LTOPartitions = 1; // --lto-partitions= bool Verify = true; // Linker verifies output file. bool Colormap = false; // Map file with color. bool EnableThreads = true; // threads enabled ? diff --git a/include/eld/Driver/GnuLdDriver.h b/include/eld/Driver/GnuLdDriver.h index d6321edf3..a556153b6 100644 --- a/include/eld/Driver/GnuLdDriver.h +++ b/include/eld/Driver/GnuLdDriver.h @@ -145,6 +145,8 @@ class DLL_A_EXPORT GnuLdDriver { getAllArgs(const std::vector &allArgs, const std::vector &ELDFlagsArgs) const; + bool isRunLTOOnly() const { return m_RunLTOOnly; } + protected: int getInteger(llvm::opt::InputArgList &Args, unsigned Key, int Default) const; @@ -191,6 +193,10 @@ class DLL_A_EXPORT GnuLdDriver { DriverFlavor m_DriverFlavor; std::vector m_SupportedTargets; std::string LinkerProgramName; + + // In LTO, only invoke compiler to generate the output file, but do not link + // it. This option is used by --lto-emit-llvm and --lto-emit-asm options. + bool m_RunLTOOnly = false; }; #endif diff --git a/include/eld/Driver/GnuLinkerOptions.td b/include/eld/Driver/GnuLinkerOptions.td index fdbc0d209..01698e054 100644 --- a/include/eld/Driver/GnuLinkerOptions.td +++ b/include/eld/Driver/GnuLinkerOptions.td @@ -1073,6 +1073,9 @@ defm save_temps_EQ : smDashOnlyEq<"save-temps", "save_temps_EQ", "Save the temporary files produced by LTO">, MetaVarName<"">, Group; +def : F<"plugin-opt=save-temps">, + Alias, + HelpText<"Alias for --save-temps">; defm exclude_lto_filelist : smDashWithOpt<"exclude-lto-filelist", "exclude_lto_filelist", "Specify a list of files that are to be disregarded while " @@ -1087,11 +1090,51 @@ defm include_lto_filelist "be used for LTO. This has no effect if -flto switch is used">, MetaVarName<"">, Group; + +def lto_cs_profile_generate + : FF<"lto-cs-profile-generate">, + HelpText<"Perform context sensitive PGO instrumentation">; +def lto_cs_profile_file : JJ<"lto-cs-profile-file=">, + HelpText<"Context sensitive profile file path">; + +def : F<"plugin-opt=cs-profile-generate">, + Alias, + HelpText<"Alias for --lto-cs-profile-generate">; +def : J<"plugin-opt=cs-profile-path=">, + Alias, + HelpText<"Alias for --lto-cs-profile-file">; + +def disable_verify : F<"disable-verify">; +def : F<"plugin-opt=disable-verify">, + Alias, + HelpText<"Alias for --disable-verify">; + defm dwodir : smDash<"dwodir", "dwodir", "Directory to save .dwo files when split DWARF is used in LTO">, Group; +def lto_obj_path_eq : JJ<"lto-obj-path=">; +def : J<"plugin-opt=obj-path=">, + Alias, + HelpText<"Alias for --lto-obj-path=">; + +def lto_emit_asm : FF<"lto-emit-asm">, HelpText<"Emit assembly code">; +def plugin_opt_emit_asm : F<"plugin-opt=emit-asm">, + Alias, + HelpText<"Alias for --lto-emit-asm">; + +def lto_emit_llvm : FF<"lto-emit-llvm">, HelpText<"Emit LLVM-IR bitcode">; +def plugin_opt_emit_llvm : F<"plugin-opt=emit-llvm">, + Alias, + HelpText<"Alias for --lto-emit-llvm">; + +def thinlto_jobs_eq : JJ<"thinlto-jobs=">, + HelpText<"Number of ThinLTO jobs. Default to --threads=">; +def : J<"plugin-opt=jobs=">, + Alias, + HelpText<"Alias for --thinlto-jobs=">; + def lto_O : JJ<"lto-O">, MetaVarName<"">, HelpText<"Optimization level for LTO">; @@ -1107,6 +1150,12 @@ defm plugin_opt_sample_profile "Alias for -lto-sample-profile=">, Group; +def lto_partitions : JJ<"lto-partitions=">, + HelpText<"Number of LTO codegen partitions">; +def : J<"plugin-opt=lto-partitions=">, + Alias, + HelpText<"Alias for --lto-partitions">; + def lto_debug_pass_manager : FF<"lto-debug-pass-manager">, HelpText<"Debug new pass manager">, Group; diff --git a/include/eld/Object/ObjectLinker.h b/include/eld/Object/ObjectLinker.h index e4e362aa5..d3411ad72 100644 --- a/include/eld/Object/ObjectLinker.h +++ b/include/eld/Object/ObjectLinker.h @@ -365,13 +365,14 @@ class ObjectLinker { } private: - std::unique_ptr ltoInit(llvm::lto::Config Conf); + std::unique_ptr ltoInit(llvm::lto::Config Conf, + bool CompileToAssembly); bool finalizeLtoSymbolResolution(llvm::lto::LTO <O, const std::vector &BitCodeFiles); - bool doLto(llvm::lto::LTO <O); + bool doLto(llvm::lto::LTO <O, bool CompileToAssembly); /// writeRelocationResult - helper function of syncRelocationResult, write /// relocation target data to output @@ -415,8 +416,10 @@ class ObjectLinker { std::string getLTOTempPrefix() const; + std::unique_ptr createLTOOutputFile() const; + llvm::Expected> - createLTOTempFile(size_t Task, const std::string &Suffix, + createLTOTempFile(size_t Number, bool Asm, llvm::SmallString<256> &FileName) const; /// Adds input files to outputTar if --reproduce option is used @@ -497,7 +500,7 @@ class ObjectLinker { bool MTraceLTO = false; - std::string MLtoTempPrefix; + std::optional MLtoTempPrefix; // Paths of all generated LTO objects std::vector LtoObjects; diff --git a/lib/Core/Linker.cpp b/lib/Core/Linker.cpp index bbaf23828..f23c9b260 100644 --- a/lib/Core/Linker.cpp +++ b/lib/Core/Linker.cpp @@ -410,6 +410,9 @@ bool Linker::normalize() { if (!ObjLinker->createLTOObject()) return false; + if (Driver->isRunLTOOnly()) + return true; + if (ThisModule->getPrinter()->isVerbose()) ThisConfig->raise(Diag::beginning_post_LTO_phase); LinkerProgress->incrementAndDisplayProgress(); diff --git a/lib/LinkerWrapper/GnuLdDriver.cpp b/lib/LinkerWrapper/GnuLdDriver.cpp index 480ceb6a5..71e201974 100644 --- a/lib/LinkerWrapper/GnuLdDriver.cpp +++ b/lib/LinkerWrapper/GnuLdDriver.cpp @@ -1555,15 +1555,59 @@ bool GnuLdDriver::processLTOOptions(llvm::lto::Config &Conf, for (opt::Arg *Arg : Args.filtered(OptTable::plugin_opt_eq_minus)) LLVMOptions.push_back(std::string("-") + Arg->getValue()); + if (const auto *Arg = Args.getLastArg(OptTable::lto_cs_profile_file)) + Conf.CSIRProfile = Arg->getValue(); + + if (Args.hasArg(OptTable::lto_cs_profile_generate)) + Conf.RunCSIRInstr = true; + if (const auto *Arg = Args.getLastArg(OptTable::dwodir)) Conf.DwoDir = Arg->getValue(); + if (Args.hasArg(OptTable::lto_emit_llvm)) + m_RunLTOOnly = true; + + if (Args.hasArg(OptTable::lto_emit_asm)) { + Conf.CGFileType = CodeGenFileType::AssemblyFile; + Conf.Options.MCOptions.AsmVerbose = true; + m_RunLTOOnly = true; + } + + if (const auto *Arg = Args.getLastArg(OptTable::thinlto_jobs_eq)) { + llvm::StringRef S = Arg->getValue(); + if (!get_threadpool_strategy(S)) { + Config.raise(Diag::invalid_value_for_option) + << Arg->getOption().getPrefixedName() << S; + return false; + } + Config.options().setThinLTOJobs(S); + } + + if (const auto *Arg = Args.getLastArg(OptTable::lto_obj_path_eq)) { + Conf.AlwaysEmitRegularLTOObj = true; + Config.options().setLTOObjPath(Arg->getValue()); + } + + if (const auto *Arg = Args.getLastArg(OptTable::lto_partitions)) { + llvm::StringRef S = Arg->getValue(); + unsigned Value; + if (S.getAsInteger(0, Value) || Value == 0) { + Config.raise(Diag::invalid_value_for_option) + << Arg->getOption().getPrefixedName() << S; + return false; + } + Config.options().setLTOPartitions(Value); + } + if (const auto *Arg = Args.getLastArg(OptTable::lto_sample_profile)) Conf.SampleProfile = Arg->getValue(); if (Args.hasArg(OptTable::lto_debug_pass_manager)) Conf.DebugPassManager = true; + if (Args.hasArg(OptTable::disable_verify)) + Conf.DisableVerify = true; + if (const auto *Arg = Args.getLastArg(OptTable::lto_O)) { llvm::StringRef S = Arg->getValue(); uint64_t Value; @@ -1733,12 +1777,14 @@ bool GnuLdDriver::doLink(llvm::opt::InputArgList &Args, // llvm::errs() << "prepare: linkStatus: " << linkStatus << "\n"; if (!linkStatus || Config.options().getRecordInputFiles()) handleReproduce(Args, actions, false); - if (linkStatus) - linkStatus = linker.link(); - // llvm::errs() << "link: linkStatus: " << linkStatus << "\n"; + if (!isRunLTOOnly()) { + if (linkStatus) + linkStatus = linker.link(); + // llvm::errs() << "link: linkStatus: " << linkStatus << "\n"; + linker.printLayout(); + } if (!linkStatus || Config.options().getRecordInputFiles()) handleReproduce(Args, actions, true); - linker.printLayout(); linkStatus &= ThisModule->getPluginManager().callDestroyHook(); // llvm::errs() << "destroy hook: linkStatus: " << linkStatus << "\n"; linker.unloadPlugins(); diff --git a/lib/Object/ObjectLinker.cpp b/lib/Object/ObjectLinker.cpp index 839858cf4..36ab1c455 100644 --- a/lib/Object/ObjectLinker.cpp +++ b/lib/Object/ObjectLinker.cpp @@ -65,6 +65,7 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/BinaryFormat/ELF.h" +#include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/CodeGen/CommandFlags.h" #include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h" #include "llvm/IR/DiagnosticPrinter.h" @@ -111,7 +112,6 @@ ObjectLinker::ObjectLinker(LinkerConfig &PConfig, Module &M) : ThisConfig(PConfig), ThisModule(&M) { MSaveTemps = ThisConfig.options().getSaveTemps(); MTraceLTO = ThisConfig.options().traceLTO(); - MLtoTempPrefix = getLTOTempPrefix(); } void ObjectLinker::close() { @@ -452,16 +452,41 @@ std::string ObjectLinker::getLTOTempPrefix() const { } llvm::Expected> -ObjectLinker::createLTOTempFile(size_t Task, const std::string &Suffix, +ObjectLinker::createLTOTempFile(size_t Number, bool Asm, SmallString<256> &FileName) const { + std::optional Prefix; + std::string Suffix; + if (Asm) { + Suffix = "s"; + // Also use non-temporary location for output asm file with -emit-asm, + // independently from -save-temps. + Prefix = ThisModule->getLinker()->getLinkerDriver()->isRunLTOOnly() + ? getLTOTempPrefix() + : MLtoTempPrefix; + } else { + if (auto P = ThisConfig.options().getLTOObjPath()) { + // Do not add suffix to file names created from --lto-obj-path. + // It may be worth to not add ".0" even without --lto-obj-path, + // but for now, keep existing convention. + if (Number == 0) + Number = -1; + Prefix = P; + } else { + Suffix = "o"; + Prefix = MLtoTempPrefix; + } + } + int FD; std::error_code EC; std::string ErrMsg; - if (MSaveTemps) { - FileName = - (Twine(MLtoTempPrefix) + Twine(Task) + Twine(".") + Twine(Suffix)) - .str(); + if (Prefix) { + FileName = *Prefix; + if (Number != size_t(-1)) + FileName += Twine(Number).str(); + if (!Suffix.empty()) + FileName += "." + Suffix; EC = llvm::sys::fs::openFileForWrite(FileName, FD); ErrMsg = std::string(FileName); } else { @@ -477,6 +502,19 @@ ObjectLinker::createLTOTempFile(size_t Task, const std::string &Suffix, return std::make_unique(FD, true); } +std::unique_ptr +ObjectLinker::createLTOOutputFile() const { + int FD; + std::string FileName = ThisConfig.options().outputFileName(); + if (std::error_code EC = llvm::sys::fs::openFileForWrite(FileName, FD)) { + ThisConfig.raise(Diag::fatal_no_codegen_compile) + << FileName << ": " << EC.message(); + return {}; + } + + return std::make_unique(FD, true); +} + void ObjectLinker::addInputFileToTar(const std::string &Name, eld::MappingFile::Kind K) { auto *OutputTar = ThisModule->getOutputTarWriter(); @@ -2663,35 +2701,18 @@ static void ltoDiagnosticHandler(const llvm::DiagnosticInfo &DI) { } } -bool ObjectLinker::runAssembler( - std::vector &Files, std::string RelocModel, - const std::vector &AsmFilesFromLto) { - std::vector FileList; - - if (ThisConfig.options().hasLTOAsmFile()) { - for (auto &F : ThisConfig.options().ltoAsmFile()) { - ThisConfig.raise(Diag::using_lto_asm_file) << F; - FileList.push_back(F); - } - } else { - for (auto &F : AsmFilesFromLto) { - if (!F.empty()) - FileList.push_back(F); - } - } - - if (ThisConfig.options().hasLTOOutputFile()) { - for (auto &F : ThisConfig.options().ltoOutputFile()) { - ThisConfig.raise(Diag::using_lto_output_file) << F; - Files.push_back(F); - } - return true; - } - - uint32_t Count = 0; - for (auto F : FileList) { +bool ObjectLinker::runAssembler(std::vector &Files, + std::string RelocModel, + const std::vector &FileList) { + for (unsigned Count = 0; Count != FileList.size(); ++Count) { + const auto &F = FileList[Count]; + // Some array elements may be empty if LTO did not produce anything for + // this slot. In that case, do not run assembler but keep incrementing the + // index. + if (F.empty()) + continue; SmallString<256> OutputFileName; - auto OS = createLTOTempFile(Count, "o", OutputFileName); + auto OS = createLTOTempFile(Count, false, OutputFileName); if (!OS) return false; Files.push_back(std::string(OutputFileName)); @@ -2699,13 +2720,12 @@ bool ObjectLinker::runAssembler( if (!getTargetBackend().ltoCallExternalAssembler( F, RelocModel, std::string(OutputFileName))) return false; - - ++Count; } return true; } -std::unique_ptr ObjectLinker::ltoInit(llvm::lto::Config Conf) { +std::unique_ptr ObjectLinker::ltoInit(llvm::lto::Config Conf, + bool CompileToAssembly) { // Parse codegen options and pre-initialize the config eld::RegisterTimer T("Initialize LTO", "LTO", ThisConfig.options().printTimingStats()); @@ -2739,7 +2759,7 @@ std::unique_ptr ObjectLinker::ltoInit(llvm::lto::Config Conf) { Conf.DefaultTriple = ThisConfig.targets().triple().str(); - if (getTargetBackend().ltoNeedAssembler()) + if (CompileToAssembly) Conf.CGFileType = llvm::CodeGenFileType::AssemblyFile; auto Model = ltoCodegenModel(); @@ -2751,30 +2771,44 @@ std::unique_ptr ObjectLinker::ltoInit(llvm::lto::Config Conf) { // want to curtail this to just two as before (for RegularLTO) and perhaps // more for ThinLTO. if (MTraceLTO || MSaveTemps) { - if (llvm::Error E = Conf.addSaveTemps(MLtoTempPrefix)) { + std::string TempPrefix = getLTOTempPrefix(); + if (llvm::Error E = Conf.addSaveTemps(TempPrefix)) { ThisConfig.raise(Diag::lto_cannot_save_temps) << llvm::toString(std::move(E)); return {}; } // FIXME: Output actual file names (this will go with the above TODO). - ThisConfig.raise(Diag::note_temp_lto_bitcode) << MLtoTempPrefix + "*"; + ThisConfig.raise(Diag::note_temp_lto_bitcode) << TempPrefix + "*"; + MLtoTempPrefix = TempPrefix; } // Set the number of backend threads to use in ThinLTO - unsigned NumThreads = 1; - if (ThisConfig.options().threadsEnabled()) { - NumThreads = ThisConfig.options().numThreads(); - if (!NumThreads) - NumThreads = std::thread::hardware_concurrency(); - if (!NumThreads) - NumThreads = 4; // if hardware_concurrency returns 0 - ThisConfig.raise(Diag::note_lto_threads) << NumThreads; + ThreadPoolStrategy ThinLTOParallelism; + if (!ThisConfig.options().getThinLTOJobs().empty()) { + // --thinlto-jobs= overrides --threads=. + ThinLTOParallelism = + llvm::hardware_concurrency(ThisConfig.options().getThinLTOJobs()); + ThisConfig.raise(Diag::note_lto_threads) + << ThisConfig.options().getThinLTOJobs(); + } else { + unsigned NumThreads = 1; + if (ThisConfig.options().threadsEnabled()) { + NumThreads = ThisConfig.options().numThreads(); + if (!NumThreads) + NumThreads = std::thread::hardware_concurrency(); + if (!NumThreads) + NumThreads = 4; // if hardware_concurrency returns 0 + ThisConfig.raise(Diag::note_lto_threads) << NumThreads; + } + ThinLTOParallelism = llvm::heavyweight_hardware_concurrency(NumThreads); } // Initialize the LTO backend - llvm::lto::ThinBackend Backend = llvm::lto::createInProcessThinBackend( - llvm::heavyweight_hardware_concurrency(NumThreads)); - return std::make_unique(std::move(Conf), std::move(Backend)); + llvm::lto::ThinBackend Backend = + llvm::lto::createInProcessThinBackend(ThinLTOParallelism); + return std::make_unique( + std::move(Conf), std::move(Backend), + ThisConfig.options().getLTOPartitions()); } bool ObjectLinker::finalizeLtoSymbolResolution( @@ -3030,21 +3064,18 @@ void ObjectLinker::addLTOOutputToTar() { } } -bool ObjectLinker::doLto(llvm::lto::LTO <O) { +bool ObjectLinker::doLto(llvm::lto::LTO <O, bool CompileToAssembly) { eld::RegisterTimer T("Invoke LTO", "LTO", ThisConfig.options().printTimingStats()); // Run LTO std::vector Files; - if (!ThisConfig.options().hasLTOOutputFile()) - Files.resize(LTO.getMaxTasks()); - FilesToRemove.resize(LTO.getMaxTasks()); + bool KeepLTOOutput = + MSaveTemps || ThisModule->getLinker()->getLinkerDriver()->isRunLTOOnly(); auto AddStream = [&](size_t Task, const Twine &ModuleName) -> llvm::Expected> { SmallString<256> ObjFileName; - auto OS = createLTOTempFile( - Task, getTargetBackend().ltoNeedAssembler() ? "s" : "o", ObjFileName); - + auto OS = createLTOTempFile(Task, CompileToAssembly, ObjFileName); if (!OS) { ThisModule->setFailure(true); return OS.takeError(); @@ -3052,8 +3083,6 @@ bool ObjectLinker::doLto(llvm::lto::LTO <O) { assert(Files[Task].empty() && "LTO task already produced an output!"); Files[Task] = std::string(ObjFileName); - if (!MSaveTemps) - FilesToRemove[Task] = std::string(ObjFileName); return std::make_unique(std::move(*OS)); }; @@ -3094,46 +3123,55 @@ bool ObjectLinker::doLto(llvm::lto::LTO <O) { ThinLTOCache = *LC; } - if (getTargetBackend().ltoNeedAssembler()) { - // If the output files haven't already been provided, run compilation now - std::vector LTOAsmOutput; - if (!ThisConfig.options().hasLTOOutputFile() && - !ThisConfig.options().hasLTOAsmFile()) { - if (llvm::Error E = LTO.run(AddStream, ThinLTOCache)) { - ThisConfig.raise(Diag::fatal_no_codegen_compile) - << llvm::toString(std::move(E)); - ThisModule->setFailure(true); - return false; - } - // AddStream adds the LTO output files to 'files', but in this case - // they're just the input files to the assembler so we need to move them - // to their own array. ltoRunAssembler will add the assembled objects to - // files. - LTOAsmOutput = std::move(Files); - } - if (!runAssembler(Files, ltoCodegenModel().second, LTOAsmOutput)) { - ThisConfig.raise(Diag::lto_codegen_error) << "Assembler error occurred"; - ThisModule->setFailure(true); - return false; + if (ThisConfig.options().hasLTOOutputFile()) { + for (auto &F : ThisConfig.options().ltoOutputFile()) { + ThisConfig.raise(Diag::using_lto_output_file) << F; + Files.push_back(F); } - - if (!MSaveTemps && !ThisConfig.options().hasLTOOutputFile()) - FilesToRemove.insert(FilesToRemove.end(), Files.begin(), Files.end()); } else { - // If the output files haven't already been provided, run compilation now - if (!ThisConfig.options().hasLTOOutputFile()) { + std::vector LTOAsmInput; + if (!ThisConfig.options().hasLTOAsmFile()) { + // Create a separate file for each task, which they will access by its + // index. + Files.resize(LTO.getMaxTasks()); if (llvm::Error E = LTO.run(AddStream, ThinLTOCache)) { ThisConfig.raise(Diag::fatal_no_codegen_compile) << llvm::toString(std::move(E)); ThisModule->setFailure(true); return false; } + if (CompileToAssembly) { + // AddStream adds the LTO output files to 'Files', but in this case + // they're just the input files to the assembler so we need to move them + // to their own array. ltoRunAssembler will add the assembled objects to + // files. + // "Files" may contain holes when LTO does not run for some tasks. For + // example, in thin LTO the merged object may not be written. Keep the + // holes so file names in assembler are in sync with "Files". + LTOAsmInput = std::move(Files); + } + // Remove unused file slots. + llvm::erase_if(Files, [](const std::string &x) { return x.empty(); }); } else { - for (auto &F : ThisConfig.options().ltoOutputFile()) { - ThisConfig.raise(Diag::using_lto_output_file) << F; - Files.push_back(F); + for (const auto &F : ThisConfig.options().ltoAsmFile()) { + ThisConfig.raise(Diag::using_lto_asm_file) << F; + LTOAsmInput.push_back(F); } } + if (!ThisModule->getLinker()->getLinkerDriver()->isRunLTOOnly() && + !LTOAsmInput.empty()) { + if (!runAssembler(Files, ltoCodegenModel().second, LTOAsmInput)) { + ThisConfig.raise(Diag::lto_codegen_error) << "Assembler error occurred"; + ThisModule->setFailure(true); + return false; + } + if (!KeepLTOOutput) + for (const auto &F : LTOAsmInput) + if (!F.empty()) + FilesToRemove.push_back(F); + } + if (!KeepLTOOutput && !ThisConfig.options().getLTOObjPath()) + FilesToRemove.insert(FilesToRemove.end(), Files.begin(), Files.end()); } if (!ThisConfig.getDiagEngine()->diagnose()) { if (ThisModule->getPrinter()->isVerbose()) @@ -3141,10 +3179,14 @@ bool ObjectLinker::doLto(llvm::lto::LTO <O) { return false; } + // if (!ctx.arg.ltoObjPath.empty()) { + // saveBuffer(buf[0].second, ctx.arg.ltoObjPath); + // for (unsigned i = 1; i != maxTasks; ++i) + // saveBuffer(buf[i].second, ctx.arg.ltoObjPath + Twine(i)); + // } + // Add the generated object files into the InputBuilder structure for (auto &F : Files) { - if (F.empty()) - continue; LtoObjects.push_back(F); if (MSaveTemps) ThisConfig.raise(Diag::note_temp_lto_object) << F; @@ -3236,10 +3278,6 @@ bool ObjectLinker::createLTOObject(void) { std::vector Options; processCodegenOptions(ThisConfig.options().codeGenOpts(), Conf, Options); - if (!ThisModule->getLinker()->getLinkerDriver()->processLTOOptions(Conf, - Options)) - return false; - getTargetBackend().AddLTOOptions(Options); if (LTOPlugin) @@ -3254,6 +3292,12 @@ bool ObjectLinker::createLTOObject(void) { Conf.Options = llvm::codegen::InitTargetOptionsFromCodeGenFlags( ThisConfig.targets().triple()); + // processLTOOptions() was moved after setting Conf.Options from command line + // because it also sets Conf.Options. + if (!ThisModule->getLinker()->getLinkerDriver()->processLTOOptions(Conf, + Options)) + return false; + if (LTOPlugin) { // TODO: Why do we read all relocations here? Get rid of? @@ -3265,14 +3309,26 @@ bool ObjectLinker::createLTOObject(void) { PrepareDiagEngineForLTO PrepareDiagnosticsForLto(ThisConfig.getDiagEngine()); - std::unique_ptr LTO = ltoInit(std::move(Conf)); + // CGFileType may have been set to AssemblyFile by `--lto-emit-asm`. + bool CompileToAssembly = Conf.CGFileType == CodeGenFileType::AssemblyFile || + getTargetBackend().ltoNeedAssembler(); + if (ThisModule->getLinker()->getLinkerDriver()->isRunLTOOnly() && + !CompileToAssembly) { + Conf.PreCodeGenModuleHook = [this](size_t task, const llvm::Module &m) { + if (auto os = createLTOOutputFile()) + WriteBitcodeToFile(m, *os, false); + return false; + }; + } + std::unique_ptr LTO = + ltoInit(std::move(Conf), CompileToAssembly); if (!LTO) return false; if (!finalizeLtoSymbolResolution(*LTO, BitCodeInputs)) return false; - if (!doLto(*LTO)) { + if (!doLto(*LTO, CompileToAssembly)) { ThisModule->setFailure(true); return false; } diff --git a/test/Hexagon/standalone/LTO/LTOReuseFiles/ltoreusefiles.test b/test/Hexagon/standalone/LTO/LTOReuseFiles/ltoreusefiles.test index 75849947b..0009b4f19 100644 --- a/test/Hexagon/standalone/LTO/LTOReuseFiles/ltoreusefiles.test +++ b/test/Hexagon/standalone/LTO/LTOReuseFiles/ltoreusefiles.test @@ -19,8 +19,7 @@ RUN: %readelf -s -W %t2.out.asmoutputoverride | %filecheck %s -check-prefix=ASMO #ASMOVERRIDE: Using LTO assembly file : {{[ -\(\)_A-Za-z0-9.\\/:]+}}lto2.s #OUTPUTOVERRIDE: Using LTO output file : {{[ -\(\)_A-Za-z0-9.\\/:]+}}lto1.o #OUTPUTOVERRIDE: Using LTO output file : {{[ -\(\)_A-Za-z0-9.\\/:]+}}lto2.o -#ASMOUTPUTOVERRIDE: Using LTO assembly file : {{[ -\(\)_A-Za-z0-9.\\/:]+}}lto1.s -#ASMOUTPUTOVERRIDE: Using LTO assembly file : {{[ -\(\)_A-Za-z0-9.\\/:]+}}lto2.s +#ASMOUTPUTOVERRIDE-NOT: Using LTO assembly file #ASMOUTPUTOVERRIDE: Using LTO output file : {{[ -\(\)_A-Za-z0-9.\\/:]+}}lto1.o #ASMOUTPUTOVERRIDE: Using LTO output file : {{[ -\(\)_A-Za-z0-9.\\/:]+}}lto2.o #ASMOVERRIDESYMS: bar diff --git a/test/lit.cfg b/test/lit.cfg index 64c38c9d4..427219840 100644 --- a/test/lit.cfg +++ b/test/lit.cfg @@ -47,7 +47,7 @@ execute_external = (platform.system() != 'Windows' config.test_format = eld_test_formats.get_test_format(config, execute_external) # suffixes: A list of file extensions to treat as test files. -config.suffixes = ['.test', '.s', '.ll'] +config.suffixes = ['.test', '.s', '.ll', '.c'] # excludes: A list of directories to exclude from the testsuite. The 'Inputs' # subdirectories contain auxiliary inputs for various tests in their parent @@ -162,6 +162,7 @@ elfcopy = 'llvm-objcopy' strip = 'llvm-strip' strings = 'llvm-strings' llvmas = 'llvm-as' +llvmdis = 'llvm-dis' llvmmc = 'llvm-mc' obj2yaml = 'obj2yaml' yaml2obj = 'yaml2obj' @@ -316,6 +317,9 @@ if config.test_target == 'X86': nm = 'llvm-nm' objdump = 'llvm-objdump' dwarfdump = 'llvm-dwarfdump' + linkopts = '--thread-count 4 --emit-relocs' + if hasattr(config,'eld_option') and config.eld_option != 'default': + linkopts = '--thread-count 4 ' + config.eld_option config.available_features.add('x86') if config.test_target == 'ARM': @@ -454,6 +458,7 @@ driver = which(driver) driverxx = which(driverxx) llvmmc = which(llvmmc) llvmas = which(llvmas) +llvmdis = which(llvmdis) clangas = which(clangas) clang = which(clang) elfcopy = which(elfcopy) @@ -508,6 +513,7 @@ lit_config.note('using driverxx: {}'.format(driverxx)) lit_config.note('using clangxx: {}'.format(which(clangxx))) lit_config.note('using llvmmc: {}'.format(which(llvmmc))) lit_config.note('using llvmas: {}'.format(which(llvmas))) +lit_config.note('using llvmdis: {}'.format(llvmas)) lit_config.note('using clangas: {}'.format(which(clangas))) lit_config.note('using clang: {}'.format(which(clang))) lit_config.note('using elfcopy: {}'.format(which(elfcopy))) @@ -578,6 +584,7 @@ config.substitutions.append( ("%not","".join(cnot)) ) config.substitutions.append( ("%clangxx","".join(clangxx)) ) config.substitutions.append( ("%llvm-mc","".join(llvmmc)) ) config.substitutions.append( ("%llvm-as","".join(llvmas)) ) +config.substitutions.append( ("%llvm-dis","".join(llvmdis)) ) config.substitutions.append( ("%clangas","".join(clangas)) ) config.substitutions.append( ("%clang","".join(clang)) ) config.substitutions.append( ("%elfcopy","".join(elfcopy)) ) diff --git a/test/lld/ELF/Inputs/a.c b/test/lld/ELF/Inputs/a.c new file mode 100644 index 000000000..26b47bebf --- /dev/null +++ b/test/lld/ELF/Inputs/a.c @@ -0,0 +1,5 @@ +extern void g(); + +void f() { + g(); +} diff --git a/test/lld/ELF/Inputs/b.c b/test/lld/ELF/Inputs/b.c new file mode 100644 index 000000000..7fc534016 --- /dev/null +++ b/test/lld/ELF/Inputs/b.c @@ -0,0 +1 @@ +void g() {} diff --git a/test/lld/ELF/cspgo-gen.ll b/test/lld/ELF/cspgo-gen.ll new file mode 100644 index 000000000..f25d79b6c --- /dev/null +++ b/test/lld/ELF/cspgo-gen.ll @@ -0,0 +1,12 @@ +; RUN: opt --mtriple=%triple --data-layout=%datalayout %s -o %t.o +; RUN: %link %linkopts --lto-cs-profile-generate --lto-cs-profile-file=%t.default.profraw %t.o --lto-debug-pass-manager -o %t 2>&1 | FileCheck %s --implicit-check-not=PGOInstrumentation +; RUN: %link %linkopts --plugin-opt=cs-profile-generate --plugin-opt=cs-profile-path=%t.default.profraw --lto-debug-pass-manager %t.o -o %t 2>&1 | FileCheck %s --implicit-check-not=PGOInstrumentation + +; CHECK: PGOInstrumentationGen + +@__llvm_profile_runtime = global i32 0, align 4 + +define void @foo() { +entry: + ret void +} diff --git a/test/lld/ELF/cspgo-use.ll b/test/lld/ELF/cspgo-use.ll new file mode 100644 index 000000000..057b26883 --- /dev/null +++ b/test/lld/ELF/cspgo-use.ll @@ -0,0 +1,14 @@ +; Create an empty profile +; RUN: echo > %t.proftext +; RUN: llvm-profdata merge %t.proftext -o %t.profdata + +; RUN: opt --mtriple=%triple --data-layout=%datalayout %s -o %t.o +; RUN: %link %linkopts --lto-cs-profile-file=%t.profdata %t.o --lto-debug-pass-manager -o %t 2>&1 | FileCheck %s --implicit-check-not=PGOInstrumentation +; RUN: %link %linkopts --plugin-opt=cs-profile-path=%t.profdata --lto-debug-pass-manager %t.o -o %t 2>&1 | FileCheck %s --implicit-check-not=PGOInstrumentation + +; CHECK: Running pass: PGOInstrumentationUse + +define void @foo() { +entry: + ret void +} diff --git a/test/lld/ELF/emit-asm.ll b/test/lld/ELF/emit-asm.ll new file mode 100644 index 000000000..2df8ea70a --- /dev/null +++ b/test/lld/ELF/emit-asm.ll @@ -0,0 +1,31 @@ +; RUN: %rm %t.* +; RUN: opt --mtriple=%triple --data-layout=%datalayout %s -o %t.bc + +; RUN: %link %linkopts %emulation --lto-emit-asm -shared %t.bc -o %t.out +; RUN: FileCheck %s < %t.out.llvm-lto.0.s +; RUN: not ls %t.out.*.o + +; RUN: %link %linkopts %emulation --plugin-opt=emit-asm --plugin-opt=lto-partitions=2 -shared %t.bc -o %t.out +; RUN: cat %t.out.llvm-lto.0.s %t.out.llvm-lto.1.s | FileCheck %s +; RUN: not ls %t.out.*.o + +; RUN: %link %linkopts --lto-emit-asm --save-temps -shared %t.bc -o %t.out +; RUN: FileCheck --input-file %t.out.llvm-lto.0.s %s +; RUN: not ls %t.out.*.o +; RUN: %llvm-dis %t.out.llvm-lto.0.4.opt.bc -o - | FileCheck --check-prefix=OPT %s + +;; Note: we also check for the presence of comments; --lto-emit-asm output should be verbose. + +; CHECK-DAG: -- Begin function f1 +; CHECK-DAG: f1: +; OPT: define void @f1() +define void @f1() { + ret void +} + +; CHECK-DAG: -- Begin function f2 +; CHECK-DAG: f2: +; OPT: define void @f2() +define void @f2() { + ret void +} diff --git a/test/lld/ELF/emit-llvm.ll b/test/lld/ELF/emit-llvm.ll new file mode 100644 index 000000000..a7a60b0da --- /dev/null +++ b/test/lld/ELF/emit-llvm.ll @@ -0,0 +1,18 @@ +; RUN: %rm %t.* +; RUN: opt --mtriple=%triple --data-layout=%datalayout -module-hash -module-summary %s -o %t.o +; RUN: %link %linkopts %emulation --plugin-opt=emit-llvm -o %t.out.o %t.o +; RUN: %llvm-dis < %t.out.o -o - | FileCheck %s +; RUN: not ls %t.out.*.o + +;; Regression test for D112297: bitcode writer used to crash when +;; --plugin-opt=emit-llvmis enabled and the output is /dev/null. +; RUN: %link %linkopts %emulation --plugin-opt=emit-llvm -mllvm -bitcode-flush-threshold=0 -o /dev/null %t.o +; RUN: %link %linkopts %emulation --lto-emit-llvm -mllvm -bitcode-flush-threshold=0 -o /dev/null %t.o + +; CHECK: define hidden void @main() + +@llvm.compiler.used = appending global [1 x ptr] [ptr @main], section "llvm.metadata" + +define hidden void @main() { + ret void +} diff --git a/test/lld/ELF/obj-path.test b/test/lld/ELF/obj-path.test new file mode 100644 index 000000000..ebe448db8 --- /dev/null +++ b/test/lld/ELF/obj-path.test @@ -0,0 +1,81 @@ +## Test --lto-obj-path= for regular LTO. + +RUN: %clang %clangopts -c -flto %p/Inputs/a.c -o %t.a.o +RUN: %clang %clangopts -c -flto %p/Inputs/b.c -o %t.b.o + +RUN: rm -f %t.3* %t.objpath.o* +RUN: %link %linkopts --lto-obj-path=%t.objpath.o -shared %t.a.o %t.b.o -o %t.3 +RUN: llvm-nm %t.3 | FileCheck %s --check-prefix=NM +RUN: %objdump -d %t.objpath.o | FileCheck %s +RUN: ls %t.3* %t.objpath.o* | count 2 + +RUxN: rm -f %t.3* %t.objpath.o +RUxN: %link %linkopts --thinlto-index-only=3.txt --lto-obj-path=objpath.o -shared %t.a.o %t.b.o -o %t.3 +RUxN: %objdump -d %t.objpath.o | FileCheck %s +RUxN: not ls %t.3 + +NM: T f +NM: T g + +CHECK: file format {{.+}} +CHECK: : +CHECK: : + +## Test --lto-obj-path= for ThinLTO. +RUN: %clang %clangopts -ffunction-sections -c -flto=thin %p/Inputs/a.c -o %t.a.o +RUN: %clang %clangopts -ffunction-sections -c -flto=thin %p/Inputs/b.c -o %t.b.o + +RUN: %link %linkopts --plugin-opt=-function-sections --plugin-opt=obj-path=%t.objpath.o -shared %t.a.o %t.b.o -o %t.3 +RUN: llvm-nm %t.3 | FileCheck %s --check-prefix=NM3 +RUN: %objdump -d %t.objpath.o1 | FileCheck %s --check-prefix=CHECK1 +RUN: %objdump -d %t.objpath.o2 | FileCheck %s --check-prefix=CHECK2 + +NM3: T f +NM3-NEXT: T g + +# TODO: function sections do not work? Should be .text.f and .text.g + +CHECK1: file format {{.+}} +CHECK1-EMPTY: +CHECK1-NEXT: Disassembly of section .text: +CHECK1-EMPTY: +CHECK1-NEXT: : + +CHECK2: file format {{.+}} +CHECK2-EMPTY: +CHECK2-NEXT: Disassembly of section .text: +CHECK2-EMPTY: +CHECK2-NEXT: : + +## With --thinlto-index-only, --lto-obj-path= creates just one file. +RUxN: rm -f %t.objpath.o* +RUxN: %link %linkopts --thinlto-index-only --lto-obj-path=%t.objpath.o -shared %t.a.o %t.b.o +RUxN: %objdump -d %t.objpath.o | FileCheck %s --check-prefix=EMPTY +RUxN: not ls %t.objpath.o1 +RUxN: not ls %t.objpath.o2 + +## Test --plugin-opt=obj-path=. +RxUN: rm -f %t.objpath.o* +RxUN: %link %linkopts --plugin-opt=thinlto-index-only --plugin-opt=obj-path=%t.objpath.o -shared %t.a.o %t.b.o +RxUN: %objdump -d %t.objpath.o | FileCheck %s --check-prefix=EMPTY + +## There is a difference between lld and eld when both --save-temps and +## --lto-obj-path are specified. lld creates two copies of output object files: +## one in the normal "save-temps" location and another where --lto-obj-path +## says. eld will create only one copy, in the --lto-obj-path location so there +## will fewer "save-temps" files. + +## Ensure linker emits empty combined module if specific obj-path. +RUN: rm -f %t.objpath.o* +RUN: rm -rf %t.obj && mkdir %t.obj +RUN: %link %linkopts --plugin-opt=obj-path=%t.objpath.o -shared %t.a.o %t.b.o -o %t.obj/out --save-temps +RUN: ls %t.objpath.o %t.objpath.o1 %t.objpath.o2 + +## Ensure linker does not emit empty combined module by default. +RUN: rm -fr %t.obj && mkdir %t.obj +RUN: %link %linkopts -shared %t.a.o %t.b.o -o %t.obj/out --save-temps +RUN: not test -e %t.obj/out.llvm-lto.o +RUN: not test -e %t.obj/out.llvm-lto.0.o + +EMPTY: file format {{.+}} +EMPTY-NOT: {{.}} diff --git a/test/lld/ELF/thinlto.test b/test/lld/ELF/thinlto.test new file mode 100644 index 000000000..25eace892 --- /dev/null +++ b/test/lld/ELF/thinlto.test @@ -0,0 +1,67 @@ +# On ARM, LTO output files are numbered starting from 0, not from 1 like everywhere else. +# UNSUPPORTED: arm, aarch64 + +## Basic ThinLTO tests. +RUN: %clang %clangopts -c -flto=thin %p/Inputs/a.c -o %t.a.o +RUN: %clang %clangopts -c -flto=thin %p/Inputs/b.c -o %t.b.o + +## First force single-threaded mode +RUN: rm -f %t.out* +RUN: %link %linkopts -save-temps --thinlto-jobs=1 -shared %t.a.o %t.b.o -o %t.out 2>&1 | %filecheck %s --check-prefix=CHECK-ONE +CHECK-ONE: Note: Using 1 threads for LTO code generation +RUN: llvm-nm %t.out.llvm-lto.1.o | %filecheck %s --check-prefix=NM1 +RUN: llvm-nm %t.out.llvm-lto.2.o | %filecheck %s --check-prefix=NM2 + +## Next force multi-threaded mode +RUN: rm -f %t.out* +RUN: %link %linkopts -save-temps --thinlto-jobs=2 -shared %t.a.o %t.b.o -o %t.out 2>&1 | %filecheck %s --check-prefix=CHECK-TWO +CHECK-TWO: Note: Using 2 threads for LTO code generation +RUN: llvm-nm %t.out.llvm-lto.1.o | %filecheck %s --check-prefix=NM1 +RUN: llvm-nm %t.out.llvm-lto.2.o | %filecheck %s --check-prefix=NM2 + +## --plugin-opt=jobs= is an alias. +RUN: rm -f %t.out* +RUN: %link %linkopts -save-temps --plugin-opt=jobs=2 -shared %t.a.o %t.b.o -o %t.out 2>&1 | %filecheck %s --check-prefix=CHECK-TWO +RUN: llvm-nm %t.out.llvm-lto.1.o | %filecheck %s --check-prefix=NM1 +RUN: llvm-nm %t.out.llvm-lto.2.o | %filecheck %s --check-prefix=NM2 + +## --thinlto-jobs= defaults to --threads=. +RUN: rm -f %t.out* +RUN: %link %linkopts -save-temps --thread-count=2 -shared %t.a.o %t.b.o -o %t.out 2>&1 | %filecheck %s --check-prefix=CHECK-TWO +RUN: llvm-nm %t.out.llvm-lto.1.o | %filecheck %s --check-prefix=NM1 +RUN: llvm-nm %t.out.llvm-lto.2.o | %filecheck %s --check-prefix=NM2 + +## --thinlto-jobs= overrides --threads=. +RUN: rm -f %t.out* +RUN: %link %linkopts -save-temps --thread-count=1 --plugin-opt=jobs=2 -shared %t.a.o %t.b.o -o %t.out 2>&1 | %filecheck %s --check-prefix=CHECK-TWO +RUN: llvm-nm %t.out.llvm-lto.1.o | %filecheck %s --check-prefix=NM1 +RUN: llvm-nm %t.out.llvm-lto.2.o | %filecheck %s --check-prefix=NM2 + +# Test with all threads, on all cores, on all CPU sockets +RUN: rm -f %t.out* +RUN: %link %linkopts -save-temps --thinlto-jobs=all -shared %t.a.o %t.b.o -o %t.out 2>&1 | %filecheck %s --check-prefix=CHECK-ALL +CHECK-ALL: Note: Using all threads for LTO code generation +RUN: llvm-nm %t.out.llvm-lto.1.o | %filecheck %s --check-prefix=NM1 +RUN: llvm-nm %t.out.llvm-lto.2.o | %filecheck %s --check-prefix=NM2 + +## Test with many more threads than the system has +RUN: rm -f %t.out* +RUN: %link %linkopts -save-temps --thinlto-jobs=100 -shared %t.a.o %t.b.o -o %t.out 2>&1 | %filecheck %s --check-prefix=CHECK-HUNDRED +CHECK-HUNDRED: Note: Using 100 threads for LTO code generation +RUN: llvm-nm %t.out.llvm-lto.1.o | %filecheck %s --check-prefix=NM1 +RUN: llvm-nm %t.out.llvm-lto.2.o | %filecheck %s --check-prefix=NM2 + +## Test with a bad value +RUN: rm -f %t.out* +RUN: not %link %linkopts -save-temps --thinlto-jobs=foo -shared %t.a.o %t.b.o -o %t.out 2>&1 | %filecheck %s --check-prefix=BAD-JOBS +BAD-JOBS: Error: Invalid value for --thinlto-jobs=: foo + +## Check that -save-temps is usable with thin archives +RUN: mkdir -p %t.dir +RUN: cp %t.b.o %t.dir/t.o +RUN: llvm-ar rcsT %t.a %t.dir/t.o +RUN: %link %linkopts -save-temps %t.a.o %t.a -o %t.thin.out 2>&1 | %filecheck %s --check-prefix=CHECK-THIN +CHECK-THIN: Note: LTO generated native object: {{.+}}.thin.out.llvm-lto.{{.*}}.o + +NM1: T f +NM2: T g diff --git a/test/lld/ELF/verify-invalid.ll b/test/lld/ELF/verify-invalid.ll new file mode 100644 index 000000000..3e77c36ea --- /dev/null +++ b/test/lld/ELF/verify-invalid.ll @@ -0,0 +1,17 @@ +; RUN: opt --mtriple=%triple --data-layout=%datalayout %s -o %t.o +; RUN: %link %linkopts %t.o -o %t2 --lto-debug-pass-manager \ +; RUN: 2>&1 | FileCheck -check-prefix=DEFAULT-NPM %s +; RUN: %link %linkopts %t.o -o %t2 --lto-debug-pass-manager \ +; RUN: -disable-verify 2>&1 | FileCheck -check-prefix=DISABLE-NPM %s +; RUN: %link %linkopts %t.o -o %t2 --lto-debug-pass-manager \ +; RUN: --plugin-opt=disable-verify 2>&1 | FileCheck -check-prefix=DISABLE-NPM %s + +define void @_start() { + ret void +} + +; -disable-verify should disable the verification of bitcode. +; DEFAULT-NPM: Running pass: VerifierPass +; DEFAULT-NPM: Running pass: VerifierPass +; DEFAULT-NPM-NOT: Running pass: VerifierPass +; DISABLE-NPM-NOT: Running pass: VerifierPass