Skip to content

Conversation

@tarunprabhu
Copy link
Contributor

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.

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.
@llvmbot
Copy link
Member

llvmbot commented Dec 2, 2025

@llvm/pr-subscribers-lld

@llvm/pr-subscribers-lld-macho

Author: Tarun Prabhu (tarunprabhu)

Changes

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.


Full diff: https://github.com/llvm/llvm-project/pull/170355.diff

5 Files Affected:

  • (modified) lld/MachO/Config.h (+1)
  • (modified) lld/MachO/Driver.cpp (+5-4)
  • (modified) lld/MachO/LTO.cpp (+10)
  • (modified) lld/MachO/Options.td (+2)
  • (added) lld/test/MachO/lto-emit-llvm.ll (+18)
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<SectionAlign> sectionAlignments;
   std::vector<SegmentProtection> 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<const char *> 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<const char *> 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<raw_fd_ostream> 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<grp_lld>;
 def lto_newpm_passes: Joined<["--"], "lto-newpm-passes=">,
     HelpText<"Passes to run during LTO">, Group<grp_lld>;
+def lto_emit_llvm: Flag<["--"], "lto-emit-llvm">,
+    HelpText<"Emit LLVM-IR bitcode">;
 def load_pass_plugins : Separate<["--"], "load-pass-plugin">, Group<grp_lld>;
 def load_pass_plugins_eq : Joined<["--"], "load-pass-plugin=">,
     Alias<!cast<Separate>(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
+}

Comment on lines 191 to 192
def lto_emit_llvm: Flag<["--"], "lto-emit-llvm">,
HelpText<"Emit LLVM-IR bitcode">;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Group<grp_lld> since this is a LLD-only option and it is not supported by ld64?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thank you for taking a look.

Copy link
Contributor

@drodriguez drodriguez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing the new code to the ELF implementation it seems to duplicate the existing code pretty close, so I think this looks good to me. You might want to wait a little before merging in case someone else wants to have a look.

@tarunprabhu
Copy link
Contributor Author

Thanks for the review. I'm perfectly ok with leaving this up for a week or so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants