[llvm] [TableGen] Pool duplicate code-emitter base encodings (PR #202619)
David Zbarsky via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 9 06:59:47 PDT 2026
https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202619
Targets with multiword instruction encodings currently emit one complete base
encoding for every instruction. AMDGPU repeats many of these rows, so the
constant table retains substantially more data than the encoder needs.
Build a pool of unique base encodings and emit the smallest uint8_t, uint16_t,
or uint32_t index table that can address it. Use the pooled representation only
when its values and indices are smaller than the original table, and leave
hardware-mode tables unchanged. Generated static assertions pin the index
width, table lengths, and pool bound.
On a Release build, the AMDGPU emitter constants shrink from 708,032 to
267,144 bytes (-440,888). The resulting binaries change as follows:
llvm-mc: 26,133,864 -> 25,688,232 bytes (-445,632)
llc: 136,637,088 -> 136,191,456 bytes (-445,632)
LLVM driver: 103,106,872 -> 102,677,752 bytes (-429,120)
Thirty alternating CPU-time pairs measured common AMDGPU assembly at +0.67%,
a mixed-instruction assembly workload at -0.56%, and bf16.ll code generation
at -0.07%. A downstream composition with the existing low-word compaction
measured common GFX9 assembly at +0.50% (95% paired bootstrap CI -1.56% to
+2.86%) and rare GFX12.5 assembly at -0.01% (CI -2.52% to +2.50%).
All 760 AMDGPU MC tests passed with one expected failure, all 420 TableGen
tests passed, and output was byte-identical for 22 llc and 7 llvm-mc cases.
Work towards #202616
>From 60fcd5800fe472bc57bff1551bcacbadc39bb566 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Tue, 9 Jun 2026 06:38:43 -0400
Subject: [PATCH] [TableGen] Pool duplicate code-emitter base encodings
Targets with multiword instruction encodings currently emit one complete base
encoding for every instruction. AMDGPU repeats many of these rows, so the
constant table retains substantially more data than the encoder needs.
Build a pool of unique base encodings and emit the smallest uint8_t, uint16_t,
or uint32_t index table that can address it. Use the pooled representation only
when its values and indices are smaller than the original table, and leave
hardware-mode tables unchanged. Generated static assertions pin the index
width, table lengths, and pool bound.
On a Release build, the AMDGPU emitter constants shrink from 708,032 to
267,144 bytes (-440,888). The resulting binaries change as follows:
llvm-mc: 26,133,864 -> 25,688,232 bytes (-445,632)
llc: 136,637,088 -> 136,191,456 bytes (-445,632)
LLVM driver: 103,106,872 -> 102,677,752 bytes (-429,120)
Thirty alternating CPU-time pairs measured common AMDGPU assembly at +0.67%,
a mixed-instruction assembly workload at -0.56%, and bf16.ll code generation
at -0.07%. A downstream composition with the existing low-word compaction
measured common GFX9 assembly at +0.50% (95% paired bootstrap CI -1.56% to
+2.86%) and rare GFX12.5 assembly at -0.01% (CI -2.52% to +2.50%).
All 760 AMDGPU MC tests passed with one expected failure, all 420 TableGen
tests passed, and output was byte-identical for 22 llc and 7 llvm-mc cases.
---
.../TableGen/CodeEmitterBaseEncodingPool.td | 39 +++++
llvm/utils/TableGen/CodeEmitterGen.cpp | 163 +++++++++++++++---
2 files changed, 174 insertions(+), 28 deletions(-)
create mode 100644 llvm/test/TableGen/CodeEmitterBaseEncodingPool.td
diff --git a/llvm/test/TableGen/CodeEmitterBaseEncodingPool.td b/llvm/test/TableGen/CodeEmitterBaseEncodingPool.td
new file mode 100644
index 0000000000000..9fb4da4ce0c2c
--- /dev/null
+++ b/llvm/test/TableGen/CodeEmitterBaseEncodingPool.td
@@ -0,0 +1,39 @@
+// RUN: llvm-tblgen -gen-emitter -I %p/../../include %s | FileCheck %s
+
+include "llvm/Target/Target.td"
+
+def TestInstrInfo : InstrInfo;
+
+def Test : Target {
+ let InstructionSet = TestInstrInfo;
+}
+
+class TestInst<bits<8> Encoding> : Instruction {
+ bits<128> Inst;
+ let Inst{7...0} = Encoding;
+ let OutOperandList = (outs);
+ let InOperandList = (ins);
+}
+
+def A0 : TestInst<1>;
+def A1 : TestInst<1>;
+def A2 : TestInst<1>;
+def A3 : TestInst<1>;
+def B0 : TestInst<2>;
+def B1 : TestInst<2>;
+def B2 : TestInst<2>;
+def B3 : TestInst<2>;
+
+// CHECK: static const uint64_t InstBits[] = {
+// CHECK-NEXT: UINT64_C(1), UINT64_C(0),
+// CHECK-NEXT: UINT64_C(2), UINT64_C(0),
+// CHECK-NEXT: };
+// CHECK: static const uint8_t InstBitsIndices[] = {
+// CHECK-NEXT: 0, 0, 0, 0, 1, 1, 1, 1,
+// CHECK-NEXT: };
+// CHECK: static_assert(sizeof(InstBitsIndices[0]) == 1);
+// CHECK: static_assert(sizeof(InstBitsIndices) / sizeof(InstBitsIndices[0]) == 8);
+// CHECK: static_assert(sizeof(InstBits) / sizeof(InstBits[0]) == 4);
+// CHECK: static_assert(2 <= (UINT64_C(1) << 8));
+// CHECK: unsigned InstBitsIndex = InstBitsIndices[TableIndex];
+// CHECK: Inst = APInt(128, ArrayRef(InstBits + InstBitsIndex * 2, 2));
diff --git a/llvm/utils/TableGen/CodeEmitterGen.cpp b/llvm/utils/TableGen/CodeEmitterGen.cpp
index fadb4ee1142b6..d45156c4d7faf 100644
--- a/llvm/utils/TableGen/CodeEmitterGen.cpp
+++ b/llvm/utils/TableGen/CodeEmitterGen.cpp
@@ -53,6 +53,14 @@ namespace {
// and the value is a list of cases which share the same body.
using CaseMapT = std::map<std::string, std::vector<unsigned>>;
+struct BaseEncodingPool {
+ std::vector<std::vector<uint64_t>> Values;
+ std::vector<unsigned> Indices;
+ unsigned IndexBitWidth = 0;
+
+ bool empty() const { return Values.empty(); }
+};
+
class CodeEmitterGen {
const RecordKeeper &RK;
CodeGenTarget Target;
@@ -74,9 +82,14 @@ class CodeEmitterGen {
const std::string &VarName, std::string &Case,
std::string &BitOffsetCase);
+ APInt getInstructionBaseValue(const CodeGenInstruction *CGI, unsigned HwMode);
+ void buildBaseEncodingPool(
+ ArrayRef<const CodeGenInstruction *> NumberedInstructions);
+ void emitBaseEncodingPool(raw_ostream &O);
void emitInstructionBaseValues(
raw_ostream &O, ArrayRef<const CodeGenInstruction *> NumberedInstructions,
unsigned HwMode = DefaultMode);
+ BaseEncodingPool BaseEncodings;
unsigned BitWidth = 0u;
bool UseAPInt = false;
};
@@ -361,6 +374,109 @@ static void emitInstBits(raw_ostream &OS, const APInt &Bits) {
OS << ((I > 0) ? ", " : "") << "UINT64_C(" << Bits.getRawData()[I] << ")";
}
+APInt CodeEmitterGen::getInstructionBaseValue(const CodeGenInstruction *CGI,
+ unsigned HwMode) {
+ const Record *R = CGI->TheDef;
+ const Record *EncodingDef = R;
+ if (const Record *RV = R->getValueAsOptionalDef("EncodingInfos")) {
+ EncodingInfoByHwMode EBM(RV, CGH);
+ if (!EBM.hasMode(HwMode))
+ return APInt(BitWidth, 0);
+ EncodingDef = EBM.get(HwMode);
+ }
+
+ const BitsInit *BI = EncodingDef->getValueAsBitsInit("Inst");
+ APInt Value(BitWidth, 0);
+ for (unsigned I = 0, E = BI->getNumBits(); I != E; ++I) {
+ if (const auto *B = dyn_cast<BitInit>(BI->getBit(I)); B && B->getValue())
+ Value.setBit(I);
+ }
+ return Value;
+}
+
+void CodeEmitterGen::buildBaseEncodingPool(
+ ArrayRef<const CodeGenInstruction *> NumberedInstructions) {
+ assert(UseAPInt && "pooling is only used for multiword encodings");
+
+ const unsigned NumWords = APInt::getNumWords(BitWidth);
+ // Pool complete rows so APInt construction still reads contiguous words.
+ std::map<std::vector<uint64_t>, unsigned> ValueToIndex;
+ for (const CodeGenInstruction *CGI : NumberedInstructions) {
+ APInt Value = getInstructionBaseValue(CGI, DefaultMode);
+ std::vector<uint64_t> Words(Value.getRawData(),
+ Value.getRawData() + NumWords);
+ auto [It, Inserted] =
+ ValueToIndex.try_emplace(Words, BaseEncodings.Values.size());
+ if (Inserted)
+ BaseEncodings.Values.push_back(std::move(Words));
+ BaseEncodings.Indices.push_back(It->second);
+ }
+
+ const uint64_t NumValues = BaseEncodings.Values.size();
+ if (NumValues <= (UINT64_C(1) << 8))
+ BaseEncodings.IndexBitWidth = 8;
+ else if (NumValues <= (UINT64_C(1) << 16))
+ BaseEncodings.IndexBitWidth = 16;
+ else if (NumValues <= (UINT64_C(1) << 32))
+ BaseEncodings.IndexBitWidth = 32;
+ else
+ BaseEncodings = {};
+
+ const uint64_t RawSize =
+ uint64_t(NumberedInstructions.size()) * NumWords * sizeof(uint64_t);
+ const uint64_t PooledSize =
+ NumValues * NumWords * sizeof(uint64_t) +
+ uint64_t(NumberedInstructions.size()) * (BaseEncodings.IndexBitWidth / 8);
+ if (BaseEncodings.empty() || PooledSize >= RawSize)
+ BaseEncodings = {};
+}
+
+static StringRef getIndexType(unsigned BitWidth) {
+ switch (BitWidth) {
+ case 8:
+ return "uint8_t";
+ case 16:
+ return "uint16_t";
+ case 32:
+ return "uint32_t";
+ }
+ llvm_unreachable("unsupported base encoding index width");
+}
+
+void CodeEmitterGen::emitBaseEncodingPool(raw_ostream &O) {
+ const unsigned NumWords = APInt::getNumWords(BitWidth);
+ O << " static const uint64_t InstBits[] = {\n";
+ for (ArrayRef<uint64_t> Value : BaseEncodings.Values) {
+ O << " ";
+ for (auto [I, Word] : enumerate(Value))
+ O << ((I > 0) ? ", " : "") << "UINT64_C(" << Word << ")";
+ O << ",\n";
+ }
+ O << " };\n";
+
+ StringRef IndexType = getIndexType(BaseEncodings.IndexBitWidth);
+ O << " static const " << IndexType << " InstBitsIndices[] = {\n";
+ for (auto [I, Index] : enumerate(BaseEncodings.Indices)) {
+ if (I % 16 == 0)
+ O << " ";
+ O << Index << ",";
+ if (I % 16 == 15 || I + 1 == BaseEncodings.Indices.size())
+ O << "\n";
+ else
+ O << " ";
+ }
+ O << " };\n";
+ O << " static_assert(sizeof(InstBitsIndices[0]) == "
+ << BaseEncodings.IndexBitWidth / 8 << ");\n";
+ O << " static_assert(sizeof(InstBitsIndices) / "
+ "sizeof(InstBitsIndices[0]) == "
+ << BaseEncodings.Indices.size() << ");\n";
+ O << " static_assert(sizeof(InstBits) / sizeof(InstBits[0]) == "
+ << BaseEncodings.Values.size() * NumWords << ");\n";
+ O << " static_assert(" << BaseEncodings.Values.size()
+ << " <= (UINT64_C(1) << " << BaseEncodings.IndexBitWidth << "));\n";
+}
+
void CodeEmitterGen::emitInstructionBaseValues(
raw_ostream &O, ArrayRef<const CodeGenInstruction *> NumberedInstructions,
unsigned HwMode) {
@@ -372,29 +488,7 @@ void CodeEmitterGen::emitInstructionBaseValues(
for (const CodeGenInstruction *CGI : NumberedInstructions) {
const Record *R = CGI->TheDef;
- const Record *EncodingDef = R;
- if (const Record *RV = R->getValueAsOptionalDef("EncodingInfos")) {
- EncodingInfoByHwMode EBM(RV, CGH);
- if (EBM.hasMode(HwMode)) {
- EncodingDef = EBM.get(HwMode);
- } else {
- // If the HwMode does not match, then Encoding '0'
- // should be generated.
- APInt Value(BitWidth, 0);
- O << " ";
- emitInstBits(O, Value);
- O << "," << '\t' << "// " << R->getName() << "\n";
- continue;
- }
- }
- const BitsInit *BI = EncodingDef->getValueAsBitsInit("Inst");
-
- // Start by filling in fixed values.
- APInt Value(BitWidth, 0);
- for (unsigned I = 0, E = BI->getNumBits(); I != E; ++I) {
- if (const auto *B = dyn_cast<BitInit>(BI->getBit(I)); B && B->getValue())
- Value.setBit(I);
- }
+ APInt Value = getInstructionBaseValue(CGI, HwMode);
O << " ";
emitInstBits(O, Value);
O << "," << '\t' << "// " << R->getName() << "\n";
@@ -436,6 +530,8 @@ void CodeEmitterGen::run(raw_ostream &O) {
BitWidth = std::max(BitWidth, BI->getNumBits());
}
UseAPInt = BitWidth > 64;
+ if (UseAPInt && HwModes.empty())
+ buildBaseEncodingPool(EncodedInstructions);
// Emit function declaration
if (UseAPInt) {
@@ -453,7 +549,10 @@ void CodeEmitterGen::run(raw_ostream &O) {
}
// Emit instruction base values
- emitInstructionBaseValues(O, EncodedInstructions, DefaultMode);
+ if (BaseEncodings.empty())
+ emitInstructionBaseValues(O, EncodedInstructions, DefaultMode);
+ else
+ emitBaseEncodingPool(O);
if (!HwModes.empty()) {
// Emit table for instrs whose encodings are controlled by HwModes.
for (unsigned HwMode : HwModes) {
@@ -498,10 +597,18 @@ void CodeEmitterGen::run(raw_ostream &O) {
if (UseAPInt) {
int NumWords = APInt::getNumWords(BitWidth);
O << " if (Scratch.getBitWidth() != " << BitWidth << ")\n"
- << " Scratch = Scratch.zext(" << BitWidth << ");\n"
- << " Inst = APInt(" << BitWidth << ", ArrayRef(InstBits + TableIndex * "
- << NumWords << ", " << NumWords << "));\n"
- << " APInt &Value = Inst;\n"
+ << " Scratch = Scratch.zext(" << BitWidth << ");\n";
+ if (BaseEncodings.empty()) {
+ O << " Inst = APInt(" << BitWidth
+ << ", ArrayRef(InstBits + TableIndex * " << NumWords << ", " << NumWords
+ << "));\n";
+ } else {
+ O << " unsigned InstBitsIndex = InstBitsIndices[TableIndex];\n"
+ << " Inst = APInt(" << BitWidth
+ << ", ArrayRef(InstBits + InstBitsIndex * " << NumWords << ", "
+ << NumWords << "));\n";
+ }
+ O << " APInt &Value = Inst;\n"
<< " APInt &op = Scratch;\n"
<< " switch (opcode) {\n";
} else {
More information about the llvm-commits
mailing list