[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