[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