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

David Zbarsky via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 9 06:59:48 PDT 2026


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

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.

Work towards #202616

>From 532112c782ef6add71b192d485417cf28809ec54 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/utils/TableGen/AsmMatcherEmitter.cpp | 75 +++++++++++++++++------
 1 file changed, 57 insertions(+), 18 deletions(-)

diff --git a/llvm/utils/TableGen/AsmMatcherEmitter.cpp b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
index 4c61d64ec215a..7689fccf34e08 100644
--- a/llvm/utils/TableGen/AsmMatcherEmitter.cpp
+++ b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
@@ -116,6 +116,7 @@
 #include "llvm/TableGen/StringMatcher.h"
 #include "llvm/TableGen/StringToOffsetTable.h"
 #include "llvm/TableGen/TableGenBackend.h"
+#include <algorithm>
 #include <cassert>
 #include <cctype>
 #include <forward_list>
@@ -388,6 +389,23 @@ struct ClassInfo {
   }
 };
 
+using MatchClassSequence = std::vector<const ClassInfo *>;
+
+struct LessMatchClassSequence {
+  bool operator()(const MatchClassSequence &LHS,
+                  const MatchClassSequence &RHS) const {
+    return std::lexicographical_compare(
+        LHS.begin(), LHS.end(), RHS.begin(), RHS.end(),
+        [](const ClassInfo *LHS, const ClassInfo *RHS) {
+          if (!LHS)
+            return RHS != nullptr;
+          if (!RHS)
+            return false;
+          return LHS->Name < RHS->Name;
+        });
+  }
+};
+
 class AsmVariantInfo {
 public:
   StringRef RegisterPrefix;
@@ -3610,16 +3628,31 @@ 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, LessMatchClassSequence>
+      ClassSequenceOffsets;
+  for (const auto &MI : Info.Matchables) {
+    MatchClassSequence Sequence;
+    for (const MatchableInfo::AsmOperand &Op : MI->AsmOperands)
+      Sequence.push_back(Op.Class);
+    Sequence.resize(MaxNumOperands, nullptr);
+    ClassSequenceOffsets.try_emplace(std::move(Sequence), 0);
+  }
+
+  SmallVector<const ClassInfo *> 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 (const ClassInfo *Class : MatchClasses)
+    OS << "  " << (Class ? Class->Name : "InvalidMatchClass") << ",\n";
+  OS << "};\n\n";
+
+  // Emit the static match table.
   OS << "namespace {\n";
   OS << "  struct MatchEntry {\n";
   OS << "    " << getMinimalTypeForRange(MaxMnemonicIndex) << " Mnemonic;\n";
@@ -3627,11 +3660,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 +3710,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);
+      Sequence.resize(MaxNumOperands, nullptr);
+      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";
@@ -3819,11 +3856,13 @@ void AsmMatcherEmitter::run(raw_ostream &OS) {
   if (HasOptionalOperands)
     OS << "    OptionalOperandsMask.reset(0, "
        << MaxNumOperands + HasMnemonicFirst << ");\n";
+  OS << "    const auto *FormalClasses = "
+        "MatchClassTable + it->ClassOffset;\n";
   OS << "    for (unsigned FormalIdx = " << (HasMnemonicFirst ? "0" : "SIndex")
      << ", ActualIdx = " << (HasMnemonicFirst ? "1" : "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