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