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}