[llvm] [TableGen] Pool generated assembly matcher class sequences (PR #202621)

David Zbarsky via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 2 09:10:26 PDT 2026


https://github.com/dzbarsky updated https://github.com/llvm/llvm-project/pull/202621

>From f41d2e224825f59e84a84fd02d4de0c0a5401175 Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Mon, 8 Jun 2026 13:26:52 -0400
Subject: [PATCH] [TableGen] Pool generated assembly matcher class sequences

Generated assembly match entries currently embed a fixed-size operand-class array in every row. Large targets repeat the same padded class sequences thousands of times.

Emit each distinct padded sequence once in MatchClassTable and replace the inline array with a compact offset. Hoist the sequence base pointer outside the operand loop so each candidate computes it once.

In the stripped Darwin arm64 all-tools multicall binary this reduces size from 150,569,536 to 147,944,136 bytes, saving 2,625,400 bytes (1.74%). The pointer hoist is size-neutral.

Validation:

- Built the complete multicall binary with Bazel remote execution: https://app.buildbuddy.io/invocation/c5bb88dd-e94d-4fea-9af3-16ab9eb12e99

- Assembled x86-64, AArch64, RISC-V, ARM, and AMDGPU inputs with baseline and patched binaries; all object files were byte-identical.

- LLVM has no benchmark for target assembly instruction matching. SandboxIRBench exercises the LLVM IR parser and is not applicable.

- A matcher-dominated two-million-nop x86 assembly workload increased mean user time from 702.5 ms to 710.7 ms (+1.2%).

- An end-to-end -O0 compile of 3,000 generated C functions showed no regression in user time (188.0 ms baseline, 182.9 ms patched); wall time was too noisy to interpret.
---
 llvm/test/TableGen/RegClassByHwMode.td    | 14 +++---
 llvm/test/TableGen/RegisterByHwMode.td    | 26 +++++-----
 llvm/utils/TableGen/AsmMatcherEmitter.cpp | 58 ++++++++++++++++-------
 3 files changed, 60 insertions(+), 38 deletions(-)

diff --git a/llvm/test/TableGen/RegClassByHwMode.td b/llvm/test/TableGen/RegClassByHwMode.td
index 46aef05ebc9c2..1b071d794aceb 100644
--- a/llvm/test/TableGen/RegClassByHwMode.td
+++ b/llvm/test/TableGen/RegClassByHwMode.td
@@ -134,13 +134,13 @@ include "Common/RegClassByHwModeCommon.td"
 // ASMMATCHER: if (Operand.isReg()) {
 
 // ASMMATCHER: static const MatchEntry MatchTable0[] = {
-// ASMMATCHER: /* also_my_load_1 */, MyTarget::MY_LOAD, Convert__RegByHwMode_XRegs_EvenIfRequired1_0__RegByHwMode_MyPtrRC1_1, AMFBS_None, { MCK_RegByHwMode_XRegs_EvenIfRequired, MCK_RegByHwMode_MyPtrRC }, },
-// ASMMATCHER: /* also_my_load_2 */, MyTarget::MY_LOAD, Convert__RegByHwMode_XRegs_EvenIfRequired1_0__RegByHwMode_MyPtrRC1_1, AMFBS_None, { MCK_RegByHwMode_XRegs_EvenIfRequired, MCK_RegByHwMode_MyPtrRC }, },
-// ASMMATCHER: /* always_all */, MyTarget::ALWAYS_ALL, Convert__Reg1_0, AMFBS_None, { MCK_XRegs }, },
-// ASMMATCHER: /* always_even */, MyTarget::ALWAYS_EVEN, Convert__Reg1_0, AMFBS_None, { MCK_XRegs_Even }, },
-// ASMMATCHER: /* custom_decode */, MyTarget::CUSTOM_DECODE, Convert__RegByHwMode_YRegs_EvenIfRequired1_0, AMFBS_None, { MCK_RegByHwMode_YRegs_EvenIfRequired }, },
-// ASMMATCHER: /* even_if_mode */, MyTarget::EVEN_IF_MODE, Convert__RegByHwMode_XRegs_EvenIfRequired1_0, AMFBS_None, { MCK_RegByHwMode_XRegs_EvenIfRequired }, },
-// ASMMATCHER: /* my_mov */, MyTarget::MY_MOV, Convert__RegByHwMode_YRegs_EvenIfRequired1_0__RegByHwMode_XRegs_EvenIfRequired1_1, AMFBS_None, { MCK_RegByHwMode_YRegs_EvenIfRequired, MCK_RegByHwMode_XRegs_EvenIfRequired }, },
+// ASMMATCHER: /* also_my_load_1 */, MyTarget::MY_LOAD, Convert__RegByHwMode_XRegs_EvenIfRequired1_0__RegByHwMode_MyPtrRC1_1, AMFBS_None, [[LOAD_CLASSES:[0-9]+]] /* { MCK_RegByHwMode_XRegs_EvenIfRequired, MCK_RegByHwMode_MyPtrRC } */, },
+// ASMMATCHER: /* also_my_load_2 */, MyTarget::MY_LOAD, Convert__RegByHwMode_XRegs_EvenIfRequired1_0__RegByHwMode_MyPtrRC1_1, AMFBS_None, [[LOAD_CLASSES]] /* { MCK_RegByHwMode_XRegs_EvenIfRequired, MCK_RegByHwMode_MyPtrRC } */, },
+// ASMMATCHER: /* always_all */, MyTarget::ALWAYS_ALL, Convert__Reg1_0, AMFBS_None, {{[0-9]+}} /* { MCK_XRegs } */, },
+// ASMMATCHER: /* always_even */, MyTarget::ALWAYS_EVEN, Convert__Reg1_0, AMFBS_None, {{[0-9]+}} /* { MCK_XRegs_Even } */, },
+// ASMMATCHER: /* custom_decode */, MyTarget::CUSTOM_DECODE, Convert__RegByHwMode_YRegs_EvenIfRequired1_0, AMFBS_None, {{[0-9]+}} /* { MCK_RegByHwMode_YRegs_EvenIfRequired } */, },
+// ASMMATCHER: /* even_if_mode */, MyTarget::EVEN_IF_MODE, Convert__RegByHwMode_XRegs_EvenIfRequired1_0, AMFBS_None, {{[0-9]+}} /* { MCK_RegByHwMode_XRegs_EvenIfRequired } */, },
+// ASMMATCHER: /* my_mov */, MyTarget::MY_MOV, Convert__RegByHwMode_YRegs_EvenIfRequired1_0__RegByHwMode_XRegs_EvenIfRequired1_1, AMFBS_None, {{[0-9]+}} /* { MCK_RegByHwMode_YRegs_EvenIfRequired, MCK_RegByHwMode_XRegs_EvenIfRequired } */, },
 
 
 
diff --git a/llvm/test/TableGen/RegisterByHwMode.td b/llvm/test/TableGen/RegisterByHwMode.td
index ec8f90c4a4113..16b9758000c8b 100644
--- a/llvm/test/TableGen/RegisterByHwMode.td
+++ b/llvm/test/TableGen/RegisterByHwMode.td
@@ -235,19 +235,19 @@
 
 
 // ASMMATCHER-LABEL: static const MatchEntry MatchTable0[] = {
-// ASMMATCHER-NEXT:    /* mode_count */, MyTarget::TEST_XREG, Convert__regModeCountReg__Reg1_0, AMFBS_None, { MCK_XRegs }, },
-// ASMMATCHER-NEXT:    /* t_ptr */, MyTarget::TEST_PTRREG, Convert__regNullReg__RegByHwMode_PtrRC1_0, AMFBS_None, { MCK_RegByHwMode_PtrRC }, },
-// ASMMATCHER-NEXT:    /* t_ptr.even */, MyTarget::TEST_PTRREG, Convert__regNullReg__RegByHwMode_EvenPtrRC1_0, AMFBS_None, { MCK_RegByHwMode_EvenPtrRC }, },
-// ASMMATCHER-NEXT:    /* t_x */, MyTarget::TEST_XREG, Convert__regX0__Reg1_0, AMFBS_None, { MCK_XRegs }, },
-// ASMMATCHER-NEXT:    /* t_x.even */, MyTarget::TEST_XREG, Convert__regX0__Reg1_0, AMFBS_None, { MCK_EvenXRegs }, },
-// ASMMATCHER-NEXT:    /* t_y */, MyTarget::TEST_YREG, Convert__regY0__Reg1_0, AMFBS_None, { MCK_YRegs }, },
-// ASMMATCHER-NEXT:    /* t_y.even */, MyTarget::TEST_YREG, Convert__regY0__Reg1_0, AMFBS_None, { MCK_EvenYRegs }, },
-// ASMMATCHER-NEXT:    /* test_64_only */, MyTarget::TEST_PTRREG, Convert__regPtrRegFor64BitModesOnly__RegByHwMode_PtrRC1_0, AMFBS_None, { MCK_RegByHwMode_PtrRC }, },
-// ASMMATCHER-NEXT:    /* test_alias */, MyTarget::TEST_PTRREG, Convert__RegByHwMode_PtrRC1_0__RegByHwMode_PtrRC1_1, AMFBS_None, { MCK_RegByHwMode_PtrRC, MCK_RegByHwMode_PtrRC }, },
-// ASMMATCHER-NEXT:    /* test_alias.even */, MyTarget::TEST_PTRREG, Convert__RegByHwMode_EvenPtrRC1_0__RegByHwMode_EvenPtrRC1_1, AMFBS_None, { MCK_RegByHwMode_EvenPtrRC, MCK_RegByHwMode_EvenPtrRC }, },
-// ASMMATCHER-NEXT:    /* test_ptr */, MyTarget::TEST_PTRREG, Convert__RegByHwMode_PtrRC1_0__RegByHwMode_PtrRC1_1, AMFBS_None, { MCK_RegByHwMode_PtrRC, MCK_RegByHwMode_PtrRC }, },
-// ASMMATCHER-NEXT:    /* test_x */, MyTarget::TEST_XREG, Convert__Reg1_0__Reg1_1, AMFBS_None, { MCK_XRegs, MCK_XRegs }, },
-// ASMMATCHER-NEXT:    /* test_y */, MyTarget::TEST_YREG, Convert__Reg1_0__Reg1_1, AMFBS_None, { MCK_YRegs, MCK_YRegs }, },
+// ASMMATCHER-NEXT:    /* mode_count */, MyTarget::TEST_XREG, Convert__regModeCountReg__Reg1_0, AMFBS_None, [[XREG_CLASSES:[0-9]+]] /* { MCK_XRegs } */, },
+// ASMMATCHER-NEXT:    /* t_ptr */, MyTarget::TEST_PTRREG, Convert__regNullReg__RegByHwMode_PtrRC1_0, AMFBS_None, {{[0-9]+}} /* { MCK_RegByHwMode_PtrRC } */, },
+// ASMMATCHER-NEXT:    /* t_ptr.even */, MyTarget::TEST_PTRREG, Convert__regNullReg__RegByHwMode_EvenPtrRC1_0, AMFBS_None, {{[0-9]+}} /* { MCK_RegByHwMode_EvenPtrRC } */, },
+// ASMMATCHER-NEXT:    /* t_x */, MyTarget::TEST_XREG, Convert__regX0__Reg1_0, AMFBS_None, [[XREG_CLASSES]] /* { MCK_XRegs } */, },
+// ASMMATCHER-NEXT:    /* t_x.even */, MyTarget::TEST_XREG, Convert__regX0__Reg1_0, AMFBS_None, {{[0-9]+}} /* { MCK_EvenXRegs } */, },
+// ASMMATCHER-NEXT:    /* t_y */, MyTarget::TEST_YREG, Convert__regY0__Reg1_0, AMFBS_None, {{[0-9]+}} /* { MCK_YRegs } */, },
+// ASMMATCHER-NEXT:    /* t_y.even */, MyTarget::TEST_YREG, Convert__regY0__Reg1_0, AMFBS_None, {{[0-9]+}} /* { MCK_EvenYRegs } */, },
+// ASMMATCHER-NEXT:    /* test_64_only */, MyTarget::TEST_PTRREG, Convert__regPtrRegFor64BitModesOnly__RegByHwMode_PtrRC1_0, AMFBS_None, {{[0-9]+}} /* { MCK_RegByHwMode_PtrRC } */, },
+// ASMMATCHER-NEXT:    /* test_alias */, MyTarget::TEST_PTRREG, Convert__RegByHwMode_PtrRC1_0__RegByHwMode_PtrRC1_1, AMFBS_None, [[PTR_CLASSES:[0-9]+]] /* { MCK_RegByHwMode_PtrRC, MCK_RegByHwMode_PtrRC } */, },
+// ASMMATCHER-NEXT:    /* test_alias.even */, MyTarget::TEST_PTRREG, Convert__RegByHwMode_EvenPtrRC1_0__RegByHwMode_EvenPtrRC1_1, AMFBS_None, {{[0-9]+}} /* { MCK_RegByHwMode_EvenPtrRC, MCK_RegByHwMode_EvenPtrRC } */, },
+// ASMMATCHER-NEXT:    /* test_ptr */, MyTarget::TEST_PTRREG, Convert__RegByHwMode_PtrRC1_0__RegByHwMode_PtrRC1_1, AMFBS_None, [[PTR_CLASSES]] /* { MCK_RegByHwMode_PtrRC, MCK_RegByHwMode_PtrRC } */, },
+// ASMMATCHER-NEXT:    /* test_x */, MyTarget::TEST_XREG, Convert__Reg1_0__Reg1_1, AMFBS_None, {{[0-9]+}} /* { MCK_XRegs, MCK_XRegs } */, },
+// ASMMATCHER-NEXT:    /* test_y */, MyTarget::TEST_YREG, Convert__Reg1_0__Reg1_1, AMFBS_None, {{[0-9]+}} /* { MCK_YRegs, MCK_YRegs } */, },
 // ASMMATCHER-NEXT: };
 
 include "Common/RegisterByHwModeCommon.td"
diff --git a/llvm/utils/TableGen/AsmMatcherEmitter.cpp b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
index 4af5c8510bffb..a1ca0527f1d36 100644
--- a/llvm/utils/TableGen/AsmMatcherEmitter.cpp
+++ b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
@@ -388,6 +388,8 @@ struct ClassInfo {
   }
 };
 
+using MatchClassSequence = std::vector<StringRef>;
+
 class AsmVariantInfo {
 public:
   StringRef RegisterPrefix;
@@ -3610,16 +3612,30 @@ void AsmMatcherEmitter::run(raw_ostream &OS) {
   }
   OS << "};\n\n";
 
-  // Emit the static match table; unused classes get initialized to 0 which is
-  // guaranteed to be InvalidMatchClass.
-  //
-  // FIXME: We can reduce the size of this table very easily. First, we change
-  // it so that store the kinds in separate bit-fields for each index, which
-  // only needs to be the max width used for classes at that index (we also need
-  // to reject based on this during classification). If we then make sure to
-  // order the match kinds appropriately (putting mnemonics last), then we
-  // should only end up using a few bits for each class, especially the ones
-  // following the mnemonic.
+  std::map<MatchClassSequence, unsigned> ClassSequenceOffsets;
+  for (const auto &MI : Info.Matchables) {
+    MatchClassSequence Sequence;
+    for (const MatchableInfo::AsmOperand &Op : MI->AsmOperands)
+      Sequence.push_back(Op.Class->Name);
+    Sequence.resize(MaxNumOperands);
+    ClassSequenceOffsets.try_emplace(std::move(Sequence), 0);
+  }
+
+  SmallVector<StringRef> MatchClasses;
+  for (auto &[Sequence, Offset] : ClassSequenceOffsets) {
+    Offset = MatchClasses.size();
+    append_range(MatchClasses, Sequence);
+  }
+
+  const char *MatchClassType = getMinimalTypeForRange(
+      std::distance(Info.Classes.begin(), Info.Classes.end()) +
+      2 /* Include 'InvalidMatchClass' and 'OptionalMatchClass' */);
+  OS << "static const " << MatchClassType << " MatchClassTable[] = {\n";
+  for (StringRef Class : MatchClasses)
+    OS << "  " << (Class.empty() ? "InvalidMatchClass" : Class) << ",\n";
+  OS << "};\n\n";
+
+  // Emit the static match table.
   OS << "namespace {\n";
   OS << "  struct MatchEntry {\n";
   OS << "    " << getMinimalTypeForRange(MaxMnemonicIndex) << " Mnemonic;\n";
@@ -3627,11 +3643,8 @@ void AsmMatcherEmitter::run(raw_ostream &OS) {
   OS << "    " << getMinimalTypeForRange(NumConverters) << " ConvertFn;\n";
   OS << "    " << getMinimalTypeForRange(FeatureBitsets.size())
      << " RequiredFeaturesIdx;\n";
-  OS << "    "
-     << getMinimalTypeForRange(
-            std::distance(Info.Classes.begin(), Info.Classes.end()) +
-            2 /* Include 'InvalidMatchClass' and 'OptionalMatchClass' */)
-     << " Classes[" << MaxNumOperands << "];\n";
+  OS << "    " << getMinimalTypeForRange(MatchClasses.size())
+     << " ClassOffset;\n";
   OS << "    StringRef getMnemonic() const {\n";
   OS << "      return StringRef(MnemonicTable + Mnemonic + 1,\n";
   OS << "                       MnemonicTable[Mnemonic]);\n";
@@ -3680,11 +3693,18 @@ void AsmMatcherEmitter::run(raw_ostream &OS) {
         for (const auto &F : MI->RequiredFeatures)
           OS << '_' << F->TheDef->getName();
 
-      OS << ", { ";
+      MatchClassSequence Sequence;
+      for (const MatchableInfo::AsmOperand &Op : MI->AsmOperands)
+        Sequence.push_back(Op.Class->Name);
+      Sequence.resize(MaxNumOperands);
+      auto ClassSequence = ClassSequenceOffsets.find(Sequence);
+      assert(ClassSequence != ClassSequenceOffsets.end());
+
+      OS << ", " << ClassSequence->second << " /* { ";
       ListSeparator LS;
       for (const MatchableInfo::AsmOperand &Op : MI->AsmOperands)
         OS << LS << Op.Class->Name;
-      OS << " }, },\n";
+      OS << " } */, },\n";
     }
 
     OS << "};\n\n";
@@ -3828,10 +3848,12 @@ void AsmMatcherEmitter::run(raw_ostream &OS) {
        << MaxNumOperands + HasMnemonicFirst << ");\n";
   OS << "    unsigned ActualIdx = " << (HasMnemonicFirst ? "1" : "SIndex")
      << ";\n";
+  OS << "    const auto *FormalClasses = "
+        "MatchClassTable + it->ClassOffset;\n";
   OS << "    for (unsigned FormalIdx = " << (HasMnemonicFirst ? "0" : "SIndex")
      << "; FormalIdx != " << MaxNumOperands << "; ++FormalIdx) {\n";
   OS << "      auto Formal = "
-     << "static_cast<MatchClassKind>(it->Classes[FormalIdx]);\n";
+     << "static_cast<MatchClassKind>(FormalClasses[FormalIdx]);\n";
   OS << "      DEBUG_WITH_TYPE(\"asm-matcher\",\n";
   OS << "                      dbgs() << \"  Matching formal operand class \" "
         "<< getMatchClassName(Formal)\n";



More information about the llvm-commits mailing list