Skip to content

[MC][NFC] Store RegBitSet more compact - #218701

Merged
aengelke merged 1 commit into
mainfrom
users/aengelke/spr/mcnfc-store-regbitset-more-compact
Aug 25, 2026
Merged

[MC][NFC] Store RegBitSet more compact#218701
aengelke merged 1 commit into
mainfrom
users/aengelke/spr/mcnfc-store-regbitset-more-compact

Conversation

@aengelke

Copy link
Copy Markdown
Contributor

Most register bit sets have their first register at a large offset,
causing a lot of 0 bytes to be stored in the RegBitSets. Avoid this by
storing the number of the lowest register in the MCRegisterClass. This
substantially reduces the size of the bit sets:

  • AArch64: 35010 -> 1356
  • AMDGPU: 1500754 -> 118320
  • PowerPC: 2202 -> 610
  • RISCV: 5700 -> 429
  • X86: 2329 -> 927

This also opens up deduplication potential for the future -- in addition
to duplicates, a common pattern is a streak of the first N bits. To ease
future deduplication, store the maximum register offset instead of the
bitset size in bytes (so the bitset 0xff,0xff could absorb the bitsets
0xff,0x3f and 0x01).

Created using spr 1.3.8-wip
@aengelke
aengelke requested review from MaskRay, arsenm and lenary August 25, 2026 14:52
@llvmorg-github-actions llvmorg-github-actions Bot added tablegen llvm:mc Machine (object) code labels Aug 25, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-tablegen

@llvm/pr-subscribers-llvm-mc

Author: Alexis Engelke (aengelke)

Changes

Most register bit sets have their first register at a large offset,
causing a lot of 0 bytes to be stored in the RegBitSets. Avoid this by
storing the number of the lowest register in the MCRegisterClass. This
substantially reduces the size of the bit sets:

  • AArch64: 35010 -> 1356
  • AMDGPU: 1500754 -> 118320
  • PowerPC: 2202 -> 610
  • RISCV: 5700 -> 429
  • X86: 2329 -> 927

This also opens up deduplication potential for the future -- in addition
to duplicates, a common pattern is a streak of the first N bits. To ease
future deduplication, store the maximum register offset instead of the
bitset size in bytes (so the bitset 0xff,0xff could absorb the bitsets
0xff,0x3f and 0x01).


Full diff: https://github.com/llvm/llvm-project/pull/218701.diff

3 Files Affected:

  • (modified) llvm/include/llvm/MC/MCRegisterInfo.h (+6-4)
  • (modified) llvm/unittests/CodeGen/MachineInstrTest.cpp (+1-1)
  • (modified) llvm/utils/TableGen/RegisterInfoEmitter.cpp (+20-10)
diff --git a/llvm/include/llvm/MC/MCRegisterInfo.h b/llvm/include/llvm/MC/MCRegisterInfo.h
index 9b894f5b6bf37..76c72bb08007d 100644
--- a/llvm/include/llvm/MC/MCRegisterInfo.h
+++ b/llvm/include/llvm/MC/MCRegisterInfo.h
@@ -45,6 +45,8 @@ class MCRegisterClass {
   const uint32_t NameIdx;
   const uint32_t RegSizeInBits;
   const uint16_t RegsSize;
+  /// Register denoted by first bit in RegSet.
+  const MCPhysReg RegSetBegin;
   const uint16_t RegSetSize;
   const uint16_t ID;
   const uint8_t CopyCost;
@@ -102,11 +104,11 @@ class MCRegisterClass {
   /// contains - Return true if the specified register is included in this
   /// register class.  This does not include virtual registers.
   bool contains(MCRegister Reg) const {
-    unsigned RegNo = Reg.id();
-    unsigned InByte = RegNo % 8;
-    unsigned Byte = RegNo / 8;
-    if (Byte >= RegSetSize)
+    unsigned RegSetIdx = Reg.id() - RegSetBegin;
+    if (RegSetIdx >= RegSetSize)
       return false;
+    unsigned InByte = RegSetIdx % 8;
+    unsigned Byte = RegSetIdx / 8;
     const uint8_t *RegSet = reinterpret_cast<const uint8_t *>(this) + RegSetOff;
     return (RegSet[Byte] & (1 << InByte)) != 0;
   }
diff --git a/llvm/unittests/CodeGen/MachineInstrTest.cpp b/llvm/unittests/CodeGen/MachineInstrTest.cpp
index 03f4077ea55ac..8ac5ad175a3b5 100644
--- a/llvm/unittests/CodeGen/MachineInstrTest.cpp
+++ b/llvm/unittests/CodeGen/MachineInstrTest.cpp
@@ -593,7 +593,7 @@ TEST(MachineInstrTest, SpliceOperands) {
 
   // test tied operands
   MCRegisterClass RC{
-      0, 0, 0,  0, 0, 0, 0, 0, /*Allocatable=*/true, /*BaseClass=*/true,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, /*Allocatable=*/true, /*BaseClass=*/true,
       0, 0, {}, 0, 0, 0, 0, 0, 0, 0, 0};
   // MachineRegisterInfo will be very upset if these registers aren't
   // allocatable.
diff --git a/llvm/utils/TableGen/RegisterInfoEmitter.cpp b/llvm/utils/TableGen/RegisterInfoEmitter.cpp
index 6c64330c1f374..c57345d020708 100644
--- a/llvm/utils/TableGen/RegisterInfoEmitter.cpp
+++ b/llvm/utils/TableGen/RegisterInfoEmitter.cpp
@@ -1145,10 +1145,13 @@ void RegisterInfoEmitter::runMCDesc(raw_ostream &OS, raw_ostream &MainOS,
     unsigned BitSetIdx;
     unsigned SubClassMaskIdx;
     unsigned SuperClassIdx;
+    // Not strictly an index, but to avoid recomputation: cache reg set range.
+    unsigned MinRegVal;
+    unsigned RegSetSize;
   };
   SmallVector<StartIndex> StartIndices;
   StartIndices.reserve(RegisterClasses.size() + 1);
-  StartIndices.push_back(StartIndex{0, 0, 0, 0});
+  StartIndices.push_back(StartIndex{0, 0, 0, 0, 0, 0});
 
   // For compressing the sub-reg index lists.
   using IdxList = std::vector<const CodeGenSubRegIndex *>;
@@ -1159,9 +1162,11 @@ void RegisterInfoEmitter::runMCDesc(raw_ostream &OS, raw_ostream &MainOS,
     ArrayRef<const Record *> Order = RC.getOrder();
     RegClassStrings.add(RC.getName());
 
-    unsigned MaxRegVal = 0;
-    for (const Record *Reg : Order)
+    unsigned MinRegVal = UINT_MAX, MaxRegVal = 0;
+    for (const Record *Reg : Order) {
+      MinRegVal = std::min(MinRegVal, RegBank.getReg(Reg)->EnumValue);
       MaxRegVal = std::max(MaxRegVal, RegBank.getReg(Reg)->EnumValue);
+    }
 
     unsigned SubClassMaskSize = (RC.getSubClasses().size() + 31) / 32;
     IdxList &SRIList = SuperRegIdxLists[RC.EnumValue];
@@ -1175,13 +1180,18 @@ void RegisterInfoEmitter::runMCDesc(raw_ostream &OS, raw_ostream &MainOS,
     }
     SuperRegIdxSeqs.add(SRIList);
 
-    const auto &Last = StartIndices.back();
+    auto &Last = StartIndices.back();
+    Last.MinRegVal = Order.empty() ? 0 : MinRegVal;
+    Last.RegSetSize = Order.empty() ? 0 : MaxRegVal - MinRegVal + 1;
     StartIndices.push_back(StartIndex{
         Last.RegIdx + unsigned(Order.size()),
         // Round to next byte size.
-        Last.BitSetIdx + (Order.empty() ? 0 : (MaxRegVal / 8) + 1),
+        Last.BitSetIdx +
+            (Order.empty() ? 0 : ((MaxRegVal - MinRegVal) / 8) + 1),
         Last.SubClassMaskIdx + SubClassMaskSize,
         Last.SuperClassIdx + unsigned(RC.getSuperClasses().size()),
+        0, // MinRegVal for next RegClass.
+        0, // RegSetSize for next RegClass.
     });
   }
 
@@ -1219,13 +1229,13 @@ void RegisterInfoEmitter::runMCDesc(raw_ostream &OS, raw_ostream &MainOS,
           .str();
     };
 
-    unsigned BitSetSize =
-        StartIndices[It.index() + 1].BitSetIdx - RCIndices.BitSetIdx;
     OS << "    {\n      " << GetOff("Regs", RCIndices.RegIdx) << ",\n      "
        << GetOff("BitSets", RCIndices.BitSetIdx) << ",\n      "
        << RegClassStrings.get(RC.getName()) << ",\n      " << RegSize
-       << ",\n      " << RC.getOrder().size() << ",\n      " << BitSetSize
-       << ",\n      " << RC.getQualifiedIdName() << ",\n      "
+       << ",\n      " << RC.getOrder().size() << ",\n      "
+       << RCIndices.MinRegVal << ", /* MinRegVal */\n      "
+       << RCIndices.RegSetSize << ", /* RegSetSize */\n      "
+       << RC.getQualifiedIdName() << ",\n      "
        << static_cast<unsigned>(RC.CopyCost) << ", /* CopyCost */\n      "
        << (RC.Allocatable ? "true" : "false") << ", /* Allocatable */\n      "
        << (RC.getBaseClassOrder() ? "true" : "false") << ",\n      "
@@ -1269,7 +1279,7 @@ void RegisterInfoEmitter::runMCDesc(raw_ostream &OS, raw_ostream &MainOS,
       OS << "    /* " << StartIndices[Idx].BitSetIdx << " */ ";
       BitVectorEmitter BVE;
       for (const Record *Reg : Order)
-        BVE.add(RegBank.getReg(Reg)->EnumValue);
+        BVE.add(RegBank.getReg(Reg)->EnumValue - StartIndices[Idx].MinRegVal);
       BVE.print(OS);
       OS << "\n";
     }

@github-actions

Copy link
Copy Markdown

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions cpp,h -- llvm/include/llvm/MC/MCRegisterInfo.h llvm/unittests/CodeGen/MachineInstrTest.cpp llvm/utils/TableGen/RegisterInfoEmitter.cpp --diff_from_common_commit

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/llvm/unittests/CodeGen/MachineInstrTest.cpp b/llvm/unittests/CodeGen/MachineInstrTest.cpp
index 8ac5ad175..ff06bb13f 100644
--- a/llvm/unittests/CodeGen/MachineInstrTest.cpp
+++ b/llvm/unittests/CodeGen/MachineInstrTest.cpp
@@ -592,9 +592,28 @@ TEST(MachineInstrTest, SpliceOperands) {
   EXPECT_EQ(MI->getOperand(8).getImm(), MachineOperand::CreateImm(4).getImm());
 
   // test tied operands
-  MCRegisterClass RC{
-      0, 0, 0, 0, 0, 0, 0, 0, 0, /*Allocatable=*/true, /*BaseClass=*/true,
-      0, 0, {}, 0, 0, 0, 0, 0, 0, 0, 0};
+  MCRegisterClass RC{0,
+                     0,
+                     0,
+                     0,
+                     0,
+                     0,
+                     0,
+                     0,
+                     0,
+                     /*Allocatable=*/true,
+                     /*BaseClass=*/true,
+                     0,
+                     0,
+                     {},
+                     0,
+                     0,
+                     0,
+                     0,
+                     0,
+                     0,
+                     0,
+                     0};
   // MachineRegisterInfo will be very upset if these registers aren't
   // allocatable.
   assert(RC.isAllocatable() && "unusable TargetRegisterClass");

@aengelke

Copy link
Copy Markdown
Contributor Author

... going to ignore clang-format here.

@lenary lenary left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@aengelke
aengelke merged commit 8f46480 into main Aug 25, 2026
14 of 15 checks passed
@aengelke
aengelke deleted the users/aengelke/spr/mcnfc-store-regbitset-more-compact branch August 25, 2026 18:04
wlemkows pushed a commit to wlemkows/llvm-project that referenced this pull request Sep 4, 2026
Most register bit sets have their first register at a large offset,
causing a lot of 0 bytes to be stored in the RegBitSets. Avoid this by
storing the number of the lowest register in the MCRegisterClass. This
substantially reduces the size of the bit sets:

- AArch64: 35010 -> 1356
- AMDGPU: 1500754 -> 118320
- PowerPC: 2202 -> 610
- RISCV: 5700 -> 429
- X86: 2329 -> 927

This also opens up deduplication potential for the future -- in addition
to duplicates, a common pattern is a streak of the first N bits. To ease
future deduplication, store the maximum register offset instead of the
bitset size in bytes (so the bitset 0xff,0xff could absorb the bitsets
0xff,0x3f and 0x01).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

llvm:mc Machine (object) code tablegen

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants