[llvm] [TableGen] Use BitVector for RegisterSet in AsmMatcherEmitter (NFC) (PR #222525)

Kazu Hirata via llvm-commits llvm-commits at lists.llvm.org
Wed Sep 9 23:57:46 PDT 2026


https://github.com/kazutakahirata created https://github.com/llvm/llvm-project/pull/222525

This patch replaces std::set with BitVector for RegisterSet in
AsmMatcherEmitter.

Without this patch, RegisterSet is represented as
std::set<const Record *, LessRecordByID>.  On targets with large
register files like AMDGPU (~3,000 registers), repeatedly computing set
intersections and subset checks using std::set nodes incurs
significant overhead.

This patch assigns each register record a dense bit index ordered by
LessRecordByID.  This allows us to represent RegisterSet as a
BitVector, enabling fast bitwise operations for set intersections and
subset checks.

This reduces the wall-clock time of generating AMDGPUGenAsmMatcher.inc
from 39.53s to 31.32s (a 20.8% speedup) across 3 runs while producing
bit-for-bit identical output across all targets.

Assisted-by: Antigravity


>From 7f3e8473df9ae0dfab467db965c71d3c4feaf714 Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Wed, 9 Sep 2026 21:51:42 -0700
Subject: [PATCH] [TableGen] Use BitVector for RegisterSet in AsmMatcherEmitter
 (NFC)

This patch replaces std::set with BitVector for RegisterSet in
AsmMatcherEmitter.

Without this patch, RegisterSet is represented as
std::set<const Record *, LessRecordByID>.  On targets with large
register files like AMDGPU (~3,000 registers), repeatedly computing set
intersections and subset checks using std::set nodes incurs
significant overhead.

This patch assigns each register record a dense bit index ordered by
LessRecordByID.  This allows us to represent RegisterSet as a
BitVector, enabling fast bitwise operations for set intersections and
subset checks.

This reduces the wall-clock time of generating AMDGPUGenAsmMatcher.inc
from 39.53s to 31.32s (a 20.8% speedup) across 3 runs while producing
bit-for-bit identical output across all targets.

Assisted-by: Antigravity
---
 llvm/utils/TableGen/AsmMatcherEmitter.cpp | 92 +++++++++++++++--------
 1 file changed, 60 insertions(+), 32 deletions(-)

diff --git a/llvm/utils/TableGen/AsmMatcherEmitter.cpp b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
index 5e2127d139081..35c06e9141e16 100644
--- a/llvm/utils/TableGen/AsmMatcherEmitter.cpp
+++ b/llvm/utils/TableGen/AsmMatcherEmitter.cpp
@@ -140,8 +140,9 @@ class AsmMatcherInfo;
 // when generating its data structures. This means that the order of two
 // RegisterSets can be seen in the outputted AsmMatcher tables occasionally, and
 // can even affect compiler output (at least seen in diagnostics produced when
-// all matches fail). So we use a type that sorts them consistently.
-using RegisterSet = std::set<const Record *, LessRecordByID>;
+// all matches fail). So they must be sorted consistently (see LessRegisterSet).
+// Each bit in the BitVector represents the presence of a physical register.
+using RegisterSet = BitVector;
 
 class AsmMatcherEmitter {
   const RecordKeeper &Records;
@@ -251,12 +252,7 @@ struct ClassInfo {
       if (!isRegisterClass() || !RHS.isRegisterClass())
         return false;
 
-      std::vector<const Record *> Tmp;
-      std::set_intersection(Registers.begin(), Registers.end(),
-                            RHS.Registers.begin(), RHS.Registers.end(),
-                            std::back_inserter(Tmp), LessRecordByID());
-
-      return !Tmp.empty();
+      return Registers.anyCommon(RHS.Registers);
     }
 
     if (isRegisterClassByHwMode() || RHS.isRegisterClassByHwMode())
@@ -369,8 +365,8 @@ struct ClassInfo {
     } else if (isRegisterClass()) {
       // For register sets, sort by number of registers. This guarantees that
       // a set will always sort before all of it's strict supersets.
-      if (Registers.size() != RHS.Registers.size())
-        return Registers.size() < RHS.Registers.size();
+      if (Registers.count() != RHS.Registers.count())
+        return Registers.count() < RHS.Registers.count();
     } else if (isRegisterClassByHwMode()) {
       // Ensure the MCK enum entries are in the same order as RegClassIDs. The
       // lookup table to from RegByHwMode to concrete class relies on it.
@@ -1268,22 +1264,60 @@ ClassInfo *AsmMatcherInfo::getOperandClass(const Record *Rec, int SubOpIdx) {
   PrintFatalError(Rec->getLoc(), "operand has no match class!");
 }
 
+// Comparator for RegisterSets (BitVectors). Lexicographically compares the
+// sorted sequence of register IDs present in each set to ensure deterministic
+// ordering of RegisterSets in std::set and std::map.
 struct LessRegisterSet {
   bool operator()(const RegisterSet &LHS, const RegisterSet &RHS) const {
-    // std::set<T> defines its own compariso "operator<", but it
-    // performs a lexicographical comparison by T's innate comparison
-    // for some reason. We don't want non-deterministic pointer
-    // comparisons so use this instead.
-    return std::lexicographical_compare(LHS.begin(), LHS.end(), RHS.begin(),
-                                        RHS.end(), LessRecordByID());
+    int L = LHS.find_first();
+    int R = RHS.find_first();
+    while (L != -1 && R != -1) {
+      if (L != R)
+        return L < R;
+      L = LHS.find_next(L);
+      R = RHS.find_next(R);
+    }
+    return L == -1 && R != -1;
   }
 };
 
+// Map each register to a dense bit index ordered by LessRecordByID so that
+// bit index order matches LessRecordByID order in LessRegisterSet.
+static DenseMap<const Record *, unsigned>
+createRegisterIndexMap(const std::deque<CodeGenRegister> &Registers) {
+  std::vector<const Record *> AllRegDefs;
+  AllRegDefs.reserve(Registers.size());
+  for (const CodeGenRegister &CGR : Registers)
+    AllRegDefs.push_back(CGR.TheDef);
+  llvm::sort(AllRegDefs, LessRecordByID());
+
+  DenseMap<const Record *, unsigned> IndexMap;
+  IndexMap.reserve(AllRegDefs.size());
+  for (const auto &[I, RegDef] : llvm::enumerate(AllRegDefs))
+    IndexMap[RegDef] = I;
+  return IndexMap;
+}
+
 void AsmMatcherInfo::buildRegisterClasses(
     SmallPtrSetImpl<const Record *> &SingletonRegisters) {
   const auto &Registers = Target.getRegBank().getRegisters();
   auto &RegClassList = Target.getRegBank().getRegClasses();
 
+  const DenseMap<const Record *, unsigned> RegIndexMap =
+      createRegisterIndexMap(Registers);
+
+  // Helper to build a BitVector representing a set of register records.
+  unsigned NumRegs = Registers.size();
+  auto MakeRegisterSet = [&](ArrayRef<const Record *> Regs) {
+    BitVector BS(NumRegs);
+    for (const Record *Rec : Regs) {
+      auto It = RegIndexMap.find(Rec);
+      assert(It != RegIndexMap.end() && "Unknown register record");
+      BS.set(It->second);
+    }
+    return BS;
+  };
+
   using RegisterSetSet = std::set<RegisterSet, LessRegisterSet>;
 
   // The register sets used for matching.
@@ -1291,13 +1325,11 @@ void AsmMatcherInfo::buildRegisterClasses(
 
   // Gather the defined sets.
   for (const CodeGenRegisterClass &RC : RegClassList)
-    RegisterSets.insert(
-        RegisterSet(RC.getOrder().begin(), RC.getOrder().end()));
+    RegisterSets.insert(MakeRegisterSet(RC.getOrder()));
 
   // Add any required singleton sets.
-  for (const Record *Rec : SingletonRegisters) {
-    RegisterSets.insert(RegisterSet(&Rec, &Rec + 1));
-  }
+  for (const Record *Rec : SingletonRegisters)
+    RegisterSets.insert(MakeRegisterSet({Rec}));
 
   // Introduce derived sets where necessary (when a register does not determine
   // a unique register set class), and build the mapping of registers to the set
@@ -1306,9 +1338,10 @@ void AsmMatcherInfo::buildRegisterClasses(
   for (const CodeGenRegister &CGR : Registers) {
     // Compute the intersection of all sets containing this register.
     RegisterSet ContainingSet;
+    unsigned RegIdx = RegIndexMap.lookup(CGR.TheDef);
 
     for (const RegisterSet &RS : RegisterSets) {
-      if (!RS.count(CGR.TheDef))
+      if (!RS.test(RegIdx))
         continue;
 
       if (ContainingSet.empty()) {
@@ -1316,14 +1349,10 @@ void AsmMatcherInfo::buildRegisterClasses(
         continue;
       }
 
-      RegisterSet Tmp;
-      std::set_intersection(ContainingSet.begin(), ContainingSet.end(),
-                            RS.begin(), RS.end(),
-                            std::inserter(Tmp, Tmp.begin()), LessRecordByID());
-      ContainingSet = std::move(Tmp);
+      ContainingSet &= RS;
     }
 
-    if (!ContainingSet.empty()) {
+    if (ContainingSet.any()) {
       RegisterSets.insert(ContainingSet);
       RegisterMap.try_emplace(CGR.TheDef, ContainingSet);
     }
@@ -1356,7 +1385,7 @@ void AsmMatcherInfo::buildRegisterClasses(
   for (const RegisterSet &RS : RegisterSets) {
     ClassInfo *CI = RegisterSetClasses[RS];
     for (const RegisterSet &RS2 : RegisterSets)
-      if (RS != RS2 && llvm::includes(RS2, RS, LessRecordByID()))
+      if (RS != RS2 && RS.subsetOf(RS2))
         CI->SuperClasses.push_back(RegisterSetClasses[RS2]);
   }
 
@@ -1366,8 +1395,7 @@ void AsmMatcherInfo::buildRegisterClasses(
     const Record *Def = RC.getDef();
     if (!Def)
       continue;
-    ClassInfo *CI = RegisterSetClasses[RegisterSet(RC.getOrder().begin(),
-                                                   RC.getOrder().end())];
+    ClassInfo *CI = RegisterSetClasses[MakeRegisterSet(RC.getOrder())];
     if (CI->ValueName.empty()) {
       CI->ClassName = RC.getName();
       CI->Name = "MCK_" + RC.getName();
@@ -1660,7 +1688,7 @@ void AsmMatcherInfo::buildInfo() {
       // Check for singleton registers.
       if (const Record *RegRecord = Op.SingletonReg) {
         Op.Class = RegisterClasses[RegRecord];
-        assert(Op.Class && Op.Class->Registers.size() == 1 &&
+        assert(Op.Class && Op.Class->Registers.count() == 1 &&
                "Unexpected class for singleton register");
         continue;
       }



More information about the llvm-commits mailing list