https://github.com/pvelesko updated https://github.com/llvm/llvm-project/pull/180903
>From 808a8ccde7a9a3b6dbccf8795c8c647e15ad7099 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 b47978de7f248587ea7bf7861c5e504d8867258c Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 5dc97e14bfaaf92893307dd696898a982936122f Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 6cbc78ce962ae1e7ef109f4c134e4e011fa3fbe4 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 5399df8eb8cc93ea6bc6d258b3f6e73cca27bc32 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 3897bb9a743d14f1e2bdc28877aa0fc58aa3a9ef Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 b8381b5bc1589a9aa4d2b61161cef5b4e97aeec3 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 << ",@object\n"; ObjStream << " .section .hip_fatbin,\"a\",@progbits\n"; >From 7d5e4b3719c6db575918aebbdad3afc44012e7d9 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 16b8da35b2117535db5fc0813365b4d99d10e7c5 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 e63139ad181d9866d3cf64397ac2a95803d5030e Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 5e8816ef9bc3162f969f1058b270c07ef1e64766 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 f9bbb2e5cbcaeb4d390019ed217766729aa18263 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 6a2ac8aad10c2e11eff719db007a06fc7c0c9144 Mon Sep 17 00:00:00 2001 From: Paulius Velesko <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
