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, BTreeSet, BinaryHeap};
29
30use rucc_mir::{Func, Reg};
31use rucc_target::RegClass;
32
33use crate::assign;
34use crate::backtrack;
35use crate::live::{Area, Live};
36use crate::order::Point;
37use crate::pressure::Pressure;
38
39/// One value that could go to memory.
40#[derive(Debug, Clone, Copy)]
41struct Candidate<'a> {
42    reg: Reg,
43    area: Area<'a>,
44    weight: u128,
45    size: u32,
46}
47
48/// The values to send to memory before any register is handed out, lightest first at each point
49/// where more are live than there is room for.
50///
51/// # Panics
52///
53/// Panics on a function with more virtual registers than a `u32` can number, which is one no
54/// earlier pass could have built.
55#[must_use]
56pub fn choose(func: &Func, live: &Live, pressure: &Pressure) -> Vec<Reg> {
57    let costs = backtrack::costs(func);
58    let mut forced = vec![false; func.vregs()];
59    for reg in assign::forced(func) {
60        forced[index(reg)] = true;
61    }
62    let mut classes: BTreeMap<RegClass, Vec<Candidate<'_>>> = BTreeMap::new();
63    for (number, &forced) in forced.iter().enumerate() {
64        let reg = Reg::virtual_reg(u32::try_from(number).expect("a register number"));
65        let (Some(area), Some(class)) = (live.area(reg), func.class_of(reg)) else {
66            continue;
67        };
68        if forced {
69            continue;
70        }
71        let size = backtrack::size(area);
72        let weight = costs[number] * 1024 / u128::from(size + 8);
73        classes.entry(class).or_default().push(Candidate { reg, area, weight, size });
74    }
75
76    let mut pressure = pressure.clone();
77    let mut chosen = Vec::new();
78    for (class, mut candidates) in classes {
79        candidates.sort_by_key(|candidate| candidate.area.hull().start);
80        let over: Vec<_> = pressure.over(class).collect();
81        // The values live at the point being looked at, lightest first, which is the order they
82        // go in. A value goes in when one of its pieces starts and comes out when the piece ends,
83        // so each piece is looked at twice however many points it is live over. This used to ask
84        // every value whose hull had begun whether it covered the point, at every point, and on a
85        // function with many long lived values that was most values at most points.
86        let mut here: BTreeSet<(u128, Reverse<u32>, usize)> = BTreeSet::new();
87        // When each value next goes in or comes out, one at a time, so a value's own changes are
88        // always taken in order.
89        let mut changes: BinaryHeap<Reverse<(Point, usize)>> = (candidates.iter().enumerate())
90            .map(|(one, candidate)| Reverse((candidate.area.hull().start, one)))
91            .collect();
92        // Which piece each value is at, and whether it is in `here` for it yet.
93        let mut at = vec![(0, false); candidates.len()];
94        // The values already sent to memory, which never go back in.
95        let mut gone = vec![false; candidates.len()];
96        for point in over {
97            // Values sent to memory earlier often bring a point back under, and then the changes
98            // up to it can wait for the next point that is still over.
99            if pressure.excess(class, point) == 0 {
100                continue;
101            }
102            while let Some(&Reverse((when, one))) = changes.peek() {
103                if when > point {
104                    break;
105                }
106                changes.pop();
107                if gone[one] {
108                    continue;
109                }
110                let candidate = &candidates[one];
111                let key = (candidate.weight, Reverse(candidate.size), one);
112                let (piece, inside) = &mut at[one];
113                let range = candidate.area.piece(*piece);
114                if *inside {
115                    here.remove(&key);
116                    *inside = false;
117                    *piece += 1;
118                    if *piece < candidate.area.count() {
119                        changes.push(Reverse((candidate.area.piece(*piece).start, one)));
120                    }
121                } else {
122                    here.insert(key);
123                    *inside = true;
124                    if let Some(after) = range.end.checked_add(1) {
125                        changes.push(Reverse((after, one)));
126                    }
127                }
128            }
129            while pressure.excess(class, point) > 0 {
130                let Some((_, _, one)) = here.pop_first() else { break };
131                gone[one] = true;
132                pressure.lift(class, candidates[one].area);
133                chosen.push(candidates[one].reg);
134            }
135        }
136    }
137    chosen
138}
139
140fn index(reg: Reg) -> usize {
141    usize::try_from(reg.number().expect("a virtual register")).expect("a register number")
142}
143
144#[cfg(test)]
145mod tests {
146    use rucc_base::Interner;
147    use rucc_mir::{BlockCall, Opcode, Weight};
148    use rucc_target::x86_64::{GPR, SYSV};
149
150    use super::*;
151    use crate::assign::Env;
152    use crate::order::Order;
153
154    fn narrow(count: usize) -> Env {
155        Env::new().with(GPR, &SYSV.int_order[..count], &SYSV.int_order[count..count + 1])
156    }
157
158    fn chosen(func: &Func, env: &Env) -> Vec<Reg> {
159        let order = Order::of(func);
160        let live = Live::of(func, &order);
161        let pressure = Pressure::of(func, &order, &live, env);
162        choose(func, &live, &pressure)
163    }
164
165    #[test]
166    fn nothing_goes_where_everything_fits() {
167        let mut names = Interner::new();
168        let mut func = Func::new(names.intern("f"));
169        let opcode = Opcode::new(names.intern("x64.nop"));
170        let block = func.create_block();
171        let first = func.new_vreg(GPR);
172        let second = func.new_vreg(GPR);
173        func.build(block, opcode).def(first, GPR).finish();
174        func.build(block, opcode).def(second, GPR).finish();
175        func.build(block, opcode).uses(first, GPR).uses(second, GPR).finish();
176
177        assert!(chosen(&func, &narrow(2)).is_empty());
178    }
179
180    #[test]
181    fn the_value_read_least_is_the_one_that_goes() {
182        let mut names = Interner::new();
183        let mut func = Func::new(names.intern("f"));
184        let opcode = Opcode::new(names.intern("x64.nop"));
185        let block = func.create_block();
186        let busy = func.new_vreg(GPR);
187        let once = func.new_vreg(GPR);
188        let other = func.new_vreg(GPR);
189        func.build(block, opcode).def(busy, GPR).finish();
190        func.build(block, opcode).def(once, GPR).finish();
191        func.build(block, opcode).def(other, GPR).finish();
192        for _ in 0..3 {
193            func.build(block, opcode).uses(busy, GPR).uses(other, GPR).finish();
194        }
195        func.build(block, opcode).uses(once, GPR).uses(busy, GPR).uses(other, GPR).finish();
196
197        assert_eq!(chosen(&func, &narrow(2)), [once]);
198    }
199
200    #[test]
201    fn a_value_read_in_a_loop_stays_and_one_read_outside_it_goes() {
202        let mut names = Interner::new();
203        let mut func = Func::new(names.intern("f"));
204        let opcode = Opcode::new(names.intern("x64.nop"));
205        let entry = func.create_block();
206        let body = func.create_block();
207        let out = func.create_block();
208        let step = func.new_vreg(GPR);
209        let cold = func.new_vreg(GPR);
210        func.build(entry, opcode).def(cold, GPR).finish();
211        func.build(entry, opcode).def(step, GPR).finish();
212        *func.succs_mut(entry) = vec![BlockCall::to(body)];
213        func.build(body, opcode).uses(step, GPR).finish();
214        *func.succs_mut(body) = vec![BlockCall::to(body), BlockCall::to(out)];
215        func.set_weight(body, Weight::parts(100 * Weight::SCALE));
216        func.build(out, opcode).uses(cold, GPR).finish();
217        func.build(out, opcode).uses(step, GPR).finish();
218
219        assert_eq!(chosen(&func, &narrow(1)), [cold]);
220    }
221
222    #[test]
223    fn one_long_light_value_settles_two_points_that_are_each_one_over() {
224        let mut names = Interner::new();
225        let mut func = Func::new(names.intern("f"));
226        let opcode = Opcode::new(names.intern("x64.nop"));
227        let block = func.create_block();
228        let long = func.new_vreg(GPR);
229        let first = func.new_vreg(GPR);
230        let second = func.new_vreg(GPR);
231        func.build(block, opcode).def(long, GPR).finish();
232        func.build(block, opcode).def(first, GPR).finish();
233        func.build(block, opcode).uses(first, GPR).finish();
234        for _ in 0..4 {
235            func.build(block, opcode).finish();
236        }
237        func.build(block, opcode).def(second, GPR).finish();
238        func.build(block, opcode).uses(second, GPR).finish();
239        func.build(block, opcode).uses(long, GPR).finish();
240
241        // Each short value meets the long one at a point with room for one. Sending the long one
242        // away settles both, where sending the short ones would take two.
243        assert_eq!(chosen(&func, &narrow(1)), [long]);
244    }
245}