https://github.com/sarnex updated 
https://github.com/llvm/llvm-project/pull/169572

>From 89b0fe0a8cc57d27c69d18927c1ae10833d82a29 Mon Sep 17 00:00:00 2001
From: Nick Sarnie <[email protected]>
Date: Tue, 25 Nov 2025 13:23:36 -0800
Subject: [PATCH 1/2] [clang][Driver][SPIR-V] Allow linking IR using llvm-link

Signed-off-by: Nick Sarnie <[email protected]>
---
 clang/lib/Driver/Driver.cpp           | 16 +++++++++++---
 clang/lib/Driver/ToolChains/SPIRV.cpp | 26 ++++++++++++++++++++++
 clang/lib/Driver/ToolChains/SPIRV.h   |  5 +++++
 clang/test/Driver/spirv-llvm-link.c   | 31 +++++++++++++++++++++++++++
 4 files changed, 75 insertions(+), 3 deletions(-)
 create mode 100644 clang/test/Driver/spirv-llvm-link.c

diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index de8d4601210ae..27d5bcd4290c6 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -4265,8 +4265,11 @@ void Driver::handleArguments(Compilation &C, 
DerivedArgList &Args,
       Args.AddFlagArg(nullptr,
                       getOpts().getOption(options::OPT_frtlib_add_rpath));
     }
-    // Emitting LLVM while linking disabled except in HIPAMD Toolchain
-    if (Args.hasArg(options::OPT_emit_llvm) && 
!Args.hasArg(options::OPT_hip_link))
+    // Emitting LLVM while linking disabled except in the HIPAMD or SPIR-V
+    // Toolchains
+    if (Args.hasArg(options::OPT_emit_llvm) &&
+        !Args.hasArg(options::OPT_hip_link) &&
+        !C.getDefaultToolChain().getTriple().isSPIRV())
       Diag(clang::diag::err_drv_emit_llvm_link);
     if (C.getDefaultToolChain().getTriple().isWindowsMSVCEnvironment() &&
         LTOMode != LTOK_None &&
@@ -4595,7 +4598,14 @@ void Driver::BuildDefaultActions(Compilation &C, 
DerivedArgList &Args,
       LA->propagateHostOffloadInfo(C.getActiveOffloadKinds(),
                                    /*BoundArch=*/nullptr);
     } else {
-      LA = C.MakeAction<LinkJobAction>(LinkerInputs, types::TY_Image);
+      // If we are linking but were passed -emit-llvm, we will be calling
+      // llvm-link, so set the output type accordingly. This is only allowed in
+      // rare cases, so make sure we aren't going to error about it.
+      types::ID LT =
+          Args.hasArg(options::OPT_emit_llvm) && !Diags.hasErrorOccurred()
+              ? types::TY_LLVM_BC
+              : types::TY_Image;
+      LA = C.MakeAction<LinkJobAction>(LinkerInputs, LT);
     }
     if (!UseNewOffloadingDriver)
       LA = OffloadBuilder->processHostLinkAction(LA);
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp 
b/clang/lib/Driver/ToolChains/SPIRV.cpp
index 27de55cfebfc1..ddb2a0bbb5058 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -70,6 +70,28 @@ void SPIRV::constructAssembleCommand(Compilation &C, const 
Tool &T,
                                          Exec, CmdArgs, Input, Output));
 }
 
+void SPIRV::constructLLVMLinkCommand(Compilation &C, const Tool &T,
+                                     const JobAction &JA,
+                                     const InputInfo &Output,
+                                     const InputInfoList &Inputs,
+                                     const llvm::opt::ArgStringList &Args) {
+  // Construct llvm-link command.
+  // The output from llvm-link is a bitcode file.
+  ArgStringList LlvmLinkArgs;
+
+  assert(!Inputs.empty() && "Must have at least one input.");
+
+  LlvmLinkArgs.append({"-o", Output.getFilename()});
+  for (auto Input : Inputs)
+    LlvmLinkArgs.push_back(Input.getFilename());
+
+  const char *LlvmLink =
+      C.getArgs().MakeArgString(T.getToolChain().GetProgramPath("llvm-link"));
+  C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(),
+                                         LlvmLink, LlvmLinkArgs, Inputs,
+                                         Output));
+}
+
 void SPIRV::Translator::ConstructJob(Compilation &C, const JobAction &JA,
                                      const InputInfo &Output,
                                      const InputInfoList &Inputs,
@@ -121,6 +143,10 @@ void SPIRV::Linker::ConstructJob(Compilation &C, const 
JobAction &JA,
                                  const InputInfoList &Inputs,
                                  const ArgList &Args,
                                  const char *LinkingOutput) const {
+  if (JA.getType() == types::TY_LLVM_BC) {
+    constructLLVMLinkCommand(C, *this, JA, Output, Inputs, {});
+    return;
+  }
   const ToolChain &ToolChain = getToolChain();
   std::string Linker = ToolChain.GetProgramPath(getShortName());
   ArgStringList CmdArgs;
diff --git a/clang/lib/Driver/ToolChains/SPIRV.h 
b/clang/lib/Driver/ToolChains/SPIRV.h
index 924eb01adcbbf..249053c23b792 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.h
+++ b/clang/lib/Driver/ToolChains/SPIRV.h
@@ -27,6 +27,11 @@ void constructAssembleCommand(Compilation &C, const Tool &T,
                               const InputInfo &Input,
                               const llvm::opt::ArgStringList &Args);
 
+void constructLLVMLinkCommand(Compilation &C, const Tool &T,
+                              const JobAction &JA, const InputInfo &Output,
+                              const InputInfoList &Inputs,
+                              const llvm::opt::ArgStringList &Args);
+
 class LLVM_LIBRARY_VISIBILITY Translator : public Tool {
 public:
   Translator(const ToolChain &TC)
diff --git a/clang/test/Driver/spirv-llvm-link.c 
b/clang/test/Driver/spirv-llvm-link.c
new file mode 100644
index 0000000000000..9c30654707016
--- /dev/null
+++ b/clang/test/Driver/spirv-llvm-link.c
@@ -0,0 +1,31 @@
+// Check BC input
+// RUN: mkdir -p %t
+// RUN: touch %t/a.bc
+// RUN: touch %t/b.bc
+// RUN: %clang -### --target=spirv64 -emit-llvm %t/a.bc %t/b.bc 2>&1 | 
FileCheck --check-prefix=CHECK-TOOL-BC %s
+
+// CHECK-TOOL-BC: "-cc1" {{.*}} "-o" "[[TMP1_BC:.+]]" "-x" "ir" "{{.*}}.bc"
+// CHECK-TOOL-BC: "-cc1" {{.*}} "-o" "[[TMP2_BC:.+]]" "-x" "ir" "{{.*}}.bc"
+// CHECK-TOOL-BC: llvm-link{{.*}} "-o" {{.*}} "[[TMP1_BC]]" "[[TMP2_BC]]"
+
+// RUN: %clang -ccc-print-bindings --target=spirv64 -emit-llvm %t/a.bc %t/b.bc 
2>&1 | FileCheck -check-prefix=CHECK-BINDINGS-BC %s
+
+// CHECK-BINDINGS-BC: "spirv64" - "clang", inputs: ["{{.*}}.bc"], output: 
"[[TMP1_BINDINGS_BC:.+]]"
+// CHECK-BINDINGS-BC: "spirv64" - "clang", inputs: ["{{.*}}.bc"], output: 
"[[TMP2_BINDINGS_BC:.+]]"
+// CHECK-BINDINGS-BC: "spirv64" - "SPIR-V::Linker", inputs: 
["[[TMP1_BINDINGS_BC]]", "[[TMP2_BINDINGS_BC]]"], output: "{{.*}}.bc"
+
+// Check source input
+// RUN: touch %t/foo.c
+// RUN: touch %t/bar.c
+
+// RUN: %clang -### --target=spirv64 -emit-llvm %t/foo.c %t/bar.c 2>&1 | 
FileCheck --check-prefix=CHECK-TOOL-SRC %s
+
+// CHECK-TOOL-SRC: "-cc1" {{.*}} "-o" "[[TMP1_SRC_BC:.+]]" "-x" "c" 
"{{.*}}foo.c"
+// CHECK-TOOL-SRC: "-cc1" {{.*}} "-o" "[[TMP2_SRC_BC:.+]]" "-x" "c" 
"{{.*}}bar.c"
+// CHECK-TOOL-SRC: llvm-link{{.*}} "-o" {{.*}} "[[TMP1_SRC_BC]]" 
"[[TMP2_SRC_BC]]"
+
+// RUN: %clang -ccc-print-bindings --target=spirv64 -emit-llvm %t/foo.c 
%t/bar.c 2>&1 | FileCheck -check-prefix=CHECK-BINDINGS-SRC %s
+
+// CHECK-BINDINGS-SRC: "spirv64" - "clang", inputs: ["{{.*}}foo.c"], output: 
"[[TMP1_BINDINGS_SRC_BC:.+]]"
+// CHECK-BINDINGS-SRC: "spirv64" - "clang", inputs: ["{{.*}}bar.c"], output: 
"[[TMP2_BINDINGS_SRC_BC:.+]]"
+// CHECK-BINDINGS-SRC: "spirv64" - "SPIR-V::Linker", inputs: 
["[[TMP1_BINDINGS_SRC_BC]]", "[[TMP2_BINDINGS_SRC_BC]]"], output: "{{.*}}.bc"

>From 4af3b7e2503f0500c588f96b69c697ce282ffa60 Mon Sep 17 00:00:00 2001
From: Nick Sarnie <[email protected]>
Date: Tue, 2 Dec 2025 14:21:21 -0800
Subject: [PATCH 2/2] add utility

Signed-off-by: Nick Sarnie <[email protected]>
---
 clang/include/clang/Driver/CommonArgs.h       |  8 ++++++
 clang/lib/Driver/ToolChains/CommonArgs.cpp    | 25 ++++++++++++++++++
 clang/lib/Driver/ToolChains/HIPAMD.cpp        | 26 ++++++-------------
 clang/lib/Driver/ToolChains/HIPSPV.cpp        |  7 ++---
 clang/lib/Driver/ToolChains/SPIRV.cpp         | 12 ++++-----
 clang/lib/Driver/ToolChains/SPIRV.h           |  2 +-
 .../Driver/hipspv-link-static-library.hip     |  4 +--
 clang/test/Driver/hipspv-toolchain-rdc.hip    |  2 +-
 clang/test/Driver/hipspv-toolchain.hip        |  2 +-
 9 files changed, 53 insertions(+), 35 deletions(-)

diff --git a/clang/include/clang/Driver/CommonArgs.h 
b/clang/include/clang/Driver/CommonArgs.h
index ac17d6211d882..f86ad4b758436 100644
--- a/clang/include/clang/Driver/CommonArgs.h
+++ b/clang/include/clang/Driver/CommonArgs.h
@@ -312,6 +312,14 @@ void setComplexRange(const Driver &D, StringRef NewOpt,
                      LangOptions::ComplexRangeKind NewRange, StringRef 
&LastOpt,
                      LangOptions::ComplexRangeKind &Range);
 
+void constructLlvmLinkCommand(Compilation &C, const Tool &T,
+                              const JobAction &JA,
+                              const InputInfoList &JobInputs,
+                              const llvm::opt::ArgStringList &LinkerInputs,
+                              const InputInfo &Output,
+                              const llvm::opt::ArgList &Args,
+                              const char *OutputFilename = nullptr);
+
 } // end namespace tools
 } // end namespace driver
 } // end namespace clang
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp 
b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 4c036f0f8dee3..8c922f47eeb8d 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -3637,3 +3637,28 @@ void tools::setComplexRange(const Driver &D, StringRef 
NewOpt,
   LastOpt = NewOpt;
   Range = NewRange;
 }
+
+void tools::constructLlvmLinkCommand(Compilation &C, const Tool &T,
+                                     const JobAction &JA,
+                                     const InputInfoList &JobInputs,
+                                     const ArgStringList &LinkerInputs,
+                                     const InputInfo &Output,
+                                     const llvm::opt::ArgList &Args,
+                                     const char *OutputFilename) {
+  // Construct llvm-link command.
+  // The output from llvm-link is a bitcode file.
+
+  assert(!LinkerInputs.empty() && !JobInputs.empty() &&
+         "Must have at least one input.");
+
+  ArgStringList LlvmLinkArgs(
+      {"-o", OutputFilename ? OutputFilename : Output.getFilename()});
+
+  LlvmLinkArgs.append(LinkerInputs);
+
+  const ToolChain &TC = T.getToolChain();
+  const char *LlvmLink = Args.MakeArgString(TC.GetProgramPath("llvm-link"));
+  C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(),
+                                         LlvmLink, LlvmLinkArgs, JobInputs,
+                                         Output));
+}
diff --git a/clang/lib/Driver/ToolChains/HIPAMD.cpp 
b/clang/lib/Driver/ToolChains/HIPAMD.cpp
index 231a38c2d3717..d51a4c86cd7c2 100644
--- a/clang/lib/Driver/ToolChains/HIPAMD.cpp
+++ b/clang/lib/Driver/ToolChains/HIPAMD.cpp
@@ -33,32 +33,22 @@ using namespace llvm::opt;
 #define NULL_FILE "/dev/null"
 #endif
 
-void AMDGCN::Linker::constructLlvmLinkCommand(Compilation &C,
-                                         const JobAction &JA,
-                                         const InputInfoList &Inputs,
-                                         const InputInfo &Output,
-                                         const llvm::opt::ArgList &Args) const 
{
-  // Construct llvm-link command.
-  // The output from llvm-link is a bitcode file.
-  ArgStringList LlvmLinkArgs;
+void AMDGCN::Linker::constructLlvmLinkCommand(
+    Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
+    const InputInfo &Output, const llvm::opt::ArgList &Args) const {
 
-  assert(!Inputs.empty() && "Must have at least one input.");
+  ArgStringList LinkerInputs;
 
-  LlvmLinkArgs.append({"-o", Output.getFilename()});
   for (auto Input : Inputs)
-    LlvmLinkArgs.push_back(Input.getFilename());
+    LinkerInputs.push_back(Input.getFilename());
 
   // Look for archive of bundled bitcode in arguments, and add temporary files
   // for the extracted archive of bitcode to inputs.
   auto TargetID = Args.getLastArgValue(options::OPT_mcpu_EQ);
-  AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LlvmLinkArgs, 
"amdgcn",
+  AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LinkerInputs, 
"amdgcn",
                              TargetID, /*IsBitCodeSDL=*/true);
-
-  const char *LlvmLink =
-    Args.MakeArgString(getToolChain().GetProgramPath("llvm-link"));
-  C.addCommand(std::make_unique<Command>(JA, *this, 
ResponseFileSupport::None(),
-                                         LlvmLink, LlvmLinkArgs, Inputs,
-                                         Output));
+  tools::constructLlvmLinkCommand(C, *this, JA, Inputs, LinkerInputs, Output,
+                                  Args);
 }
 
 void AMDGCN::Linker::constructLldCommand(Compilation &C, const JobAction &JA,
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp 
b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index be0f49d8e1497..18d7636c37a6d 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -69,11 +69,8 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
       "generic"; // SPIR-V is generic, no specific target ID like -mcpu
   tools::AddStaticDeviceLibsLinking(C, *this, JA, Inputs, Args, LinkArgs, Arch,
                                     Target, /*IsBitCodeSDL=*/true);
-  LinkArgs.append({"-o", TempFile});
-  const char *LlvmLink =
-      Args.MakeArgString(getToolChain().GetProgramPath("llvm-link"));
-  C.addCommand(std::make_unique<Command>(JA, *this, 
ResponseFileSupport::None(),
-                                         LlvmLink, LinkArgs, Inputs, Output));
+  tools::constructLlvmLinkCommand(C, *this, JA, Inputs, LinkArgs, Output, Args,
+                                  TempFile);
 
   // Post-link HIP lowering.
 
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp 
b/clang/lib/Driver/ToolChains/SPIRV.cpp
index ddb2a0bbb5058..44f7a917aa84a 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -74,17 +74,15 @@ void SPIRV::constructLLVMLinkCommand(Compilation &C, const 
Tool &T,
                                      const JobAction &JA,
                                      const InputInfo &Output,
                                      const InputInfoList &Inputs,
-                                     const llvm::opt::ArgStringList &Args) {
-  // Construct llvm-link command.
-  // The output from llvm-link is a bitcode file.
-  ArgStringList LlvmLinkArgs;
+                                     const llvm::opt::ArgList &Args) {
 
-  assert(!Inputs.empty() && "Must have at least one input.");
+  ArgStringList LlvmLinkArgs;
 
-  LlvmLinkArgs.append({"-o", Output.getFilename()});
   for (auto Input : Inputs)
     LlvmLinkArgs.push_back(Input.getFilename());
 
+  tools::constructLlvmLinkCommand(C, T, JA, Inputs, LlvmLinkArgs, Output, 
Args);
+
   const char *LlvmLink =
       C.getArgs().MakeArgString(T.getToolChain().GetProgramPath("llvm-link"));
   C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(),
@@ -144,7 +142,7 @@ void SPIRV::Linker::ConstructJob(Compilation &C, const 
JobAction &JA,
                                  const ArgList &Args,
                                  const char *LinkingOutput) const {
   if (JA.getType() == types::TY_LLVM_BC) {
-    constructLLVMLinkCommand(C, *this, JA, Output, Inputs, {});
+    constructLLVMLinkCommand(C, *this, JA, Output, Inputs, Args);
     return;
   }
   const ToolChain &ToolChain = getToolChain();
diff --git a/clang/lib/Driver/ToolChains/SPIRV.h 
b/clang/lib/Driver/ToolChains/SPIRV.h
index 249053c23b792..018e9fa99607c 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.h
+++ b/clang/lib/Driver/ToolChains/SPIRV.h
@@ -30,7 +30,7 @@ void constructAssembleCommand(Compilation &C, const Tool &T,
 void constructLLVMLinkCommand(Compilation &C, const Tool &T,
                               const JobAction &JA, const InputInfo &Output,
                               const InputInfoList &Inputs,
-                              const llvm::opt::ArgStringList &Args);
+                              const llvm::opt::ArgList &Args);
 
 class LLVM_LIBRARY_VISIBILITY Translator : public Tool {
 public:
diff --git a/clang/test/Driver/hipspv-link-static-library.hip 
b/clang/test/Driver/hipspv-link-static-library.hip
index 03126ae589a09..cf16236738c12 100644
--- a/clang/test/Driver/hipspv-link-static-library.hip
+++ b/clang/test/Driver/hipspv-link-static-library.hip
@@ -22,7 +22,7 @@
 // Verify that the input files are added before the SDL files in llvm-link 
command
 // This tests the ordering fix to match HIPAMD behavior
 // SDL-LINK: "{{.*}}clang-offload-bundler" "-unbundle" "-type=a" 
"-input={{.*}}libSDL.a" "-targets=hip-spirv64-unknown-unknown-unknown-generic" 
"-output=[[SDL_A:.*\.a]]" "-allow-missing-bundles"
-// SDL-LINK: "{{.*}}llvm-link" "{{.*}}.bc" "[[SDL_A]]" "-o"
+// SDL-LINK: "{{.*}}llvm-link" "-o" "{{.*}}.bc" "{{.*}}.bc" "[[SDL_A]]"
 
 // SDL-ARCHIVE: "{{.*}}clang-offload-bundler" "-unbundle" "-type=a" 
"-input={{.*}}libSDL.a" "-targets=hip-spirv64-unknown-unknown-unknown-generic" 
"-output=[[SDL_A:.*\.a]]" "-allow-missing-bundles"  
-// SDL-ARCHIVE: "{{.*}}llvm-link" "{{.*}}.bc" "[[SDL_A]]" "-o"
+// SDL-ARCHIVE: "{{.*}}llvm-link" "-o" "{{.*}}.bc" "{{.*}}.bc" "[[SDL_A]]"
diff --git a/clang/test/Driver/hipspv-toolchain-rdc.hip 
b/clang/test/Driver/hipspv-toolchain-rdc.hip
index acdadacc49064..05d9079f8dc18 100644
--- a/clang/test/Driver/hipspv-toolchain-rdc.hip
+++ b/clang/test/Driver/hipspv-toolchain-rdc.hip
@@ -41,7 +41,7 @@
 // CHECK-SAME: {{.*}} [[B_SRC]]
 
 // Link device code, lower it with HIPSPV passes and emit SPIR-V binary.
-// CHECK: {{".*llvm-link.*"}} [[A_BC1]] [[B_BC1]] "-o" [[AB_LINK:".*bc"]]
+// CHECK: {{".*llvm-link.*"}} "-o" [[AB_LINK:".*bc"]] [[A_BC1]] [[B_BC1]]
 // CHECK: {{".*opt.*"}} [[AB_LINK]] "-load-pass-plugin"
 // CHECK-SAME: "{{.*}}/Inputs/hipspv/lib/libLLVMHipSpvPasses.so"
 // CHECK-SAME: "-o" [[AB_LOWER:".*bc"]]
diff --git a/clang/test/Driver/hipspv-toolchain.hip 
b/clang/test/Driver/hipspv-toolchain.hip
index 3c175ebf433cc..85c4333e877f2 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -14,7 +14,7 @@
 // CHECK-SAME: "-o" [[DEV_BC:".*bc"]]
 // CHECK-SAME: "-x" "hip"
 
-// CHECK: {{".*llvm-link"}} [[DEV_BC]] "-o" [[LINK_BC:".*bc"]]
+// CHECK: {{".*llvm-link"}} "-o" [[LINK_BC:".*bc"]] [[DEV_BC]]
 
 // CHECK: {{".*opt"}} [[LINK_BC]] "-load-pass-plugin"
 // CHECK-SAME: {{".*/hipspv/lib/libLLVMHipSpvPasses.so"}}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to