[HIPSPV] Add in-tree SPIR-V backend support for chipStar#186972
[HIPSPV] Add in-tree SPIR-V backend support for chipStar#186972
Conversation
|
@llvm/pr-subscribers-clang-driver Author: Paulius Velesko (pvelesko) ChangesSummarychipStar (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 This patch adds native in-tree SPIR-V backend support for chipStar targets ( ChangesHIPSPV old driver (
New offload driver (
SPIR-V toolchain (
SPIR-V backend (
Test plan
Patch is 21.95 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/186972.diff 6 Files Affected:
diff --git a/clang/include/clang/Basic/AlignedAllocation.h b/clang/include/clang/Basic/AlignedAllocation.h
index ac26eb4a276da..9b84d07286d52 100644
--- a/clang/include/clang/Basic/AlignedAllocation.h
+++ b/clang/include/clang/Basic/AlignedAllocation.h
@@ -35,6 +35,8 @@ inline llvm::VersionTuple alignedAllocMinVersion(llvm::Triple::OSType OS) {
return llvm::VersionTuple(4U);
case llvm::Triple::ZOS:
return llvm::VersionTuple(); // All z/OS versions have no support.
+ case llvm::Triple::ChipStar:
+ return llvm::VersionTuple(); // No version constraint for device targets.
}
llvm_unreachable("Unexpected OS");
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 8bdb7ab042b2b..70f831c3ade87 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -72,10 +72,56 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
tools::constructLLVMLinkCommand(C, *this, JA, Inputs, LinkArgs, Output, Args,
TempFile);
- // Post-link HIP lowering.
+ auto T = getToolChain().getTriple();
+
+ if (T.getOS() == llvm::Triple::ChipStar) {
+ // chipStar: run HipSpvPasses via opt, then use the in-tree SPIR-V backend
+ // for codegen (replaces the external llvm-spirv translator).
+
+ // Run HipSpvPasses plugin via opt (must run on LLVM IR before
+ // the SPIR-V backend lowers to MIR).
+ auto PassPluginPath = findPassPlugin(C.getDriver(), Args);
+ if (!PassPluginPath.empty()) {
+ const char *PassPathCStr = C.getArgs().MakeArgString(PassPluginPath);
+ const char *OptOutput = HIP::getTempFile(C, Name + "-lower", "bc");
+ ArgStringList OptArgs{TempFile, "-load-pass-plugin",
+ PassPathCStr, "-passes=hip-post-link-passes",
+ "-o", OptOutput};
+ const char *Opt =
+ Args.MakeArgString(getToolChain().GetProgramPath("opt"));
+ C.addCommand(std::make_unique<Command>(
+ JA, *this, ResponseFileSupport::None(), Opt, OptArgs, Inputs,
+ Output));
+ TempFile = OptOutput;
+ }
- // Run LLVM IR passes to lower/expand/emulate HIP code that does not translate
- // to SPIR-V (E.g. dynamic shared memory).
+ // Compile processed bitcode to SPIR-V using the in-tree backend.
+ ArgStringList ClangArgs;
+ ClangArgs.push_back("--no-default-config");
+ ClangArgs.push_back("-c");
+ ClangArgs.push_back(
+ C.getArgs().MakeArgString("--target=" + T.getTriple()));
+
+ ClangArgs.push_back("-mllvm");
+ ClangArgs.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
+ ",+SPV_INTEL_subgroups"
+ ",+SPV_EXT_relaxed_printf_string_address_space"
+ ",+SPV_KHR_bit_instructions"
+ ",+SPV_EXT_shader_atomic_float_add");
+
+ ClangArgs.push_back(TempFile);
+ ClangArgs.push_back("-o");
+ ClangArgs.push_back(Output.getFilename());
+
+ const char *Clang =
+ C.getArgs().MakeArgString(C.getDriver().getClangProgramPath());
+ C.addCommand(std::make_unique<Command>(
+ JA, *this, ResponseFileSupport::None(), Clang, ClangArgs, Inputs,
+ Output));
+ return;
+ }
+
+ // Non-chipStar: run HIP passes via opt, then translate with llvm-spirv.
auto PassPluginPath = findPassPlugin(C.getDriver(), Args);
if (!PassPluginPath.empty()) {
const char *PassPathCStr = C.getArgs().MakeArgString(PassPluginPath);
@@ -89,27 +135,11 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
TempFile = OptOutput;
}
- // Emit SPIR-V binary.
+ // Emit SPIR-V binary via llvm-spirv translator (non-chipStar targets).
llvm::opt::ArgStringList TrArgs;
- auto T = getToolChain().getTriple();
- bool HasNoSubArch = T.getSubArch() == llvm::Triple::NoSubArch;
- if (T.getOS() == llvm::Triple::ChipStar) {
- // chipStar needs 1.2 for supporting warp-level primitivies via sub-group
- // extensions. Strictly put we'd need 1.3 for the standard non-extension
- // shuffle operations, but it's not supported by any backend driver of the
- // chipStar.
- if (HasNoSubArch)
- TrArgs.push_back("--spirv-max-version=1.2");
- TrArgs.push_back("--spirv-ext=-all"
- // Needed for experimental indirect call support.
- ",+SPV_INTEL_function_pointers"
- // Needed for shuffles below SPIR-V 1.3
- ",+SPV_INTEL_subgroups");
- } else {
- if (HasNoSubArch)
- TrArgs.push_back("--spirv-max-version=1.1");
- TrArgs.push_back("--spirv-ext=+all");
- }
+ if (T.getSubArch() == llvm::Triple::NoSubArch)
+ TrArgs.push_back("--spirv-max-version=1.1");
+ TrArgs.push_back("--spirv-ext=+all");
InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
@@ -152,14 +182,17 @@ HIPSPVToolChain::HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple,
void HIPSPVToolChain::addClangTargetOptions(
const llvm::opt::ArgList &DriverArgs, llvm::opt::ArgStringList &CC1Args,
Action::OffloadKind DeviceOffloadingKind) const {
-
if (!HostTC) {
assert(DeviceOffloadingKind == Action::OFK_None &&
"Need host toolchain for offloading!");
return;
}
- HostTC->addClangTargetOptions(DriverArgs, CC1Args, DeviceOffloadingKind);
+ // NOTE: Unlike other HIP toolchains, we do NOT delegate to
+ // HostTC.addClangTargetOptions() here. On macOS (Darwin), the host toolchain
+ // adds flags like -faligned-alloc-unavailable that are specific to macOS
+ // libc++ and break SPIR-V device compilation. SPIR-V device code doesn't
+ // have the same stdlib limitations as the host.
assert(DeviceOffloadingKind == Action::OFK_HIP &&
"Only HIP offloading kinds are supported for GPUs.");
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index a59bd05cac0cf..e6a04e00af87b 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -185,7 +185,8 @@ SPIRVToolChain::SPIRVToolChain(const Driver &D, const llvm::Triple &Triple,
: ToolChain(D, Triple, Args) {
// TODO: Revisit need/use of --sycl-link option once SYCL toolchain is
// available and SYCL linking support is moved there.
- NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || D.isUsingLTO();
+ NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || D.isUsingLTO() ||
+ Triple.getOS() == llvm::Triple::ChipStar;
// Lookup binaries into the driver directory.
getProgramPaths().push_back(getDriver().Dir);
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index ae8d65313abfb..9af64ca4bdaa4 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -59,17 +59,50 @@
// RUN: llvm-offload-binary -o %t.dev.out \
// RUN: --image=file=%t.dev.bc,kind=hip,triple=spirv64-unknown-chipstar,arch=generic
-// RUN: clang-linker-wrapper --dry-run \
+// Test the in-tree SPIR-V backend path (no llvm-spirv available).
+// Run from a directory that doesn't contain llvm-spirv and use
+// --no-canonical-prefixes so getExecutableDir() looks there instead of
+// the build bin dir. Empty PATH ensures PATH lookup also fails.
+// RUN: mkdir -p %t/no-spirv %t/empty
+// RUN: ln -sf clang-linker-wrapper %t/no-spirv/clang-linker-wrapper
+// RUN: env "PATH=%t/empty" %t/no-spirv/clang-linker-wrapper \
+// RUN: --no-canonical-prefixes --dry-run \
// RUN: --device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
// RUN: --host-triple=spirv64-unknown-chipstar \
// RUN: --linker-path=clang-offload-bundler \
// RUN: --emit-fatbin-only -o /dev/null %t.dev.out \
// RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER -DHIP_PATH=%S/Inputs/hipspv
-// WRAPPER: clang{{.*}}" --no-default-config -o {{[^ ]*.img}}
+// The linker wrapper runs opt (HipSpvPasses) then uses the in-tree SPIR-V
+// backend when llvm-spirv is not available.
+// WRAPPER: "{{.*}}opt" {{.*}}-load-pass-plugin
+// WRAPPER-SAME: {{.*}}libLLVMHipSpvPasses.so
+// WRAPPER-SAME: -passes=hip-post-link-passes
+
+// WRAPPER: "{{.*}}clang{{.*}}" --no-default-config -o
// WRAPPER-SAME: --target=spirv64-unknown-chipstar
-// WRAPPER-SAME: {{[^ ]*.o}}
-// WRAPPER-SAME: --hip-path=[[HIP_PATH]]
+// WRAPPER-SAME: -mllvm -spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add
+// WRAPPER-SAME: -c -x ir
+
+// Test the llvm-spirv translator path (llvm-spirv available in executable dir).
+// Place a fake llvm-spirv next to the clang-linker-wrapper symlink.
+// RUN: mkdir -p %t/with-spirv
+// RUN: ln -sf clang-linker-wrapper %t/with-spirv/clang-linker-wrapper
+// RUN: touch %t/with-spirv/llvm-spirv && chmod +x %t/with-spirv/llvm-spirv
+// RUN: env "PATH=%t/empty" %t/with-spirv/clang-linker-wrapper \
+// RUN: --no-canonical-prefixes --dry-run \
+// RUN: --device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
+// RUN: --host-triple=spirv64-unknown-chipstar \
+// RUN: --linker-path=clang-offload-bundler \
+// RUN: --emit-fatbin-only -o /dev/null %t.dev.out \
+// RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER-TR
+
+// WRAPPER-TR: "{{.*}}opt" {{.*}}-load-pass-plugin
+// WRAPPER-TR-SAME: {{.*}}libLLVMHipSpvPasses.so
+// WRAPPER-TR-SAME: -passes=hip-post-link-passes
+
+// WRAPPER-TR: "{{.*}}llvm-spirv" {{.*}}--spirv-max-version=1.2
+// WRAPPER-TR-SAME: --spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups
// RUN: touch %t.dummy.o
// RUN: %clang -### --no-default-config -o %t.dummy.img \
@@ -84,8 +117,9 @@
// CHIPSTAR-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
// CHIPSTAR-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
-// CHIPSTAR: {{".*llvm-spirv"}} "--spirv-max-version=1.2"
-// CHIPSTAR-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+// CHIPSTAR: {{".*clang.*"}} "--no-default-config" "-c"
+// CHIPSTAR-SAME: "--target=spirv64-unknown-chipstar"
+// CHIPSTAR-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
// CHIPSTAR-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
// RUN: %clang -### --no-default-config -o %t.dummy.img \
@@ -100,8 +134,9 @@
// CHIPSTAR-SUBARCH-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
// CHIPSTAR-SUBARCH-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
-// CHIPSTAR-SUBARCH: {{".*llvm-spirv"}}
-// CHIPSTAR-SUBARCH-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+// CHIPSTAR-SUBARCH: {{".*clang.*"}} "--no-default-config" "-c"
+// CHIPSTAR-SUBARCH-SAME: "--target=spirv64v1.3-unknown-chipstar"
+// CHIPSTAR-SUBARCH-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
// CHIPSTAR-SUBARCH-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
//-----------------------------------------------------------------------------
@@ -115,9 +150,4 @@
// RUN: | FileCheck -DVERSION=%llvm-version-major \
// RUN: --check-prefix=VERSIONED %s
-// RUN: env "PATH=%t/versioned" %clang -### --no-default-config \
-// RUN: -o %t.dummy.img --target=spirv64-unknown-chipstar %t.dummy.o \
-// RUN: --hip-path="%S/Inputs/hipspv" -o /dev/null 2>&1 \
-// RUN: | FileCheck -DVERSION=%llvm-version-major --check-prefix=VERSIONED %s
-
// VERSIONED: {{.*}}llvm-spirv-[[VERSION]]
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index 9e24a9c26d897..2522d0ac71df1 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -125,6 +125,9 @@ static StringRef ExecutableName;
/// Binary path for the CUDA installation.
static std::string CudaBinaryPath;
+/// HIP installation path.
+static std::string HipPath;
+
/// Mutex lock to protect writes to shared TempFiles in parallel.
static std::mutex TempFilesMutex;
@@ -479,9 +482,25 @@ fatbinary(ArrayRef<std::tuple<StringRef, StringRef, StringRef>> InputFiles,
SmallVector<StringRef> Targets = {
Saver.save("-targets=host-" + HostTriple.normalize())};
for (const auto &[File, TripleRef, Arch] : InputFiles) {
- std::string NormalizedTriple =
- normalizeForBundler(Triple(TripleRef), !Arch.empty());
- Targets.push_back(Saver.save("hip-" + NormalizedTriple + "-" + Arch));
+ llvm::Triple T(TripleRef);
+ // For SPIR-V targets, derive arch from triple if not provided
+ StringRef EffectiveArch = Arch;
+ if (EffectiveArch.empty() && T.isSPIRV()) {
+ EffectiveArch = T.getArchName();
+ }
+ StringRef BundleID;
+ if (EffectiveArch == "amdgcnspirv") {
+ BundleID = Saver.save("hip-spirv64-amd-amdhsa--" + EffectiveArch);
+ } else if (T.isSPIRV()) {
+ // ChipStar and other SPIR-V HIP targets: use hip-spirv64-<vendor>-<os>--<arch>
+ BundleID = Saver.save("hip-spirv64-" + T.getVendorName() + "-" +
+ T.getOSName() + "--" + EffectiveArch);
+ } else {
+ std::string NormalizedTriple =
+ normalizeForBundler(T, !Arch.empty());
+ BundleID = Saver.save("hip-" + NormalizedTriple + "-" + Arch);
+ }
+ Targets.push_back(BundleID);
}
CmdArgs.push_back(Saver.save(llvm::join(Targets, ",")));
@@ -554,7 +573,161 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
if (!Triple.isNVPTX() && !Triple.isSPIRV())
CmdArgs.push_back("-Wl,--no-undefined");
- for (StringRef InputFile : InputFiles)
+ // For non-chipStar SPIR-V targets, pass the HIP path to clang so it can
+ // find resources. For chipStar, passes are run via opt separately, so the
+ // inner clang doesn't need --hip-path (it just compiles IR to SPIR-V).
+ if (Triple.isSPIRV() && !HipPath.empty() &&
+ Triple.getOS() != llvm::Triple::ChipStar)
+ CmdArgs.push_back(Args.MakeArgString("--hip-path=" + HipPath));
+
+ // For chipStar targets: llvm-link (merge) → opt (HipSpvPasses) → clang
+ // (SPIR-V backend). The passes must operate on LLVM IR before the backend
+ // lowers to MIR, and all TU bitcode must be merged first for RDC support.
+ SmallVector<StringRef, 16> ProcessedInputFiles;
+ if (Triple.isSPIRV() && Triple.getOS() == llvm::Triple::ChipStar) {
+ // Step 1: Merge all input bitcode files with llvm-link (needed for RDC
+ // where functions can be defined across translation units).
+ StringRef MergedFile;
+ if (InputFiles.size() > 1) {
+ Expected<std::string> LinkPath =
+ findProgram("llvm-link", {getExecutableDir("llvm-link")});
+ if (!LinkPath)
+ return LinkPath.takeError();
+
+ auto LinkOutOrErr = createOutputFile(
+ sys::path::filename(ExecutableName) + ".merged", "bc");
+ if (!LinkOutOrErr)
+ return LinkOutOrErr.takeError();
+
+ SmallVector<StringRef, 16> LinkArgs{*LinkPath};
+ for (StringRef F : InputFiles)
+ LinkArgs.push_back(F);
+ LinkArgs.push_back("-o");
+ LinkArgs.push_back(*LinkOutOrErr);
+
+ if (Error Err = executeCommands(*LinkPath, LinkArgs))
+ return std::move(Err);
+
+ MergedFile = *LinkOutOrErr;
+ } else {
+ MergedFile = InputFiles[0];
+ }
+
+ // Step 2: Run HipSpvPasses via opt on the merged bitcode.
+ SmallString<128> PluginPath;
+ if (!HipPath.empty()) {
+ PluginPath.assign(HipPath);
+ sys::path::append(PluginPath, "lib", "libLLVMHipSpvPasses.so");
+ if (!sys::fs::exists(PluginPath)) {
+ PluginPath.assign(HipPath);
+ sys::path::append(PluginPath, "lib", "llvm",
+ "libLLVMHipSpvPasses.so");
+ }
+ if (!sys::fs::exists(PluginPath))
+ PluginPath.clear();
+ }
+
+ StringRef OptOutputFile = MergedFile;
+ if (!PluginPath.empty()) {
+ Expected<std::string> OptPath =
+ findProgram("opt", {getExecutableDir("opt")});
+ if (!OptPath)
+ return OptPath.takeError();
+
+ auto OptOutOrErr = createOutputFile(
+ sys::path::filename(ExecutableName) + ".lowered", "bc");
+ if (!OptOutOrErr)
+ return OptOutOrErr.takeError();
+
+ SmallVector<StringRef, 16> OptArgs{
+ *OptPath,
+ MergedFile,
+ "-load-pass-plugin",
+ Args.MakeArgString(PluginPath),
+ "-passes=hip-post-link-passes",
+ "-o",
+ *OptOutOrErr,
+ };
+
+ if (Error Err = executeCommands(*OptPath, OptArgs))
+ return std::move(Err);
+
+ OptOutputFile = *OptOutOrErr;
+ }
+
+ // Step 3: Convert processed bitcode to SPIR-V.
+ // Check if llvm-spirv translator is available. If so, use it directly;
+ // otherwise use the in-tree SPIR-V backend via clang.
+ // Use sys::findProgramByName() instead of findProgram() to avoid the
+ // dry-run fallback that always "finds" programs by returning their name.
+ bool UseLLVMSpirvTranslator = false;
+ std::string LLVMSpirvPathStr;
+ {
+ ErrorOr<std::string> LLVMSpirvPath =
+ sys::findProgramByName("llvm-spirv", {getExecutableDir("llvm-spirv")});
+ if (!LLVMSpirvPath)
+ LLVMSpirvPath = sys::findProgramByName("llvm-spirv");
+ if (LLVMSpirvPath) {
+ LLVMSpirvPathStr = *LLVMSpirvPath;
+ UseLLVMSpirvTranslator = true;
+ }
+ }
+ if (UseLLVMSpirvTranslator) {
+ // Use llvm-spirv translator: BC → SPIR-V binary directly.
+ auto SpirvOutOrErr = createOutputFile(
+ sys::path::filename(ExecutableName) + ".spirv", "spv");
+ if (!SpirvOutOrErr)
+ return SpirvOutOrErr.takeError();
+
+ // Derive SPIR-V max version from the triple's sub-arch.
+ // chipStar needs v1.2 for sub-group extensions by default.
+ std::string MaxVerArg;
+ if (Triple.getSubArch() == llvm::Triple::SPIRVSubArch_v13)
+ MaxVerArg = "--spirv-max-version=1.3";
+ else if (Triple.getSubArch() == llvm::Triple::SPIRVSubArch_v12 ||
+ Triple.getOS() == llvm::Triple::ChipStar)
+ MaxVerArg = "--spirv-max-version=1.2";
+ else
+ MaxVerArg = "--spirv-max-version=1.1";
+
+ SmallVector<StringRef, 16> TranslateArgs{
+ LLVMSpirvPathStr,
+ OptOutputFile,
+ Args.MakeArgString(MaxVerArg),
+ "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups",
+ "-o",
+ *SpirvOutOrErr,
+ };
+
+ if (Error Err = executeCommands(LLVMSpirvPathStr, TranslateArgs))
+ return std::move(Err);
+
+ // The SPIR-V binary is the final output; skip the inner clang
+ // compilation by returning it directly as the linked image.
+ return *SpirvOutOrErr;
+ }
+
+ // No llvm-spirv available; use the in-tree SPIR-V backend via clang.
+ ProcessedInputFiles.push_back(OptOutputFile);
+ CmdArgs.push_back("-mllvm");
+ CmdArgs.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
+ ",+SPV_INTEL_subgroups"
+ ",+SPV_EXT_relaxed_printf_string_address_space"
+ ",+SPV_KHR_bit_instructions"
+ ",+SPV_EXT_shader_atomic_float_add");
+ // The extracted bitcode files have a .o extension which causes the driver
+ // to treat them as pre-compiled objects, skipping the Backend compilation
+ // step. Force the input language to LLVM IR so the SPIR-V backend runs.
+ // Use -c to skip the link phase — the SPIR-V backend output is the final
+ // binary; hitting HIPSPV::Linker would re-run the full pipeline.
+ CmdArgs.push_back("-c");
+ CmdArgs.push_back("-x");
+ CmdArgs.push_back("ir");
+ } else {
+ ProcessedInputFiles.append(InputFiles.begin(), InputFiles.end());
+ }
+
+ for (StringRef InputFile : ProcessedInputFiles)
CmdArgs.push_back(InputFile);
// If this is CPU offloading we copy the input libraries.
@@ -613,8 +786,14 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
for (StringRef Arg : Args.getA...
[truncated]
|
|
@llvm/pr-subscribers-backend-spir-v Author: Paulius Velesko (pvelesko) ChangesSummarychipStar (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 This patch adds native in-tree SPIR-V backend support for chipStar targets ( ChangesHIPSPV old driver (
New offload driver (
SPIR-V toolchain (
SPIR-V backend (
Test plan
Patch is 21.95 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/186972.diff 6 Files Affected:
diff --git a/clang/include/clang/Basic/AlignedAllocation.h b/clang/include/clang/Basic/AlignedAllocation.h
index ac26eb4a276da..9b84d07286d52 100644
--- a/clang/include/clang/Basic/AlignedAllocation.h
+++ b/clang/include/clang/Basic/AlignedAllocation.h
@@ -35,6 +35,8 @@ inline llvm::VersionTuple alignedAllocMinVersion(llvm::Triple::OSType OS) {
return llvm::VersionTuple(4U);
case llvm::Triple::ZOS:
return llvm::VersionTuple(); // All z/OS versions have no support.
+ case llvm::Triple::ChipStar:
+ return llvm::VersionTuple(); // No version constraint for device targets.
}
llvm_unreachable("Unexpected OS");
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 8bdb7ab042b2b..70f831c3ade87 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -72,10 +72,56 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
tools::constructLLVMLinkCommand(C, *this, JA, Inputs, LinkArgs, Output, Args,
TempFile);
- // Post-link HIP lowering.
+ auto T = getToolChain().getTriple();
+
+ if (T.getOS() == llvm::Triple::ChipStar) {
+ // chipStar: run HipSpvPasses via opt, then use the in-tree SPIR-V backend
+ // for codegen (replaces the external llvm-spirv translator).
+
+ // Run HipSpvPasses plugin via opt (must run on LLVM IR before
+ // the SPIR-V backend lowers to MIR).
+ auto PassPluginPath = findPassPlugin(C.getDriver(), Args);
+ if (!PassPluginPath.empty()) {
+ const char *PassPathCStr = C.getArgs().MakeArgString(PassPluginPath);
+ const char *OptOutput = HIP::getTempFile(C, Name + "-lower", "bc");
+ ArgStringList OptArgs{TempFile, "-load-pass-plugin",
+ PassPathCStr, "-passes=hip-post-link-passes",
+ "-o", OptOutput};
+ const char *Opt =
+ Args.MakeArgString(getToolChain().GetProgramPath("opt"));
+ C.addCommand(std::make_unique<Command>(
+ JA, *this, ResponseFileSupport::None(), Opt, OptArgs, Inputs,
+ Output));
+ TempFile = OptOutput;
+ }
- // Run LLVM IR passes to lower/expand/emulate HIP code that does not translate
- // to SPIR-V (E.g. dynamic shared memory).
+ // Compile processed bitcode to SPIR-V using the in-tree backend.
+ ArgStringList ClangArgs;
+ ClangArgs.push_back("--no-default-config");
+ ClangArgs.push_back("-c");
+ ClangArgs.push_back(
+ C.getArgs().MakeArgString("--target=" + T.getTriple()));
+
+ ClangArgs.push_back("-mllvm");
+ ClangArgs.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
+ ",+SPV_INTEL_subgroups"
+ ",+SPV_EXT_relaxed_printf_string_address_space"
+ ",+SPV_KHR_bit_instructions"
+ ",+SPV_EXT_shader_atomic_float_add");
+
+ ClangArgs.push_back(TempFile);
+ ClangArgs.push_back("-o");
+ ClangArgs.push_back(Output.getFilename());
+
+ const char *Clang =
+ C.getArgs().MakeArgString(C.getDriver().getClangProgramPath());
+ C.addCommand(std::make_unique<Command>(
+ JA, *this, ResponseFileSupport::None(), Clang, ClangArgs, Inputs,
+ Output));
+ return;
+ }
+
+ // Non-chipStar: run HIP passes via opt, then translate with llvm-spirv.
auto PassPluginPath = findPassPlugin(C.getDriver(), Args);
if (!PassPluginPath.empty()) {
const char *PassPathCStr = C.getArgs().MakeArgString(PassPluginPath);
@@ -89,27 +135,11 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
TempFile = OptOutput;
}
- // Emit SPIR-V binary.
+ // Emit SPIR-V binary via llvm-spirv translator (non-chipStar targets).
llvm::opt::ArgStringList TrArgs;
- auto T = getToolChain().getTriple();
- bool HasNoSubArch = T.getSubArch() == llvm::Triple::NoSubArch;
- if (T.getOS() == llvm::Triple::ChipStar) {
- // chipStar needs 1.2 for supporting warp-level primitivies via sub-group
- // extensions. Strictly put we'd need 1.3 for the standard non-extension
- // shuffle operations, but it's not supported by any backend driver of the
- // chipStar.
- if (HasNoSubArch)
- TrArgs.push_back("--spirv-max-version=1.2");
- TrArgs.push_back("--spirv-ext=-all"
- // Needed for experimental indirect call support.
- ",+SPV_INTEL_function_pointers"
- // Needed for shuffles below SPIR-V 1.3
- ",+SPV_INTEL_subgroups");
- } else {
- if (HasNoSubArch)
- TrArgs.push_back("--spirv-max-version=1.1");
- TrArgs.push_back("--spirv-ext=+all");
- }
+ if (T.getSubArch() == llvm::Triple::NoSubArch)
+ TrArgs.push_back("--spirv-max-version=1.1");
+ TrArgs.push_back("--spirv-ext=+all");
InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
@@ -152,14 +182,17 @@ HIPSPVToolChain::HIPSPVToolChain(const Driver &D, const llvm::Triple &Triple,
void HIPSPVToolChain::addClangTargetOptions(
const llvm::opt::ArgList &DriverArgs, llvm::opt::ArgStringList &CC1Args,
Action::OffloadKind DeviceOffloadingKind) const {
-
if (!HostTC) {
assert(DeviceOffloadingKind == Action::OFK_None &&
"Need host toolchain for offloading!");
return;
}
- HostTC->addClangTargetOptions(DriverArgs, CC1Args, DeviceOffloadingKind);
+ // NOTE: Unlike other HIP toolchains, we do NOT delegate to
+ // HostTC.addClangTargetOptions() here. On macOS (Darwin), the host toolchain
+ // adds flags like -faligned-alloc-unavailable that are specific to macOS
+ // libc++ and break SPIR-V device compilation. SPIR-V device code doesn't
+ // have the same stdlib limitations as the host.
assert(DeviceOffloadingKind == Action::OFK_HIP &&
"Only HIP offloading kinds are supported for GPUs.");
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index a59bd05cac0cf..e6a04e00af87b 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -185,7 +185,8 @@ SPIRVToolChain::SPIRVToolChain(const Driver &D, const llvm::Triple &Triple,
: ToolChain(D, Triple, Args) {
// TODO: Revisit need/use of --sycl-link option once SYCL toolchain is
// available and SYCL linking support is moved there.
- NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || D.isUsingLTO();
+ NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) || D.isUsingLTO() ||
+ Triple.getOS() == llvm::Triple::ChipStar;
// Lookup binaries into the driver directory.
getProgramPaths().push_back(getDriver().Dir);
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index ae8d65313abfb..9af64ca4bdaa4 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -59,17 +59,50 @@
// RUN: llvm-offload-binary -o %t.dev.out \
// RUN: --image=file=%t.dev.bc,kind=hip,triple=spirv64-unknown-chipstar,arch=generic
-// RUN: clang-linker-wrapper --dry-run \
+// Test the in-tree SPIR-V backend path (no llvm-spirv available).
+// Run from a directory that doesn't contain llvm-spirv and use
+// --no-canonical-prefixes so getExecutableDir() looks there instead of
+// the build bin dir. Empty PATH ensures PATH lookup also fails.
+// RUN: mkdir -p %t/no-spirv %t/empty
+// RUN: ln -sf clang-linker-wrapper %t/no-spirv/clang-linker-wrapper
+// RUN: env "PATH=%t/empty" %t/no-spirv/clang-linker-wrapper \
+// RUN: --no-canonical-prefixes --dry-run \
// RUN: --device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
// RUN: --host-triple=spirv64-unknown-chipstar \
// RUN: --linker-path=clang-offload-bundler \
// RUN: --emit-fatbin-only -o /dev/null %t.dev.out \
// RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER -DHIP_PATH=%S/Inputs/hipspv
-// WRAPPER: clang{{.*}}" --no-default-config -o {{[^ ]*.img}}
+// The linker wrapper runs opt (HipSpvPasses) then uses the in-tree SPIR-V
+// backend when llvm-spirv is not available.
+// WRAPPER: "{{.*}}opt" {{.*}}-load-pass-plugin
+// WRAPPER-SAME: {{.*}}libLLVMHipSpvPasses.so
+// WRAPPER-SAME: -passes=hip-post-link-passes
+
+// WRAPPER: "{{.*}}clang{{.*}}" --no-default-config -o
// WRAPPER-SAME: --target=spirv64-unknown-chipstar
-// WRAPPER-SAME: {{[^ ]*.o}}
-// WRAPPER-SAME: --hip-path=[[HIP_PATH]]
+// WRAPPER-SAME: -mllvm -spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add
+// WRAPPER-SAME: -c -x ir
+
+// Test the llvm-spirv translator path (llvm-spirv available in executable dir).
+// Place a fake llvm-spirv next to the clang-linker-wrapper symlink.
+// RUN: mkdir -p %t/with-spirv
+// RUN: ln -sf clang-linker-wrapper %t/with-spirv/clang-linker-wrapper
+// RUN: touch %t/with-spirv/llvm-spirv && chmod +x %t/with-spirv/llvm-spirv
+// RUN: env "PATH=%t/empty" %t/with-spirv/clang-linker-wrapper \
+// RUN: --no-canonical-prefixes --dry-run \
+// RUN: --device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
+// RUN: --host-triple=spirv64-unknown-chipstar \
+// RUN: --linker-path=clang-offload-bundler \
+// RUN: --emit-fatbin-only -o /dev/null %t.dev.out \
+// RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER-TR
+
+// WRAPPER-TR: "{{.*}}opt" {{.*}}-load-pass-plugin
+// WRAPPER-TR-SAME: {{.*}}libLLVMHipSpvPasses.so
+// WRAPPER-TR-SAME: -passes=hip-post-link-passes
+
+// WRAPPER-TR: "{{.*}}llvm-spirv" {{.*}}--spirv-max-version=1.2
+// WRAPPER-TR-SAME: --spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups
// RUN: touch %t.dummy.o
// RUN: %clang -### --no-default-config -o %t.dummy.img \
@@ -84,8 +117,9 @@
// CHIPSTAR-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
// CHIPSTAR-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
-// CHIPSTAR: {{".*llvm-spirv"}} "--spirv-max-version=1.2"
-// CHIPSTAR-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+// CHIPSTAR: {{".*clang.*"}} "--no-default-config" "-c"
+// CHIPSTAR-SAME: "--target=spirv64-unknown-chipstar"
+// CHIPSTAR-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
// CHIPSTAR-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
// RUN: %clang -### --no-default-config -o %t.dummy.img \
@@ -100,8 +134,9 @@
// CHIPSTAR-SUBARCH-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
// CHIPSTAR-SUBARCH-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
-// CHIPSTAR-SUBARCH: {{".*llvm-spirv"}}
-// CHIPSTAR-SUBARCH-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+// CHIPSTAR-SUBARCH: {{".*clang.*"}} "--no-default-config" "-c"
+// CHIPSTAR-SUBARCH-SAME: "--target=spirv64v1.3-unknown-chipstar"
+// CHIPSTAR-SUBARCH-SAME: "-mllvm" "-spirv-ext=+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups,+SPV_EXT_relaxed_printf_string_address_space,+SPV_KHR_bit_instructions,+SPV_EXT_shader_atomic_float_add"
// CHIPSTAR-SUBARCH-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
//-----------------------------------------------------------------------------
@@ -115,9 +150,4 @@
// RUN: | FileCheck -DVERSION=%llvm-version-major \
// RUN: --check-prefix=VERSIONED %s
-// RUN: env "PATH=%t/versioned" %clang -### --no-default-config \
-// RUN: -o %t.dummy.img --target=spirv64-unknown-chipstar %t.dummy.o \
-// RUN: --hip-path="%S/Inputs/hipspv" -o /dev/null 2>&1 \
-// RUN: | FileCheck -DVERSION=%llvm-version-major --check-prefix=VERSIONED %s
-
// VERSIONED: {{.*}}llvm-spirv-[[VERSION]]
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index 9e24a9c26d897..2522d0ac71df1 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -125,6 +125,9 @@ static StringRef ExecutableName;
/// Binary path for the CUDA installation.
static std::string CudaBinaryPath;
+/// HIP installation path.
+static std::string HipPath;
+
/// Mutex lock to protect writes to shared TempFiles in parallel.
static std::mutex TempFilesMutex;
@@ -479,9 +482,25 @@ fatbinary(ArrayRef<std::tuple<StringRef, StringRef, StringRef>> InputFiles,
SmallVector<StringRef> Targets = {
Saver.save("-targets=host-" + HostTriple.normalize())};
for (const auto &[File, TripleRef, Arch] : InputFiles) {
- std::string NormalizedTriple =
- normalizeForBundler(Triple(TripleRef), !Arch.empty());
- Targets.push_back(Saver.save("hip-" + NormalizedTriple + "-" + Arch));
+ llvm::Triple T(TripleRef);
+ // For SPIR-V targets, derive arch from triple if not provided
+ StringRef EffectiveArch = Arch;
+ if (EffectiveArch.empty() && T.isSPIRV()) {
+ EffectiveArch = T.getArchName();
+ }
+ StringRef BundleID;
+ if (EffectiveArch == "amdgcnspirv") {
+ BundleID = Saver.save("hip-spirv64-amd-amdhsa--" + EffectiveArch);
+ } else if (T.isSPIRV()) {
+ // ChipStar and other SPIR-V HIP targets: use hip-spirv64-<vendor>-<os>--<arch>
+ BundleID = Saver.save("hip-spirv64-" + T.getVendorName() + "-" +
+ T.getOSName() + "--" + EffectiveArch);
+ } else {
+ std::string NormalizedTriple =
+ normalizeForBundler(T, !Arch.empty());
+ BundleID = Saver.save("hip-" + NormalizedTriple + "-" + Arch);
+ }
+ Targets.push_back(BundleID);
}
CmdArgs.push_back(Saver.save(llvm::join(Targets, ",")));
@@ -554,7 +573,161 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
if (!Triple.isNVPTX() && !Triple.isSPIRV())
CmdArgs.push_back("-Wl,--no-undefined");
- for (StringRef InputFile : InputFiles)
+ // For non-chipStar SPIR-V targets, pass the HIP path to clang so it can
+ // find resources. For chipStar, passes are run via opt separately, so the
+ // inner clang doesn't need --hip-path (it just compiles IR to SPIR-V).
+ if (Triple.isSPIRV() && !HipPath.empty() &&
+ Triple.getOS() != llvm::Triple::ChipStar)
+ CmdArgs.push_back(Args.MakeArgString("--hip-path=" + HipPath));
+
+ // For chipStar targets: llvm-link (merge) → opt (HipSpvPasses) → clang
+ // (SPIR-V backend). The passes must operate on LLVM IR before the backend
+ // lowers to MIR, and all TU bitcode must be merged first for RDC support.
+ SmallVector<StringRef, 16> ProcessedInputFiles;
+ if (Triple.isSPIRV() && Triple.getOS() == llvm::Triple::ChipStar) {
+ // Step 1: Merge all input bitcode files with llvm-link (needed for RDC
+ // where functions can be defined across translation units).
+ StringRef MergedFile;
+ if (InputFiles.size() > 1) {
+ Expected<std::string> LinkPath =
+ findProgram("llvm-link", {getExecutableDir("llvm-link")});
+ if (!LinkPath)
+ return LinkPath.takeError();
+
+ auto LinkOutOrErr = createOutputFile(
+ sys::path::filename(ExecutableName) + ".merged", "bc");
+ if (!LinkOutOrErr)
+ return LinkOutOrErr.takeError();
+
+ SmallVector<StringRef, 16> LinkArgs{*LinkPath};
+ for (StringRef F : InputFiles)
+ LinkArgs.push_back(F);
+ LinkArgs.push_back("-o");
+ LinkArgs.push_back(*LinkOutOrErr);
+
+ if (Error Err = executeCommands(*LinkPath, LinkArgs))
+ return std::move(Err);
+
+ MergedFile = *LinkOutOrErr;
+ } else {
+ MergedFile = InputFiles[0];
+ }
+
+ // Step 2: Run HipSpvPasses via opt on the merged bitcode.
+ SmallString<128> PluginPath;
+ if (!HipPath.empty()) {
+ PluginPath.assign(HipPath);
+ sys::path::append(PluginPath, "lib", "libLLVMHipSpvPasses.so");
+ if (!sys::fs::exists(PluginPath)) {
+ PluginPath.assign(HipPath);
+ sys::path::append(PluginPath, "lib", "llvm",
+ "libLLVMHipSpvPasses.so");
+ }
+ if (!sys::fs::exists(PluginPath))
+ PluginPath.clear();
+ }
+
+ StringRef OptOutputFile = MergedFile;
+ if (!PluginPath.empty()) {
+ Expected<std::string> OptPath =
+ findProgram("opt", {getExecutableDir("opt")});
+ if (!OptPath)
+ return OptPath.takeError();
+
+ auto OptOutOrErr = createOutputFile(
+ sys::path::filename(ExecutableName) + ".lowered", "bc");
+ if (!OptOutOrErr)
+ return OptOutOrErr.takeError();
+
+ SmallVector<StringRef, 16> OptArgs{
+ *OptPath,
+ MergedFile,
+ "-load-pass-plugin",
+ Args.MakeArgString(PluginPath),
+ "-passes=hip-post-link-passes",
+ "-o",
+ *OptOutOrErr,
+ };
+
+ if (Error Err = executeCommands(*OptPath, OptArgs))
+ return std::move(Err);
+
+ OptOutputFile = *OptOutOrErr;
+ }
+
+ // Step 3: Convert processed bitcode to SPIR-V.
+ // Check if llvm-spirv translator is available. If so, use it directly;
+ // otherwise use the in-tree SPIR-V backend via clang.
+ // Use sys::findProgramByName() instead of findProgram() to avoid the
+ // dry-run fallback that always "finds" programs by returning their name.
+ bool UseLLVMSpirvTranslator = false;
+ std::string LLVMSpirvPathStr;
+ {
+ ErrorOr<std::string> LLVMSpirvPath =
+ sys::findProgramByName("llvm-spirv", {getExecutableDir("llvm-spirv")});
+ if (!LLVMSpirvPath)
+ LLVMSpirvPath = sys::findProgramByName("llvm-spirv");
+ if (LLVMSpirvPath) {
+ LLVMSpirvPathStr = *LLVMSpirvPath;
+ UseLLVMSpirvTranslator = true;
+ }
+ }
+ if (UseLLVMSpirvTranslator) {
+ // Use llvm-spirv translator: BC → SPIR-V binary directly.
+ auto SpirvOutOrErr = createOutputFile(
+ sys::path::filename(ExecutableName) + ".spirv", "spv");
+ if (!SpirvOutOrErr)
+ return SpirvOutOrErr.takeError();
+
+ // Derive SPIR-V max version from the triple's sub-arch.
+ // chipStar needs v1.2 for sub-group extensions by default.
+ std::string MaxVerArg;
+ if (Triple.getSubArch() == llvm::Triple::SPIRVSubArch_v13)
+ MaxVerArg = "--spirv-max-version=1.3";
+ else if (Triple.getSubArch() == llvm::Triple::SPIRVSubArch_v12 ||
+ Triple.getOS() == llvm::Triple::ChipStar)
+ MaxVerArg = "--spirv-max-version=1.2";
+ else
+ MaxVerArg = "--spirv-max-version=1.1";
+
+ SmallVector<StringRef, 16> TranslateArgs{
+ LLVMSpirvPathStr,
+ OptOutputFile,
+ Args.MakeArgString(MaxVerArg),
+ "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups",
+ "-o",
+ *SpirvOutOrErr,
+ };
+
+ if (Error Err = executeCommands(LLVMSpirvPathStr, TranslateArgs))
+ return std::move(Err);
+
+ // The SPIR-V binary is the final output; skip the inner clang
+ // compilation by returning it directly as the linked image.
+ return *SpirvOutOrErr;
+ }
+
+ // No llvm-spirv available; use the in-tree SPIR-V backend via clang.
+ ProcessedInputFiles.push_back(OptOutputFile);
+ CmdArgs.push_back("-mllvm");
+ CmdArgs.push_back("-spirv-ext=+SPV_INTEL_function_pointers"
+ ",+SPV_INTEL_subgroups"
+ ",+SPV_EXT_relaxed_printf_string_address_space"
+ ",+SPV_KHR_bit_instructions"
+ ",+SPV_EXT_shader_atomic_float_add");
+ // The extracted bitcode files have a .o extension which causes the driver
+ // to treat them as pre-compiled objects, skipping the Backend compilation
+ // step. Force the input language to LLVM IR so the SPIR-V backend runs.
+ // Use -c to skip the link phase — the SPIR-V backend output is the final
+ // binary; hitting HIPSPV::Linker would re-run the full pipeline.
+ CmdArgs.push_back("-c");
+ CmdArgs.push_back("-x");
+ CmdArgs.push_back("ir");
+ } else {
+ ProcessedInputFiles.append(InputFiles.begin(), InputFiles.end());
+ }
+
+ for (StringRef InputFile : ProcessedInputFiles)
CmdArgs.push_back(InputFile);
// If this is CPU offloading we copy the input libraries.
@@ -613,8 +786,14 @@ Expected<StringRef> clang(ArrayRef<StringRef> InputFiles, const ArgList &Args,
for (StringRef Arg : Args.getA...
[truncated]
|
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
ed3a552 to
bbae359
Compare
|
The Test SPIR-V CI failure is pre-existing on main — confirmed by #186992 (no-op baseline PR on main that shows the same 5 pointer/SpecConstantOp test failures). |
bbae359 to
5b7d5d7
Compare
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
5b7d5d7 to
a4a5227
Compare
…rapper Address review feedback on llvm#186972: the chipStar SPIR-V pipeline (llvm-link -> opt(HipSpvPasses) -> SPIR-V backend) was duplicated inside ClangLinkerWrapper.cpp. The same pipeline already lives in HIPSPV::Linker::constructLinkAndEmitSpirvCommand and runs whenever an inner clang is invoked with --target=spirv64*-unknown-chipstar. Drop the wrapper-side duplication and let the inner clang's HIPSPV toolchain do the work: - Remove the global HipPath and its extraction in main(); --hip-path is already forwarded from --device-compiler= via the existing OPT_compiler_arg_EQ channel and reaches the inner clang automatically. - Remove the chipStar-specific llvm-link/opt/SPIR-V emission block from the linker-wrapper clang() helper. - Remove the unconditional --hip-path push for non-chipStar SPIR-V targets; no consumer of that branch existed. - Remove the chipStar --hip-path filter in the compiler_arg loop, restoring the simple forwarding loop. Tests: - hipspv-toolchain.hip: collapse the WRAPPER / WRAPPER-TR runs into a single WRAPPER run that verifies the wrapper invokes inner clang with --target, --hip-path, and the input objects. The opt + in-tree-backend pipeline is already covered by the CHIPSTAR / CHIPSTAR-SUBARCH driver runs that hit HIPSPV::Linker directly. - hipspv-link-static-library.hip: same simplification of SDL-NEW-WRAPPER. Net change: -203 lines in the wrapper, no behavioral regression. All clang/test/Driver lit tests pass (1250/1250 modulo the one pre-existing expected failure).
When HIPSPV::Linker is reached via the new offload driver (the inner clang spawned by clang-linker-wrapper for chipStar SPIR-V emission), the InputInfoList passed to constructLinkAndEmitSpirvCommand may contain non-filename entries (Nothing or InputArg placeholders) alongside the real bitcode inputs. The pre-existing loop called getFilename() unconditionally on every Input, which trips the assert in debug builds and reads garbage from the InputInfo union in release builds. The garbage was then forwarded to llvm-link as an input filename, producing errors like: llvm-link: No such file or directory: '<random bytes>' clang: error: hipspv-link command failed with exit code 1 This bug existed prior to the in-tree SPIR-V backend changes but was latent because the old (--no-offload-new-driver) HIPSPV path always produced a filename-only InputInfoList. The new-driver delegation in "[HIPSPV] Delegate chipStar SPIR-V emission to inner clang in linker wrapper" makes the same code reachable via the new driver and surfaces the bug. Filter the loop on isFilename(), matching the canonical pattern used elsewhere in the driver (e.g. CommonArgs.cpp line 1014 in the LTO link helper). Verified by reproducing the failure with a single-file chipStar HIP compile against the new offload driver, applying the fix, and confirming the same compile succeeds end-to-end. clang/test/Driver lit tests still pass.
| // hip-spirv64-<vendor>-<os>--<arch> | ||
| BundleID = Saver.save("hip-spirv64-" + T.getVendorName() + "-" + | ||
| T.getOSName() + "--" + EffectiveArch); |
There was a problem hiding this comment.
I'm lost here - does this fix a breakage for chipStar? I have tested CHIP-SPV/chipStar#1219 locally on couple days old LLVM and I haven't seen issues. I'm wondering if it necessary to have this.
| // For SPIR-V targets, derive arch from triple if not provided | ||
| StringRef EffectiveArch = Arch; | ||
| if (EffectiveArch.empty() && T.isSPIRV()) { | ||
| EffectiveArch = T.getArchName(); |
There was a problem hiding this comment.
I think CPU/GPU model is expected here which is not same thing as what Triple::getArchName() returns. For SPIR-V I think we can set it to "generic" - that's the default model used elsewhere for SPIR-V targets.
There was a problem hiding this comment.
Also, is it a problem if the Arch is empty for non-chipStar SPIR-V targets?
| case llvm::Triple::ChipStar: | ||
| return llvm::VersionTuple(); // No version constraint for device targets. |
There was a problem hiding this comment.
IIUC, this function is related to availability of std::aligned_alloc(). I'm wondering what this has to do with HIP. Like does HIP language support std::aligned_alloc() to be called from device code?
| for (const auto &Input : Inputs) | ||
| if (Input.isFilename()) | ||
| LinkArgs.push_back(Input.getFilename()); |
There was a problem hiding this comment.
This change got introduced in #187655 so a rebase maybe needed before merge.
| const char *Clang = | ||
| C.getArgs().MakeArgString(C.getDriver().getClangProgramPath()); |
There was a problem hiding this comment.
Using clang driver for emitting SPIR-V (through SPIR-V BE) seems hazarous - it think it's better to use cc1 like HIPAMD does.
| // 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; | ||
| } |
There was a problem hiding this comment.
This portion is a duplicate from the below - could you refactor the duplication away?
| @@ -91,7 +91,8 @@ SPIRVSubtarget::SPIRVSubtarget(const Triple &TT, const std::string &CPU, | |||
| if (TargetTriple.getOS() == Triple::Vulkan) | |||
| Env = Shader; | |||
| else if (TargetTriple.getOS() == Triple::OpenCL || | |||
| TargetTriple.getVendor() == Triple::AMD) | |||
| TargetTriple.getVendor() == Triple::AMD || | |||
| TargetTriple.getOS() == Triple::ChipStar) | |||
There was a problem hiding this comment.
This if fine for now. I just want to comment that I regret making the "ChipStar" an OS component but it doesn't matter unless/until chipStar adds a driver backend that requires Vulkan or other SPIR-V environment. It would have been better if it were a vendor component instead to differentiate the target environment - something like "spirv64-chipstar-vulkan".
There was a problem hiding this comment.
doesn't matter unless/until chipStar adds a driver backend that requires Vulkan
I have a branch that converts our generated SPIRV from OpenCL to Vulkan SPIRV. This is for simplifying integration with clvk - converting SPIRV to Vulkan SPIRV on our side allows us to bypass the clspv component of clvk.
Not sure if this is something that will ever get merged or how that would affect llvm if so.
Summary
chipStar (https://github.com/CHIP-SPV/chipStar) enables HIP/CUDA programs to run on OpenCL and Level Zero devices via SPIR-V. Until now, the HIPSPV toolchain relied exclusively on the external
llvm-spirvtranslator for bitcode-to-SPIR-V conversion.This patch adds native in-tree SPIR-V backend support for chipStar targets (
spirv64*-unknown-chipstar), removing the hard dependency onllvm-spirv.Changes
HIPSPV old driver (
HIPSPV.cpp):opt(HipSpvPasses) +clang -c(SPIR-V backend) instead ofopt+llvm-spirvllvm-spirvunchangedHostTC->addClangTargetOptions()delegation to avoid macOS Darwin flags (-faligned-alloc-unavailable) breaking SPIR-V device compilationNew offload driver (
ClangLinkerWrapper.cpp):llvm-link→opt(HipSpvPasses) →clang -c --target=spirv64with SPIR-V extensions--hip-pathfrom--device-compiler=args for locating the HipSpvPasses plugin and device librariesllvm-spirvtranslator when available for non-chipStar SPIR-V targetsSPIR-V toolchain (
SPIRV.cpp):NativeLLVMSupportfor chipStar triples so the toolchain does not require an external translatorSPIR-V backend (
SPIRVSubtarget.cpp):Kernelenvironment for chipStar triples (needed for OpenCL kernel ABI)AlignedAllocation.h:ChipStarcase to avoid unhandled enum warningTest plan
hipspv-toolchain.hipdriver test updated to verify the new in-tree backend pipelineExpandVariadicsregression in LLVM 23)