[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