Skip to main content

rucc_regalloc/
spill.rs

1//! Which values go to memory before any register is handed out.
2//!
3//! Design: `spec/optimizer/39-register-allocation.md` section 39.7, and tamnd/rucc#1177.
4//!
5//! [`crate::pressure`] finds the points where more values are live than there are registers. At
6//! each of those some of the values live there have to be in memory, and this picks which: the
7//! lightest first, by the weight [`crate::backtrack`] places values by. That is how often a value
8//! is read or written, each time counted by how often its block runs, over how much of the
9//! function it is live across. A value read in a loop is heavy for the loop's sake, so what goes
10//! is one read outside it, and a value live over a long stretch and rarely read is light, since
11//! sending it to memory frees a register over the most points for the fewest loads.
12//!
13//! The points are taken in order along the line, and at each one values go until no more are
14//! over the room. A value that goes is taken off every point it was live at, so one long light
15//! value can settle several points at once, and a later point may already be settled by the time
16//! it is reached.
17//!
18//! # What it promises
19//!
20//! It takes off each point no more values than the pressure model says have to go, and that
21//! number is a floor, so every value picked here is standing in for one that some value at that
22//! point had to be. It does not promise that the rest then fit. A register a fixed operand claims
23//! for one value, the answer of a two address instruction live where its source is read, and a
24//! value wanted across a call in one of the registers the call keeps are all left to the
25//! assignment, which can still evict and spill after this the way it did before.
26
27use std::cmp::Reverse;
28use std::collections::BTreeMap;
29
30use rucc_mir::{Func, Reg};
31use rucc_target::RegClass;
32
33use crate::assign;
34use crate::backtrack;
35use crate::live::{Area, Live};
36use crate::pressure::Pressure;
37
38/// One value that could go to memory.
39#[derive(Debug, Clone, Copy)]
40struct Candidate<'a> {
41    reg: Reg,
42    area: Area<'a>,
43    weight: u128,
44    size: u32,
45}
46
47/// The values to send to memory before any register is handed out, lightest first at each point
48/// where more are live than there is room for.
49///
50/// # Panics
51///
52/// Panics on a function with more virtual registers than a `u32` can number, which is one no
53/// earlier pass could have built.
54#[must_use]
55pub fn choose(func: &Func, live: &Live, pressure: &Pressure) -> Vec<Reg> {
56    let costs = backtrack::costs(func);
57    let mut forced = vec![false; func.vregs()];
58    for reg in assign::forced(func) {
59        forced[index(reg)] = true;
60    }
61    let mut classes: BTreeMap<RegClass, Vec<Candidate<'_>>> = BTreeMap::new();
62    for (number, &forced) in forced.iter().enumerate() {
63        let reg = Reg::virtual_reg(u32::try_from(number).expect("a register number"));
64        let (Some(area), Some(class)) = (live.area(reg), func.class_of(reg)) else {
65            continue;
66        };
67        if forced {
68            continue;
69        }
70        let size = backtrack::size(area);
71        let weight = costs[number] * 1024 / u128::from(size + 8);
72        classes.entry(class).or_default().push(Candidate { reg, area, weight, size });
73    }
74
75    let mut pressure = pressure.clone();
76    let mut chosen = Vec::new();
77    for (class, mut candidates) in classes {
78        candidates.sort_by_key(|candidate| candidate.area.hull().start);
79        let over: Vec<_> = pressure.over(class).collect();
80        // The values whose hull has begun by the point being looked at, and which have not gone.
81        let mut open: Vec<usize> = Vec::new();
82        // The values already sent to memory, which the next walk over `open` takes out of it.
83        let mut gone = vec![false; candidates.len()];
84        let mut next = 0;
85        for point in over {
86            while next < candidates.len() && candidates[next].area.hull().start <= point {
87                open.push(next);
88                next += 1;
89            }
90            // Values sent to memory earlier often bring a point back under, and then nothing here
91            // reads the list. Clearing out the values that have gone only when something will
92            // read it saves a walk over every open value at every one of those points.
93            if pressure.excess(class, point) == 0 {
94                continue;
95            }
96            // The values live here are found once, in the same walk that clears out the ones that
97            // have gone. Sending one to memory changes no other value's area, so the next to go is
98            // always the lightest of those left in this list, and the list is short next to `open`.
99            let mut here = Vec::new();
100            open.retain(|&one| {
101                let candidate = &candidates[one];
102                if gone[one] || candidate.area.hull().end < point {
103                    return false;
104                }
105                if candidate.area.covers(point) {
106                    here.push(one);
107                }
108                true
109            });
110            while pressure.excess(class, point) > 0 {
111                let lightest = (0..here.len()).min_by_key(|&at| {
112                    let one = here[at];
113                    (candidates[one].weight, Reverse(candidates[one].size), one)
114                });
115                let Some(at) = lightest else { break };
116                let one = here.swap_remove(at);
117                gone[one] = true;
118                pressure.lift(class, candidates[one].area);
119                chosen.push(candidates[one].reg);
120            }
121        }
122    }
123    chosen
124}
125
126fn index(reg: Reg) -> usize {
127    usize::try_from(reg.number().expect("a virtual register")).expect("a register number")
128}
129
130#[cfg(test)]
131mod tests {
132    use rucc_base::Interner;
133    use rucc_mir::{BlockCall, Opcode, Weight};
134    use rucc_target::x86_64::{GPR, SYSV};
135
136    use super::*;
137    use crate::assign::Env;
138    use crate::order::Order;
139
140    fn narrow(count: usize) -> Env {
141        Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 1])
142    }
143
144    fn chosen(func: &Func, env: &Env) -> Vec<Reg> {
145        let order = Order::of(func);
146        let live = Live::of(func, &order);
147        let pressure = Pressure::of(func, &order, &live, env);
148        choose(func, &live, &pressure)
149    }
150
151    #[test]
152    fn nothing_goes_where_everything_fits() {
153        let mut names = Interner::new();
154        let mut func = Func::new(names.intern("f"));
155        let opcode = Opcode::new(names.intern("x64.nop"));
156        let block = func.create_block();
157        let first = func.new_vreg(GPR);
158        let second = func.new_vreg(GPR);
159        func.build(block, opcode).def(first, GPR).finish();
160        func.build(block, opcode).def(second, GPR).finish();
161        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
162
163        assert!(chosen(&func, &narrow(2)).is_empty());
164    }
165
166    #[test]
167    fn the_value_read_least_is_the_one_that_goes() {
168        let mut names = Interner::new();
169        let mut func = Func::new(names.intern("f"));
170        let opcode = Opcode::new(names.intern("x64.nop"));
171        let block = func.create_block();
172        let busy = func.new_vreg(GPR);
173        let once = func.new_vreg(GPR);
174        let other = func.new_vreg(GPR);
175        func.build(block, opcode).def(busy, GPR).finish();
176        func.build(block, opcode).def(once, GPR).finish();
177        func.build(block, opcode).def(other, GPR).finish();
178        for _ in 0..3 {
179            func.build(block, opcode).uses(busy, GPR).uses(other, GPR).finish();
180        }
181        func.build(block, opcode).uses(once, GPR).uses(busy, GPR).uses(other, GPR).finish();
182
183        assert_eq!(chosen(&func, &narrow(2)), [once]);
184    }
185
186    #[test]
187    fn a_value_read_in_a_loop_stays_and_one_read_outside_it_goes() {
188        let mut names = Interner::new();
189        let mut func = Func::new(names.intern("f"));
190        let opcode = Opcode::new(names.intern("x64.nop"));
191        let entry = func.create_block();
192        let body = func.create_block();
193        let out = func.create_block();
194        let step = func.new_vreg(GPR);
195        let cold = func.new_vreg(GPR);
196        func.build(entry, opcode).def(cold, GPR).finish();
197        func.build(entry, opcode).def(step, GPR).finish();
198        *func.succs_mut(entry) = vec![BlockCall::to(body)];
199        func.build(body, opcode).uses(step, GPR).finish();
200        *func.succs_mut(body) = vec![BlockCall::to(body), BlockCall::to(out)];
201        func.set_weight(body, Weight::parts(100 * Weight::SCALE));
202        func.build(out, opcode).uses(cold, GPR).finish();
203        func.build(out, opcode).uses(step, GPR).finish();
204
205        assert_eq!(chosen(&func, &narrow(1)), [cold]);
206    }
207
208    #[test]
209    fn one_long_light_value_settles_two_points_that_are_each_one_over() {
210        let mut names = Interner::new();
211        let mut func = Func::new(names.intern("f"));
212        let opcode = Opcode::new(names.intern("x64.nop"));
213        let block = func.create_block();
214        let long = func.new_vreg(GPR);
215        let first = func.new_vreg(GPR);
216        let second = func.new_vreg(GPR);
217        func.build(block, opcode).def(long, GPR).finish();
218        func.build(block, opcode).def(first, GPR).finish();
219        func.build(block, opcode).uses(first, GPR).finish();
220        for _ in 0..4 {
221            func.build(block, opcode).finish();
222        }
223        func.build(block, opcode).def(second, GPR).finish();
224        func.build(block, opcode).uses(second, GPR).finish();
225        func.build(block, opcode).uses(long, GPR).finish();
226
227        // Each short value meets the long one at a point with room for one. Sending the long one
228        // away settles both, where sending the short ones would take two.
229        assert_eq!(chosen(&func, &narrow(1)), [long]);
230    }
231}