[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