Skip to main content

rucc_opt/
live.rs

1//! Which values are live where, which is what the pressure model counts and what a scheduler
2//! has to know before it moves anything.
3//!
4//! Design: section 40.6 of `spec/optimizer/40-cost-models.md`, which needs this before it can
5//! count anything, and document 39.5, which is where the count becomes meaningful.
6//!
7//! # Live means used later, and in this IR that is exact
8//!
9//! A value is live at a point when some path from that point reaches a use of it. The IR is in
10//! SSA with block parameters rather than phi nodes, so the awkward case other compilers have here
11//! does not arise: a phi's operand is used in the predecessor and not in the block holding the
12//! phi, which every liveness implementation over phi nodes has to special case and half of them
13//! get wrong. Here the argument travels on the branch, the branch is an instruction in the
14//! predecessor, and the ordinary rule that an instruction uses its operands already says the right
15//! thing.
16//!
17//! # The fixpoint
18//!
19//! Backwards, over the reverse of reverse postorder, until nothing changes. A block's live-in is
20//! what is live at its first instruction with its own parameters taken out, since a parameter is
21//! defined by arriving. Its live-out is the union of the live-ins of its successors. Postorder
22//! means a block is visited after the blocks it branches to wherever the graph allows, so the
23//! usual function settles in one round and a loop costs one more.
24//!
25//! A value passed as a branch argument is live at the branch and not on the edge, because what
26//! crosses the edge is the parameter it becomes. [`Liveness::through`] is where a caller sees it,
27//! and it is the walk the pressure model counts along, so the argument is counted where it is
28//! actually held.
29//!
30//! # What is not counted
31//!
32//! Values of type `mem` are the memory dependence chain and are not data. They are live in the
33//! same sense as anything else and [`Liveness`] reports them, because a pass asking whether a
34//! store is still needed wants them. The pressure model is what drops them, because memory is not
35//! held in a register, and that decision belongs where the registers are being counted rather than
36//! here.
37
38use rucc_ir::{Block, Func, Inst, Value};
39
40use crate::cfg::Cfg;
41
42/// A dense set of values.
43///
44/// One bit per value rather than a hash set, because the fixpoint unions one of these per edge
45/// per round and a union of two bitmaps is a loop over words.
46#[derive(Debug, Clone, PartialEq, Eq)]
47struct Set {
48    words: Vec<u64>,
49}
50
51impl Set {
52    /// An empty set with room for that many values.
53    fn with_room_for(values: usize) -> Self {
54        Self { words: vec![0; values.div_ceil(64)] }
55    }
56
57    fn contains(&self, value: Value) -> bool {
58        let at = value.index();
59        match self.words.get(at / 64) {
60            Some(word) => word & (1 << (at % 64)) != 0,
61            None => false,
62        }
63    }
64
65    fn insert(&mut self, value: Value) {
66        let at = value.index();
67        self.words[at / 64] |= 1 << (at % 64);
68    }
69
70    fn remove(&mut self, value: Value) {
71        let at = value.index();
72        self.words[at / 64] &= !(1 << (at % 64));
73    }
74
75    /// Adds everything in the other, and answers whether that changed anything.
76    fn union_with(&mut self, other: &Self) -> bool {
77        let mut changed = false;
78        for (mine, theirs) in self.words.iter_mut().zip(&other.words) {
79            let before = *mine;
80            *mine |= theirs;
81            changed |= *mine != before;
82        }
83        changed
84    }
85
86    fn len(&self) -> usize {
87        self.words.iter().map(|word| word.count_ones() as usize).sum()
88    }
89
90    fn iter(&self) -> impl Iterator<Item = Value> + use<'_> {
91        self.words.iter().enumerate().flat_map(|(at, &word)| {
92            (0..64)
93                .filter(move |bit| word & (1 << bit) != 0)
94                .map(move |bit| Value::new((at * 64 + bit) as u32))
95        })
96    }
97}
98
99/// What is live at the edges of every block.
100///
101/// Per block rather than per instruction, because the sets inside a block are recoverable from the
102/// live-out by walking the block backwards and nothing wants to pay for storing them.
103/// [`Liveness::through`] is that walk, and the pressure model is its first caller.
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct Liveness {
106    live_in: Vec<Set>,
107    live_out: Vec<Set>,
108}
109
110impl Liveness {
111    /// Works out what is live where.
112    #[must_use]
113    pub fn of(func: &Func, cfg: &Cfg) -> Self {
114        let blocks = cfg.capacity();
115        let values = func.counts().values;
116        let empty = Set::with_room_for(values);
117        let mut live_in = vec![empty.clone(); blocks];
118        let mut live_out = vec![empty; blocks];
119
120        // Postorder, so a block is reached after the blocks it branches to wherever the graph
121        // allows one order to do that. A loop is what makes a second round necessary.
122        let order: Vec<Block> = cfg.postorder().to_vec();
123        let mut again = true;
124        while again {
125            again = false;
126            for &block in &order {
127                let mut out = Set::with_room_for(values);
128                for &successor in cfg.successors(block) {
129                    out.union_with(&live_in[successor.index()]);
130                }
131                let mut set = out.clone();
132                walk(func, block, &mut set, |_, _| {});
133                for &param in &func[block].params {
134                    set.remove(param);
135                }
136                again |= live_out[block.index()].union_with(&out);
137                again |= live_in[block.index()].union_with(&set);
138            }
139        }
140
141        Self { live_in, live_out }
142    }
143
144    /// What is live when control arrives at the block, which excludes its own parameters.
145    pub fn live_in(&self, block: Block) -> impl Iterator<Item = Value> + use<'_> {
146        self.live_in[block.index()].iter()
147    }
148
149    /// What is live when control leaves it.
150    pub fn live_out(&self, block: Block) -> impl Iterator<Item = Value> + use<'_> {
151        self.live_out[block.index()].iter()
152    }
153
154    /// Whether that value is live on the way in.
155    #[must_use]
156    pub fn is_live_in(&self, block: Block, value: Value) -> bool {
157        self.live_in[block.index()].contains(value)
158    }
159
160    /// Whether that value is live on the way out.
161    #[must_use]
162    pub fn is_live_out(&self, block: Block, value: Value) -> bool {
163        self.live_out[block.index()].contains(value)
164    }
165
166    /// How many values are live on the way in.
167    #[must_use]
168    pub fn count_in(&self, block: Block) -> usize {
169        self.live_in[block.index()].len()
170    }
171
172    /// How many are live on the way out.
173    #[must_use]
174    pub fn count_out(&self, block: Block) -> usize {
175        self.live_out[block.index()].len()
176    }
177
178    /// Walks the block backwards from its live-out, calling `at` before each instruction with what
179    /// is live there.
180    ///
181    /// This is where the per instruction sets come from, for the callers that want them. The set
182    /// handed to `at` is what is live just before that instruction runs, so it holds the
183    /// instruction's operands and not its results.
184    pub fn through(&self, func: &Func, block: Block, mut at: impl FnMut(Inst, &LiveHere<'_>)) {
185        let mut set = self.live_out[block.index()].clone();
186        walk(func, block, &mut set, |inst, set| at(inst, &LiveHere { set }));
187    }
188}
189
190/// What is live at one point inside a block.
191///
192/// A borrowed view rather than a set the caller keeps, because the walk reuses one set and handing
193/// out a copy per instruction is the whole cost of the walk.
194#[derive(Debug)]
195pub struct LiveHere<'a> {
196    set: &'a Set,
197}
198
199impl LiveHere<'_> {
200    /// Whether that value is live here.
201    #[must_use]
202    pub fn contains(&self, value: Value) -> bool {
203        self.set.contains(value)
204    }
205
206    /// How many values are live here.
207    #[must_use]
208    pub fn len(&self) -> usize {
209        self.set.len()
210    }
211
212    /// Whether nothing is.
213    #[must_use]
214    pub fn is_empty(&self) -> bool {
215        self.len() == 0
216    }
217
218    /// Them, in order.
219    pub fn iter(&self) -> impl Iterator<Item = Value> + use<'_> {
220        self.set.iter()
221    }
222}
223
224/// Walks one block backwards, taking out what each instruction defines and putting in what it
225/// uses, and calling `at` with the set as it stands before each instruction.
226///
227/// The order matters and is the reason this is one function rather than two loops at each caller.
228/// The results go out before the operands come in, so an instruction whose operand is also its
229/// result leaves the value live, which is what a use before a redefinition means.
230fn walk(func: &Func, block: Block, set: &mut Set, mut at: impl FnMut(Inst, &Set)) {
231    for this in func.insts_backwards(block) {
232        let data = &func[this];
233        for result in data.results() {
234            set.remove(result);
235        }
236        for &arg in &func[data.args] {
237            set.insert(arg);
238        }
239        // A branch's arguments are used by the branch, in the block holding it, which is the whole
240        // reason block parameters are easier to be right about than phi nodes.
241        for call in func.successors(this) {
242            for &arg in &func[call.args] {
243                set.insert(arg);
244            }
245        }
246        at(this, set);
247    }
248}
249
250#[cfg(test)]
251mod tests {
252    use rucc_base::Interner;
253    use rucc_ir::{Block, Builder, Flags, Func, Opcode, Signature, Type};
254
255    use super::Liveness;
256    use crate::cfg::Cfg;
257
258    const I32: Type = Type::int(32);
259
260    fn blank(count: usize) -> (Func, Vec<Block>) {
261        let mut names = Interner::new();
262        let mut func = Func::new(names.intern("f"), Signature::new());
263        let blocks: Vec<Block> = (0..count).map(|_| func.create_block()).collect();
264        (func, blocks)
265    }
266
267    fn liveness(func: &Func) -> (Cfg, Liveness) {
268        let cfg = Cfg::new(func);
269        let live = Liveness::of(func, &cfg);
270        (cfg, live)
271    }
272
273    #[test]
274    fn a_value_made_and_read_in_one_block_never_crosses_an_edge() {
275        let (mut func, blocks) = blank(1);
276        let mut build = Builder::new(&mut func, blocks[0]);
277        let one = build.iconst(I32, 1);
278        let two = build.iconst(I32, 2);
279        let sum = build.binary(Opcode::Add, one, two, Flags::NONE);
280        build.ret(&[sum]);
281
282        let (_, live) = liveness(&func);
283        assert_eq!(live.count_in(blocks[0]), 0);
284        assert_eq!(live.count_out(blocks[0]), 0);
285    }
286
287    #[test]
288    fn a_value_read_in_a_later_block_is_live_on_the_edge_between_them() {
289        let (mut func, blocks) = blank(2);
290        let mut build = Builder::new(&mut func, blocks[0]);
291        let kept = build.iconst(I32, 7);
292        build.jump(blocks[1], &[]);
293        let mut build = Builder::new(&mut func, blocks[1]);
294        build.ret(&[kept]);
295
296        let (_, live) = liveness(&func);
297        assert!(live.is_live_out(blocks[0], kept), "it is read after the branch");
298        assert!(live.is_live_in(blocks[1], kept), "and it has to arrive there to be read");
299        assert!(!live.is_live_in(blocks[0], kept), "it does not exist before it is made");
300    }
301
302    #[test]
303    fn a_value_passed_on_the_branch_is_used_by_the_branch_and_not_by_the_block_it_arrives_at() {
304        // The whole reason block parameters are easier to be right about than phi nodes. The
305        // argument is live in the predecessor, and the parameter it becomes is defined by
306        // arriving, so it is not live-in of the block that holds it.
307        let (mut func, blocks) = blank(2);
308        let param = func.append_param(blocks[1], I32);
309        let mut build = Builder::new(&mut func, blocks[0]);
310        let sent = build.iconst(I32, 7);
311        build.jump(blocks[1], &[sent]);
312        let mut build = Builder::new(&mut func, blocks[1]);
313        build.ret(&[param]);
314
315        let (_, live) = liveness(&func);
316        // It is live at the branch and dead on the edge, which is the point. Live-out is what
317        // survives the edge, and what the argument becomes on the other side is the parameter.
318        let mut at_the_jump = false;
319        live.through(&func, blocks[0], |inst, here| {
320            if func[inst].opcode == Opcode::Jump {
321                at_the_jump = here.contains(sent);
322            }
323        });
324        assert!(at_the_jump, "the branch uses it");
325        assert!(!live.is_live_out(blocks[0], sent), "and it does not survive the edge");
326        assert!(!live.is_live_in(blocks[1], param), "a parameter is defined by arriving");
327        assert!(!live.is_live_in(blocks[1], sent), "nor does it arrive under its own name");
328        assert_eq!(live.count_in(blocks[1]), 0);
329    }
330
331    #[test]
332    fn a_value_read_on_one_arm_only_is_live_on_that_arm_and_not_the_other() {
333        let (mut func, blocks) = blank(4);
334        let mut build = Builder::new(&mut func, blocks[0]);
335        let kept = build.iconst(I32, 7);
336        let cond = build.iconst(Type::I1, 1);
337        build.br_if(cond, blocks[1], &[], blocks[2], &[]);
338        let mut build = Builder::new(&mut func, blocks[1]);
339        build.jump(blocks[3], &[]);
340        let mut build = Builder::new(&mut func, blocks[2]);
341        build.ret(&[kept]);
342        let mut build = Builder::new(&mut func, blocks[3]);
343        build.ret(&[]);
344
345        let (_, live) = liveness(&func);
346        assert!(live.is_live_out(blocks[0], kept), "one arm reads it, so it survives the branch");
347        assert!(live.is_live_in(blocks[2], kept));
348        assert!(!live.is_live_in(blocks[1], kept), "this arm never mentions it");
349    }
350
351    #[test]
352    fn a_value_read_after_the_loop_stays_live_all_the_way_round_it() {
353        // Block 0 makes it, block 1 is the loop and does not touch it, block 2 reads it. The
354        // fixpoint is what gets this right: one backwards pass over the blocks in postorder puts
355        // it live-in of the loop, and the second round is what carries that back to the latch.
356        let (mut func, blocks) = blank(3);
357        let mut build = Builder::new(&mut func, blocks[0]);
358        let kept = build.iconst(I32, 7);
359        let cond = build.iconst(Type::I1, 1);
360        build.jump(blocks[1], &[]);
361        let mut build = Builder::new(&mut func, blocks[1]);
362        build.br_if(cond, blocks[1], &[], blocks[2], &[]);
363        let mut build = Builder::new(&mut func, blocks[2]);
364        build.ret(&[kept]);
365
366        let (_, live) = liveness(&func);
367        assert!(live.is_live_in(blocks[1], kept), "it has to survive the loop to be read after it");
368        assert!(live.is_live_out(blocks[1], kept), "including round the back edge");
369        assert!(live.is_live_in(blocks[2], kept));
370    }
371
372    #[test]
373    fn nothing_is_live_in_a_block_control_never_reaches() {
374        let (mut func, blocks) = blank(2);
375        let mut build = Builder::new(&mut func, blocks[0]);
376        let kept = build.iconst(I32, 7);
377        build.ret(&[kept]);
378        let mut build = Builder::new(&mut func, blocks[1]);
379        build.ret(&[]);
380
381        let (cfg, live) = liveness(&func);
382        assert!(!cfg.reaches(blocks[1]));
383        assert_eq!(live.count_in(blocks[1]), 0);
384        assert_eq!(live.count_out(blocks[1]), 0);
385    }
386
387    #[test]
388    fn the_walk_through_a_block_says_what_is_live_before_each_instruction() {
389        let (mut func, blocks) = blank(2);
390        let mut build = Builder::new(&mut func, blocks[0]);
391        let one = build.iconst(I32, 1);
392        let two = build.iconst(I32, 2);
393        let sum = build.binary(Opcode::Add, one, two, Flags::NONE);
394        let jump = build.jump(blocks[1], &[sum]);
395        let param = func.append_param(blocks[1], I32);
396        let mut build = Builder::new(&mut func, blocks[1]);
397        build.ret(&[param]);
398
399        let (_, live) = liveness(&func);
400        let mut counts = Vec::new();
401        live.through(&func, blocks[0], |inst, here| counts.push((inst, here.len())));
402        // Backwards: before the jump only the sum is live, before the add both operands are,
403        // before the second constant only the first is, and before the first nothing is.
404        assert_eq!(counts.len(), 4);
405        assert_eq!(counts[0], (jump, 1));
406        assert_eq!(counts[1].1, 2, "the add's two operands");
407        assert_eq!(counts[2].1, 1);
408        assert_eq!(counts[3].1, 0);
409        assert!(counts[0].1 <= counts[1].1, "the sum replaces the two it was made from");
410    }
411
412    #[test]
413    fn a_value_that_is_its_own_operand_stays_live_across_the_instruction_that_redefines_nothing() {
414        // Results go out before operands come in, which is what makes a use of a value the
415        // instruction also produces read as a use rather than as a definition.
416        let (mut func, blocks) = blank(1);
417        let mut build = Builder::new(&mut func, blocks[0]);
418        let start = build.iconst(I32, 1);
419        let doubled = build.binary(Opcode::Add, start, start, Flags::NONE);
420        build.ret(&[doubled]);
421
422        let (_, live) = liveness(&func);
423        let mut most = 0;
424        live.through(&func, blocks[0], |_, here| most = most.max(here.len()));
425        assert_eq!(most, 1, "one value used twice is one value");
426    }
427}