diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -188,6 +188,7 @@ bool ltoPGOWarnMismatch; bool ltoDebugPassManager; bool ltoEmitAsm; + llvm::StringRef ltoExitOn; bool ltoUniqueBasicBlockSectionNames; bool ltoWholeProgramVisibility; bool mergeArmExidx; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1102,6 +1102,13 @@ OPT_no_lto_pgo_warn_mismatch, true); config->ltoDebugPassManager = args.hasArg(OPT_lto_debug_pass_manager); config->ltoEmitAsm = args.hasArg(OPT_lto_emit_asm); + config->ltoExitOn = args.getLastArgValue(OPT_lto_exit_on_eq); + if (!config->ltoExitOn.empty() && config->ltoExitOn != "postpromote" && + config->ltoExitOn != "postimport" && config->ltoExitOn != "postopt" && + config->ltoExitOn != "precodegen" && config->ltoExitOn != "prelink") { + error("unknown --lto-exit-on: " + config->ltoExitOn); + config->ltoExitOn = ""; + } config->ltoNewPmPasses = args.getLastArgValue(OPT_lto_newpm_passes); config->ltoWholeProgramVisibility = args.hasFlag(OPT_lto_whole_program_visibility, @@ -2593,9 +2600,11 @@ // --plugin-opt=emit-asm create output files in bitcode or assembly code, // respectively. When only certain thinLTO modules are specified for // compilation, the intermediate object file are the expected output. - const bool skipLinkedOutput = config->thinLTOIndexOnly || config->emitLLVM || - config->ltoEmitAsm || - !config->thinLTOModulesToCompile.empty(); + // --lto-exit-on is specified to prevent the linker from executing after + // certain LTO stages. + const bool skipLinkedOutput = + config->thinLTOIndexOnly || config->emitLLVM || config->ltoEmitAsm || + !config->ltoExitOn.empty() || !config->thinLTOModulesToCompile.empty(); // Do link-time optimization if given files are LLVM bitcode files. // This compiles bitcode files into real object files. diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -182,6 +182,9 @@ checkError(c.addSaveTemps(config->outputFile.str() + ".", /*UseInputModulePath*/ true, config->saveTempsArgs)); + + if (!config->ltoExitOn.empty()) + checkError(c.addExitOn(config->ltoExitOn)); return c; } diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -547,6 +547,8 @@ HelpText<"Debug new pass manager">; def lto_emit_asm: FF<"lto-emit-asm">, HelpText<"Emit assembly code">; +def lto_exit_on_eq: JJ<"lto-exit-on=">, HelpText<"Stop the linker at a given stage">, + Values<"postpromote,postimport,postopt,precodegen,prelink">; def no_lto_legacy_pass_manager: FF<"no-lto-legacy-pass-manager">, HelpText<"Use the new pass manager in LLVM">; def lto_newpm_passes: JJ<"lto-newpm-passes=">, diff --git a/lld/test/ELF/lto/lto-exit-on.ll b/lld/test/ELF/lto/lto-exit-on.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/lto-exit-on.ll @@ -0,0 +1,92 @@ +; REQUIRES: x86 +; RUN: rm -fr %t && mkdir %t && cd %t +; RUN: mkdir build && cd build + +; RUN: opt -thinlto-bc -o main.o %s +; RUN: opt -thinlto-bc -o thin1.o %S/Inputs/thinlto.ll + +;; Check postpromote +; RUN: rm -f *.o.* +; RUN: ld.lld main.o thin1.o --save-temps --lto-exit-on=postpromote +; RUN: ls *.0.preopt* +; RUN: ls *.1.promote* +; RUN: ls *.2.internalize* | count 1 +; RUN: not ls *.3.import* +; RUN: not ls *.4.opt* +; RUN: not ls *.5.precodegen* +; RUN: ls a.out*.lto.o +; RUN: not ls a.out + +;; Check postimport +; RUN: rm -f *.o.* +; RUN: ld.lld main.o thin1.o --save-temps --lto-exit-on=postimport +; RUN: ls *.0.preopt* +; RUN: ls *.1.promote* +; RUN: ls *.2.internalize* | count 3 +; RUN: ls *.3.import* +; RUN: not ls *.4.opt* +; RUN: not ls *.5.precodegen* +; RUN: ls a.out*.lto.o +; RUN: not ls a.out + +;; Check postopt +; RUN: rm -f *.o.* +; RUN: ld.lld main.o thin1.o --save-temps --lto-exit-on=postopt +; RUN: ls *.0.preopt* +; RUN: ls *.1.promote* +; RUN: ls *.2.internalize* | count 3 +; RUN: ls *.3.import* +; RUN: ls *.4.opt* +; RUN: not ls *.5.precodegen* +; RUN: ls a.out*.lto.o +; RUN: not ls a.out + +;; Check precodegen +; RUN: rm -f *.o.* +; RUN: ld.lld main.o thin1.o --save-temps --lto-exit-on=precodegen +; RUN: ls *.0.preopt* +; RUN: ls *.1.promote* +; RUN: ls *.2.internalize* | count 3 +; RUN: ls *.3.import* +; RUN: ls *.4.opt* +; RUN: ls *.5.precodegen* +; RUN: ls a.out*.lto.o +; RUN: not ls a.out + +;; Check prelink +; RUN: rm -f *.o.* a.out* +; RUN: ld.lld main.o thin1.o --save-temps --lto-exit-on=prelink +; RUN: ls *.0.preopt* +; RUN: ls *.1.promote* +; RUN: ls *.2.internalize* | count 3 +; RUN: ls *.3.import* +; RUN: ls *.4.opt* +; RUN: ls *.5.precodegen* +; RUN: ls a.out*.lto.o +; RUN: not ls a.out + +;; Check output files are as expected +; RUN: mkdir %t/all2 +; RUN: mv *.o.* a.out* %t/all2 + +;; Create the .all dir with save-temps, without exit-on, then diff +; RUN: mkdir %t/all +; RUN: ld.lld main.o thin1.o --save-temps +; RUN: rm a.out +; RUN: mv *.o.* a.out* %t/all +; RUN: diff -r %t/all %t/all2 + +;; Check input validation +; RUN: not ld.lld main.o thin1.o --save-temps --lto-exit-on=notastage 2>&1 \ +; RUN: | FileCheck %s --check-prefix=ERR1 +; ERR1: unknown --lto-exit-on: notastage + +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +declare void @g() + +define i32 @_start() { + call void @g() + ret i32 0 +} \ No newline at end of file diff --git a/llvm/include/llvm/LTO/Config.h b/llvm/include/llvm/LTO/Config.h --- a/llvm/include/llvm/LTO/Config.h +++ b/llvm/include/llvm/LTO/Config.h @@ -271,6 +271,13 @@ Error addSaveTemps(std::string OutputFileName, bool UseInputModulePath = false, const llvm::SmallSet &saveTempsArgs = {}); + + /// Configures this Config object to stop executing further LTO/linker stages + /// upon reaching a specified stage. + /// This is done by modifying the above module hooks such that they: + /// 1) Run the existing hook, if any. + /// 2) return false. + Error addExitOn(llvm::StringRef ltoExitOn); }; struct LTOLLVMDiagnosticHandler : public DiagnosticHandler { diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp --- a/llvm/lib/LTO/LTOBackend.cpp +++ b/llvm/lib/LTO/LTOBackend.cpp @@ -166,6 +166,29 @@ return Error::success(); } +Error Config::addExitOn(llvm::StringRef ltoExitOn) { + auto setHook = [&](ModuleHookFn &Hook) { + // Keep track of the hook provided by the linker, which needs to run first. + ModuleHookFn LinkerHook = Hook; + Hook = [=](unsigned Task, const Module &M) { + if (LinkerHook) + LinkerHook(Task, M); + return false; + }; + }; + + if (ltoExitOn == "postpromote") + setHook(PostPromoteModuleHook); + else if (ltoExitOn == "postimport") + setHook(PostImportModuleHook); + else if (ltoExitOn == "postopt") + setHook(PostOptModuleHook); + else if (ltoExitOn == "precodegen") + setHook(PreCodeGenModuleHook); + + return Error::success(); +} + #define HANDLE_EXTENSION(Ext) \ llvm::PassPluginLibraryInfo get##Ext##PluginInfo(); #include "llvm/Support/Extension.def"