[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
Tue Jul 28 00:20:05 PDT 2026
https://github.com/pvelesko updated https://github.com/llvm/llvm-project/pull/206910
>From 6c694dd07f6d30417ee2ffef7bb63b43831e0663 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/20] [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 | 198 +++++++++++++++++-
llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp | 3 +-
9 files changed, 308 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 1e6fa1e4d54c6..df5232e3923fa 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");
// Preserve debug info requested via -g into the emitted SPIR-V using the
// NonSemantic.Shader.DebugInfo form. Downstream consumers such as Intel's IGC
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.h b/clang/lib/Driver/ToolChains/HIPSPV.h
index 337c9c9993876..fb53d65b335f5 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.h
+++ b/clang/lib/Driver/ToolChains/HIPSPV.h
@@ -52,8 +52,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 3e11d647fd821..870ebbde92c85 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,11 +158,6 @@
// 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 c2de6578773c7..e9a9afce25edd 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, ",")));
@@ -543,7 +562,160 @@ 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.
@@ -602,8 +774,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);
@@ -1529,6 +1707,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 be140640962d3..5899f60360a11 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 609a01e7aed91a7097ebe6c28bd2df76fc3fbee3 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/20] [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 870ebbde92c85..75764979fa38f 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 e9a9afce25edd..bdf12acc2a9b9 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;
@@ -562,160 +559,7 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
if (!Triple.isNVPTX() && !Triple.isSPIRV())
CmdArgs.push_back("-Wl,--no-undefined");
- // 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.
@@ -774,14 +618,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);
@@ -1707,16 +1545,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 e7587c08a5fef07d019a7d4590e1030135632974 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/20] [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 df5232e3923fa..ca911c8982be2 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 ff164a8d1c07281ab9e68146809fc18a2ca22e54 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/20] [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 ca911c8982be2..c8d03315bb604 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 c1b444ac2a4c7049c685b5049803ef059020fc13 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/20] [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 c8d03315bb604..ecd9e1e828e57 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 8fd64a7d6487466cce524cbcbd313b80b7fb6193 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/20] [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 | 26 +++-----
4 files changed, 44 insertions(+), 55 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index ecd9e1e828e57..933c01777a3b2 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 75764979fa38f..4e2f59a1b6473 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 bdf12acc2a9b9..9910ceb1a339a 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, ",")));
>From 27ef58ac207968741129188db29c93b32f4a14c8 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/20] [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 933c01777a3b2..585fa2b0b7439 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 4e2f59a1b6473..b05bdb1272cab 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 5295a226266f2171a51711febf5ce6151353e057 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/20] [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 6cb84fd7cfa19c975f0150b09ad6fe6f02d906a6 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/20] [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 585fa2b0b7439..fe627836bf258 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 a072abd0bcb14a73a7b3dcada875002c48fd3b2f 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/20] [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 57a9f69963f80f02e96dccb71f74b73aa83feadd 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/20] [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 | 29 ++++++++++++++++++--------
clang/test/Driver/hipspv-toolchain.hip | 8 +++++++
2 files changed, 28 insertions(+), 9 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index fe627836bf258..132bbad2fb3fd 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,27 @@ 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 b05bdb1272cab..621ac93f7f81e 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -129,6 +129,14 @@
// 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 26ba2c11c0a101c3a75aa72a24f9d6172f71c55f 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/20] [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 132bbad2fb3fd..5743e833f8d98 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());
>From 53c9a1b5997e95310f976ef70cd8a49be7009b4c Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Sat, 18 Jul 2026 14:31:14 +0300
Subject: [PATCH 13/20] [HIPSPV] Only cap SPIR-V version when the triple has no
subarch
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 5743e833f8d98..fb65e5a553c86 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -136,10 +136,7 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
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
+ if (T.getSubArch() == llvm::Triple::NoSubArch)
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
>From cb6725722148f79a5bb9e7ceebf4598e162dec9a Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Sat, 18 Jul 2026 14:52:21 +0300
Subject: [PATCH 14/20] [HIPSPV] Format translator extension list one extension
per line
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index fb65e5a553c86..3ae0b1e7db650 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -143,11 +143,12 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
// 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"
- ",+SPV_EXT_relaxed_printf_string_address_space"
- ",+SPV_KHR_bit_instructions"
- ",+SPV_EXT_shader_atomic_float_add");
+ TrArgs.push_back("--spirv-ext=-all"
+ ",+SPV_INTEL_function_pointers"
+ ",+SPV_INTEL_subgroups"
+ ",+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);
>From 2250908547aab658e2960c9fda922fe0cd3f0499 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Sat, 18 Jul 2026 15:20:22 +0300
Subject: [PATCH 15/20] [HIPSPV] Drop redundant arch default in linker wrapper
The clang driver already defaults SPIR-V HIP offload arch to "generic"
(Driver::getOffloadArchs and the packager job in Clang.cpp), so
fatbinary() never sees an empty arch from driver-produced binaries.
---
.../clang-linker-wrapper/ClangLinkerWrapper.cpp | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index 9910ceb1a339a..c2de6578773c7 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -473,17 +473,9 @@ fatbinary(ArrayRef<std::tuple<StringRef, StringRef, StringRef>> InputFiles,
SmallVector<StringRef> Targets = {
Saver.save("-targets=host-" + HostTriple.normalize())};
for (const auto &[File, TripleRef, Arch] : InputFiles) {
- llvm::Triple T(TripleRef);
- // 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 = "generic";
std::string NormalizedTriple =
- normalizeForBundler(T, !EffectiveArch.empty());
- Targets.push_back(
- Saver.save("hip-" + NormalizedTriple + "-" + EffectiveArch));
+ normalizeForBundler(Triple(TripleRef), !Arch.empty());
+ Targets.push_back(Saver.save("hip-" + NormalizedTriple + "-" + Arch));
}
CmdArgs.push_back(Saver.save(llvm::join(Targets, ",")));
>From abba49a041b3957561a603ed0098965f1733454a Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Tue, 21 Jul 2026 15:14:14 +0300
Subject: [PATCH 16/20] [HIPSPV] Drop
SPV_EXT_relaxed_printf_string_address_space
No tested OpenCL runtime accepts SPIR-V modules using this extension:
Intel compute-runtime rejects them at clBuildProgram, rusticl aborts,
and Mali returns CL_INVALID_VALUE from clCreateProgramWithIL. Allowing
it only defers the failure from compile time to load time. Non-constant
printf format strings (device-side assert) are instead handled in
chipStar's device library.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 7 +------
clang/test/Driver/hipspv-toolchain.hip | 4 ++--
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 3ae0b1e7db650..8281ffae45ce8 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -139,14 +139,10 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
if (T.getSubArch() == llvm::Triple::NoSubArch)
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.
+ // below.
TrArgs.push_back("--spirv-ext=-all"
",+SPV_INTEL_function_pointers"
",+SPV_INTEL_subgroups"
- ",+SPV_EXT_relaxed_printf_string_address_space"
",+SPV_KHR_bit_instructions"
",+SPV_EXT_shader_atomic_float_add");
@@ -175,7 +171,6 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
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");
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index 621ac93f7f81e..8d1a60d527dba 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -91,7 +91,7 @@
// 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: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
// CHIPSTAR-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
// RUN: env "PATH=" %clang -### --no-default-config -o %t.dummy.img \
@@ -108,7 +108,7 @@
// 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: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+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
>From 3601a7f91fc0ef0125efa03c03f8953bd3556324 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Wed, 22 Jul 2026 12:25:48 +0300
Subject: [PATCH 17/20] [HIPSPV] Select SPIR-V emitter with
-f[no-]integrated-objemitter
Use the existing integrated-backend machinery instead of probing for
llvm-spirv on PATH: the in-tree SPIR-V backend is the default (when
built), and -fno-integrated-objemitter selects the external llvm-spirv
translator, located as in SPIRV::constructTranslateCommand. This keeps
a stray llvm-spirv in PATH from silently changing the emitter.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 58 ++++++++++++--------------
clang/lib/Driver/ToolChains/HIPSPV.h | 4 ++
clang/test/Driver/hipspv-toolchain.hip | 35 +++++++++++-----
3 files changed, 56 insertions(+), 41 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 8281ffae45ce8..1e29fec12df14 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -13,10 +13,9 @@
#include "clang/Driver/Driver.h"
#include "clang/Driver/InputInfo.h"
#include "clang/Options/Options.h"
-#include "llvm/Config/llvm-config.h"
+#include "llvm/MC/TargetRegistry.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;
@@ -100,40 +99,16 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
auto T = getToolChain().getTriple();
if (T.getOS() == llvm::Triple::ChipStar) {
- // 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).
+ // chipStar: run HipSpvPasses via opt, then emit SPIR-V with the in-tree
+ // SPIR-V backend by default, or with the external llvm-spirv translator
+ // when -fno-integrated-objemitter is given (or the backend is not built).
// Run HipSpvPasses plugin via opt (must run on LLVM IR before
// the SPIR-V backend lowers to MIR).
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 (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;
- {
- 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()) {
+ if (!getToolChain().useIntegratedBackend()) {
// External translator path: BC -> SPIR-V via llvm-spirv.
llvm::opt::ArgStringList TrArgs;
if (T.getSubArch() == llvm::Triple::NoSubArch)
@@ -146,12 +121,21 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
",+SPV_KHR_bit_instructions"
",+SPV_EXT_shader_atomic_float_add");
+ // Preserve debug info requested via -g (see the comment on the
+ // equivalent block in the non-chipStar path below).
+ if (const Arg *A = Args.getLastArg(options::OPT_g_Group);
+ A && !A->getOption().matches(options::OPT_g0)) {
+ TrArgs.push_back("--spirv-ext=+SPV_KHR_non_semantic_info"
+ ",+SPV_INTEL_optnone");
+ TrArgs.push_back("--spirv-debug-info-version=nonsemantic-shader-200");
+ }
+
InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
return;
}
- // Fallback: compile the lowered bitcode to SPIR-V with the in-tree backend.
+ // Default: 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,
@@ -259,6 +243,18 @@ HIPSPVToolChain::HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple,
getProgramPaths().push_back(getDriver().Dir);
}
+bool HIPSPVToolChain::IsIntegratedBackendSupported() const {
+ // The in-tree SPIR-V backend can only be used when it is built.
+ std::string IgnoredError;
+ return llvm::TargetRegistry::lookupTarget(getTriple(), IgnoredError);
+}
+
+bool HIPSPVToolChain::IsIntegratedBackendDefault() const {
+ // Prefer the in-tree SPIR-V backend; fall back to the external llvm-spirv
+ // translator when the backend is not built.
+ return IsIntegratedBackendSupported();
+}
+
void HIPSPVToolChain::addClangTargetOptions(
const llvm::opt::ArgList &DriverArgs, llvm::opt::ArgStringList &CC1Args,
BoundArch BA, Action::OffloadKind DeviceOffloadingKind) const {
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.h b/clang/lib/Driver/ToolChains/HIPSPV.h
index fb53d65b335f5..176ef5ddde8b3 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.h
+++ b/clang/lib/Driver/ToolChains/HIPSPV.h
@@ -55,6 +55,10 @@ class LLVM_LIBRARY_VISIBILITY HIPSPVToolChain final : public ToolChain {
return HostTC ? &HostTC->getTriple() : nullptr;
}
+ bool IsIntegratedBackendDefault() const override;
+ bool IsIntegratedBackendSupported() const override;
+ bool IsNonIntegratedBackendSupported() const override { return true; }
+
void
addClangTargetOptions(const llvm::opt::ArgList &DriverArgs,
llvm::opt::ArgStringList &CC1Args, BoundArch BA,
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index 8d1a60d527dba..7ca217aabf545 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -76,8 +76,10 @@
// WRAPPER-SAME: {{[^ ]*.o}}
// WRAPPER-SAME: --hip-path=[[HIP_PATH]]
+// The in-tree SPIR-V backend is the default emitter; PATH content (e.g. a
+// stray llvm-spirv) must not affect the choice.
// RUN: touch %t.dummy.o
-// RUN: env "PATH=" %clang -### --no-default-config -o %t.dummy.img \
+// RUN: %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 +96,7 @@
// CHIPSTAR-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
// CHIPSTAR-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
-// RUN: env "PATH=" %clang -### --no-default-config -o %t.dummy.img \
+// RUN: %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
@@ -111,9 +113,23 @@
// CHIPSTAR-SUBARCH-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
// CHIPSTAR-SUBARCH-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
+// -fno-integrated-objemitter selects the external llvm-spirv translator.
+// RUN: %clang -### --no-default-config -o %t.dummy.img \
+// RUN: --target=spirv64-unknown-chipstar %t.dummy.o \
+// RUN: --hip-path="%S/Inputs/hipspv" -fno-integrated-objemitter \
+// RUN: 2>&1 | FileCheck %s --check-prefix=CHIPSTAR-XTOR -DHIP_PATH=%S/Inputs/hipspv
+
+// CHIPSTAR-XTOR: {{".*opt"}} {{".*bc"}} "-load-pass-plugin"
+// CHIPSTAR-XTOR-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
+// CHIPSTAR-XTOR-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
+
+// CHIPSTAR-XTOR: {{".*llvm-spirv.*"}} "--spirv-max-version=1.2"
+// CHIPSTAR-XTOR-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
+// CHIPSTAR-XTOR-SAME: [[LOWER_BC]] "-o" "{{.*img}}"
+
// Check unknown linker options are ignored - such as ones that are targeted at
// spirv-link. HIPSPV toolchain does linking via llvm-link.
-// RUN: env "PATH=" %clang -### --no-default-config -o %t.dummy.img \
+// RUN: %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
@@ -129,12 +145,11 @@
// 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).
+// With -fno-integrated-objemitter the chipStar path must pick the same
+// versioned translator (lookup shared with 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: --hip-path="%S/Inputs/hipspv" -fno-integrated-objemitter 2>&1 \
// RUN: | FileCheck -DVERSION=%llvm-version-major --check-prefix=VERSIONED %s
// VERSIONED: {{.*}}llvm-spirv-[[VERSION]]
@@ -145,17 +160,17 @@
// translator, and that no debug flags are emitted without -g.
// RUN: %clang -### --no-default-config -g -o %t.dummy.img \
// RUN: --target=spirv64-unknown-chipstar %t.dummy.o \
-// RUN: --hip-path="%S/Inputs/hipspv" \
+// RUN: --hip-path="%S/Inputs/hipspv" -fno-integrated-objemitter \
// RUN: 2>&1 | FileCheck %s --check-prefix=DEBUG
// DEBUG: {{".*llvm-spirv"}} "--spirv-max-version=1.2"
-// DEBUG-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+// DEBUG-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
// DEBUG-SAME: "--spirv-ext=+SPV_KHR_non_semantic_info,+SPV_INTEL_optnone"
// DEBUG-SAME: "--spirv-debug-info-version=nonsemantic-shader-200"
// RUN: %clang -### --no-default-config -o %t.dummy.img \
// RUN: --target=spirv64-unknown-chipstar %t.dummy.o \
-// RUN: --hip-path="%S/Inputs/hipspv" \
+// RUN: --hip-path="%S/Inputs/hipspv" -fno-integrated-objemitter \
// RUN: 2>&1 | FileCheck %s --check-prefix=NODEBUG
// NODEBUG: {{".*llvm-spirv"}}
>From 74b1d93bc6bae12d1061adcdc6cf3a529b0f7a6f Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Thu, 23 Jul 2026 22:34:09 +0300
Subject: [PATCH 18/20] [HIPSPV] Key chipStar debug-info emission on bitcode
contents, not -g
In RDC-mode links the SPIR-V emission job runs in a clang invoked by
clang-linker-wrapper, where the compile-time -g is not visible, so
gating the debug flags on -g silently dropped device debug info. The
debug info itself travels in the bitcode, and the flags only allow
extensions and select the debug-info form: with debug-info-free
bitcode the translator output is bit-identical, and the backend's
NonSemantic.Shader.DebugInfo handler is only registered when the
module carries llvm.dbg.cu. Pass them unconditionally on both the
translator path and the in-tree backend path (which previously had no
way to emit debug info or the OptNoneINTEL function control at all).
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 28 +++++++++++++++---------
clang/test/Driver/hipspv-toolchain.hip | 30 ++++++++------------------
2 files changed, 27 insertions(+), 31 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 1e29fec12df14..f82af83993849 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -121,14 +121,17 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
",+SPV_KHR_bit_instructions"
",+SPV_EXT_shader_atomic_float_add");
- // Preserve debug info requested via -g (see the comment on the
- // equivalent block in the non-chipStar path below).
- if (const Arg *A = Args.getLastArg(options::OPT_g_Group);
- A && !A->getOption().matches(options::OPT_g0)) {
- TrArgs.push_back("--spirv-ext=+SPV_KHR_non_semantic_info"
- ",+SPV_INTEL_optnone");
- TrArgs.push_back("--spirv-debug-info-version=nonsemantic-shader-200");
- }
+ // Preserve debug info in the NonSemantic.Shader.DebugInfo form (see the
+ // comment on the equivalent block in the non-chipStar path below).
+ // These flags are passed unconditionally instead of gating on -g: in
+ // RDC-mode links this job runs in a clang invoked by
+ // clang-linker-wrapper where the original -g is not visible, but the
+ // debug info itself travels in the bitcode. The flags only allow
+ // extensions and select the debug info form; with debug-info-free
+ // bitcode the output is unchanged.
+ TrArgs.push_back("--spirv-ext=+SPV_KHR_non_semantic_info"
+ ",+SPV_INTEL_optnone");
+ TrArgs.push_back("--spirv-debug-info-version=nonsemantic-shader-200");
InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
@@ -151,12 +154,17 @@ 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.
+ // llvm-spirv translator path above. SPV_KHR_non_semantic_info and
+ // SPV_INTEL_optnone let the backend emit NonSemantic.Shader.DebugInfo and
+ // the OptNoneINTEL function control when the bitcode carries debug info /
+ // optnone attributes (the backend's debug handler is a no-op otherwise).
Cc1Args.push_back("-mllvm");
Cc1Args.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
",+SPV_INTEL_subgroups"
",+SPV_KHR_bit_instructions"
- ",+SPV_EXT_shader_atomic_float_add");
+ ",+SPV_EXT_shader_atomic_float_add"
+ ",+SPV_KHR_non_semantic_info"
+ ",+SPV_INTEL_optnone");
Cc1Args.push_back(TempFile);
Cc1Args.push_back("-o");
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index 7ca217aabf545..3a262a2ce6002 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -93,7 +93,7 @@
// CHIPSTAR: {{".*clang.*"}} "-cc1" "-triple" "spirv64-unknown-chipstar"
// CHIPSTAR-SAME: "-emit-obj"
-// CHIPSTAR-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
+// CHIPSTAR-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add,+SPV_KHR_non_semantic_info,+SPV_INTEL_optnone"
// CHIPSTAR-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
// RUN: %clang -### --no-default-config -o %t.dummy.img \
@@ -110,7 +110,7 @@
// 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_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
+// CHIPSTAR-SUBARCH-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add,+SPV_KHR_non_semantic_info,+SPV_INTEL_optnone"
// CHIPSTAR-SUBARCH-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
// -fno-integrated-objemitter selects the external llvm-spirv translator.
@@ -125,6 +125,8 @@
// CHIPSTAR-XTOR: {{".*llvm-spirv.*"}} "--spirv-max-version=1.2"
// CHIPSTAR-XTOR-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
+// CHIPSTAR-XTOR-SAME: "--spirv-ext=+SPV_KHR_non_semantic_info,+SPV_INTEL_optnone"
+// CHIPSTAR-XTOR-SAME: "--spirv-debug-info-version=nonsemantic-shader-200"
// CHIPSTAR-XTOR-SAME: [[LOWER_BC]] "-o" "{{.*img}}"
// Check unknown linker options are ignored - such as ones that are targeted at
@@ -155,25 +157,11 @@
// VERSIONED: {{.*}}llvm-spirv-[[VERSION]]
//-----------------------------------------------------------------------------
-// Check that -g preserves device debug info by requesting the
-// NonSemantic.Shader.DebugInfo form (and the extension it depends on) from the
-// translator, and that no debug flags are emitted without -g.
+// The debug-info flags are passed regardless of -g (covered by the
+// CHIPSTAR-XTOR checks above): in RDC-mode links this job runs in a clang
+// invoked by clang-linker-wrapper where the original -g is not visible, but
+// debug info travels in the bitcode and the flags are no-ops without it.
// RUN: %clang -### --no-default-config -g -o %t.dummy.img \
// RUN: --target=spirv64-unknown-chipstar %t.dummy.o \
// RUN: --hip-path="%S/Inputs/hipspv" -fno-integrated-objemitter \
-// RUN: 2>&1 | FileCheck %s --check-prefix=DEBUG
-
-// DEBUG: {{".*llvm-spirv"}} "--spirv-max-version=1.2"
-// DEBUG-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
-// DEBUG-SAME: "--spirv-ext=+SPV_KHR_non_semantic_info,+SPV_INTEL_optnone"
-// DEBUG-SAME: "--spirv-debug-info-version=nonsemantic-shader-200"
-
-// RUN: %clang -### --no-default-config -o %t.dummy.img \
-// RUN: --target=spirv64-unknown-chipstar %t.dummy.o \
-// RUN: --hip-path="%S/Inputs/hipspv" -fno-integrated-objemitter \
-// RUN: 2>&1 | FileCheck %s --check-prefix=NODEBUG
-
-// NODEBUG: {{".*llvm-spirv"}}
-// NODEBUG-NOT: SPV_KHR_non_semantic_info
-// NODEBUG-NOT: SPV_INTEL_optnone
-// NODEBUG-NOT: spirv-debug-info-version
+// RUN: 2>&1 | FileCheck %s --check-prefix=CHIPSTAR-XTOR -DHIP_PATH=%S/Inputs/hipspv
>From ecb1bb8f65f0945e5764cc7d6ec78b625c990af2 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Tue, 28 Jul 2026 10:04:03 +0300
Subject: [PATCH 19/20] [HIPSPV] Clarify that SPV_INTEL_optnone is not tied to
debug info
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index f82af83993849..e67b1541ef51b 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -126,9 +126,11 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
// These flags are passed unconditionally instead of gating on -g: in
// RDC-mode links this job runs in a clang invoked by
// clang-linker-wrapper where the original -g is not visible, but the
- // debug info itself travels in the bitcode. The flags only allow
- // extensions and select the debug info form; with debug-info-free
- // bitcode the output is unchanged.
+ // debug info itself travels in the bitcode. SPV_KHR_non_semantic_info
+ // and the debug info version only take effect when the bitcode carries
+ // debug info. SPV_INTEL_optnone is not tied to debug info: clang emits
+ // optnone at -O0 even without -g, and the emitter needs the extension
+ // allowed to encode it.
TrArgs.push_back("--spirv-ext=+SPV_KHR_non_semantic_info"
",+SPV_INTEL_optnone");
TrArgs.push_back("--spirv-debug-info-version=nonsemantic-shader-200");
>From c9ce4f2efc8ba45de330d80b013d064228648dfd Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Tue, 28 Jul 2026 10:04:04 +0300
Subject: [PATCH 20/20] [HIPSPV] Guard hipspv-pass-plugin.hip with
spirv-registered-target
---
clang/test/Driver/hipspv-pass-plugin.hip | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang/test/Driver/hipspv-pass-plugin.hip b/clang/test/Driver/hipspv-pass-plugin.hip
index d13894ed50c9b..ae6194b74b2bf 100644
--- a/clang/test/Driver/hipspv-pass-plugin.hip
+++ b/clang/test/Driver/hipspv-pass-plugin.hip
@@ -1,3 +1,4 @@
+// REQUIRES: spirv-registered-target
// UNSUPPORTED: system-windows
// RUN: %clang -### -target x86_64-linux-gnu --offload=spirv64 \
More information about the cfe-commits
mailing list