From 1e785a757eba6ecb2f276a4d5088de7bd919996b Mon Sep 17 00:00:00 2001 From: Tarun Prabhu Date: Tue, 2 Dec 2025 11:48:48 -0700 Subject: [PATCH 1/2] [lld][MachO] Add --lto-emit-llvm command line option This option will cause the linker to emit LLVM bitcode instead of an object file. The implementation is similar to that of the corresponding option in the ELF backend. This only works with LLD and will not work the gold plugin. --- lld/MachO/Config.h | 1 + lld/MachO/Driver.cpp | 9 +++++---- lld/MachO/LTO.cpp | 10 ++++++++++ lld/MachO/Options.td | 2 ++ lld/test/MachO/lto-emit-llvm.ll | 18 ++++++++++++++++++ 5 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 lld/test/MachO/lto-emit-llvm.ll diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h index a2ca5770bf952..bacfaa241f69b 100644 --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -216,6 +216,7 @@ struct Configuration { std::vector sectionAlignments; std::vector segmentProtections; bool ltoDebugPassManager = false; + bool emitLLVM = false; llvm::StringRef codegenDataGeneratePath; bool csProfileGenerate = false; llvm::StringRef csProfilePath; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp index 28c817c54c85d..94dbbf04a56f1 100644 --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -1999,6 +1999,7 @@ bool link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, config->ignoreAutoLinkOptions.insert(arg->getValue()); config->strictAutoLink = args.hasArg(OPT_strict_auto_link); config->ltoDebugPassManager = args.hasArg(OPT_lto_debug_pass_manager); + config->emitLLVM = args.hasArg(OPT_lto_emit_llvm); config->codegenDataGeneratePath = args.getLastArgValue(OPT_codegen_data_generate_path); config->csProfileGenerate = args.hasArg(OPT_cs_profile_generate); @@ -2344,10 +2345,10 @@ bool link(ArrayRef argsArr, llvm::raw_ostream &stdoutOS, resolveLCLinkerOptions(); - // If --thinlto-index-only is given, we should create only "index - // files" and not object files. Index file creation is already done - // in compileBitcodeFiles, so we are done if that's the case. - if (config->thinLTOIndexOnly) + // If either --thinlto-index-only or --lto-emit-llvm is given, we should + // not create object files. Index file creation is already done in + // compileBitcodeFiles, so we are done if that's the case. + if (config->thinLTOIndexOnly || config->emitLLVM) return errorCount() == 0; // LTO may emit a non-hidden (extern) object file symbol even if the diff --git a/lld/MachO/LTO.cpp b/lld/MachO/LTO.cpp index 4695b639dcc96..2c360374ef3cc 100644 --- a/lld/MachO/LTO.cpp +++ b/lld/MachO/LTO.cpp @@ -64,6 +64,16 @@ static lto::Config createConfig() { if (config->saveTemps) checkError(c.addSaveTemps(config->outputFile.str() + ".", /*UseInputModulePath=*/true)); + + if (config->emitLLVM) { + llvm::StringRef outputFile = config->outputFile; + c.PreCodeGenModuleHook = [outputFile](size_t task, const Module &m) { + if (std::unique_ptr os = openLTOOutputFile(outputFile)) + WriteBitcodeToFile(m, *os, false); + return false; + }; + } + return c; } diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td index be1a1cc2963d9..15491e7d42fd2 100644 --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -188,6 +188,8 @@ def lto_debug_pass_manager: Flag<["--"], "lto-debug-pass-manager">, HelpText<"Debug new pass manager">, Group; def lto_newpm_passes: Joined<["--"], "lto-newpm-passes=">, HelpText<"Passes to run during LTO">, Group; +def lto_emit_llvm: Flag<["--"], "lto-emit-llvm">, + HelpText<"Emit LLVM-IR bitcode">; def load_pass_plugins : Separate<["--"], "load-pass-plugin">, Group; def load_pass_plugins_eq : Joined<["--"], "load-pass-plugin=">, Alias(load_pass_plugins)>, diff --git a/lld/test/MachO/lto-emit-llvm.ll b/lld/test/MachO/lto-emit-llvm.ll new file mode 100644 index 0000000000000..77bf700837dfb --- /dev/null +++ b/lld/test/MachO/lto-emit-llvm.ll @@ -0,0 +1,18 @@ +; REQUIRES: x86 +; +; Check that the --lto-emit-llvm option is handled correctly. +; +; RUN: opt %s -o %t.o +; RUN: ld.lld --lto-emit-llvm %t.o -o %t.out.o +; RUN: llvm-dis < %t.out.o -o - | FileCheck %s +; +; CHECK: define hidden void @main() + +target triple = "x86_64-apple-darwin" +target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" + +@llvm.compiler.used = appending global [1 x ptr] [ptr @main], section "llvm.metadata" + +define hidden void @main() { + ret void +} From e2bde18320f09d0017fca374773936f117872174 Mon Sep 17 00:00:00 2001 From: Tarun Prabhu Date: Wed, 3 Dec 2025 18:57:52 -0700 Subject: [PATCH 2/2] Add --lto-emit-llvm to the LLD group since it is not supported by ld64 --- lld/MachO/Options.td | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td index 15491e7d42fd2..4954903e2548e 100644 --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -189,7 +189,7 @@ def lto_debug_pass_manager: Flag<["--"], "lto-debug-pass-manager">, def lto_newpm_passes: Joined<["--"], "lto-newpm-passes=">, HelpText<"Passes to run during LTO">, Group; def lto_emit_llvm: Flag<["--"], "lto-emit-llvm">, - HelpText<"Emit LLVM-IR bitcode">; + HelpText<"Emit LLVM-IR bitcode">, Group; def load_pass_plugins : Separate<["--"], "load-pass-plugin">, Group; def load_pass_plugins_eq : Joined<["--"], "load-pass-plugin=">, Alias(load_pass_plugins)>,