[clang] [llvm] [HIPSPV] Add in-tree SPIR-V backend support for chipStar (PR #206910)
Paulius Velesko via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 1 08:10:30 PDT 2026
https://github.com/pvelesko updated https://github.com/llvm/llvm-project/pull/206910
>From 588c758e2d9ea2837f35a3ae8a586b1f4ed2d388 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Tue, 17 Mar 2026 09:55:28 +0200
Subject: [PATCH 01/12] [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 <pvelesko at pglc.io>
Date: Wed, 8 Apr 2026 17:09:17 +0300
Subject: [PATCH 02/12] [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 <pvelesko at pglc.io>
Date: Wed, 8 Apr 2026 17:40:18 +0300
Subject: [PATCH 03/12] [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 <pvelesko at pglc.io>
Date: Wed, 29 Apr 2026 22:04:30 +0300
Subject: [PATCH 04/12] [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 b95628fdcf90f520f4cfb3bf900c290ed22936d7 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at gmail.com>
Date: Sun, 14 Jun 2026 13:35:39 +0300
Subject: [PATCH 05/12] [HIPSPV] Allow relaxed_printf ext on external
llvm-spirv path
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.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 71ff0aa77fd27..0d9e6e8d2a218 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -127,8 +127,15 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
TrArgs.push_back("--spirv-max-version=1.3");
else
TrArgs.push_back("--spirv-max-version=1.2");
+ // Keep this extension list in sync with the in-tree backend fallback
+ // below. SPV_EXT_relaxed_printf_string_address_space is required to
+ // translate modules whose printf format string is not in the constant
+ // address space (e.g. device-side assert / dynamic %s lowering); without
+ // it llvm-spirv aborts with exit code 18.
TrArgs.push_back(
- "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups");
+ "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+ ",+SPV_EXT_shader_atomic_float_add"
+ ",+SPV_EXT_relaxed_printf_string_address_space");
InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
>From 0bb9b2d7a8a81029692266a7cd163fc0d25e0d05 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Tue, 30 Jun 2026 10:58:53 +0300
Subject: [PATCH 06/12] [HIPSPV] Address review feedback (NFC + test hardening)
- Factor the duplicated HipSpvPasses opt invocation into a single
runHipSpvPasses() helper used by both the chipStar and non-chipStar
paths.
- clang-linker-wrapper fatbinary(): default an empty SPIR-V arch to the
canonical "generic" CPU model and route all targets through
normalizeForBundler() instead of hand-built bundle IDs.
- Drop the dead llvm-spirv lookup in the install root (parent_path of
the driver dir is never a program directory) and fix a non-ASCII
comment character.
- Pin PATH= on the CHIPSTAR driver-test RUN lines so the in-tree backend
path is exercised deterministically regardless of a host llvm-spirv.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 59 +++++++++----------
clang/test/Driver/hipspv-pass-plugin.hip | 8 +--
clang/test/Driver/hipspv-toolchain.hip | 6 +-
.../ClangLinkerWrapper.cpp | 27 +++------
4 files changed, 44 insertions(+), 56 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 0d9e6e8d2a218..495e1732c8943 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -49,6 +49,28 @@ static std::string findPassPlugin(const Driver &D,
return std::string();
}
+// Runs the HipSpvPasses plugin via `opt` on TempFile when the plugin is found.
+// Returns the lowered bitcode path, or TempFile unchanged if no plugin exists.
+static const char *runHipSpvPasses(Compilation &C, const JobAction &JA,
+ const Tool &Creator, const ToolChain &TC,
+ const InputInfoList &Inputs,
+ const InputInfo &Output,
+ const llvm::opt::ArgList &Args,
+ StringRef Name, const char *TempFile) {
+ auto PassPluginPath = findPassPlugin(C.getDriver(), Args);
+ if (PassPluginPath.empty())
+ return TempFile;
+ const char *PassPathCStr = C.getArgs().MakeArgString(PassPluginPath);
+ const char *OptOutput = HIP::getTempFile(C, Name.str() + "-lower", "bc");
+ ArgStringList OptArgs{TempFile, "-load-pass-plugin",
+ PassPathCStr, "-passes=hip-post-link-passes",
+ "-o", OptOutput};
+ const char *Opt = Args.MakeArgString(TC.GetProgramPath("opt"));
+ C.addCommand(std::make_unique<Command>(
+ JA, Creator, ResponseFileSupport::None(), Opt, OptArgs, Inputs, Output));
+ return OptOutput;
+}
+
void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
Compilation &C, const JobAction &JA, const InputInfoList &Inputs,
const InputInfo &Output, const llvm::opt::ArgList &Args) const {
@@ -87,32 +109,17 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
// 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;
- }
+ TempFile = runHipSpvPasses(C, JA, *this, getToolChain(), Inputs, Output,
+ Args, Name, TempFile);
// 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
+ // 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)});
+ "llvm-spirv", {llvm::StringRef(C.getDriver().Dir)});
if (!P)
P = llvm::sys::findProgramByName("llvm-spirv");
if (P)
@@ -168,18 +175,8 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
}
// 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);
- 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;
- }
+ TempFile = runHipSpvPasses(C, JA, *this, getToolChain(), Inputs, Output, Args,
+ Name, TempFile);
// Emit SPIR-V binary via llvm-spirv translator (non-chipStar targets).
llvm::opt::ArgStringList TrArgs;
diff --git a/clang/test/Driver/hipspv-pass-plugin.hip b/clang/test/Driver/hipspv-pass-plugin.hip
index 5cf891e4c6114..eb915ebc58617 100644
--- a/clang/test/Driver/hipspv-pass-plugin.hip
+++ b/clang/test/Driver/hipspv-pass-plugin.hip
@@ -20,19 +20,19 @@
// backend instead of llvm-spirv):
// RUN: touch %t.dummy.o
-// RUN: %clang -### --no-default-config -o /dev/null --target=spirv64-unknown-chipstar \
+// RUN: env "PATH=" %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=CHIPSTAR,FROM-HIP-PATH
-// RUN: %clang -### --no-default-config -o /dev/null --target=spirv64-unknown-chipstar \
+// RUN: env "PATH=" %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=CHIPSTAR,FROM-OPTION
-// RUN: not %clang -### --no-default-config -o /dev/null --target=spirv64-unknown-chipstar \
+// RUN: not env "PATH=" %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=CHIPSTAR,FROM-OPTION-INVALID
-// RUN: %clang -### --no-default-config -o /dev/null --target=spirv64-unknown-chipstar \
+// RUN: env "PATH=" %clang -### --no-default-config -o /dev/null --target=spirv64-unknown-chipstar \
// RUN: %t.dummy.o 2>&1 | FileCheck %s --check-prefixes=CHIPSTAR,NO-PLUGIN
// FROM-HIP-PATH: {{".*opt"}} {{".*.bc"}} "-load-pass-plugin"
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index e1914218c74be..e97d2426b5113 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -77,7 +77,7 @@
// WRAPPER-SAME: --hip-path=[[HIP_PATH]]
// RUN: touch %t.dummy.o
-// RUN: %clang -### --no-default-config -o %t.dummy.img \
+// RUN: env "PATH=" %clang -### --no-default-config -o %t.dummy.img \
// RUN: --target=spirv64-unknown-chipstar %t.dummy.o \
// RUN: --hip-path="%S/Inputs/hipspv" \
// RUN: 2>&1 | FileCheck %s --check-prefix=CHIPSTAR -DHIP_PATH=%S/Inputs/hipspv
@@ -94,7 +94,7 @@
// 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 \
+// RUN: env "PATH=" %clang -### --no-default-config -o %t.dummy.img \
// RUN: --target=spirv64v1.3-unknown-chipstar \
// RUN: %t.dummy.o --hip-path="%S/Inputs/hipspv" \
// RUN: 2>&1 | FileCheck %s --check-prefix=CHIPSTAR-SUBARCH -DHIP_PATH=%S/Inputs/hipspv
@@ -113,7 +113,7 @@
// Check unknown linker options are ignored - such as ones that are targeted at
// spirv-link. HIPSPV toolchain does linking via llvm-link.
-// RUN: %clang -### --no-default-config -o %t.dummy.img \
+// RUN: env "PATH=" %clang -### --no-default-config -o %t.dummy.img \
// RUN: --target=spirv64-unknown-chipstar %t.dummy.o \
// RUN: --hip-path="%S/Inputs/hipspv" -Xlinker foobar \
// RUN: 2>&1 | FileCheck %s --check-prefix=CHIPSTAR -DHIP_PATH=%S/Inputs/hipspv
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index a8d96a1a18a38..0dada0f3346b7 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -474,24 +474,16 @@ fatbinary(ArrayRef<std::tuple<StringRef, StringRef, StringRef>> InputFiles,
Saver.save("-targets=host-" + HostTriple.normalize())};
for (const auto &[File, TripleRef, Arch] : InputFiles) {
llvm::Triple T(TripleRef);
- // For SPIR-V targets, derive arch from triple if not provided
+ // SPIR-V HIP targets (e.g. chipStar) can arrive without an arch, but the
+ // offload bundler requires a non-empty arch field; default to the
+ // canonical SPIR-V CPU model "generic".
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);
+ if (EffectiveArch.empty() && T.isSPIRV())
+ EffectiveArch = "generic";
+ std::string NormalizedTriple =
+ normalizeForBundler(T, !EffectiveArch.empty());
+ Targets.push_back(
+ Saver.save("hip-" + NormalizedTriple + "-" + EffectiveArch));
}
CmdArgs.push_back(Saver.save(llvm::join(Targets, ",")));
@@ -575,7 +567,6 @@ 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)
CmdArgs.push_back(InputFile);
>From 4fd07d8a7a1e602867709c66735de7422d5a42f7 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Wed, 1 Jul 2026 13:14:29 +0300
Subject: [PATCH 07/12] [HIPSPV] Emit SPIR-V via clang -cc1 instead of the
clang driver
The in-tree SPIR-V backend fallback invoked the clang driver to compile
the lowered bitcode to SPIR-V. That re-runs config-file loading, toolchain
detection and argument translation over an input that is already
device-compiled and lowered - wasteful and fragile. Invoke clang -cc1
directly instead, mirroring how HIPAMD drives its SPIR-V backend emission.
Update the hipspv driver tests to match the cc1 command line.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 55 ++++++++++++++----------
clang/test/Driver/hipspv-pass-plugin.hip | 3 +-
clang/test/Driver/hipspv-toolchain.hip | 8 ++--
3 files changed, 39 insertions(+), 27 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 495e1732c8943..944a577adb45e 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -149,28 +149,39 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
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");
- 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));
+ // Fallback: compile the lowered bitcode to SPIR-V with the in-tree backend.
+ // Invoke `clang -cc1` directly rather than the clang driver: the driver
+ // would re-run config-file loading, toolchain detection and argument
+ // translation over an input that is already device-compiled and lowered,
+ // which is both wasteful and fragile. This mirrors how HIPAMD drives its
+ // SPIR-V backend emission (see HIPAMD::constructLinkAndEmitSpirvCommand).
+ // Keep the default -O0 backend pipeline (i.e. no -disable-llvm-optzns) so
+ // the mandatory lowering passes still run, matching the previously
+ // validated driver `-c` behavior.
+ ArgStringList Cc1Args;
+ Cc1Args.push_back("-cc1");
+ Cc1Args.push_back("-triple");
+ Cc1Args.push_back(C.getArgs().MakeArgString(T.getTriple()));
+ Cc1Args.push_back("-emit-obj");
+
+ // SPIR-V extensions the chipStar runtime relies on. Keep in sync with the
+ // llvm-spirv translator path above and the clang-linker-wrapper fallback.
+ Cc1Args.push_back("-mllvm");
+ Cc1Args.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");
+
+ Cc1Args.push_back(TempFile);
+ Cc1Args.push_back("-o");
+ Cc1Args.push_back(Output.getFilename());
+
+ const Driver &Drv = C.getDriver();
+ const char *Clang = Drv.getDriverProgramPath();
+ C.addCommand(std::make_unique<Command>(
+ JA, *this, ResponseFileSupport::None(), Clang, Cc1Args, Inputs, Output,
+ Drv.getPrependArg()));
return;
}
diff --git a/clang/test/Driver/hipspv-pass-plugin.hip b/clang/test/Driver/hipspv-pass-plugin.hip
index eb915ebc58617..d13894ed50c9b 100644
--- a/clang/test/Driver/hipspv-pass-plugin.hip
+++ b/clang/test/Driver/hipspv-pass-plugin.hip
@@ -43,4 +43,5 @@
// 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"
+// CHIPSTAR: {{".*clang.*"}} "-cc1"
+// CHIPSTAR-SAME: "-emit-obj"
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index e97d2426b5113..e09cc6817a40b 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -89,8 +89,8 @@
// CHIPSTAR-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
// CHIPSTAR-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
-// CHIPSTAR: {{".*clang.*"}} "--no-default-config" "-c"
-// CHIPSTAR-SAME: "--target=spirv64-unknown-chipstar"
+// CHIPSTAR: {{".*clang.*"}} "-cc1" "-triple" "spirv64-unknown-chipstar"
+// CHIPSTAR-SAME: "-emit-obj"
// 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]]"
@@ -106,8 +106,8 @@
// CHIPSTAR-SUBARCH-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
// CHIPSTAR-SUBARCH-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
-// CHIPSTAR-SUBARCH: {{".*clang.*"}} "--no-default-config" "-c"
-// CHIPSTAR-SUBARCH-SAME: "--target=spirv64v1.3-unknown-chipstar"
+// CHIPSTAR-SUBARCH: {{".*clang.*"}} "-cc1" "-triple" "spirv64v1.3-unknown-chipstar"
+// CHIPSTAR-SUBARCH-SAME: "-emit-obj"
// 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]]"
>From 2eb9267ecd61e31ceac38b765feba372266ea696 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Wed, 1 Jul 2026 13:26:20 +0300
Subject: [PATCH 08/12] [HIPSPV] Drop the AlignedAllocation ChipStar case
alignedAllocMinVersion() feeds only the C++17 aligned operator new
availability diagnostic, which fires solely when the host Darwin
toolchain injects -faligned-alloc-unavailable. That flag no longer leaks
into chipStar device compilation now that Darwin's addClangTargetOptions
skips host flags for device offload (#183991, in main), so this case is
unreachable in the chipStar backend path and does not belong in this PR.
---
clang/include/clang/Basic/AlignedAllocation.h | 2 --
1 file changed, 2 deletions(-)
diff --git a/clang/include/clang/Basic/AlignedAllocation.h b/clang/include/clang/Basic/AlignedAllocation.h
index 9b84d07286d52..ac26eb4a276da 100644
--- a/clang/include/clang/Basic/AlignedAllocation.h
+++ b/clang/include/clang/Basic/AlignedAllocation.h
@@ -35,8 +35,6 @@ 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");
>From 22856d0821e6f14f98fd483f674185888dfa334e Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Wed, 1 Jul 2026 13:49:35 +0300
Subject: [PATCH 09/12] [HIPSPV] Align llvm-spirv and in-tree SPIR-V extension
lists
The llvm-spirv translator path was missing SPV_KHR_bit_instructions that
the in-tree backend path enables. Enable the same extension set in the
same order on both paths so the "keep in sync" comment holds, and drop
the stale reference to the removed clang-linker-wrapper fallback.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 944a577adb45e..322913b568103 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -141,8 +141,9 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
// it llvm-spirv aborts with exit code 18.
TrArgs.push_back(
"--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
- ",+SPV_EXT_shader_atomic_float_add"
- ",+SPV_EXT_relaxed_printf_string_address_space");
+ ",+SPV_EXT_relaxed_printf_string_address_space"
+ ",+SPV_KHR_bit_instructions"
+ ",+SPV_EXT_shader_atomic_float_add");
InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
@@ -165,7 +166,7 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
Cc1Args.push_back("-emit-obj");
// SPIR-V extensions the chipStar runtime relies on. Keep in sync with the
- // llvm-spirv translator path above and the clang-linker-wrapper fallback.
+ // llvm-spirv translator path above.
Cc1Args.push_back("-mllvm");
Cc1Args.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
",+SPV_INTEL_subgroups"
>From 7e21c0d24e10c33c229e1d00e4760615d06b8be5 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Wed, 1 Jul 2026 14:16:57 +0300
Subject: [PATCH 10/12] [HIPSPV] Drop the ineffective SPIRVToolChain ChipStar
clause
A spirv64-unknown-chipstar (HIP) target routes to HIPSPVToolChain, never
to SPIRVToolChain: the OS switch in getToolChain sends OS=ChipStar to
HIPSPVToolChain, and offloaded HIP to spirv goes there too. SPIRVToolChain
is only built for Vulkan/ShaderModel or the OS-default arch path (and its
SPIRVOpenMPToolChain subclass, only for OpenMP offload). So making
NativeLLVMSupport true for ChipStar here has no effect on the chipStar
flow - which drives its own llvm-link/opt/backend command and bypasses the
HasNativeLLVMSupport linker gate anyway. Drop the dead clause.
---
clang/lib/Driver/ToolChains/SPIRV.cpp | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index c6a0b667971fb..5cc1eec74c1cc 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -201,8 +201,7 @@ 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) ||
- Triple.getOS() == llvm::Triple::ChipStar;
+ NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || isUsingLTO(Args);
// Lookup binaries into the driver directory.
getProgramPaths().push_back(getDriver().Dir);
>From f1b8e5e422bbdbb823cca7f3de23448351fa07bd Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Wed, 1 Jul 2026 14:47:57 +0300
Subject: [PATCH 11/12] [HIPSPV] Detect versioned llvm-spirv on the chipStar
path
The chipStar branch decided translator-vs-in-tree-backend by probing only
the unversioned "llvm-spirv", but SPIRV::constructTranslateCommand runs
"llvm-spirv-<major>" in preference. So an install shipping only
llvm-spirv-<major> was mis-detected as "no translator" and silently fell
back to the in-tree backend. Probe the versioned name first, matching the
binary that actually runs, and restore the chipStar VERSIONED test that
covers it.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 28 +++++++++++++++++---------
clang/test/Driver/hipspv-toolchain.hip | 8 ++++++++
2 files changed, 27 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 322913b568103..530648ffdf919 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -13,6 +13,7 @@
#include "clang/Driver/Driver.h"
#include "clang/Driver/InputInfo.h"
#include "clang/Options/Options.h"
+#include "llvm/Config/llvm-config.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
@@ -113,17 +114,26 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
Args, Name, TempFile);
// 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.
+ // the toolchain (or on PATH) - required when LLVM is built without the
+ // in-tree SPIR-V target. Mirror SPIRV::constructTranslateCommand, which
+ // prefers the versioned "llvm-spirv-<major>" over the plain name, so the
+ // translator-vs-backend choice matches the binary that will actually run.
+ // Use findProgramByName (not GetProgramPath) so a missing binary is
+ // reported as not-found rather than being silently substituted by its name.
std::string LLVMSpirvPath;
{
- llvm::ErrorOr<std::string> P = llvm::sys::findProgramByName(
- "llvm-spirv", {llvm::StringRef(C.getDriver().Dir)});
- if (!P)
- P = llvm::sys::findProgramByName("llvm-spirv");
- if (P)
- LLVMSpirvPath = *P;
+ StringRef DriverDir(C.getDriver().Dir);
+ std::string Versioned = "llvm-spirv-" + std::to_string(LLVM_VERSION_MAJOR);
+ for (StringRef Name : {StringRef(Versioned), StringRef("llvm-spirv")}) {
+ llvm::ErrorOr<std::string> P =
+ llvm::sys::findProgramByName(Name, {DriverDir});
+ if (!P)
+ P = llvm::sys::findProgramByName(Name);
+ if (P) {
+ LLVMSpirvPath = *P;
+ break;
+ }
+ }
}
if (!LLVMSpirvPath.empty()) {
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index e09cc6817a40b..adfcafa74935c 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -129,4 +129,12 @@
// RUN: | FileCheck -DVERSION=%llvm-version-major \
// RUN: --check-prefix=VERSIONED %s
+// The chipStar path must make the same choice: a versioned llvm-spirv on PATH
+// is preferred over the in-tree backend (detection mirrors the translator
+// lookup in SPIRV::constructTranslateCommand).
+// 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" 2>&1 \
+// RUN: | FileCheck -DVERSION=%llvm-version-major --check-prefix=VERSIONED %s
+
// VERSIONED: {{.*}}llvm-spirv-[[VERSION]]
>From db34c47a02b787c91a49c6df5439ddf90b3eface Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Wed, 1 Jul 2026 16:08:34 +0300
Subject: [PATCH 12/12] [HIPSPV] Drop the llvm-link input-loop
comment/const-ref churn
The isFilename() guard on the llvm-link input loop already landed upstream
in #187655; this branch only added an explanatory comment and an auto ->
const auto& tweak. Revert that hunk to keep the PR focused on the SPIR-V
backend work.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 530648ffdf919..fcf368537436c 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -83,11 +83,7 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
// Link LLVM bitcode.
ArgStringList LinkArgs{};
- // 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)
+ for (auto Input : Inputs)
if (Input.isFilename())
LinkArgs.push_back(Input.getFilename());
More information about the cfe-commits
mailing list