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

Matt Arsenault via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 13 13:56:36 PDT 2026


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

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)

>From f3e4417812050dfc20a59fd703898bf881aa1e94 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] 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  | 173 +++++++++++++-----
 .../TargetParser/TargetParserTest.cpp         | 149 +++++++++++++++
 7 files changed, 312 insertions(+), 56 deletions(-)

diff --git a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
index 0fba0d4956d02..2c5b05eb1a461 100644
--- a/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
+++ b/llvm/include/llvm/TargetParser/AMDGPUTargetParser.h
@@ -154,6 +154,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;
@@ -226,8 +232,6 @@ class LLVM_ABI TargetID {
     SramEccSetting = NewSramEccSetting;
   }
 
-  void setTargetIDFromTargetIDStream(StringRef TargetID);
-
   GPUKind getGPUKind() const { return Arch; }
 
   StringRef getTargetTripleString() const { return TargetTripleString; }
@@ -235,6 +239,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);
 
@@ -246,6 +259,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 6747f60cb9c9d..1f75876560970 100644
--- a/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp
+++ b/llvm/lib/Target/AMDGPU/AsmParser/AMDGPUAsmParser.cpp
@@ -6345,9 +6345,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 18d341c3b229b..2dbb72cea54b2 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 71d95a23e30d3..7246563d1de3b 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 c5f4247117d1b..9af5621135a7c 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"
@@ -906,14 +907,34 @@ 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::Unsupported;
+  SramEccSetting = TargetIDSetting::Unsupported;
+
+  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"
@@ -925,66 +946,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 {
@@ -994,6 +1067,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 1b6216f9ae68f..acde2ca2bf9be 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, testAMDGPUTargetIDCompat) {
   using AMDGPU::TargetID;
   Triple AMDHSA("amdgcn-amd-amdhsa");



More information about the llvm-commits mailing list