https://github.com/srividya-sundaram updated https://github.com/llvm/llvm-project/pull/229248
>From 4e07d042e43ba094a8f05c838631d32c24853e84 Mon Sep 17 00:00:00 2001 From: srividya sundaram <[email protected]> Date: Sat, 12 Sep 2026 19:09:30 -0700 Subject: [PATCH 1/2] [TargetParser][Clang] Parse Intel GPU architecture names Add support for Intel GPU names in --offload-arch. #222072 added the mapping from a detected GPU version to its name. This PR adds the reverse mapping needed when a user specifies a name through --offload-arch. Previously, clang recognized only the hardcoded name bmg_g21. The shared Intel GPU list now recognizes names such as xe-pvc, xe-dg2, bmg_g21, and xe_12.60.7. Names for the same GPU IP are treated as aliases, so SYCL creates one device job and matches all spellings consistently. clang-sycl-linker uses the canonical name for the image and the name expected by ocloc. User-visible changes: - A device is printed and labelled by its canonical name, e.g. bmg_g21 becomes xe-bmg-g21. - Every listed Intel GPU name is compiled ahead of time with ocloc, as bmg_g21 already was. - Products on the same GPU IP become aliases, so 13 GPUKind enumerators are removed, e.g. GK_XE_ARL_U is now GK_XE_MTL_U. - An Intel name is no longer accepted as an NVPTX CPU; -target-cpu bmg_g21 was accepted before. - An Intel name with a target-ID suffix, e.g. xe-pvc:foo, is not turned into a device. Canonicalization is limited to SYCL, the only offload kind that compiles for Intel GPUs today. Selecting the SPIR-V target from an Intel GPU name and diagnosing unknown names are handled separately in follow-up PRs. Assisted-by: Claude Opus 5.5 --- clang/include/clang/Basic/OffloadArch.h | 26 ++-- clang/lib/Basic/OffloadArch.cpp | 12 +- clang/lib/Basic/Targets/NVPTX.h | 11 +- clang/lib/Driver/Driver.cpp | 22 ++- clang/lib/Driver/ToolChain.cpp | 4 + clang/lib/Driver/ToolChains/Clang.cpp | 8 +- clang/test/Driver/sycl-offload-jit.cpp | 61 ++++++++- .../test/Misc/target-invalid-cpu-note/nvptx.c | 4 + .../OffloadTools/clang-sycl-linker/basic.ll | 13 +- .../clang-sycl-linker/ClangSYCLLinker.cpp | 15 ++- clang/unittests/Basic/OffloadArchTest.cpp | 23 +++- .../TargetParser/IntelGPUTargetParser.def | 59 +++++--- .../llvm/TargetParser/IntelGPUTargetParser.h | 41 +++++- .../lib/TargetParser/IntelGPUTargetParser.cpp | 127 +++++++++++++++++- .../TargetParser/IntelGPUTargetParserTest.cpp | 104 ++++++++++++++ 15 files changed, 473 insertions(+), 57 deletions(-) diff --git a/clang/include/clang/Basic/OffloadArch.h b/clang/include/clang/Basic/OffloadArch.h index a0e5e72db220bb..95a17952d4a2a7 100644 --- a/clang/include/clang/Basic/OffloadArch.h +++ b/clang/include/clang/Basic/OffloadArch.h @@ -22,6 +22,9 @@ enum GPUKind : uint8_t; namespace AMDGPU { enum GPUKind : uint8_t; } +namespace IntelGPU { +enum GPUKind : uint8_t; +} } // namespace llvm namespace clang { @@ -37,17 +40,11 @@ class OffloadArch { NVPTX, // Kind is an llvm::NVPTX::GPUKind. AMDGPU, // Kind is an llvm::AMDGPU::GPUKind. AMDGCNSPIRV, // The 'amdgcnspirv' pseudo target. - IntelCPU, // Kind is an IntelArch. - IntelGPU, // Kind is an IntelArch. + IntelCPU, // The 'graniterapids' processor; Kind is unused. + IntelGPU, // Kind is an llvm::IntelGPU::GPUKind. Generic, // The 'generic' processor model. }; - // Intel architectures, which have no TargetParser list yet. - enum class IntelArch : uint32_t { - GRANITERAPIDS, - BMG_G21, - }; - private: // Interpreted according to V; unused for the tagless TargetArch values. uint32_t Kind = 0; @@ -64,8 +61,13 @@ class OffloadArch { static OffloadArch getAMDGPU(llvm::AMDGPU::GPUKind K) { return {TargetArch::AMDGPU, static_cast<uint32_t>(K)}; } - static constexpr OffloadArch getIntel(TargetArch V, IntelArch A) { - return {V, static_cast<uint32_t>(A)}; + /// An Intel GPU, named by its entry in the TargetParser device list. + static OffloadArch getIntelGPU(llvm::IntelGPU::GPUKind K) { + return {TargetArch::IntelGPU, static_cast<uint32_t>(K)}; + } + /// Intel CPU offload, which has a single target, so no kind is needed. + static constexpr OffloadArch getIntelCPU() { + return {TargetArch::IntelCPU, 0}; } static constexpr OffloadArch getUnused() { return {TargetArch::Unused, 0}; } static constexpr OffloadArch getUnknown() { return {TargetArch::Unknown, 0}; } @@ -97,6 +99,10 @@ class OffloadArch { llvm::AMDGPU::GPUKind amdgpuKind() const { return static_cast<llvm::AMDGPU::GPUKind>(Kind); } + /// Which Intel GPU this is; only meaningful when isIntelGPU(). + llvm::IntelGPU::GPUKind intelGPUKind() const { + return static_cast<llvm::IntelGPU::GPUKind>(Kind); + } bool operator==(const OffloadArch &Other) const { return V == Other.V && Kind == Other.Kind; diff --git a/clang/lib/Basic/OffloadArch.cpp b/clang/lib/Basic/OffloadArch.cpp index f8c811f552f387..adc94b35ce025b 100644 --- a/clang/lib/Basic/OffloadArch.cpp +++ b/clang/lib/Basic/OffloadArch.cpp @@ -10,6 +10,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/TargetParser/AMDGPUTargetParser.h" +#include "llvm/TargetParser/IntelGPUTargetParser.h" #include "llvm/TargetParser/NVPTXTargetParser.h" #include "llvm/TargetParser/Triple.h" @@ -38,7 +39,7 @@ const char *OffloadArchToString(OffloadArch A) { case OffloadArch::TargetArch::IntelCPU: return "graniterapids"; case OffloadArch::TargetArch::IntelGPU: - return "bmg_g21"; + return llvm::IntelGPU::getArchName(A.intelGPUKind()).data(); case OffloadArch::TargetArch::Generic: return "generic"; } @@ -73,18 +74,17 @@ OffloadArch StringToOffloadArch(llvm::StringRef S) { return OffloadArch::getAMDGCNSPIRV(); if (S == "generic") return OffloadArch::getGeneric(); + // Intel CPU offload has a single processor and no TargetParser list. if (S == "graniterapids") - return OffloadArch::getIntel(OffloadArch::TargetArch::IntelCPU, - OffloadArch::IntelArch::GRANITERAPIDS); - if (S == "bmg_g21") - return OffloadArch::getIntel(OffloadArch::TargetArch::IntelGPU, - OffloadArch::IntelArch::BMG_G21); + return OffloadArch::getIntelCPU(); // Otherwise defer to the vendor TargetParser GPU lists. if (llvm::NVPTX::GPUKind NV = llvm::NVPTX::parseArch(S)) return OffloadArch::getNVPTX(NV); if (llvm::AMDGPU::GPUKind AK = llvm::AMDGPU::parseArchAMDGCN(S)) return OffloadArch::getAMDGPU(AK); + if (llvm::IntelGPU::GPUKind IK = llvm::IntelGPU::parseArch(S)) + return OffloadArch::getIntelGPU(IK); return OffloadArch::getUnknown(); } diff --git a/clang/lib/Basic/Targets/NVPTX.h b/clang/lib/Basic/Targets/NVPTX.h index b6d2bfd3201034..45ba8322cf5fca 100644 --- a/clang/lib/Basic/Targets/NVPTX.h +++ b/clang/lib/Basic/Targets/NVPTX.h @@ -135,8 +135,13 @@ class LLVM_LIBRARY_VISIBILITY NVPTXTargetInfo : public TargetInfo { return TargetInfo::CharPtrBuiltinVaList; } + // StringToOffloadArch recognizes architectures from all vendors. Accept only + // the ones NVPTX accepted before, so Sema does not accept an Intel GPU name + // as an NVPTX CPU. bool isValidCPUName(StringRef Name) const override { - return !StringToOffloadArch(Name).isUnknown(); + OffloadArch Arch = StringToOffloadArch(Name); + return Arch.isNVPTX() || Arch.isAMDGPU() || Arch.isAMDGCNSPIRV() || + Arch.isGeneric() || Arch.isUnused(); } void fillValidCPUList(SmallVectorImpl<StringRef> &Values) const override { @@ -144,8 +149,10 @@ class LLVM_LIBRARY_VISIBILITY NVPTXTargetInfo : public TargetInfo { } bool setCPU(StringRef Name) override { + if (!isValidCPUName(Name)) + return false; GPU = StringToOffloadArch(Name); - return !GPU.isUnknown(); + return true; } void setSupportedOpenCLOpts() override { diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index 57beab92011a9f..bbcf88d8237762 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -3899,15 +3899,20 @@ void Driver::BuildActions(Compilation &C, DerivedArgList &Args, } /// Returns the canonical name for the offloading architecture when using a HIP -/// or CUDA architecture. +/// or CUDA architecture, or an Intel GPU for SYCL. static StringRef getCanonicalArchString(Compilation &C, const llvm::opt::DerivedArgList &Args, StringRef ArchStr, - const llvm::Triple &Triple) { + const llvm::Triple &Triple, + Action::OffloadKind Kind) { // Lookup the CUDA / HIP architecture string. Only report an error if we were // expecting the triple to be only NVPTX / AMDGPU. OffloadArch Arch = StringToOffloadArch(getProcessorFromTargetID(Triple, ArchStr)); + // An Intel name takes no target features, so it must parse in full; otherwise + // "xe-pvc:garbage" would silently become "xe-pvc". + if (Arch.isIntel() && ArchStr.contains(':')) + Arch = OffloadArch::getUnknown(); if (Triple.isNVPTX() && (Arch.isUnknown() || !Arch.isNVPTX())) { C.getDriver().Diag(clang::diag::err_drv_offload_bad_gpu_arch) << "CUDA" << ArchStr; @@ -3930,7 +3935,10 @@ static StringRef getCanonicalArchString(Compilation &C, } } - if (Arch.isNVPTX()) + // A SYCL Intel GPU has several accepted spellings, e.g. an alias or a numeric + // name, so canonicalize it for the same reason as an NVPTX one: two spellings + // of one device must be one architecture. + if (Arch.isNVPTX() || (Kind == Action::OFK_SYCL && Arch.isIntelGPU())) return Args.MakeArgStringRef(OffloadArchToString(Arch)); if (Arch.isAMDGPU() || Arch.isAMDGCNSPIRV()) { @@ -3991,7 +3999,7 @@ Driver::getOffloadArchs(Compilation &C, const llvm::opt::DerivedArgList &Args, for (auto ArchStr : *GPUsOrErr) { StringRef CanonicalStr = getCanonicalArchString( - C, Args, Args.MakeArgString(ArchStr), TC.getTriple()); + C, Args, Args.MakeArgString(ArchStr), TC.getTriple(), Kind); if (!CanonicalStr.empty()) Archs.insert(CanonicalStr); else @@ -3999,7 +4007,7 @@ Driver::getOffloadArchs(Compilation &C, const llvm::opt::DerivedArgList &Args, } } else { StringRef CanonicalStr = - getCanonicalArchString(C, Args, Arch, TC.getTriple()); + getCanonicalArchString(C, Args, Arch, TC.getTriple(), Kind); if (!CanonicalStr.empty()) Archs.insert(CanonicalStr); else @@ -4012,7 +4020,7 @@ Driver::getOffloadArchs(Compilation &C, const llvm::opt::DerivedArgList &Args, Archs.clear(); } else { StringRef ArchStr = - getCanonicalArchString(C, Args, Arch, TC.getTriple()); + getCanonicalArchString(C, Args, Arch, TC.getTriple(), Kind); Archs.erase(ArchStr); } } @@ -4068,7 +4076,7 @@ Driver::getOffloadArchs(Compilation &C, const llvm::opt::DerivedArgList &Args, ? "" : OffloadArchToString(TripleOffloadArch); StringRef CanonicalStr = - getCanonicalArchString(C, Args, ArchStr, TC.getTriple()); + getCanonicalArchString(C, Args, ArchStr, TC.getTriple(), Kind); if (!CanonicalStr.empty()) Archs.insert(CanonicalStr); } diff --git a/clang/lib/Driver/ToolChain.cpp b/clang/lib/Driver/ToolChain.cpp index 255a9c9602b540..d7928cd10ea242 100644 --- a/clang/lib/Driver/ToolChain.cpp +++ b/clang/lib/Driver/ToolChain.cpp @@ -2189,7 +2189,11 @@ llvm::opt::DerivedArgList *ToolChain::TranslateXarchArgs( Skip = IsDevice; } else if (A->getOption().matches(options::OPT_Xarch__)) { StringRef Val = A->getValue(); + // A SYCL Intel GPU has several spellings, so -Xarch_ may name it by + // another. NeedTrans = Val == getArchName() || (BA && Val == BA.ArchName) || + (OFK == Action::OFK_SYCL && BA && BA.Arch.isIntelGPU() && + StringToOffloadArch(Val) == BA.Arch) || isXArchCompatibleTripleArch(Triple, Val); Skip = !NeedTrans; } diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index 2325e7cc552ef9..7071ee6b3bebe7 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -9864,8 +9864,14 @@ void LinkerWrapper::ConstructJob(Compilation &C, const JobAction &JA, // If the user explicitly requested it via `--offload-arch` we should // extract it from any static libraries if present. - for (StringRef Arg : ToolChainArgs.getAllArgValues(OPT_offload_arch_EQ)) + // A SYCL Intel GPU is requested by its canonical name, the one its device + // images are labeled with, whichever spelling the user wrote. + for (StringRef Arg : ToolChainArgs.getAllArgValues(OPT_offload_arch_EQ)) { + OffloadArch ID = StringToOffloadArch(Arg); + if (Kind == Action::OFK_SYCL && ID.isIntelGPU()) + Arg = OffloadArchToString(ID); CmdArgs.emplace_back(Args.MakeArgString("--should-extract=" + Arg)); + } // If this is OpenMP the device linker will need `-lompdevice`. if (Kind == Action::OFK_OpenMP && !Args.hasArg(OPT_no_offloadlib) && diff --git a/clang/test/Driver/sycl-offload-jit.cpp b/clang/test/Driver/sycl-offload-jit.cpp index 82733ccd2bc6f2..cb4283db51d25b 100644 --- a/clang/test/Driver/sycl-offload-jit.cpp +++ b/clang/test/Driver/sycl-offload-jit.cpp @@ -90,11 +90,68 @@ // RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl -fno-gpu-rdc \ // RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=generic --offload-arch=bmg_g21 \ // RUN: -c %s 2>&1 | FileCheck -check-prefixes=CHK-PHASES-NORDC-ARCHS %s -// CHK-PHASES-NORDC-ARCHS: 7: offload, "device-sycl (spirv64-unknown-unknown:bmg_g21)" {6}, ir -// CHK-PHASES-NORDC-ARCHS: 12: offload, "device-sycl (spirv64-unknown-unknown:generic)" {11}, ir +// CHK-PHASES-NORDC-ARCHS: 7: offload, "device-sycl (spirv64-unknown-unknown:generic)" {6}, ir +// CHK-PHASES-NORDC-ARCHS: 12: offload, "device-sycl (spirv64-unknown-unknown:xe-bmg-g21)" {11}, ir // CHK-PHASES-NORDC-ARCHS-NEXT: 13: llvm-offload-binary, {7, 12}, image, (device-sycl) // CHK-PHASES-NORDC-ARCHS-NEXT: 14: clang-linker-wrapper, {13}, sycl-fatbin, (device-sycl) +/// Every spelling of an Intel GPU names one architecture, spelled canonically, +/// so several spellings of one device build it once. +// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=bmg_g21 \ +// RUN: --offload-arch=xe-bmg-g21 --offload-arch=xe_20.1.0 -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-SPELLINGS %s +// CHK-INTEL-SPELLINGS-NOT: offload, "device-sycl +// CHK-INTEL-SPELLINGS: offload, "device-sycl (spirv64-unknown-unknown:xe-bmg-g21)" +// CHK-INTEL-SPELLINGS-NOT: offload, "device-sycl + +/// Products built on one GPU IP version are one architecture too. +// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=xe-arl-u \ +// RUN: --offload-arch=xe-mtl-u --offload-arch=xe_12.70 -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-SAME-IP %s +// CHK-INTEL-SAME-IP-NOT: offload, "device-sycl +// CHK-INTEL-SAME-IP: offload, "device-sycl (spirv64-unknown-unknown:xe-mtl-u)" +// CHK-INTEL-SAME-IP-NOT: offload, "device-sycl + +/// Removing any spelling of a device removes the device. +// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=xe-bmg-g21 \ +// RUN: --offload-arch=generic --no-offload-arch=bmg_g21 -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-NO-ARCH %s +// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=bmg_g21 \ +// RUN: --offload-arch=generic --no-offload-arch=xe_20.1.0 -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-NO-ARCH %s +// CHK-INTEL-NO-ARCH-NOT: bmg +// CHK-INTEL-NO-ARCH: offload, "device-sycl (spirv64-unknown-unknown:generic)" +// CHK-INTEL-NO-ARCH-NOT: bmg + +/// An Intel GPU name takes no target-ID features, so a suffix is not dropped to +/// turn the name into a device. +// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=xe-pvc:foo -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-SUFFIX %s +// CHK-INTEL-SUFFIX: offload, "device-sycl (spirv64-unknown-unknown:xe-pvc:foo)" + +/// -Xarch_ may name an Intel GPU by any of its spellings. +// RUN: %clang -### --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=bmg_g21 \ +// RUN: -Xarch_bmg_g21 -DBMG -Xarch_xe-pvc -DPVC -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-XARCH %s +// CHK-INTEL-XARCH: "-fsycl-is-device" +// CHK-INTEL-XARCH-SAME: "-D" "BMG" +// CHK-INTEL-XARCH-NOT: "PVC" + +/// A static library's device code is requested by the canonical name. +// RUN: %clang -### --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=bmg_g21 %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-EXTRACT %s +// RUN: %clang -### --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=xe_20.1.0 %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-EXTRACT %s +// CHK-INTEL-EXTRACT: clang-linker-wrapper{{.*}}"--should-extract=xe-bmg-g21" + /// Multiple device triples are not supported today in non-RDC mode. // RUN: not %clang -### --target=x86_64-unknown-linux-gnu -fsycl -fno-gpu-rdc \ // RUN: --offload-targets=spirv64-unknown-unknown,spirv32-unknown-unknown -c %s 2>&1 \ diff --git a/clang/test/Misc/target-invalid-cpu-note/nvptx.c b/clang/test/Misc/target-invalid-cpu-note/nvptx.c index 20dbd8dc6aa3b6..b2a38b6b268253 100644 --- a/clang/test/Misc/target-invalid-cpu-note/nvptx.c +++ b/clang/test/Misc/target-invalid-cpu-note/nvptx.c @@ -2,6 +2,10 @@ // previous check finished matching (specifically, caret is not treated as // matching a start of line when used like this in FileCheck). +// An Intel GPU name is an offload architecture but never an NVPTX CPU. +// RUN: not %clang_cc1 -triple nvptx64-nvidia-cuda -target-cpu xe-pvc -fsyntax-only %s 2>&1 | FileCheck %s --check-prefix=INTEL +// INTEL: error: unknown target CPU 'xe-pvc' + // RUN: not %clang_cc1 -triple nvptx--- -target-cpu not-a-cpu -fsyntax-only %s 2>&1 | FileCheck %s --implicit-check-not={{[a-zA-Z0-9]}} // CHECK: error: unknown target CPU 'not-a-cpu' // CHECK-NEXT: note: valid target CPU values are: diff --git a/clang/test/OffloadTools/clang-sycl-linker/basic.ll b/clang/test/OffloadTools/clang-sycl-linker/basic.ll index 0ba81f61005a43..0376e7bf883388 100644 --- a/clang/test/OffloadTools/clang-sycl-linker/basic.ll +++ b/clang/test/OffloadTools/clang-sycl-linker/basic.ll @@ -115,9 +115,20 @@ ; AOT-INTEL-GPU: link: inputs: {{.*}}.bc, {{.*}}.bc output: [[LLVMLINKOUT:.*]].bc ; AOT-INTEL-GPU-NEXT: LLVM backend: input: [[LLVMLINKOUT]].bc, output: [[SPVSTEM:.*]].spv ; AOT-INTEL-GPU-NEXT: "{{.*}}ocloc{{.*}}" {{.*}}-device bmg_g21 -a -b {{.*}}-output [[SPVSTEM]].out -file [[SPVSTEM]].spv -; AOT-INTEL-GPU-NEXT: sycl-bundle: image kind: o, triple: spirv64, arch: bmg_g21 +; AOT-INTEL-GPU-NEXT: sycl-bundle: image kind: o, triple: spirv64, arch: xe-bmg-g21 ; AOT-INTEL-GPU-NOT: {{.+}} ; +; Test that every accepted spelling of an Intel GPU reaches ocloc as ocloc's own. +; RUN: clang-sycl-linker --dry-run -v --module-split-mode=link_unit -arch=xe-bmg-g21 %t/input1.bc -o %t/aot-gpu.out 2>&1 \ +; RUN: | FileCheck %s --check-prefix=AOT-INTEL-NAME +; RUN: clang-sycl-linker --dry-run -v --module-split-mode=link_unit -arch=xe_20.1.0 %t/input1.bc -o %t/aot-gpu.out 2>&1 \ +; RUN: | FileCheck %s --check-prefix=AOT-INTEL-NAME +; AOT-INTEL-NAME: "{{.*}}ocloc{{.*}}" {{.*}}-device bmg_g21 {{.*}}-output +; AOT-INTEL-NAME: sycl-bundle: image kind: o, triple: spirv64, arch: xe-bmg-g21 +; RUN: clang-sycl-linker --dry-run -v --module-split-mode=link_unit -arch=xe-pvc %t/input1.bc -o %t/aot-gpu.out 2>&1 \ +; RUN: | FileCheck %s --check-prefix=AOT-INTEL-PVC +; AOT-INTEL-PVC: "{{.*}}ocloc{{.*}}" {{.*}}-device pvc {{.*}}-output + ; Test that all --ocloc-options are passed to ocloc, even if they contain spaces or quotes. ; RUN: clang-sycl-linker --dry-run -v --module-split-mode=link_unit -arch=bmg_g21 %t/input1.bc -o %t/aot-gpu.out 2>&1 \ ; RUN: --ocloc-options="-a -b" --ocloc-options=-c --ocloc-options="d" --ocloc-options='"-e -f"' \ diff --git a/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp b/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp index 60ab7dc4e2a7d1..01463c15aa0dbf 100644 --- a/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp +++ b/clang/tools/clang-sycl-linker/ClangSYCLLinker.cpp @@ -716,8 +716,15 @@ static Error runAOTCompileIntelGPU(StringRef InputFile, StringRef OutputFile, StringRef Arch(Args.getLastArgValue(OPT_arch_EQ)); assert(!Arch.empty() && "Arch must be specified for AOT compilation"); + // ocloc spells a device without the "xe-" prefix and with "_" between words, + // e.g. "bmg_g21" for "xe-bmg-g21", so translate any accepted spelling. + StringRef Canonical = OffloadArchToString(StringToOffloadArch(Arch)); + [[maybe_unused]] bool HasPrefix = Canonical.consume_front("xe-"); + assert(HasPrefix && "Intel GPU name without the xe- prefix"); + std::string Device = Canonical.str(); + llvm::replace(Device, '-', '_'); CmdArgs.push_back("-device"); - CmdArgs.push_back(Arch); + CmdArgs.push_back(Device); // getAllArgValues returns a temporary vector; retain it so the StringRefs // remain valid through the executeCommands call below. @@ -1045,8 +1052,12 @@ static Error runSYCLLink(ArrayRef<std::unique_ptr<MemoryBuffer>> Inputs, TheImage.TheOffloadKind = OFK_SYCL; TheImage.StringData["triple"] = Args.MakeArgString(Result.TargetTriple.str()); + // Label an Intel GPU image with its canonical name, whichever spelling was + // given, so that one device's images always carry one arch. + StringRef Arch = Args.getLastArgValue(OPT_arch_EQ); + OffloadArch OA = StringToOffloadArch(Arch); TheImage.StringData["arch"] = - Args.MakeArgString(Args.getLastArgValue(OPT_arch_EQ)); + OA.isIntelGPU() ? OffloadArchToString(OA) : Args.MakeArgString(Arch); TheImage.StringData["symbols"] = SI.Symbols; TheImage.Image = std::move(*FileOrErr); Images.emplace_back(std::move(TheImage)); diff --git a/clang/unittests/Basic/OffloadArchTest.cpp b/clang/unittests/Basic/OffloadArchTest.cpp index 013ed75fbd57eb..980a88aba6114b 100644 --- a/clang/unittests/Basic/OffloadArchTest.cpp +++ b/clang/unittests/Basic/OffloadArchTest.cpp @@ -36,10 +36,12 @@ TEST(OffloadArchTest, TargetArchClassification) { EXPECT_TRUE(IntelCPU.isIntelCPU()); EXPECT_FALSE(IntelCPU.isIntelGPU()); - OffloadArch IntelGPU = parse("bmg_g21"); + OffloadArch IntelGPU = parse("xe-bmg-g21"); EXPECT_TRUE(IntelGPU.isIntel()); EXPECT_FALSE(IntelGPU.isIntelCPU()); EXPECT_TRUE(IntelGPU.isIntelGPU()); + EXPECT_TRUE(parse("xe-dg2").isIntelGPU()); + EXPECT_TRUE(parse("xe_12.60.7").isIntelGPU()); OffloadArch Generic = parse("generic"); EXPECT_FALSE(Generic.isNVPTX()); @@ -57,13 +59,30 @@ TEST(OffloadArchTest, Unknown) { TEST(OffloadArchTest, RoundTrip) { for (const char *Name : {"sm_52", "sm_90a", "gfx906", "gfx1201", "gfx12-generic", "amdgcnspirv", - "graniterapids", "bmg_g21", "generic"}) { + "graniterapids", "xe-bmg-g21", "xe-pvc", "xe-dg2", "generic"}) { OffloadArch A = parse(Name); EXPECT_FALSE(A.isUnknown()) << Name; EXPECT_STREQ(OffloadArchToString(A), Name); } } +// A spelling that is not the architecture name still denotes the device, and +// canonicalizes to that name rather than round-tripping to itself. +TEST(OffloadArchTest, IntelGPUAliases) { + // An alias, here the spelling accepted before the Intel GPU list existed. + EXPECT_EQ(parse("bmg_g21"), parse("xe-bmg-g21")); + // A numeric name, whose revision takes no part in the lookup. + EXPECT_EQ(parse("xe_12.60.0"), parse("xe-pvc")); + EXPECT_EQ(parse("xe_12.60.7"), parse("xe-pvc")); + EXPECT_EQ(parse("xe_12.60"), parse("xe-pvc")); + EXPECT_STREQ(OffloadArchToString(parse("bmg_g21")), "xe-bmg-g21"); + EXPECT_STREQ(OffloadArchToString(parse("xe_12.60.7")), "xe-pvc"); + + // A well-formed numeric name for a device this build does not know is as + // unknown as any other unknown name. + EXPECT_TRUE(parse("xe_99.99.0").isUnknown()); +} + TEST(OffloadArchTest, Defaults) { EXPECT_STREQ(OffloadArchToString(OffloadArch::CudaDefault()), "sm_52"); EXPECT_STREQ(OffloadArchToString(OffloadArch::HIPDefault()), "gfx906"); diff --git a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def index bd3b0a4634e066..c8faba2a11e1b8 100644 --- a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def +++ b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def @@ -21,7 +21,9 @@ // IGCA_FEATURE_SETS - Token naming the IGCA feature sets the device // implements: Core (none), Compute ("c"), // Render ("r"), ComputeExact ("ca") or -// RenderExact ("ra"). +// RenderExact ("ra"). It is a token rather than the +// suffix itself so that a typo is a build error; +// getIGCAName maps it onto the spelling. // // INTEL_GPU_COMPAT(NAME, KIND, IGCA_TARGET, IGCA_FEATURE_SETS) // A compatibility name that covers several minor versions, e.g. "xe-dg2", @@ -30,12 +32,28 @@ // offload-arch utility never prints one, but they are legal --offload-arch // values. The columns mean the same as above. // +// INTEL_GPU_ALIAS(NAME, KIND) +// A further name for a device that already has a row: "bmg_g21", which +// --offload-arch accepted before the list existed, or another product +// built on the same GPU IP version, e.g. "xe-arl-u" for "xe-mtl-u". Code +// for one GPU IP version is the same whichever product runs it, so these +// are one architecture. An alias declares no kind of its own and is never +// printed, so a device keeps one canonical name; it only widens what +// --offload-arch accepts. +// // A device is keyed by its major and minor version alone. The revision // component of the GPU IP version is deliberately left out of the key, so that // every revision of a device shares one name and one IGCA target. // -// Several devices can share a major and a minor version. The rows are ordered -// so that the name to print for such a group comes first. +// Products that share a major and a minor version form one INTEL_GPU row, named +// after the product the offload-arch utility prints; the other products are +// aliases. The exception is "xe-pvc-sdv": it has the same major and minor +// version as "xe-pvc", but the AOT compiler generates different code for it, so +// it gets its own row. +// +// Devices without a published name are left out on purpose; offload-arch names +// them by GPU IP version, as "xe_*.*.*". A device gets a row only once it has +// an official name. // //===----------------------------------------------------------------------===// @@ -46,10 +64,7 @@ INTEL_GPU("xe-cri", XE_CRI, 35, 11, 60, Compute) INTEL_GPU("xe-nvl-p", XE_NVL_P, 35, 10, 60, Render) INTEL_GPU("xe-nvl-u", XE_NVL_U, 30, 5, 60, Render) -INTEL_GPU("xe-nvl-h", XE_NVL_H, 30, 5, 60, Render) INTEL_GPU("xe-nvl-s", XE_NVL_S, 30, 4, 60, Render) -INTEL_GPU("xe-nvl-hx", XE_NVL_HX, 30, 4, 60, Render) -INTEL_GPU("xe-nvl-ul", XE_NVL_UL, 30, 4, 60, Render) INTEL_GPU("xe-wcl", XE_WCL, 30, 3, 50, Render) INTEL_GPU("xe-ptl-u", XE_PTL_U, 30, 1, 50, Render) INTEL_GPU("xe-ptl-h", XE_PTL_H, 30, 0, 50, Render) @@ -59,28 +74,18 @@ INTEL_GPU("xe-bmg-g21", XE_BMG_G21, 20, 1, 40, Render) INTEL_GPU("xe-arl-h", XE_ARL_H, 12, 74, 35, Render) INTEL_GPU("xe-mtl-h", XE_MTL_H, 12, 71, 30, Render) INTEL_GPU("xe-mtl-u", XE_MTL_U, 12, 70, 30, Render) -INTEL_GPU("xe-arl-u", XE_ARL_U, 12, 70, 30, Render) -INTEL_GPU("xe-arl-s", XE_ARL_S, 12, 70, 30, Render) INTEL_GPU("xe-pvc-vg", XE_PVC_VG, 12, 61, 20, ComputeExact) INTEL_GPU("xe-pvc", XE_PVC, 12, 60, 20, ComputeExact) INTEL_GPU("xe-pvc-sdv", XE_PVC_SDV, 12, 60, 20, ComputeExact) INTEL_GPU("xe-acm-g12", XE_ACM_G12, 12, 57, 15, RenderExact) -INTEL_GPU("xe-dg2-g12", XE_DG2_G12, 12, 57, 15, RenderExact) INTEL_GPU("xe-acm-g11", XE_ACM_G11, 12, 56, 15, RenderExact) -INTEL_GPU("xe-dg2-g11", XE_DG2_G11, 12, 56, 15, RenderExact) -INTEL_GPU("xe-ats-m75", XE_ATS_M75, 12, 56, 15, RenderExact) INTEL_GPU("xe-acm-g10", XE_ACM_G10, 12, 55, 15, RenderExact) -INTEL_GPU("xe-dg2-g10", XE_DG2_G10, 12, 55, 15, RenderExact) -INTEL_GPU("xe-ats-m150", XE_ATS_M150, 12, 55, 15, RenderExact) INTEL_GPU("xe-dg1", XE_DG1, 12, 10, 10, Render) INTEL_GPU("xe-adl-n", XE_ADL_N, 12, 4, 10, Render) INTEL_GPU("xe-adl-p", XE_ADL_P, 12, 3, 10, Render) -INTEL_GPU("xe-rpl-p", XE_RPL_P, 12, 3, 10, Render) INTEL_GPU("xe-adl-s", XE_ADL_S, 12, 2, 10, Render) -INTEL_GPU("xe-rpl-s", XE_RPL_S, 12, 2, 10, Render) INTEL_GPU("xe-rkl", XE_RKL, 12, 1, 10, Render) INTEL_GPU("xe-tgllp", XE_TGLLP, 12, 0, 10, Render) -INTEL_GPU("xe-tgl", XE_TGL, 12, 0, 10, Render) // Compatibility names, which cover a group of platforms #ifndef INTEL_GPU_COMPAT @@ -92,5 +97,27 @@ INTEL_GPU_COMPAT("xe-bmg", XE_BMG, 40, Render) INTEL_GPU_COMPAT("xe-mtl", XE_MTL, 30, Render) INTEL_GPU_COMPAT("xe-dg2", XE_DG2, 15, RenderExact) +// Aliases, which are further names of a device named above +#ifndef INTEL_GPU_ALIAS +#define INTEL_GPU_ALIAS(NAME, KIND) +#endif + +// Accepted before this list existed; to be removed in favor of "xe-bmg-g21". +INTEL_GPU_ALIAS("bmg_g21", XE_BMG_G21) +INTEL_GPU_ALIAS("xe-nvl-h", XE_NVL_U) +INTEL_GPU_ALIAS("xe-nvl-hx", XE_NVL_S) +INTEL_GPU_ALIAS("xe-nvl-ul", XE_NVL_S) +INTEL_GPU_ALIAS("xe-arl-u", XE_MTL_U) +INTEL_GPU_ALIAS("xe-arl-s", XE_MTL_U) +INTEL_GPU_ALIAS("xe-dg2-g12", XE_ACM_G12) +INTEL_GPU_ALIAS("xe-dg2-g11", XE_ACM_G11) +INTEL_GPU_ALIAS("xe-ats-m75", XE_ACM_G11) +INTEL_GPU_ALIAS("xe-dg2-g10", XE_ACM_G10) +INTEL_GPU_ALIAS("xe-ats-m150", XE_ACM_G10) +INTEL_GPU_ALIAS("xe-rpl-p", XE_ADL_P) +INTEL_GPU_ALIAS("xe-rpl-s", XE_ADL_S) +INTEL_GPU_ALIAS("xe-tgl", XE_TGLLP) + #undef INTEL_GPU #undef INTEL_GPU_COMPAT +#undef INTEL_GPU_ALIAS diff --git a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h index 4ae635e97167ba..8713de9579b48f 100644 --- a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h +++ b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h @@ -6,10 +6,8 @@ // //===----------------------------------------------------------------------===// // -// This file provides access to the Intel GPU list in IntelGPUTargetParser.def. -// Only what is needed to name the device a driver reports is declared here; the -// table itself carries more, and a consumer that needs the rest either declares -// it here as well or expands the table directly. +// This file names the Intel GPU a driver reports, and finds the Intel GPU a +// user names. // //===----------------------------------------------------------------------===// @@ -22,10 +20,15 @@ #include <string> namespace llvm { +template <typename T> class SmallVectorImpl; + namespace IntelGPU { /// Intel GPU architecture names, covering both physical devices and the /// compatibility names that stand for a whole product line. +/// +/// The underlying type is fixed because clang/Basic/OffloadArch.h forward +/// declares this enumeration; the two declarations have to agree. enum GPUKind : uint8_t { GK_NONE = 0, #define INTEL_GPU(NAME, KIND, MAJOR, MINOR, IGCA_TARGET, IGCA_FEATURE_SETS) \ @@ -44,6 +47,36 @@ LLVM_ABI StringRef getArchName(uint32_t GPUIPVersion); /// \return the numeric name of \p GPUIPVersion, e.g. "xe_35.11.0". LLVM_ABI std::string getNumericArchName(uint32_t GPUIPVersion); +/// \return the human-friendly name of \p Kind, e.g. "xe-pvc", or an empty +/// string for GK_NONE. +LLVM_ABI StringRef getArchName(GPUKind Kind); + +/// \return the device \p Name denotes, or GK_NONE for a name this build does +/// not know. +/// +/// Every spelling the user may write is accepted: a human-friendly name such as +/// "xe-pvc", a compatibility name such as "xe-dg2", an alias such as "bmg_g21", +/// and a numeric name such as "xe_12.60.7". Only the human-friendly name is +/// reported back for a device, so a spelling that is not one canonicalizes to +/// the one that is. The revision of a numeric name takes no part in the lookup, +/// just as it takes none in getArchName, so "xe_12.60.0" and "xe_12.60.7" name +/// the same device; the revision may also be omitted. A numeric name for a +/// device that is not in the table yields GK_NONE like any other unknown name. +LLVM_ABI GPUKind parseArch(StringRef Name); + +/// \return the IGCA target name to compile \p Kind for, e.g. "xe-pvc" -> +/// "igca_20ca", or an empty string for GK_NONE. This is the spelling +/// -target-cpu is invoked with. +/// +/// The name is the numeric IGCA target followed by the suffix of the feature +/// sets: igca_60 is the core features, igca_60c adds the compute features, and +/// igca_60ca is exact, meaning that only a device at that target will do. +LLVM_ABI StringRef getIGCAName(GPUKind Kind); + +/// Append every architecture name this build accepts, for diagnostics that +/// offer the user an alternative to a name that did not parse. +LLVM_ABI void fillValidArchList(SmallVectorImpl<StringRef> &Values); + } // namespace IntelGPU } // namespace llvm diff --git a/llvm/lib/TargetParser/IntelGPUTargetParser.cpp b/llvm/lib/TargetParser/IntelGPUTargetParser.cpp index 4b4d162ffbfff7..044daddc868f1e 100644 --- a/llvm/lib/TargetParser/IntelGPUTargetParser.cpp +++ b/llvm/lib/TargetParser/IntelGPUTargetParser.cpp @@ -11,8 +11,12 @@ //===----------------------------------------------------------------------===// #include "llvm/TargetParser/IntelGPUTargetParser.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/ADT/Twine.h" +#include "llvm/Support/ErrorHandling.h" #include <cassert> +#include <tuple> using namespace llvm; using namespace IntelGPU; @@ -28,7 +32,7 @@ using namespace IntelGPU; // The reserved bits carry no information. static constexpr uint32_t GPUIPMajorShift = 22; static constexpr uint32_t GPUIPMinorShift = 14; -[[maybe_unused]] static constexpr uint32_t GPUIPMajorMask = 0x3ff; +static constexpr uint32_t GPUIPMajorMask = 0x3ff; static constexpr uint32_t GPUIPMinorMask = 0xff; static constexpr uint32_t GPUIPRevisionMask = 0x3f; @@ -47,13 +51,20 @@ static constexpr uint32_t packDevice(uint32_t Major, uint32_t Minor) { return (Major << GPUIPMajorShift) | (Minor << GPUIPMinorShift); } -StringRef llvm::IntelGPU::getArchName(uint32_t GPUIPVersion) { +// The device that \p GPUIPVersion identifies, or GK_NONE if the table lists +// none. Only INTEL_GPU rows are expanded, so a compatibility name can never +// match. +static GPUKind getKindForVersion(uint32_t GPUIPVersion) { const uint32_t Device = GPUIPVersion & GPUIPDeviceMask; #define INTEL_GPU(NAME, KIND, MAJOR, MINOR, IGCA_TARGET, IGCA_FEATURE_SETS) \ if (Device == packDevice(MAJOR, MINOR)) \ - return NAME; + return GK_##KIND; #include "llvm/TargetParser/IntelGPUTargetParser.def" - return ""; + return GK_NONE; +} + +StringRef llvm::IntelGPU::getArchName(uint32_t GPUIPVersion) { + return getArchName(getKindForVersion(GPUIPVersion)); } std::string llvm::IntelGPU::getNumericArchName(uint32_t GPUIPVersion) { @@ -63,3 +74,111 @@ std::string llvm::IntelGPU::getNumericArchName(uint32_t GPUIPVersion) { return ("xe_" + Twine(Major) + "." + Twine(Minor) + "." + Twine(Revision)) .str(); } + +StringRef llvm::IntelGPU::getArchName(GPUKind Kind) { + switch (Kind) { + case GK_NONE: + return ""; +#define INTEL_GPU(NAME, KIND, MAJOR, MINOR, IGCA_TARGET, IGCA_FEATURE_SETS) \ + case GK_##KIND: \ + return NAME; +#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_TARGET, IGCA_FEATURE_SETS) \ + case GK_##KIND: \ + return NAME; +#include "llvm/TargetParser/IntelGPUTargetParser.def" + } + llvm_unreachable("invalid Intel GPU GPUKind"); +} + +// Read a numeric architecture name, e.g. "xe_12.60.7", into the GPU IP version +// it spells. The revision may be omitted, since it takes no part in a lookup +// either way. Anything else is not a numeric name, which is not the same as +// naming no device: a caller distinguishes the two by whether this succeeds. +static bool parseNumericArchName(StringRef Name, uint32_t &GPUIPVersion) { + if (!Name.consume_front("xe_")) + return false; + + StringRef MajorStr, MinorStr, RevisionStr; + std::tie(MajorStr, Name) = Name.split('.'); + // The revision may be omitted, but a separator with no revision after it is + // malformed rather than an omitted revision. + const bool HasRevision = Name.contains('.'); + std::tie(MinorStr, RevisionStr) = Name.split('.'); + uint32_t Major, Minor, Revision = 0; + if (MajorStr.getAsInteger(10, Major) || MinorStr.getAsInteger(10, Minor)) + return false; + if (HasRevision && RevisionStr.getAsInteger(10, Revision)) + return false; + + // A field too wide for the GPU IP version it spells describes no GPU that + // could ever report it, so this is not a numeric name rather than one naming + // no device. + if (Major > GPUIPMajorMask || Minor > GPUIPMinorMask || + Revision > GPUIPRevisionMask) + return false; + GPUIPVersion = packDevice(Major, Minor) | Revision; + return true; +} + +GPUKind llvm::IntelGPU::parseArch(StringRef Name) { + GPUKind Kind = StringSwitch<GPUKind>(Name) +#define INTEL_GPU(NAME, KIND, MAJOR, MINOR, IGCA_TARGET, IGCA_FEATURE_SETS) \ + .Case(NAME, GK_##KIND) +#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_TARGET, IGCA_FEATURE_SETS) \ + .Case(NAME, GK_##KIND) +#define INTEL_GPU_ALIAS(NAME, KIND) .Case(NAME, GK_##KIND) +#include "llvm/TargetParser/IntelGPUTargetParser.def" + .Default(GK_NONE); + if (Kind != GK_NONE) + return Kind; + + // A device with no human-friendly name is spelled numerically, so the same + // lookup the driver does for a reported GPU IP version has to be reachable by + // name. + uint32_t GPUIPVersion; + if (parseNumericArchName(Name, GPUIPVersion)) + return getKindForVersion(GPUIPVersion); + return GK_NONE; +} + +// The suffix each IGCA_FEATURE_SETS token contributes to a target name. +#define IGCA_FEATURE_SETS_Core "" +#define IGCA_FEATURE_SETS_Compute "c" +#define IGCA_FEATURE_SETS_Render "r" +#define IGCA_FEATURE_SETS_ComputeExact "ca" +#define IGCA_FEATURE_SETS_RenderExact "ra" + +StringRef llvm::IntelGPU::getIGCAName(GPUKind Kind) { + // Unlike the NVPTX virtual architecture name, this is not a column of its + // own: the target and the feature sets already spell it, and a column would + // let the three disagree. + switch (Kind) { + case GK_NONE: + return ""; +#define INTEL_GPU(NAME, KIND, MAJOR, MINOR, IGCA_TARGET, IGCA_FEATURE_SETS) \ + case GK_##KIND: \ + return "igca_" #IGCA_TARGET IGCA_FEATURE_SETS_##IGCA_FEATURE_SETS; +#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_TARGET, IGCA_FEATURE_SETS) \ + case GK_##KIND: \ + return "igca_" #IGCA_TARGET IGCA_FEATURE_SETS_##IGCA_FEATURE_SETS; +#include "llvm/TargetParser/IntelGPUTargetParser.def" + } + llvm_unreachable("invalid Intel GPU GPUKind"); +} + +#undef IGCA_FEATURE_SETS_Core +#undef IGCA_FEATURE_SETS_Compute +#undef IGCA_FEATURE_SETS_Render +#undef IGCA_FEATURE_SETS_ComputeExact +#undef IGCA_FEATURE_SETS_RenderExact + +void llvm::IntelGPU::fillValidArchList(SmallVectorImpl<StringRef> &Values) { + // An alias is a name the user may write, so it belongs here even though it is + // never the name reported for a device. +#define INTEL_GPU(NAME, KIND, MAJOR, MINOR, IGCA_TARGET, IGCA_FEATURE_SETS) \ + Values.push_back(NAME); +#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_TARGET, IGCA_FEATURE_SETS) \ + Values.push_back(NAME); +#define INTEL_GPU_ALIAS(NAME, KIND) Values.push_back(NAME); +#include "llvm/TargetParser/IntelGPUTargetParser.def" +} diff --git a/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp b/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp index ab8d456769d0da..147a3e3b4485c7 100644 --- a/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp +++ b/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp @@ -7,6 +7,8 @@ //===----------------------------------------------------------------------===// #include "llvm/TargetParser/IntelGPUTargetParser.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" #include "gtest/gtest.h" #include <cassert> @@ -78,4 +80,106 @@ TEST(IntelGPUTargetParserTest, NumericArchName) { "xe_12.60.7"); } +TEST(IntelGPUTargetParserTest, KindArchNames) { + EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_XE_PVC), "xe-pvc"); + EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_XE_DG2), "xe-dg2"); + EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_NONE), ""); +} + +TEST(IntelGPUTargetParserTest, ParseArch) { + // A human-friendly name, a compatibility name, and an alias. + EXPECT_EQ(IntelGPU::parseArch("xe-pvc"), IntelGPU::GK_XE_PVC); + EXPECT_EQ(IntelGPU::parseArch("xe-dg2"), IntelGPU::GK_XE_DG2); + EXPECT_EQ(IntelGPU::parseArch("bmg_g21"), IntelGPU::GK_XE_BMG_G21); + // Products built on one GPU IP version are one device, named after the one + // the offload-arch utility prints. + EXPECT_EQ(IntelGPU::parseArch("xe-arl-u"), IntelGPU::GK_XE_MTL_U); + EXPECT_EQ(IntelGPU::parseArch("xe-ats-m150"), IntelGPU::GK_XE_ACM_G10); + EXPECT_EQ(IntelGPU::parseArch("xe_12.70"), IntelGPU::GK_XE_MTL_U); + // Except PVC-SDV, which the AOT compiler builds for its own stepping. + EXPECT_EQ(IntelGPU::parseArch("xe-pvc-sdv"), IntelGPU::GK_XE_PVC_SDV); + + // A numeric name names the same device the driver would have reported. + EXPECT_EQ(IntelGPU::parseArch("xe_12.60.0"), IntelGPU::GK_XE_PVC); + // The revision takes no part in the lookup, and may be left out. + EXPECT_EQ(IntelGPU::parseArch("xe_12.60.7"), IntelGPU::GK_XE_PVC); + EXPECT_EQ(IntelGPU::parseArch("xe_12.60"), IntelGPU::GK_XE_PVC); + // A numeric name for a device this build does not know names no device. + EXPECT_EQ(IntelGPU::parseArch("xe_40.11.0"), IntelGPU::GK_NONE); + + // Neither an empty name nor a malformed one names a device. + EXPECT_EQ(IntelGPU::parseArch(""), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("pvc"), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_"), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_12"), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_12.pvc"), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_12.60.7.1"), IntelGPU::GK_NONE); + // A trailing separator is not an omitted revision. + EXPECT_EQ(IntelGPU::parseArch("xe_12.60."), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_12."), IntelGPU::GK_NONE); + + // A field wider than the GPU IP version field it spells describes no GPU + // that could ever report it, so it names no device however well the rest of + // the name matches a row. The major version holds 10 bits, the minor 8, the + // revision 6. + EXPECT_EQ(IntelGPU::parseArch("xe_12.60.63"), IntelGPU::GK_XE_PVC); + EXPECT_EQ(IntelGPU::parseArch("xe_12.60.64"), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_12.256"), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_1024.60"), IntelGPU::GK_NONE); + // The widest values that fit name no row either. + EXPECT_EQ(IntelGPU::parseArch("xe_12.255"), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_1023.60"), IntelGPU::GK_NONE); + // Truncated to its field, each of these would name xe-pvc. + EXPECT_EQ(IntelGPU::parseArch("xe_12.316"), IntelGPU::GK_NONE); + EXPECT_EQ(IntelGPU::parseArch("xe_1036.60"), IntelGPU::GK_NONE); + // Wider than the whole version, so the integer itself does not fit. + EXPECT_EQ(IntelGPU::parseArch("xe_12.60.99999999999"), IntelGPU::GK_NONE); +} + +TEST(IntelGPUTargetParserTest, EveryNameParses) { + // Every name the table declares has to be an --offload-arch value, and has to + // name the row it came from. +#define INTEL_GPU(NAME, KIND, MAJOR, MINOR, IGCA_TARGET, IGCA_FEATURE_SETS) \ + EXPECT_EQ(IntelGPU::parseArch(NAME), IntelGPU::GK_##KIND) << NAME; +#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_TARGET, IGCA_FEATURE_SETS) \ + EXPECT_EQ(IntelGPU::parseArch(NAME), IntelGPU::GK_##KIND) << NAME; +#define INTEL_GPU_ALIAS(NAME, KIND) \ + EXPECT_EQ(IntelGPU::parseArch(NAME), IntelGPU::GK_##KIND) << NAME; +#include "llvm/TargetParser/IntelGPUTargetParser.def" +} + +TEST(IntelGPUTargetParserTest, AliasesAreNeverReported) { + // An alias declares no kind of its own, so the name reported for a device is + // the row's own name. A device with two spellings would otherwise depend on + // which one the table happened to reach first. +#define INTEL_GPU_ALIAS(NAME, KIND) \ + EXPECT_NE(IntelGPU::getArchName(IntelGPU::GK_##KIND), NAME) << NAME; +#include "llvm/TargetParser/IntelGPUTargetParser.def" +} + +TEST(IntelGPUTargetParserTest, IGCANames) { + // One of each suffix, since the target and the feature sets are pasted + // together. + EXPECT_EQ(IntelGPU::getIGCAName(IntelGPU::GK_XE_CRI), "igca_60c"); + EXPECT_EQ(IntelGPU::getIGCAName(IntelGPU::GK_XE_NVL_P), "igca_60r"); + EXPECT_EQ(IntelGPU::getIGCAName(IntelGPU::GK_XE_PVC), "igca_20ca"); + EXPECT_EQ(IntelGPU::getIGCAName(IntelGPU::GK_XE_DG2), "igca_15ra"); + EXPECT_EQ(IntelGPU::getIGCAName(IntelGPU::GK_NONE), ""); +} + +TEST(IntelGPUTargetParserTest, ValidArchList) { + SmallVector<StringRef> Values; + IntelGPU::fillValidArchList(Values); + + EXPECT_FALSE(Values.empty()); + EXPECT_NE(llvm::find(Values, "xe-pvc"), Values.end()); + // A compatibility name and an alias are as valid as any other name. + EXPECT_NE(llvm::find(Values, "xe-dg2"), Values.end()); + EXPECT_NE(llvm::find(Values, "bmg_g21"), Values.end()); + // The list is offered to a user whose name did not parse, so every entry has + // to be a name that would have. + for (StringRef Value : Values) + EXPECT_NE(IntelGPU::parseArch(Value), IntelGPU::GK_NONE) << Value; +} + } // namespace >From 3f32c4853a511540a559f42d3556ffef24b73f8d Mon Sep 17 00:00:00 2001 From: srividya sundaram <[email protected]> Date: Mon, 5 Oct 2026 14:29:55 -0700 Subject: [PATCH 2/2] [Clang][SYCL] Infer the SPIR-V target from an Intel offload architecture Select the SPIR-V target from an Intel offload architecture in --offload-arch. Previously, clang++ -fsycl --offload-arch=xe-pvc failed with "invalid or unsupported offload target" unless --offload-targets was also given, although the name alone says which target to use. For CUDA and HIP, an NVIDIA or AMD name already selects the target. For SYCL, an Intel GPU name, an Intel CPU name (graniterapids) or generic now selects SPIR-V of the host's width, the same target SYCL uses when no architecture is given. generic previously had no inferred target. With -foffload-via-llvm the target gets the LLVM environment, as it does for CUDA and HIP. OpenMP, CUDA and HIP still reject an Intel name. Assisted-by: Claude Opus 5.5 --- clang/lib/Driver/Driver.cpp | 20 +++++++++++-- clang/test/Driver/sycl-offload-jit.cpp | 40 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp index bbcf88d8237762..8d4eb664c48082 100644 --- a/clang/lib/Driver/Driver.cpp +++ b/clang/lib/Driver/Driver.cpp @@ -967,21 +967,29 @@ using TripleSet = std::multiset<llvm::Triple>; // requested offloading kind and architectures. static TripleSet inferOffloadToolchains(Compilation &C, Action::OffloadKind Kind) { + // A SYCL Intel GPU has several accepted spellings, so key it by its canonical + // name, or --no-offload-arch with another spelling would not remove it. + auto canonicalize = [Kind](StringRef Arch) { + OffloadArch ID = StringToOffloadArch(Arch); + return Kind == Action::OFK_SYCL && ID.isIntelGPU() + ? std::string(OffloadArchToString(ID)) + : Arch.str(); + }; std::set<std::string> Archs; for (Arg *A : C.getInputArgs()) { for (StringRef Arch : A->getValues()) { if (A->getOption().matches(options::OPT_offload_arch_EQ)) { if (Arch == "native") { for (StringRef Str : getSystemOffloadArchs(C, Kind)) - Archs.insert(Str.str()); + Archs.insert(canonicalize(Str)); } else { - Archs.insert(Arch.str()); + Archs.insert(canonicalize(Arch)); } } else if (A->getOption().matches(options::OPT_no_offload_arch_EQ)) { if (Arch == "all") Archs.clear(); else - Archs.erase(Arch.str()); + Archs.erase(canonicalize(Arch)); } } } @@ -1023,6 +1031,12 @@ static TripleSet inferOffloadToolchains(Compilation &C, llvm::Triple Triple = OffloadArchToTriple(C.getDefaultToolChain().getTriple(), ID); + // A SYCL Intel device, or a generic one, is SPIR-V of the host's width, the + // same target SYCL picks when no architecture is given. + if (Kind == Action::OFK_SYCL && (ID.isIntel() || ID.isGeneric())) + Triple = llvm::Triple(C.getDefaultToolChain().getTriple().isArch64Bit() + ? llvm::Triple::spirv64 + : llvm::Triple::spirv32); if (UsesLLVMOffloading) Triple.setEnvironment(llvm::Triple::LLVM); diff --git a/clang/test/Driver/sycl-offload-jit.cpp b/clang/test/Driver/sycl-offload-jit.cpp index cb4283db51d25b..81adeb0941240e 100644 --- a/clang/test/Driver/sycl-offload-jit.cpp +++ b/clang/test/Driver/sycl-offload-jit.cpp @@ -134,6 +134,46 @@ // RUN: | FileCheck -check-prefixes=CHK-INTEL-SUFFIX %s // CHK-INTEL-SUFFIX: offload, "device-sycl (spirv64-unknown-unknown:xe-pvc:foo)" +/// Without --offload-targets, an Intel device or "generic" picks SPIR-V of the +/// host's width, and removing any spelling still removes the device. +// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-arch=xe-pvc --offload-arch=bmg_g21 --offload-arch=graniterapids \ +// RUN: --offload-arch=generic -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-NO-TARGETS %s +// CHK-INTEL-NO-TARGETS-DAG: offload, "device-sycl (spirv64-unknown-unknown:generic)" +// CHK-INTEL-NO-TARGETS-DAG: offload, "device-sycl (spirv64-unknown-unknown:graniterapids)" +// CHK-INTEL-NO-TARGETS-DAG: offload, "device-sycl (spirv64-unknown-unknown:xe-bmg-g21)" +// CHK-INTEL-NO-TARGETS-DAG: offload, "device-sycl (spirv64-unknown-unknown:xe-pvc)" +// RUN: %clang -ccc-print-phases --target=i386-unknown-linux-gnu -fsycl \ +// RUN: --offload-arch=xe-pvc -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-NO-TARGETS-32 %s +// CHK-INTEL-NO-TARGETS-32: offload, "device-sycl (spirv32-unknown-unknown:xe-pvc)" +// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-arch=xe-pvc --offload-arch=xe_12.60.7 -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-NO-TARGETS-ONE %s +// CHK-INTEL-NO-TARGETS-ONE-NOT: offload, "device-sycl +// CHK-INTEL-NO-TARGETS-ONE: offload, "device-sycl (spirv64-unknown-unknown:xe-pvc)" +// CHK-INTEL-NO-TARGETS-ONE-NOT: offload, "device-sycl +// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \ +// RUN: --offload-arch=bmg_g21 --offload-arch=xe-pvc --no-offload-arch=xe-bmg-g21 \ +// RUN: -c %s 2>&1 | FileCheck -check-prefixes=CHK-INTEL-NO-TARGETS-NO-ARCH %s +// CHK-INTEL-NO-TARGETS-NO-ARCH-NOT: bmg +// CHK-INTEL-NO-TARGETS-NO-ARCH: offload, "device-sycl (spirv64-unknown-unknown:xe-pvc)" +// CHK-INTEL-NO-TARGETS-NO-ARCH-NOT: bmg + +/// As for CUDA and HIP, -foffload-via-llvm gives the inferred target the LLVM +/// environment. +// RUN: %clang -### --target=x86_64-unknown-linux-gnu -fsycl -foffload-via-llvm \ +// RUN: --offload-arch=xe-pvc -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-VIA-LLVM %s +// CHK-INTEL-VIA-LLVM: "-cc1" "-triple" "spirv64-unknown-unknown-llvm" + +/// Only SYCL infers a target from an Intel name. +// RUN: not %clang -### --target=x86_64-unknown-linux-gnu -x cuda -nocudainc \ +// RUN: -nocudalib --offload-arch=xe-pvc -c %s 2>&1 \ +// RUN: | FileCheck -check-prefixes=CHK-INTEL-CUDA %s +// CHK-INTEL-CUDA: error: unsupported CUDA gpu architecture: xe-pvc + /// -Xarch_ may name an Intel GPU by any of its spellings. // RUN: %clang -### --target=x86_64-unknown-linux-gnu -fsycl \ // RUN: --offload-targets=spirv64-unknown-unknown --offload-arch=bmg_g21 \ _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
