[llvm] AMDGPU: Validate processor and features in TargetID parsing (PR #209304)

Matt Arsenault via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 17 04:56:33 PDT 2026


https://github.com/arsenm updated https://github.com/llvm/llvm-project/pull/209304

>From 8e324e38253b957315684c1fcc219ef552b221b4 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Mon, 13 Jul 2026 13:58:10 +0200
Subject: [PATCH 1/4] AMDGPU: Validate processor and features in TargetID
 parsing

TargetID::parseTargetIDString previously only checked that the string was
structurally a 4-component triple followed by a processor field. It
accepted unrecognized processors and silently ignored malformed or
unsupported feature modifiers. Work towards improving validation so in
the future clang's copy of TargetID can be replaced.

Co-authored-by: Claude (Opus 4.8)
---
 .../llvm/TargetParser/AMDGPUTargetParser.h    |  22 ++-
 .../AMDGPU/AsmParser/AMDGPUAsmParser.cpp      |  10 +-
 llvm/lib/Target/AMDGPU/GCNSubtarget.h         |   4 +-
 .../MCTargetDesc/AMDGPUTargetStreamer.cpp     |   8 +-
 .../Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp    |   2 +-
 llvm/lib/TargetParser/AMDGPUTargetParser.cpp  | 174 +++++++++++++-----
 .../TargetParser/TargetParserTest.cpp         | 149 +++++++++++++++
 7 files changed, 313 insertions(+), 56 deletions(-)

diff --git a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
index db8969b8f663e..aa77ee9677682 100644
--- a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
+++ b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
@@ -155,6 +155,12 @@ fillAMDGPUFeatureMap(StringRef GPU, const Triple &T, StringMap<bool> &Features);
 
 enum class TargetIDSetting { Unsupported, Any, Off, On };
 
+/// Split a target-id string \p TargetID into its processor and the
+/// xnack/sramecc feature modifiers present. This is purely syntactic.
+LLVM_ABI StringRef splitTargetID(StringRef TargetID,
+                                 TargetIDSetting &XnackSetting,
+                                 TargetIDSetting &SramEccSetting);
+
 class LLVM_ABI TargetID {
 private:
   GPUKind Arch;
@@ -227,8 +233,6 @@ class LLVM_ABI TargetID {
     SramEccSetting = NewSramEccSetting;
   }
 
-  void setTargetIDFromTargetIDStream(StringRef TargetID);
-
   GPUKind getGPUKind() const { return Arch; }
 
   StringRef getTargetTripleString() const { return TargetTripleString; }
@@ -236,6 +240,15 @@ class LLVM_ABI TargetID {
   /// \returns True if this is an AMDHSA target.
   bool isAMDHSA() const { return IsAMDHSA; }
 
+  /// Parse and validate a TargetID for triple \p TT from the processor+features
+  /// string \p ProcAndFeatures (e.g. "gfx90a", "gfx90a:xnack+:sramecc-", "").
+  /// Returns std::nullopt if the triple is not AMDGCN, the processor is
+  /// unrecognized, or a feature modifier is invalid for the processor.
+  static std::optional<TargetID> parse(const Triple &TT,
+                                       StringRef ProcAndFeatures);
+
+  /// Parse and validate a TargetID from a full
+  /// "<triple>-<processor>:<features>" directive string.
   static std::optional<TargetID>
   parseTargetIDString(StringRef TargetIDDirective);
 
@@ -253,6 +266,11 @@ class LLVM_ABI TargetID {
 
   std::string toString() const;
 
+  /// \returns the canonical processor name followed by any explicit xnack and
+  /// sramecc feature modifiers order (e.g.  "gfx908:sramecc-:xnack+"), without
+  /// the triple prefix.
+  std::string getCanonicalFeatureString() const;
+
   bool operator==(const TargetID &Other) const;
   bool operator!=(const TargetID &Other) const { return !(*this == Other); }
 };
diff --git a/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp b/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp
index 7ead3d6f2b263..699b4f7896026 100644
--- a/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp
+++ b/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp
@@ -6418,9 +6418,13 @@ bool AMDGPUAsmParser::ParseDirectiveAMDHSAKernel() {
         return Error(IDRange.Start, "directive requires gfx8+", IDRange);
       if (!isUInt<1>(Val))
         return OutOfRangeError(ValRange);
-      if (Val != getTargetStreamer().getTargetID()->isXnackOnOrAny())
-        return getParser().Error(IDRange.Start, ".amdhsa_reserve_xnack_mask does not match target id",
-                                 IDRange);
+      bool XnackOn = getTargetStreamer().getTargetID()->isXnackOnOrAny() ||
+                     getSTI().hasFeature(AMDGPU::FeatureXNACK);
+      if (Val != XnackOn) {
+        return getParser().Error(
+            IDRange.Start,
+            ".amdhsa_reserve_xnack_mask does not match target id", IDRange);
+      }
     } else if (ID == ".amdhsa_float_round_mode_32") {
       PARSE_BITS_ENTRY(KD.compute_pgm_rsrc1,
                        COMPUTE_PGM_RSRC1_FLOAT_ROUND_MODE_32, ExprVal,
diff --git a/llvm/lib/Target/AMDGPU/GCNSubtarget.h b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
index 170ec7f85aa23..710d25e535b80 100644
--- a/llvm/lib/Target/AMDGPU/GCNSubtarget.h
+++ b/llvm/lib/Target/AMDGPU/GCNSubtarget.h
@@ -343,7 +343,9 @@ class GCNSubtarget final : public AMDGPUGenSubtargetInfo,
     return HasUnalignedScratchAccess && HasUnalignedAccessMode;
   }
 
-  bool isXNACKEnabled() const { return TargetID.isXnackOnOrAny(); }
+  bool isXNACKEnabled() const {
+    return enableXNACK() || TargetID.isXnackOnOrAny();
+  }
 
   bool hasRelaxedBufferOOBMode() const { return BufferOOBRelaxed; }
   bool hasRelaxedTBufferOOBMode() const { return TBufferOOBRelaxed; }
diff --git a/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp b/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp
index d70ad37cca867..466464142ef34 100644
--- a/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp
+++ b/llvm/lib/Target/AMDGPU/MCTargetDesc/AMDGPUTargetStreamer.cpp
@@ -554,9 +554,11 @@ void AMDGPUTargetAsmStreamer::EmitAmdhsaKernelDescriptor(
     break;
   case AMDGPU::AMDHSA_COV4:
   case AMDGPU::AMDHSA_COV5:
-    if (getTargetID()->isXnackSupported())
-      OS << "\t\t.amdhsa_reserve_xnack_mask " << getTargetID()->isXnackOnOrAny()
-         << '\n';
+    if (STI.hasFeature(AMDGPU::FeatureSupportsXNACK)) {
+      bool XnackOn = getTargetID()->isXnackOnOrAny() ||
+                     STI.hasFeature(AMDGPU::FeatureXNACK);
+      OS << "\t\t.amdhsa_reserve_xnack_mask " << XnackOn << '\n';
+    }
     break;
   }
 
diff --git a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
index e5d9c4237c2b4..3e9997a4ca088 100644
--- a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
+++ b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
@@ -1081,7 +1081,7 @@ VOPD::InstInfo getVOPDInstInfo(unsigned VOPDOpcode,
 TargetID createAMDGPUTargetID(const MCSubtargetInfo &STI,
                               StringRef FeatureString) {
   TargetID TargetID(parseArchAMDGCN(STI.getCPU()), STI.getTargetTriple(),
-                    STI.getFeatureBits().test(FeatureSupportsXNACK)
+                    STI.getFeatureBits().test(FeatureXNACKOnOffModes)
                         ? TargetIDSetting::Any
                         : TargetIDSetting::Unsupported,
                     STI.getFeatureBits().test(FeatureSupportsSRAMECC)
diff --git a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
index 16acaaeb7b71c..c93a0896e78de 100644
--- a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
+++ b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/TargetParser/AMDGPUTargetParser.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/ADT/Twine.h"
@@ -952,14 +953,35 @@ TargetID::TargetID(GPUKind Arch, const Triple &TT, TargetIDSetting XnackSetting,
       XnackSetting(XnackSetting), SramEccSetting(SramEccSetting),
       IsAMDHSA(TT.getOS() == Triple::AMDHSA) {}
 
-static TargetIDSetting
-getTargetIDSettingFromFeatureString(StringRef FeatureString) {
-  if (FeatureString.ends_with("-"))
-    return TargetIDSetting::Off;
-  if (FeatureString.ends_with("+"))
+// Parse a feature modifier sign ("+"/"-"). Returns "Unsupported" if \p Sign is
+// neither (i.e. the modifier is malformed).
+static TargetIDSetting getTargetIDSettingFromFeatureString(StringRef Sign) {
+  if (Sign == "+")
     return TargetIDSetting::On;
+  if (Sign == "-")
+    return TargetIDSetting::Off;
 
-  llvm_unreachable("Malformed feature string");
+  return TargetIDSetting::Unsupported;
+}
+
+StringRef AMDGPU::splitTargetID(StringRef TargetID,
+                                TargetIDSetting &XnackSetting,
+                                TargetIDSetting &SramEccSetting) {
+  XnackSetting = TargetIDSetting::Any;
+  SramEccSetting = TargetIDSetting::Any;
+
+  SmallVector<StringRef, 3> Split;
+  TargetID.split(Split, ':');
+
+  // The substring before the first ':' is the processor
+  for (StringRef FeatureString : ArrayRef<StringRef>(Split).drop_front()) {
+    if (FeatureString.consume_front("xnack"))
+      XnackSetting = getTargetIDSettingFromFeatureString(FeatureString);
+    else if (FeatureString.consume_front("sramecc"))
+      SramEccSetting = getTargetIDSettingFromFeatureString(FeatureString);
+  }
+
+  return Split.front();
 }
 
 // Derive the architecture from the processor name in \p TargetIDStr. "generic"
@@ -971,66 +993,118 @@ static GPUKind getGPUKindFromTargetID(const Triple &TT, StringRef TargetIDStr) {
              : parseArchAMDGCN(CPUName);
 }
 
+// Compute the xnack/sramecc settings for processor \p Arch from the
+// processor+features string \p TargetIDStr
+// (e.g. "gfx90a:xnack+:sramecc-"). Returns false if a modifier names an unknown
+// or repeated feature, names one the processor does not support, or has a
+// malformed sign.
+static bool computeTargetIDFeatures(GPUKind Arch, StringRef TargetIDStr,
+                                    TargetIDSetting &XnackSetting,
+                                    TargetIDSetting &SramEccSetting) {
+  unsigned ArchAttr = getArchAttrAMDGCN(Arch);
+  XnackSetting = (ArchAttr & FEATURE_XNACK_ON_OFF_MODES)
+                     ? TargetIDSetting::Any
+                     : TargetIDSetting::Unsupported;
+  SramEccSetting = (ArchAttr & FEATURE_SRAMECC) ? TargetIDSetting::Any
+                                                : TargetIDSetting::Unsupported;
+
+  // The first component is the processor; the rest are feature modifiers of the
+  // form "<feature><+|->".
+  SmallVector<StringRef, 3> Split;
+  TargetIDStr.split(Split, ':');
+  bool SeenXnack = false;
+  bool SeenSramEcc = false;
+  bool Valid = true;
+  for (unsigned I = 1, E = Split.size(); I != E; ++I) {
+    StringRef FeatureString = Split[I];
+    if (FeatureString.consume_front("xnack")) {
+      TargetIDSetting Sign = getTargetIDSettingFromFeatureString(FeatureString);
+      if (SeenXnack || XnackSetting == TargetIDSetting::Unsupported ||
+          Sign == TargetIDSetting::Unsupported)
+        Valid = false;
+      else
+        XnackSetting = Sign;
+      SeenXnack = true;
+    } else if (FeatureString.consume_front("sramecc")) {
+      TargetIDSetting Sign = getTargetIDSettingFromFeatureString(FeatureString);
+      if (SeenSramEcc || SramEccSetting == TargetIDSetting::Unsupported ||
+          Sign == TargetIDSetting::Unsupported)
+        Valid = false;
+      else
+        SramEccSetting = Sign;
+      SeenSramEcc = true;
+    } else {
+      // Unknown feature name.
+      Valid = false;
+    }
+  }
+  return Valid;
+}
+
 TargetID::TargetID(const Triple &TT, StringRef TargetIDStr)
     : TargetID(getGPUKindFromTargetID(TT, TargetIDStr), TT,
                TargetIDSetting::Unsupported, TargetIDSetting::Unsupported) {
-  // Default xnack/sramecc to the "Any" wildcard when the architecture supports
-  // them, then apply any explicit feature overrides from the target-id string.
-  unsigned ArchAttr = getArchAttrAMDGCN(Arch);
-  if (ArchAttr & FEATURE_XNACK)
-    XnackSetting = TargetIDSetting::Any;
-  if (ArchAttr & FEATURE_SRAMECC)
-    SramEccSetting = TargetIDSetting::Any;
-  setTargetIDFromTargetIDStream(TargetIDStr);
+  // Derive the feature settings from the string. Validity is not checked here;
+  // parseTargetIDString validates untrusted input.
+  computeTargetIDFeatures(Arch, TargetIDStr, XnackSetting, SramEccSetting);
 }
 
-void TargetID::setTargetIDFromTargetIDStream(StringRef TargetID) {
-  SmallVector<StringRef, 3> TargetIDSplit;
-  TargetID.split(TargetIDSplit, ':');
+std::optional<TargetID> TargetID::parse(const Triple &TT,
+                                        StringRef ProcAndFeatures) {
+  if (!TT.isAMDGCN())
+    return std::nullopt;
+
+  // A named processor (i.e. not the empty/generic wildcard, which is resolved
+  // from the triple's subarch) must be a recognized GPU.
+  StringRef CPUName = ProcAndFeatures.split(':').first;
+  if (!CPUName.empty() && CPUName != "generic" &&
+      parseArchAMDGCN(CPUName) == GK_NONE)
+    return std::nullopt;
 
-  for (const auto &FeatureString : TargetIDSplit) {
-    if (FeatureString.starts_with("xnack"))
-      XnackSetting = getTargetIDSettingFromFeatureString(FeatureString);
-    if (FeatureString.starts_with("sramecc"))
-      SramEccSetting = getTargetIDSettingFromFeatureString(FeatureString);
-  }
+  // Parse the processor and its feature modifiers, then construct directly from
+  // the resulting fields.
+  GPUKind Arch = getGPUKindFromTargetID(TT, ProcAndFeatures);
+  TargetIDSetting XnackSetting, SramEccSetting;
+  if (!computeTargetIDFeatures(Arch, ProcAndFeatures, XnackSetting,
+                               SramEccSetting))
+    return std::nullopt;
+
+  return TargetID(Arch, TT, XnackSetting, SramEccSetting);
 }
 
 std::optional<TargetID>
 TargetID::parseTargetIDString(StringRef TargetIDDirective) {
-  // Split on '-' to get arch-vendor-os-environment-processor:features
-  // There is a single dash separator after the 4-component triple
+  // Split on '-' to get arch-vendor-os-environment-processor:features. There is
+  // a single dash separator after the 4-component triple, so the
+  // processor+features field must be present (even if empty).
   SmallVector<StringRef, 5> Parts;
   TargetIDDirective.split(Parts, '-', /*MaxSplit=*/4);
-  if (Parts.size() < 4)
+  if (Parts.size() < 5)
     return std::nullopt;
 
-  Triple TT(Parts[0], Parts[1], Parts[2], Parts[3]);
-  if (!TT.isAMDGCN())
-    return std::nullopt;
+  return parse(Triple(Parts[0], Parts[1], Parts[2], Parts[3]), Parts[4]);
+}
 
-  // The processor+features field must be present, even if empty (the ISA can
-  // be encoded in the triple's subarch, e.g.
-  // "amdgpu12.50-amd-amdhsa-unknown-").
-  return TargetID(TT, Parts[4]);
+// Append the explicit (On/Off) sramecc/xnack feature modifiers in canonical
+// order, e.g. ":sramecc-:xnack+".
+static void printFeatureModifiers(raw_ostream &OS, TargetIDSetting SramEcc,
+                                  TargetIDSetting Xnack) {
+  if (SramEcc == TargetIDSetting::Off)
+    OS << ":sramecc-";
+  else if (SramEcc == TargetIDSetting::On)
+    OS << ":sramecc+";
+
+  if (Xnack == TargetIDSetting::Off)
+    OS << ":xnack-";
+  else if (Xnack == TargetIDSetting::On)
+    OS << ":xnack+";
 }
 
 void TargetID::print(raw_ostream &StreamRep) const {
   StreamRep << TargetTripleString << '-' << getArchNameAMDGCN(Arch);
 
-  if (IsAMDHSA) {
-    // sramecc.
-    if (getSramEccSetting() == TargetIDSetting::Off)
-      StreamRep << ":sramecc-";
-    else if (getSramEccSetting() == TargetIDSetting::On)
-      StreamRep << ":sramecc+";
-
-    // xnack.
-    if (getXnackSetting() == TargetIDSetting::Off)
-      StreamRep << ":xnack-";
-    else if (getXnackSetting() == TargetIDSetting::On)
-      StreamRep << ":xnack+";
-  }
+  if (IsAMDHSA)
+    printFeatureModifiers(StreamRep, getSramEccSetting(), getXnackSetting());
 }
 
 std::string TargetID::toString() const {
@@ -1040,6 +1114,14 @@ std::string TargetID::toString() const {
   return Str;
 }
 
+std::string TargetID::getCanonicalFeatureString() const {
+  std::string Str;
+  raw_string_ostream OS(Str);
+  OS << getArchNameAMDGCN(Arch);
+  printFeatureModifiers(OS, getSramEccSetting(), getXnackSetting());
+  return Str;
+}
+
 bool TargetID::operator==(const TargetID &Other) const {
   return Arch == Other.Arch && XnackSetting == Other.XnackSetting &&
          SramEccSetting == Other.SramEccSetting && IsAMDHSA == Other.IsAMDHSA &&
diff --git a/llvm/unittests/TargetParser/TargetParserTest.cpp b/llvm/unittests/TargetParser/TargetParserTest.cpp
index 12ca39d2af4bd..bd67cd6264ac2 100644
--- a/llvm/unittests/TargetParser/TargetParserTest.cpp
+++ b/llvm/unittests/TargetParser/TargetParserTest.cpp
@@ -2828,6 +2828,155 @@ TEST(TargetParserTest, testAMDGPUgetIsaVersionFromSubArch) {
             (AMDGPU::IsaVersion{0, 0, 0}));
 }
 
+TEST(TargetParserTest, testAMDGPUParseTargetIDString) {
+  using AMDGPU::TargetID;
+  using AMDGPU::TargetIDSetting;
+
+  // A well-formed target id parses, canonicalizing the processor and features.
+  {
+    std::optional<TargetID> TID =
+        TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx90a");
+    ASSERT_TRUE(TID.has_value());
+    EXPECT_EQ(TID->getGPUKind(), AMDGPU::GK_GFX90A);
+    EXPECT_EQ(TID->getXnackSetting(), TargetIDSetting::Any);
+    EXPECT_EQ(TID->getSramEccSetting(), TargetIDSetting::Any);
+  }
+
+  // Explicit feature modifiers are applied.
+  {
+    std::optional<TargetID> TID = TargetID::parseTargetIDString(
+        "amdgcn-amd-amdhsa-unknown-gfx90a:xnack+:sramecc-");
+    ASSERT_TRUE(TID.has_value());
+    EXPECT_EQ(TID->getXnackSetting(), TargetIDSetting::On);
+    EXPECT_EQ(TID->getSramEccSetting(), TargetIDSetting::Off);
+  }
+
+  // The processor+features field may be empty; the ISA is taken from the
+  // triple subarch.
+  EXPECT_TRUE(TargetID::parseTargetIDString("amdgpu9.0a-amd-amdhsa-unknown-")
+                  .has_value());
+
+  // Structurally malformed strings (missing the processor+features field or a
+  // non-AMDGCN triple) are rejected.
+  EXPECT_FALSE(TargetID::parseTargetIDString("not-a-valid-target-id"));
+  EXPECT_FALSE(
+      TargetID::parseTargetIDString("x86_64-unknown-linux-gnu-gfx90a"));
+
+  // An unrecognized processor is rejected.
+  EXPECT_FALSE(
+      TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfxbogus"));
+
+  // A feature the processor does not support is rejected: gfx600 has neither
+  // xnack nor sramecc.
+  EXPECT_FALSE(
+      TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx600:xnack+"));
+  EXPECT_FALSE(TargetID::parseTargetIDString(
+      "amdgcn-amd-amdhsa-unknown-gfx900:sramecc+"));
+
+  // xnack is only a valid modifier when the processor supports on/off modes.
+  // gfx1250 has xnack permanently enabled (FEATURE_XNACK without
+  // FEATURE_XNACK_ON_OFF_MODES), so an xnack modifier is rejected.
+  EXPECT_FALSE(TargetID::parseTargetIDString(
+      "amdgcn-amd-amdhsa-unknown-gfx1250:xnack+"));
+  EXPECT_FALSE(TargetID::parseTargetIDString(
+      "amdgcn-amd-amdhsa-unknown-gfx1250:xnack-"));
+
+  // A feature modifier with no "+"/"-" sign is rejected.
+  EXPECT_FALSE(
+      TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx908:xnack"));
+
+  // An unknown feature name is rejected, even alongside a valid one.
+  EXPECT_FALSE(TargetID::parseTargetIDString(
+      "amdgcn-amd-amdhsa-unknown-gfx908:unknown+"));
+  EXPECT_FALSE(TargetID::parseTargetIDString(
+      "amdgcn-amd-amdhsa-unknown-gfx908:sramecc+:unknown+"));
+
+  // A repeated feature is rejected.
+  EXPECT_FALSE(TargetID::parseTargetIDString(
+      "amdgcn-amd-amdhsa-unknown-gfx908:xnack+:xnack+"));
+
+  // Empty processor and/or feature components must be handled without crashing.
+  EXPECT_FALSE(TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-:"));
+  EXPECT_FALSE(TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-::"));
+  EXPECT_FALSE(
+      TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx900:"));
+  EXPECT_FALSE(
+      TargetID::parseTargetIDString("amdgcn-amd-amdhsa-unknown-gfx900::"));
+  EXPECT_FALSE(TargetID::parseTargetIDString(
+      "amdgcn-amd-amdhsa-unknown-gfx900:xnack+:"));
+
+  // Constructing directly from a triple and processor+features string must also
+  // be crash-safe on empty components.
+  Triple AMDHSA("amdgcn-amd-amdhsa");
+  (void)TargetID(AMDHSA, ":");
+  (void)TargetID(AMDHSA, "::");
+  (void)TargetID(AMDHSA, "gfx900:");
+
+  // TargetID::parse validates a separate triple + processor/features string.
+  {
+    std::optional<TargetID> TID = TargetID::parse(AMDHSA, "gfx90a:xnack+");
+    ASSERT_TRUE(TID.has_value());
+    EXPECT_EQ(TID->getGPUKind(), AMDGPU::GK_GFX90A);
+    EXPECT_EQ(TID->getXnackSetting(), AMDGPU::TargetIDSetting::On);
+  }
+
+  EXPECT_EQ(TargetID::parse(AMDHSA, "gfx908:xnack+:sramecc-")
+                ->getCanonicalFeatureString(),
+            "gfx908:sramecc-:xnack+");
+  EXPECT_EQ(TargetID::parse(AMDHSA, "gfx908")->getCanonicalFeatureString(),
+            "gfx908");
+  EXPECT_EQ(TargetID::parse(Triple("amdgcn-amd-amdpal"), "gfx908:xnack-")
+                ->getCanonicalFeatureString(),
+            "gfx908:xnack-");
+  EXPECT_TRUE(TargetID::parse(AMDHSA, "").has_value());
+  EXPECT_FALSE(TargetID::parse(AMDHSA, "gfxbogus").has_value());
+  EXPECT_FALSE(TargetID::parse(AMDHSA, "gfx600:xnack+").has_value());
+  EXPECT_FALSE(TargetID::parse(AMDHSA, "gfx900:").has_value());
+  // A non-AMDGCN triple has no target-id features.
+  EXPECT_FALSE(
+      TargetID::parse(Triple("r600-unknown-unknown"), "cypress").has_value());
+}
+
+TEST(TargetParserTest, testAMDGPUSplitTargetID) {
+  using AMDGPU::TargetIDSetting;
+  auto Split = [](StringRef ID) {
+    TargetIDSetting Xnack, SramEcc;
+    StringRef Proc = AMDGPU::splitTargetID(ID, Xnack, SramEcc);
+    return std::make_tuple(Proc, Xnack, SramEcc);
+  };
+
+  // Bare processor, no modifiers.
+  EXPECT_EQ(Split("gfx90a"),
+            std::make_tuple(StringRef("gfx90a"), TargetIDSetting::Any,
+                            TargetIDSetting::Any));
+
+  // Explicit modifiers, either order.
+  EXPECT_EQ(Split("gfx90a:xnack+:sramecc-"),
+            std::make_tuple(StringRef("gfx90a"), TargetIDSetting::On,
+                            TargetIDSetting::Off));
+  EXPECT_EQ(Split("gfx90a:sramecc+:xnack-"),
+            std::make_tuple(StringRef("gfx90a"), TargetIDSetting::Off,
+                            TargetIDSetting::On));
+
+  // The processor may be a multi-component prefix (e.g. a full bundle-entry
+  // id); splitTargetID is purely syntactic and does not validate it.
+  EXPECT_EQ(Split("hip-amdgcn-amd-amdhsa-gfx906:xnack+"),
+            std::make_tuple(StringRef("hip-amdgcn-amd-amdhsa-gfx906"),
+                            TargetIDSetting::On, TargetIDSetting::Any));
+
+  EXPECT_EQ(Split(""), std::make_tuple(StringRef(""), TargetIDSetting::Any,
+                                       TargetIDSetting::Any));
+  EXPECT_EQ(Split(":"), std::make_tuple(StringRef(""), TargetIDSetting::Any,
+                                        TargetIDSetting::Any));
+  EXPECT_EQ(Split("::"), std::make_tuple(StringRef(""), TargetIDSetting::Any,
+                                         TargetIDSetting::Any));
+  // An absent modifier defaults to "Any", but a present but malformed modifier
+  // (no +/- sign, or unknown name) yields "Unsupported".
+  EXPECT_EQ(Split("gfx90a:xnack"),
+            std::make_tuple(StringRef("gfx90a"), TargetIDSetting::Unsupported,
+                            TargetIDSetting::Any));
+}
+
 TEST(TargetParserTest, testAMDGPUTargetIDProvidesFor) {
   using AMDGPU::TargetID;
   Triple AMDHSA("amdgcn-amd-amdhsa");

>From 9df8fdf985be432b6898e053b13da419b861596b Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Wed, 15 Jul 2026 10:10:23 +0200
Subject: [PATCH 2/4] comment

---
 llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
index 3e9997a4ca088..26bcc9798b01e 100644
--- a/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
+++ b/llvm/lib/Target/AMDGPU/Utils/AMDGPUBaseInfo.cpp
@@ -1107,7 +1107,8 @@ TargetID createAMDGPUTargetID(const MCSubtargetInfo &STI,
   }
 
   // Only allow changing xnack setting if the target supports on/off modes.
-  // Targets without on/off mode support keep their initial setting (Any).
+  // Targets without on/off mode support keep their initial setting
+  // (Unsupported).
 
   bool XnackSupported = STI.getFeatureBits().test(FeatureXNACKOnOffModes);
   bool SramEccSupported = TargetID.isSramEccSupported();

>From fb68e4f4a9f541f0949bc5972205ee8f13e8e2d6 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Wed, 15 Jul 2026 14:48:35 +0200
Subject: [PATCH 3/4] AMDGPU: Remove now-unused splitTargetID

splitTargetID was a syntactic-only target-ID splitter used solely by
clang's getConflictTargetIDCombination, which now parses through the
canonical llvm::AMDGPU::TargetID::parse instead. With that last user
gone, drop the function, its declaration, and its unit test.
---
 .../llvm/TargetParser/AMDGPUTargetParser.h    |  6 ---
 llvm/lib/TargetParser/AMDGPUTargetParser.cpp  | 20 ----------
 .../TargetParser/TargetParserTest.cpp         | 40 -------------------
 3 files changed, 66 deletions(-)

diff --git a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
index aa77ee9677682..433a3f0a9ea41 100644
--- a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
+++ b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
@@ -155,12 +155,6 @@ fillAMDGPUFeatureMap(StringRef GPU, const Triple &T, StringMap<bool> &Features);
 
 enum class TargetIDSetting { Unsupported, Any, Off, On };
 
-/// Split a target-id string \p TargetID into its processor and the
-/// xnack/sramecc feature modifiers present. This is purely syntactic.
-LLVM_ABI StringRef splitTargetID(StringRef TargetID,
-                                 TargetIDSetting &XnackSetting,
-                                 TargetIDSetting &SramEccSetting);
-
 class LLVM_ABI TargetID {
 private:
   GPUKind Arch;
diff --git a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
index c93a0896e78de..53bda0bb20a74 100644
--- a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
+++ b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
@@ -964,26 +964,6 @@ static TargetIDSetting getTargetIDSettingFromFeatureString(StringRef Sign) {
   return TargetIDSetting::Unsupported;
 }
 
-StringRef AMDGPU::splitTargetID(StringRef TargetID,
-                                TargetIDSetting &XnackSetting,
-                                TargetIDSetting &SramEccSetting) {
-  XnackSetting = TargetIDSetting::Any;
-  SramEccSetting = TargetIDSetting::Any;
-
-  SmallVector<StringRef, 3> Split;
-  TargetID.split(Split, ':');
-
-  // The substring before the first ':' is the processor
-  for (StringRef FeatureString : ArrayRef<StringRef>(Split).drop_front()) {
-    if (FeatureString.consume_front("xnack"))
-      XnackSetting = getTargetIDSettingFromFeatureString(FeatureString);
-    else if (FeatureString.consume_front("sramecc"))
-      SramEccSetting = getTargetIDSettingFromFeatureString(FeatureString);
-  }
-
-  return Split.front();
-}
-
 // Derive the architecture from the processor name in \p TargetIDStr. "generic"
 // and the empty processor name act as a wildcard.
 static GPUKind getGPUKindFromTargetID(const Triple &TT, StringRef TargetIDStr) {
diff --git a/llvm/unittests/TargetParser/TargetParserTest.cpp b/llvm/unittests/TargetParser/TargetParserTest.cpp
index bd67cd6264ac2..2f5436a1d7f9e 100644
--- a/llvm/unittests/TargetParser/TargetParserTest.cpp
+++ b/llvm/unittests/TargetParser/TargetParserTest.cpp
@@ -2937,46 +2937,6 @@ TEST(TargetParserTest, testAMDGPUParseTargetIDString) {
       TargetID::parse(Triple("r600-unknown-unknown"), "cypress").has_value());
 }
 
-TEST(TargetParserTest, testAMDGPUSplitTargetID) {
-  using AMDGPU::TargetIDSetting;
-  auto Split = [](StringRef ID) {
-    TargetIDSetting Xnack, SramEcc;
-    StringRef Proc = AMDGPU::splitTargetID(ID, Xnack, SramEcc);
-    return std::make_tuple(Proc, Xnack, SramEcc);
-  };
-
-  // Bare processor, no modifiers.
-  EXPECT_EQ(Split("gfx90a"),
-            std::make_tuple(StringRef("gfx90a"), TargetIDSetting::Any,
-                            TargetIDSetting::Any));
-
-  // Explicit modifiers, either order.
-  EXPECT_EQ(Split("gfx90a:xnack+:sramecc-"),
-            std::make_tuple(StringRef("gfx90a"), TargetIDSetting::On,
-                            TargetIDSetting::Off));
-  EXPECT_EQ(Split("gfx90a:sramecc+:xnack-"),
-            std::make_tuple(StringRef("gfx90a"), TargetIDSetting::Off,
-                            TargetIDSetting::On));
-
-  // The processor may be a multi-component prefix (e.g. a full bundle-entry
-  // id); splitTargetID is purely syntactic and does not validate it.
-  EXPECT_EQ(Split("hip-amdgcn-amd-amdhsa-gfx906:xnack+"),
-            std::make_tuple(StringRef("hip-amdgcn-amd-amdhsa-gfx906"),
-                            TargetIDSetting::On, TargetIDSetting::Any));
-
-  EXPECT_EQ(Split(""), std::make_tuple(StringRef(""), TargetIDSetting::Any,
-                                       TargetIDSetting::Any));
-  EXPECT_EQ(Split(":"), std::make_tuple(StringRef(""), TargetIDSetting::Any,
-                                        TargetIDSetting::Any));
-  EXPECT_EQ(Split("::"), std::make_tuple(StringRef(""), TargetIDSetting::Any,
-                                         TargetIDSetting::Any));
-  // An absent modifier defaults to "Any", but a present but malformed modifier
-  // (no +/- sign, or unknown name) yields "Unsupported".
-  EXPECT_EQ(Split("gfx90a:xnack"),
-            std::make_tuple(StringRef("gfx90a"), TargetIDSetting::Unsupported,
-                            TargetIDSetting::Any));
-}
-
 TEST(TargetParserTest, testAMDGPUTargetIDProvidesFor) {
   using AMDGPU::TargetID;
   Triple AMDHSA("amdgcn-amd-amdhsa");

>From 4005d75c78a821b64cb0264ffac21aeef0e3a95e Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Wed, 15 Jul 2026 18:48:49 +0200
Subject: [PATCH 4/4] AMDGPU: Add TargetID::printCanonicalTargetIDString

Factor the canonical feature-string formatting into a raw_ostream print
method so callers can stream directly without a temporary std::string.
getCanonicalFeatureString now delegates to it.
---
 llvm/include/llvm/TargetParser/AMDGPUTargetParser.h | 5 +++++
 llvm/lib/TargetParser/AMDGPUTargetParser.cpp        | 8 ++++++--
 2 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
index 433a3f0a9ea41..9a7ee2e03ae4c 100644
--- a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
+++ b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
@@ -260,6 +260,11 @@ class LLVM_ABI TargetID {
 
   std::string toString() const;
 
+  /// Print the canonical processor name followed by any explicit xnack and
+  /// sramecc feature modifiers (e.g. "gfx908:sramecc-:xnack+"), without the
+  /// triple prefix.
+  void printCanonicalTargetIDString(raw_ostream &OS) const;
+
   /// \returns the canonical processor name followed by any explicit xnack and
   /// sramecc feature modifiers order (e.g.  "gfx908:sramecc-:xnack+"), without
   /// the triple prefix.
diff --git a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
index 53bda0bb20a74..630a466fa3edc 100644
--- a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
+++ b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
@@ -1094,11 +1094,15 @@ std::string TargetID::toString() const {
   return Str;
 }
 
+void TargetID::printCanonicalTargetIDString(raw_ostream &OS) const {
+  OS << getArchNameAMDGCN(Arch);
+  printFeatureModifiers(OS, getSramEccSetting(), getXnackSetting());
+}
+
 std::string TargetID::getCanonicalFeatureString() const {
   std::string Str;
   raw_string_ostream OS(Str);
-  OS << getArchNameAMDGCN(Arch);
-  printFeatureModifiers(OS, getSramEccSetting(), getXnackSetting());
+  printCanonicalTargetIDString(OS);
   return Str;
 }
 



More information about the llvm-commits mailing list