Skip to main content

backend_lang/regalloc/
mod.rs

1//! SSA register allocation: virtual registers to physical registers and
2//! stack slots.
3//!
4//! The input is a MIR function in SSA form (one definition per virtual
5//! register, every use dominated by its definition) in which physical
6//! registers appear only in short, block-local ranges: the copies into and
7//! out of argument and result registers around calls and returns, the
8//! operands of fixed-register instructions (`idiv`'s `rdx:rax`, a shift's
9//! `cl`), and the entry block's incoming arguments. Instruction selection
10//! ([`isel`](crate::isel)) produces exactly this. The output is the same
11//! function on physical registers, ready for [`emit`](crate::emit).
12//!
13//! # Algorithm
14//!
15//! 1. **Liveness** by SSA path exploration: from each use outside its
16//!    definition's block, walk predecessors up to the definition, marking
17//!    the value live-in on the way. Cost is the size of the live sets, with
18//!    an explicit budget.
19//! 2. **Constraints** in one backward walk per block: which uses are last
20//!    uses, which physical registers are live after each instruction, and,
21//!    for each value, every register written while it is live (a call's
22//!    clobbers, a fixed-register definition), so the value never sits in one
23//!    of them.
24//! 3. **Coloring in dominance order.** In strict SSA, two values that
25//!    interfere have one definition dominating the other, and the first is
26//!    live where the second is defined; so walking blocks in reverse
27//!    postorder and giving each value at its definition a register no live
28//!    value holds is a correct coloring, with no interference graph.
29//!    Block parameters get registers at their block's start; the parallel
30//!    copy on each edge is resolved at emission by the move resolver.
31//! 4. **Coalescing.** Values joined by a copy, a two-address tie, or a
32//!    block parameter and its arguments form affinity classes (union-find);
33//!    a value takes its class's register, its tied operand's register, or the
34//!    physical register it is copied to or from, whenever that register is
35//!    free, so the copy, the tie, or the edge move disappears. It is best
36//!    effort: a value that interferes with its class takes another register
37//!    and keeps its move.
38//! 5. **Spilling** when no register is free at a definition: the value with
39//!    the lowest weight (uses per instruction spanned) among the definition
40//!    and the values occupying a usable register is spilled for its whole
41//!    life (spill everywhere: no live-range splitting). A spilled constant
42//!    or address is **rematerialized** at each use instead of stored; any
43//!    other value is stored to a spill slot after its definition and
44//!    reloaded into a short-lived temporary before each use that cannot read
45//!    the slot directly (x86-64 reads one memory operand per instruction,
46//!    and branch arguments and copies read slots as they are). A spilled
47//!    block parameter simply lives in its slot. The allocation then reruns;
48//!    temporaries are never spilled, so each round spills at least one more
49//!    long-lived value, and after [`MAX_ROUNDS`] every remaining long-lived
50//!    value is spilled, which always succeeds.
51//!
52//! **Callee-saved registers** are used like any other; a value live across a
53//! call can only sit in one (the call clobbers the rest), and the frame
54//! saves exactly the callee-saved registers the function writes.
55//!
56//! **Stack maps** need nothing from the allocator: instruction selection
57//! stores each GC reference of a safepoint into a dedicated root slot before
58//! the call and reloads the relocated value after it, so no reference is in
59//! a register while the collector runs, and the slots are recorded on the
60//! call ([`Function::set_call_site`]).
61
62mod color;
63mod liveness;
64mod rewrite;
65
66use alloc::vec::Vec;
67use core::fmt;
68
69use crate::mir::cfg::Cfg;
70use crate::mir::{Block, Function, MirError, StackSlot};
71use crate::regs::{PReg, RegClass, VReg};
72
73/// After this many rounds that still spill, every long-lived value is
74/// spilled at once, which always allocates.
75pub const MAX_ROUNDS: u32 = 6;
76
77/// The default budget on live-set entries (one per value live into a
78/// block): 16 Mi entries, 64 MiB.
79pub const DEFAULT_LIVE_BUDGET: usize = 1 << 24;
80
81/// Why a function could not be allocated.
82///
83/// # Examples
84///
85/// ```
86/// use backend_lang::mir::{Function, Operand};
87/// use backend_lang::regalloc::{self, RegAllocError};
88/// use backend_lang::x86_64::{op, GPR, SYSV};
89///
90/// let mut f = Function::new("f", &SYSV);
91/// let b = f.block()?;
92/// let v = f.vreg(GPR)?;
93/// let w = f.vreg(GPR)?;
94/// f.push(b, op::MOV64, &[w.into(), v.into()])?; // v is never defined
95/// f.push(b, op::RET, &[])?;
96/// assert!(matches!(regalloc::allocate(&mut f), Err(RegAllocError::Mir(_))));
97/// # Ok::<(), backend_lang::mir::MirError>(())
98/// ```
99#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
100#[non_exhaustive]
101pub enum RegAllocError {
102    /// The function is malformed or not in SSA form
103    /// ([`Function::verify_ssa`]).
104    Mir(MirError),
105    /// A physical register is live into a block other than through the
106    /// entry block's parameters. Physical registers must be block-local:
107    /// copy values out of them into virtual registers.
108    PhysLiveIn {
109        /// The block.
110        block: Block,
111        /// The register.
112        reg: PReg,
113    },
114    /// An instruction writes a physical register that holds a live value
115    /// (two fixed-register ranges overlap).
116    PhysClobbered {
117        /// The block.
118        block: Block,
119        /// The register.
120        reg: PReg,
121    },
122    /// The live sets exceed the budget.
123    TooLarge,
124    /// No register of the class can hold a value even after spilling
125    /// everything spillable (the class has no allocatable registers, or a
126    /// single instruction needs more than the class has).
127    NoRegister {
128        /// The class.
129        class: RegClass,
130    },
131}
132
133impl fmt::Display for RegAllocError {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self {
136            RegAllocError::Mir(e) => fmt::Display::fmt(e, f),
137            RegAllocError::PhysLiveIn { block, reg } => write!(
138                f,
139                "physical register {} is live into {block}; copy it to a virtual register first",
140                reg.index()
141            ),
142            RegAllocError::PhysClobbered { block, reg } => write!(
143                f,
144                "in {block}, physical register {} is written while it holds a live value",
145                reg.index()
146            ),
147            RegAllocError::TooLarge => f.write_str("the live sets exceed the allocator's budget"),
148            RegAllocError::NoRegister { class } => write!(
149                f,
150                "no register of class {} can be found for a value that cannot be spilled",
151                class.index()
152            ),
153        }
154    }
155}
156
157impl core::error::Error for RegAllocError {
158    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
159        match self {
160            RegAllocError::Mir(e) => Some(e),
161            _ => None,
162        }
163    }
164}
165
166impl From<MirError> for RegAllocError {
167    fn from(e: MirError) -> RegAllocError {
168        RegAllocError::Mir(e)
169    }
170}
171
172/// Where a virtual register lives for its whole life after allocation.
173///
174/// # Examples
175///
176/// ```
177/// use backend_lang::regalloc::Location;
178/// use backend_lang::x86_64::RAX;
179///
180/// assert_eq!(Location::Reg(RAX), Location::Reg(RAX));
181/// ```
182#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
183#[non_exhaustive]
184pub enum Location {
185    /// Not live anywhere reachable (never used, or only in dead code).
186    None,
187    /// A physical register.
188    Reg(PReg),
189    /// A stack slot (a spilled block parameter).
190    Slot(StackSlot),
191}
192
193/// Allocation settings.
194///
195/// # Examples
196///
197/// ```
198/// use backend_lang::regalloc::Options;
199///
200/// let o = Options::new().with_live_budget(1 << 20).keep_assignment(true);
201/// assert_eq!(o.live_budget, 1 << 20);
202/// ```
203#[derive(Clone, Copy, Debug, PartialEq, Eq)]
204#[non_exhaustive]
205pub struct Options {
206    /// The most live-set entries (values live into a block, summed over
207    /// blocks) the allocator builds before giving up with
208    /// [`RegAllocError::TooLarge`].
209    pub live_budget: usize,
210    /// Whether to keep the final virtual function and its assignment in the
211    /// [`Allocation`] (for checking an allocation independently; it costs a
212    /// copy of the function).
213    pub keep_assignment: bool,
214}
215
216impl Default for Options {
217    fn default() -> Options {
218        Options::new()
219    }
220}
221
222impl Options {
223    /// The defaults: [`DEFAULT_LIVE_BUDGET`], no assignment kept.
224    ///
225    /// # Examples
226    ///
227    /// ```
228    /// use backend_lang::regalloc::Options;
229    ///
230    /// assert!(!Options::new().keep_assignment);
231    /// ```
232    #[must_use]
233    pub const fn new() -> Options {
234        Options {
235            live_budget: DEFAULT_LIVE_BUDGET,
236            keep_assignment: false,
237        }
238    }
239
240    /// Sets the live-set budget.
241    ///
242    /// # Examples
243    ///
244    /// ```
245    /// use backend_lang::regalloc::Options;
246    ///
247    /// assert_eq!(Options::new().with_live_budget(10).live_budget, 10);
248    /// ```
249    #[must_use]
250    pub const fn with_live_budget(mut self, entries: usize) -> Options {
251        self.live_budget = entries;
252        self
253    }
254
255    /// Keeps the final virtual function and assignment.
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use backend_lang::regalloc::Options;
261    ///
262    /// assert!(Options::new().keep_assignment(true).keep_assignment);
263    /// ```
264    #[must_use]
265    pub const fn keep_assignment(mut self, keep: bool) -> Options {
266        self.keep_assignment = keep;
267        self
268    }
269}
270
271/// What the allocator did.
272///
273/// # Examples
274///
275/// ```
276/// use backend_lang::mir::{Effects, Function, Operand};
277/// use backend_lang::regalloc;
278/// use backend_lang::x86_64::{op, GPR, SYSV, RAX};
279///
280/// let mut f = Function::new("f", &SYSV);
281/// let b = f.block()?;
282/// let v = f.vreg(GPR)?;
283/// f.push(b, op::MOV64RI, &[v.into(), Operand::Imm(7)])?;
284/// f.push(b, op::COPY, &[RAX.into(), v.into()])?;
285/// f.push_with(b, op::RET, &[], Effects::new().uses(&[RAX]))?;
286/// let a = regalloc::allocate(&mut f)?;
287/// assert_eq!(a.spilled(), 0);
288/// // The value was given rax, the register it is copied to: no move left.
289/// assert_eq!(backend_lang::emit(&f)?, [0x55, 0x48, 0x89, 0xE5, 0xB8, 7, 0, 0, 0, 0x5D, 0xC3]);
290/// # Ok::<(), Box<dyn std::error::Error>>(())
291/// ```
292#[derive(Clone, Debug)]
293pub struct Allocation {
294    rounds: u32,
295    spilled: u32,
296    rematerialized: u32,
297    max_pressure: u32,
298    assignment: Option<(Function, Vec<Location>)>,
299}
300
301impl Allocation {
302    /// The coloring rounds run (1 when nothing spilled).
303    ///
304    /// # Examples
305    ///
306    /// See [`Allocation`].
307    #[inline]
308    #[must_use]
309    pub fn rounds(&self) -> u32 {
310        self.rounds
311    }
312
313    /// The values spilled to stack slots.
314    ///
315    /// # Examples
316    ///
317    /// See [`Allocation`].
318    #[inline]
319    #[must_use]
320    pub fn spilled(&self) -> u32 {
321        self.spilled
322    }
323
324    /// The spilled values rematerialized at their uses instead of stored.
325    ///
326    /// # Examples
327    ///
328    /// See [`Allocation`].
329    #[inline]
330    #[must_use]
331    pub fn rematerialized(&self) -> u32 {
332        self.rematerialized
333    }
334
335    /// The most values of one class live at once in the final round.
336    ///
337    /// # Examples
338    ///
339    /// See [`Allocation`].
340    #[inline]
341    #[must_use]
342    pub fn max_pressure(&self) -> u32 {
343        self.max_pressure
344    }
345
346    /// With [`Options::keep_assignment`]: the virtual function the final
347    /// round colored (after spill code was inserted), and each of its virtual
348    /// registers' locations, indexed by [`VReg::index`].
349    ///
350    /// # Examples
351    ///
352    /// ```
353    /// use backend_lang::mir::{Function, Operand};
354    /// use backend_lang::regalloc::{self, Location, Options};
355    /// use backend_lang::x86_64::{op, GPR, SYSV};
356    ///
357    /// let mut f = Function::new("f", &SYSV);
358    /// let b = f.block()?;
359    /// let v = f.vreg(GPR)?;
360    /// f.push(b, op::MOV64RI, &[v.into(), Operand::Imm(7)])?;
361    /// f.push(b, op::CMP64, &[v.into(), Operand::Imm(1)])?;
362    /// f.push(b, op::RET, &[])?;
363    /// let a = regalloc::allocate_with(&mut f, &Options::new().keep_assignment(true))?;
364    /// let (virt, locs) = a.assignment().unwrap();
365    /// assert_eq!(virt.vreg_count(), locs.len());
366    /// assert!(matches!(locs[0], Location::Reg(_)));
367    /// # Ok::<(), Box<dyn std::error::Error>>(())
368    /// ```
369    #[must_use]
370    pub fn assignment(&self) -> Option<(&Function, &[Location])> {
371        self.assignment.as_ref().map(|(f, l)| (f, l.as_slice()))
372    }
373}
374
375/// Allocates registers for `f` in place with the default [`Options`].
376///
377/// # Errors
378///
379/// A [`RegAllocError`]; `f` is unspecified after an error.
380///
381/// # Examples
382///
383/// ```
384/// use backend_lang::mir::{Effects, Function, Operand};
385/// use backend_lang::regalloc;
386/// use backend_lang::x86_64::{op, GPR, SYSV, RAX, RDI, RSI};
387///
388/// // fn add(a, b) -> a + b, on virtual registers.
389/// let mut f = Function::new("add", &SYSV);
390/// f.omit_frame_pointer();
391/// let b = f.block()?;
392/// f.add_param(b, RDI.into())?;
393/// f.add_param(b, RSI.into())?;
394/// let (x, y, s) = (f.vreg(GPR)?, f.vreg(GPR)?, f.vreg(GPR)?);
395/// f.push(b, op::PCOPY, &[x.into(), y.into(), RDI.into(), RSI.into()])?;
396/// f.push(b, op::ADD64, &[s.into(), x.into(), y.into()])?;
397/// f.push(b, op::COPY, &[RAX.into(), s.into()])?;
398/// f.push_with(b, op::RET, &[], Effects::new().uses(&[RAX]))?;
399/// regalloc::allocate(&mut f)?;
400/// let code = backend_lang::emit(&f)?;
401/// assert_eq!(code.last(), Some(&0xC3));
402/// # Ok::<(), Box<dyn std::error::Error>>(())
403/// ```
404pub fn allocate(f: &mut Function) -> Result<Allocation, RegAllocError> {
405    allocate_with(f, &Options::new())
406}
407
408/// Allocates registers for `f` in place.
409///
410/// # Errors
411///
412/// A [`RegAllocError`]; `f` is unspecified after an error.
413///
414/// # Examples
415///
416/// ```
417/// use backend_lang::mir::{Function, Operand};
418/// use backend_lang::regalloc::{self, Options, RegAllocError};
419/// use backend_lang::x86_64::{op, GPR, SYSV};
420///
421/// let mut f = Function::new("f", &SYSV);
422/// let (a, b) = (f.block()?, f.block()?);
423/// let v = f.vreg(GPR)?;
424/// f.push(a, op::MOV64RI, &[v.into(), Operand::Imm(1)])?;
425/// let to_b = f.block_call(b, &[])?;
426/// f.push(a, op::JMP, &[to_b.into()])?;
427/// f.push(b, op::CMP64, &[v.into(), Operand::Imm(0)])?;
428/// f.push(b, op::RET, &[])?;
429/// // v is live into b: one live-set entry, over a budget of zero.
430/// let err = regalloc::allocate_with(&mut f, &Options::new().with_live_budget(0));
431/// assert_eq!(err.unwrap_err(), RegAllocError::TooLarge);
432/// # Ok::<(), backend_lang::mir::MirError>(())
433/// ```
434pub fn allocate_with(f: &mut Function, options: &Options) -> Result<Allocation, RegAllocError> {
435    // The control-flow graph never changes during allocation (spill code
436    // and dead-code removal touch no branch), so it is built once.
437    f.verify()?;
438    let cfg = Cfg::new(f);
439    f.check_dominance(&cfg)?;
440    let mut state = rewrite::SpillState::new(f);
441    rewrite::remove_dead_pure(f, &cfg, &mut state)?;
442    let mut rounds = 0u32;
443    loop {
444        rounds += 1;
445        let last = rounds > MAX_ROUNDS;
446        if last {
447            rewrite::spill_all(f, &cfg, &mut state)?;
448        }
449        let analysis = liveness::analyze(f, &cfg, &state, options.live_budget)?;
450        let colored = color::color(f, &analysis, &state)?;
451        if colored.spill.is_empty() {
452            let assignment = if options.keep_assignment {
453                Some((f.clone(), colored.locations.clone()))
454            } else {
455                None
456            };
457            rewrite::apply(f, &analysis, &colored.locations)?;
458            return Ok(Allocation {
459                rounds,
460                spilled: state.spilled,
461                rematerialized: state.rematerialized,
462                max_pressure: analysis.max_pressure,
463                assignment,
464            });
465        }
466        if last {
467            let class = colored
468                .spill
469                .first()
470                .map_or(RegClass::new(0), |&v| class_of(f, v));
471            return Err(RegAllocError::NoRegister { class });
472        }
473        rewrite::spill(f, &cfg, &mut state, &colored.spill)?;
474    }
475}
476
477/// A virtual register's class, for a register known to the function.
478fn class_of(f: &Function, v: VReg) -> RegClass {
479    f.vreg_class(v).unwrap_or(RegClass::new(0))
480}