https://github.com/pvelesko updated 
https://github.com/llvm/llvm-project/pull/186972

>From 588c758e2d9ea2837f35a3ae8a586b1f4ed2d388 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <[email protected]>
Date: Tue, 17 Mar 2026 09:55:28 +0200
Subject: [PATCH 1/5] [HIPSPV] Add in-tree SPIR-V backend support for chipStar

chipStar (https://github.com/CHIP-SPV/chipStar) enables HIP/CUDA
programs to run on OpenCL and Level Zero devices via SPIR-V. Until
now, the HIPSPV toolchain relied exclusively on the external
llvm-spirv translator for bitcode-to-SPIR-V conversion.

This patch adds native in-tree SPIR-V backend support for chipStar
targets (triple: spirv64*-unknown-chipstar), removing the hard
dependency on llvm-spirv.

Changes:

HIPSPV old driver (HIPSPV.cpp):
- chipStar targets now use opt (HipSpvPasses) + clang -c (SPIR-V
  backend) instead of opt + llvm-spirv translator
- Non-chipStar HIPSPV targets continue using llvm-spirv unchanged
- Remove HostTC->addClangTargetOptions() delegation to avoid macOS
  Darwin flags (-faligned-alloc-unavailable) breaking SPIR-V device
  compilation

New offload driver (ClangLinkerWrapper.cpp):
- Add chipStar SPIR-V pipeline: llvm-link -> opt (HipSpvPasses) ->
  clang -c --target=spirv64 with SPIR-V extensions
- Extract --hip-path from --device-compiler= args for locating the
  HipSpvPasses plugin and device libraries
- Fall back to llvm-spirv translator when available for non-chipStar
  SPIR-V targets

SPIR-V toolchain (SPIRV.cpp):
- Enable NativeLLVMSupport for chipStar triples so the toolchain
  does not require an external translator

SPIR-V backend (SPIRVSubtarget.cpp):
- Set Kernel environment for chipStar triples (needed for OpenCL
  kernel ABI)

AlignedAllocation.h:
- Add ChipStar case to avoid unhandled enum warning

Tests:
- Update hipspv-toolchain.hip driver test to verify the new in-tree
  backend pipeline for chipStar targets
---
 clang/include/clang/Basic/AlignedAllocation.h |   2 +
 clang/lib/Driver/ToolChains/HIPSPV.cpp        |  75 +++++--
 clang/lib/Driver/ToolChains/HIPSPV.h          |   3 +-
 clang/lib/Driver/ToolChains/SPIRV.cpp         |   3 +-
 .../Driver/hipspv-link-static-library.hip     |   9 +-
 clang/test/Driver/hipspv-pass-plugin.hip      |  12 +-
 clang/test/Driver/hipspv-toolchain.hip        |  56 +++--
 .../ClangLinkerWrapper.cpp                    | 199 +++++++++++++++++-
 llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp      |   3 +-
 9 files changed, 309 insertions(+), 53 deletions(-)

diff --git a/clang/include/clang/Basic/AlignedAllocation.h 
b/clang/include/clang/Basic/AlignedAllocation.h
index ac26eb4a276da..9b84d07286d52 100644
--- a/clang/include/clang/Basic/AlignedAllocation.h
+++ b/clang/include/clang/Basic/AlignedAllocation.h
@@ -35,6 +35,8 @@ inline llvm::VersionTuple 
alignedAllocMinVersion(llvm::Triple::OSType OS) {
     return llvm::VersionTuple(4U);
   case llvm::Triple::ZOS:
     return llvm::VersionTuple(); // All z/OS versions have no support.
+  case llvm::Triple::ChipStar:
+    return llvm::VersionTuple(); // No version constraint for device targets.
   }
 
   llvm_unreachable("Unexpected OS");
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp 
b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index edfb03bd03c84..0dc303e05890b 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -73,10 +73,55 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
   tools::constructLLVMLinkCommand(C, *this, JA, Inputs, LinkArgs, Output, Args,
                                   TempFile);
 
-  // Post-link HIP lowering.
+  auto T = getToolChain().getTriple();
+
+  if (T.getOS() == llvm::Triple::ChipStar) {
+    // chipStar: run HipSpvPasses via opt, then use the in-tree SPIR-V backend
+    // for codegen (replaces the external llvm-spirv translator).
+
+    // Run HipSpvPasses plugin via opt (must run on LLVM IR before
+    // the SPIR-V backend lowers to MIR).
+    auto PassPluginPath = findPassPlugin(C.getDriver(), Args);
+    if (!PassPluginPath.empty()) {
+      const char *PassPathCStr = C.getArgs().MakeArgString(PassPluginPath);
+      const char *OptOutput = HIP::getTempFile(C, Name + "-lower", "bc");
+      ArgStringList OptArgs{TempFile,     "-load-pass-plugin",
+                            PassPathCStr, "-passes=hip-post-link-passes",
+                            "-o",         OptOutput};
+      const char *Opt =
+          Args.MakeArgString(getToolChain().GetProgramPath("opt"));
+      C.addCommand(std::make_unique<Command>(JA, *this,
+                                             ResponseFileSupport::None(), Opt,
+                                             OptArgs, Inputs, Output));
+      TempFile = OptOutput;
+    }
 
-  // Run LLVM IR passes to lower/expand/emulate HIP code that does not 
translate
-  // to SPIR-V (E.g. dynamic shared memory).
+    // Compile processed bitcode to SPIR-V using the in-tree backend.
+    ArgStringList ClangArgs;
+    ClangArgs.push_back("--no-default-config");
+    ClangArgs.push_back("-c");
+    ClangArgs.push_back(C.getArgs().MakeArgString("--target=" + 
T.getTriple()));
+
+    ClangArgs.push_back("-mllvm");
+    ClangArgs.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
+                        ",+SPV_INTEL_subgroups"
+                        ",+SPV_EXT_relaxed_printf_string_address_space"
+                        ",+SPV_KHR_bit_instructions"
+                        ",+SPV_EXT_shader_atomic_float_add");
+
+    ClangArgs.push_back(TempFile);
+    ClangArgs.push_back("-o");
+    ClangArgs.push_back(Output.getFilename());
+
+    const char *Clang =
+        C.getArgs().MakeArgString(C.getDriver().getDriverProgramPath());
+    C.addCommand(std::make_unique<Command>(JA, *this,
+                                           ResponseFileSupport::None(), Clang,
+                                           ClangArgs, Inputs, Output));
+    return;
+  }
+
+  // Non-chipStar: run HIP passes via opt, then translate with llvm-spirv.
   auto PassPluginPath = findPassPlugin(C.getDriver(), Args);
   if (!PassPluginPath.empty()) {
     const char *PassPathCStr = C.getArgs().MakeArgString(PassPluginPath);
@@ -90,27 +135,11 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
     TempFile = OptOutput;
   }
 
-  // Emit SPIR-V binary.
+  // Emit SPIR-V binary via llvm-spirv translator (non-chipStar targets).
   llvm::opt::ArgStringList TrArgs;
-  auto T = getToolChain().getTriple();
-  bool HasNoSubArch = T.getSubArch() == llvm::Triple::NoSubArch;
-  if (T.getOS() == llvm::Triple::ChipStar) {
-    // chipStar needs 1.2 for supporting warp-level primitivies via sub-group
-    // extensions.  Strictly put we'd need 1.3 for the standard non-extension
-    // shuffle operations, but it's not supported by any backend driver of the
-    // chipStar.
-    if (HasNoSubArch)
-      TrArgs.push_back("--spirv-max-version=1.2");
-    TrArgs.push_back("--spirv-ext=-all"
-                     // Needed for experimental indirect call support.
-                     ",+SPV_INTEL_function_pointers"
-                     // Needed for shuffles below SPIR-V 1.3
-                     ",+SPV_INTEL_subgroups");
-  } else {
-    if (HasNoSubArch)
-      TrArgs.push_back("--spirv-max-version=1.1");
-    TrArgs.push_back("--spirv-ext=+all");
-  }
+  if (T.getSubArch() == llvm::Triple::NoSubArch)
+    TrArgs.push_back("--spirv-max-version=1.1");
+  TrArgs.push_back("--spirv-ext=+all");
 
   InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
   SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.h 
b/clang/lib/Driver/ToolChains/HIPSPV.h
index f9e11a7fb6977..811ce0c3a7bc4 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.h
+++ b/clang/lib/Driver/ToolChains/HIPSPV.h
@@ -51,8 +51,7 @@ class LLVM_LIBRARY_VISIBILITY HIPSPVToolChain final : public 
ToolChain {
                   const llvm::opt::ArgList &Args);
 
   const llvm::Triple *getAuxTriple() const override {
-    assert(HostTC);
-    return &HostTC->getTriple();
+    return HostTC ? &HostTC->getTriple() : nullptr;
   }
 
   void
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp 
b/clang/lib/Driver/ToolChains/SPIRV.cpp
index 5cc1eec74c1cc..c6a0b667971fb 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -201,7 +201,8 @@ SPIRVToolChain::SPIRVToolChain(const Driver &D, const 
llvm::Triple &Triple,
     : ToolChain(D, Triple, Args) {
   // TODO: Revisit need/use of --sycl-link option once SYCL toolchain is
   // available and SYCL linking support is moved there.
-  NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || isUsingLTO(Args);
+  NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || isUsingLTO(Args) 
||
+                      Triple.getOS() == llvm::Triple::ChipStar;
 
   // Lookup binaries into the driver directory.
   getProgramPaths().push_back(getDriver().Dir);
diff --git a/clang/test/Driver/hipspv-link-static-library.hip 
b/clang/test/Driver/hipspv-link-static-library.hip
index eb114ada49020..dbcb5bd4e5cbb 100644
--- a/clang/test/Driver/hipspv-link-static-library.hip
+++ b/clang/test/Driver/hipspv-link-static-library.hip
@@ -48,9 +48,12 @@
 // SDL-NEW-SAME: "{{.*}}/tu0.o" "{{.*}}/libSDL2.a"
 // DELETE-SDL-NEW: "{{.*}}llvm-link" "-o" "{{.*}}.bc" "{{.*}}.o" "{{.*}}.o"
 
-// SDL-NEW-WRAPPER: clang{{.*}}" --no-default-config -o {{[^ ]*.img}}
-// SDL-NEW-WRAPPER-SAME: {{[^ ]*.o}} {{[^ ]*.o}}
-// SDL-NEW-WRAPPER-SAME: --hip-path=[[HIP_PATH]]
+// SDL-NEW-WRAPPER: "{{.*}}opt"
+// SDL-NEW-WRAPPER-SAME: -load-pass-plugin
+// SDL-NEW-WRAPPER-SAME: [[HIP_PATH]]/lib/libLLVMHipSpvPasses.so
+// SDL-NEW-WRAPPER: clang{{.*}}" --no-default-config
+// SDL-NEW-WRAPPER-SAME: --target=spirv64-unknown-chipstar
+// SDL-NEW-WRAPPER-SAME: -c -x ir
 
 // SDL: "{{.*}}opt"
 // SDL-SAME: "-load-pass-plugin" {{".*/hipspv/lib/libLLVMHipSpvPasses.so"}}
diff --git a/clang/test/Driver/hipspv-pass-plugin.hip 
b/clang/test/Driver/hipspv-pass-plugin.hip
index 3a0979ad6df01..5cf891e4c6114 100644
--- a/clang/test/Driver/hipspv-pass-plugin.hip
+++ b/clang/test/Driver/hipspv-pass-plugin.hip
@@ -16,23 +16,24 @@
 // RUN: --no-offload-new-driver -nogpuinc -nogpulib %s \
 // RUN: 2>&1 | FileCheck --check-prefixes=ALL,NO-PLUGIN %s
 
-// Run commands for the new offload driver:
+// Run commands for the new offload driver (chipStar uses in-tree SPIR-V
+// backend instead of llvm-spirv):
 
 // RUN: touch %t.dummy.o
 // RUN: %clang -### --no-default-config -o /dev/null 
--target=spirv64-unknown-chipstar \
 // RUN:   %t.dummy.o --hip-path=%S/Inputs/hipspv \
-// RUN: 2>&1 | FileCheck %s --check-prefixes=ALL,FROM-HIP-PATH
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHIPSTAR,FROM-HIP-PATH
 
 // RUN: %clang -### --no-default-config -o /dev/null 
--target=spirv64-unknown-chipstar \
 // RUN:   %t.dummy.o --hipspv-pass-plugin=%S/Inputs/pass-plugin.so \
-// RUN: 2>&1 | FileCheck %s --check-prefixes=ALL,FROM-OPTION
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHIPSTAR,FROM-OPTION
 
 // RUN: not %clang -### --no-default-config -o /dev/null 
--target=spirv64-unknown-chipstar \
 // RUN:   %t.dummy.o --hipspv-pass-plugin=foo.so \
-// RUN: 2>&1 | FileCheck %s --check-prefixes=ALL,FROM-OPTION-INVALID
+// RUN: 2>&1 | FileCheck %s --check-prefixes=CHIPSTAR,FROM-OPTION-INVALID
 
 // RUN: %clang -### --no-default-config -o /dev/null 
--target=spirv64-unknown-chipstar \
-// RUN:   %t.dummy.o 2>&1 | FileCheck %s --check-prefixes=ALL,NO-PLUGIN
+// RUN:   %t.dummy.o 2>&1 | FileCheck %s --check-prefixes=CHIPSTAR,NO-PLUGIN
 
 // FROM-HIP-PATH: {{".*opt"}} {{".*.bc"}} "-load-pass-plugin"
 // FROM-HIP-PATH-SAME: {{".*/Inputs/hipspv/lib/libLLVMHipSpvPasses.so"}}
@@ -42,3 +43,4 @@
 // NO-PLUGIN-NOT: {{".*opt"}} {{".*.bc"}} "-load-pass-plugin"
 // NO-PLUGIN-NOT: {{".*/Inputs/hipspv/lib/libLLVMHipSpvPasses.so"}}
 // ALL: {{".*llvm-spirv[^ ]*"}}
+// CHIPSTAR: {{".*clang.*"}} "--no-default-config" "-c"
diff --git a/clang/test/Driver/hipspv-toolchain.hip 
b/clang/test/Driver/hipspv-toolchain.hip
index 7a9d58f546c98..2f96b132a3e0c 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -60,17 +60,50 @@
 // RUN: llvm-offload-binary -o %t.dev.out \
 // RUN:   
--image=file=%t.dev.bc,kind=hip,triple=spirv64-unknown-chipstar,arch=generic
 
-// RUN: clang-linker-wrapper --dry-run \
+// Test the in-tree SPIR-V backend path (no llvm-spirv available).
+// Run from a directory that doesn't contain llvm-spirv and use
+// --no-canonical-prefixes so getExecutableDir() looks there instead of
+// the build bin dir. Empty PATH ensures PATH lookup also fails.
+// RUN: mkdir -p %t/no-spirv %t/empty
+// RUN: ln -sf clang-linker-wrapper %t/no-spirv/clang-linker-wrapper
+// RUN: env "PATH=%t/empty" %t/no-spirv/clang-linker-wrapper \
+// RUN:   --no-canonical-prefixes --dry-run \
 // RUN:   
--device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
 // RUN:   --host-triple=spirv64-unknown-chipstar \
 // RUN:   --linker-path=clang-offload-bundler \
 // RUN:   --emit-fatbin-only -o /dev/null %t.dev.out \
 // RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER -DHIP_PATH=%S/Inputs/hipspv
 
-// WRAPPER: clang{{.*}}" --no-default-config -o {{[^ ]*.img}}
+// The linker wrapper runs opt (HipSpvPasses) then uses the in-tree SPIR-V
+// backend when llvm-spirv is not available.
+// WRAPPER: "{{.*}}opt" {{.*}}-load-pass-plugin
+// WRAPPER-SAME: {{.*}}libLLVMHipSpvPasses.so
+// WRAPPER-SAME: -passes=hip-post-link-passes
+
+// WRAPPER: "{{.*}}clang{{.*}}" --no-default-config -o
 // WRAPPER-SAME: --target=spirv64-unknown-chipstar
-// WRAPPER-SAME: {{[^ ]*.o}}
-// WRAPPER-SAME: --hip-path=[[HIP_PATH]]
+// WRAPPER-SAME: -mllvm 
-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add
+// WRAPPER-SAME: -c -x ir
+
+// Test the llvm-spirv translator path (llvm-spirv available in executable 
dir).
+// Place a fake llvm-spirv next to the clang-linker-wrapper symlink.
+// RUN: mkdir -p %t/with-spirv
+// RUN: ln -sf clang-linker-wrapper %t/with-spirv/clang-linker-wrapper
+// RUN: touch %t/with-spirv/llvm-spirv && chmod +x %t/with-spirv/llvm-spirv
+// RUN: env "PATH=%t/empty" %t/with-spirv/clang-linker-wrapper \
+// RUN:   --no-canonical-prefixes --dry-run \
+// RUN:   
--device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
+// RUN:   --host-triple=spirv64-unknown-chipstar \
+// RUN:   --linker-path=clang-offload-bundler \
+// RUN:   --emit-fatbin-only -o /dev/null %t.dev.out \
+// RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER-TR
+
+// WRAPPER-TR: "{{.*}}opt" {{.*}}-load-pass-plugin
+// WRAPPER-TR-SAME: {{.*}}libLLVMHipSpvPasses.so
+// WRAPPER-TR-SAME: -passes=hip-post-link-passes
+
+// WRAPPER-TR: "{{.*}}llvm-spirv" {{.*}}--spirv-max-version=1.2
+// WRAPPER-TR-SAME: 
--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups
 
 // RUN: touch %t.dummy.o
 // RUN: %clang -### --no-default-config -o %t.dummy.img \
@@ -85,8 +118,9 @@
 // CHIPSTAR-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
 // CHIPSTAR-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
 
-//      CHIPSTAR: {{".*llvm-spirv"}} "--spirv-max-version=1.2"
-// CHIPSTAR-SAME: 
"--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+//      CHIPSTAR: {{".*clang.*"}} "--no-default-config" "-c"
+// CHIPSTAR-SAME: "--target=spirv64-unknown-chipstar"
+// CHIPSTAR-SAME: "-mllvm" 
"-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
 // CHIPSTAR-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
 
 // RUN: %clang -### --no-default-config -o %t.dummy.img \
@@ -101,8 +135,9 @@
 // CHIPSTAR-SUBARCH-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
 // CHIPSTAR-SUBARCH-SAME: "-passes=hip-post-link-passes" "-o" 
[[LOWER_BC:".*bc"]]
 
-//      CHIPSTAR-SUBARCH: {{".*llvm-spirv"}}
-// CHIPSTAR-SUBARCH-SAME: 
"--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+//      CHIPSTAR-SUBARCH: {{".*clang.*"}} "--no-default-config" "-c"
+// CHIPSTAR-SUBARCH-SAME: "--target=spirv64v1.3-unknown-chipstar"
+// CHIPSTAR-SUBARCH-SAME: "-mllvm" 
"-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
 // CHIPSTAR-SUBARCH-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
 
 // Check unknown linker options are ignored - such as ones that are targeted at
@@ -123,9 +158,4 @@
 // RUN:   | FileCheck -DVERSION=%llvm-version-major \
 // RUN:   --check-prefix=VERSIONED %s
 
-// RUN: env "PATH=%t/versioned" %clang -### --no-default-config \
-// RUN:  -o %t.dummy.img --target=spirv64-unknown-chipstar %t.dummy.o \
-// RUN:  --hip-path="%S/Inputs/hipspv" -o /dev/null 2>&1 \
-// RUN: | FileCheck -DVERSION=%llvm-version-major --check-prefix=VERSIONED %s
-
 // VERSIONED: {{.*}}llvm-spirv-[[VERSION]]
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp 
b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index 6e4fc7060389c..8fe1658407098 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -125,6 +125,9 @@ static StringRef ExecutableName;
 /// Binary path for the CUDA installation.
 static std::string CudaBinaryPath;
 
+/// HIP installation path.
+static std::string HipPath;
+
 /// Mutex lock to protect writes to shared TempFiles in parallel.
 static std::mutex TempFilesMutex;
 
@@ -473,9 +476,25 @@ fatbinary(ArrayRef<std::tuple<StringRef, StringRef, 
StringRef>> InputFiles,
   SmallVector<StringRef> Targets = {
       Saver.save("-targets=host-" + HostTriple.normalize())};
   for (const auto &[File, TripleRef, Arch] : InputFiles) {
-    std::string NormalizedTriple =
-        normalizeForBundler(Triple(TripleRef), !Arch.empty());
-    Targets.push_back(Saver.save("hip-" + NormalizedTriple + "-" + Arch));
+    llvm::Triple T(TripleRef);
+    // For SPIR-V targets, derive arch from triple if not provided
+    StringRef EffectiveArch = Arch;
+    if (EffectiveArch.empty() && T.isSPIRV()) {
+      EffectiveArch = T.getArchName();
+    }
+    StringRef BundleID;
+    if (EffectiveArch == "amdgcnspirv") {
+      BundleID = Saver.save("hip-spirv64-amd-amdhsa--" + EffectiveArch);
+    } else if (T.isSPIRV()) {
+      // ChipStar and other SPIR-V HIP targets: use
+      // hip-spirv64-<vendor>-<os>--<arch>
+      BundleID = Saver.save("hip-spirv64-" + T.getVendorName() + "-" +
+                            T.getOSName() + "--" + EffectiveArch);
+    } else {
+      std::string NormalizedTriple = normalizeForBundler(T, !Arch.empty());
+      BundleID = Saver.save("hip-" + NormalizedTriple + "-" + Arch);
+    }
+    Targets.push_back(BundleID);
   }
   CmdArgs.push_back(Saver.save(llvm::join(Targets, ",")));
 
@@ -559,7 +578,161 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, 
const ArgList &Args,
   // the LTO link and defeat the non-LTO pipeline.
   if (NonLTOAMDGPU)
     CmdArgs.append({"-x", "ir"});
-  for (StringRef InputFile : InputFiles)
+
+  // For non-chipStar SPIR-V targets, pass the HIP path to clang so it can
+  // find resources. For chipStar, passes are run via opt separately, so the
+  // inner clang doesn't need --hip-path (it just compiles IR to SPIR-V).
+  if (Triple.isSPIRV() && !HipPath.empty() &&
+      Triple.getOS() != llvm::Triple::ChipStar)
+    CmdArgs.push_back(Args.MakeArgString("--hip-path=" + HipPath));
+
+  // For chipStar targets: llvm-link (merge) -> opt (HipSpvPasses) -> clang
+  // (SPIR-V backend). The passes must operate on LLVM IR before the backend
+  // lowers to MIR, and all TU bitcode must be merged first for RDC support.
+  SmallVector<StringRef, 16> ProcessedInputFiles;
+  if (Triple.isSPIRV() && Triple.getOS() == llvm::Triple::ChipStar) {
+    // Step 1: Merge all input bitcode files with llvm-link (needed for RDC
+    // where functions can be defined across translation units).
+    StringRef MergedFile;
+    if (InputFiles.size() > 1) {
+      Expected<std::string> LinkPath =
+          findProgram("llvm-link", {getExecutableDir("llvm-link")});
+      if (!LinkPath)
+        return LinkPath.takeError();
+
+      auto LinkOutOrErr = createOutputFile(
+          sys::path::filename(ExecutableName) + ".merged", "bc");
+      if (!LinkOutOrErr)
+        return LinkOutOrErr.takeError();
+
+      SmallVector<StringRef, 16> LinkArgs{*LinkPath};
+      for (StringRef F : InputFiles)
+        LinkArgs.push_back(F);
+      LinkArgs.push_back("-o");
+      LinkArgs.push_back(*LinkOutOrErr);
+
+      if (Error Err = executeCommands(*LinkPath, LinkArgs))
+        return std::move(Err);
+
+      MergedFile = *LinkOutOrErr;
+    } else {
+      MergedFile = InputFiles[0];
+    }
+
+    // Step 2: Run HipSpvPasses via opt on the merged bitcode.
+    SmallString<128> PluginPath;
+    if (!HipPath.empty()) {
+      PluginPath.assign(HipPath);
+      sys::path::append(PluginPath, "lib", "libLLVMHipSpvPasses.so");
+      if (!sys::fs::exists(PluginPath)) {
+        PluginPath.assign(HipPath);
+        sys::path::append(PluginPath, "lib", "llvm", "libLLVMHipSpvPasses.so");
+      }
+      if (!sys::fs::exists(PluginPath))
+        PluginPath.clear();
+    }
+
+    StringRef OptOutputFile = MergedFile;
+    if (!PluginPath.empty()) {
+      Expected<std::string> OptPath =
+          findProgram("opt", {getExecutableDir("opt")});
+      if (!OptPath)
+        return OptPath.takeError();
+
+      auto OptOutOrErr = createOutputFile(
+          sys::path::filename(ExecutableName) + ".lowered", "bc");
+      if (!OptOutOrErr)
+        return OptOutOrErr.takeError();
+
+      SmallVector<StringRef, 16> OptArgs{
+          *OptPath,
+          MergedFile,
+          "-load-pass-plugin",
+          Args.MakeArgString(PluginPath),
+          "-passes=hip-post-link-passes",
+          "-o",
+          *OptOutOrErr,
+      };
+
+      if (Error Err = executeCommands(*OptPath, OptArgs))
+        return std::move(Err);
+
+      OptOutputFile = *OptOutOrErr;
+    }
+
+    // Step 3: Convert processed bitcode to SPIR-V.
+    // Check if llvm-spirv translator is available. If so, use it directly;
+    // otherwise use the in-tree SPIR-V backend via clang.
+    // Use sys::findProgramByName() instead of findProgram() to avoid the
+    // dry-run fallback that always "finds" programs by returning their name.
+    bool UseLLVMSpirvTranslator = false;
+    std::string LLVMSpirvPathStr;
+    {
+      ErrorOr<std::string> LLVMSpirvPath = sys::findProgramByName(
+          "llvm-spirv", {getExecutableDir("llvm-spirv")});
+      if (!LLVMSpirvPath)
+        LLVMSpirvPath = sys::findProgramByName("llvm-spirv");
+      if (LLVMSpirvPath) {
+        LLVMSpirvPathStr = *LLVMSpirvPath;
+        UseLLVMSpirvTranslator = true;
+      }
+    }
+    if (UseLLVMSpirvTranslator) {
+      // Use llvm-spirv translator: BC -> SPIR-V binary directly.
+      auto SpirvOutOrErr = createOutputFile(
+          sys::path::filename(ExecutableName) + ".spirv", "spv");
+      if (!SpirvOutOrErr)
+        return SpirvOutOrErr.takeError();
+
+      // Derive SPIR-V max version from the triple's sub-arch.
+      // chipStar needs v1.2 for sub-group extensions by default.
+      std::string MaxVerArg;
+      if (Triple.getSubArch() == llvm::Triple::SPIRVSubArch_v13)
+        MaxVerArg = "--spirv-max-version=1.3";
+      else if (Triple.getSubArch() == llvm::Triple::SPIRVSubArch_v12 ||
+               Triple.getOS() == llvm::Triple::ChipStar)
+        MaxVerArg = "--spirv-max-version=1.2";
+      else
+        MaxVerArg = "--spirv-max-version=1.1";
+
+      SmallVector<StringRef, 16> TranslateArgs{
+          LLVMSpirvPathStr,
+          OptOutputFile,
+          Args.MakeArgString(MaxVerArg),
+          "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups",
+          "-o",
+          *SpirvOutOrErr,
+      };
+
+      if (Error Err = executeCommands(LLVMSpirvPathStr, TranslateArgs))
+        return std::move(Err);
+
+      // The SPIR-V binary is the final output; skip the inner clang
+      // compilation by returning it directly as the linked image.
+      return *SpirvOutOrErr;
+    }
+
+    // No llvm-spirv available; use the in-tree SPIR-V backend via clang.
+    ProcessedInputFiles.push_back(OptOutputFile);
+    CmdArgs.push_back("-mllvm");
+    CmdArgs.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
+                      ",+SPV_INTEL_subgroups"
+                      ",+SPV_EXT_relaxed_printf_string_address_space"
+                      ",+SPV_KHR_bit_instructions"
+                      ",+SPV_EXT_shader_atomic_float_add");
+    // The extracted bitcode files have a .o extension which causes the driver
+    // to treat them as pre-compiled objects, skipping the Backend compilation
+    // step. Force the input language to LLVM IR so the SPIR-V backend runs.
+    // Use -c to skip the link phase -- the SPIR-V backend output is the final
+    // binary; hitting HIPSPV::Linker would re-run the full pipeline.
+    CmdArgs.push_back("-c");
+    CmdArgs.push_back("-x");
+    CmdArgs.push_back("ir");
+  } else {
+    ProcessedInputFiles.append(InputFiles.begin(), InputFiles.end());
+  }
+
+  for (StringRef InputFile : ProcessedInputFiles)
     CmdArgs.push_back(InputFile);
 
   // If this is CPU offloading we copy the input libraries.
@@ -618,8 +791,14 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, 
const ArgList &Args,
 
   for (StringRef Arg : Args.getAllArgValues(OPT_linker_arg_EQ))
     CmdArgs.append({"-Xlinker", Args.MakeArgString(Arg)});
-  for (StringRef Arg : Args.getAllArgValues(OPT_compiler_arg_EQ))
+  for (StringRef Arg : Args.getAllArgValues(OPT_compiler_arg_EQ)) {
+    // For chipStar, --hip-path is already handled by opt step above;
+    // passing it to the inner clang (which just does IR->SPIR-V) is unused.
+    if (Triple.isSPIRV() && Triple.getOS() == llvm::Triple::ChipStar &&
+        Arg.starts_with("--hip-path="))
+      continue;
     CmdArgs.push_back(Args.MakeArgString(Arg));
+  }
 
   if (Error Err = executeCommands(*ClangPath, CmdArgs))
     return std::move(Err);
@@ -1347,6 +1526,16 @@ int main(int Argc, char **Argv) {
   CudaBinaryPath = Args.getLastArgValue(OPT_cuda_path_EQ).str();
   CanonicalPrefixes = !Args.hasArg(OPT_no_canonical_prefixes);
 
+  // Extract --hip-path= from --device-compiler= args, where the outer driver
+  // forwards it via the CompilerOptions forwarding mechanism.
+  for (StringRef Arg : Args.getAllArgValues(OPT_device_compiler_args_EQ)) {
+    auto [DevTriple, Value] = Arg.split('=');
+    if (Value.consume_front("--hip-path=")) {
+      HipPath = Value.str();
+      break;
+    }
+  }
+
   llvm::Triple Triple(
       Args.getLastArgValue(OPT_host_triple_EQ, sys::getDefaultTargetTriple()));
   if (Args.hasArg(OPT_o))
diff --git a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp 
b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
index 5619748965112..2cdfecb82a3ea 100644
--- a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
@@ -91,7 +91,8 @@ SPIRVSubtarget::SPIRVSubtarget(const Triple &TT, const 
std::string &CPU,
   if (TargetTriple.getOS() == Triple::Vulkan)
     Env = Shader;
   else if (TargetTriple.getOS() == Triple::OpenCL ||
-           TargetTriple.getVendor() == Triple::AMD)
+           TargetTriple.getVendor() == Triple::AMD ||
+           TargetTriple.getOS() == Triple::ChipStar)
     Env = Kernel;
   else
     Env = Unknown;

>From d0713754457fe9fe360663c705c19f3a7144cbdd Mon Sep 17 00:00:00 2001
From: Paulius Velesko <[email protected]>
Date: Wed, 8 Apr 2026 17:09:17 +0300
Subject: [PATCH 2/5] [HIPSPV] Delegate chipStar SPIR-V emission to inner clang
 in linker wrapper

Address review feedback on llvm/llvm-project#186972: the chipStar SPIR-V
pipeline (llvm-link -> opt(HipSpvPasses) -> SPIR-V backend) was duplicated
inside ClangLinkerWrapper.cpp. The same pipeline already lives in
HIPSPV::Linker::constructLinkAndEmitSpirvCommand and runs whenever an inner
clang is invoked with --target=spirv64*-unknown-chipstar.

Drop the wrapper-side duplication and let the inner clang's HIPSPV toolchain
do the work:

- Remove the global HipPath and its extraction in main(); --hip-path is
  already forwarded from --device-compiler= via the existing OPT_compiler_arg_EQ
  channel and reaches the inner clang automatically.
- Remove the chipStar-specific llvm-link/opt/SPIR-V emission block from
  the linker-wrapper clang() helper.
- Remove the unconditional --hip-path push for non-chipStar SPIR-V targets;
  no consumer of that branch existed.
- Remove the chipStar --hip-path filter in the compiler_arg loop, restoring
  the simple forwarding loop.

Tests:
- hipspv-toolchain.hip: collapse the WRAPPER / WRAPPER-TR runs into a single
  WRAPPER run that verifies the wrapper invokes inner clang with --target,
  --hip-path, and the input objects. The opt + in-tree-backend pipeline is
  already covered by the CHIPSTAR / CHIPSTAR-SUBARCH driver runs that hit
  HIPSPV::Linker directly.
- hipspv-link-static-library.hip: same simplification of SDL-NEW-WRAPPER.

Net change: -203 lines in the wrapper, no behavioral regression. All
clang/test/Driver lit tests pass (1250/1250 modulo the one pre-existing
expected failure).
---
 .../Driver/hipspv-link-static-library.hip     |   8 +-
 clang/test/Driver/hipspv-toolchain.hip        |  45 +----
 .../ClangLinkerWrapper.cpp                    | 176 +-----------------
 3 files changed, 13 insertions(+), 216 deletions(-)

diff --git a/clang/test/Driver/hipspv-link-static-library.hip 
b/clang/test/Driver/hipspv-link-static-library.hip
index dbcb5bd4e5cbb..a00f385b3288b 100644
--- a/clang/test/Driver/hipspv-link-static-library.hip
+++ b/clang/test/Driver/hipspv-link-static-library.hip
@@ -48,12 +48,10 @@
 // SDL-NEW-SAME: "{{.*}}/tu0.o" "{{.*}}/libSDL2.a"
 // DELETE-SDL-NEW: "{{.*}}llvm-link" "-o" "{{.*}}.bc" "{{.*}}.o" "{{.*}}.o"
 
-// SDL-NEW-WRAPPER: "{{.*}}opt"
-// SDL-NEW-WRAPPER-SAME: -load-pass-plugin
-// SDL-NEW-WRAPPER-SAME: [[HIP_PATH]]/lib/libLLVMHipSpvPasses.so
-// SDL-NEW-WRAPPER: clang{{.*}}" --no-default-config
+// SDL-NEW-WRAPPER: clang{{.*}}" --no-default-config -o {{[^ ]*.img}}
 // SDL-NEW-WRAPPER-SAME: --target=spirv64-unknown-chipstar
-// SDL-NEW-WRAPPER-SAME: -c -x ir
+// SDL-NEW-WRAPPER-SAME: {{[^ ]*.o}}
+// SDL-NEW-WRAPPER-SAME: --hip-path=[[HIP_PATH]]
 
 // SDL: "{{.*}}opt"
 // SDL-SAME: "-load-pass-plugin" {{".*/hipspv/lib/libLLVMHipSpvPasses.so"}}
diff --git a/clang/test/Driver/hipspv-toolchain.hip 
b/clang/test/Driver/hipspv-toolchain.hip
index 2f96b132a3e0c..e1914218c74be 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -60,50 +60,21 @@
 // RUN: llvm-offload-binary -o %t.dev.out \
 // RUN:   
--image=file=%t.dev.bc,kind=hip,triple=spirv64-unknown-chipstar,arch=generic
 
-// Test the in-tree SPIR-V backend path (no llvm-spirv available).
-// Run from a directory that doesn't contain llvm-spirv and use
-// --no-canonical-prefixes so getExecutableDir() looks there instead of
-// the build bin dir. Empty PATH ensures PATH lookup also fails.
-// RUN: mkdir -p %t/no-spirv %t/empty
-// RUN: ln -sf clang-linker-wrapper %t/no-spirv/clang-linker-wrapper
-// RUN: env "PATH=%t/empty" %t/no-spirv/clang-linker-wrapper \
-// RUN:   --no-canonical-prefixes --dry-run \
+// The linker wrapper forwards --hip-path from --device-compiler= to the inner
+// clang invocation; the HIPSPV toolchain inside that clang then drives the
+// llvm-link / opt (HipSpvPasses) / SPIR-V backend pipeline (covered by the
+// CHIPSTAR run below).
+// RUN: clang-linker-wrapper --dry-run \
 // RUN:   
--device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
 // RUN:   --host-triple=spirv64-unknown-chipstar \
 // RUN:   --linker-path=clang-offload-bundler \
 // RUN:   --emit-fatbin-only -o /dev/null %t.dev.out \
 // RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER -DHIP_PATH=%S/Inputs/hipspv
 
-// The linker wrapper runs opt (HipSpvPasses) then uses the in-tree SPIR-V
-// backend when llvm-spirv is not available.
-// WRAPPER: "{{.*}}opt" {{.*}}-load-pass-plugin
-// WRAPPER-SAME: {{.*}}libLLVMHipSpvPasses.so
-// WRAPPER-SAME: -passes=hip-post-link-passes
-
-// WRAPPER: "{{.*}}clang{{.*}}" --no-default-config -o
+// WRAPPER: clang{{.*}}" --no-default-config -o {{[^ ]*.img}}
 // WRAPPER-SAME: --target=spirv64-unknown-chipstar
-// WRAPPER-SAME: -mllvm 
-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add
-// WRAPPER-SAME: -c -x ir
-
-// Test the llvm-spirv translator path (llvm-spirv available in executable 
dir).
-// Place a fake llvm-spirv next to the clang-linker-wrapper symlink.
-// RUN: mkdir -p %t/with-spirv
-// RUN: ln -sf clang-linker-wrapper %t/with-spirv/clang-linker-wrapper
-// RUN: touch %t/with-spirv/llvm-spirv && chmod +x %t/with-spirv/llvm-spirv
-// RUN: env "PATH=%t/empty" %t/with-spirv/clang-linker-wrapper \
-// RUN:   --no-canonical-prefixes --dry-run \
-// RUN:   
--device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
-// RUN:   --host-triple=spirv64-unknown-chipstar \
-// RUN:   --linker-path=clang-offload-bundler \
-// RUN:   --emit-fatbin-only -o /dev/null %t.dev.out \
-// RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER-TR
-
-// WRAPPER-TR: "{{.*}}opt" {{.*}}-load-pass-plugin
-// WRAPPER-TR-SAME: {{.*}}libLLVMHipSpvPasses.so
-// WRAPPER-TR-SAME: -passes=hip-post-link-passes
-
-// WRAPPER-TR: "{{.*}}llvm-spirv" {{.*}}--spirv-max-version=1.2
-// WRAPPER-TR-SAME: 
--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups
+// WRAPPER-SAME: {{[^ ]*.o}}
+// WRAPPER-SAME: --hip-path=[[HIP_PATH]]
 
 // RUN: touch %t.dummy.o
 // RUN: %clang -### --no-default-config -o %t.dummy.img \
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp 
b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index 8fe1658407098..a8d96a1a18a38 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -125,9 +125,6 @@ static StringRef ExecutableName;
 /// Binary path for the CUDA installation.
 static std::string CudaBinaryPath;
 
-/// HIP installation path.
-static std::string HipPath;
-
 /// Mutex lock to protect writes to shared TempFiles in parallel.
 static std::mutex TempFilesMutex;
 
@@ -579,160 +576,7 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, 
const ArgList &Args,
   if (NonLTOAMDGPU)
     CmdArgs.append({"-x", "ir"});
 
-  // For non-chipStar SPIR-V targets, pass the HIP path to clang so it can
-  // find resources. For chipStar, passes are run via opt separately, so the
-  // inner clang doesn't need --hip-path (it just compiles IR to SPIR-V).
-  if (Triple.isSPIRV() && !HipPath.empty() &&
-      Triple.getOS() != llvm::Triple::ChipStar)
-    CmdArgs.push_back(Args.MakeArgString("--hip-path=" + HipPath));
-
-  // For chipStar targets: llvm-link (merge) -> opt (HipSpvPasses) -> clang
-  // (SPIR-V backend). The passes must operate on LLVM IR before the backend
-  // lowers to MIR, and all TU bitcode must be merged first for RDC support.
-  SmallVector<StringRef, 16> ProcessedInputFiles;
-  if (Triple.isSPIRV() && Triple.getOS() == llvm::Triple::ChipStar) {
-    // Step 1: Merge all input bitcode files with llvm-link (needed for RDC
-    // where functions can be defined across translation units).
-    StringRef MergedFile;
-    if (InputFiles.size() > 1) {
-      Expected<std::string> LinkPath =
-          findProgram("llvm-link", {getExecutableDir("llvm-link")});
-      if (!LinkPath)
-        return LinkPath.takeError();
-
-      auto LinkOutOrErr = createOutputFile(
-          sys::path::filename(ExecutableName) + ".merged", "bc");
-      if (!LinkOutOrErr)
-        return LinkOutOrErr.takeError();
-
-      SmallVector<StringRef, 16> LinkArgs{*LinkPath};
-      for (StringRef F : InputFiles)
-        LinkArgs.push_back(F);
-      LinkArgs.push_back("-o");
-      LinkArgs.push_back(*LinkOutOrErr);
-
-      if (Error Err = executeCommands(*LinkPath, LinkArgs))
-        return std::move(Err);
-
-      MergedFile = *LinkOutOrErr;
-    } else {
-      MergedFile = InputFiles[0];
-    }
-
-    // Step 2: Run HipSpvPasses via opt on the merged bitcode.
-    SmallString<128> PluginPath;
-    if (!HipPath.empty()) {
-      PluginPath.assign(HipPath);
-      sys::path::append(PluginPath, "lib", "libLLVMHipSpvPasses.so");
-      if (!sys::fs::exists(PluginPath)) {
-        PluginPath.assign(HipPath);
-        sys::path::append(PluginPath, "lib", "llvm", "libLLVMHipSpvPasses.so");
-      }
-      if (!sys::fs::exists(PluginPath))
-        PluginPath.clear();
-    }
-
-    StringRef OptOutputFile = MergedFile;
-    if (!PluginPath.empty()) {
-      Expected<std::string> OptPath =
-          findProgram("opt", {getExecutableDir("opt")});
-      if (!OptPath)
-        return OptPath.takeError();
-
-      auto OptOutOrErr = createOutputFile(
-          sys::path::filename(ExecutableName) + ".lowered", "bc");
-      if (!OptOutOrErr)
-        return OptOutOrErr.takeError();
-
-      SmallVector<StringRef, 16> OptArgs{
-          *OptPath,
-          MergedFile,
-          "-load-pass-plugin",
-          Args.MakeArgString(PluginPath),
-          "-passes=hip-post-link-passes",
-          "-o",
-          *OptOutOrErr,
-      };
-
-      if (Error Err = executeCommands(*OptPath, OptArgs))
-        return std::move(Err);
-
-      OptOutputFile = *OptOutOrErr;
-    }
-
-    // Step 3: Convert processed bitcode to SPIR-V.
-    // Check if llvm-spirv translator is available. If so, use it directly;
-    // otherwise use the in-tree SPIR-V backend via clang.
-    // Use sys::findProgramByName() instead of findProgram() to avoid the
-    // dry-run fallback that always "finds" programs by returning their name.
-    bool UseLLVMSpirvTranslator = false;
-    std::string LLVMSpirvPathStr;
-    {
-      ErrorOr<std::string> LLVMSpirvPath = sys::findProgramByName(
-          "llvm-spirv", {getExecutableDir("llvm-spirv")});
-      if (!LLVMSpirvPath)
-        LLVMSpirvPath = sys::findProgramByName("llvm-spirv");
-      if (LLVMSpirvPath) {
-        LLVMSpirvPathStr = *LLVMSpirvPath;
-        UseLLVMSpirvTranslator = true;
-      }
-    }
-    if (UseLLVMSpirvTranslator) {
-      // Use llvm-spirv translator: BC -> SPIR-V binary directly.
-      auto SpirvOutOrErr = createOutputFile(
-          sys::path::filename(ExecutableName) + ".spirv", "spv");
-      if (!SpirvOutOrErr)
-        return SpirvOutOrErr.takeError();
-
-      // Derive SPIR-V max version from the triple's sub-arch.
-      // chipStar needs v1.2 for sub-group extensions by default.
-      std::string MaxVerArg;
-      if (Triple.getSubArch() == llvm::Triple::SPIRVSubArch_v13)
-        MaxVerArg = "--spirv-max-version=1.3";
-      else if (Triple.getSubArch() == llvm::Triple::SPIRVSubArch_v12 ||
-               Triple.getOS() == llvm::Triple::ChipStar)
-        MaxVerArg = "--spirv-max-version=1.2";
-      else
-        MaxVerArg = "--spirv-max-version=1.1";
-
-      SmallVector<StringRef, 16> TranslateArgs{
-          LLVMSpirvPathStr,
-          OptOutputFile,
-          Args.MakeArgString(MaxVerArg),
-          "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups",
-          "-o",
-          *SpirvOutOrErr,
-      };
-
-      if (Error Err = executeCommands(LLVMSpirvPathStr, TranslateArgs))
-        return std::move(Err);
-
-      // The SPIR-V binary is the final output; skip the inner clang
-      // compilation by returning it directly as the linked image.
-      return *SpirvOutOrErr;
-    }
-
-    // No llvm-spirv available; use the in-tree SPIR-V backend via clang.
-    ProcessedInputFiles.push_back(OptOutputFile);
-    CmdArgs.push_back("-mllvm");
-    CmdArgs.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
-                      ",+SPV_INTEL_subgroups"
-                      ",+SPV_EXT_relaxed_printf_string_address_space"
-                      ",+SPV_KHR_bit_instructions"
-                      ",+SPV_EXT_shader_atomic_float_add");
-    // The extracted bitcode files have a .o extension which causes the driver
-    // to treat them as pre-compiled objects, skipping the Backend compilation
-    // step. Force the input language to LLVM IR so the SPIR-V backend runs.
-    // Use -c to skip the link phase -- the SPIR-V backend output is the final
-    // binary; hitting HIPSPV::Linker would re-run the full pipeline.
-    CmdArgs.push_back("-c");
-    CmdArgs.push_back("-x");
-    CmdArgs.push_back("ir");
-  } else {
-    ProcessedInputFiles.append(InputFiles.begin(), InputFiles.end());
-  }
-
-  for (StringRef InputFile : ProcessedInputFiles)
+  for (StringRef InputFile : InputFiles)
     CmdArgs.push_back(InputFile);
 
   // If this is CPU offloading we copy the input libraries.
@@ -791,14 +635,8 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, 
const ArgList &Args,
 
   for (StringRef Arg : Args.getAllArgValues(OPT_linker_arg_EQ))
     CmdArgs.append({"-Xlinker", Args.MakeArgString(Arg)});
-  for (StringRef Arg : Args.getAllArgValues(OPT_compiler_arg_EQ)) {
-    // For chipStar, --hip-path is already handled by opt step above;
-    // passing it to the inner clang (which just does IR->SPIR-V) is unused.
-    if (Triple.isSPIRV() && Triple.getOS() == llvm::Triple::ChipStar &&
-        Arg.starts_with("--hip-path="))
-      continue;
+  for (StringRef Arg : Args.getAllArgValues(OPT_compiler_arg_EQ))
     CmdArgs.push_back(Args.MakeArgString(Arg));
-  }
 
   if (Error Err = executeCommands(*ClangPath, CmdArgs))
     return std::move(Err);
@@ -1526,16 +1364,6 @@ int main(int Argc, char **Argv) {
   CudaBinaryPath = Args.getLastArgValue(OPT_cuda_path_EQ).str();
   CanonicalPrefixes = !Args.hasArg(OPT_no_canonical_prefixes);
 
-  // Extract --hip-path= from --device-compiler= args, where the outer driver
-  // forwards it via the CompilerOptions forwarding mechanism.
-  for (StringRef Arg : Args.getAllArgValues(OPT_device_compiler_args_EQ)) {
-    auto [DevTriple, Value] = Arg.split('=');
-    if (Value.consume_front("--hip-path=")) {
-      HipPath = Value.str();
-      break;
-    }
-  }
-
   llvm::Triple Triple(
       Args.getLastArgValue(OPT_host_triple_EQ, sys::getDefaultTargetTriple()));
   if (Args.hasArg(OPT_o))

>From b10b644c6872a08604ea44942c28a9bbc51e3312 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <[email protected]>
Date: Wed, 8 Apr 2026 17:40:18 +0300
Subject: [PATCH 3/5] [HIPSPV] Skip non-filename Inputs in
 constructLinkAndEmitSpirvCommand

When HIPSPV::Linker is reached via the new offload driver (the inner clang
spawned by clang-linker-wrapper for chipStar SPIR-V emission), the
InputInfoList passed to constructLinkAndEmitSpirvCommand may contain
non-filename entries (Nothing or InputArg placeholders) alongside the real
bitcode inputs. The pre-existing loop called getFilename() unconditionally on
every Input, which trips the assert in debug builds and reads garbage from
the InputInfo union in release builds. The garbage was then forwarded to
llvm-link as an input filename, producing errors like:

  llvm-link: No such file or directory: '<random bytes>'
  clang: error: hipspv-link command failed with exit code 1

This bug existed prior to the in-tree SPIR-V backend changes but was latent
because the old (--no-offload-new-driver) HIPSPV path always produced a
filename-only InputInfoList. The new-driver delegation in
"[HIPSPV] Delegate chipStar SPIR-V emission to inner clang in linker wrapper"
makes the same code reachable via the new driver and surfaces the bug.

Filter the loop on isFilename(), matching the canonical pattern used
elsewhere in the driver (e.g. CommonArgs.cpp line 1014 in the LTO link
helper).

Verified by reproducing the failure with a single-file chipStar HIP compile
against the new offload driver, applying the fix, and confirming the same
compile succeeds end-to-end. clang/test/Driver lit tests still pass.
---
 clang/lib/Driver/ToolChains/HIPSPV.cpp | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp 
b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 0dc303e05890b..590a90abdc147 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -59,7 +59,11 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
   // Link LLVM bitcode.
   ArgStringList LinkArgs{};
 
-  for (auto Input : Inputs)
+  // The new offload driver can pass non-filename InputInfo entries (e.g.
+  // Nothing/InputArg placeholders) alongside the real bitcode inputs. Calling
+  // getFilename() on those reads garbage from the union; only forward genuine
+  // filename inputs to llvm-link.
+  for (const auto &Input : Inputs)
     if (Input.isFilename())
       LinkArgs.push_back(Input.getFilename());
 

>From 234d174f9e9ba8ac0b375bd1d5e20bc3156902c5 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <[email protected]>
Date: Wed, 29 Apr 2026 22:04:30 +0300
Subject: [PATCH 4/5] [HIPSPV] Fall back to llvm-spirv when in-tree SPIR-V
 target is missing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The chipstar OS branch in HIPSPV::Linker::constructLinkAndEmitSpirvCommand
unconditionally invoked the in-tree SPIR-V backend via
'clang -mllvm -spirv-ext=...'. On LLVM builds without the SPIRV target
(translator-only configurations) this aborts with
"Unknown command line argument '-spirv-ext='" because the backend's
options are never registered.

Probe for an llvm-spirv binary first (toolchain dir, then PATH) and, when
found, translate bitcode to SPIR-V via SPIRV::constructTranslateCommand
with --spirv-max-version derived from the triple's subarch — matching the
fallback already implemented in clang-linker-wrapper. The in-tree backend
remains the path used when llvm-spirv is unavailable.

Restores --offload-device-only -c to working state for chipStar builds
that ship the external translator.
---
 clang/lib/Driver/ToolChains/HIPSPV.cpp | 41 ++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 3 deletions(-)

diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp 
b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 590a90abdc147..71ff0aa77fd27 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -15,6 +15,7 @@
 #include "clang/Options/Options.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/Path.h"
+#include "llvm/Support/Program.h"
 
 using namespace clang::driver;
 using namespace clang::driver::toolchains;
@@ -80,8 +81,9 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
   auto T = getToolChain().getTriple();
 
   if (T.getOS() == llvm::Triple::ChipStar) {
-    // chipStar: run HipSpvPasses via opt, then use the in-tree SPIR-V backend
-    // for codegen (replaces the external llvm-spirv translator).
+    // chipStar: run HipSpvPasses via opt, then emit SPIR-V either via the
+    // external llvm-spirv translator (if available) or the in-tree SPIR-V
+    // backend (mirrors the fallback in clang-linker-wrapper).
 
     // Run HipSpvPasses plugin via opt (must run on LLVM IR before
     // the SPIR-V backend lowers to MIR).
@@ -100,7 +102,40 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
       TempFile = OptOutput;
     }
 
-    // Compile processed bitcode to SPIR-V using the in-tree backend.
+    // Prefer the external llvm-spirv translator when it is available next to
+    // the toolchain — required when LLVM is built without the in-tree SPIR-V
+    // target. Use findProgramByName (not GetProgramPath) so a missing binary
+    // is reported as not-found rather than being silently substituted.
+    std::string LLVMSpirvPath;
+    {
+      llvm::ErrorOr<std::string> P = llvm::sys::findProgramByName(
+          "llvm-spirv", {llvm::sys::path::parent_path(C.getDriver().Dir)});
+      if (!P)
+        P = llvm::sys::findProgramByName("llvm-spirv",
+                                         {llvm::StringRef(C.getDriver().Dir)});
+      if (!P)
+        P = llvm::sys::findProgramByName("llvm-spirv");
+      if (P)
+        LLVMSpirvPath = *P;
+    }
+
+    if (!LLVMSpirvPath.empty()) {
+      // External translator path: BC -> SPIR-V via llvm-spirv.
+      llvm::opt::ArgStringList TrArgs;
+      // Match clang-linker-wrapper's chipstar version selection.
+      if (T.getSubArch() == llvm::Triple::SPIRVSubArch_v13)
+        TrArgs.push_back("--spirv-max-version=1.3");
+      else
+        TrArgs.push_back("--spirv-max-version=1.2");
+      TrArgs.push_back(
+          
"--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups");
+
+      InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
+      SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
+      return;
+    }
+
+    // Fallback: compile processed bitcode to SPIR-V using the in-tree backend.
     ArgStringList ClangArgs;
     ClangArgs.push_back("--no-default-config");
     ClangArgs.push_back("-c");

>From c43f22bc465f2b8bc44e2733b411f5479f8da16b Mon Sep 17 00:00:00 2001
From: Paulius Velesko <[email protected]>
Date: Sun, 7 Jun 2026 15:43:25 +0300
Subject: [PATCH 5/5] Don't lower unmangled C printf on SPIR-V

The SPIR-V VariadicABIInfo::ignoreFunction already skips OpenCL printf so
the backend can emit OpExtInst printf with inline arguments, but it only
matched the demangled spelling "printf(" (the C++/OpenCL mangled form).
An unmangled C printf declaration demangles to bare "printf" with no
argument list, so it slipped through and ExpandVariadics packed its
arguments into a vararg buffer. The backend then passed the buffer
pointer as printf's first variadic operand, so device printf printed
pointer values instead of the actual arguments.

Match the bare function name as well so OpenCL/HIP printf (emitted
unmangled) is left intact for the OpenCL.std printf lowering.
---
 llvm/lib/Transforms/IPO/ExpandVariadics.cpp | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/IPO/ExpandVariadics.cpp 
b/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
index cfba260af9df4..bc1b26ef2b1b3 100644
--- a/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
+++ b/llvm/lib/Transforms/IPO/ExpandVariadics.cpp
@@ -1059,8 +1059,13 @@ struct SPIRV final : public VariadicABIInfo {
     StringRef DemangledName(Demangled);
 
     // Skip any SPIR-V builtins.
+    // Note: an unmangled C `printf` declaration demangles to "printf" with no
+    // argument list, so the "printf(" prefix check below misses it. Match the
+    // bare name as well so OpenCL/HIP printf (emitted unmangled) is left as a
+    // variadic call for the backend's OpenCL.std printf lowering to expand
+    // inline, rather than being packed into a vararg buffer here.
     if (DemangledName.starts_with("__spirv_") ||
-        DemangledName.starts_with("printf("))
+        DemangledName.starts_with("printf(") || F->getName() == "printf")
       return true;
 
     return false;

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

Reply via email to