[llvm] [RegAllocFast] Document the allocator state and operand phases. NFC (PR #219835)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sun Aug 30 23:05:10 PDT 2026


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/219835

>From be4a82292d4afc6d78a97f5dc753b5a7234f74e1 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 30 Aug 2026 11:03:51 -0700
Subject: [PATCH] [RegAllocFast] Document the allocator state and operand
 phases. NFC

RegUnitStates is indexed by register unit, not by physical register, and
holds a tagged union whose default case is a virtual register number.
Record why that cannot collide with the RegUnitState enumerators, and
that regLiveIn is a reloadAtBegin() scratch marker the backward walk
never observes.

allocateInstruction() described itself as a two-step algorithm while the
body runs eight phases in a different order; list the phases and the
constraints that fix their order.

LiveOut and Reloaded are the two reasons a def must spill.

Drop a redundant local in reloadAtBegin().

Aided by Opus 5
---
 llvm/lib/CodeGen/RegAllocFast.cpp | 68 ++++++++++++++++---------------
 1 file changed, 35 insertions(+), 33 deletions(-)

diff --git a/llvm/lib/CodeGen/RegAllocFast.cpp b/llvm/lib/CodeGen/RegAllocFast.cpp
index 9fc3f211ad2b7..1877888a0b1e7 100644
--- a/llvm/lib/CodeGen/RegAllocFast.cpp
+++ b/llvm/lib/CodeGen/RegAllocFast.cpp
@@ -196,13 +196,14 @@ class RegAllocFastImpl {
   /// Maps virtual regs to the frame index where these values are spilled.
   IndexedMap<int, VirtReg2IndexFunctor> StackSlotForVirtReg;
 
-  /// Everything we know about a live virtual register.
+  /// A virtual register live at the current point of the backward walk.
+  /// Created at its last reference, cleared only when the block is done.
   struct LiveReg {
     MachineInstr *LastUse = nullptr; ///< Last instr to use reg.
     Register VirtReg;                ///< Virtual register number.
-    MCRegister PhysReg;              ///< Currently held here.
-    bool LiveOut = false;            ///< Register is possibly live out.
-    bool Reloaded = false;           ///< Register was reloaded.
+    MCRegister PhysReg;              ///< Currently held here, 0 if none.
+    bool LiveOut = false;            ///< Live out of the block; the def spills.
+    bool Reloaded = false;           ///< Reloaded below; the def spills.
     bool Error = false;              ///< Could not allocate.
 
     explicit LiveReg(Register VirtReg) : VirtReg(VirtReg) {}
@@ -228,26 +229,26 @@ class RegAllocFastImpl {
   /// that it is alive across blocks.
   BitVector MayLiveAcrossBlocks;
 
-  /// State of a register unit.
+  /// What occupies a register unit. Registers interfere exactly when their
+  /// unit sets intersect, so overlap needs no alias walk.
   enum RegUnitState {
-    /// A free register is not currently in use and can be allocated
-    /// immediately without checking aliases.
+    /// Not in use; a register is allocatable iff all of its units are free.
     regFree,
 
-    /// A pre-assigned register has been assigned before register allocation
-    /// (e.g., setting up a call parameter).
+    /// In use by a register fixed before allocation: a physreg operand such as
+    /// a call argument, or a block live-out. Cannot be spilled.
     regPreAssigned,
 
-    /// Used temporarily in reloadAtBegin() to mark register units that are
-    /// live-in to the basic block.
+    /// A scratch marker, not an occupancy state: reloadAtBegin() stamps
+    /// MBB.liveins() over the finished map to skip the reload for a virtual
+    /// register left in a register the block already receives.
     regLiveIn,
 
-    /// A register state may also be a virtual register number, indication
-    /// that the physical register is currently allocated to a virtual
-    /// register. In that case, LiveVirtRegs contains the inverse mapping.
+    /// Any other value is a virtual register number (>= VirtualRegFlag);
+    /// LiveVirtRegs holds the inverse mapping.
   };
 
-  /// Maps each physical register to a RegUnitState enum or virtual register.
+  /// State of each register unit, indexed by MCRegUnit.
   std::vector<unsigned> RegUnitStates;
 
   SmallVector<MachineInstr *, 32> Coalesced;
@@ -506,6 +507,7 @@ static bool dominates(InstrPosIndexes &PosIndexes, const MachineInstr &A,
                       const MachineInstr &B) {
   uint64_t IndexA, IndexB;
   PosIndexes.getIndex(A, IndexA);
+  // getIndex() returns true when it renumbered the block, invalidating IndexA.
   if (LLVM_UNLIKELY(PosIndexes.getIndex(B, IndexB)))
     PosIndexes.getIndex(A, IndexA);
   return IndexA < IndexB;
@@ -704,12 +706,10 @@ void RegAllocFastImpl::reloadAtBegin(MachineBasicBlock &MBB) {
   if (LiveVirtRegs.empty())
     return;
 
-  for (MachineBasicBlock::RegisterMaskPair P : MBB.liveins()) {
-    MCRegister Reg = P.PhysReg;
-    // Set state to live-in. This possibly overrides mappings to virtual
-    // registers but we don't care anymore at this point.
-    setPhysRegState(Reg, regLiveIn);
-  }
+  // A live-in register's units can be in three states: regFree, regPreAssigned,
+  // or a virtual register; only overwriting the last has an effect.
+  for (MachineBasicBlock::RegisterMaskPair P : MBB.liveins())
+    setPhysRegState(P.PhysReg, regLiveIn);
 
   SmallSet<Register, 2> PrologLiveIns;
 
@@ -1452,8 +1452,7 @@ void RegAllocFastImpl::findAndSortDefOperandIndexes(const MachineInstr &MI) {
   });
 }
 
-// Returns true if MO is tied and the operand it's tied to is not Undef (not
-// Undef is not the same thing as Def).
+// Returns true if MO is tied to an operand that is not undef.
 static bool isTiedToNotUndef(const MachineInstr &MI, const MachineOperand &MO) {
   if (!MO.isTied())
     return false;
@@ -1463,17 +1462,20 @@ static bool isTiedToNotUndef(const MachineInstr &MI, const MachineOperand &MO) {
 }
 
 void RegAllocFastImpl::allocateInstruction(MachineInstr &MI) {
-  // The basic algorithm here is:
-  // 1. Mark registers of def operands as free
-  // 2. Allocate registers to use operands and place reload instructions for
-  //    registers displaced by the allocation.
+  // Backwards, a def frees a register and a use occupies it; freeing the defs
+  // and allocating the uses are the heart of it, the rest ordered around them:
+  // * pre-assigned physreg defs
+  // * virtual register defs
+  // * free the def operands' registers
+  // * displace registers clobbered by regmasks
+  // * pre-assigned physreg uses
+  // * virtual register uses, inserting reloads
+  // * undef uses
+  // * free early-clobber defs
   //
-  // However we need to handle some corner cases:
-  // - pre-assigned defs and uses need to be handled before the other def/use
-  //   operands are processed to avoid the allocation heuristics clashing with
-  //   the pre-assignment.
-  // - The "free def operands" step has to come last instead of first for tied
-  //   operands and early-clobbers.
+  // Freeing follows the def allocation so a def is not handed a register this
+  // instruction also writes, and it skips tied and early-clobber defs, which
+  // the uses still need.
 
   InstrGen += 2;
   // In the event we ever get more than 2**31 instructions...



More information about the llvm-commits mailing list