diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -13,6 +13,7 @@ #include "llvm/ADT/CachedHashString.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SetVector.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/BinaryFormat/ELF.h" @@ -212,6 +213,7 @@ bool relrGlibc = false; bool relrPackDynRelocs = false; bool saveTemps; + llvm::SmallSet saveTempsArgs; std::vector> shuffleSections; bool singleRoRx; bool shared; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1159,6 +1159,19 @@ config->rpath = getRpath(args); config->relocatable = args.hasArg(OPT_relocatable); config->saveTemps = args.hasArg(OPT_save_temps); + + // --save-temps takes precedence over --save-temps= + if (!config->saveTemps) { + // parse --save-temps= + for (auto &s : args.getAllArgValues(OPT_save_temps_eq)) + if (s == "resolution" || s == "preopt" || s == "postpromote" || + s == "postinternalize" || s == "postimport" || s == "postopt" || + s == "precodegen" || s == "prelink" || s == "combinedindex") + config->saveTempsArgs.insert(s); + else + error("unknown --save-temps: " + s); + } + config->searchPaths = args::getStrings(args, OPT_library_path); config->sectionStartMap = getSectionStartMap(args); config->shared = args.hasArg(OPT_shared); diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -178,9 +178,10 @@ if (config->ltoEmitAsm) c.CGFileType = CGFT_AssemblyFile; - if (config->saveTemps) + if (config->saveTemps || !config->saveTempsArgs.empty()) checkError(c.addSaveTemps(config->outputFile.str() + ".", - /*UseInputModulePath*/ true)); + /*UseInputModulePath*/ true, + config->saveTempsArgs)); return c; } @@ -362,7 +363,7 @@ saveBuffer(buf[i], config->ltoObjPath + Twine(i)); } - if (config->saveTemps) { + if (config->saveTemps || config->saveTempsArgs.contains("prelink")) { if (!buf[0].empty()) saveBuffer(buf[0], config->outputFile + ".lto.o"); for (unsigned i = 1; i != maxTasks; ++i) diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -583,6 +583,8 @@ def opt_remarks_format: Separate<["--"], "opt-remarks-format">, HelpText<"The format used for serializing remarks (default: YAML)">; def save_temps: F<"save-temps">, HelpText<"Save intermediate LTO compilation results">; +def save_temps_eq: JJ<"save-temps=">, HelpText<"Save select intermediate LTO compilation results">, + Values<"resolution,preopt,postpromote,postinternalize,postimport,postopt,precodegen,prelink,combinedindex">; def lto_basic_block_sections: JJ<"lto-basic-block-sections=">, HelpText<"Enable basic block sections for LTO">; defm lto_unique_basic_block_section_names: BB<"lto-unique-basic-block-section-names", diff --git a/lld/test/ELF/lto/save-temps-eq.ll b/lld/test/ELF/lto/save-temps-eq.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/save-temps-eq.ll @@ -0,0 +1,124 @@ +; REQUIRES: x86 +; RUN: rm -rf %t && mkdir %t && cd %t + +; RUN: opt -thinlto-bc -o main.o %s +; RUN: opt -thinlto-bc -o thin1.o %S/Inputs/thinlto.ll + +;; Create the .all dir with save-temps saving everything, this will be used to compare +;; with the output from individualized save-temps later +; RUN: rm -rf %t.all +; RUN: mkdir %t.all +; RUN: ld.lld main.o thin1.o --save-temps -o %t.all/a.out +; RUN: mv %t/*.o.* %t.all +;; Sanity check that everything got moved +; RUN: ls | count 2 + +;; Check precedence if both --save-temps and --save-temps= are present +; RUN: rm -rf %t.all2 && mkdir %t.all2 +; RUN: ld.lld main.o thin1.o --save-temps=preopt --save-temps --save-temps=postopt -o %t.all2/a.out +; RUN: diff %t.all2/a.out %t.all/a.out +; RUN: mv %t/*.o.* %t.all2 +; RUN: ls | count 2 +; RUN: diff -r %t.all %t.all2 + +; RUN: rm -rf %t.all3 && mkdir %t.all3 +; RUN: rm -rf %t.subset && mkdir %t.subset + +;; The next 9 blocks follow this structure: +;; for each option of save-temps= +;; Run linker and generate files +;; Make sure a.out exists and is correct (by diff-ing) +;; this is the only file that should recur between runs +;; (Also, for some stages, copy the generated files to %t.subset2 to check composability later) +;; Move files that were expected to be generated to %t.all3 +;; Make sure there's no unexpected extra files +;; After that, we'll diff %t.all and %t.all3 to make sure all contents are identical + +;; Check preopt +; RUN: ld.lld main.o thin1.o --save-temps=preopt +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: cp %t/*.0.preopt.* %t.subset +; RUN: mv %t/*.0.preopt.* %t.all3 +; RUN: ls | count 2 + +;; Check postpromote +; RUN: ld.lld main.o thin1.o --save-temps=postpromote +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: mv %t/*.1.promote* %t.all3 +; RUN: ls | count 2 + +;; Check postinternalize +; RUN: ld.lld main.o thin1.o --save-temps=postinternalize +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: mv %t/*.2.internalize* %t.all3 +; RUN: ls | count 2 + +;; Check postimport +; RUN: ld.lld main.o thin1.o --save-temps=postimport +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: mv %t/*.3.import* %t.all3 +; RUN: ls | count 2 + +;; Check postopt +; RUN: ld.lld main.o thin1.o --save-temps=postopt +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: cp %t/*.4.opt* %t.subset +; RUN: mv %t/*.4.opt* %t.all3 +; RUN: ls | count 2 + +;; Check precodegen +; RUN: ld.lld main.o thin1.o --save-temps=precodegen +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: mv %t/*.5.precodegen* %t.all3 +; RUN: ls | count 2 + +;; Check combinedindex +; RUN: ld.lld main.o thin1.o --save-temps=combinedindex +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: mv %t/*.index.bc %t.all3 +; RUN: mv %t/*.index.dot %t.all3 +; RUN: ls | count 2 + +;; Check prelink +; RUN: ld.lld main.o thin1.o --save-temps=prelink +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: cp %t/*.lto.o %t.subset +; RUN: mv %t/*.lto.o %t.all3 +; RUN: ls | count 2 + +;; Check resolution +; RUN: ld.lld main.o thin1.o --save-temps=resolution +;; %t.all3 needs at least 1 copy of a.out, move it over now since its the last block +; RUN: mv %t/a.out %t.all3 +; RUN: mv %t/*.resolution.txt %t.all3 +; RUN: ls | count 2 + +;; If no files were left out from individual stages, the .all3 dir should be identical to .all +; RUN: diff -r %t.all %t.all3 + +;; Check multi-stage composability +;; Similar to the above, but do it with a subset instead. +;; .all -> .subset, .all3 -> .subset2 +; RUN: rm -rf %t.subset2 && mkdir %t.subset2 +; RUN: ld.lld main.o thin1.o --save-temps=preopt --save-temps=prelink --save-temps=postopt +; RUN: diff %t.all/a.out a.out && rm -f a.out +; RUN: mv %t/*.0.preopt.* %t.subset2 +; RUN: mv %t/*.4.opt* %t.subset2 +; RUN: mv %t/*.lto.o %t.subset2 +; RUN: ls | count 2 +; RUN: diff -r %t.subset %t.subset2 + +;; Check error message +; RUN: not ld.lld --save-temps=prelink --save-temps=postopt --save-temps=notastage 2>&1 \ +; RUN: | FileCheck %s +; CHECK: unknown --save-temps: 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 +} 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 @@ -15,6 +15,7 @@ #define LLVM_LTO_CONFIG_H #include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/Config/llvm-config.h" #include "llvm/IR/DiagnosticInfo.h" #include "llvm/IR/GlobalValue.h" @@ -268,7 +269,8 @@ /// is prefixed by the given output file name and sets ResolutionFile to its /// file handle. Error addSaveTemps(std::string OutputFileName, - bool UseInputModulePath = false); + bool UseInputModulePath = false, + const llvm::SmallSet &saveTempsArgs = {}); }; 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 @@ -14,6 +14,7 @@ //===----------------------------------------------------------------------===// #include "llvm/LTO/LTOBackend.h" +#include "llvm/ADT/SmallSet.h" #include "llvm/Analysis/AliasAnalysis.h" #include "llvm/Analysis/CGSCCPassManager.h" #include "llvm/Analysis/ModuleSummaryAnalysis.h" @@ -81,17 +82,20 @@ exit(1); } -Error Config::addSaveTemps(std::string OutputFileName, - bool UseInputModulePath) { +Error Config::addSaveTemps( + std::string OutputFileName, bool UseInputModulePath, + const llvm::SmallSet &saveTempsArgs) { ShouldDiscardValueNames = false; std::error_code EC; - ResolutionFile = - std::make_unique(OutputFileName + "resolution.txt", EC, - sys::fs::OpenFlags::OF_TextWithCRLF); - if (EC) { - ResolutionFile.reset(); - return errorCodeToError(EC); + if (saveTempsArgs.empty() || saveTempsArgs.contains("resolution")) { + ResolutionFile = + std::make_unique(OutputFileName + "resolution.txt", EC, + sys::fs::OpenFlags::OF_TextWithCRLF); + if (EC) { + ResolutionFile.reset(); + return errorCodeToError(EC); + } } auto setHook = [&](std::string PathSuffix, ModuleHookFn &Hook) { @@ -125,14 +129,7 @@ }; }; - setHook("0.preopt", PreOptModuleHook); - setHook("1.promote", PostPromoteModuleHook); - setHook("2.internalize", PostInternalizeModuleHook); - setHook("3.import", PostImportModuleHook); - setHook("4.opt", PostOptModuleHook); - setHook("5.precodegen", PreCodeGenModuleHook); - - CombinedIndexHook = + auto SaveCombinedIndex = [=](const ModuleSummaryIndex &Index, const DenseSet &GUIDPreservedSymbols) { std::string Path = OutputFileName + "index.bc"; @@ -152,6 +149,31 @@ return true; }; + if (saveTempsArgs.empty()) { + setHook("0.preopt", PreOptModuleHook); + setHook("1.promote", PostPromoteModuleHook); + setHook("2.internalize", PostInternalizeModuleHook); + setHook("3.import", PostImportModuleHook); + setHook("4.opt", PostOptModuleHook); + setHook("5.precodegen", PreCodeGenModuleHook); + CombinedIndexHook = SaveCombinedIndex; + } else { + if (saveTempsArgs.contains("preopt")) + setHook("0.preopt", PreOptModuleHook); + if (saveTempsArgs.contains("postpromote")) + setHook("1.promote", PostPromoteModuleHook); + if (saveTempsArgs.contains("postinternalize")) + setHook("2.internalize", PostInternalizeModuleHook); + if (saveTempsArgs.contains("postimport")) + setHook("3.import", PostImportModuleHook); + if (saveTempsArgs.contains("postopt")) + setHook("4.opt", PostOptModuleHook); + if (saveTempsArgs.contains("precodegen")) + setHook("5.precodegen", PreCodeGenModuleHook); + if (saveTempsArgs.contains("combinedindex")) + CombinedIndexHook = SaveCombinedIndex; + } + return Error::success(); }