Remove an explicitly-set-aside scratch register per class. (#51)
Currently, regalloc2 sets aside one register per class, unconditionally,
to make move resolution possible. To solve the "parallel moves problem",
we sometimes need to conjure a cyclic permutation of data among
registers or stack slots (this can result, for example, from blockparam
flow that swaps two values on a loop backedge). This set-aside scratch
register is used when a cycle exists.
regalloc2 also uses the scratch register when needed to break down a
stack-to-stack move (which could happen due to blockparam moves on edges
when source and destination are both spilled) into a stack-to-reg move
followed by reg-to-stack, because most machines have loads and stores
but not memory-to-memory moves.
A set-aside register is certainly the simplest solution, but it is not
optimal: it means that we have one fewer register available for use by
the program, and this can be costly especially on machines with fewer
registers (e.g., 16 GPRs/XMMs on x86-64) and especially when some
registers may be set aside by our embedder for other purposes too. Every
register we can reclaim is some nontrivial performance in large function
bodies!
This PR removes this restriction and allows regalloc2 to use all
available physical registers. It then solves the two problems above,
cyclic moves and stack-to-stack moves, with a two-stage approach:
- First, it finds a location to use to resolve cycles, if any exist. If
a register is unallocated at the location of the move, we can use it.
Often we get lucky and this is the case. Otherwise, we allocate a
stackslot to use as the temp. This is perfectly fine at this stage,
even if it means that we have more stack-to-stack moves.
- Then, it resolves stack-to-stack moves into stack-to-reg /
reg-to-stack. There are two subcases here. If there is *another*
available free physical register, we opportunistically use it for this
decomposition. If not, we fall back to our last-ditch option: we pick
a victim register of the appropriate class, we allocate another
temporary stackslot, we spill the victim to that slot just for this
move, we do the move in the above way (stack-to-reg / reg-to-stack)
with the victim, then we reload the victim. So one move (original
stack-to-stack) becomes four moves, but no state is clobbered.
This PR extends the `moves` fuzz-target to exercise this functionality
as well, randomly choosing for some spare registers to exist or not, and
randomly generating {stack,reg}-to-{stack,reg} moves in the initial
parallel-move input set. The target does a simple symbolic simulation of
the sequential move sequence and ensures that the final state is
equivalent to the parallel-move semantics.
I fuzzed both the `moves` target, focusing on the new logic; as well as
the `ion_checker` target, checking the whole register allocator, and
both seem clean (~150M cases on the former, ~1M cases on the latter).
This commit is contained in:
21
src/lib.rs
21
src/lib.rs
@@ -248,10 +248,13 @@ pub struct SpillSlot {
|
||||
}
|
||||
|
||||
impl SpillSlot {
|
||||
/// The maximum spillslot index.
|
||||
pub const MAX: usize = (1 << 24) - 1;
|
||||
|
||||
/// Create a new SpillSlot of a given class.
|
||||
#[inline(always)]
|
||||
pub fn new(slot: usize, class: RegClass) -> Self {
|
||||
debug_assert!(slot < (1 << 24));
|
||||
debug_assert!(slot <= Self::MAX);
|
||||
SpillSlot {
|
||||
bits: (slot as u32) | (class as u8 as u32) << 24,
|
||||
}
|
||||
@@ -1250,25 +1253,11 @@ pub struct MachineEnv {
|
||||
/// but still better than spilling.
|
||||
pub non_preferred_regs_by_class: [Vec<PReg>; 2],
|
||||
|
||||
/// One scratch register per class. This is needed to perform
|
||||
/// moves between registers when cyclic move patterns occur. The
|
||||
/// register should not be placed in either the preferred or
|
||||
/// non-preferred list (i.e., it is not otherwise allocatable).
|
||||
///
|
||||
/// Note that the register allocator will freely use this register
|
||||
/// between instructions, but *within* the machine code generated
|
||||
/// by a single (regalloc-level) instruction, the client is free
|
||||
/// to use the scratch register. E.g., if one "instruction" causes
|
||||
/// the emission of two machine-code instructions, this lowering
|
||||
/// can use the scratch register between them.
|
||||
pub scratch_by_class: [PReg; 2],
|
||||
|
||||
/// Some `PReg`s can be designated as locations on the stack rather than
|
||||
/// actual registers. These can be used to tell the register allocator about
|
||||
/// pre-defined stack slots used for function arguments and return values.
|
||||
///
|
||||
/// `PReg`s in this list cannot be used as a scratch register or as an
|
||||
/// allocatable regsiter.
|
||||
/// `PReg`s in this list cannot be used as an allocatable register.
|
||||
pub fixed_stack_slots: Vec<PReg>,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user