diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -11,6 +11,7 @@ #include "lld/Common/ErrorHandler.h" #include "llvm/ADT/CachedHashString.h" +#include "llvm/ADT/DenseSet.h" #include "llvm/ADT/MapVector.h" #include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringRef.h" @@ -211,7 +212,7 @@ bool relocatable; bool relrGlibc = false; bool relrPackDynRelocs = false; - bool saveTemps; + llvm::DenseSet 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 @@ -506,6 +506,10 @@ warn("unknown -z value: " + StringRef(arg->getValue())); } +constexpr const char *saveTempsValues[] = { + "resolution", "preopt", "promote", "internalize", "import", + "opt", "precodegen", "prelink", "combinedindex"}; + void LinkerDriver::linkerMain(ArrayRef argsArr) { ELFOptTable parser; opt::InputArgList args = parser.parse(argsArr.slice(1)); @@ -1157,7 +1161,21 @@ 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. + for (const char *s : saveTempsValues) + config->saveTempsArgs.insert(s); + } else { + for (auto *arg : args.filtered(OPT_save_temps_eq)) { + StringRef s = arg->getValue(); + if (llvm::is_contained(saveTempsValues, s)) + config->saveTempsArgs.insert(s); + else + error("unknown --save-temps value: " + 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,promote,internalize,import,opt,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=%\opt -o %t/all2/a.out +; RUN: cmp %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: cmp %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 promote +; RUN: ld.lld main.o thin1.o --save-temps=promote +; RUN: cmp %t/all/a.out a.out && rm -f a.out +; RUN: mv *.1.promote* %t/all3 +; RUN: ls | count 2 + +;; Check internalize +; RUN: ld.lld main.o thin1.o --save-temps=internalize +; RUN: cmp %t/all/a.out a.out && rm -f a.out +; RUN: mv *.2.internalize* %t/all3 +; RUN: ls | count 2 + +;; Check import +; RUN: ld.lld main.o thin1.o --save-temps=import +; RUN: cmp %t/all/a.out a.out && rm -f a.out +; RUN: mv *.3.import* %t/all3 +; RUN: ls | count 2 + +;; Check opt +; RUN: ld.lld main.o thin1.o --save-temps=%\opt +; RUN: cmp %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: cmp %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: cmp %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: cmp %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=%\opt +; RUN: cmp %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=%\opt --save-temps=notastage 2>&1 \ +; RUN: | FileCheck %s +; CHECK: unknown --save-temps value: 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 @@ -267,8 +267,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 DenseSet &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,19 @@ exit(1); } -Error Config::addSaveTemps(std::string OutputFileName, - bool UseInputModulePath) { +Error Config::addSaveTemps(std::string OutputFileName, bool UseInputModulePath, + const DenseSet &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 +127,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 +147,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("promote")) + setHook("1.promote", PostPromoteModuleHook); + if (SaveTempsArgs.contains("internalize")) + setHook("2.internalize", PostInternalizeModuleHook); + if (SaveTempsArgs.contains("import")) + setHook("3.import", PostImportModuleHook); + if (SaveTempsArgs.contains("opt")) + 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,156 @@ +; 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 1.bc 2.bc -o all/a.out \ +; RUN: -import-constants-with-refs -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -save-temps + +;; The next 8 blocks follow this structure: +;; for each option of save-temps= +;; Run lto 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 -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -select-save-temps=preopt +; RUN: cmp all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: cmp 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 promote +; RUN: rm -f all2/*.1.promote* +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -select-save-temps=promote +; RUN: cmp all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: cmp 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 internalize +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -select-save-temps=internalize +; RUN: cmp all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: cmp 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 import +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -select-save-temps=import +; RUN: cmp all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: cmp 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 opt +; RUN: llvm-lto2 run 1.bc 2.bc -o build/a.out \ +; RUN: -import-constants-with-refs -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -select-save-temps=%\opt +; RUN: cmp all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: cmp 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 -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -select-save-temps=precodegen +; RUN: cmp all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: cmp 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 -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -select-save-temps=combinedindex +; RUN: cmp all/a.out.1 build/a.out.1 && rm -f build/a.out.1 +; RUN: cmp 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 -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -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 -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -r=2.bc,outer,pl \ +; RUN: -select-save-temps=preopt,combinedindex,%\opt +; RUN: cmp all/a.out.1 subset/a.out.1 && rm -f subset/a.out.1 +; RUN: cmp 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 -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -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 -r=1.bc,main,plx -r=1.bc,_Z6getObjv,l \ +; RUN: -r=2.bc,_Z6getObjv,pl -r=2.bc,val,pl -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,19 @@ 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,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 ThinLTODistributedIndexes("thinlto-distributed-indexes", cl::init(false), cl::desc("Write out individual index and " @@ -258,9 +271,22 @@ 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; + } + if (SaveTemps || !SelectSaveTemps.empty()) { + DenseSet 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; diff --git a/llvm/utils/lit/lit/TestRunner.py b/llvm/utils/lit/lit/TestRunner.py --- a/llvm/utils/lit/lit/TestRunner.py +++ b/llvm/utils/lit/lit/TestRunner.py @@ -1159,7 +1159,8 @@ ('%{pathsep}', os.pathsep), ('%t', tmpName), ('%basename_t', baseName), - ('%T', tmpDir)]) + ('%T', tmpDir), + ('%\\\\', "^" if kIsWindows else "\\\\")]) substitutions.extend([ ('%{fs-src-root}', pathlib.Path(sourcedir).anchor), @@ -1363,7 +1364,7 @@ return processed process = processLine if recursion_limit is None else processLineToFixedPoint - + return [unescapePercents(process(ln)) for ln in script]