[llvm] [TableGen] Compact two-word APInt encoder tables (PR #202630)

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


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

For instruction encodings between 65 and 128 bits without hardware-mode tables, store only the low 64-bit base value in InstBits. Emit the fixed nonzero high word in the existing opcode switch, where opcodes with identical operand code and high bits already share a case.

The common encoding path removes one table load. Encodings wider than two words and hardware-mode encodings retain the existing APInt word array.

For AMDGPU, encoder constants shrink from 708,064 to 354,192 bytes and encoder object sections shrink by 354,280 bytes. A stripped AMDGPU llvm-mc shrinks from 20,900,800 to 20,537,544 bytes, saving 363,256 bytes (1.74%).

CPU measurements show no regression: common instructions improve from 556.0 to 548.3 ms, and instructions with nonzero upper words change from 191.3 to 192.2 ms.

Extend BigEncoder.td to check low-word tables and high-word insertion. Validate BigEncoder.td and HwModeEncodeAPInt.td, compare all 58 nonzero AMDGPU upper-word values, and produce byte-identical tensor, WMMA, and MFMA encodings with the official MC tests.

Work towards #202616

>From 40ae1cd7a18af7657b5076f15073436622c4a8a8 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Mon, 8 Jun 2026 23:51:42 -0400
Subject: [PATCH] [TableGen] Compact two-word APInt encoder tables

For instruction encodings between 65 and 128 bits without hardware-mode tables, store only the low 64-bit base value in InstBits. Emit the fixed nonzero high word in the existing opcode switch, where opcodes with identical operand code and high bits already share a case.

The common encoding path removes one table load. Encodings wider than two words and hardware-mode encodings retain the existing APInt word array.

For AMDGPU, encoder constants shrink from 708,064 to 354,192 bytes and encoder object sections shrink by 354,280 bytes. A stripped AMDGPU llvm-mc shrinks from 20,900,800 to 20,537,544 bytes, saving 363,256 bytes (1.74%).

CPU measurements show no regression: common instructions improve from 556.0 to 548.3 ms, and instructions with nonzero upper words change from 191.3 to 192.2 ms.

Extend BigEncoder.td to check low-word tables and high-word insertion. Validate BigEncoder.td and HwModeEncodeAPInt.td, compare all 58 nonzero AMDGPU upper-word values, and produce byte-identical tensor, WMMA, and MFMA encodings with the official MC tests.
---
 llvm/test/TableGen/BigEncoder.td       | 15 ++++++--
 llvm/utils/TableGen/CodeEmitterGen.cpp | 50 ++++++++++++++++++--------
 2 files changed, 48 insertions(+), 17 deletions(-)

diff --git a/llvm/test/TableGen/BigEncoder.td b/llvm/test/TableGen/BigEncoder.td
index 9b9d382433508..999acee8b191c 100644
--- a/llvm/test/TableGen/BigEncoder.td
+++ b/llvm/test/TableGen/BigEncoder.td
@@ -21,6 +21,7 @@ def foo : Instruction {
     bits<32> factor;
     let Inst{7...0} = 0xAA;
     let Inst{14...8} = factor{6...0}; // no offset
+    let Inst{64} = 1;
     let AsmString = "foo  $factor";
     field bits<16> SoftFail = 0;
     }
@@ -46,12 +47,20 @@ def biz : Instruction {
     }
 
 }
+// CHECK: static const uint64_t InstBits[] = {
+// CHECK: UINT64_C(187), // bar
+// CHECK: UINT64_C(204), // biz
+// CHECK: UINT64_C(170), // foo
+// CHECK: };
+// CHECK: Inst = APInt(65, InstBits[TableIndex]);
+
 // CHECK-LABEL: case ::biz: {
 // CHECK:      Value.insertBits(op.extractBitsAsZExtValue(4, 3), 12, 4);
 // CHECK-NEXT: Value.insertBits(op.extractBitsAsZExtValue(4, 7), 8, 4);
 
-// CHECK-LABEL: case ::foo: {
-// CHECK:      Value.insertBits(op.extractBitsAsZExtValue(7, 0), 8, 7);
-
 // CHECK-LABEL: case ::bar: {
 // CHECK:      Value.insertBits(op.extractBitsAsZExtValue(8, 3), 8, 8);
+
+// CHECK-LABEL: case ::foo: {
+// CHECK:      Value.insertBits(UINT64_C(1), 64, 1);
+// CHECK:      Value.insertBits(op.extractBitsAsZExtValue(7, 0), 8, 7);
diff --git a/llvm/utils/TableGen/CodeEmitterGen.cpp b/llvm/utils/TableGen/CodeEmitterGen.cpp
index fadb4ee1142b6..8cdf0c5e4966d 100644
--- a/llvm/utils/TableGen/CodeEmitterGen.cpp
+++ b/llvm/utils/TableGen/CodeEmitterGen.cpp
@@ -79,6 +79,7 @@ class CodeEmitterGen {
       unsigned HwMode = DefaultMode);
   unsigned BitWidth = 0u;
   bool UseAPInt = false;
+  bool CompactTwoWordInstBits = false;
 };
 
 } // end anonymous namespace
@@ -361,6 +362,16 @@ static void emitInstBits(raw_ostream &OS, const APInt &Bits) {
     OS << ((I > 0) ? ", " : "") << "UINT64_C(" << Bits.getRawData()[I] << ")";
 }
 
+static APInt getInstructionBaseValue(const Record *EncodingDef,
+                                     unsigned BitWidth) {
+  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::emitInstructionBaseValues(
     raw_ostream &O, ArrayRef<const CodeGenInstruction *> NumberedInstructions,
     unsigned HwMode) {
@@ -387,16 +398,12 @@ void CodeEmitterGen::emitInstructionBaseValues(
         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(EncodingDef, BitWidth);
     O << "    ";
-    emitInstBits(O, Value);
+    if (CompactTwoWordInstBits)
+      O << "UINT64_C(" << Value.getRawData()[0] << ")";
+    else
+      emitInstBits(O, Value);
     O << "," << '\t' << "// " << R->getName() << "\n";
   }
   O << "  };\n";
@@ -436,6 +443,8 @@ void CodeEmitterGen::run(raw_ostream &O) {
     BitWidth = std::max(BitWidth, BI->getNumBits());
   }
   UseAPInt = BitWidth > 64;
+  CompactTwoWordInstBits =
+      UseAPInt && HwModes.empty() && APInt::getNumWords(BitWidth) == 2;
 
   // Emit function declaration
   if (UseAPInt) {
@@ -474,6 +483,14 @@ void CodeEmitterGen::run(raw_ostream &O) {
     const Record *R = CGI->TheDef;
     auto [Case, BitOffsetCase] = getInstructionCases(R);
 
+    if (CompactTwoWordInstBits) {
+      APInt BaseValue = getInstructionBaseValue(R, BitWidth);
+      uint64_t HighWord = BaseValue.getRawData()[1];
+      if (HighWord)
+        Case.insert(0, "      Value.insertBits(UINT64_C(" + utostr(HighWord) +
+                           "), 64, " + utostr(BitWidth - 64) + ");\n");
+    }
+
     CaseMap[Case].push_back(Index);
     BitOffsetCaseMap[BitOffsetCase].push_back(Index);
   }
@@ -496,12 +513,17 @@ void CodeEmitterGen::run(raw_ostream &O) {
 
   // Emit initial function code
   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 (CompactTwoWordInstBits)
+      O << "  Inst = APInt(" << BitWidth << ", InstBits[TableIndex]);\n";
+    else {
+      int NumWords = APInt::getNumWords(BitWidth);
+      O << "  Inst = APInt(" << BitWidth
+        << ", ArrayRef(InstBits + TableIndex * " << NumWords << ", " << NumWords
+        << "));\n";
+    }
+    O << "  APInt &Value = Inst;\n"
       << "  APInt &op = Scratch;\n"
       << "  switch (opcode) {\n";
   } else {



More information about the llvm-commits mailing list