[llvm] [TableGen] Compress register-class subregister tables (PR #202658)

David Zbarsky via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 9 07:01:50 PDT 2026


https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202658

RegisterInfoEmitter emits dense register-class by subregister-index matrices for getSubClassWithSubReg() and getSubRegisterClass(). AMDGPU has two 889 by 399 matrices, but the matrices contain many repeated rectangular blocks.

Search power-of-two block dimensions of at most 64 entries, intern identical blocks, and emit a block-index matrix when it saves at least 128 bytes. The generated lookup remains constant time. RegisterInfoEmitter verifies every reconstructed entry, and generated static_asserts preserve the selected table sizes.

For AMDGPU, the two raw matrices decrease from 1,418,844 to 156,358 bytes. A stripped AMDGPU-only llc decreases from 88,499,000 to 87,227,576 bytes, saving 1,271,424 bytes (1.44%). SIRegisterInfo.cpp.o decreases from 4,313,968 to 3,051,848 bytes.

Validated all 709,422 AMDGPU entries against the dense representation. gfx900, gfx1100, gfx1250, and R600 assembly output is byte-identical. The focused TableGen test passes. Alternating gfx900 and gfx1100 code-generation benchmarks changed user CPU time by +0.51% and +0.27%, respectively, within run-to-run variance.

Work towards #202616

>From 213b4041494c68db95d31c3d54333d3e371143df Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Tue, 9 Jun 2026 00:29:18 -0400
Subject: [PATCH] [TableGen] Compress register-class subregister tables

RegisterInfoEmitter emits dense register-class by subregister-index matrices for getSubClassWithSubReg() and getSubRegisterClass(). AMDGPU has two 889 by 399 matrices, but the matrices contain many repeated rectangular blocks.

Search power-of-two block dimensions of at most 64 entries, intern identical blocks, and emit a block-index matrix when it saves at least 128 bytes. The generated lookup remains constant time. RegisterInfoEmitter verifies every reconstructed entry, and generated static_asserts preserve the selected table sizes.

For AMDGPU, the two raw matrices decrease from 1,418,844 to 156,358 bytes. A stripped AMDGPU-only llc decreases from 88,499,000 to 87,227,576 bytes, saving 1,271,424 bytes (1.44%). SIRegisterInfo.cpp.o decreases from 4,313,968 to 3,051,848 bytes.

Validated all 709,422 AMDGPU entries against the dense representation. gfx900, gfx1100, gfx1250, and R600 assembly output is byte-identical. The focused TableGen test passes. Alternating gfx900 and gfx1100 code-generation benchmarks changed user CPU time by +0.51% and +0.27%, respectively, within run-to-run variance.
---
 .../register-info-subreg-table-blocks.td      |  56 +++++
 llvm/utils/TableGen/RegisterInfoEmitter.cpp   | 212 +++++++++++++++---
 2 files changed, 240 insertions(+), 28 deletions(-)
 create mode 100644 llvm/test/TableGen/register-info-subreg-table-blocks.td

diff --git a/llvm/test/TableGen/register-info-subreg-table-blocks.td b/llvm/test/TableGen/register-info-subreg-table-blocks.td
new file mode 100644
index 0000000000000..9c076e13fc3bf
--- /dev/null
+++ b/llvm/test/TableGen/register-info-subreg-table-blocks.td
@@ -0,0 +1,56 @@
+// RUN: llvm-tblgen -gen-register-info -I %p/../../include %s -o %t.inc
+// RUN: FileCheck %s < %tTargetDesc.inc
+
+include "llvm/Target/Target.td"
+
+def TestTarget : Target;
+
+def sub0 : SubRegIndex<32, 0>;
+def sub1 : SubRegIndex<32, 32>;
+def sub2 : SubRegIndex<32, 64>;
+def sub3 : SubRegIndex<32, 96>;
+def sub4 : SubRegIndex<32, 128>;
+def sub5 : SubRegIndex<32, 160>;
+def sub6 : SubRegIndex<32, 192>;
+def sub7 : SubRegIndex<32, 224>;
+
+def R0 : Register<"">;
+def R1 : Register<"">;
+def R2 : Register<"">;
+def R3 : Register<"">;
+def R4 : Register<"">;
+def R5 : Register<"">;
+def R6 : Register<"">;
+def R7 : Register<"">;
+
+def Super : Register<""> {
+  let SubRegs = [R0, R1, R2, R3, R4, R5, R6, R7];
+  let SubRegIndices = [sub0, sub1, sub2, sub3, sub4, sub5, sub6, sub7];
+}
+
+def LeafRC : RegisterClass<"Test", [i32], 32,
+                           (add R0, R1, R2, R3, R4, R5, R6, R7)>;
+foreach I = 0-31 in
+  def ExtraLeafRC#I : RegisterClass<"Test", [i32], 32,
+                                     (add R0, R1, R2, R3, R4, R5, R6, R7)>;
+def SuperRC : RegisterClass<"Test", [v8i32], 256, (add Super)>;
+
+// CHECK-LABEL: getSubClassWithSubReg(
+// CHECK: static constexpr uint8_t BlockMap[17][1] = {
+// CHECK: static_assert(sizeof(BlockMap) == 17);
+// CHECK: static constexpr uint8_t Blocks[2][16] = {
+// CHECK-NEXT: { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+// CHECK-NEXT: { 0, 0, 0, 0, 0, 0, 0, 0, 34, 34, 34, 34, 34, 34, 34, 34 },
+// CHECK: static_assert(sizeof(Blocks) == 32);
+// CHECK: unsigned BlockID = BlockMap[RCID / 2][Idx / 8];
+// CHECK: unsigned TV = Blocks[BlockID][(RCID % 2) * 8 + Idx % 8];
+
+// CHECK-LABEL: getSubRegisterClass(
+// CHECK: static constexpr uint8_t BlockMap[17][1] = {
+// CHECK: static_assert(sizeof(BlockMap) == 17);
+// CHECK: static constexpr uint8_t Blocks[2][16] = {
+// CHECK-NEXT: { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+// CHECK-NEXT: { 0, 0, 0, 0, 0, 0, 0, 0, 33, 33, 33, 33, 33, 33, 33, 33 },
+// CHECK: static_assert(sizeof(Blocks) == 32);
+// CHECK: unsigned BlockID = BlockMap[RCID / 2][Idx / 8];
+// CHECK: unsigned TV = Blocks[BlockID][(RCID % 2) * 8 + Idx % 8];
diff --git a/llvm/utils/TableGen/RegisterInfoEmitter.cpp b/llvm/utils/TableGen/RegisterInfoEmitter.cpp
index cb5b72e0c64b8..e6033113df68a 100644
--- a/llvm/utils/TableGen/RegisterInfoEmitter.cpp
+++ b/llvm/utils/TableGen/RegisterInfoEmitter.cpp
@@ -20,6 +20,7 @@
 #include "Common/Types.h"
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/ADT/BitVector.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/ADT/SmallVector.h"
@@ -100,6 +101,9 @@ class RegisterInfoEmitter {
                             const std::deque<CodeGenRegister> &Regs,
                             bool isCtor);
   void EmitRegUnitPressure(raw_ostream &OS, StringRef ClassName);
+  void emitRegClassSubRegTable(raw_ostream &OS,
+                               ArrayRef<SmallVector<unsigned>> Table,
+                               StringRef RegClassTy, size_t SubRegIndicesSize);
   void emitComposeSubRegIndices(raw_ostream &OS, StringRef ClassName);
   void emitComposeSubRegIndexLaneMask(raw_ostream &OS, StringRef ClassName);
 };
@@ -674,6 +678,166 @@ static void printMask(raw_ostream &OS, LaneBitmask Val) {
   OS << "LaneBitmask(0x" << PrintLaneMask(Val) << ')';
 }
 
+void RegisterInfoEmitter::emitRegClassSubRegTable(
+    raw_ostream &OS, ArrayRef<SmallVector<unsigned>> Table,
+    StringRef RegClassTy, size_t SubRegIndicesSize) {
+  struct BlockTable {
+    unsigned BlockRows;
+    unsigned BlockColumns;
+    unsigned BlockRowCount;
+    unsigned BlockColumnCount;
+    std::vector<unsigned> BlockMap;
+    std::deque<SmallVector<unsigned>> Blocks;
+    size_t Size;
+  } Best;
+
+  assert(!Table.empty() && SubRegIndicesSize);
+  assert(llvm::all_of(Table, [SubRegIndicesSize](ArrayRef<unsigned> Row) {
+    return Row.size() == SubRegIndicesSize;
+  }));
+
+  const unsigned RegClassBytes = RegClassTy == "uint8_t" ? 1 : 2;
+  const size_t DirectSize = Table.size() * SubRegIndicesSize * RegClassBytes;
+  Best.Size = DirectSize;
+
+  // Limit blocks to 64 entries so table generation remains inexpensive even
+  // for targets with large register files.
+  for (unsigned BlockRows = 1; BlockRows <= 64; BlockRows *= 2) {
+    for (unsigned BlockColumns = 1; BlockRows * BlockColumns <= 64;
+         BlockColumns *= 2) {
+      if (BlockRows == 1 && BlockColumns == 1)
+        continue;
+
+      const unsigned BlockRowCount =
+          divideCeil(Table.size(), size_t(BlockRows));
+      const unsigned BlockColumnCount =
+          divideCeil(SubRegIndicesSize, size_t(BlockColumns));
+      DenseMap<ArrayRef<unsigned>, unsigned> BlockIDs;
+      std::vector<unsigned> BlockMap;
+      std::deque<SmallVector<unsigned>> Blocks;
+
+      for (unsigned Row = 0; Row < Table.size(); Row += BlockRows) {
+        for (unsigned Column = 0; Column < SubRegIndicesSize;
+             Column += BlockColumns) {
+          SmallVector<unsigned> Block(BlockRows * BlockColumns);
+          for (unsigned BlockRow = 0; BlockRow < BlockRows; ++BlockRow) {
+            for (unsigned BlockColumn = 0; BlockColumn < BlockColumns;
+                 ++BlockColumn) {
+              if (Row + BlockRow < Table.size() &&
+                  Column + BlockColumn < SubRegIndicesSize)
+                Block[BlockRow * BlockColumns + BlockColumn] =
+                    Table[Row + BlockRow][Column + BlockColumn];
+            }
+          }
+          auto It = BlockIDs.find(Block);
+          if (It == BlockIDs.end()) {
+            unsigned BlockID = Blocks.size();
+            Blocks.push_back(std::move(Block));
+            It = BlockIDs.try_emplace(ArrayRef(Blocks.back()), BlockID).first;
+          }
+          BlockMap.push_back(It->second);
+        }
+      }
+
+      const unsigned BlockIDBytes = Blocks.size() <= uint64_t(UINT8_MAX) + 1 ? 1
+                                    : Blocks.size() <= uint64_t(UINT16_MAX) + 1
+                                        ? 2
+                                        : 4;
+      const size_t Size =
+          BlockMap.size() * BlockIDBytes +
+          Blocks.size() * BlockRows * BlockColumns * RegClassBytes;
+      if (Size < Best.Size)
+        Best = {BlockRows,
+                BlockColumns,
+                BlockRowCount,
+                BlockColumnCount,
+                std::move(BlockMap),
+                std::move(Blocks),
+                Size};
+    }
+  }
+
+  // The block lookup needs more instructions than the direct lookup. Keep the
+  // direct table unless the data saving comfortably exceeds that code cost.
+  if (!Best.Blocks.empty() && DirectSize - Best.Size < 128)
+    Best.Blocks.clear();
+
+  if (Best.Blocks.empty()) {
+    OS << formatv("  static constexpr {} Table[{}][{}] = {{\n", RegClassTy,
+                  Table.size(), SubRegIndicesSize);
+    for (ArrayRef<unsigned> Row : Table) {
+      OS << "    { ";
+      interleaveComma(Row, OS);
+      OS << " },\n";
+    }
+    OS << "  };\n";
+    OS << "  unsigned TV = Table[RC->getID()][Idx];\n";
+    return;
+  }
+
+  assert(Best.BlockMap.size() == Best.BlockRowCount * Best.BlockColumnCount);
+  for (unsigned Row = 0; Row < Table.size(); ++Row) {
+    for (unsigned Column = 0; Column < SubRegIndicesSize; ++Column) {
+      [[maybe_unused]] unsigned BlockID =
+          Best.BlockMap[(Row / Best.BlockRows) * Best.BlockColumnCount +
+                        Column / Best.BlockColumns];
+      assert(BlockID < Best.Blocks.size());
+      assert(Best.Blocks[BlockID][(Row % Best.BlockRows) * Best.BlockColumns +
+                                  Column % Best.BlockColumns] ==
+             Table[Row][Column]);
+    }
+  }
+
+  StringRef BlockIDTy = getMinimalTypeForRange(Best.Blocks.size() - 1);
+  OS << formatv("  static constexpr {} BlockMap[{}][{}] = {{\n", BlockIDTy,
+                Best.BlockRowCount, Best.BlockColumnCount);
+  for (size_t Start = 0; Start < Best.BlockMap.size();
+       Start += Best.BlockColumnCount) {
+    ArrayRef<unsigned> Row(Best.BlockMap.data() + Start, Best.BlockColumnCount);
+    OS << "    { ";
+    interleaveComma(Row, OS);
+    OS << " },\n";
+  }
+  OS << "  };\n";
+  OS << formatv("  static_assert(sizeof(BlockMap) == {});\n",
+                Best.BlockRowCount * Best.BlockColumnCount *
+                    (BlockIDTy == "uint8_t"    ? 1
+                     : BlockIDTy == "uint16_t" ? 2
+                                               : 4));
+
+  OS << formatv("  static constexpr {} Blocks[{}][{}] = {{\n", RegClassTy,
+                Best.Blocks.size(), Best.BlockRows * Best.BlockColumns);
+  for (ArrayRef<unsigned> Block : Best.Blocks) {
+    OS << "    { ";
+    interleaveComma(Block, OS);
+    OS << " },\n";
+  }
+  OS << "  };\n";
+  OS << formatv("  static_assert(sizeof(Blocks) == {});\n",
+                Best.Blocks.size() * Best.BlockRows * Best.BlockColumns *
+                    RegClassBytes);
+  OS << "  unsigned RCID = RC->getID();\n"
+     << "  unsigned BlockID = BlockMap[";
+  if (Best.BlockRows == 1)
+    OS << "RCID";
+  else
+    OS << "RCID / " << Best.BlockRows;
+  OS << "][";
+  if (Best.BlockColumns == 1)
+    OS << "Idx";
+  else
+    OS << "Idx / " << Best.BlockColumns;
+  OS << "];\n  unsigned TV = Blocks[BlockID][";
+  if (Best.BlockRows == 1)
+    OS << "Idx % " << Best.BlockColumns;
+  else if (Best.BlockColumns == 1)
+    OS << "RCID % " << Best.BlockRows;
+  else
+    OS << "(RCID % " << Best.BlockRows << ") * " << Best.BlockColumns
+       << " + Idx % " << Best.BlockColumns;
+  OS << "];\n";
+}
+
 // Try to combine Idx's compose map into Vec if it is compatible.
 // Return false if it's not possible.
 static bool combine(const CodeGenSubRegIndex *Idx,
@@ -1602,43 +1766,42 @@ void RegisterInfoEmitter::runTargetDesc(raw_ostream &OS, raw_ostream &MainOS,
     // sentinel.
     const size_t NumRegClasses = RegisterClasses.size();
     const char *RegClassTy = getMinimalTypeForRange(NumRegClasses + 1);
-    auto EmitTableLookup = [&]() {
+    auto EmitTableLookupPreamble = [&]() {
       OS << formatv(R"(
-  };
   assert(RC && "Missing regclass");
   if (!Idx) return RC;
   --Idx;
   assert(Idx < {} && "Bad subreg");
-  unsigned TV = Table[RC->getID()][Idx];
-  return TV ? getRegClass(TV - 1) : nullptr;
-})",
+)",
                     SubRegIndicesSize);
     };
 
-    OS << formatv("  static constexpr {} Table[{}][{}] = {{\n", RegClassTy,
-                  NumRegClasses, SubRegIndicesSize);
+    SmallVector<SmallVector<unsigned>> Table;
+    Table.reserve(NumRegClasses);
     for (const auto &RC : RegisterClasses) {
-      OS << "    {\t// " << RC.getName() << "\n";
+      SmallVector<unsigned> Row;
+      Row.reserve(SubRegIndicesSize);
       for (auto &Idx : SubRegIndices) {
         if (CodeGenRegisterClass *SRC = RC.getSubClassWithSubReg(&Idx))
-          OS << "      " << SRC->EnumValue + 1 << ",\t// " << Idx.getName()
-             << " -> " << SRC->getName() << "\n";
+          Row.push_back(SRC->EnumValue + 1);
         else
-          OS << "      0,\t// " << Idx.getName() << "\n";
+          Row.push_back(0);
       }
-      OS << "    },\n";
+      Table.push_back(std::move(Row));
     }
-    EmitTableLookup();
+    EmitTableLookupPreamble();
+    emitRegClassSubRegTable(OS, Table, RegClassTy, SubRegIndicesSize);
+    OS << "  return TV ? getRegClass(TV - 1) : nullptr;\n}\n";
 
     // Emit getSubRegisterClass.
     OS << "const TargetRegisterClass *" << ClassName
        << "::getSubRegisterClass(const TargetRegisterClass *RC, unsigned Idx)"
        << " const {\n";
 
-    OS << formatv("  static constexpr {} Table[{}][{}] = {{\n", RegClassTy,
-                  NumRegClasses, SubRegIndicesSize);
+    Table.clear();
     for (const auto &RC : RegisterClasses) {
-      OS << "    {\t// " << RC.getName() << '\n';
+      SmallVector<unsigned> Row;
+      Row.reserve(SubRegIndicesSize);
       for (auto &Idx : SubRegIndices) {
         std::optional<std::pair<CodeGenRegisterClass *, CodeGenRegisterClass *>>
             MatchingSubClass = RC.getMatchingSubClassWithSubRegs(RegBank, &Idx);
@@ -1649,20 +1812,13 @@ void RegisterInfoEmitter::runTargetDesc(raw_ostream &OS, raw_ostream &MainOS,
           EnumValue = SubRegClass->EnumValue + 1;
         }
 
-        OS << "      " << EnumValue << ",\t// " << RC.getName() << ':'
-           << Idx.getName();
-
-        if (MatchingSubClass) {
-          CodeGenRegisterClass *SubRegClass = MatchingSubClass->second;
-          OS << " -> " << SubRegClass->getName();
-        }
-
-        OS << '\n';
+        Row.push_back(EnumValue);
       }
-
-      OS << "    },\n";
+      Table.push_back(std::move(Row));
     }
-    EmitTableLookup();
+    EmitTableLookupPreamble();
+    emitRegClassSubRegTable(OS, Table, RegClassTy, SubRegIndicesSize);
+    OS << "  return TV ? getRegClass(TV - 1) : nullptr;\n}\n";
   }
 
   EmitRegUnitPressure(OS, ClassName);



More information about the llvm-commits mailing list