diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -535,8 +535,8 @@ } constexpr const char *saveTempsValues[] = { - "resolution", "preopt", "promote", "internalize", "import", - "opt", "precodegen", "prelink", "combinedindex"}; + "resolution", "preopt", "promote", "internalize", "import", + "opt", "precodegen", "asm", "prelink", "combinedindex"}; void LinkerDriver::linkerMain(ArrayRef argsArr) { ELFOptTable parser; diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -591,7 +591,7 @@ 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">; + Values<"resolution,preopt,promote,internalize,import,opt,precodegen,asm,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/COFF/savetemps.ll b/lld/test/COFF/savetemps.ll --- a/lld/test/COFF/savetemps.ll +++ b/lld/test/COFF/savetemps.ll @@ -7,7 +7,7 @@ ; RUN: not llvm-dis -o - %T/savetemps/savetemps.exe.0.0.preopt.bc ; RUN: not llvm-dis -o - %T/savetemps/savetemps.exe.0.2.internalize.bc ; RUN: not llvm-dis -o - %T/savetemps/savetemps.exe.0.4.opt.bc -; RUN: not llvm-dis -o - %T/savetemps/savetemps.exe.0.5.precodegen.bc +; RUN: not llvm-dis -o - %T/savetemps/savetemps.exe.0.5.precodegen.bc. ; RUN: not llvm-objdump -s %T/savetemps/savetemps.exe.lto.obj ; RUN: lld-link /lldsavetemps /out:%T/savetemps/savetemps.exe /entry:main \ ; RUN: /subsystem:console %T/savetemps/savetemps.obj @@ -15,11 +15,13 @@ ; RUN: llvm-dis -o - %T/savetemps/savetemps.exe.0.2.internalize.bc | FileCheck %s ; RUN: llvm-dis -o - %T/savetemps/savetemps.exe.0.4.opt.bc | FileCheck %s ; RUN: llvm-dis -o - %T/savetemps/savetemps.exe.0.5.precodegen.bc | FileCheck %s +; RUN: cat %T/savetemps/savetemps.exe.0.6.asm.s | FileCheck %s --check-prefix=CHECK-ASM ; RUN: llvm-objdump -s %T/savetemps/savetemps.exe.lto.obj | \ ; RUN: FileCheck --check-prefix=CHECK-OBJDUMP %s ; CHECK: define i32 @main() ; CHECK-OBJDUMP: file format coff +; CHECK-ASM: .globl main target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-windows-msvc" diff --git a/lld/test/ELF/lto/save-temps-eq.ll b/lld/test/ELF/lto/save-temps-eq.ll --- a/lld/test/ELF/lto/save-temps-eq.ll +++ b/lld/test/ELF/lto/save-temps-eq.ll @@ -73,6 +73,12 @@ ; RUN: mv *.5.precodegen* %t/all3 ; RUN: ls | count 2 +;; Check asm +; RUN: ld.lld main.o thin1.o --save-temps=asm +; RUN: cmp %t/all/a.out a.out && rm -f a.out +; RUN: mv *.6.asm* %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 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 @@ -239,6 +239,12 @@ /// splitting the module. ModuleHookFn PreCodeGenModuleHook; + // When this hook is non-null, and the output file type isn't an assembly file + // performs codegen a second time to emit an assembly file into + // the output stream returned by this hook. + std::function(const Module &, unsigned Task)> + GetAssemblyOutputStream; + /// A combined index hook is called after all per-module indexes have been /// combined (ThinLTO-specific). It can be used to implement -save-temps for /// the combined index. 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 @@ -82,6 +82,37 @@ exit(1); } +/// Creates an output stream for temporary files based on the Module's name, +/// the output file name and other information such as the desired file +/// extension. +std::unique_ptr +createTempFileOutputStream(const Module &M, std::string OutputFileName, + bool UseInputModulePath, unsigned Task, + std::string PathSuffix, std::string Extension) { + std::string PathPrefix; + // If this is the combined module (not a ThinLTO backend compile) or the + // user hasn't requested using the input module's path, emit to a file + // named from the provided OutputFileName with the Task ID appended. + if (M.getModuleIdentifier() == "ld-temp.o" || !UseInputModulePath) { + PathPrefix = OutputFileName; + if (Task != (unsigned)-1) + PathPrefix += utostr(Task) + "."; + } else + PathPrefix = M.getModuleIdentifier() + "."; + std::string Path = PathPrefix + PathSuffix; + if (!Extension.empty()) + Path += "." + Extension; + std::error_code EC; + auto OS = + std::make_unique(Path, EC, sys::fs::OpenFlags::OF_None); + // Because -save-temps is a debugging feature, we report the error + // directly and exit. + if (EC) + reportOpenError(Path, EC.message()); + + return OS; +} + Error Config::addSaveTemps(std::string OutputFileName, bool UseInputModulePath, const DenseSet &SaveTempsArgs) { ShouldDiscardValueNames = false; @@ -106,28 +137,20 @@ if (LinkerHook && !LinkerHook(Task, M)) return false; - std::string PathPrefix; - // If this is the combined module (not a ThinLTO backend compile) or the - // user hasn't requested using the input module's path, emit to a file - // named from the provided OutputFileName with the Task ID appended. - if (M.getModuleIdentifier() == "ld-temp.o" || !UseInputModulePath) { - PathPrefix = OutputFileName; - if (Task != (unsigned)-1) - PathPrefix += utostr(Task) + "."; - } else - PathPrefix = M.getModuleIdentifier() + "."; - std::string Path = PathPrefix + PathSuffix + ".bc"; - std::error_code EC; - raw_fd_ostream OS(Path, EC, sys::fs::OpenFlags::OF_None); - // Because -save-temps is a debugging feature, we report the error - // directly and exit. - if (EC) - reportOpenError(Path, EC.message()); - WriteBitcodeToFile(M, OS, /*ShouldPreserveUseListOrder=*/false); + std::unique_ptr OS = createTempFileOutputStream( + M, OutputFileName, UseInputModulePath, Task, PathSuffix, "bc"); + WriteBitcodeToFile(M, *OS, /*ShouldPreserveUseListOrder=*/false); return true; }; }; + auto setSaveAsmOutputHook = [&](std::string PathSuffix) { + GetAssemblyOutputStream = [=](const Module &M, unsigned TaskID) { + return createTempFileOutputStream(M, OutputFileName, UseInputModulePath, + TaskID, PathSuffix, "s"); + }; + }; + auto SaveCombinedIndex = [=](const ModuleSummaryIndex &Index, const DenseSet &GUIDPreservedSymbols) { @@ -155,6 +178,7 @@ setHook("3.import", PostImportModuleHook); setHook("4.opt", PostOptModuleHook); setHook("5.precodegen", PreCodeGenModuleHook); + setSaveAsmOutputHook("6.asm"); CombinedIndexHook = SaveCombinedIndex; } else { if (SaveTempsArgs.contains("preopt")) @@ -169,6 +193,8 @@ setHook("4.opt", PostOptModuleHook); if (SaveTempsArgs.contains("precodegen")) setHook("5.precodegen", PreCodeGenModuleHook); + if (SaveTempsArgs.contains("asm")) + setSaveAsmOutputHook("6.asm"); if (SaveTempsArgs.contains("combinedindex")) CombinedIndexHook = SaveCombinedIndex; } @@ -398,18 +424,39 @@ std::unique_ptr &Stream = *StreamOrErr; TM->Options.ObjectFilenameForDebug = Stream->ObjectPathName; - legacy::PassManager CodeGenPasses; - TargetLibraryInfoImpl TLII(Triple(Mod.getTargetTriple())); - CodeGenPasses.add(new TargetLibraryInfoWrapperPass(TLII)); - CodeGenPasses.add( - createImmutableModuleSummaryIndexWrapperPass(&CombinedIndex)); - if (Conf.PreCodeGenPassesHook) - Conf.PreCodeGenPassesHook(CodeGenPasses); - if (TM->addPassesToEmitFile(CodeGenPasses, *Stream->OS, - DwoOut ? &DwoOut->os() : nullptr, - Conf.CGFileType)) - report_fatal_error("Failed to setup codegen"); - CodeGenPasses.run(Mod); + const auto GetPassManager = [&]() { + legacy::PassManager CodeGenPasses; + TargetLibraryInfoImpl TLII(Triple(Mod.getTargetTriple())); + CodeGenPasses.add(new TargetLibraryInfoWrapperPass(TLII)); + CodeGenPasses.add( + createImmutableModuleSummaryIndexWrapperPass(&CombinedIndex)); + if (Conf.PreCodeGenPassesHook) + Conf.PreCodeGenPassesHook(CodeGenPasses); + return CodeGenPasses; + }; + + { + legacy::PassManager CodeGenPasses = GetPassManager(); + if (TM->addPassesToEmitFile(CodeGenPasses, *Stream->OS, + DwoOut ? &DwoOut->os() : nullptr, + Conf.CGFileType)) + report_fatal_error("Failed to setup codegen"); + CodeGenPasses.run(Mod); + } + + // If we didn't output an assembly file, but one is desired, re-codegen + // the module to create the file in the desired output stream. + // Doing codegen twice may seem inefficient, but this is designed as a debug + // option (primarily for --save-temps) so performance isn't critical. + if (Conf.CGFileType != CGFT_AssemblyFile && Conf.GetAssemblyOutputStream) { + std::unique_ptr AsmOutputStream = + Conf.GetAssemblyOutputStream(Mod, Task); + legacy::PassManager CodeGenPasses = GetPassManager(); + if (TM->addPassesToEmitFile(CodeGenPasses, *AsmOutputStream, nullptr, + CGFT_AssemblyFile)) + report_fatal_error("Failed to setup codegen"); + CodeGenPasses.run(Mod); + } if (DwoOut) DwoOut->keep(); diff --git a/llvm/test/ThinLTO/X86/selective-save-temps.ll b/llvm/test/ThinLTO/X86/selective-save-temps.ll --- a/llvm/test/ThinLTO/X86/selective-save-temps.ll +++ b/llvm/test/ThinLTO/X86/selective-save-temps.ll @@ -89,6 +89,16 @@ ; RUN: mv build/*.5.precodegen* all2 ; RUN: ls build | count 0 +;; Check asm +; 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=asm +; 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/*.6.asm* 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 \ 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 @@ -71,14 +71,14 @@ "select-save-temps", cl::value_desc("One, or multiple of: " "resolution,preopt,promote,internalize,import,opt,precodegen" - ",combinedindex"), + ",asm,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"}; + "resolution", "preopt", "promote", "internalize", "import", + "opt", "precodegen", "asm", "combinedindex"}; static cl::opt ThinLTODistributedIndexes("thinlto-distributed-indexes",