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" @@ -211,7 +212,7 @@ bool relocatable; 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 @@ -1157,7 +1157,28 @@ config->relax = args.hasFlag(OPT_relax, OPT_no_relax, true); config->rpath = getRpath(args); config->relocatable = args.hasArg(OPT_relocatable); - config->saveTemps = args.hasArg(OPT_save_temps); + + if (args.hasArg(OPT_save_temps)) { + // --save-temps implies saving all temps. + config->saveTempsArgs.insert("resolution"); + config->saveTempsArgs.insert("preopt"); + config->saveTempsArgs.insert("postpromote"); + config->saveTempsArgs.insert("postinternalize"); + config->saveTempsArgs.insert("postimport"); + config->saveTempsArgs.insert("postopt"); + config->saveTempsArgs.insert("precodegen"); + config->saveTempsArgs.insert("prelink"); + config->saveTempsArgs.insert("combinedindex"); + } else { + 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->saveTempsArgs.empty()) checkError(c.addSaveTemps(config->outputFile.str() + ".", - /*UseInputModulePath*/ true)); + /*UseInputModulePath*/ true, + config->saveTempsArgs)); return c; } @@ -365,7 +366,7 @@ saveBuffer(buf[i], config->ltoObjPath + Twine(i)); } - if (config->saveTemps) { + if (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 @@ -585,6 +585,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,120 @@ +; This test is similar to llvm/test/ThinLTO/X86/selective-save-temps.ll + +; REQUIRES: x86 +; RUN: rm -rf %t && mkdir %t && cd %t +; RUN: mkdir all all2 all3 build subset subset2 && cd build + +; 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: ld.lld main.o thin1.o --save-temps -o %t/all/a.out +; RUN: mv *.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: 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 *.o.* %t/all2 +; RUN: ls | count 2 +; RUN: diff -r %t/all %t/all2 + +;; 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 *.0.preopt.* %t/subset2 +; RUN: mv *.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 *.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 *.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 *.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 *.4.opt* %t/subset2 +; RUN: mv *.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 *.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 *.index.bc %t/all3 +; RUN: mv *.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 *.lto.o %t/subset2 +; RUN: mv *.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 a.out %t/all3 +; RUN: mv *.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: 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 *.0.preopt.* %t/subset +; RUN: mv *.4.opt* %t/subset +; RUN: mv *.lto.o %t/subset +; RUN: ls | count 2 +; RUN: diff -r %t/subset2 %t/subset + +;; 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" @@ -267,8 +268,12 @@ /// the given output file name, and (2) creates a resolution file whose name /// is prefixed by the given output file name and sets ResolutionFile to its /// file handle. + /// + /// SaveTempsArgs can be specified to select which temps to save. + /// If SaveTempsArgs is not provided, all temps are saved. 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 @@ -81,17 +81,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 +128,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 +148,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(); } diff --git a/llvm/test/ThinLTO/X86/selective-save-temps.ll b/llvm/test/ThinLTO/X86/selective-save-temps.ll new file mode 100644 --- /dev/null +++ b/llvm/test/ThinLTO/X86/selective-save-temps.ll @@ -0,0 +1,206 @@ +; This test is similar to lld/test/ELF/lto/save-temps-eq.ll + +; RUN: rm -rf %t && mkdir %t && cd %t + +; Copy IR from import-constant.ll since it generates all the temps +; RUN: opt -thinlto-bc %s -o 1.bc +; RUN: opt -thinlto-bc %p/Inputs/import-constant.ll -o 2.bc + +;; Create the .all dir with save-temps saving everything, this will be used to compare +;; with the output from individualized save-temps later +; RUN: mkdir all all2 build subset subset2 +; RUN: llvm-lto2 run -save-temps 1.bc 2.bc -o all/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -save-temps + +;; The next 8 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 subset2 to check composability later) +;; Move files that were expected to be generated to all2 +;; Make sure there's no unexpected extra files +;; After that, we'll diff all and all2 to make sure all contents are identical + +;; Check preopt +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=preopt +; RUN: diff all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: diff all/a.out.2 build/a.out.2 && rm -f build/a.out.2 +; RUN: cp build/*.0.preopt.* subset2 +; RUN: mv build/*.0.preopt.* all2 +; RUN: ls build | count 0 + +;; Check postpromote +; RUN: rm -f all2/*.1.promote* +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=postpromote +; RUN: diff all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: diff all/a.out.2 build/a.out.2 && rm -f build/a.out.2 +; RUN: mv build/*.1.promote* all2 +; RUN: ls build | count 0 + +;; Check postinternalize +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=postinternalize +; RUN: diff all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: diff all/a.out.2 build/a.out.2 && rm -f build/a.out.2 +; RUN: mv build/*.2.internalize* all2 +; RUN: ls build | count 0 + +;; Check postimport +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=postimport +; RUN: diff all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: diff all/a.out.2 build/a.out.2 && rm -f build/a.out.2 +; RUN: mv build/*.3.import* all2 +; RUN: ls build | count 0 + +;; Check postopt +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=postopt +; RUN: diff all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: diff all/a.out.2 build/a.out.2 && rm -f build/a.out.2 +; RUN: cp build/*.4.opt* subset2 +; RUN: mv build/*.4.opt* all2 +; RUN: ls build | count 0 + +;; Check precodegen +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=precodegen +; RUN: diff all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: diff all/a.out.2 build/a.out.2 && rm -f build/a.out.2 +; RUN: mv build/*.5.precodegen* all2 +; RUN: ls build | count 0 + +;; Check combinedindex +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=combinedindex +; RUN: diff all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: diff all/a.out.2 build/a.out.2 && rm -f build/a.out.2 +; RUN: cp build/*.index.bc subset2 +; RUN: cp build/*.index.dot subset2 +; RUN: mv build/*.index.bc all2 +; RUN: mv build/*.index.dot all2 +; RUN: ls build | count 0 + +;; Check resolution +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=resolution +;; all2 needs at least 1 copy of a.out, move it over now since its the last block +; RUN: mv build/a.out.1 build/a.out.2 all2 +; RUN: mv build/*.resolution.txt all2 +; RUN: ls build | count 0 + +;; If no files were left out from individual stages, the .all2 dir should be identical to .all +; RUN: diff -r all all2 + +;; Check multi-stage composability +;; Similar to the above, but do it with a subset instead. +;; .all -> .subset, .all2 -> .subset2 +; RUN: llvm-lto2 run 1.bc 2.bc -o subset/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=preopt,combinedindex,postopt +; RUN: diff all/a.out.1 subset/a.out.1 && rm -f subset/a.out.1 +; RUN: diff all/a.out.2 subset/a.out.2 && rm -f subset/a.out.2 +; RUN: diff -r subset subset2 + +;; Check error messages +; RUN: not llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=prelink 2>&1 \ +; RUN: | FileCheck %s --check-prefix=ERR1 +; ERR1: invalid -select-save-temps argument: prelink + +; RUN: not llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs \ +; RUN: -r=1.bc,main,plx \ +; RUN: -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl \ +; RUN: -r=2.bc,val,pl \ +; RUN: -r=2.bc,outer,pl \ +; RUN: -select-save-temps=preopt -save-temps 2>&1 \ +; RUN: | FileCheck %s --check-prefix=ERR2 +; ERR2: -save-temps cannot be specified with -select-save-temps + +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" + +%struct.S = type { i32, i32, i32* } + +define dso_local i32 @main() local_unnamed_addr { +entry: + %call = tail call %struct.S* @_Z6getObjv() + %d = getelementptr inbounds %struct.S, %struct.S* %call, i64 0, i32 0 + %0 = load i32, i32* %d, align 8 + %v = getelementptr inbounds %struct.S, %struct.S* %call, i64 0, i32 1 + %1 = load i32, i32* %v, align 4 + %add = add nsw i32 %1, %0 + ret i32 %add +} + +declare dso_local %struct.S* @_Z6getObjv() local_unnamed_addr diff --git a/llvm/tools/llvm-lto2/llvm-lto2.cpp b/llvm/tools/llvm-lto2/llvm-lto2.cpp --- a/llvm/tools/llvm-lto2/llvm-lto2.cpp +++ b/llvm/tools/llvm-lto2/llvm-lto2.cpp @@ -67,6 +67,15 @@ static cl::opt SaveTemps("save-temps", cl::desc("Save temporary files")); +static cl::list SelectSaveTemps( + "select-save-temps", + cl::value_desc("One, or multiple of: " + "resolution,preopt,postpromote,postinternalize,postimport," + "postopt,precodegen,combinedindex"), + cl::desc("Save select temporary files. Cannot be specified together with " + "-save-temps"), + cl::CommaSeparated); + static cl::opt ThinLTODistributedIndexes("thinlto-distributed-indexes", cl::init(false), cl::desc("Write out individual index and " @@ -258,9 +267,24 @@ Conf.DebugPassManager = DebugPassManager; - if (SaveTemps) - check(Conf.addSaveTemps(OutputFilename + "."), + if (SaveTemps && !SelectSaveTemps.empty()) { + llvm::errs() << "-save-temps cannot be specified with -select-save-temps\n"; + return 1; + } else if (SaveTemps || !SelectSaveTemps.empty()) { + llvm::SmallSet SaveTempsArgs; + for (auto &S : SelectSaveTemps) { + if (S == "resolution" || S == "preopt" || S == "postpromote" || + S == "postinternalize" || S == "postimport" || S == "postopt" || + S == "precodegen" || S == "combinedindex") + 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;