Skip to content

Conversation

@mgcarrasco
Copy link
Contributor

@mgcarrasco mgcarrasco commented Nov 11, 2025

This PR enables the SPIR-V backend in the new driver via -use-spirv-backend.

When the SPIR-V backend must be called in --offload-device-only mode, we rely on the default compiler path. The changes for this end are only to avoid external dependencies such as spirv-as and spirv-link.

When --offload-device-only is unset, clang-linker-wrapper calls clang for linking the bitcode(s). In this case, we link the LLVM bitcode and call the backend to avoid changing clang-linker-wrapper and relying on spirv-link.

The expected behavior is that when -use-spirv-backend is set and the new driver requires generating SPIR-V it does so using the SPIR-V backend instead of the SPIR-V translator; this must work with and without --offload-device-only.
The output file type remains as if -use-spirv-backend is unset (always in the new driver).

These scenarios that we expect to support, starting with clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv -use-spirv-backend:

  • No extra options
  • --offload-device-only -S
  • --offload-device-only
  • --offload-device-only -S -fgpu-rdc
  • --offload-device-only -fgpu-rdc
  • -S -fgpu-rdc
  • -S
  • -fgpu-rdc

EDIT: These changes also introduce support for -use-spirv-backend in the old driver

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' labels Nov 11, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 11, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-driver

Author: Manuel Carrasco (mgcarrasco)

Changes

This PR enables the SPIR-V backend in the new driver via -use-spirv-backend.

When the SPIR-V backend must be called in --offload-device-only mode, we rely on the default compiler path. The changes for this end are only to avoid external dependencies such as spirv-as and spirv-link.

When --offload-device-only is unset, clang-linker-wrapper calls clang for linking the bitcode(s). In this case, we link the LLVM bitcode and call the backend to avoid changing clang-linker-wrapper and relying on spirv-link.


Patch is 26.45 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/167543.diff

7 Files Affected:

  • (modified) clang/include/clang/Options/Options.td (+10)
  • (modified) clang/lib/Driver/Driver.cpp (+65-17)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+9-1)
  • (modified) clang/lib/Driver/ToolChains/HIPAMD.cpp (+35-13)
  • (added) clang/test/Driver/hip-spirv-backend-bindings.c (+54)
  • (added) clang/test/Driver/hip-spirv-backend-opt.c (+55)
  • (added) clang/test/Driver/hip-spirv-backend-phases.c (+82)
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 0a414c685eae6..c129ac3f6db84 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1423,6 +1423,16 @@ def fhip_emit_relocatable : Flag<["-"], "fhip-emit-relocatable">,
   HelpText<"Compile HIP source to relocatable">;
 def fno_hip_emit_relocatable : Flag<["-"], "fno-hip-emit-relocatable">,
   HelpText<"Do not override toolchain to compile HIP source to relocatable">;
+def use_spirv_backend
+    : Flag<["-"], "use-spirv-backend">,
+      Group<hip_Group>,
+      Flags<[HelpHidden]>,
+      HelpText<"Use the SPIRV backend for compilation ">;
+def no_use_spirv_backend
+    : Flag<["-"], "no-use-spirv-backend">,
+      Group<hip_Group>,
+      Flags<[HelpHidden]>,
+      HelpText<"Do not use the SPIRV backend for compilation ">;
 }
 
 // Clang specific/exclusive options for OpenACC.
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 9fd64d4aac514..729dc48d15a9f 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -4999,15 +4999,24 @@ Action *Driver::BuildOffloadingActions(Compilation &C,
     // Compiling HIP in device-only non-RDC mode requires linking each action
     // individually.
     for (Action *&A : DeviceActions) {
-      // Special handling for the HIP SPIR-V toolchain because it doesn't use
-      // the SPIR-V backend yet doesn't report the output as an object.
       bool IsAMDGCNSPIRV = A->getOffloadingToolChain() &&
                            A->getOffloadingToolChain()->getTriple().getOS() ==
                                llvm::Triple::OSType::AMDHSA &&
                            A->getOffloadingToolChain()->getTriple().isSPIRV();
+      bool UseSPIRVBackend = Args.hasFlag(options::OPT_use_spirv_backend,
+                                          options::OPT_no_use_spirv_backend,
+                                          /*Default=*/false);
+
+      // Special handling for the HIP SPIR-V toolchain in device-only.
+      // The translator path has a linking step, whereas the SPIR-V backend path
+      // does not to avoid any external dependency such as spirv-link. The
+      // linking step is skipped for the SPIR-V backend path.
+      bool IsAMDGCNSPIRVWithBackend = IsAMDGCNSPIRV && UseSPIRVBackend;
+
       if ((A->getType() != types::TY_Object && !IsAMDGCNSPIRV &&
            A->getType() != types::TY_LTO_BC) ||
-          HIPRelocatableObj || !HIPNoRDC || !offloadDeviceOnly())
+          HIPRelocatableObj || !HIPNoRDC || !offloadDeviceOnly() ||
+          (IsAMDGCNSPIRVWithBackend && offloadDeviceOnly()))
         continue;
       ActionList LinkerInput = {A};
       A = C.MakeAction<LinkJobAction>(LinkerInput, types::TY_Image);
@@ -5210,20 +5219,43 @@ Action *Driver::ConstructPhaseAction(
           Args.hasArg(options::OPT_S) ? types::TY_LTO_IR : types::TY_LTO_BC;
       return C.MakeAction<BackendJobAction>(Input, Output);
     }
-    if (Args.hasArg(options::OPT_emit_llvm) ||
-        TargetDeviceOffloadKind == Action::OFK_SYCL ||
-        (((Input->getOffloadingToolChain() &&
-           Input->getOffloadingToolChain()->getTriple().isAMDGPU()) ||
-          TargetDeviceOffloadKind == Action::OFK_HIP) &&
-         ((Args.hasFlag(options::OPT_fgpu_rdc, options::OPT_fno_gpu_rdc,
-                        false) ||
-           (Args.hasFlag(options::OPT_offload_new_driver,
-                         options::OPT_no_offload_new_driver, false) &&
-            (!offloadDeviceOnly() ||
-             (Input->getOffloadingToolChain() &&
-              TargetDeviceOffloadKind == Action::OFK_HIP &&
-              Input->getOffloadingToolChain()->getTriple().isSPIRV())))) ||
-          TargetDeviceOffloadKind == Action::OFK_OpenMP))) {
+
+    bool IsEmitLLVM = Args.hasArg(options::OPT_emit_llvm);
+    bool IsSYCL = TargetDeviceOffloadKind == Action::OFK_SYCL;
+
+    auto OffloadingToolChain = Input->getOffloadingToolChain();
+    bool IsAMDGPU =
+        OffloadingToolChain && OffloadingToolChain->getTriple().isAMDGPU();
+    bool IsRDC =
+        Args.hasFlag(options::OPT_fgpu_rdc, options::OPT_fno_gpu_rdc, false);
+    bool IsNewOffloadDriver =
+        Args.hasFlag(options::OPT_offload_new_driver,
+                     options::OPT_no_offload_new_driver, false);
+    bool IsHIP = TargetDeviceOffloadKind == Action::OFK_HIP;
+    bool IsSPIRV =
+        OffloadingToolChain && OffloadingToolChain->getTriple().isSPIRV();
+    bool IsAMDGPUOrHIP = IsAMDGPU || IsHIP;
+    bool IsHIPToSPIRV = IsHIP && IsSPIRV;
+    bool IsOpenMP = TargetDeviceOffloadKind == Action::OFK_OpenMP;
+
+    bool UseSPIRVBackend = Args.hasFlag(options::OPT_use_spirv_backend,
+                                        options::OPT_no_use_spirv_backend,
+                                        /*Default=*/false);
+
+    // If offloadDeviceOnly(), we call the SPIRV backend unless LLVM bitcode was
+    // requested explicitly or RDC is set. If !offloadDeviceOnly, we emit LLVM
+    // bitcode, and clang-linker-wrapper will compile it to SPIRV.
+    bool UseSPIRVBackendForHipDeviceOnlyNoRDC =
+        IsHIPToSPIRV && UseSPIRVBackend && offloadDeviceOnly() && !IsRDC;
+
+    bool IsLLVMBitcodeOutput =
+        IsEmitLLVM || IsSYCL ||
+        (IsAMDGPUOrHIP && !UseSPIRVBackendForHipDeviceOnlyNoRDC &&
+         ((IsRDC ||
+           (IsNewOffloadDriver && (!offloadDeviceOnly() || IsHIPToSPIRV))) ||
+          IsOpenMP));
+
+    if (IsLLVMBitcodeOutput) {
       types::ID Output =
           Args.hasArg(options::OPT_S) &&
                   (TargetDeviceOffloadKind == Action::OFK_None ||
@@ -5236,6 +5268,22 @@ Action *Driver::ConstructPhaseAction(
               : types::TY_LLVM_BC;
       return C.MakeAction<BackendJobAction>(Input, Output);
     }
+
+    // The SPIRV backend compilation path for HIP must avoid external
+    // dependencies. The default compilation path assembles and links its
+    // output, but the SPIRV assembler and linker are external tools. This code
+    // ensures the backend emits binary SPIRV directly to bypass those steps and
+    // avoid failures. Without -save-temps, the compiler may already skip
+    // assembling and linking. With -save-temps, these steps must be explicitly
+    // disabled, as done here. We also force skipping these steps regardless of
+    // -save-temps to avoid relying on optimizations (unless -S is set).
+    bool IsBinarySPIRVOutput =
+        UseSPIRVBackendForHipDeviceOnlyNoRDC && !Args.hasArg(options::OPT_S);
+    if (IsBinarySPIRVOutput) {
+      // The current HIP bundling expects the type to be types::TY_Image
+      return C.MakeAction<BackendJobAction>(Input, types::TY_Image);
+    }
+
     return C.MakeAction<BackendJobAction>(Input, types::TY_PP_Asm);
   }
   case phases::Assemble:
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 2791b1e57877e..639b70e0902fa 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -5057,6 +5057,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
     Args.ClaimAllArgs(options::OPT_femit_dwarf_unwind_EQ);
   }
 
+  bool IsAMDSPIRVForHIPDevice =
+      IsHIPDevice && getToolChain().getTriple().isSPIRV() &&
+      getToolChain().getTriple().getVendor() == llvm::Triple::AMD;
+
   if (isa<AnalyzeJobAction>(JA)) {
     assert(JA.getType() == types::TY_Plist && "Invalid output type.");
     CmdArgs.push_back("-analyze");
@@ -5154,6 +5158,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
       rewriteKind = RK_Fragile;
     } else if (JA.getType() == types::TY_CIR) {
       CmdArgs.push_back("-emit-cir");
+    } else if (JA.getType() == types::TY_Image && IsAMDSPIRVForHIPDevice) {
+      CmdArgs.push_back("-emit-obj");
     } else {
       assert(JA.getType() == types::TY_PP_Asm && "Unexpected output type!");
     }
@@ -9074,7 +9080,9 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA,
       OPT_fno_lto,
       OPT_flto,
       OPT_flto_partitions_EQ,
-      OPT_flto_EQ};
+      OPT_flto_EQ,
+      OPT_use_spirv_backend};
+
   const llvm::DenseSet<unsigned> LinkerOptions{OPT_mllvm, OPT_Zlinker_input};
   auto ShouldForwardForToolChain = [&](Arg *A, const ToolChain &TC) {
     // Don't forward -mllvm to toolchains that don't support LLVM.
diff --git a/clang/lib/Driver/ToolChains/HIPAMD.cpp b/clang/lib/Driver/ToolChains/HIPAMD.cpp
index 0fbfa090ed9d3..492cfe7fe8063 100644
--- a/clang/lib/Driver/ToolChains/HIPAMD.cpp
+++ b/clang/lib/Driver/ToolChains/HIPAMD.cpp
@@ -159,10 +159,9 @@ void AMDGCN::Linker::constructLldCommand(Compilation &C, const JobAction &JA,
 
 // For SPIR-V the inputs for the job are device AMDGCN SPIR-V flavoured bitcode
 // and the output is either a compiled SPIR-V binary or bitcode (-emit-llvm). It
-// calls llvm-link and then the llvm-spirv translator. Once the SPIR-V BE will
-// be promoted from experimental, we will switch to using that. TODO: consider
-// if we want to run any targeted optimisations over IR here, over generic
-// SPIR-V.
+// calls llvm-link and then the llvm-spirv translator or the SPIR-V BE.
+// TODO: consider if we want to run any targeted optimisations over IR here,
+// over generic SPIR-V.
 void AMDGCN::Linker::constructLinkAndEmitSpirvCommand(
     Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
     const InputInfo &Output, const llvm::opt::ArgList &Args) const {
@@ -173,17 +172,40 @@ void AMDGCN::Linker::constructLinkAndEmitSpirvCommand(
   const char *LinkedBCFilePath = HIP::getTempFile(C, LinkedBCFilePrefix, "bc");
   InputInfo LinkedBCFile(&JA, LinkedBCFilePath, Output.getBaseInput());
 
+  bool UseSPIRVBackend =
+      Args.hasFlag(options::OPT_use_spirv_backend,
+                   options::OPT_no_use_spirv_backend, /*Default=*/false);
+
   constructLlvmLinkCommand(C, JA, Inputs, LinkedBCFile, Args);
 
-  // Emit SPIR-V binary.
-  llvm::opt::ArgStringList TrArgs{
-      "--spirv-max-version=1.6",
-      "--spirv-ext=+all",
-      "--spirv-allow-unknown-intrinsics",
-      "--spirv-lower-const-expr",
-      "--spirv-preserve-auxdata",
-      "--spirv-debug-info-version=nonsemantic-shader-200"};
-  SPIRV::constructTranslateCommand(C, *this, JA, Output, LinkedBCFile, TrArgs);
+  if (UseSPIRVBackend) {
+    // This code handles the case in the new driver when --offload-device-only
+    // is unset and clang-linker-wrapper forwards the bitcode that must be
+    // compiled to SPIR-V.
+
+    llvm::opt::ArgStringList CmdArgs;
+    const char *Triple =
+        C.getArgs().MakeArgString("-triple=spirv64-amd-amdhsa");
+
+    CmdArgs.append({"-cc1", Triple, "-emit-obj", "-disable-llvm-optzns",
+                    LinkedBCFile.getFilename(), "-o", Output.getFilename()});
+
+    const char *Exec = getToolChain().getDriver().getClangProgramPath();
+    C.addCommand(std::make_unique<Command>(JA, *this,
+                                           ResponseFileSupport::None(), Exec,
+                                           CmdArgs, LinkedBCFile, Output));
+  } else {
+    // Emit SPIR-V binary using the translator
+    llvm::opt::ArgStringList TrArgs{
+        "--spirv-max-version=1.6",
+        "--spirv-ext=+all",
+        "--spirv-allow-unknown-intrinsics",
+        "--spirv-lower-const-expr",
+        "--spirv-preserve-auxdata",
+        "--spirv-debug-info-version=nonsemantic-shader-200"};
+    SPIRV::constructTranslateCommand(C, *this, JA, Output, LinkedBCFile,
+                                     TrArgs);
+  }
 }
 
 // For amdgcn the inputs of the linker job are device bitcode and output is
diff --git a/clang/test/Driver/hip-spirv-backend-bindings.c b/clang/test/Driver/hip-spirv-backend-bindings.c
new file mode 100644
index 0000000000000..e1adc4ec07465
--- /dev/null
+++ b/clang/test/Driver/hip-spirv-backend-bindings.c
@@ -0,0 +1,54 @@
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -x hip %s -save-temps \
+// RUN:         -use-spirv-backend -ccc-print-bindings \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-BASE,CHECK-SPIRV
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -x hip %s -save-temps \
+// RUN:         -use-spirv-backend -fgpu-rdc -ccc-print-bindings \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-BASE,CHECK-SPIRV-RDC
+
+// CHECK-SPIRV-BASE: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[INPUT:.+]]"], output: "[[HIPI:.+\.hipi]]"
+// CHECK-SPIRV-BASE: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[HIPI]]"], output: "[[SPV_BC:.+\.bc]]"
+// CHECK-SPIRV-BASE: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[SPV_BC]]"], output: "[[SPV_BC:.+\.bc]]"
+// CHECK-SPIRV-BASE: # "x86_64-unknown-linux-gnu" - "Offload::Packager", inputs: ["[[SPV_BC]]"], output: "[[HIP_OUT:.+\.out]]"
+// CHECK-SPIRV-BASE: # "x86_64-unknown-linux-gnu" - "clang", inputs: ["[[INPUT]]"], output: "[[HIPI:.+\.hipi]]"
+// CHECK-SPIRV-BASE: # "x86_64-unknown-linux-gnu" - "clang", inputs: ["[[HIPI]]", "[[HIP_OUT]]"], output: "[[x86_BC:.+\.bc]]"
+// CHECK-SPIRV-BASE: # "x86_64-unknown-linux-gnu" - "clang", inputs: ["[[x86_BC]]"], output: "[[x86_S:.+\.s]]"
+// CHECK-SPIRV-BASE: # "x86_64-unknown-linux-gnu" - "clang::as", inputs: ["[[x86_S]]"], output: "[[x86_O:.+\.o]]"
+// CHECK-SPIRV-RDC: # "x86_64-unknown-linux-gnu" - "Offload::Linker", inputs: ["[[x86_O]]"], output: "{{.+\.out}}"
+// CHECK-SPIRV: # "x86_64-unknown-linux-gnu" - "Offload::Linker", inputs: ["[[x86_O]]"], output: "[[x86_O:.+\.o]]"
+// CHECK-SPIRV: # "x86_64-unknown-linux-gnu" - "GNU::Linker", inputs: ["[[x86_O]]"], output: "{{.+\.out}}"
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -x hip %s -save-temps \
+// RUN:         -use-spirv-backend --offload-device-only -ccc-print-bindings \
+// RUN: 2>&1 | FileCheck %s --check-prefix=CHECK-SPIRV-OFFLOAD-DEVICE-ONLY
+
+// CHECK-SPIRV-OFFLOAD-DEVICE-ONLY: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[INPUT:.+]]"], output: "[[HIPI:.+\.hipi]]"
+// CHECK-SPIRV-OFFLOAD-DEVICE-ONLY: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[HIPI]]"], output: "[[SPV_BC:.+\.bc]]"
+// CHECK-SPIRV-OFFLOAD-DEVICE-ONLY: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[SPV_BC]]"], output: "[[SPV_OUT:.+\.out]]"
+// CHECK-SPIRV-OFFLOAD-DEVICE-ONLY: # "spirv64-amd-amdhsa" - "AMDGCN::Linker", inputs: ["[[SPV_OUT]]"], output: "{{.+\.hipfb}}"
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -x hip %s -save-temps \
+// RUN:         -use-spirv-backend --offload-device-only -fgpu-rdc -ccc-print-bindings \
+// RUN: 2>&1 | FileCheck %s --check-prefix=CHECK-SPIRV-OFFLOAD-DEVICE-ONLY-RDC
+
+// CHECK-SPIRV-OFFLOAD-DEVICE-ONLY-RDC: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[INPUT:.+]]"], output: "[[HIPI:.+\.hipi]]"
+// CHECK-SPIRV-OFFLOAD-DEVICE-ONLY-RDC: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[HIPI]]"], output: "[[SPV_BC:.+\.bc]]"
+// CHECK-SPIRV-OFFLOAD-DEVICE-ONLY-RDC: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[SPV_BC]]"], output: "{{.+}}"
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -x hip %s -save-temps \
+// RUN:         -use-spirv-backend --offload-device-only -S -fgpu-rdc -ccc-print-bindings \
+// RUN: 2>&1 | FileCheck %s --check-prefix=CHECK-SPIRV-OFFLOAD-DEVICE-ONLY-RDC
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -x hip %s -save-temps \
+// RUN:         -use-spirv-backend --offload-device-only -S -ccc-print-bindings \
+// RUN: 2>&1 | FileCheck %s --check-prefix=CHECK-SPIRV-TEXTUAL-OFFLOAD-DEVICE-ONLY
+
+// CHECK-SPIRV-TEXTUAL-OFFLOAD-DEVICE-ONLY: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[INPUT:.+]]"], output: "[[HIPI:.+\.hipi]]"
+// CHECK-SPIRV-TEXTUAL-OFFLOAD-DEVICE-ONLY: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[HIPI]]"], output: "[[SPV_BC:.+\.bc]]"
+// CHECK-SPIRV-TEXTUAL-OFFLOAD-DEVICE-ONLY: # "spirv64-amd-amdhsa" - "clang", inputs: ["[[SPV_BC]]"], output: "{{.+\.s}}"
diff --git a/clang/test/Driver/hip-spirv-backend-opt.c b/clang/test/Driver/hip-spirv-backend-opt.c
new file mode 100644
index 0000000000000..d5f55f766d787
--- /dev/null
+++ b/clang/test/Driver/hip-spirv-backend-opt.c
@@ -0,0 +1,55 @@
+// This test case validates the behavior of -use-spirv-backend
+
+// --offload-device-only is always set --- testing interactions with -S and -fgpu-rdc
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -### -x hip %s -save-temps  \
+// RUN:         -use-spirv-backend --offload-device-only -S \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-TRANSLATOR,CHECK-SPIRV-BACKEND-TEXTUAL
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -### -x hip %s -save-temps  \
+// RUN:         -use-spirv-backend --offload-device-only \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-TRANSLATOR,CHECK-SPIRV-BACKEND-BINARY
+
+// The new driver's behavior is to emit LLVM IR for --offload-device-only and -fgpu-rdc (independently of SPIR-V).
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -### -nogpuinc -nogpulib -x hip %s -save-temps \
+// RUN:         -use-spirv-backend --offload-device-only -S -fgpu-rdc \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-TRANSLATOR,CHECK-SPIRV-BACKEND-LL,CHECK-FGPU-RDC
+
+// The new driver's behavior is to emit LLVM IR for --offload-device-only and -fgpu-rdc (independently of SPIR-V).
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -### -x hip %s -save-temps  \
+// RUN:         -use-spirv-backend --offload-device-only -fgpu-rdc \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-TRANSLATOR,CHECK-SPIRV-BACKEND-BC,CHECK-FGPU-RDC
+
+// --offload-device-only is always unset --- testing interactions with -S and -fgpu-rdc
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -### -x hip %s -save-temps  \
+// RUN:         -use-spirv-backend -S -fgpu-rdc \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-TRANSLATOR,CHECK-SPIRV-BACKEND-BC,CHECK-FGPU-RDC
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -### -x hip %s -save-temps  \
+// RUN:         -use-spirv-backend -S \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-TRANSLATOR,CHECK-SPIRV-BACKEND-BC
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -### -x hip %s -save-temps  \
+// RUN:         -use-spirv-backend -fgpu-rdc \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-TRANSLATOR,CHECK-SPIRV-BACKEND-BC,CHECK-CLANG-LINKER-WRAPPER
+
+// RUN: %clang --offload-new-driver --target=x86_64-unknown-linux-gnu --offload-arch=amdgcnspirv \
+// RUN:         -nogpuinc -nogpulib -### -x hip %s -save-temps  \
+// RUN:         -use-spirv-backend \
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHECK-SPIRV-TRANSLATOR,CHECK-SPIRV-BACKEND-BC,CHECK-CLANG-LINKER-WRAPPER
+
+// CHECK-SPIRV-TRANSLATOR-NOT: "{{.*llvm-spirv.*}}"
+// CHECK-SPIRV-BACKEND-TEXTUAL: "{{.*}}clang{{.*}}" "-cc1" "-triple" "spirv64-amd-amdhsa" {{.*}} "-S"
+// CHECK-SPIRV-BACKEND-BINARY: "{{.*}}clang{{.*}}" "-cc1" "-triple" "spirv64-amd-amdhsa" {{.*}} "-emit-obj"
+// CHECK-SPIRV-BACKEND-BC: "{{.*}}clang{{.*}}" "-cc1" "-triple" "spirv64-amd-amdhsa" {{.*}} "-emit-llvm-bc"
+// CHECK-SPIRV-BACKEND-LL: "{{.*}}clang{{.*}}" "-cc1" "-triple" "spirv64-amd-amdhsa" {{.*}} "-emit-llvm"
+// CHECK-FGPU-RDC-SAME: {{.*}} "-fgpu-rdc"
+// CHECK-CLANG-LINKER-WRAPPER: "{{.*}}clang-linker-wrapper" "--should-extract=amdgcnspirv" {{.*}} "--device-compiler=spirv64-amd-amdhsa=-use-spirv-backend"
diff --git a/clang/test/Driver/hip-spirv-backend-phases.c b/clang/test/Driv...
[truncated]

Copy link
Contributor

@jhuber6 jhuber6 left a comment

Choose a reason for hiding this comment

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

I don't fully understand what the SPIR-V plan is exactly. Maybe @AlexVlx or @yxsamliu can chime in. This seems to apply for --offload-device-only cases? What's the expected output there. For me I would expect it to be SPIR-V. I think the --offload-new-driver behavior in device only mode differs slightly so I'll need to confirm what we want to actually happen.

@mgcarrasco
Copy link
Contributor Author

mgcarrasco commented Nov 12, 2025

I don't fully understand what the SPIR-V plan is exactly. Maybe @AlexVlx or @yxsamliu can chime in. This seems to apply for --offload-device-only cases? What's the expected output there. For me I would expect it to be SPIR-V. I think the --offload-new-driver behavior in device only mode differs slightly so I'll need to confirm what we want to actually happen.

@jhuber6 Thanks for the feedback.

The PR enables the SPIR-V BE when -use-spirv-backend and --offload-arch=amdgcnspirv are set with and without --offload-device-only. When SPIR-V has to be generated, and those options are set, the compiler is expected to use the SPIR-V BE instead of the SPIRV translator. The plan is to provide support but without external dependencies. This PR is only for the new driver.

This seems to apply for --offload-device-only cases? What's the expected output there. For me I would expect it to be SPIR-V.

Yes, it handles those cases and also handles the cases when --offload-device-only is unset. Rather than calling the translator, the BE is now called if -use-spirv-backend is set. If the output is SPIR-V prior to these changes (using the new driver and for --offload-arch=amdgcnspirv), the output should remain as SPIR-V.

I think the --offload-new-driver behavior in device only mode differs slightly so I'll need to confirm what we want to actually happen.

Just to follow, do you mean that it differs slightly in regards to what precisely? I don't understand if you mean with respect to non device only in the new driver, or device only but in the old driver. What two things are you comparing?

@github-actions
Copy link

github-actions bot commented Nov 19, 2025

🐧 Linux x64 Test Results

  • 111356 tests passed
  • 4431 tests skipped

@mgcarrasco mgcarrasco force-pushed the macarras/spirv/backend-opt-new-driver branch 2 times, most recently from 31b08be to 46bd79c Compare November 20, 2025 11:31
@jmmartinez
Copy link
Contributor

Hello,

Just to add one data point: I've cherry-picked this PR over amd-staging and added 2 quick and dirty workarounds* for the SPIRV backend to work in https://github.com/ROCm/llvm-project/commits/users/jmmartinez/spirv/hacks_but_it_works .

I then tried the following combinations over the vectoradd_hip.cpp (clang -x hip -use-spirv-backend --offload-arch=amdgcnspirv vectoradd_hip.cpp $flags) with the following values for flags:

  • --offload-device-only -S -> prints textual spirv code (with and without --offload-new-driver)
  • --offload-device-only -S -fgpu-rdc -> prints textual llvm-ir (with and without --offload-new-driver)
  • -O3 -o a.out -lstdc++ && ./a.out -> compiles and the test passes (with and without --offload-new-driver; with and without -fgpu-rdc)

I haven't tested the SPIRV translator path.

* The workaround are related to visibility-hidden not being representable in SPIRV (the backend crashes when there are declarations with this visibility)
* The SPIRV backend tries to use the SPV_KHR_float_controls2 extension that is not implemented in the translator, so Comgr cannot translate SPIRV back to LLVM-IR.

@mgcarrasco
Copy link
Contributor Author

mgcarrasco commented Nov 20, 2025

Hello,

Just to add one data point: I've cherry-picked this PR over amd-staging and added 2 quick and dirty workarounds* for the SPIRV backend to work in https://github.com/ROCm/llvm-project/commits/users/jmmartinez/spirv/hacks_but_it_works .

I then tried the following combinations over the vectoradd_hip.cpp (clang -x hip -use-spirv-backend --offload-arch=amdgcnspirv vectoradd_hip.cpp $flags) with the following values for flags:

  • --offload-device-only -S -> prints textual spirv code (with and without --offload-new-driver)
  • --offload-device-only -S -fgpu-rdc -> prints textual llvm-ir (with and without --offload-new-driver)
  • -O3 -o a.out -lstdc++ && ./a.out -> compiles and the test passes (with and without --offload-new-driver; with and without -fgpu-rdc)

I haven't tested the SPIRV translator path.

  • The workaround are related to visibility-hidden not being representable in SPIRV (the backend crashes when there are declarations with this visibility) * The SPIRV backend tries to use the SPV_KHR_float_controls2 extension that is not implemented in the translator, so Comgr cannot translate SPIRV back to LLVM-IR.

Thanks @jmmartinez. I've tested the translator path too.

There is only one minor expected difference between the output types for -use-spriv-backend and -no-use-spirv-backend with --offload-device-only -S in the new driver. Prior to this PR, the new driver is not emitting textual SPIRV for --offload-device-only when -S is set. This is because the translator is invoked as the linker, so -S emits LLVM textual bitcode. Now, in this PR, this is inherited behavior and reflected when toggling -use-spriv-backend and -no-use-spirv-backend.

@mgcarrasco mgcarrasco requested a review from jhuber6 November 20, 2025 14:25
@mgcarrasco mgcarrasco requested a review from jhuber6 November 20, 2025 16:14
Copy link
Contributor

@jhuber6 jhuber6 left a comment

Choose a reason for hiding this comment

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

Seems fine, one nit. I'll wait for @AlexVlx to give the final go-ahead because he knows the goals for this better than I do

@mgcarrasco mgcarrasco force-pushed the macarras/spirv/backend-opt-new-driver branch from 5f452ef to 6a500dd Compare November 21, 2025 11:40
@mgcarrasco mgcarrasco changed the title [clang][Driver] Support for the SPIR-V backend in the new driver when compiling HIP [clang][Driver] Support for the SPIR-V backend when compiling HIP Nov 21, 2025
@mgcarrasco mgcarrasco merged commit 1a03673 into llvm:main Nov 25, 2025
10 checks passed
@mgcarrasco
Copy link
Contributor Author

Got green light from Alex.

mgcarrasco added a commit to mgcarrasco/llvm-project that referenced this pull request Nov 25, 2025
… HIP (llvm#167543)"

This reverts commit 1a03673.

Reverted due to a failure in hip-spirv-backend-opt.c for fuchsia-x86_64-linux.
mgcarrasco added a commit that referenced this pull request Nov 25, 2025
… HIP (#167543)" (#169528)

This reverts commit 1a03673.

Reverted due to a failure in hip-spirv-backend-opt.c for
fuchsia-x86_64-linux.
mgcarrasco added a commit that referenced this pull request Nov 26, 2025
…67543)

For HIP, the SPIR-V backend can be optionally activated with the -use-spirv-backend flag. This option uses the SPIR-V BE instead of the SPIR-V translator. These changes also ensure that -use-spirv-backend does not require external dependencies, such as spirv-as and spirv-link
mgcarrasco added a commit that referenced this pull request Nov 26, 2025
… HIP" (#169637)

This relands "[clang][Driver] Support for the SPIR-V backend when compiling HIP" #167543. The only new change is a small fix for the multicall driver.

For HIP, the SPIR-V backend can be optionally activated with the -use-spirv-backend flag. This option uses the SPIR-V BE instead of the SPIR-V translator. These changes also ensure that -use-spirv-backend does not require external dependencies, such as spirv-as and spirv-link
tanji-dg pushed a commit to tanji-dg/llvm-project that referenced this pull request Nov 27, 2025
… HIP" (llvm#169637)

This relands "[clang][Driver] Support for the SPIR-V backend when compiling HIP" llvm#167543. The only new change is a small fix for the multicall driver.

For HIP, the SPIR-V backend can be optionally activated with the -use-spirv-backend flag. This option uses the SPIR-V BE instead of the SPIR-V translator. These changes also ensure that -use-spirv-backend does not require external dependencies, such as spirv-as and spirv-link
GeneraluseAI pushed a commit to GeneraluseAI/llvm-project that referenced this pull request Nov 27, 2025
… HIP" (llvm#169637)

This relands "[clang][Driver] Support for the SPIR-V backend when compiling HIP" llvm#167543. The only new change is a small fix for the multicall driver.

For HIP, the SPIR-V backend can be optionally activated with the -use-spirv-backend flag. This option uses the SPIR-V BE instead of the SPIR-V translator. These changes also ensure that -use-spirv-backend does not require external dependencies, such as spirv-as and spirv-link
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants