Skip to main content

rucc_opt/
pressure.rs

1//! How many registers the program needs at each point, which is the one number four passes ask
2//! for and none of them should compute for itself.
3//!
4//! Design: section 40.6 of `spec/optimizer/40-cost-models.md`. It discharges document 12.5's
5//! obligation for global code motion and document 27.2's for loop invariant motion, which are the
6//! same obligation, and document 39.5's finding that the allocator and the scheduler want the same
7//! model.
8//!
9//! # It is the count, not an estimate of it
10//!
11//! In SSA the number of values live at a point is the number of registers the program needs at
12//! that point. That is document 39.5's chordality result and it is what makes this worth computing
13//! exactly rather than approximating: the interference graph of an SSA program is chordal, its
14//! chromatic number is the size of its largest clique, and the largest clique at a point is
15//! exactly what is live there. Everywhere else in a compiler a pressure number is a guess. Here it
16//! is not, and the four consumers can be written against a number rather than against a heuristic.
17//!
18//! # The four consumers
19//!
20//! Loop invariant motion and global code motion ask whether the pressure inside a loop is already
21//! at the allocatable count less a margin, and hoist only division and calls when it is. The
22//! scheduler asks, among instructions on equally long critical paths, which one reduces the live
23//! count. The spill phase asks for the maximum and reduces it to the register count, and is the
24//! consumer that defines the quantity. If conversion asks what merging two arms' live ranges into
25//! one block would do to the block it merges them into.
26//!
27//! # Two classes, and where the register count comes from
28//!
29//! [`Class`] is integer or floating point, which is the split every target has. A vector lands in
30//! the floating point class because on x86-64 the same registers hold both, and a target where
31//! that is wrong is a target that needs a third class here rather than a different rule.
32//!
33//! What this does not hold is how many registers there are. That is the target's, this crate does
34//! not see the target, and the comparison belongs where the register file is in hand. So the
35//! answer here is a count and [`Pressure::is_tight`] takes the allocatable count from the caller.
36//! The margin, which is GCC's `ira-loop-reserved-regs`, is a tuning constant and lives with the
37//! others in `rucc_cost::heuristics`.
38//!
39//! # What is not counted
40//!
41//! Values of type `mem` are the memory dependence chain rather than data, and nothing holds one in
42//! a register. Values of type `void` are not values. Both are dropped here rather than in
43//! [`crate::live`], because a pass asking what a store depends on wants the memory chain and only
44//! the register counting wants it gone.
45
46use rucc_cost::heuristics::LOOP_RESERVED_REGS;
47use rucc_ir::{Block, Func, Type};
48
49use crate::cfg::Cfg;
50use crate::live::Liveness;
51use crate::loops::{LoopId, Loops};
52
53/// Which bank of registers a value needs.
54#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55pub enum Class {
56    /// Integers, pointers and capabilities, which the general purpose registers hold.
57    Integer,
58    /// Floating point and vectors, which on every target rucc targets share a bank.
59    Float,
60}
61
62impl Class {
63    /// Both of them, for a caller that reports each.
64    pub const ALL: [Self; 2] = [Self::Integer, Self::Float];
65
66    /// How many there are, for the arrays keyed by one.
67    pub const COUNT: usize = Self::ALL.len();
68
69    /// How it reads in a dump.
70    #[must_use]
71    pub const fn as_str(self) -> &'static str {
72        match self {
73            Self::Integer => "integer",
74            Self::Float => "float",
75        }
76    }
77
78    /// Which bank holds a value of that type, and `None` for a type no register holds.
79    #[must_use]
80    pub const fn of(ty: Type) -> Option<Self> {
81        if ty.is_float() {
82            return Some(Self::Float);
83        }
84        if ty.is_vector() {
85            // A vector of integers still lives in the vector bank, which is the float one here.
86            return Some(Self::Float);
87        }
88        if ty.is_int() || ty.is_ptr() || ty.is_cap() {
89            return Some(Self::Integer);
90        }
91        // `mem` is the dependence chain and `void` is not a value.
92        None
93    }
94
95    const fn index(self) -> usize {
96        match self {
97            Self::Integer => 0,
98            Self::Float => 1,
99        }
100    }
101}
102
103impl std::fmt::Display for Class {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        f.write_str(self.as_str())
106    }
107}
108
109/// A count per register class.
110type PerClass = [u32; Class::COUNT];
111
112/// How many values of each class are live, at the places a consumer asks about.
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct Pressure {
115    arriving: Vec<PerClass>,
116    most_in: Vec<PerClass>,
117    most: PerClass,
118}
119
120impl Pressure {
121    /// Counts what is live at every point of every block.
122    #[must_use]
123    pub fn of(func: &Func, cfg: &Cfg, live: &Liveness) -> Self {
124        let blocks = cfg.capacity();
125        let mut arriving = vec![[0; Class::COUNT]; blocks];
126        let mut most_in = vec![[0; Class::COUNT]; blocks];
127        let mut most = [0; Class::COUNT];
128
129        for block in cfg.reverse_postorder() {
130            let at = block.index();
131            for value in live.live_in(block) {
132                if let Some(class) = Class::of(func[value].ty) {
133                    arriving[at][class.index()] += 1;
134                }
135            }
136            // The walk is backwards from the live-out, which is what gives the count just before
137            // each instruction without keeping a set per instruction.
138            let mut here = [0; Class::COUNT];
139            for value in live.live_out(block) {
140                if let Some(class) = Class::of(func[value].ty) {
141                    here[class.index()] += 1;
142                }
143            }
144            most_in[at] = here;
145            live.through(func, block, |_, at_inst| {
146                let mut counted = [0; Class::COUNT];
147                for value in at_inst.iter() {
148                    if let Some(class) = Class::of(func[value].ty) {
149                        counted[class.index()] += 1;
150                    }
151                }
152                for class in Class::ALL {
153                    let index = class.index();
154                    most_in[at][index] = most_in[at][index].max(counted[index]);
155                }
156            });
157            for class in Class::ALL {
158                let index = class.index();
159                most[index] = most[index].max(most_in[at][index]);
160            }
161        }
162
163        Self { arriving, most_in, most }
164    }
165
166    /// How many are live when control arrives at the block.
167    #[must_use]
168    pub fn arriving_at(&self, block: Block, class: Class) -> u32 {
169        self.arriving[block.index()][class.index()]
170    }
171
172    /// The most that are live at any point of the block.
173    #[must_use]
174    pub fn most_in_block(&self, block: Block, class: Class) -> u32 {
175        self.most_in[block.index()][class.index()]
176    }
177
178    /// The most that are live at any point of the function.
179    ///
180    /// The spill phase's number, and the one that says how many registers the function needs.
181    #[must_use]
182    pub fn most_in_function(&self, class: Class) -> u32 {
183        self.most[class.index()]
184    }
185
186    /// The most that are live at any point of the loop, including the loops nested in it.
187    ///
188    /// What loop invariant motion and global code motion ask, since a value hoisted out of a loop
189    /// is live across all of it and the pressure it adds is added everywhere inside.
190    #[must_use]
191    pub fn most_in_loop(&self, loops: &Loops, id: LoopId, class: Class) -> u32 {
192        loops.blocks(id).iter().map(|&block| self.most_in_block(block, class)).max().unwrap_or(0)
193    }
194
195    /// Whether hoisting into that loop is already too expensive to be worth it.
196    ///
197    /// The allocatable count is the caller's, because this crate does not see the target. The
198    /// margin is `LOOP_RESERVED_REGS`, which is GCC's `ira-loop-reserved-regs` and is two: a hoist
199    /// that takes the pressure up to the register count has bought nothing, because the value it
200    /// hoisted is now live across the loop and something else gets spilled to make room for it.
201    #[must_use]
202    pub fn is_tight(&self, loops: &Loops, id: LoopId, class: Class, allocatable: u32) -> bool {
203        self.most_in_loop(loops, id, class) >= allocatable.saturating_sub(LOOP_RESERVED_REGS)
204    }
205
206    /// What is wrong with these numbers, which should be nothing.
207    ///
208    /// The one invariant worth checking is that no block's own maximum exceeds the function's,
209    /// since the function's is the maximum over the blocks and a consumer comparing against the
210    /// wrong one of the two would be making a decision on a number that is too small.
211    #[must_use]
212    pub fn problems(&self, cfg: &Cfg) -> Vec<String> {
213        let mut problems = Vec::new();
214        for block in cfg.reverse_postorder() {
215            for class in Class::ALL {
216                let here = self.most_in_block(block, class);
217                let whole = self.most_in_function(class);
218                if here > whole {
219                    problems.push(format!(
220                        "block{} needs {here} {class} registers and the function claims {whole}",
221                        block.index()
222                    ));
223                }
224                if self.arriving_at(block, class) > here {
225                    problems.push(format!(
226                        "block{} has more {class} values arriving than it ever holds",
227                        block.index()
228                    ));
229                }
230            }
231        }
232        problems
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use rucc_base::Interner;
239    use rucc_ir::{Block, Builder, Flags, Float, Func, Opcode, Signature, Type};
240
241    use super::{Class, Pressure};
242    use crate::cfg::Cfg;
243    use crate::dom::Dominators;
244    use crate::live::Liveness;
245    use crate::loops::Loops;
246
247    const I32: Type = Type::int(32);
248
249    fn blank(count: usize) -> (Func, Vec<Block>) {
250        let mut names = Interner::new();
251        let mut func = Func::new(names.intern("f"), Signature::new());
252        let blocks: Vec<Block> = (0..count).map(|_| func.create_block()).collect();
253        (func, blocks)
254    }
255
256    fn pressure(func: &Func) -> (Cfg, Pressure) {
257        let cfg = Cfg::new(func);
258        let live = Liveness::of(func, &cfg);
259        let of = Pressure::of(func, &cfg, &live);
260        assert!(of.problems(&cfg).is_empty(), "{:?}", of.problems(&cfg));
261        (cfg, of)
262    }
263
264    #[test]
265    fn the_most_live_at_once_is_the_number_of_registers_the_block_needs() {
266        // Three constants alive together before the first add takes two of them.
267        let (mut func, blocks) = blank(1);
268        let mut build = Builder::new(&mut func, blocks[0]);
269        let one = build.iconst(I32, 1);
270        let two = build.iconst(I32, 2);
271        let three = build.iconst(I32, 3);
272        let first = build.binary(Opcode::Add, one, two, Flags::NONE);
273        let second = build.binary(Opcode::Add, first, three, Flags::NONE);
274        build.ret(&[second]);
275
276        let (_, of) = pressure(&func);
277        assert_eq!(of.most_in_block(blocks[0], Class::Integer), 3);
278        assert_eq!(of.most_in_function(Class::Integer), 3);
279        assert_eq!(of.most_in_function(Class::Float), 0);
280    }
281
282    #[test]
283    fn the_two_classes_are_counted_apart_because_they_are_two_banks_of_registers() {
284        let (mut func, blocks) = blank(1);
285        let mut build = Builder::new(&mut func, blocks[0]);
286        let whole = build.iconst(I32, 1);
287        let fraction = build.fconst(Type::float(Float::F64), 0);
288        let other = build.fconst(Type::float(Float::F64), 1);
289        let sum = build.binary(Opcode::FAdd, fraction, other, Flags::NONE);
290        build.ret(&[whole, sum]);
291
292        let (_, of) = pressure(&func);
293        assert_eq!(of.most_in_function(Class::Integer), 1);
294        assert_eq!(of.most_in_function(Class::Float), 2);
295    }
296
297    #[test]
298    fn nothing_that_is_not_held_in_a_register_is_counted() {
299        // The memory chain is a value and it is live, and no register holds one.
300        let (mut func, blocks) = blank(1);
301        let mut build = Builder::new(&mut func, blocks[0]);
302        let mem = build.mem_entry();
303        build.ret(&[]);
304        let ty = func[mem].ty;
305
306        assert!(ty.is_mem());
307        assert_eq!(Class::of(ty), None);
308        assert_eq!(Class::of(Type::VOID), None);
309        assert_eq!(Class::of(Type::PTR), Some(Class::Integer));
310        assert_eq!(Class::of(Type::vector(I32, 4)), Some(Class::Float));
311
312        let (_, of) = pressure(&func);
313        assert_eq!(of.most_in_function(Class::Integer), 0);
314    }
315
316    #[test]
317    fn a_value_read_after_the_loop_costs_a_register_everywhere_inside_it() {
318        // Which is the whole reason loop invariant motion asks this question before hoisting.
319        let (mut func, blocks) = blank(3);
320        let mut build = Builder::new(&mut func, blocks[0]);
321        let kept = build.iconst(I32, 7);
322        let cond = build.iconst(Type::I1, 1);
323        build.jump(blocks[1], &[]);
324        let mut build = Builder::new(&mut func, blocks[1]);
325        build.br_if(cond, blocks[1], &[], blocks[2], &[]);
326        let mut build = Builder::new(&mut func, blocks[2]);
327        build.ret(&[kept]);
328
329        let (cfg, of) = pressure(&func);
330        let doms = Dominators::new(&cfg);
331        let loops = Loops::new(&cfg, &doms);
332        let id = loops.innermost(blocks[1]).expect("block 1 is a loop");
333        // The value and the condition both cross the loop, so the loop holds two.
334        assert_eq!(of.most_in_loop(&loops, id, Class::Integer), 2);
335        assert!(of.is_tight(&loops, id, Class::Integer, 4), "two of four, less a margin of two");
336        assert!(!of.is_tight(&loops, id, Class::Integer, 16), "there is room on a real machine");
337    }
338
339    #[test]
340    fn the_margin_is_what_stops_a_hoist_from_walking_up_to_the_edge() {
341        let (mut func, blocks) = blank(2);
342        let mut build = Builder::new(&mut func, blocks[0]);
343        let cond = build.iconst(Type::I1, 1);
344        build.jump(blocks[1], &[]);
345        let mut build = Builder::new(&mut func, blocks[1]);
346        build.br_if(cond, blocks[1], &[], blocks[0], &[]);
347
348        let (cfg, of) = pressure(&func);
349        let doms = Dominators::new(&cfg);
350        let loops = Loops::new(&cfg, &doms);
351        let id = loops.innermost(blocks[1]).expect("block 1 is a loop");
352        assert_eq!(of.most_in_loop(&loops, id, Class::Integer), 1);
353        // One value live, and a machine with three registers has two reserved, so one is already
354        // at the line. A machine with four is not.
355        assert!(of.is_tight(&loops, id, Class::Integer, 3));
356        assert!(!of.is_tight(&loops, id, Class::Integer, 4));
357        // A machine with fewer registers than the margin does not underflow into a huge number.
358        assert!(of.is_tight(&loops, id, Class::Integer, 1));
359    }
360
361    #[test]
362    fn a_block_control_never_reaches_needs_nothing() {
363        let (mut func, blocks) = blank(2);
364        let mut build = Builder::new(&mut func, blocks[0]);
365        let one = build.iconst(I32, 1);
366        build.ret(&[one]);
367        let mut build = Builder::new(&mut func, blocks[1]);
368        let two = build.iconst(I32, 2);
369        build.ret(&[two]);
370
371        let (cfg, of) = pressure(&func);
372        assert!(!cfg.reaches(blocks[1]));
373        assert_eq!(of.most_in_block(blocks[1], Class::Integer), 0);
374        assert_eq!(of.arriving_at(blocks[1], Class::Integer), 0);
375    }
376
377    #[test]
378    fn what_arrives_at_a_block_is_never_more_than_the_block_ever_holds() {
379        let (mut func, blocks) = blank(3);
380        let mut build = Builder::new(&mut func, blocks[0]);
381        let kept = build.iconst(I32, 7);
382        let cond = build.iconst(Type::I1, 1);
383        build.br_if(cond, blocks[1], &[], blocks[2], &[]);
384        let mut build = Builder::new(&mut func, blocks[1]);
385        build.ret(&[kept]);
386        let mut build = Builder::new(&mut func, blocks[2]);
387        build.ret(&[]);
388
389        let (cfg, of) = pressure(&func);
390        assert!(of.problems(&cfg).is_empty());
391        for block in cfg.reverse_postorder() {
392            for class in Class::ALL {
393                assert!(of.arriving_at(block, class) <= of.most_in_block(block, class));
394                assert!(of.most_in_block(block, class) <= of.most_in_function(class));
395            }
396        }
397        assert_eq!(of.arriving_at(blocks[1], Class::Integer), 1, "the value it returns");
398        assert_eq!(of.arriving_at(blocks[2], Class::Integer), 0, "this arm reads nothing");
399    }
400
401    #[test]
402    fn every_class_names_itself_and_there_are_only_the_two() {
403        assert_eq!(Class::ALL.len(), Class::COUNT);
404        for class in Class::ALL {
405            assert!(!class.as_str().is_empty());
406            assert_eq!(class.to_string(), class.as_str());
407        }
408        assert_ne!(Class::Integer, Class::Float);
409    }
410}