[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 00:59:43 PST 2026


https://github.com/pvelesko created https://github.com/llvm/llvm-project/pull/180903

Build on #123359 to add chipStar (HIP-to-SPIR-V) support for the new offload
driver. Replace the external llvm-spirv translator with LLVM's in-tree SPIR-V
backend and fix several driver/toolchain issues exposed by the switch.

## Changes

**SPIR-V backend**
- Recognize ChipStar as Kernel execution environment
- Add atomic_fetch_min/max builtin mappings
- Give Import linkage to hidden-visibility declarations
- Use explicit data layouts (drop n8:16:32:64)
- Use pointer-width OpTypeArray length constants; set SPIR-V 1.2

**Toolchain / driver**
- Add chipStar merge-and-translate flow (llvm-link -> opt -> SPIR-V backend)
- Add chipStar codegen path in HIPSPV.cpp and RDC pipeline in ClangLinkerWrapper
- Add --hip-path to clang-linker-wrapper for locating HipSpvPasses
- Derive llvm-link/opt/llvm-spirv paths from the clang binary location
- Skip host target options for HIPSPV device compilations

**Platform fixes**
- [Darwin] Guard against uninitialized target during HIP offload setup
- [clang] Handle unknown OS in alignedAllocMinVersion
- [HIP] Use Mach-O section format for fatbinary on macOS

>From 086f2bcfaecb57bb3c9669474bf55d536664f53e 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 8556342a061d5c6587260fcb8a7e37247bdb90a3 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 14cb8097d5041c7e593d0e5544c73d0dae09b8a3 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 f4916d27d33b244ee1587da298553e406e9c7f6a 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 d5372a6b158c04da1f4757033ab4dbd4fd7d6ade 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 a09c3db42b423..f1523466e2506 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -3545,7 +3545,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 9a357e7c3e83df4f610f4782e9217e5aae1f3b46 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 ab6eab22a3401db18e699a9445ca3f98a8365418 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 e6705497d04e77c7ad505bf36d2201e3d44b5788 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 1bbaf02dd39075db4a67a7b0b5357de1b6b49279 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 04b0f26d155a042783ecd54dae2dd362333e3f9d 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 69909315633d63c040b81bf072149fa8e26c3bf9 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 6a798057240de..ec1229371d1b8 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.getEnvironment() == 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 385c2c20298c298fc494f962bb365737011dca87 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 ec1229371d1b8..f70bc5797cf41 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 d176f543b7a474b1b2e4882af116a1294e60863f 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