[lld] [llvm] [LTO][lld] Emit textual assembly code with '--save-temps' (PR #195922)
Joseph Huber via llvm-commits
llvm-commits at lists.llvm.org
Wed May 6 06:25:18 PDT 2026
https://github.com/jhuber6 updated https://github.com/llvm/llvm-project/pull/195922
>From 7fc611fe4f67e305ae68d5c52fe4606858ae6261 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Tue, 5 May 2026 14:41:54 -0500
Subject: [PATCH 1/2] [LTO][lld] Emit textual assembly code with '--save-temps'
Summary:
Right now `--save-temps` does not yield `.s` files like you would
recieve if run through `clang foo.bc --save-temps`. This PR adds a
simple hook that outputs a `.s` as well.
The main motivation for this is for the AMD GPU target and offloading
where `--lto-emit-asm` does not work because the linker step occurs in
the middle of the compilation. This means the lack of output causes the
rest of the compilation to fail.
The implementation here just uses `cloneModule` because the typical
pipeline just emits directly to an object file. An alternative approach
would be to emit textual assembly, then create an MC object to turn that
into an object file. I thought this was overkill and likely slower
overall. The only reason I think this approach would be useful is if we
wanted to expose a callback on the target machine, but that would
probably slow down the rest of the pipeline. I will leave this up to
reviewers.
---
lld/ELF/Driver.cpp | 4 ++--
llvm/include/llvm/LTO/Config.h | 4 ++++
llvm/lib/LTO/LTOBackend.cpp | 29 +++++++++++++++++++++++++++++
3 files changed, 35 insertions(+), 2 deletions(-)
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index ce8eb25a0d5da..2b872f5675a6e 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -621,8 +621,8 @@ static void checkZOptions(Ctx &ctx, opt::InputArgList &args) {
}
constexpr const char *saveTempsValues[] = {
- "resolution", "preopt", "promote", "internalize", "import",
- "opt", "precodegen", "prelink", "combinedindex"};
+ "resolution", "preopt", "promote", "internalize", "import",
+ "opt", "precodegen", "prelink", "combinedindex", "asm"};
LinkerDriver::LinkerDriver(Ctx &ctx) : ctx(ctx) {}
diff --git a/llvm/include/llvm/LTO/Config.h b/llvm/include/llvm/LTO/Config.h
index 2aeb902bcfccf..2523b49158e30 100644
--- a/llvm/include/llvm/LTO/Config.h
+++ b/llvm/include/llvm/LTO/Config.h
@@ -95,6 +95,10 @@ struct Config {
/// want to know a priori all possible output files.
bool AlwaysEmitRegularLTOObj = false;
+ /// When non-empty, LTO codegen will also emit textual assembly to this
+ /// prefix.
+ std::string SaveTempsAsmPrefix;
+
/// If true, the LTO instance creates copies of the symbol names for LTO::run.
/// The lld linker uses string saver to keep symbol names alive and doesn't
/// need to create copies, so it can set this field to false.
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 7ee2557a68bd5..9736811a0c09f 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -43,6 +43,7 @@
#include "llvm/Target/TargetMachine.h"
#include "llvm/TargetParser/SubtargetFeature.h"
#include "llvm/Transforms/IPO/WholeProgramDevirt.h"
+#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/FunctionImportUtils.h"
#include "llvm/Transforms/Utils/SplitModule.h"
#include <optional>
@@ -173,6 +174,7 @@ Error Config::addSaveTemps(std::string OutputFileName, bool UseInputModulePath,
setHook("3.import", PostImportModuleHook);
setHook("4.opt", PostOptModuleHook);
setHook("5.precodegen", PreCodeGenModuleHook);
+ SaveTempsAsmPrefix = OutputFileName;
CombinedIndexHook = SaveCombinedIndex;
} else {
if (SaveTempsArgs.contains("preopt"))
@@ -189,6 +191,8 @@ Error Config::addSaveTemps(std::string OutputFileName, bool UseInputModulePath,
setHook("5.precodegen", PreCodeGenModuleHook);
if (SaveTempsArgs.contains("combinedindex"))
CombinedIndexHook = SaveCombinedIndex;
+ if (SaveTempsArgs.contains("asm"))
+ SaveTempsAsmPrefix = OutputFileName;
}
return Error::success();
@@ -423,6 +427,31 @@ static void codegen(const Config &Conf, TargetMachine *TM,
if (Conf.PreCodeGenModuleHook && !Conf.PreCodeGenModuleHook(Task, Mod))
return;
+ if (!Conf.SaveTempsAsmPrefix.empty()) {
+ std::string PathPrefix;
+ if (Mod.getModuleIdentifier() == "ld-temp.o") {
+ PathPrefix = Conf.SaveTempsAsmPrefix;
+ if (Task != (unsigned)-1)
+ PathPrefix += utostr(Task) + ".";
+ } else {
+ PathPrefix = Mod.getModuleIdentifier() + ".";
+ }
+ std::string AsmPath = PathPrefix + "s";
+ std::error_code EC;
+ raw_fd_ostream AsmOS(AsmPath, EC, sys::fs::OF_Text);
+ if (EC)
+ reportOpenError(AsmPath, EC.message());
+ auto ClonedMod = CloneModule(Mod);
+ legacy::PassManager AsmPasses;
+ TargetLibraryInfoImpl TLII(ClonedMod->getTargetTriple(),
+ TM->Options.VecLib);
+ AsmPasses.add(new TargetLibraryInfoWrapperPass(TLII));
+ if (TM->addPassesToEmitFile(AsmPasses, AsmOS, nullptr,
+ CodeGenFileType::AssemblyFile))
+ report_fatal_error("Failed to setup assembly codegen");
+ AsmPasses.run(*ClonedMod);
+ }
+
if (EmbedBitcode == LTOBitcodeEmbedding::EmbedOptimized)
llvm::embedBitcodeInModule(Mod, llvm::MemoryBufferRef(),
/*EmbedBitcode*/ true,
>From 535188684f5e87409e02ec7f09469a438ade7e20 Mon Sep 17 00:00:00 2001
From: Joseph Huber <huberjn at outlook.com>
Date: Wed, 6 May 2026 08:25:06 -0500
Subject: [PATCH 2/2] Not by default
---
lld/ELF/Driver.cpp | 6 ++++--
lld/test/ELF/lto/save-temps-eq.ll | 8 ++++++++
llvm/lib/LTO/LTOBackend.cpp | 1 -
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 2b872f5675a6e..49bb3341fb279 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1551,8 +1551,10 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
!args.hasArg(OPT_relocatable) || args.hasArg(OPT_force_group_allocation);
if (args.hasArg(OPT_save_temps)) {
- // --save-temps implies saving all temps.
- ctx.arg.saveTempsArgs.insert_range(saveTempsValues);
+ // --save-temps implies saving all temps except "asm".
+ for (const char *v : saveTempsValues)
+ if (StringRef(v) != "asm")
+ ctx.arg.saveTempsArgs.insert(v);
} else {
for (auto *arg : args.filtered(OPT_save_temps_eq)) {
StringRef s = arg->getValue();
diff --git a/lld/test/ELF/lto/save-temps-eq.ll b/lld/test/ELF/lto/save-temps-eq.ll
index 9befc99af9253..240876041dc5a 100644
--- a/lld/test/ELF/lto/save-temps-eq.ll
+++ b/lld/test/ELF/lto/save-temps-eq.ll
@@ -14,6 +14,8 @@
;; 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
+;; --save-temps should not produce .s files.
+; RUN: not ls *.s 2>/dev/null
; RUN: mv a.out.lto.* *.o.*.bc %t/all
;; Sanity check that everything got moved
; RUN: ls | count 2
@@ -94,6 +96,12 @@
; RUN: mv *.resolution.txt %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: ls main.o.s thin1.o.s && rm main.o.s thin1.o.s
+; 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
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index 9736811a0c09f..4db08ce484b81 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -174,7 +174,6 @@ Error Config::addSaveTemps(std::string OutputFileName, bool UseInputModulePath,
setHook("3.import", PostImportModuleHook);
setHook("4.opt", PostOptModuleHook);
setHook("5.precodegen", PreCodeGenModuleHook);
- SaveTempsAsmPrefix = OutputFileName;
CombinedIndexHook = SaveCombinedIndex;
} else {
if (SaveTempsArgs.contains("preopt"))
More information about the llvm-commits
mailing list