https://github.com/srividya-sundaram updated 
https://github.com/llvm/llvm-project/pull/229249

>From 455ea8cf2e9a4409d61e9218424e63e470c30299 Mon Sep 17 00:00:00 2001
From: srividya sundaram <[email protected]>
Date: Sat, 12 Sep 2026 19:09:30 -0700
Subject: [PATCH 1/3] [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     |  58 +++++---
 .../llvm/TargetParser/IntelGPUTargetParser.h  |  41 +++++-
 .../lib/TargetParser/IntelGPUTargetParser.cpp | 127 +++++++++++++++++-
 .../TargetParser/IntelGPUTargetParserTest.cpp | 104 ++++++++++++++
 15 files changed, 472 insertions(+), 57 deletions(-)

diff --git a/clang/include/clang/Basic/OffloadArch.h 
b/clang/include/clang/Basic/OffloadArch.h
index a0e5e72db220b..95a17952d4a2a 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 f8c811f552f38..adc94b35ce025 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 b6d2bfd320103..c6a8525b050c6 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 also knows names that are never an NVPTX CPU, such as
+  // the Intel ones, so list the kinds accepted here; Sema would otherwise read
+  // another vendor's kind as an NVPTX one.
   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 7649941a68b1c..ead0e3d19b3d9 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 255a9c9602b54..d7928cd10ea24 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 f0d04bf4b6a83..3950f69f53f7e 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -9862,8 +9862,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 1813e9d346667..7c5c3ab65eeb1 100644
--- a/clang/test/Driver/sycl-offload-jit.cpp
+++ b/clang/test/Driver/sycl-offload-jit.cpp
@@ -77,11 +77,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 20dbd8dc6aa3b..b2a38b6b26825 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 0ba81f61005a4..0376e7bf88338 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 60ab7dc4e2a7d..01463c15aa0db 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 013ed75fbd57e..980a88aba6114 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 bd3b0a4634e06..3b909c97c26a2 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,26 @@ 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
+
+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 4ae635e97167b..8713de9579b48 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 4b4d162ffbfff..044daddc868f1 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 ab8d456769d0d..147a3e3b4485c 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 c5927b506265a77d35c91f102db9c9c47e9862f8 Mon Sep 17 00:00:00 2001
From: srividya sundaram <[email protected]>
Date: Mon, 5 Oct 2026 14:29:55 -0700
Subject: [PATCH 2/3] [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 ead0e3d19b3d9..9787b4ea6398e 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 7c5c3ab65eeb1..4646f02fafa0d 100644
--- a/clang/test/Driver/sycl-offload-jit.cpp
+++ b/clang/test/Driver/sycl-offload-jit.cpp
@@ -121,6 +121,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 \

>From 9dc865598ae1c9065eadc289137c72c2cf827177 Mon Sep 17 00:00:00 2001
From: srividya sundaram <[email protected]>
Date: Mon, 5 Oct 2026 14:33:24 -0700
Subject: [PATCH 3/3] [Clang][SYCL] Reject an unknown offload architecture on a
 SPIR-V target

Diagnose unknown --offload-arch names on a SPIR-V target.

Previously, with --offload-targets=spirv64-unknown-unknown, any --offload-arch 
value was accepted, so a typo such as xe-pcv silently created a device job for 
a device that does not exist. CUDA and HIP already reject unknown names.

For SYCL on spirv32-unknown-unknown or spirv64-unknown-unknown, --offload-arch 
must now be an Intel GPU or CPU name, generic, or a well-formed numeric name 
such as xe_40.11.0, which offload-arch prints for a GPU the list does not know 
yet and which is compiled at run time. Anything else is an error, e.g. 
"unsupported SYCL gpu architecture: xe-pcv"; the existing diagnostic says "gpu" 
for a CPU name too.

User-visible changes, noted in the release notes:
- --offload-arch=all, other vendors' names, and suffixed names such as 
generic:foo are now errors on these targets.
- As for CUDA and HIP, --no-offload-arch is checked as well when the target is 
given; without --offload-targets, removing an unknown name stays harmless, as 
it is for every offload kind.
- Other SPIR-V targets, such as spirv64-intel (OpenMP) and spirv64-amd-amdhsa 
(HIP), are not checked, since they belong to other offload kinds' devices.

Assisted-by: Claude Opus 5.5
---
 clang/docs/ReleaseNotes.md                    |  6 +++
 clang/lib/Driver/Driver.cpp                   | 31 ++++++++++--
 clang/test/Driver/sycl-offload-jit.cpp        | 50 ++++++++++++++++---
 .../OffloadTools/clang-sycl-linker/basic.ll   |  5 ++
 .../llvm/TargetParser/IntelGPUTargetParser.h  |  8 +++
 .../lib/TargetParser/IntelGPUTargetParser.cpp |  5 ++
 .../TargetParser/IntelGPUTargetParserTest.cpp | 13 +++++
 7 files changed, 106 insertions(+), 12 deletions(-)

diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 81e4d1fc8360f..07a1a4612a0ea 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -1070,6 +1070,12 @@ The `alpha.cplusplus.UseAfterLifetimeEnd` checker was 
renamed to `alpha.core.Use
 
 ### SYCL Support
 
+- On a `spirv32-unknown-unknown` or `spirv64-unknown-unknown` offload target,
+  `--offload-arch` and `--no-offload-arch` must now name an Intel GPU or CPU,
+  `generic`, or the numeric name of an Intel GPU such as `xe_40.11.0`. Any 
other
+  value, e.g. a misspelled `xe-pcv`, is an error rather than a device job for a
+  device that does not exist.
+
 #### Improvements
 
 ## Additional Information
diff --git a/clang/lib/Driver/Driver.cpp b/clang/lib/Driver/Driver.cpp
index 9787b4ea6398e..ac3511d2207de 100644
--- a/clang/lib/Driver/Driver.cpp
+++ b/clang/lib/Driver/Driver.cpp
@@ -109,6 +109,7 @@
 #include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/TargetParser/Host.h"
+#include "llvm/TargetParser/IntelGPUTargetParser.h"
 #include "llvm/TargetParser/RISCVISAInfo.h"
 #include <cstdlib> // ::getenv
 #include <map>
@@ -1023,7 +1024,15 @@ static TripleSet inferOffloadToolchains(Compilation &C,
           << Arch;
       return {};
     }
-    if (ID.isUnknown() || ID.isUnused()) {
+    // An Intel name or "generic" takes no target features.
+    if (Kind == Action::OFK_SYCL && (ID.isIntel() || ID.isGeneric()) &&
+        Arch.contains(':'))
+      ID = OffloadArch::getUnknown();
+    // The offload-arch utility prints a numeric name for an Intel GPU absent
+    // from the list, so SYCL accepts one; such a GPU is compiled at run time.
+    bool IsUnlistedIntelGPU = Kind == Action::OFK_SYCL && !ID.isIntelGPU() &&
+                              llvm::IntelGPU::isNumericArchName(Arch);
+    if ((ID.isUnknown() || ID.isUnused()) && !IsUnlistedIntelGPU) {
       C.getDriver().Diag(clang::diag::err_drv_offload_bad_gpu_arch)
           << "offload" << Arch;
       return {};
@@ -1033,7 +1042,8 @@ static TripleSet inferOffloadToolchains(Compilation &C,
         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()))
+    if (Kind == Action::OFK_SYCL &&
+        (ID.isIntel() || ID.isGeneric() || IsUnlistedIntelGPU))
       Triple = llvm::Triple(C.getDefaultToolChain().getTriple().isArch64Bit()
                                 ? llvm::Triple::spirv64
                                 : llvm::Triple::spirv32);
@@ -3923,9 +3933,9 @@ static StringRef getCanonicalArchString(Compilation &C,
   // 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(':'))
+  // An Intel name or "generic" takes no target features, so it must parse in
+  // full; otherwise "xe-pvc:garbage" would silently become "xe-pvc".
+  if ((Arch.isIntel() || Arch.isGeneric()) && ArchStr.contains(':'))
     Arch = OffloadArch::getUnknown();
   if (Triple.isNVPTX() && (Arch.isUnknown() || !Arch.isNVPTX())) {
     C.getDriver().Diag(clang::diag::err_drv_offload_bad_gpu_arch)
@@ -3947,6 +3957,17 @@ static StringRef getCanonicalArchString(Compilation &C,
         return StringRef();
       }
     }
+  } else if (Kind == Action::OFK_SYCL && Triple.isSPIRV() &&
+             Triple.getVendor() == llvm::Triple::UnknownVendor &&
+             !ArchStr.empty() && !Arch.isIntel() && !Arch.isGeneric() &&
+             !llvm::IntelGPU::isNumericArchName(ArchStr)) {
+    // A SYCL device on a plain SPIR-V target is an Intel GPU or CPU, "generic"
+    // for none in particular, or a numeric name for a GPU the list does not
+    // know yet; reject anything else, as for CUDA and HIP, rather than build
+    // for a device that does not exist.
+    C.getDriver().Diag(clang::diag::err_drv_offload_bad_gpu_arch)
+        << "SYCL" << ArchStr;
+    return StringRef();
   }
 
   // A SYCL Intel GPU has several accepted spellings, e.g. an alias or a 
numeric
diff --git a/clang/test/Driver/sycl-offload-jit.cpp 
b/clang/test/Driver/sycl-offload-jit.cpp
index 4646f02fafa0d..6cd000e7f7fe6 100644
--- a/clang/test/Driver/sycl-offload-jit.cpp
+++ b/clang/test/Driver/sycl-offload-jit.cpp
@@ -114,13 +114,6 @@
 // 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)"
-
 /// 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 \
@@ -161,6 +154,49 @@
 // RUN:   | FileCheck -check-prefixes=CHK-INTEL-CUDA %s
 // CHK-INTEL-CUDA: error: unsupported CUDA gpu architecture: xe-pvc
 
+/// On a SPIR-V target, a SYCL device must be an Intel GPU or CPU, "generic", 
or
+/// the numeric name of a GPU the list does not know yet.
+// RUN: not %clang -### --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-targets=spirv64-unknown-unknown --offload-arch=xe-pcv -c 
%s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-BAD-ARCH %s
+// CHK-INTEL-BAD-ARCH: error: unsupported SYCL gpu architecture: xe-pcv
+// RUN: not %clang -### --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-targets=spirv64-unknown-unknown --offload-arch=xe_12.60. 
-c %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-BAD-ARCH-NUM %s
+// CHK-INTEL-BAD-ARCH-NUM: error: unsupported SYCL gpu architecture: xe_12.60.
+// RUN: not %clang -### --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-targets=spirv64-unknown-unknown 
--offload-arch=xe-pvc:garbage -c %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-BAD-ARCH-SUFFIX %s
+// CHK-INTEL-BAD-ARCH-SUFFIX: error: unsupported SYCL gpu architecture: 
xe-pvc:garbage
+// RUN: not %clang -### --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-targets=spirv64-unknown-unknown --offload-arch=generic:foo 
-c %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-BAD-GENERIC %s
+// RUN: not %clang -### --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-arch=generic:foo -c %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-BAD-GENERIC %s
+// CHK-INTEL-BAD-GENERIC: error: unsupported {{SYCL|offload}} gpu 
architecture: generic:foo
+/// As for CUDA and HIP, --no-offload-arch is checked too when the target is
+/// given.
+// RUN: not %clang -### --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-targets=spirv64-unknown-unknown --offload-arch=xe-pvc \
+// RUN:   --no-offload-arch=foo -c %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-BAD-NO-ARCH %s
+// CHK-INTEL-BAD-NO-ARCH: error: unsupported SYCL gpu architecture: foo
+/// Only a plain SPIR-V target is checked.
+// RUN: %clang -### --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-targets=spirv64-intel --offload-arch=foo -c %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-NOT-CHECKED %s
+// CHK-INTEL-NOT-CHECKED-NOT: error:
+// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-targets=spirv64-unknown-unknown 
--offload-arch=graniterapids \
+// RUN:   --offload-arch=xe_40.11.0 -c %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-GOOD-ARCH %s
+// RUN: %clang -ccc-print-phases --target=x86_64-unknown-linux-gnu -fsycl \
+// RUN:   --offload-arch=graniterapids --offload-arch=xe_40.11.0 -c %s 2>&1 \
+// RUN:   | FileCheck -check-prefixes=CHK-INTEL-GOOD-ARCH %s
+// CHK-INTEL-GOOD-ARCH-DAG: offload, "device-sycl 
(spirv64-unknown-unknown:graniterapids)"
+// CHK-INTEL-GOOD-ARCH-DAG: offload, "device-sycl 
(spirv64-unknown-unknown:xe_40.11.0)"
+
 /// -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 \
diff --git a/clang/test/OffloadTools/clang-sycl-linker/basic.ll 
b/clang/test/OffloadTools/clang-sycl-linker/basic.ll
index 0376e7bf88338..376cb2a37d73a 100644
--- a/clang/test/OffloadTools/clang-sycl-linker/basic.ll
+++ b/clang/test/OffloadTools/clang-sycl-linker/basic.ll
@@ -128,6 +128,11 @@
 ; 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 a GPU the list does not know yet stays SPIR-V, compiled at run 
time.
+; RUN: clang-sycl-linker --dry-run -v --module-split-mode=link_unit 
-arch=xe_40.11.0 %t/input1.bc -o %t/jit-gpu.out 2>&1 \
+; RUN:   | FileCheck %s --check-prefix=JIT-INTEL-UNLISTED 
--implicit-check-not=ocloc
+; JIT-INTEL-UNLISTED: sycl-bundle: image kind: spv, triple: spirv64, arch: 
xe_40.11.0
 
 ; 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 \
diff --git a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h 
b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h
index 8713de9579b48..3627bdcd36abe 100644
--- a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h
+++ b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h
@@ -64,6 +64,14 @@ LLVM_ABI StringRef getArchName(GPUKind Kind);
 /// device that is not in the table yields GK_NONE like any other unknown name.
 LLVM_ABI GPUKind parseArch(StringRef Name);
 
+/// \return true if \p Name is a well-formed numeric name, "xe_" followed by a
+/// major, a minor and an optional revision version separated by ".", each
+/// fitting its GPU IP version field, e.g. "xe_40.11.0", whether or not the
+/// table knows the device. The offload-arch utility prints
+/// one for a device that has no row yet, so that a newer device is usable with
+/// a compiler that predates it.
+LLVM_ABI bool isNumericArchName(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.
diff --git a/llvm/lib/TargetParser/IntelGPUTargetParser.cpp 
b/llvm/lib/TargetParser/IntelGPUTargetParser.cpp
index 044daddc868f1..2afe53fbe6d73 100644
--- a/llvm/lib/TargetParser/IntelGPUTargetParser.cpp
+++ b/llvm/lib/TargetParser/IntelGPUTargetParser.cpp
@@ -148,6 +148,11 @@ GPUKind llvm::IntelGPU::parseArch(StringRef Name) {
 #define IGCA_FEATURE_SETS_ComputeExact "ca"
 #define IGCA_FEATURE_SETS_RenderExact "ra"
 
+bool llvm::IntelGPU::isNumericArchName(StringRef Name) {
+  uint32_t GPUIPVersion;
+  return parseNumericArchName(Name, GPUIPVersion);
+}
+
 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
diff --git a/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp 
b/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp
index 147a3e3b4485c..6b09f129d5ba8 100644
--- a/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp
+++ b/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp
@@ -136,6 +136,19 @@ TEST(IntelGPUTargetParserTest, ParseArch) {
   EXPECT_EQ(IntelGPU::parseArch("xe_12.60.99999999999"), IntelGPU::GK_NONE);
 }
 
+TEST(IntelGPUTargetParserTest, IsNumericArchName) {
+  EXPECT_TRUE(IntelGPU::isNumericArchName("xe_12.60.7"));
+  EXPECT_TRUE(IntelGPU::isNumericArchName("xe_12.60"));
+  // A device the table does not know yet is still well-formed.
+  EXPECT_TRUE(IntelGPU::isNumericArchName("xe_40.11.0"));
+  EXPECT_FALSE(IntelGPU::isNumericArchName("xe-pvc"));
+  EXPECT_FALSE(IntelGPU::isNumericArchName("xe_12.60."));
+  EXPECT_FALSE(IntelGPU::isNumericArchName("xe_12.60.64"));
+  EXPECT_FALSE(IntelGPU::isNumericArchName(""));
+  EXPECT_FALSE(IntelGPU::isNumericArchName("xe_40"));
+  EXPECT_FALSE(IntelGPU::isNumericArchName("XE_12.60"));
+}
+
 TEST(IntelGPUTargetParserTest, EveryNameParses) {
   // Every name the table declares has to be an --offload-arch value, and has 
to
   // name the row it came from.

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to