[clang] [llvm] [TargetParser] Parse Intel GPU architecture names, and use the list in clang (PR #223199)

Srividya Sundaram via cfe-commits cfe-commits at lists.llvm.org
Sat Sep 12 19:50:25 PDT 2026


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

>From 4a271e5a31ae42fcb8c250be01e3865ffd1c6fcd Mon Sep 17 00:00:00 2001
From: "Kornev, Nikita" <nikita.kornev at intel.com>
Date: Tue, 8 Sep 2026 18:34:21 +0200
Subject: [PATCH 1/3] [TargetParser] Add a list of Intel GPUs, and use it in
 offload-arch

Currently offload-arch prints Intel GPU names which are not a legal parameter
for --offload-arch, e.g. "Intel(R) Data Center GPU Max 1100".

Print an architecture name instead, e.g. "xe-pvc". The driver reports a GPU IP
version, the GMDID, for every device. Add a table that maps a GMDID to a name,
and look the device up in it.

The table goes in llvm/TargetParser, next to the other GPU lists, because
other tools need it too. Some of them are LLVM libraries, which cannot
include a clang header.

Every row of IntelGPUTargetParser.def holds three things: a name that
--offload-arch accepts, the GMDID that the device reports, and the IGCA
(Intel Graphics Compute Architecture) level, which is the virtual
architecture that the compiler targets. A few names, such as xe-dg2,
cover a whole product line. No device reports a GMDID for those, so
offload-arch never prints them.

A device that is not in the table is reported as an error. Making up a
name from its GMDID would not help, because the compiler would not know
which IGCA level to compile for.

The header declares only the functions that offload-arch needs. More will
follow when something needs them.

Co-Authored-By: Claude Opus 5 <noreply at anthropic.com>
---
 clang/tools/offload-arch/CMakeLists.txt       |  2 +-
 clang/tools/offload-arch/LevelZeroArch.cpp    | 51 +++++++++-
 clang/unittests/offload-arch/CMakeLists.txt   |  2 +
 .../offload-arch/OffloadArchTest.cpp          | 47 ++++++++++
 .../TargetParser/IntelGPUTargetParser.def     | 94 +++++++++++++++++++
 .../llvm/TargetParser/IntelGPUTargetParser.h  | 67 +++++++++++++
 llvm/include/module.modulemap                 |  1 +
 llvm/lib/TargetParser/CMakeLists.txt          |  1 +
 .../lib/TargetParser/IntelGPUTargetParser.cpp | 60 ++++++++++++
 llvm/unittests/TargetParser/CMakeLists.txt    |  1 +
 .../TargetParser/IntelGPUTargetParserTest.cpp | 76 +++++++++++++++
 11 files changed, 399 insertions(+), 3 deletions(-)
 create mode 100644 llvm/include/llvm/TargetParser/IntelGPUTargetParser.def
 create mode 100644 llvm/include/llvm/TargetParser/IntelGPUTargetParser.h
 create mode 100644 llvm/lib/TargetParser/IntelGPUTargetParser.cpp
 create mode 100644 llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp

diff --git a/clang/tools/offload-arch/CMakeLists.txt b/clang/tools/offload-arch/CMakeLists.txt
index f7d7012cf7272..8e37e3d2ae5db 100644
--- a/clang/tools/offload-arch/CMakeLists.txt
+++ b/clang/tools/offload-arch/CMakeLists.txt
@@ -1,4 +1,4 @@
-set(LLVM_LINK_COMPONENTS Support)
+set(LLVM_LINK_COMPONENTS Support TargetParser)
 
 add_clang_tool(offload-arch OffloadArch.cpp NVPTXArch.cpp AMDGPUArchByKFD.cpp
                AMDGPUArchByHIP.cpp LevelZeroArch.cpp)
diff --git a/clang/tools/offload-arch/LevelZeroArch.cpp b/clang/tools/offload-arch/LevelZeroArch.cpp
index 47d80aa813085..77716e2d52f66 100644
--- a/clang/tools/offload-arch/LevelZeroArch.cpp
+++ b/clang/tools/offload-arch/LevelZeroArch.cpp
@@ -14,6 +14,7 @@
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/DynamicLibrary.h"
 #include "llvm/Support/Error.h"
+#include "llvm/TargetParser/IntelGPUTargetParser.h"
 #include <cstdio>
 
 #define ZE_MAX_DEVICE_NAME 256
@@ -30,6 +31,7 @@ enum ze_result_t {
 enum ze_structure_type_t {
   ZE_STRUCTURE_TYPE_INIT_DRIVER_TYPE_DESC = 0x00020021,
   ZE_STRUCTURE_TYPE_DEVICE_PROPERTIES = 0x3,
+  ZE_STRUCTURE_TYPE_DEVICE_IP_VERSION_EXT = 0x1000f,
   ZE_STRUCTURE_TYPE_FORCE_UINT32 = 0x7fffffff
 };
 
@@ -72,6 +74,13 @@ struct ze_device_properties_t {
   char name[ZE_MAX_DEVICE_NAME];
 };
 
+// Chained onto ze_device_properties_t::pNext to request the device IP version.
+struct ze_device_ip_version_ext_t {
+  ze_structure_type_t stype;
+  const void *pNext;
+  uint32_t ipVersion;
+};
+
 ze_result_t zeInitDrivers(uint32_t *pCount, ze_driver_handle_t *phDrivers,
                           ze_init_driver_type_desc_t *desc);
 ze_result_t zeDeviceGet(ze_driver_handle_t hDriver, uint32_t *pCount,
@@ -148,6 +157,13 @@ static bool loadLevelZero() {
     }                                                                          \
   } while (0)
 
+// Translate a GMDID into an architecture name that is a legal --offload-arch
+// parameter, or "" if this build does not know the device.
+StringRef getIntelGPUArchName(uint32_t IPVersion) {
+  return IntelGPU::getArchName(
+      IntelGPU::getKindForGMDID(IntelGPU::decodeGMDID(IPVersion)));
+}
+
 int printGPUsByLevelZero() {
   if (!loadLevelZero())
     return 1;
@@ -173,11 +189,42 @@ int printGPUsByLevelZero() {
     CALL_ZE_AND_CHECK(zeDeviceGet, Driver, &DeviceCount, Devices.data());
 
     for (auto Device : Devices) {
+      ze_device_ip_version_ext_t IPVersion = {};
+      IPVersion.stype = ZE_STRUCTURE_TYPE_DEVICE_IP_VERSION_EXT;
+      IPVersion.pNext = nullptr;
+
       ze_device_properties_t DeviceProperties = {};
       DeviceProperties.stype = ZE_STRUCTURE_TYPE_DEVICE_PROPERTIES;
-      DeviceProperties.pNext = nullptr;
+      DeviceProperties.pNext = &IPVersion;
       CALL_ZE_AND_CHECK(zeDeviceGetProperties, Device, &DeviceProperties);
-      llvm::outs() << DeviceProperties.name << '\n';
+
+      // A driver that does not support the extension leaves the chained
+      // structure untouched, in which case there is no architecture to name.
+      if (IPVersion.ipVersion == 0) {
+        if (Verbose)
+          llvm::errs() << "Unable to query the IP version of device '"
+                       << DeviceProperties.name << "'\n";
+        continue;
+      }
+
+      if (Verbose)
+        llvm::errs() << "Found device '" << DeviceProperties.name << "'\n";
+
+      // Naming an unknown device after its GMDID would print something that
+      // --offload-arch cannot accept, because this build knows no IGCA level to
+      // compile for.  Report it instead, spelling out the GMDID so that the
+      // device can be identified.
+      StringRef Arch = getIntelGPUArchName(IPVersion.ipVersion);
+      if (Arch.empty()) {
+        llvm::errs() << "Unknown Intel GPU '" << DeviceProperties.name
+                     << "', which reports the architecture "
+                     << IntelGPU::getNumericArchName(
+                            IntelGPU::decodeGMDID(IPVersion.ipVersion))
+                     << "\n";
+        return 1;
+      }
+
+      llvm::outs() << Arch << '\n';
     }
   }
 
diff --git a/clang/unittests/offload-arch/CMakeLists.txt b/clang/unittests/offload-arch/CMakeLists.txt
index 8d9cbf5c60205..523b5f33ed6b3 100644
--- a/clang/unittests/offload-arch/CMakeLists.txt
+++ b/clang/unittests/offload-arch/CMakeLists.txt
@@ -1,6 +1,7 @@
 set(OffloadArchTestSources
   OffloadArchTest.cpp
   ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/offload-arch/AMDGPUArchByKFD.cpp
+  ${CMAKE_CURRENT_SOURCE_DIR}/../../tools/offload-arch/LevelZeroArch.cpp
   )
 
 if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
@@ -16,4 +17,5 @@ add_distinct_clang_unittest(OffloadArchTests
     LLVMTestingSupport
   LLVM_COMPONENTS
     Support
+    TargetParser
   )
diff --git a/clang/unittests/offload-arch/OffloadArchTest.cpp b/clang/unittests/offload-arch/OffloadArchTest.cpp
index 5f5e49f5c72cc..dbdcf52703dc3 100644
--- a/clang/unittests/offload-arch/OffloadArchTest.cpp
+++ b/clang/unittests/offload-arch/OffloadArchTest.cpp
@@ -26,6 +26,9 @@ llvm::SmallVector<std::string, 8> getCandidateBinPaths(llvm::StringRef ExeDir);
 // Defined in AMDGPUArchByKFD.cpp (non-static, compiled into this test).
 int printGPUsByKFD(llvm::StringRef NodePath);
 
+// Defined in LevelZeroArch.cpp.
+llvm::StringRef getIntelGPUArchName(uint32_t IPVersion);
+
 using namespace llvm;
 
 cl::opt<bool> Verbose("offload-arch-test-verbose", cl::Hidden, cl::init(false));
@@ -207,3 +210,47 @@ TEST(KFDTopology, MultipleGPUsArePrintedInNodeOrder) {
   EXPECT_EQ(printGPUsByKFDCapturingStdout(Dir.path(), Output), 0);
   EXPECT_EQ(Output, "gfx1101\ngfx90a\n");
 }
+
+// --- getIntelGPUArchName ---
+
+namespace {
+// Build a GMDID the way the Level Zero driver reports it.
+constexpr uint32_t gmdid(uint32_t Architecture, uint32_t Release,
+                         uint32_t Revision) {
+  return (Architecture << 22) | (Release << 14) | Revision;
+}
+} // namespace
+
+TEST(IntelGPUArchName, KnownArchitecturesGetAFriendlyName) {
+  EXPECT_EQ(getIntelGPUArchName(gmdid(12, 60, 7)), "xe-pvc");
+  EXPECT_EQ(getIntelGPUArchName(gmdid(20, 1, 4)), "xe-bmg-g21");
+  EXPECT_EQ(getIntelGPUArchName(gmdid(35, 10, 0)), "xe-nvl-p");
+  EXPECT_EQ(getIntelGPUArchName(gmdid(12, 0, 0)), "xe-tgllp");
+}
+
+// When several devices share an architecture and a release, the first one
+// listed in IntelGPUTargetParser.def names the whole group.
+TEST(IntelGPUArchName, FirstNameOfAGroupWins) {
+  EXPECT_EQ(getIntelGPUArchName(gmdid(30, 5, 0)), "xe-nvl-u");
+  EXPECT_EQ(getIntelGPUArchName(gmdid(12, 55, 0)), "xe-acm-g10");
+}
+
+// The revision is not part of the lookup: every stepping of an architecture
+// shares one name.
+TEST(IntelGPUArchName, RevisionDoesNotAffectTheName) {
+  EXPECT_EQ(getIntelGPUArchName(gmdid(12, 60, 0)), "xe-pvc");
+  EXPECT_EQ(getIntelGPUArchName(gmdid(12, 60, 63)), "xe-pvc");
+}
+
+// An architecture that is not in the table has no name at all.  Naming it after
+// its GMDID would print something that --offload-arch cannot accept, so the
+// utility reports it as an error instead.
+TEST(IntelGPUArchName, UnknownArchitecturesHaveNoName) {
+  EXPECT_TRUE(getIntelGPUArchName(gmdid(40, 11, 0)).empty());
+  EXPECT_TRUE(getIntelGPUArchName(gmdid(12, 99, 3)).empty());
+}
+
+// Pre-Xe devices report a GMDID too, and none of them are in the table.
+TEST(IntelGPUArchName, LegacyArchitecture) {
+  EXPECT_TRUE(getIntelGPUArchName(gmdid(9, 0, 9)).empty());
+}
diff --git a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def
new file mode 100644
index 0000000000000..7af65e547e93d
--- /dev/null
+++ b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def
@@ -0,0 +1,94 @@
+//===--- IntelGPUTargetParser.def - Intel GPU target data ------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file is the single source of truth for the Intel GPU list. Each row
+// describes one architecture name that --offload-arch accepts, along with the
+// IGCA (Intel Graphics Compute Architecture) level the compiler targets when
+// the user names it. Adding a device is a single row here.
+//
+//   INTEL_GPU(NAME, KIND, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)
+//     NAME         - Human-friendly device name, e.g. "xe-cri".
+//     KIND         - GPUKind enumerator suffix; the enumerator is GK_<KIND>.
+//     ARCHITECTURE - Architecture component of the GMDID the device reports.
+//     RELEASE      - Release component of the GMDID the device reports.
+//     IGCA_LEVEL   - Numeric IGCA level.
+//     IGCA_SUFFIX  - Token naming the feature sets that the level comprises:
+//                    Core (no suffix), Compute ("c"), Render ("r"),
+//                    ComputeExact ("ca") or RenderExact ("ra"). A consumer maps
+//                    the token onto an enumerator of its own.
+//
+//   INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)
+//     A compatibility name that covers several releases, e.g. "xe-dg2", which
+//     covers every xe-dg2-* and xe-acm-* platform. These names have no GMDID
+//     of their own, so no device reports one and the offload-arch utility
+//     never prints one, but they are legal --offload-arch values. The columns
+//     mean the same as above.
+//
+// The revision (stepping) component of the GMDID is deliberately not part of
+// the key: every stepping of a release shares one name and one IGCA level.
+//
+// Several devices can share an architecture and a release. The rows are ordered
+// so that the name to print for such a group comes first.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef INTEL_GPU
+#define INTEL_GPU(NAME, KIND, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)
+#endif
+
+#ifndef INTEL_GPU_COMPAT
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)
+#endif
+
+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)
+INTEL_GPU("xe-lnl-m",    XE_LNL_M,    20, 4,   40, Render)
+INTEL_GPU("xe-bmg-g31",  XE_BMG_G31,  20, 2,   40, Render)
+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
+INTEL_GPU_COMPAT("xe-ptl", XE_PTL, 50, Render)
+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)
+
+#undef INTEL_GPU
+#undef INTEL_GPU_COMPAT
diff --git a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h
new file mode 100644
index 0000000000000..d0ed929a5b914
--- /dev/null
+++ b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h
@@ -0,0 +1,67 @@
+//===-- IntelGPUTargetParser.h - Parser for Intel GPU targets ---*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// 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.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_TARGETPARSER_INTELGPUTARGETPARSER_H
+#define LLVM_TARGETPARSER_INTELGPUTARGETPARSER_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Compiler.h"
+#include <cstdint>
+#include <string>
+
+namespace llvm {
+namespace IntelGPU {
+
+/// The Intel GPU architecture names this build knows, covering both physical
+/// devices and the compatibility names that stand for a whole product line.
+enum GPUKind : uint16_t {
+  GK_NONE = 0,
+#define INTEL_GPU(NAME, KIND, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
+  GK_##KIND,
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX) GK_##KIND,
+#include "llvm/TargetParser/IntelGPUTargetParser.def"
+};
+
+/// The components that a GPU IP version, the "GMDID", packs into one 32-bit
+/// value. The revision identifies the hardware stepping.
+struct GMDID {
+  unsigned Architecture = 0;
+  unsigned Release = 0;
+  unsigned Revision = 0;
+};
+
+/// Split the GPU IP version \p IPVersion, as reported by the driver, into its
+/// components.
+LLVM_ABI GMDID decodeGMDID(uint32_t IPVersion);
+
+/// The device whose GMDID has the same architecture and release as \p ID, or
+/// GK_NONE if this build knows no such device. The revision is ignored: as far
+/// as the compiler is concerned, every stepping of a release is one device.
+/// When several devices share an architecture and a release, the first one
+/// listed in IntelGPUTargetParser.def names the group and is returned.
+LLVM_ABI GPUKind getKindForGMDID(GMDID ID);
+
+/// The human-friendly name of \p Kind, e.g. "xe-pvc", or "" for GK_NONE.
+LLVM_ABI StringRef getArchName(GPUKind Kind);
+
+/// Spell \p ID the way an architecture name spells a GMDID, e.g. "xe_35.11.0".
+/// Every device has such a name, including one that is not in the table, which
+/// makes this the only way to name a device this build does not know.
+LLVM_ABI std::string getNumericArchName(GMDID ID);
+
+} // namespace IntelGPU
+} // namespace llvm
+
+#endif // LLVM_TARGETPARSER_INTELGPUTARGETPARSER_H
diff --git a/llvm/include/module.modulemap b/llvm/include/module.modulemap
index 69836bf2e3158..03cab392cb619 100644
--- a/llvm/include/module.modulemap
+++ b/llvm/include/module.modulemap
@@ -438,6 +438,7 @@ module LLVM_Utils {
     // These are intended for textual inclusion.
     textual header "llvm/TargetParser/ARMTargetParser.def"
     textual header "llvm/TargetParser/CSKYTargetParser.def"
+    textual header "llvm/TargetParser/IntelGPUTargetParser.def"
     textual header "llvm/TargetParser/X86TargetParser.def"
     textual header "llvm/TargetParser/LoongArchTargetParser.def"
     textual header "llvm/TargetParser/NVPTXTargetParser.def"
diff --git a/llvm/lib/TargetParser/CMakeLists.txt b/llvm/lib/TargetParser/CMakeLists.txt
index cb45571583d23..27d6511ec0849 100644
--- a/llvm/lib/TargetParser/CMakeLists.txt
+++ b/llvm/lib/TargetParser/CMakeLists.txt
@@ -21,6 +21,7 @@ add_llvm_component_library(LLVMTargetParser
   AVRTargetParser.cpp
   CSKYTargetParser.cpp
   Host.cpp
+  IntelGPUTargetParser.cpp
   LoongArchTargetParser.cpp
   NVPTXTargetParser.cpp
   PPCTargetParser.cpp
diff --git a/llvm/lib/TargetParser/IntelGPUTargetParser.cpp b/llvm/lib/TargetParser/IntelGPUTargetParser.cpp
new file mode 100644
index 0000000000000..33e8c93b32612
--- /dev/null
+++ b/llvm/lib/TargetParser/IntelGPUTargetParser.cpp
@@ -0,0 +1,60 @@
+//===-- IntelGPUTargetParser - Parser for Intel GPU targets ----*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements a target parser for the Intel GPU list.
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/TargetParser/IntelGPUTargetParser.h"
+#include "llvm/ADT/Twine.h"
+
+using namespace llvm;
+using namespace IntelGPU;
+
+// A GMDID packs the architecture, release and revision of the GPU IP.
+static constexpr uint32_t GMDIDArchitectureShift = 22;
+static constexpr uint32_t GMDIDReleaseShift = 14;
+static constexpr uint32_t GMDIDReleaseMask = 0xff;
+static constexpr uint32_t GMDIDRevisionMask = 0x3f;
+
+GMDID llvm::IntelGPU::decodeGMDID(uint32_t IPVersion) {
+  return {IPVersion >> GMDIDArchitectureShift,
+          (IPVersion >> GMDIDReleaseShift) & GMDIDReleaseMask,
+          IPVersion & GMDIDRevisionMask};
+}
+
+GPUKind llvm::IntelGPU::getKindForGMDID(GMDID ID) {
+  // Only INTEL_GPU rows are expanded, so a compatibility name can never match.
+  // The rows are ordered so that the first match in a group names the group.
+#define INTEL_GPU(NAME, KIND, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
+  if (ID.Architecture == ARCHITECTURE && ID.Release == RELEASE)                \
+    return GK_##KIND;
+#include "llvm/TargetParser/IntelGPUTargetParser.def"
+  return GK_NONE;
+}
+
+StringRef llvm::IntelGPU::getArchName(GPUKind Kind) {
+  switch (Kind) {
+  case GK_NONE:
+    return "";
+#define INTEL_GPU(NAME, KIND, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
+  case GK_##KIND:                                                              \
+    return NAME;
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)                  \
+  case GK_##KIND:                                                              \
+    return NAME;
+#include "llvm/TargetParser/IntelGPUTargetParser.def"
+  }
+  llvm_unreachable("invalid Intel GPU GPUKind");
+}
+
+std::string llvm::IntelGPU::getNumericArchName(GMDID ID) {
+  return ("xe_" + Twine(ID.Architecture) + "." + Twine(ID.Release) + "." +
+          Twine(ID.Revision))
+      .str();
+}
diff --git a/llvm/unittests/TargetParser/CMakeLists.txt b/llvm/unittests/TargetParser/CMakeLists.txt
index 9ef532603517b..65b2ff085a299 100644
--- a/llvm/unittests/TargetParser/CMakeLists.txt
+++ b/llvm/unittests/TargetParser/CMakeLists.txt
@@ -7,6 +7,7 @@ add_llvm_unittest(TargetParserTests
   AtomicScopeTest.cpp
   CSKYTargetParserTest.cpp
   Host.cpp
+  IntelGPUTargetParserTest.cpp
   NVPTXTargetParserTest.cpp
   RISCVISAInfoTest.cpp
   RISCVTargetParserTest.cpp
diff --git a/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp b/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp
new file mode 100644
index 0000000000000..a80aa80e5dab7
--- /dev/null
+++ b/llvm/unittests/TargetParser/IntelGPUTargetParserTest.cpp
@@ -0,0 +1,76 @@
+//===------- IntelGPUTargetParserTest.cpp - Intel GPU Target Parser -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/TargetParser/IntelGPUTargetParser.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+// Build a GMDID the way the Level Zero driver reports it.
+constexpr uint32_t gmdid(uint32_t Architecture, uint32_t Release,
+                         uint32_t Revision) {
+  return (Architecture << 22) | (Release << 14) | Revision;
+}
+
+TEST(IntelGPUTargetParserTest, DecodeGMDID) {
+  IntelGPU::GMDID ID = IntelGPU::decodeGMDID(gmdid(35, 11, 7));
+  EXPECT_EQ(ID.Architecture, 35u);
+  EXPECT_EQ(ID.Release, 11u);
+  EXPECT_EQ(ID.Revision, 7u);
+
+  // The revision occupies the low 6 bits and the release the 8 above it, so
+  // neither can bleed into the architecture.
+  ID = IntelGPU::decodeGMDID(gmdid(12, 0xff, 0x3f));
+  EXPECT_EQ(ID.Architecture, 12u);
+  EXPECT_EQ(ID.Release, 0xffu);
+  EXPECT_EQ(ID.Revision, 0x3fu);
+}
+
+TEST(IntelGPUTargetParserTest, KindForGMDID) {
+  EXPECT_EQ(IntelGPU::getKindForGMDID({12, 60, 7}), IntelGPU::GK_XE_PVC);
+  EXPECT_EQ(IntelGPU::getKindForGMDID({35, 11, 0}), IntelGPU::GK_XE_CRI);
+  // The revision is not part of the key: every stepping of a release is the
+  // same device.
+  EXPECT_EQ(IntelGPU::getKindForGMDID({12, 60, 0}), IntelGPU::GK_XE_PVC);
+  EXPECT_EQ(IntelGPU::getKindForGMDID({12, 60, 63}), IntelGPU::GK_XE_PVC);
+  // When several devices share an architecture and a release, the first row of
+  // the group wins.
+  EXPECT_EQ(IntelGPU::getKindForGMDID({30, 5, 0}), IntelGPU::GK_XE_NVL_U);
+  EXPECT_EQ(IntelGPU::getKindForGMDID({12, 55, 0}), IntelGPU::GK_XE_ACM_G10);
+  // A device that is not in the table has no kind at all.
+  EXPECT_EQ(IntelGPU::getKindForGMDID({40, 11, 0}), IntelGPU::GK_NONE);
+  EXPECT_EQ(IntelGPU::getKindForGMDID({9, 0, 9}), IntelGPU::GK_NONE);
+}
+
+TEST(IntelGPUTargetParserTest, ArchNames) {
+  EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_XE_PVC), "xe-pvc");
+  EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_XE_ATS_M150), "xe-ats-m150");
+  EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_XE_MTL), "xe-mtl");
+  EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_NONE), "");
+}
+
+TEST(IntelGPUTargetParserTest, EveryKindIsNamed) {
+  // A row with no name would make the offload-arch utility print an empty
+  // architecture, so every kind the table declares must have a spelling.
+#define INTEL_GPU(NAME, KIND, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
+  EXPECT_FALSE(IntelGPU::getArchName(IntelGPU::GK_##KIND).empty()) << #KIND;
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)                  \
+  EXPECT_FALSE(IntelGPU::getArchName(IntelGPU::GK_##KIND).empty()) << #KIND;
+#include "llvm/TargetParser/IntelGPUTargetParser.def"
+}
+
+TEST(IntelGPUTargetParserTest, NumericArchName) {
+  EXPECT_EQ(IntelGPU::getNumericArchName({35, 11, 0}), "xe_35.11.0");
+  EXPECT_EQ(IntelGPU::getNumericArchName({12, 99, 3}), "xe_12.99.3");
+  // Pre-Xe devices report a GMDID too, and none of them are in the table.
+  EXPECT_EQ(IntelGPU::getNumericArchName({9, 0, 9}), "xe_9.0.9");
+}
+
+} // namespace

>From 169f74c173edb087e6bf9bc20efc0cea12ac5a3b Mon Sep 17 00:00:00 2001
From: "Kornev, Nikita" <nikita.kornev at intel.com>
Date: Fri, 11 Sep 2026 10:47:24 +0200
Subject: [PATCH 2/3] [offload-arch] Name an unknown Intel GPU after its GMDID

Fold the numeric fallback into getIntelGPUArchName(), so that the GMDID is
decoded once and every device gets a name.
---
 clang/tools/offload-arch/LevelZeroArch.cpp    | 29 +++++++------------
 .../offload-arch/OffloadArchTest.cpp          | 15 +++++-----
 2 files changed, 17 insertions(+), 27 deletions(-)

diff --git a/clang/tools/offload-arch/LevelZeroArch.cpp b/clang/tools/offload-arch/LevelZeroArch.cpp
index 77716e2d52f66..a7c58b0efccc6 100644
--- a/clang/tools/offload-arch/LevelZeroArch.cpp
+++ b/clang/tools/offload-arch/LevelZeroArch.cpp
@@ -16,6 +16,7 @@
 #include "llvm/Support/Error.h"
 #include "llvm/TargetParser/IntelGPUTargetParser.h"
 #include <cstdio>
+#include <string>
 
 #define ZE_MAX_DEVICE_NAME 256
 #define ZE_MAX_DEVICE_UUID_SIZE 16
@@ -158,10 +159,14 @@ static bool loadLevelZero() {
   } while (0)
 
 // Translate a GMDID into an architecture name that is a legal --offload-arch
-// parameter, or "" if this build does not know the device.
-StringRef getIntelGPUArchName(uint32_t IPVersion) {
-  return IntelGPU::getArchName(
-      IntelGPU::getKindForGMDID(IntelGPU::decodeGMDID(IPVersion)));
+// parameter. A device this build knows no name for is named after its GMDID, so
+// that it is reported like any other one.
+std::string getIntelGPUArchName(uint32_t IPVersion) {
+  IntelGPU::GMDID ID = IntelGPU::decodeGMDID(IPVersion);
+  StringRef Name = IntelGPU::getArchName(IntelGPU::getKindForGMDID(ID));
+  if (!Name.empty())
+    return Name.str();
+  return IntelGPU::getNumericArchName(ID);
 }
 
 int printGPUsByLevelZero() {
@@ -210,21 +215,7 @@ int printGPUsByLevelZero() {
       if (Verbose)
         llvm::errs() << "Found device '" << DeviceProperties.name << "'\n";
 
-      // Naming an unknown device after its GMDID would print something that
-      // --offload-arch cannot accept, because this build knows no IGCA level to
-      // compile for.  Report it instead, spelling out the GMDID so that the
-      // device can be identified.
-      StringRef Arch = getIntelGPUArchName(IPVersion.ipVersion);
-      if (Arch.empty()) {
-        llvm::errs() << "Unknown Intel GPU '" << DeviceProperties.name
-                     << "', which reports the architecture "
-                     << IntelGPU::getNumericArchName(
-                            IntelGPU::decodeGMDID(IPVersion.ipVersion))
-                     << "\n";
-        return 1;
-      }
-
-      llvm::outs() << Arch << '\n';
+      llvm::outs() << getIntelGPUArchName(IPVersion.ipVersion) << '\n';
     }
   }
 
diff --git a/clang/unittests/offload-arch/OffloadArchTest.cpp b/clang/unittests/offload-arch/OffloadArchTest.cpp
index dbdcf52703dc3..de9fe398fcdae 100644
--- a/clang/unittests/offload-arch/OffloadArchTest.cpp
+++ b/clang/unittests/offload-arch/OffloadArchTest.cpp
@@ -27,7 +27,7 @@ llvm::SmallVector<std::string, 8> getCandidateBinPaths(llvm::StringRef ExeDir);
 int printGPUsByKFD(llvm::StringRef NodePath);
 
 // Defined in LevelZeroArch.cpp.
-llvm::StringRef getIntelGPUArchName(uint32_t IPVersion);
+std::string getIntelGPUArchName(uint32_t IPVersion);
 
 using namespace llvm;
 
@@ -242,15 +242,14 @@ TEST(IntelGPUArchName, RevisionDoesNotAffectTheName) {
   EXPECT_EQ(getIntelGPUArchName(gmdid(12, 60, 63)), "xe-pvc");
 }
 
-// An architecture that is not in the table has no name at all.  Naming it after
-// its GMDID would print something that --offload-arch cannot accept, so the
-// utility reports it as an error instead.
-TEST(IntelGPUArchName, UnknownArchitecturesHaveNoName) {
-  EXPECT_TRUE(getIntelGPUArchName(gmdid(40, 11, 0)).empty());
-  EXPECT_TRUE(getIntelGPUArchName(gmdid(12, 99, 3)).empty());
+// An architecture that is not in the table still has to be named, so that a
+// newer device is usable with a compiler that predates it.
+TEST(IntelGPUArchName, UnknownArchitecturesGetANumericName) {
+  EXPECT_EQ(getIntelGPUArchName(gmdid(40, 11, 0)), "xe_40.11.0");
+  EXPECT_EQ(getIntelGPUArchName(gmdid(12, 99, 3)), "xe_12.99.3");
 }
 
 // Pre-Xe devices report a GMDID too, and none of them are in the table.
 TEST(IntelGPUArchName, LegacyArchitecture) {
-  EXPECT_TRUE(getIntelGPUArchName(gmdid(9, 0, 9)).empty());
+  EXPECT_EQ(getIntelGPUArchName(gmdid(9, 0, 9)), "xe_9.0.9");
 }

>From ef2917150c054eae4660fd2572d160b4cd2a844f Mon Sep 17 00:00:00 2001
From: srividya sundaram <srividya.sundaram at intel.com>
Date: Sat, 12 Sep 2026 19:09:30 -0700
Subject: [PATCH 3/3] [TargetParser] Parse Intel GPU architecture names, and
 use the list in clang

The Intel GPU list could only name the device a driver reports; add parseArch
for the other direction, accepting the friendly, compatibility, alias and
numeric spellings, plus getIGCAName for the level to compile for. In clang,
drop the two hardcoded Intel architecture strings and consult the list instead.

Co-Authored-By: Claude Opus 5 <noreply at anthropic.com>
---
 clang/include/clang/Basic/OffloadArch.h       | 28 +++---
 clang/lib/Basic/OffloadArch.cpp               | 13 +--
 .../test/Misc/target-invalid-cpu-note/nvptx.c | 43 +++++++++
 clang/unittests/Basic/OffloadArchTest.cpp     | 23 ++++-
 .../TargetParser/IntelGPUTargetParser.def     | 34 ++++++-
 .../llvm/TargetParser/IntelGPUTargetParser.h  | 38 +++++++-
 .../lib/TargetParser/IntelGPUTargetParser.cpp | 89 +++++++++++++++++-
 .../TargetParser/IntelGPUTargetParserTest.cpp | 90 +++++++++++++++++++
 8 files changed, 331 insertions(+), 27 deletions(-)

diff --git a/clang/include/clang/Basic/OffloadArch.h b/clang/include/clang/Basic/OffloadArch.h
index a0e5e72db220b..8de7af8d42e5d 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)};
+  static OffloadArch getIntelGPU(llvm::IntelGPU::GPUKind K) {
+    return {TargetArch::IntelGPU, static_cast<uint32_t>(K)};
+  }
+  // Intel CPU offload has one processor and no TargetParser list, so unlike a
+  // GPU it carries no kind.
+  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);
   }
+  // Only valid 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;
@@ -115,7 +121,7 @@ const char *OffloadArchToVirtualArchString(OffloadArch A);
 // string is not recognized.
 OffloadArch StringToOffloadArch(llvm::StringRef S);
 
-/// Append the canonical names of all NVIDIA and AMDGPU GPUs.
+/// Append the canonical names of all NVIDIA, AMDGPU and Intel GPUs.
 void fillValidOffloadArchList(llvm::SmallVectorImpl<llvm::StringRef> &Values);
 
 OffloadArch getSubArchOffloadArch(llvm::Triple::SubArchType SubArch);
diff --git a/clang/lib/Basic/OffloadArch.cpp b/clang/lib/Basic/OffloadArch.cpp
index f8c811f552f38..bf00c41a5eada 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();
 }
 
@@ -93,6 +93,7 @@ void fillValidOffloadArchList(llvm::SmallVectorImpl<llvm::StringRef> &Values) {
   Values.push_back(NAME);
 #include "llvm/TargetParser/NVPTXTargetParser.def"
   llvm::AMDGPU::fillValidArchListAMDGCN(Values, llvm::Triple::NoSubArch);
+  llvm::IntelGPU::fillValidArchList(Values);
 }
 
 OffloadArch getSubArchOffloadArch(llvm::Triple::SubArchType SubArch) {
diff --git a/clang/test/Misc/target-invalid-cpu-note/nvptx.c b/clang/test/Misc/target-invalid-cpu-note/nvptx.c
index 20dbd8dc6aa3b..23fb8a637df87 100644
--- a/clang/test/Misc/target-invalid-cpu-note/nvptx.c
+++ b/clang/test/Misc/target-invalid-cpu-note/nvptx.c
@@ -128,4 +128,47 @@
 // CHECK-SAME: {{^}}, polaris11
 // CHECK-SAME: {{^}}, tongapro
 // CHECK-SAME: {{^}}, stoney
+// CHECK-SAME: {{^}}, xe-cri
+// CHECK-SAME: {{^}}, xe-nvl-p
+// CHECK-SAME: {{^}}, xe-nvl-u
+// CHECK-SAME: {{^}}, xe-nvl-h
+// CHECK-SAME: {{^}}, xe-nvl-s
+// CHECK-SAME: {{^}}, xe-nvl-hx
+// CHECK-SAME: {{^}}, xe-nvl-ul
+// CHECK-SAME: {{^}}, xe-wcl
+// CHECK-SAME: {{^}}, xe-ptl-u
+// CHECK-SAME: {{^}}, xe-ptl-h
+// CHECK-SAME: {{^}}, xe-lnl-m
+// CHECK-SAME: {{^}}, xe-bmg-g31
+// CHECK-SAME: {{^}}, xe-bmg-g21
+// CHECK-SAME: {{^}}, xe-arl-h
+// CHECK-SAME: {{^}}, xe-mtl-h
+// CHECK-SAME: {{^}}, xe-mtl-u
+// CHECK-SAME: {{^}}, xe-arl-u
+// CHECK-SAME: {{^}}, xe-arl-s
+// CHECK-SAME: {{^}}, xe-pvc-vg
+// CHECK-SAME: {{^}}, xe-pvc
+// CHECK-SAME: {{^}}, xe-pvc-sdv
+// CHECK-SAME: {{^}}, xe-acm-g12
+// CHECK-SAME: {{^}}, xe-dg2-g12
+// CHECK-SAME: {{^}}, xe-acm-g11
+// CHECK-SAME: {{^}}, xe-dg2-g11
+// CHECK-SAME: {{^}}, xe-ats-m75
+// CHECK-SAME: {{^}}, xe-acm-g10
+// CHECK-SAME: {{^}}, xe-dg2-g10
+// CHECK-SAME: {{^}}, xe-ats-m150
+// CHECK-SAME: {{^}}, xe-dg1
+// CHECK-SAME: {{^}}, xe-adl-n
+// CHECK-SAME: {{^}}, xe-adl-p
+// CHECK-SAME: {{^}}, xe-rpl-p
+// CHECK-SAME: {{^}}, xe-adl-s
+// CHECK-SAME: {{^}}, xe-rpl-s
+// CHECK-SAME: {{^}}, xe-rkl
+// CHECK-SAME: {{^}}, xe-tgllp
+// CHECK-SAME: {{^}}, xe-tgl
+// CHECK-SAME: {{^}}, xe-ptl
+// CHECK-SAME: {{^}}, xe-bmg
+// CHECK-SAME: {{^}}, xe-mtl
+// CHECK-SAME: {{^}}, xe-dg2
+// CHECK-SAME: {{^}}, bmg_g21
 // CHECK-SAME: {{$}}
diff --git a/clang/unittests/Basic/OffloadArchTest.cpp b/clang/unittests/Basic/OffloadArchTest.cpp
index 013ed75fbd57e..6c5f255249ea7 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 name ocloc uses for its own -device parameter.
+  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 7af65e547e93d..0f5830da2a767 100644
--- a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def
+++ b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.def
@@ -17,10 +17,17 @@
 //     ARCHITECTURE - Architecture component of the GMDID the device reports.
 //     RELEASE      - Release component of the GMDID the device reports.
 //     IGCA_LEVEL   - Numeric IGCA level.
-//     IGCA_SUFFIX  - Token naming the feature sets that the level comprises:
+//     IGCA_SUFFIX  - Token naming the suffix class of the level, which says
+//                    which feature sets it comprises and whether it is exact:
 //                    Core (no suffix), Compute ("c"), Render ("r"),
-//                    ComputeExact ("ca") or RenderExact ("ra"). A consumer maps
-//                    the token onto an enumerator of its own.
+//                    ComputeExact ("ca") or 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. Should
+//                    anything need to reason about the class itself, rather
+//                    than spell it, the tokens are spelled to serve as the
+//                    enumerators of an IntelGPU::LevelSuffix, in the way that
+//                    NVPTXTargetParser.def's SUFFIX column serves
+//                    NVPTX::ArchSuffix.
 //
 //   INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)
 //     A compatibility name that covers several releases, e.g. "xe-dg2", which
@@ -29,12 +36,25 @@
 //     never prints one, but they are legal --offload-arch values. The columns
 //     mean the same as above.
 //
+//   INTEL_GPU_ALIAS(NAME, KIND)
+//     A further spelling of a device that already has a row, e.g. the name
+//     ocloc uses for its own -device parameter. 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.
+//
 // The revision (stepping) component of the GMDID is deliberately not part of
 // the key: every stepping of a release shares one name and one IGCA level.
 //
 // Several devices can share an architecture and a release. The rows are ordered
 // so that the name to print for such a group comes first.
 //
+// Devices that exist but have no published human-friendly name are deliberately
+// left out, because NAME has no legal value for them. They are not forgotten:
+// the offload-arch utility names such a device after its GMDID, and adding a
+// row later is all that is needed to make it targetable. Do not supply an
+// invented name in the meantime -- a name that ships becomes an --offload-arch
+// value that has to keep working, and an unofficial one cannot be withdrawn.
+//
 //===----------------------------------------------------------------------===//
 
 #ifndef INTEL_GPU
@@ -45,6 +65,10 @@
 #define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)
 #endif
 
+#ifndef INTEL_GPU_ALIAS
+#define INTEL_GPU_ALIAS(NAME, KIND)
+#endif
+
 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)
@@ -90,5 +114,9 @@ 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 spellings of a device named above
+INTEL_GPU_ALIAS("bmg_g21", XE_BMG_G21)
+
 #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 d0ed929a5b914..038000d5f9a97 100644
--- a/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h
+++ b/llvm/include/llvm/TargetParser/IntelGPUTargetParser.h
@@ -7,9 +7,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.
+// It answers the two questions the compiler asks of the list: what to call the
+// device a driver reports, and what to compile for when the user names one.
 //
 //===----------------------------------------------------------------------===//
 
@@ -22,11 +21,16 @@
 #include <string>
 
 namespace llvm {
+template <typename T> class SmallVectorImpl;
+
 namespace IntelGPU {
 
 /// The Intel GPU architecture names this build knows, covering both physical
 /// devices and the compatibility names that stand for a whole product line.
-enum GPUKind : uint16_t {
+///
+/// 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, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
   GK_##KIND,
@@ -61,6 +65,32 @@ LLVM_ABI StringRef getArchName(GPUKind Kind);
 /// makes this the only way to name a device this build does not know.
 LLVM_ABI std::string getNumericArchName(GMDID ID);
 
+/// 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,
+/// since the table is keyed on the architecture and the release alone, 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 is not a name
+/// this build knows, and yields GK_NONE like any other unknown name.
+LLVM_ABI GPUKind parseArch(StringRef Name);
+
+/// The IGCA level name to compile \p Kind for, e.g. "xe-pvc" -> "igca_20ca",
+/// or "" for GK_NONE. This is the spelling -target-cpu is invoked with.
+///
+/// A level names a set of features rather than a device, and its suffix says
+/// which sets it comprises: igca_60 is the core features, igca_60c adds the
+/// compute features, and igca_60ca is exact, meaning that only a device at that
+/// level 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 33e8c93b32612..8dccc60d8a07c 100644
--- a/llvm/lib/TargetParser/IntelGPUTargetParser.cpp
+++ b/llvm/lib/TargetParser/IntelGPUTargetParser.cpp
@@ -11,12 +11,16 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/TargetParser/IntelGPUTargetParser.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringSwitch.h"
 #include "llvm/ADT/Twine.h"
 
 using namespace llvm;
 using namespace IntelGPU;
 
-// A GMDID packs the architecture, release and revision of the GPU IP.
+// A GMDID packs the architecture (bits 31:22), the release (21:14) and the
+// revision (5:0) of the GPU IP into one 32-bit value. Bits 13:6 are reserved,
+// which is why the release and the revision fields do not meet.
 static constexpr uint32_t GMDIDArchitectureShift = 22;
 static constexpr uint32_t GMDIDReleaseShift = 14;
 static constexpr uint32_t GMDIDReleaseMask = 0xff;
@@ -58,3 +62,86 @@ std::string llvm::IntelGPU::getNumericArchName(GMDID ID) {
           Twine(ID.Revision))
       .str();
 }
+
+// Read a numeric architecture name, e.g. "xe_12.60.7", into \p ID. 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, GMDID &ID) {
+  if (!Name.consume_front("xe_"))
+    return false;
+
+  StringRef Architecture, Release, Revision;
+  std::tie(Architecture, Name) = Name.split('.');
+  std::tie(Release, Revision) = Name.split('.');
+  if (Architecture.getAsInteger(10, ID.Architecture) ||
+      Release.getAsInteger(10, ID.Release))
+    return false;
+  if (!Revision.empty() && Revision.getAsInteger(10, ID.Revision))
+    return false;
+  return true;
+}
+
+GPUKind llvm::IntelGPU::parseArch(StringRef Name) {
+  GPUKind Kind = StringSwitch<GPUKind>(Name)
+#define INTEL_GPU(NAME, KIND, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
+  .Case(NAME, GK_##KIND)
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)                  \
+  .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 GMDID has to be reachable by name.
+  GMDID ID;
+  if (parseNumericArchName(Name, ID))
+    return getKindForGMDID(ID);
+  return GK_NONE;
+}
+
+// The suffix each IGCA_SUFFIX token contributes to a level name. Pasting a
+// row's token onto this prefix turns the column straight into its spelling.
+#define IGCA_SUFFIX_Core ""
+#define IGCA_SUFFIX_Compute "c"
+#define IGCA_SUFFIX_Render "r"
+#define IGCA_SUFFIX_ComputeExact "ca"
+#define IGCA_SUFFIX_RenderExact "ra"
+
+StringRef llvm::IntelGPU::getIGCAName(GPUKind Kind) {
+  // Unlike the NVPTX virtual architecture name, this is not a column of its own:
+  // the level and the suffix already spell it, and a column would let the three
+  // disagree. Both parts are known at compile time, so each row yields one
+  // literal rather than a string built on demand.
+  switch (Kind) {
+  case GK_NONE:
+    return "";
+#define INTEL_GPU(NAME, KIND, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
+  case GK_##KIND:                                                              \
+    return "igca_" #IGCA_LEVEL IGCA_SUFFIX_##IGCA_SUFFIX;
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)                  \
+  case GK_##KIND:                                                              \
+    return "igca_" #IGCA_LEVEL IGCA_SUFFIX_##IGCA_SUFFIX;
+#include "llvm/TargetParser/IntelGPUTargetParser.def"
+  }
+  llvm_unreachable("invalid Intel GPU GPUKind");
+}
+
+#undef IGCA_SUFFIX_Core
+#undef IGCA_SUFFIX_Compute
+#undef IGCA_SUFFIX_Render
+#undef IGCA_SUFFIX_ComputeExact
+#undef IGCA_SUFFIX_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, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
+  Values.push_back(NAME);
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)                  \
+  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 a80aa80e5dab7..5debf945d9610 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"
 
 using namespace llvm;
@@ -49,6 +51,25 @@ TEST(IntelGPUTargetParserTest, KindForGMDID) {
   EXPECT_EQ(IntelGPU::getKindForGMDID({9, 0, 9}), IntelGPU::GK_NONE);
 }
 
+TEST(IntelGPUTargetParserTest, CompatibilityNamesHaveNoGMDID) {
+  // A compatibility name stands for a group of releases and no device reports
+  // it, so no GMDID may ever resolve to one. A row that grew GMDID columns
+  // would silently start being printed by the offload-arch utility.
+  for (unsigned Architecture = 0; Architecture != 64; ++Architecture)
+    for (unsigned Release = 0; Release != 256; ++Release) {
+      IntelGPU::GPUKind Kind =
+          IntelGPU::getKindForGMDID({Architecture, Release, 0});
+      switch (Kind) {
+      default:
+        break;
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)                  \
+  case IntelGPU::GK_##KIND:                                                    \
+    FAIL() << NAME << " matched a GMDID";
+#include "llvm/TargetParser/IntelGPUTargetParser.def"
+      }
+    }
+}
+
 TEST(IntelGPUTargetParserTest, ArchNames) {
   EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_XE_PVC), "xe-pvc");
   EXPECT_EQ(IntelGPU::getArchName(IntelGPU::GK_XE_ATS_M150), "xe-ats-m150");
@@ -73,4 +94,73 @@ TEST(IntelGPUTargetParserTest, NumericArchName) {
   EXPECT_EQ(IntelGPU::getNumericArchName({9, 0, 9}), "xe_9.0.9");
 }
 
+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-ats-m150"), IntelGPU::GK_XE_ATS_M150);
+  EXPECT_EQ(IntelGPU::parseArch("xe-dg2"), IntelGPU::GK_XE_DG2);
+  EXPECT_EQ(IntelGPU::parseArch("bmg_g21"), IntelGPU::GK_XE_BMG_G21);
+
+  // 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_35.32.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);
+}
+
+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, ARCHITECTURE, RELEASE, IGCA_LEVEL, IGCA_SUFFIX)  \
+  EXPECT_EQ(IntelGPU::parseArch(NAME), IntelGPU::GK_##KIND) << NAME;
+#define INTEL_GPU_COMPAT(NAME, KIND, IGCA_LEVEL, IGCA_SUFFIX)                  \
+  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 level and the suffix 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



More information about the cfe-commits mailing list