Skip to content

[HIPSPV] Add in-tree SPIR-V backend support for chipStar#186972

Open
pvelesko wants to merge 7 commits into
llvm:mainfrom
CHIP-SPV:chipstar-native-spirv
Open

[HIPSPV] Add in-tree SPIR-V backend support for chipStar#186972
pvelesko wants to merge 7 commits into
llvm:mainfrom
CHIP-SPV:chipstar-native-spirv

Conversation

@pvelesko

Copy link
Copy Markdown
Contributor

Summary

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 (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
  • 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-linkopt (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

Test plan

  • hipspv-toolchain.hip driver test updated to verify the new in-tree backend pipeline
  • chipStar test suite: 1173/1173 tests pass (100%) on the translator path; 1169/1173 (99.7%) on the native in-tree backend (4 remaining printf failures due to upstream ExpandVariadics regression in LLVM 23)

@llvmbot llvmbot added clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" backend:SPIR-V labels Mar 17, 2026
@llvmbot

llvmbot commented Mar 17, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-clang-driver

Author: Paulius Velesko (pvelesko)

Changes

Summary

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 (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
  • 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-linkopt (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

Test plan

  • hipspv-toolchain.hip driver test updated to verify the new in-tree backend pipeline
  • chipStar test suite: 1173/1173 tests pass (100%) on the translator path; 1169/1173 (99.7%) on the native in-tree backend (4 remaining printf failures due to upstream ExpandVariadics regression in LLVM 23)

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

6 Files Affected:

  • (modified) clang/include/clang/Basic/AlignedAllocation.h (+2)
  • (modified) clang/lib/Driver/ToolChains/HIPSPV.cpp (+58-25)
  • (modified) clang/lib/Driver/ToolChains/SPIRV.cpp (+2-1)
  • (modified) clang/test/Driver/hipspv-toolchain.hip (+43-13)
  • (modified) clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp (+194-5)
  • (modified) llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp (+2-1)
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 8bdb7ab042b2b..70f831c3ade87 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -72,10 +72,56 @@ 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().getClangProgramPath());
+    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);
@@ -89,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);
@@ -152,14 +182,17 @@ HIPSPVToolChain::HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple,
 void HIPSPVToolChain::addClangTargetOptions(
     const llvm::opt::ArgList &DriverArgs, llvm::opt::ArgStringList &CC1Args,
     Action::OffloadKind DeviceOffloadingKind) const {
-
   if (!HostTC) {
     assert(DeviceOffloadingKind == Action::OFK_None &&
            "Need host toolchain for offloading!");
     return;
   }
 
-  HostTC->addClangTargetOptions(DriverArgs, CC1Args, DeviceOffloadingKind);
+  // NOTE: Unlike other HIP toolchains, we do NOT delegate to
+  // HostTC.addClangTargetOptions() here. On macOS (Darwin), the host toolchain
+  // adds flags like -faligned-alloc-unavailable that are specific to macOS
+  // libc++ and break SPIR-V device compilation. SPIR-V device code doesn't
+  // have the same stdlib limitations as the host.
 
   assert(DeviceOffloadingKind == Action::OFK_HIP &&
          "Only HIP offloading kinds are supported for GPUs.");
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index a59bd05cac0cf..e6a04e00af87b 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -185,7 +185,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) || D.isUsingLTO();
+  NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || D.isUsingLTO() ||
+                      Triple.getOS() == llvm::Triple::ChipStar;
 
   // Lookup binaries into the driver directory.
   getProgramPaths().push_back(getDriver().Dir);
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index ae8d65313abfb..9af64ca4bdaa4 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -59,17 +59,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 \
@@ -84,8 +117,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 \
@@ -100,8 +134,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]]"
 
 //-----------------------------------------------------------------------------
@@ -115,9 +150,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 9e24a9c26d897..2522d0ac71df1 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;
 
@@ -479,9 +482,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, ",")));
 
@@ -554,7 +573,161 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
   if (!Triple.isNVPTX() && !Triple.isSPIRV())
     CmdArgs.push_back("-Wl,--no-undefined");
 
-  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.
@@ -613,8 +786,14 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
 
   for (StringRef Arg : Args.getA...
[truncated]

@llvmbot

llvmbot commented Mar 17, 2026

Copy link
Copy Markdown
Member

@llvm/pr-subscribers-backend-spir-v

Author: Paulius Velesko (pvelesko)

Changes

Summary

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 (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
  • Non-chipStar HIPSPV targets continue using llvm-spirv unchanged
  • Remove HostTC-&gt;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-linkopt (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

Test plan

  • hipspv-toolchain.hip driver test updated to verify the new in-tree backend pipeline
  • chipStar test suite: 1173/1173 tests pass (100%) on the translator path; 1169/1173 (99.7%) on the native in-tree backend (4 remaining printf failures due to upstream ExpandVariadics regression in LLVM 23)

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

6 Files Affected:

  • (modified) clang/include/clang/Basic/AlignedAllocation.h (+2)
  • (modified) clang/lib/Driver/ToolChains/HIPSPV.cpp (+58-25)
  • (modified) clang/lib/Driver/ToolChains/SPIRV.cpp (+2-1)
  • (modified) clang/test/Driver/hipspv-toolchain.hip (+43-13)
  • (modified) clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp (+194-5)
  • (modified) llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp (+2-1)
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 8bdb7ab042b2b..70f831c3ade87 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -72,10 +72,56 @@ 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().getClangProgramPath());
+    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);
@@ -89,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);
@@ -152,14 +182,17 @@ HIPSPVToolChain::HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple,
 void HIPSPVToolChain::addClangTargetOptions(
     const llvm::opt::ArgList &DriverArgs, llvm::opt::ArgStringList &CC1Args,
     Action::OffloadKind DeviceOffloadingKind) const {
-
   if (!HostTC) {
     assert(DeviceOffloadingKind == Action::OFK_None &&
            "Need host toolchain for offloading!");
     return;
   }
 
-  HostTC->addClangTargetOptions(DriverArgs, CC1Args, DeviceOffloadingKind);
+  // NOTE: Unlike other HIP toolchains, we do NOT delegate to
+  // HostTC.addClangTargetOptions() here. On macOS (Darwin), the host toolchain
+  // adds flags like -faligned-alloc-unavailable that are specific to macOS
+  // libc++ and break SPIR-V device compilation. SPIR-V device code doesn't
+  // have the same stdlib limitations as the host.
 
   assert(DeviceOffloadingKind == Action::OFK_HIP &&
          "Only HIP offloading kinds are supported for GPUs.");
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index a59bd05cac0cf..e6a04e00af87b 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -185,7 +185,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) || D.isUsingLTO();
+  NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || D.isUsingLTO() ||
+                      Triple.getOS() == llvm::Triple::ChipStar;
 
   // Lookup binaries into the driver directory.
   getProgramPaths().push_back(getDriver().Dir);
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index ae8d65313abfb..9af64ca4bdaa4 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -59,17 +59,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 \
@@ -84,8 +117,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 \
@@ -100,8 +134,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]]"
 
 //-----------------------------------------------------------------------------
@@ -115,9 +150,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 9e24a9c26d897..2522d0ac71df1 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;
 
@@ -479,9 +482,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, ",")));
 
@@ -554,7 +573,161 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
   if (!Triple.isNVPTX() && !Triple.isSPIRV())
     CmdArgs.push_back("-Wl,--no-undefined");
 
-  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.
@@ -613,8 +786,14 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
 
   for (StringRef Arg : Args.getA...
[truncated]

@github-actions

github-actions Bot commented Mar 17, 2026

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@pvelesko pvelesko marked this pull request as draft March 17, 2026 08:17
@pvelesko pvelesko force-pushed the chipstar-native-spirv branch 4 times, most recently from ed3a552 to bbae359 Compare March 17, 2026 09:26
@pvelesko

Copy link
Copy Markdown
Contributor Author

The Test SPIR-V CI failure is pre-existing on main — confirmed by #186992 (no-op baseline PR on main that shows the same 5 pointer/SpecConstantOp test failures).

@pvelesko pvelesko force-pushed the chipstar-native-spirv branch from bbae359 to 5b7d5d7 Compare April 1, 2026 05:47
@pvelesko pvelesko force-pushed the chipstar-native-spirv branch from 5b7d5d7 to a4a5227 Compare April 8, 2026 05:48
@pvelesko pvelesko marked this pull request as ready for review April 8, 2026 05:48
@pvelesko

pvelesko commented Apr 8, 2026

Copy link
Copy Markdown
Contributor Author

@linehill

Comment thread clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp Outdated
Comment thread clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp Outdated
Comment thread clang/lib/Driver/ToolChains/HIPSPV.cpp Outdated
pvelesko added a commit to CHIP-SPV/llvm-project that referenced this pull request Apr 9, 2026
…rapper

Address review feedback on llvm#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).
Comment on lines +493 to +495
// hip-spirv64-<vendor>-<os>--<arch>
BundleID = Saver.save("hip-spirv64-" + T.getVendorName() + "-" +
T.getOSName() + "--" + EffectiveArch);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm lost here - does this fix a breakage for chipStar? I have tested CHIP-SPV/chipStar#1219 locally on couple days old LLVM and I haven't seen issues. I'm wondering if it necessary to have this.

// For SPIR-V targets, derive arch from triple if not provided
StringRef EffectiveArch = Arch;
if (EffectiveArch.empty() && T.isSPIRV()) {
EffectiveArch = T.getArchName();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think CPU/GPU model is expected here which is not same thing as what Triple::getArchName() returns. For SPIR-V I think we can set it to "generic" - that's the default model used elsewhere for SPIR-V targets.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also, is it a problem if the Arch is empty for non-chipStar SPIR-V targets?

Comment on lines +38 to +39
case llvm::Triple::ChipStar:
return llvm::VersionTuple(); // No version constraint for device targets.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

IIUC, this function is related to availability of std::aligned_alloc(). I'm wondering what this has to do with HIP. Like does HIP language support std::aligned_alloc() to be called from device code?

Comment on lines +66 to 68
for (const auto &Input : Inputs)
if (Input.isFilename())
LinkArgs.push_back(Input.getFilename());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This change got introduced in #187655 so a rebase maybe needed before merge.

Comment thread clang/lib/Driver/ToolChains/HIPSPV.cpp Outdated
Comment on lines +120 to +121
const char *Clang =
C.getArgs().MakeArgString(C.getDriver().getClangProgramPath());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Using clang driver for emitting SPIR-V (through SPIR-V BE) seems hazarous - it think it's better to use cc1 like HIPAMD does.

Comment on lines +86 to +101
// 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;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This portion is a duplicate from the below - could you refactor the duplication away?

@@ -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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This if fine for now. I just want to comment that I regret making the "ChipStar" an OS component but it doesn't matter unless/until chipStar adds a driver backend that requires Vulkan or other SPIR-V environment. It would have been better if it were a vendor component instead to differentiate the target environment - something like "spirv64-chipstar-vulkan".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

doesn't matter unless/until chipStar adds a driver backend that requires Vulkan

I have a branch that converts our generated SPIRV from OpenCL to Vulkan SPIRV. This is for simplifying integration with clvk - converting SPIRV to Vulkan SPIRV on our side allows us to bypass the clspv component of clvk.

Not sure if this is something that will ever get merged or how that would affect llvm if so.

@linehill

@pvelesko pvelesko force-pushed the chipstar-native-spirv branch from 29d34a7 to 23c5b7b Compare April 29, 2026 19:39
pvelesko added a commit to CHIP-SPV/llvm-project that referenced this pull request Apr 29, 2026
…rapper

Address review feedback on llvm#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).
@pvelesko pvelesko force-pushed the chipstar-native-spirv branch from 23c5b7b to 2829c67 Compare April 29, 2026 20:07
pvelesko added 5 commits June 7, 2026 19:02
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
…rapper

Address review feedback on llvm#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).
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.
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.
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.
pvelesko added 2 commits June 14, 2026 13:35
constructLinkAndEmitSpirvCommand has two SPIR-V emission paths: the external
llvm-spirv translator (used when the binary is found next to the toolchain)
and the in-tree SPIR-V backend fallback. The fallback already allows
SPV_EXT_relaxed_printf_string_address_space (and SPV_EXT_shader_atomic_float_add),
but the external translator path did not.

On LLVM 23 with the external translator present, any HIP module whose printf
format string is not in the constant address space (e.g. device-side assert,
dynamic %s lowering) failed translation with:

  Either SPV_EXT_relaxed_printf_string_address_space extension should be
  allowed to translate this module ...
  clang: error: hipspv-link command failed with exit code 18

Add the two extensions to the external translator ext list so both paths
behave identically.
…ffload

Darwin::ensureTargetInitialized() (the lazy target-init helper added for the
HIP-SPIR-V offload toolchain) called setTarget() with the raw triple OS
version from getOSVersion(). On macOS that returns the Darwin *kernel*
version (e.g. 24.3.0) rather than the macOS product version (e.g. 15.3.0).

When clang later builds the offload host job, AddDeploymentTarget() calls
setTarget() again with the real macOS version. setTarget()'s reinit guard
only short-circuits when the versions match; kernel 24.3.0 != macOS 15.3.0,
so it falls through to assert(!TargetInitialized) and clang crashes:

  Assertion failed: (!TargetInitialized && "Target already initialized!"),
  function setTarget, file Darwin.h, line 453.

This crashes every '-x hip --offload=spirv64* --target=arm64-apple-darwin'
compile on macOS, independent of the offload driver (--no-offload-new-driver
does not help).

Convert MacOS kernel versions via getMacOSXVersion() so the lazy init sets
the same version AddDeploymentTarget later uses, matching the behavior of
chipStar's LLVM-22 macOS patch (0006-macos-hip-spirv-llvm22).
@github-actions

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 175589 tests passed
  • 3495 tests skipped
  • 1 test failed

Failed Tests

(click on a test name to see its output)

lldb-api

lldb-api.python_api/run_locker/TestRunLocker.py
Script:
--
/usr/bin/python3 /home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/test/API/dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=/home/gha/actions-runner/_work/llvm-project/llvm-project/build/./lib --env LLVM_INCLUDE_DIR=/home/gha/actions-runner/_work/llvm-project/llvm-project/build/include --env LLVM_TOOLS_DIR=/home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin --libcxx-include-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/include/c++/v1 --libcxx-include-target-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/include/x86_64-unknown-linux-gnu/c++/v1 --libcxx-library-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./lib/x86_64-unknown-linux-gnu --triple x86_64-unknown-linux-gnu --build-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lldb-test-build.noindex --lldb-module-cache-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lldb-test-build.noindex/module-cache-lldb/lldb-api --clang-module-cache-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lldb-test-build.noindex/module-cache-clang/lldb-api --executable /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin/lldb --compiler /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin/clang --dsymutil /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin/dsymutil --make /usr/bin/gmake --llvm-tools-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./bin --lldb-obj-root /home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/lldb --lldb-libs-dir /home/gha/actions-runner/_work/llvm-project/llvm-project/build/./lib --cmake-build-type Release /home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/test/API/python_api/run_locker -p TestRunLocker.py
--
Exit Code: 1

Command Output (stdout):
--
"can't evaluate expressions when the process is running."
Skipping the following test categories: msvcstl, dsym, pdb, gmodules, debugserver, objc

--
Command Output (stderr):
--
FAIL: LLDB (/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-x86_64) :: test_run_locker (TestRunLocker.TestRunLocker.test_run_locker)
Log Files:
 - /home/gha/actions-runner/_work/llvm-project/llvm-project/build/lldb-test-build.noindex/python_api/run_locker/TestRunLocker/Failure_test_run_locker.log
PASS: LLDB (/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang-x86_64) :: test_run_locker_stop_at_entry (TestRunLocker.TestRunLocker.test_run_locker_stop_at_entry)
======================================================================
FAIL: test_run_locker (TestRunLocker.TestRunLocker.test_run_locker)
   Test that the run locker is set correctly when we launch
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/packages/Python/lldbsuite/test/decorators.py", line 160, in wrapper
    return func(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/test/API/python_api/run_locker/TestRunLocker.py", line 23, in test_run_locker
    self.runlocker_test(False)
  File "/home/gha/actions-runner/_work/llvm-project/llvm-project/lldb/test/API/python_api/run_locker/TestRunLocker.py", line 126, in runlocker_test
    self.assertIn(
AssertionError: "can't evaluate expressions when the process is running" not found in ''
Config=x86_64-/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/clang
----------------------------------------------------------------------
Ran 2 tests in 0.679s

FAILED (failures=1)

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

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

Labels

backend:SPIR-V clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants