[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