[llvm] [AMDGPU] Expose addressable LDS size features to the frontend (PR #220394)
Chinmay Deshpande via llvm-commits
llvm-commits at lists.llvm.org
Wed Sep 9 14:00:12 PDT 2026
https://github.com/chinmaydd updated https://github.com/llvm/llvm-project/pull/220394
>From d4881a45f50553fd5df14b21a21f693c7ea6f2c8 Mon Sep 17 00:00:00 2001
From: Chinmay Deshpande <chdeshpa at amd.com>
Date: Thu, 3 Sep 2026 01:20:38 -0400
Subject: [PATCH] [AMDGPU] Expose addressable LDS size features to TargetParser
Expose addressable LDS size through the per-GPU feature bitset without
serializing scalar settings into target-feature strings. Validate that
generic targets retain a conservative addressable size.
Change-Id: I3bc5265e190497817867fcaae16847d2eac81cf2
---
llvm/lib/Target/AMDGPU/AMDGPU.td | 5 +
llvm/lib/TargetParser/AMDGPUTargetParser.cpp | 7 +-
llvm/test/TableGen/AMDGPUTargetDefErrors.td | 92 +++++++++++++++++++
.../TargetParser/TargetParserTest.cpp | 54 +++++++++++
.../TableGen/Basic/AMDGPUTargetDefEmitter.cpp | 85 ++++++++++-------
5 files changed, 207 insertions(+), 36 deletions(-)
diff --git a/llvm/lib/Target/AMDGPU/AMDGPU.td b/llvm/lib/Target/AMDGPU/AMDGPU.td
index 3502e65a3f6e6..37034d3240638 100644
--- a/llvm/lib/Target/AMDGPU/AMDGPU.td
+++ b/llvm/lib/Target/AMDGPU/AMDGPU.td
@@ -3237,6 +3237,11 @@ def AMDGPUFrontendVisibleFeatures {
FeatureSGPRInitBug, FeatureApertureRegs, FeatureGetDoorbellID,
FeatureAGPRAlloc, Feature1536VGPRs, Feature1024AddressableVGPRs,
FeatureHalfAddressablePhysicalLocalMemory,
+ FeatureAddressableLocalMemorySize32768,
+ FeatureAddressableLocalMemorySize65536,
+ FeatureAddressableLocalMemorySize163840,
+ FeatureAddressableLocalMemorySize196608,
+ FeatureAddressableLocalMemorySize327680,
];
}
diff --git a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
index 40775d7c6e132..e5678b9ebdf39 100644
--- a/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
+++ b/llvm/lib/TargetParser/AMDGPUTargetParser.cpp
@@ -530,7 +530,12 @@ static const AMDGPUFeatureBitset FrontendOnlyFeatures = {
FEAT_AGPR_ALLOC,
FEAT_1536_PHYSICAL_VGPRS,
FEAT_HALF_ADDRESSABLE_PHYSICAL_LOCAL_MEMORY,
- FEAT_1024_ADDRESSABLE_VGPRS};
+ FEAT_1024_ADDRESSABLE_VGPRS,
+ FEAT_ADDRESSABLELOCALMEMORYSIZE32768,
+ FEAT_ADDRESSABLELOCALMEMORYSIZE65536,
+ FEAT_ADDRESSABLELOCALMEMORYSIZE163840,
+ FEAT_ADDRESSABLELOCALMEMORYSIZE196608,
+ FEAT_ADDRESSABLELOCALMEMORYSIZE327680};
// Add a GPU's features (minus the frontend-only ones) to \p Features. With \p
// Overwrite false, existing entries are kept so user -mattr overrides win.
diff --git a/llvm/test/TableGen/AMDGPUTargetDefErrors.td b/llvm/test/TableGen/AMDGPUTargetDefErrors.td
index ce540b5bd5a32..4dfbc43a19106 100644
--- a/llvm/test/TableGen/AMDGPUTargetDefErrors.td
+++ b/llvm/test/TableGen/AMDGPUTargetDefErrors.td
@@ -19,6 +19,12 @@
// RUN: | FileCheck %t/bad-stepping.td -DFILE=%t/bad-stepping.td --implicit-check-not="error:"
// RUN: not llvm-tblgen -gen-amdgpu-target-def -I %p/../../include %t/generic-feature-superset.td 2>&1 \
// RUN: | FileCheck %t/generic-feature-superset.td -DFILE=%t/generic-feature-superset.td --implicit-check-not="error:"
+// RUN: not llvm-tblgen -gen-amdgpu-target-def -I %p/../../include %t/generic-default-true-feature.td 2>&1 \
+// RUN: | FileCheck %t/generic-default-true-feature.td -DFILE=%t/generic-default-true-feature.td --implicit-check-not="error:"
+// RUN: llvm-tblgen -gen-amdgpu-target-def -I %p/../../include %t/generic-addressable-size-valid.td 2>&1 \
+// RUN: | FileCheck %t/generic-addressable-size-valid.td --check-prefix=VALID
+// RUN: not llvm-tblgen -gen-amdgpu-target-def -I %p/../../include %t/generic-addressable-size-invalid.td 2>&1 \
+// RUN: | FileCheck %t/generic-addressable-size-invalid.td -DFILE=%t/generic-addressable-size-invalid.td --implicit-check-not="error:"
// Verify the validation performed by the -gen-amdgpu-target-def backend.
@@ -185,3 +191,89 @@ def : ProcessorModel<"gfx9-generic", NoSchedModel, [FeatureFoo]>,
AMDGPUGPUInfo<[9, 0, 0]> {
let CoveredGPUs = [GFX900];
}
+
+//--- generic-default-true-feature.td
+include "llvm/Target/Target.td"
+def MyTarget : Target;
+class AMDGPUArchFeature<string spelling> { string Spelling = spelling; }
+class AMDGPUGPUInfo<list<int> isa = []> {
+ list<AMDGPUArchFeature> ArchFeatures = [];
+ list<int> IsaVersion = isa;
+ list<Processor> CoveredGPUs = [];
+ bit IsPseudoTarget = false;
+}
+
+// A feature clearing a field that defaults to true is still a boolean one.
+def FeatureNoFoo : SubtargetFeature<"no-foo", "HasFoo", "false", "No foo">;
+
+def AMDGPUFrontendVisibleFeatures {
+ list<SubtargetFeature> Features = [FeatureNoFoo];
+}
+
+def GFX900 : ProcessorModel<"gfx900", NoSchedModel, []>, AMDGPUGPUInfo<[9, 0, 0]>;
+
+// CHECK: [[FILE]]:[[#@LINE+1]]:1: error: generic target 'gfx9-generic' exposes feature 'no-foo' not supported by covered GPU 'gfx900'
+def : ProcessorModel<"gfx9-generic", NoSchedModel, [FeatureNoFoo]>,
+ AMDGPUGPUInfo<[9, 0, 0]> {
+ let CoveredGPUs = [GFX900];
+}
+
+//--- generic-addressable-size-valid.td
+include "llvm/Target/Target.td"
+def MyTarget : Target;
+class AMDGPUArchFeature<string spelling> { string Spelling = spelling; }
+class AMDGPUGPUInfo<list<int> isa = []> {
+ list<AMDGPUArchFeature> ArchFeatures = [];
+ list<int> IsaVersion = isa;
+ list<Processor> CoveredGPUs = [];
+ bit IsPseudoTarget = false;
+}
+
+def FeatureSizeSmall : SubtargetFeature<
+ "size-small", "AddressableLocalMemorySize", "1024", "Small">;
+def FeatureSizeBig : SubtargetFeature<
+ "size-big", "AddressableLocalMemorySize", "2048", "Big">;
+
+def AMDGPUFrontendVisibleFeatures {
+ list<SubtargetFeature> Features = [FeatureSizeSmall, FeatureSizeBig];
+}
+
+def GFX900 : ProcessorModel<"gfx900", NoSchedModel, [FeatureSizeBig]>,
+ AMDGPUGPUInfo<[9, 0, 0]>;
+
+// A generic carries the worst case of a numeric field, so it keeps the smaller
+// size while covering a GPU with the larger one. That is not an error.
+// VALID: GK_GFX9_GENERIC
+def : ProcessorModel<"gfx9-generic", NoSchedModel, [FeatureSizeSmall]>,
+ AMDGPUGPUInfo<[9, 0, 0]> {
+ let CoveredGPUs = [GFX900];
+}
+
+//--- generic-addressable-size-invalid.td
+include "llvm/Target/Target.td"
+def MyTarget : Target;
+class AMDGPUArchFeature<string spelling> { string Spelling = spelling; }
+class AMDGPUGPUInfo<list<int> isa = []> {
+ list<AMDGPUArchFeature> ArchFeatures = [];
+ list<int> IsaVersion = isa;
+ list<Processor> CoveredGPUs = [];
+ bit IsPseudoTarget = false;
+}
+
+def FeatureSizeSmall : SubtargetFeature<
+ "size-small", "AddressableLocalMemorySize", "1024", "Small">;
+def FeatureSizeBig : SubtargetFeature<
+ "size-big", "AddressableLocalMemorySize", "2048", "Big">;
+
+def AMDGPUFrontendVisibleFeatures {
+ list<SubtargetFeature> Features = [FeatureSizeSmall, FeatureSizeBig];
+}
+
+def GFX900 : ProcessorModel<"gfx900", NoSchedModel, [FeatureSizeSmall]>,
+ AMDGPUGPUInfo<[9, 0, 0]>;
+
+// CHECK: [[FILE]]:[[#@LINE+1]]:1: error: generic target 'gfx9-generic' exposes feature 'size-big' exceeding the 'AddressableLocalMemorySize' of covered GPU 'gfx900'
+def : ProcessorModel<"gfx9-generic", NoSchedModel, [FeatureSizeBig]>,
+ AMDGPUGPUInfo<[9, 0, 0]> {
+ let CoveredGPUs = [GFX900];
+}
diff --git a/llvm/unittests/TargetParser/TargetParserTest.cpp b/llvm/unittests/TargetParser/TargetParserTest.cpp
index 55e99754a44a9..705d04191bf81 100644
--- a/llvm/unittests/TargetParser/TargetParserTest.cpp
+++ b/llvm/unittests/TargetParser/TargetParserTest.cpp
@@ -2824,6 +2824,14 @@ TEST(TargetParserTest, testAMDGPUfillAMDGPUFeatureMap) {
// A capability feature is queried through the bitset only.
EXPECT_FALSE(HasFeature("gfx1030", "half-addressable-physical-local-memory"));
+
+ // The addressable LDS sizes stay out of the target-feature string, since a
+ // string merging two of them would silently take the larger.
+ EXPECT_FALSE(HasFeature("gfx600", "addressablelocalmemorysize32768"));
+ EXPECT_FALSE(HasFeature("gfx900", "addressablelocalmemorysize65536"));
+ EXPECT_FALSE(HasFeature("gfx950", "addressablelocalmemorysize163840"));
+ EXPECT_FALSE(HasFeature("gfx1310", "addressablelocalmemorysize196608"));
+ EXPECT_FALSE(HasFeature("gfx1250", "addressablelocalmemorysize327680"));
}
TEST(TargetParserTest, testAMDGPUgetFeatureBitset) {
@@ -2874,6 +2882,52 @@ TEST(TargetParserTest, testAMDGPUHalfAddressableLDSFeature) {
EXPECT_FALSE(Has(AMDGPU::GK_GFX1310));
}
+TEST(TargetParserTest, testAMDGPUAddressableLocalMemorySizeFeatures) {
+ auto Has = [](AMDGPU::GPUKind AK, AMDGPU::AMDGPUFeature Feature) {
+ return AMDGPU::getFeatureBitset(AK).test(Feature);
+ };
+ auto Count = [&Has](AMDGPU::GPUKind AK) {
+ return Has(AK, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE32768) +
+ Has(AK, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE65536) +
+ Has(AK, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE163840) +
+ Has(AK, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE196608) +
+ Has(AK, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE327680);
+ };
+
+ // Exactly one addressable LDS size is set per GPU.
+ EXPECT_EQ(Count(AMDGPU::GK_GFX600), 1);
+ EXPECT_EQ(Count(AMDGPU::GK_GFX900), 1);
+ EXPECT_EQ(Count(AMDGPU::GK_GFX950), 1);
+ EXPECT_EQ(Count(AMDGPU::GK_GFX1310), 1);
+ EXPECT_EQ(Count(AMDGPU::GK_GFX1250), 1);
+ EXPECT_EQ(Count(AMDGPU::GK_GFX9_4_GENERIC), 1);
+
+ // The legacy pseudo-targets do not represent hardware, so they have no
+ // addressable LDS size feature.
+ EXPECT_EQ(Count(AMDGPU::GK_GENERIC), 0);
+ EXPECT_EQ(Count(AMDGPU::GK_GENERIC_HSA), 0);
+
+ EXPECT_TRUE(
+ Has(AMDGPU::GK_GFX600, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE32768));
+ EXPECT_TRUE(
+ Has(AMDGPU::GK_GFX900, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE65536));
+ EXPECT_FALSE(
+ Has(AMDGPU::GK_GFX900, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE32768));
+ EXPECT_TRUE(
+ Has(AMDGPU::GK_GFX950, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE163840));
+ EXPECT_TRUE(
+ Has(AMDGPU::GK_GFX1310, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE196608));
+ EXPECT_TRUE(
+ Has(AMDGPU::GK_GFX1250, AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE327680));
+
+ // A generic target takes the smallest size of the GPUs it covers, so
+ // gfx9-4-generic keeps 64k despite covering gfx950's 160k.
+ EXPECT_TRUE(Has(AMDGPU::GK_GFX9_4_GENERIC,
+ AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE65536));
+ EXPECT_FALSE(Has(AMDGPU::GK_GFX9_4_GENERIC,
+ AMDGPU::FEAT_ADDRESSABLELOCALMEMORYSIZE163840));
+}
+
TEST(TargetParserTest, testAMDGPUfillValidArchListAMDGCN) {
SmallVector<StringRef, 0> All;
AMDGPU::fillValidArchListAMDGCN(All, Triple::NoSubArch);
diff --git a/llvm/utils/TableGen/Basic/AMDGPUTargetDefEmitter.cpp b/llvm/utils/TableGen/Basic/AMDGPUTargetDefEmitter.cpp
index 96f82fb42c27a..f96a239fe2b10 100644
--- a/llvm/utils/TableGen/Basic/AMDGPUTargetDefEmitter.cpp
+++ b/llvm/utils/TableGen/Basic/AMDGPUTargetDefEmitter.cpp
@@ -466,6 +466,38 @@ collectVisibleFeatures(const Record *GPU,
return Visible;
}
+// The value of the SubtargetFeature in \p GPU's closure that sets \p FieldName,
+// or \p Default if it has none. Two features setting the same field to
+// different values is an error: SubtargetFeature silently takes the larger.
+static int64_t getFeatureValue(const Record *GPU, StringRef FieldName,
+ int64_t Default) {
+ SetVector<const Record *> Closure;
+ collectFeatureClosure(GPU, Closure);
+
+ const Record *Found = nullptr;
+ int64_t Value = Default;
+ for (const Record *F : Closure) {
+ if (F->getValueAsString("FieldName") != FieldName)
+ continue;
+
+ int64_t V;
+ if (!to_integer(F->getValueAsString("Value"), V)) {
+ PrintFatalError(F->getLoc(), "feature '" + F->getValueAsString("Name") +
+ "' must have an integer value");
+ }
+ if (Found && V != Value) {
+ PrintFatalError(GPU->getLoc(),
+ "GPU '" + GPU->getValueAsString("Name") +
+ "' gets conflicting '" + FieldName +
+ "' values from '" + Found->getValueAsString("Name") +
+ "' and '" + F->getValueAsString("Name") + "'");
+ }
+ Found = F;
+ Value = V;
+ }
+ return Value;
+}
+
// Make sure a "gfxN-generic" processor doesn't expose a frontend-visible
// feature missing from any covered processor.
//
@@ -488,14 +520,29 @@ validateGenericFeatures(const Record *GPU,
SetVector<const Record *> MemberFeatures =
collectVisibleFeatures(Member, FeatureIdx);
for (const Record *F : GenericFeatures) {
- if (!MemberFeatures.contains(F)) {
+ if (MemberFeatures.contains(F))
+ continue;
+
+ StringRef FieldName = F->getValueAsString("FieldName");
+ if (FieldName == "AddressableLocalMemorySize") {
+ int64_t GenericValue = getFeatureValue(GPU, FieldName, 32768);
+ int64_t MemberValue = getFeatureValue(Member, FieldName, 32768);
+ if (GenericValue <= MemberValue)
+ continue;
+
PrintFatalError(GPU->getLoc(),
"generic target '" + GPU->getValueAsString("Name") +
"' exposes feature '" +
- F->getValueAsString("Name") +
- "' not supported by covered GPU '" +
+ F->getValueAsString("Name") + "' exceeding the '" +
+ FieldName + "' of covered GPU '" +
Member->getValueAsString("Name") + "'");
}
+
+ PrintFatalError(GPU->getLoc(),
+ "generic target '" + GPU->getValueAsString("Name") +
+ "' exposes feature '" + F->getValueAsString("Name") +
+ "' not supported by covered GPU '" +
+ Member->getValueAsString("Name") + "'");
}
}
}
@@ -536,38 +583,6 @@ emitFeatureBitset(raw_ostream &OS, const Record *GPU,
OS << "})";
}
-// The value of the SubtargetFeature in \p GPU's closure that sets \p FieldName,
-// or \p Default if it has none. Two features setting the same field to
-// different values is an error: SubtargetFeature silently takes the larger.
-static int64_t getFeatureValue(const Record *GPU, StringRef FieldName,
- int64_t Default) {
- SetVector<const Record *> Closure;
- collectFeatureClosure(GPU, Closure);
-
- const Record *Found = nullptr;
- int64_t Value = Default;
- for (const Record *F : Closure) {
- if (F->getValueAsString("FieldName") != FieldName)
- continue;
-
- int64_t V;
- if (!to_integer(F->getValueAsString("Value"), V)) {
- PrintFatalError(F->getLoc(), "feature '" + F->getValueAsString("Name") +
- "' must have an integer value");
- }
- if (Found && V != Value) {
- PrintFatalError(GPU->getLoc(),
- "GPU '" + GPU->getValueAsString("Name") +
- "' gets conflicting '" + FieldName +
- "' values from '" + Found->getValueAsString("Name") +
- "' and '" + F->getValueAsString("Name") + "'");
- }
- Found = F;
- Value = V;
- }
- return Value;
-}
-
/// Emit a GPUInfo table indexed by (GPUKind - AMDGPUFirstGPUKind). Name and
/// family strings are stored as offsets into the shared \p Names table.
static void
More information about the llvm-commits
mailing list