[clang] [llvm] [HIPSPV] Add chipStar SPIR-V support for the new offload driver (PR #180903)
Paulius Velesko via cfe-commits
cfe-commits at lists.llvm.org
Wed Feb 11 02:06:34 PST 2026
https://github.com/pvelesko updated https://github.com/llvm/llvm-project/pull/180903
>From 0ec85de45abad2c13a052ba7657146f27d291a01 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:00:00 +0200
Subject: [PATCH 01/13] Add llvm/build* to .gitignore
Keep in-tree build directories out of git status.
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 9ce99993e767c..b6c9f710365ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -86,3 +86,4 @@ pythonenv*
/clang/utils/analyzer/projects/*/RefScanBuildResults
# automodapi puts generated documentation files here.
/lldb/docs/python_api/
+llvm/build*
>From 38ef41ba709f8e545fb284d9a5c5d8551ca83817 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:00:10 +0200
Subject: [PATCH 02/13] [SPIRV] Use explicit data layouts for SPIR-V targets
Both frontend (SPIR.h) and backend (SPIRVTargetMachine.cpp) were using
computeDataLayout() which includes n8:16:32:64. SPIR-V has no native
integer widths so the n field causes wrong type legalization decisions.
Use hardcoded layout strings without the n specifier, matching the
older SPIR target classes.
---
clang/lib/Basic/Targets/SPIR.h | 9 ++++++---
llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp | 20 +++++++++++++++++++-
2 files changed, 25 insertions(+), 4 deletions(-)
diff --git a/clang/lib/Basic/Targets/SPIR.h b/clang/lib/Basic/Targets/SPIR.h
index eef9521c7434a..9ccaf28e8ee3c 100644
--- a/clang/lib/Basic/Targets/SPIR.h
+++ b/clang/lib/Basic/Targets/SPIR.h
@@ -349,7 +349,8 @@ class LLVM_LIBRARY_VISIBILITY SPIRVTargetInfo : public BaseSPIRVTargetInfo {
// SPIR-V IDs are represented with a single 32-bit word.
SizeType = TargetInfo::UnsignedInt;
- resetDataLayout();
+ resetDataLayout("e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-"
+ "v256:256-v512:512-v1024:1024-G10");
}
void getTargetDefines(const LangOptions &Opts,
@@ -373,7 +374,8 @@ class LLVM_LIBRARY_VISIBILITY SPIRV32TargetInfo : public BaseSPIRVTargetInfo {
// SPIR-V has core support for atomic ops, and Int32 is always available;
// we take the maximum because it's possible the Host supports wider types.
MaxAtomicInlineWidth = std::max<unsigned char>(MaxAtomicInlineWidth, 32);
- resetDataLayout();
+ resetDataLayout("e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-"
+ "v192:256-v256:256-v512:512-v1024:1024-G1");
}
void getTargetDefines(const LangOptions &Opts,
@@ -397,7 +399,8 @@ class LLVM_LIBRARY_VISIBILITY SPIRV64TargetInfo : public BaseSPIRVTargetInfo {
// SPIR-V has core support for atomic ops, and Int64 is always available;
// we take the maximum because it's possible the Host supports wider types.
MaxAtomicInlineWidth = std::max<unsigned char>(MaxAtomicInlineWidth, 64);
- resetDataLayout();
+ resetDataLayout("e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-"
+ "v256:256-v512:512-v1024:1024-G1");
}
void getTargetDefines(const LangOptions &Opts,
diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
index 301fe3d487565..b5c546f96c388 100644
--- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp
@@ -74,6 +74,24 @@ static Reloc::Model getEffectiveRelocModel(std::optional<Reloc::Model> RM) {
return *RM;
}
+static std::string computeDataLayout(const Triple &TT) {
+ Triple::ArchType Arch = TT.getArch();
+ // SPIR-V doesn't have native integer widths, so n8:16:32:64 should not be
+ // in the data layout. The n*: part specifies native integer widths which
+ // mean anything.
+ if (Arch == Triple::spirv32)
+ return "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-"
+ "v256:256-v512:512-v1024:1024-G1";
+ if (Arch == Triple::spirv)
+ return "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-"
+ "v512:512-v1024:1024-G10";
+ if (TT.getVendor() == Triple::VendorType::AMD &&
+ TT.getOS() == Triple::OSType::AMDHSA)
+ return "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-"
+ "v512:512-v1024:1024-n32:64-S32-G1-P4-A0";
+ return "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-"
+ "v512:512-v1024:1024-G1";
+}
// Pin SPIRVTargetObjectFile's vtables to this file.
SPIRVTargetObjectFile::~SPIRVTargetObjectFile() = default;
@@ -83,7 +101,7 @@ SPIRVTargetMachine::SPIRVTargetMachine(const Target &T, const Triple &TT,
std::optional<Reloc::Model> RM,
std::optional<CodeModel::Model> CM,
CodeGenOptLevel OL, bool JIT)
- : CodeGenTargetMachineImpl(T, TT.computeDataLayout(), TT, CPU, FS, Options,
+ : CodeGenTargetMachineImpl(T, computeDataLayout(TT), TT, CPU, FS, Options,
getEffectiveRelocModel(RM),
getEffectiveCodeModel(CM, CodeModel::Small), OL),
TLOF(std::make_unique<SPIRVTargetObjectFile>()),
>From 12dc04a43d387280e1e5482b7c8d5f7f3fea2404 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:00:13 +0200
Subject: [PATCH 03/13] [clang] Handle unknown OS in alignedAllocMinVersion
alignedAllocMinVersion() hit llvm_unreachable() for OS types not in
its switch (e.g. chipStar). Return empty VersionTuple for unknown OS
types so aligned allocation is considered always available on device
targets.
---
clang/include/clang/Basic/AlignedAllocation.h | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/clang/include/clang/Basic/AlignedAllocation.h b/clang/include/clang/Basic/AlignedAllocation.h
index ac26eb4a276da..21bb7e2ccf409 100644
--- a/clang/include/clang/Basic/AlignedAllocation.h
+++ b/clang/include/clang/Basic/AlignedAllocation.h
@@ -24,7 +24,9 @@ namespace clang {
inline llvm::VersionTuple alignedAllocMinVersion(llvm::Triple::OSType OS) {
switch (OS) {
default:
- break;
+ // For unknown/unsupported OS types (e.g. SPIRV, CUDA device targets),
+ // return empty version tuple indicating aligned alloc is always available.
+ return llvm::VersionTuple();
case llvm::Triple::Darwin:
case llvm::Triple::MacOSX: // Earliest supporting version is 10.13.
return llvm::VersionTuple(10U, 13U);
@@ -36,8 +38,6 @@ inline llvm::VersionTuple alignedAllocMinVersion(llvm::Triple::OSType OS) {
case llvm::Triple::ZOS:
return llvm::VersionTuple(); // All z/OS versions have no support.
}
-
- llvm_unreachable("Unexpected OS");
}
} // end namespace clang
>From b3f10fa956d441a7ec834a0c2e575b6e4f20da65 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:00:16 +0200
Subject: [PATCH 04/13] [Darwin] Guard against uninitialized target
When Darwin is the host toolchain for HIP offloading, setTarget() is
never called. Methods like addClangWarningOptions, CheckObjCARC, and
getSupportedSanitizers read uninitialized state, causing assertion
failures in Debug builds.
Add early-return guards on !isTargetInitialized().
---
clang/lib/Driver/ToolChains/Darwin.cpp | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/clang/lib/Driver/ToolChains/Darwin.cpp b/clang/lib/Driver/ToolChains/Darwin.cpp
index 073f23950160c..32a5b1fbe4993 100644
--- a/clang/lib/Driver/ToolChains/Darwin.cpp
+++ b/clang/lib/Driver/ToolChains/Darwin.cpp
@@ -1239,6 +1239,11 @@ void DarwinClang::addClangWarningOptions(ArgStringList &CC1Args) const {
CC1Args.push_back("-Werror=undef-prefix");
// For modern targets, promote certain warnings to errors.
+ // Guard against uninitialized target (e.g. when Darwin is used as host
+ // toolchain for HIP/CUDA offloading where the target platform may not
+ // have been fully set up).
+ if (!isTargetInitialized())
+ return;
if (isTargetWatchOSBased() || getTriple().isArch64Bit()) {
// Always enable -Wdeprecated-objc-isa-usage and promote it
// to an error.
@@ -3874,6 +3879,10 @@ void Darwin::addStartObjectFileArgs(const ArgList &Args,
}
void Darwin::CheckObjCARC() const {
+ // Guard against uninitialized target (e.g. when Darwin is used as host
+ // toolchain for HIP/CUDA offloading).
+ if (!isTargetInitialized())
+ return;
if (isTargetIOSBased() || isTargetWatchOSBased() || isTargetXROS() ||
(isTargetMacOSBased() && !isMacosxVersionLT(10, 6)))
return;
@@ -3893,6 +3902,10 @@ SanitizerMask Darwin::getSupportedSanitizers() const {
Res |= SanitizerKind::FuzzerNoLink;
Res |= SanitizerKind::ObjCCast;
+ // Guard against uninitialized target (e.g. when Darwin is used as host
+ // toolchain for HIP/CUDA offloading). Return base sanitizers only.
+ if (!isTargetInitialized())
+ return Res;
// Prior to 10.9, macOS shipped a version of the C++ standard library without
// C++11 support. The same is true of iOS prior to version 5. These OS'es are
// incompatible with -fsanitize=vptr.
>From 4b35b239a5e9e58316e018133ab432d088d50bcb Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:00:22 +0200
Subject: [PATCH 05/13] [Driver] Derive tool paths from clang binary location
GetProgramPath() can find the wrong version of llvm-link, opt, or
llvm-spirv via $PATH on systems with multiple LLVM installs. Derive
tool paths from the clang binary directory instead.
Affects CommonArgs.cpp, HIPSPV.cpp, SPIRV.cpp.
---
clang/lib/Driver/ToolChains/CommonArgs.cpp | 7 ++++++-
clang/lib/Driver/ToolChains/HIPSPV.cpp | 7 ++++++-
clang/lib/Driver/ToolChains/SPIRV.cpp | 23 ++++++++++++++--------
3 files changed, 27 insertions(+), 10 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 6857231b2aafe..af0549221604e 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -3550,7 +3550,12 @@ void tools::constructLLVMLinkCommand(Compilation &C, const Tool &T,
LlvmLinkArgs.append(LinkerInputs);
const ToolChain &TC = T.getToolChain();
- const char *LlvmLink = Args.MakeArgString(TC.GetProgramPath("llvm-link"));
+ // Derive llvm-link path from clang path to ensure we use the same LLVM version
+ std::string ClangPath = C.getDriver().getClangProgramPath();
+ SmallString<128> LlvmLinkPath(ClangPath);
+ llvm::sys::path::remove_filename(LlvmLinkPath);
+ llvm::sys::path::append(LlvmLinkPath, "llvm-link");
+ const char *LlvmLink = Args.MakeArgString(LlvmLinkPath);
C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(),
LlvmLink, LlvmLinkArgs, JobInputs,
Output));
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index 8bdb7ab042b2b..223778fa3ff77 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -83,7 +83,12 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
ArgStringList OptArgs{TempFile, "-load-pass-plugin",
PassPathCStr, "-passes=hip-post-link-passes",
"-o", OptOutput};
- const char *Opt = Args.MakeArgString(getToolChain().GetProgramPath("opt"));
+ // Derive opt path from clang path to ensure we use the same LLVM version
+ std::string ClangPath = C.getDriver().getClangProgramPath();
+ SmallString<128> OptPath(ClangPath);
+ llvm::sys::path::remove_filename(OptPath);
+ llvm::sys::path::append(OptPath, "opt");
+ const char *Opt = C.getArgs().MakeArgString(OptPath);
C.addCommand(std::make_unique<Command>(
JA, *this, ResponseFileSupport::None(), Opt, OptArgs, Inputs, Output));
TempFile = OptOutput;
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index c1ccb1e7d8508..1406aed5950cb 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -11,6 +11,8 @@
#include "clang/Driver/Driver.h"
#include "clang/Driver/InputInfo.h"
#include "clang/Options/Options.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
using namespace clang::driver;
using namespace clang::driver::toolchains;
@@ -32,15 +34,20 @@ void SPIRV::constructTranslateCommand(Compilation &C, const Tool &T,
CmdArgs.append({"-o", Output.getFilename()});
- // Try to find "llvm-spirv-<LLVM_VERSION_MAJOR>". Otherwise, fall back to
- // plain "llvm-spirv".
- using namespace std::string_literals;
- auto VersionedTool = "llvm-spirv-"s + std::to_string(LLVM_VERSION_MAJOR);
- std::string ExeCand = T.getToolChain().GetProgramPath(VersionedTool.c_str());
- if (!llvm::sys::fs::can_execute(ExeCand))
- ExeCand = T.getToolChain().GetProgramPath("llvm-spirv");
+ // Derive llvm-spirv path from clang path to ensure we use the same LLVM version.
+ // Try versioned tool first, then fall back to unversioned.
+ std::string TranslateCmdClangPath = C.getDriver().getClangProgramPath();
+ SmallString<128> TranslateCmdPath(TranslateCmdClangPath);
+ llvm::sys::path::remove_filename(TranslateCmdPath);
+ SmallString<128> TranslateCmdVersionedPath(TranslateCmdPath);
+ llvm::sys::path::append(TranslateCmdVersionedPath, "llvm-spirv-" + std::to_string(LLVM_VERSION_MAJOR));
+ if (llvm::sys::fs::can_execute(TranslateCmdVersionedPath)) {
+ llvm::sys::path::append(TranslateCmdPath, "llvm-spirv-" + std::to_string(LLVM_VERSION_MAJOR));
+ } else {
+ llvm::sys::path::append(TranslateCmdPath, "llvm-spirv");
+ }
- const char *Exec = C.getArgs().MakeArgString(ExeCand);
+ const char *Exec = C.getArgs().MakeArgString(TranslateCmdPath);
C.addCommand(std::make_unique<Command>(JA, T, ResponseFileSupport::None(),
Exec, CmdArgs, Input, Output));
}
>From 46905d1f4d601982a3eb14fe77a7a1c62c25f9ce Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:00:31 +0200
Subject: [PATCH 06/13] [llvm-link] Inherit data layout and triple from first
archive member
loadArFile() creates an empty Result module with no data layout.
Copy data layout and target triple from the first archive member
before linking to avoid data-layout-mismatch warnings and invalid
output modules.
---
llvm/tools/llvm-link/llvm-link.cpp | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/llvm/tools/llvm-link/llvm-link.cpp b/llvm/tools/llvm-link/llvm-link.cpp
index 33c3e6fc350fb..b7d4400634df8 100644
--- a/llvm/tools/llvm-link/llvm-link.cpp
+++ b/llvm/tools/llvm-link/llvm-link.cpp
@@ -166,6 +166,7 @@ static std::unique_ptr<Module> loadArFile(const char *Argv0,
std::unique_ptr<object::Archive> Archive = std::move(ArchiveOrError.get());
+ bool FirstModule = true;
Linker L(*Result);
Error Err = Error::success();
for (const object::Archive::Child &C : Archive->children(Err)) {
@@ -216,6 +217,17 @@ static std::unique_ptr<Module> loadArFile(const char *Argv0,
<< "'\n";
return nullptr;
}
+
+ // Inherit data layout and target triple from the first module in the
+ // archive to avoid warnings about linking modules with different layouts.
+ if (FirstModule) {
+ FirstModule = false;
+ if (!M->getDataLayoutStr().empty())
+ Result->setDataLayout(M->getDataLayout());
+ if (!M->getTargetTriple().empty())
+ Result->setTargetTriple(M->getTargetTriple());
+ }
+
if (Verbose)
errs() << "Linking member '" << ChildName << "' of archive library.\n";
if (L.linkInModule(std::move(M)))
>From ac18cff0799e1f091db4c25d7f33d6f67e61c9cf Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:00:36 +0200
Subject: [PATCH 07/13] [HIP] Use Mach-O section format for fatbinary on macOS
ELF-style section names like .hip_fatbin don't work with Mach-O.
Use __HIP,__hip_fatbin and related segment,section names when the
target triple is macOS.
Affects CGCUDANV.cpp and HIPUtility.cpp.
---
clang/lib/CodeGen/CGCUDANV.cpp | 10 +++++++---
clang/lib/Driver/ToolChains/HIPUtility.cpp | 7 +++++--
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/clang/lib/CodeGen/CGCUDANV.cpp b/clang/lib/CodeGen/CGCUDANV.cpp
index e04da90b3cbf6..f08040d1d3d15 100644
--- a/clang/lib/CodeGen/CGCUDANV.cpp
+++ b/clang/lib/CodeGen/CGCUDANV.cpp
@@ -817,10 +817,14 @@ llvm::Function *CGNVCUDARuntime::makeModuleCtorFunction() {
llvm::Constant *FatBinStr;
unsigned FatMagic;
if (IsHIP) {
- FatbinConstantName = ".hip_fatbin";
- FatbinSectionName = ".hipFatBinSegment";
+ // On macOS (Mach-O), section names must be in "segment,section" format.
+ FatbinConstantName =
+ CGM.getTriple().isMacOSX() ? "__HIP,__hip_fatbin" : ".hip_fatbin";
+ FatbinSectionName =
+ CGM.getTriple().isMacOSX() ? "__HIP,__fatbin" : ".hipFatBinSegment";
- ModuleIDSectionName = "__hip_module_id";
+ ModuleIDSectionName =
+ CGM.getTriple().isMacOSX() ? "__HIP,__module_id" : "__hip_module_id";
ModuleIDPrefix = "__hip_";
if (CudaGpuBinary) {
diff --git a/clang/lib/Driver/ToolChains/HIPUtility.cpp b/clang/lib/Driver/ToolChains/HIPUtility.cpp
index 1fcb36cc3a390..c1ca3d5df2a7e 100644
--- a/clang/lib/Driver/ToolChains/HIPUtility.cpp
+++ b/clang/lib/Driver/ToolChains/HIPUtility.cpp
@@ -430,9 +430,12 @@ void HIP::constructGenerateObjFileFromHIPFatBinary(
}
if (FoundPrimaryHipFatbinSymbol) {
// Define the first fatbin symbol
- if (HostTriple.isWindowsMSVCEnvironment())
+ if (HostTriple.isWindowsMSVCEnvironment()) {
ObjStream << " .section .hip_fatbin,\"dw\"\n";
- else {
+ } else if (HostTriple.isMacOSX()) {
+ // Mach-O requires "segment,section" format
+ ObjStream << " .section __HIP,__hip_fatbin\n";
+ } else {
ObjStream << " .protected " << PrimaryHipFatbinSymbol << "\n";
ObjStream << " .type " << PrimaryHipFatbinSymbol << ", at object\n";
ObjStream << " .section .hip_fatbin,\"a\", at progbits\n";
>From 27578c793c864da91d2458c34cff83231f548d27 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:00:44 +0200
Subject: [PATCH 08/13] [HIPSPV] Skip host target options for device
compilation
Don't delegate to HostTC->addClangTargetOptions() for device
compilations. On Darwin hosts this injects -faligned-alloc-unavailable
into the device invocation where it doesn't apply.
---
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 223778fa3ff77..e698c62fe8ac2 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -164,7 +164,11 @@ void HIPSPVToolChain::addClangTargetOptions(
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.");
>From f03f3e3e108d85b7805cebef255541e63ba1f57d Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:01:07 +0200
Subject: [PATCH 09/13] [LinkerWrapper] Add --hip-path option
clang-linker-wrapper needs --hip-path to locate
libLLVMHipSpvPasses.so for HIP-to-SPIR-V lowering during RDC
linking.
---
clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td | 3 +++
1 file changed, 3 insertions(+)
diff --git a/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td b/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
index ef3a16b2f58bb..1257ef9918900 100644
--- a/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
+++ b/clang/tools/clang-linker-wrapper/LinkerWrapperOpts.td
@@ -16,6 +16,9 @@ def linker_path_EQ : Joined<["--"], "linker-path=">,
def cuda_path_EQ : Joined<["--"], "cuda-path=">,
Flags<[WrapperOnlyOption]>, MetaVarName<"<dir>">,
HelpText<"Set the system CUDA path">;
+def hip_path_EQ : Joined<["--"], "hip-path=">,
+ Flags<[WrapperOnlyOption]>, MetaVarName<"<dir>">,
+ HelpText<"Set the HIP installation path">;
def host_triple_EQ : Joined<["--"], "host-triple=">,
Flags<[WrapperOnlyOption]>,
MetaVarName<"<triple>">,
>From 8f71f0c56b005f4bda3b73ad9f35a9c6e7fd0bf8 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Mon, 9 Feb 2026 15:01:24 +0200
Subject: [PATCH 10/13] [SPIRV] Add chipStar merge-and-translate flow
For chipStar non-RDC builds, merge all .bc files with llvm-link
and run HipSpvPasses before SPIR-V translation. HIP device code
has cross-TU references that require whole-module visibility.
New flow: llvm-link -> opt (HipSpvPasses) -> llvm-spirv.
---
clang/lib/Driver/ToolChains/SPIRV.cpp | 75 +++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index 1406aed5950cb..ef81638b222ee 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -6,6 +6,7 @@
//
//===----------------------------------------------------------------------===//
#include "SPIRV.h"
+#include "HIPUtility.h"
#include "clang/Driver/CommonArgs.h"
#include "clang/Driver/Compilation.h"
#include "clang/Driver/Driver.h"
@@ -18,6 +19,7 @@ using namespace clang::driver;
using namespace clang::driver::toolchains;
using namespace clang::driver::tools;
using namespace llvm::opt;
+using namespace clang;
void SPIRV::constructTranslateCommand(Compilation &C, const Tool &T,
const JobAction &JA,
@@ -142,6 +144,24 @@ clang::driver::Tool *SPIRVToolChain::buildLinker() const {
return new tools::SPIRV::Linker(*this);
}
+// Locates HIP pass plugin for chipstar targets.
+static std::string findPassPlugin(const Driver &D,
+ const llvm::opt::ArgList &Args) {
+ llvm::StringRef hipPath = Args.getLastArgValue(options::OPT_hip_path_EQ);
+ if (!hipPath.empty()) {
+ llvm::SmallString<128> PluginPath(hipPath);
+ llvm::sys::path::append(PluginPath, "lib", "libLLVMHipSpvPasses.so");
+ if (llvm::sys::fs::exists(PluginPath))
+ return PluginPath.str().str();
+ PluginPath.assign(hipPath);
+ llvm::sys::path::append(PluginPath, "lib", "llvm",
+ "libLLVMHipSpvPasses.so");
+ if (llvm::sys::fs::exists(PluginPath))
+ return PluginPath.str().str();
+ }
+ return std::string();
+}
+
void SPIRV::Linker::ConstructJob(Compilation &C, const JobAction &JA,
const InputInfo &Output,
const InputInfoList &Inputs,
@@ -151,7 +171,62 @@ void SPIRV::Linker::ConstructJob(Compilation &C, const JobAction &JA,
constructLLVMLinkCommand(C, *this, JA, Output, Inputs, Args);
return;
}
+
const ToolChain &ToolChain = getToolChain();
+ auto Triple = ToolChain.getTriple();
+
+ // For chipstar targets with new offload driver, implement merge-then-process flow:
+ // 1. Merge bitcode with llvm-link
+ // 2. Run HipSpvPasses plugin
+ // 3. Translate to SPIR-V with llvm-spirv
+ // 4. Pass to spirv-link
+ if (Triple.getOS() == llvm::Triple::ChipStar) {
+ assert(!Inputs.empty() && "Must have at least one input.");
+ std::string Name = std::string(llvm::sys::path::stem(Output.getFilename()));
+ const char *LinkBCFile = HIP::getTempFile(C, Name + "-link", "bc");
+
+ // Step 1: Merge all bitcode files with llvm-link
+ ArgStringList LinkArgs;
+ for (auto Input : Inputs)
+ LinkArgs.push_back(Input.getFilename());
+ tools::constructLLVMLinkCommand(C, *this, JA, Inputs, LinkArgs, Output, Args,
+ LinkBCFile);
+
+ // Step 2: Run HipSpvPasses plugin
+ const char *ProcessedBCFile = LinkBCFile;
+ 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{LinkBCFile, "-load-pass-plugin",
+ PassPathCStr, "-passes=hip-post-link-passes",
+ "-o", OptOutput};
+ // Derive opt path from clang path to ensure we use the same LLVM version
+ std::string ClangPath = C.getDriver().getClangProgramPath();
+ SmallString<128> OptPath(ClangPath);
+ llvm::sys::path::remove_filename(OptPath);
+ llvm::sys::path::append(OptPath, "opt");
+ const char *Opt = C.getArgs().MakeArgString(OptPath);
+ C.addCommand(std::make_unique<Command>(
+ JA, *this, ResponseFileSupport::None(), Opt, OptArgs, Inputs, Output));
+ ProcessedBCFile = OptOutput;
+ }
+
+ // Step 3: Translate bitcode to SPIR-V (output goes directly to final output)
+ llvm::opt::ArgStringList TrArgs;
+ bool HasNoSubArch = Triple.getSubArch() == llvm::Triple::NoSubArch;
+ if (HasNoSubArch)
+ 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, ProcessedBCFile, "");
+ constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
+ return;
+ }
+
+ // Default flow for non-chipstar targets
+ // spirv-link is from SPIRV-Tools (Khronos), not LLVM, so use PATH lookup
std::string Linker = ToolChain.GetProgramPath(getShortName());
ArgStringList CmdArgs;
AddLinkerInputs(getToolChain(), Inputs, Args, CmdArgs, JA);
>From 3a6ac3861141fe88f72acdf23964457484b7b709 Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Fri, 6 Feb 2026 19:17:56 +0200
Subject: [PATCH 11/13] [HIPSPV] Switch chipStar to in-tree SPIR-V backend
Replace the external llvm-spirv translator with LLVM's native
SPIR-V backend for chipStar targets.
Backend: recognize ChipStar as Kernel env, add atomic_fetch_min/max
builtins, give Import linkage to hidden-visibility declarations.
Toolchain: set NativeLLVMSupport for chipStar, add chipStar codegen
path in HIPSPV.cpp, add RDC pipeline in ClangLinkerWrapper.cpp.
---
clang/lib/Driver/ToolChains/HIPSPV.cpp | 77 +++++---
clang/lib/Driver/ToolChains/SPIRV.cpp | 84 ++-------
.../ClangLinkerWrapper.cpp | 173 +++++++++++++++---
llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp | 4 +
llvm/lib/Target/SPIRV/SPIRVBuiltins.td | 10 +
llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp | 3 +-
llvm/lib/Target/SPIRV/SPIRVUtils.cpp | 12 +-
7 files changed, 243 insertions(+), 120 deletions(-)
diff --git a/clang/lib/Driver/ToolChains/HIPSPV.cpp b/clang/lib/Driver/ToolChains/HIPSPV.cpp
index e698c62fe8ac2..cce04756fe19c 100644
--- a/clang/lib/Driver/ToolChains/HIPSPV.cpp
+++ b/clang/lib/Driver/ToolChains/HIPSPV.cpp
@@ -72,10 +72,58 @@ 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).
+
+ // Step 2: 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};
+ std::string ClangPath = C.getDriver().getClangProgramPath();
+ SmallString<128> OptPath(ClangPath);
+ llvm::sys::path::remove_filename(OptPath);
+ llvm::sys::path::append(OptPath, "opt");
+ const char *Opt = C.getArgs().MakeArgString(OptPath);
+ C.addCommand(std::make_unique<Command>(
+ JA, *this, ResponseFileSupport::None(), Opt, OptArgs, Inputs,
+ Output));
+ TempFile = OptOutput;
+ }
+
+ // Step 3: Compile processed bitcode to SPIR-V using the in-tree backend.
+ // Use -c to skip the link phase (spirv-link is not needed for single TU).
+ 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");
+
+ 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;
+ }
- // Run LLVM IR passes to lower/expand/emulate HIP code that does not translate
- // to SPIR-V (E.g. dynamic shared memory).
+ // 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);
@@ -83,7 +131,6 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
ArgStringList OptArgs{TempFile, "-load-pass-plugin",
PassPathCStr, "-passes=hip-post-link-passes",
"-o", OptOutput};
- // Derive opt path from clang path to ensure we use the same LLVM version
std::string ClangPath = C.getDriver().getClangProgramPath();
SmallString<128> OptPath(ClangPath);
llvm::sys::path::remove_filename(OptPath);
@@ -94,27 +141,11 @@ void HIPSPV::Linker::constructLinkAndEmitSpirvCommand(
TempFile = OptOutput;
}
- // Emit SPIR-V binary.
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 (HasNoSubArch)
+ TrArgs.push_back("--spirv-max-version=1.1");
+ TrArgs.push_back("--spirv-ext=+all");
InputInfo TrInput = InputInfo(types::TY_LLVM_BC, TempFile, "");
SPIRV::constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
diff --git a/clang/lib/Driver/ToolChains/SPIRV.cpp b/clang/lib/Driver/ToolChains/SPIRV.cpp
index ef81638b222ee..b9a62b7356061 100644
--- a/clang/lib/Driver/ToolChains/SPIRV.cpp
+++ b/clang/lib/Driver/ToolChains/SPIRV.cpp
@@ -144,24 +144,6 @@ clang::driver::Tool *SPIRVToolChain::buildLinker() const {
return new tools::SPIRV::Linker(*this);
}
-// Locates HIP pass plugin for chipstar targets.
-static std::string findPassPlugin(const Driver &D,
- const llvm::opt::ArgList &Args) {
- llvm::StringRef hipPath = Args.getLastArgValue(options::OPT_hip_path_EQ);
- if (!hipPath.empty()) {
- llvm::SmallString<128> PluginPath(hipPath);
- llvm::sys::path::append(PluginPath, "lib", "libLLVMHipSpvPasses.so");
- if (llvm::sys::fs::exists(PluginPath))
- return PluginPath.str().str();
- PluginPath.assign(hipPath);
- llvm::sys::path::append(PluginPath, "lib", "llvm",
- "libLLVMHipSpvPasses.so");
- if (llvm::sys::fs::exists(PluginPath))
- return PluginPath.str().str();
- }
- return std::string();
-}
-
void SPIRV::Linker::ConstructJob(Compilation &C, const JobAction &JA,
const InputInfo &Output,
const InputInfoList &Inputs,
@@ -171,60 +153,23 @@ void SPIRV::Linker::ConstructJob(Compilation &C, const JobAction &JA,
constructLLVMLinkCommand(C, *this, JA, Output, Inputs, Args);
return;
}
-
+
const ToolChain &ToolChain = getToolChain();
auto Triple = ToolChain.getTriple();
-
- // For chipstar targets with new offload driver, implement merge-then-process flow:
- // 1. Merge bitcode with llvm-link
- // 2. Run HipSpvPasses plugin
- // 3. Translate to SPIR-V with llvm-spirv
- // 4. Pass to spirv-link
- if (Triple.getOS() == llvm::Triple::ChipStar) {
- assert(!Inputs.empty() && "Must have at least one input.");
- std::string Name = std::string(llvm::sys::path::stem(Output.getFilename()));
- const char *LinkBCFile = HIP::getTempFile(C, Name + "-link", "bc");
-
- // Step 1: Merge all bitcode files with llvm-link
- ArgStringList LinkArgs;
- for (auto Input : Inputs)
- LinkArgs.push_back(Input.getFilename());
- tools::constructLLVMLinkCommand(C, *this, JA, Inputs, LinkArgs, Output, Args,
- LinkBCFile);
-
- // Step 2: Run HipSpvPasses plugin
- const char *ProcessedBCFile = LinkBCFile;
- 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{LinkBCFile, "-load-pass-plugin",
- PassPathCStr, "-passes=hip-post-link-passes",
- "-o", OptOutput};
- // Derive opt path from clang path to ensure we use the same LLVM version
- std::string ClangPath = C.getDriver().getClangProgramPath();
- SmallString<128> OptPath(ClangPath);
- llvm::sys::path::remove_filename(OptPath);
- llvm::sys::path::append(OptPath, "opt");
- const char *Opt = C.getArgs().MakeArgString(OptPath);
- C.addCommand(std::make_unique<Command>(
- JA, *this, ResponseFileSupport::None(), Opt, OptArgs, Inputs, Output));
- ProcessedBCFile = OptOutput;
- }
-
- // Step 3: Translate bitcode to SPIR-V (output goes directly to final output)
- llvm::opt::ArgStringList TrArgs;
- bool HasNoSubArch = Triple.getSubArch() == llvm::Triple::NoSubArch;
- if (HasNoSubArch)
- 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, ProcessedBCFile, "");
- constructTranslateCommand(C, *this, JA, Output, TrInput, TrArgs);
+
+ // For chipStar targets using the in-tree SPIR-V backend, the backend
+ // compile step already produced a valid SPIR-V binary. When there is a
+ // single input, just copy it to the output (no spirv-link needed).
+ if (Triple.getOS() == llvm::Triple::ChipStar && Inputs.size() == 1) {
+ ArgStringList CpArgs;
+ CpArgs.push_back(Inputs[0].getFilename());
+ CpArgs.push_back(Output.getFilename());
+ const char *CpPath = Args.MakeArgString("/usr/bin/cp");
+ C.addCommand(std::make_unique<Command>(JA, *this, ResponseFileSupport::None(),
+ CpPath, CpArgs, Inputs, Output));
return;
}
-
+
// Default flow for non-chipstar targets
// spirv-link is from SPIRV-Tools (Khronos), not LLVM, so use PATH lookup
std::string Linker = ToolChain.GetProgramPath(getShortName());
@@ -253,7 +198,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);
+ NativeLLVMSupport = Args.hasArg(options::OPT_sycl_link) ||
+ Triple.getOS() == llvm::Triple::ChipStar;
// Lookup binaries into the driver directory.
getProgramPaths().push_back(getDriver().Dir);
diff --git a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
index 619e539857fc6..ba12287ef900c 100644
--- a/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
+++ b/clang/tools/clang-linker-wrapper/ClangLinkerWrapper.cpp
@@ -131,6 +131,8 @@ 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;
@@ -409,19 +411,8 @@ fatbinary(ArrayRef<std::pair<StringRef, StringRef>> InputFiles,
} // namespace nvptx
namespace amdgcn {
-
-// Constructs a triple string for clang offload bundler.
-// NOTE: copied from HIPUtility.cpp.
-static std::string normalizeForBundler(const llvm::Triple &T,
- bool HasTargetID) {
- return HasTargetID ? (T.getArchName() + "-" + T.getVendorName() + "-" +
- T.getOSName() + "-" + T.getEnvironmentName())
- .str()
- : T.normalize(llvm::Triple::CanonicalForm::FOUR_IDENT);
-}
-
Expected<StringRef>
-fatbinary(ArrayRef<std::tuple<StringRef, StringRef, StringRef>> InputFiles,
+fatbinary(ArrayRef<std::tuple<StringRef, StringRef, StringRef>> InputFilesWithTriple,
const ArgList &Args) {
llvm::TimeTraceScope TimeScope("AMDGPU Fatbinary");
@@ -452,10 +443,24 @@ fatbinary(ArrayRef<std::tuple<StringRef, StringRef, StringRef>> InputFiles,
Args.MakeArgString(Twine("-compression-level=") + Arg->getValue()));
SmallVector<StringRef> Targets = {"-targets=host-x86_64-unknown-linux-gnu"};
- for (const auto &[File, TripleRef, Arch] : InputFiles) {
- std::string NormalizedTriple =
- normalizeForBundler(Triple(TripleRef), !Arch.empty());
- Targets.push_back(Saver.save("hip-" + NormalizedTriple + "-" + Arch));
+ for (const auto &[File, Arch, TripleStr] : InputFilesWithTriple) {
+ llvm::Triple Triple(TripleStr);
+ // For SPIR-V targets, derive arch from triple if not provided
+ StringRef EffectiveArch = Arch;
+ if (EffectiveArch.empty() && Triple.isSPIRV()) {
+ EffectiveArch = Triple.getArchName();
+ }
+ StringRef BundleID;
+ if (EffectiveArch == "amdgcnspirv") {
+ BundleID = Saver.save("hip-spirv64-amd-amdhsa--" + EffectiveArch);
+ } else if (Triple.isSPIRV()) {
+ // ChipStar and other SPIR-V HIP targets: use hip-spirv64-<vendor>-<os>--<arch>
+ BundleID = Saver.save("hip-spirv64-" + Triple.getVendorName() + "-" +
+ Triple.getOSName() + "--" + EffectiveArch);
+ } else {
+ BundleID = Saver.save("hip-amdgcn-amd-amdhsa--" + EffectiveArch);
+ }
+ Targets.push_back(BundleID);
}
CmdArgs.push_back(Saver.save(llvm::join(Targets, ",")));
@@ -464,7 +469,7 @@ fatbinary(ArrayRef<std::tuple<StringRef, StringRef, StringRef>> InputFiles,
#else
CmdArgs.push_back("-input=/dev/null");
#endif
- for (const auto &[File, Triple, Arch] : InputFiles)
+ for (const auto &[File, Arch, TripleStr] : InputFilesWithTriple)
CmdArgs.push_back(Saver.save("-input=" + File));
CmdArgs.push_back(Saver.save("-output=" + *TempFileOrErr));
@@ -528,7 +533,107 @@ 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 accross translation units).
+ StringRef MergedFile;
+ if (InputFiles.size() > 1) {
+ Expected<std::string> LinkPath =
+ findProgram("llvm-link", {getMainExecutable("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", {getMainExecutable("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;
+ }
+
+ ProcessedInputFiles.push_back(OptOutputFile);
+
+ // Step 3: Configure the inner clang for SPIR-V backend codegen.
+ 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.
+ 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.
@@ -587,8 +692,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);
@@ -827,13 +938,14 @@ bundleCuda(ArrayRef<OffloadingImage> Images, const ArgList &Args) {
Expected<SmallVector<std::unique_ptr<MemoryBuffer>>>
bundleHIP(ArrayRef<OffloadingImage> Images, const ArgList &Args) {
- SmallVector<std::tuple<StringRef, StringRef, StringRef>, 4> InputFiles;
+ // Collect (file, arch, triple) tuples for bundling
+ SmallVector<std::tuple<StringRef, StringRef, StringRef>, 4> InputFilesWithTriple;
for (const OffloadingImage &Image : Images)
- InputFiles.emplace_back(std::make_tuple(Image.Image->getBufferIdentifier(),
- Image.StringData.lookup("triple"),
- Image.StringData.lookup("arch")));
+ InputFilesWithTriple.emplace_back(Image.Image->getBufferIdentifier(),
+ Image.StringData.lookup("arch"),
+ Image.StringData.lookup("triple"));
- auto FileOrErr = amdgcn::fatbinary(InputFiles, Args);
+ auto FileOrErr = amdgcn::fatbinary(InputFilesWithTriple, Args);
if (!FileOrErr)
return FileOrErr.takeError();
@@ -1322,6 +1434,19 @@ int main(int Argc, char **Argv) {
DryRun = Args.hasArg(OPT_dry_run);
SaveTemps = Args.hasArg(OPT_save_temps);
CudaBinaryPath = Args.getLastArgValue(OPT_cuda_path_EQ).str();
+ HipPath = Args.getLastArgValue(OPT_hip_path_EQ).str();
+
+ // Also extract --hip-path= from --device-compiler= args, since the outer
+ // driver passes it that way for HIP/SPIR-V targets.
+ if (HipPath.empty()) {
+ for (StringRef Arg : Args.getAllArgValues(OPT_device_compiler_args_EQ)) {
+ auto [Triple, Value] = Arg.split('=');
+ if (Value.starts_with("--hip-path=")) {
+ HipPath = Value.substr(strlen("--hip-path=")).str();
+ break;
+ }
+ }
+ }
llvm::Triple Triple(
Args.getLastArgValue(OPT_host_triple_EQ, sys::getDefaultTargetTriple()));
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
index 4086d3228ff67..4c982243c2b0b 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.cpp
@@ -1775,6 +1775,10 @@ static bool generateAtomicInst(const SPIRV::IncomingCall *Call,
case SPIRV::OpAtomicXor:
case SPIRV::OpAtomicAnd:
case SPIRV::OpAtomicExchange:
+ case SPIRV::OpAtomicSMin:
+ case SPIRV::OpAtomicSMax:
+ case SPIRV::OpAtomicUMin:
+ case SPIRV::OpAtomicUMax:
return buildAtomicRMWInst(Call, Opcode, MIRBuilder, GR);
case SPIRV::OpMemoryBarrier:
return buildBarrierInst(Call, SPIRV::OpMemoryBarrier, MIRBuilder, GR);
diff --git a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
index 3898dca7dcb97..a6cbbdb76f123 100644
--- a/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
+++ b/llvm/lib/Target/SPIRV/SPIRVBuiltins.td
@@ -628,6 +628,16 @@ defm : DemangledNativeBuiltin<"atomic_fetch_sub_explicit", OpenCL_std, Atomic, 3
defm : DemangledNativeBuiltin<"atomic_fetch_or_explicit", OpenCL_std, Atomic, 3, 4, OpAtomicOr>;
defm : DemangledNativeBuiltin<"atomic_fetch_xor_explicit", OpenCL_std, Atomic, 3, 4, OpAtomicXor>;
defm : DemangledNativeBuiltin<"atomic_fetch_and_explicit", OpenCL_std, Atomic, 3, 4, OpAtomicAnd>;
+// atomic_fetch_min/max: signed vs unsigned determined by argument type prefix.
+// The lookup adds "s_" for signed (int, long) and "u_" for unsigned (uint, ulong).
+defm : DemangledNativeBuiltin<"s_atomic_fetch_min", OpenCL_std, Atomic, 2, 4, OpAtomicSMin>;
+defm : DemangledNativeBuiltin<"u_atomic_fetch_min", OpenCL_std, Atomic, 2, 4, OpAtomicUMin>;
+defm : DemangledNativeBuiltin<"s_atomic_fetch_max", OpenCL_std, Atomic, 2, 4, OpAtomicSMax>;
+defm : DemangledNativeBuiltin<"u_atomic_fetch_max", OpenCL_std, Atomic, 2, 4, OpAtomicUMax>;
+defm : DemangledNativeBuiltin<"s_atomic_fetch_min_explicit", OpenCL_std, Atomic, 3, 4, OpAtomicSMin>;
+defm : DemangledNativeBuiltin<"u_atomic_fetch_min_explicit", OpenCL_std, Atomic, 3, 4, OpAtomicUMin>;
+defm : DemangledNativeBuiltin<"s_atomic_fetch_max_explicit", OpenCL_std, Atomic, 3, 4, OpAtomicSMax>;
+defm : DemangledNativeBuiltin<"u_atomic_fetch_max_explicit", OpenCL_std, Atomic, 3, 4, OpAtomicUMax>;
defm : DemangledNativeBuiltin<"atomic_flag_test_and_set", OpenCL_std, Atomic, 1, 1, OpAtomicFlagTestAndSet>;
defm : DemangledNativeBuiltin<"__spirv_AtomicFlagTestAndSet", OpenCL_std, Atomic, 3, 3, OpAtomicFlagTestAndSet>;
defm : DemangledNativeBuiltin<"atomic_flag_test_and_set_explicit", OpenCL_std, Atomic, 2, 3, OpAtomicFlagTestAndSet>;
diff --git a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
index a1aeeff69fb42..5532b0b66f27a 100644
--- a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
@@ -90,7 +90,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;
diff --git a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
index c69eb6f92a7c4..e3d45bab469c1 100644
--- a/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVUtils.cpp
@@ -1197,12 +1197,18 @@ Type *reconstitutePeeledArrayType(Type *Ty) {
std::optional<SPIRV::LinkageType::LinkageType>
getSpirvLinkageTypeFor(const SPIRVSubtarget &ST, const GlobalValue &GV) {
+ // Declarations must always get Import linkage so they can be resolved at
+ // link time, even if they have hidden visibility (e.g. from
+ // -fapply-global-visibility-to-externs used by the HIPSPV toolchain).
+ if (GV.isDeclarationForLinker()) {
+ if (GV.hasLocalLinkage())
+ return std::nullopt;
+ return SPIRV::LinkageType::Import;
+ }
+
if (GV.hasLocalLinkage() || GV.hasHiddenVisibility())
return std::nullopt;
- if (GV.isDeclarationForLinker())
- return SPIRV::LinkageType::Import;
-
if (GV.hasLinkOnceODRLinkage() &&
ST.canUseExtension(SPIRV::Extension::SPV_KHR_linkonce_odr))
return SPIRV::LinkageType::LinkOnceODR;
>From 0fa3bdcdccdeb0d81e2db5cfdb90793210dfdb9d Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Sun, 8 Feb 2026 06:13:33 +0200
Subject: [PATCH 12/13] [SPIRV] Use pointer-width array lengths for chipStar
targets
Use target pointer width for OpTypeArray length constants instead
of hardcoded i32. Also set SPIR-V version 1.2 for ChipStar OS
triples.
---
llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp | 8 ++++++--
llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp | 2 ++
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
index 14f1c97741ccc..6b8e37dcceabc 100644
--- a/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVGlobalRegistry.cpp
@@ -893,13 +893,17 @@ SPIRVType *SPIRVGlobalRegistry::getOpTypeArray(uint32_t NumElems,
bool EmitIR) {
assert((ElemType->getOpcode() != SPIRV::OpTypeVoid) &&
"Invalid array element type");
- SPIRVType *SpvTypeInt32 = getOrCreateSPIRVIntegerType(32, MIRBuilder);
+ // Use the target's pointer width for array length constants so that
+ // spirv64 targets emit i64 lengths. Some OpenCL drivers (e.g. Intel)
+ // expect the array length width to match the target pointer width.
+ unsigned LenWidth = getPointerSize();
+ SPIRVType *SpvTypeLenInt = getOrCreateSPIRVIntegerType(LenWidth, MIRBuilder);
SPIRVType *ArrayType = nullptr;
const SPIRVSubtarget &ST =
cast<SPIRVSubtarget>(MIRBuilder.getMF().getSubtarget());
if (NumElems != 0) {
Register NumElementsVReg =
- buildConstantInt(NumElems, MIRBuilder, SpvTypeInt32, EmitIR);
+ buildConstantInt(NumElems, MIRBuilder, SpvTypeLenInt, EmitIR);
ArrayType = createOpType(MIRBuilder, [&](MachineIRBuilder &MIRBuilder) {
return MIRBuilder.buildInstr(SPIRV::OpTypeArray)
.addDef(createTypeVReg(MIRBuilder))
diff --git a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
index 5532b0b66f27a..f2ca4602d31a4 100644
--- a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp
@@ -81,6 +81,8 @@ SPIRVSubtarget::SPIRVSubtarget(const Triple &TT, const std::string &CPU,
default:
if (TT.getVendor() == Triple::AMD)
SPIRVVersion = VersionTuple(1, 6);
+ else if (TT.getOS() == Triple::ChipStar)
+ SPIRVVersion = VersionTuple(1, 2);
else
SPIRVVersion = VersionTuple(1, 4);
}
>From b5e4edc35202fc183168c2564d4dd321545d5cee Mon Sep 17 00:00:00 2001
From: Paulius Velesko <pvelesko at pglc.io>
Date: Tue, 10 Feb 2026 11:22:34 +0200
Subject: [PATCH 13/13] [HIPSPV] Update tests for dual SPIR-V codegen paths
Add test RUN lines for both the native SPIR-V backend path and
the llvm-spirv translator path (-no-use-spirv-backend).
---
clang/test/Driver/hipspv-toolchain.hip | 55 +++++++++++++++++++++-----
1 file changed, 46 insertions(+), 9 deletions(-)
diff --git a/clang/test/Driver/hipspv-toolchain.hip b/clang/test/Driver/hipspv-toolchain.hip
index ae8d65313abfb..96751bfb8ddc8 100644
--- a/clang/test/Driver/hipspv-toolchain.hip
+++ b/clang/test/Driver/hipspv-toolchain.hip
@@ -68,8 +68,19 @@
// WRAPPER: clang{{.*}}" --no-default-config -o {{[^ ]*.img}}
// 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
+// WRAPPER-SAME: -x ir
+
+// Linker wrapper with -no-use-spirv-backend uses llvm-spirv translator.
+// RUN: clang-linker-wrapper --dry-run \
+// RUN: --device-compiler=spirv64-unknown-chipstar=--hip-path="%S/Inputs/hipspv" \
+// RUN: --device-compiler=spirv64-unknown-chipstar=-no-use-spirv-backend \
+// RUN: --host-triple=x86_64-unknown-linux-gnu \
+// RUN: --linker-path=ld --emit-fatbin-only -o /dev/null %t.dev.out \
+// RUN: 2>&1 | FileCheck %s --check-prefix=WRAPPER-TRANSLATOR
+
+// WRAPPER-TRANSLATOR: --spirv-max-version=1.2
+// WRAPPER-TRANSLATOR-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,10 +95,29 @@
// 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"
+// Default chipStar: in-tree SPIR-V backend (native).
+// 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"
// CHIPSTAR-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
+// chipStar with -no-use-spirv-backend: llvm-spirv translator path.
+// RUN: %clang -### --no-default-config -o %t.dummy.img \
+// RUN: --target=spirv64-unknown-chipstar %t.dummy.o \
+// RUN: --hip-path="%S/Inputs/hipspv" -no-use-spirv-backend \
+// RUN: 2>&1 | FileCheck %s --check-prefix=CHIPSTAR-TRANSLATOR -DHIP_PATH=%S/Inputs/hipspv
+
+// CHIPSTAR-TRANSLATOR: {{".*llvm-link"}}
+// CHIPSTAR-TRANSLATOR-SAME: "-o" [[LINK_BC:".*bc"]] "{{[^ ]*.o}}"
+
+// CHIPSTAR-TRANSLATOR: {{".*opt"}} [[LINK_BC]] "-load-pass-plugin"
+// CHIPSTAR-TRANSLATOR-SAME: "[[HIP_PATH]]/lib/libLLVMHipSpvPasses.so"
+// CHIPSTAR-TRANSLATOR-SAME: "-passes=hip-post-link-passes" "-o" [[LOWER_BC:".*bc"]]
+
+// CHIPSTAR-TRANSLATOR: {{".*llvm-spirv"}} "--spirv-max-version=1.2"
+// CHIPSTAR-TRANSLATOR-SAME: "--spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups"
+// CHIPSTAR-TRANSLATOR-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*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" \
@@ -100,8 +130,10 @@
// 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"
+// Subarch chipStar also defaults to native backend.
+// 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"
// CHIPSTAR-SUBARCH-SAME: [[LOWER_BC]] "-o" "[[SPIRV_OUT:.*img]]"
//-----------------------------------------------------------------------------
@@ -117,7 +149,12 @@
// 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
+// RUN: --hip-path="%S/Inputs/hipspv" -no-use-spirv-backend -o /dev/null 2>&1 \
+// RUN: | FileCheck %s --check-prefix=VERSIONED-CHIPSTAR
+
+// Accept either versioned (llvm-spirv-[[VERSION]] from PATH) or clang-adjacent path.
+// VERSIONED: {{.*}}llvm-spirv
-// VERSIONED: {{.*}}llvm-spirv-[[VERSION]]
+// ChipStar with -no-use-spirv-backend may use clang-adjacent llvm-spirv (no version in name).
+// VERSIONED-CHIPSTAR: --spirv-max-version=1.2
+// VERSIONED-CHIPSTAR-SAME: --spirv-ext=-all,+SPV_INTEL_function_pointers,+SPV_INTEL_subgroups
More information about the cfe-commits
mailing list