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    /// Puts it in, and answers whether it was not already there.
66    fn insert(&mut self, value: Value) -> bool {
67        let at = value.index();
68        let word = &mut self.words[at / 64];
69        let bit = 1 << (at % 64);
70        let had = *word & bit != 0;
71        *word |= bit;
72        !had
73    }
74
75    /// Takes it out, and answers whether it was there.
76    fn remove(&mut self, value: Value) -> bool {
77        let at = value.index();
78        let word = &mut self.words[at / 64];
79        let bit = 1 << (at % 64);
80        let had = *word & bit != 0;
81        *word &= !bit;
82        had
83    }
84
85    /// Adds everything in the other, and answers whether that changed anything.
86    fn union_with(&mut self, other: &Self) -> bool {
87        let mut changed = false;
88        for (mine, theirs) in self.words.iter_mut().zip(&other.words) {
89            let before = *mine;
90            *mine |= theirs;
91            changed |= *mine != before;
92        }
93        changed
94    }
95
96    fn len(&self) -> usize {
97        self.words.iter().map(|word| word.count_ones() as usize).sum()
98    }
99
100    /// Them, in order.
101    ///
102    /// The empty words are skipped and the set bits of the rest are taken one at a time rather than
103    /// by testing all sixty four. A set has a word per sixty four values in the whole function, so
104    /// testing every bit of every word costs the size of the function every time somebody asks what
105    /// is live somewhere, whatever the answer turns out to be, and on a function of a hundred and
106    /// ninety thousand instructions that was most of an optimized compile. tamnd/rucc#1015.
107    fn iter(&self) -> impl Iterator<Item = Value> + use<'_> {
108        self.words.iter().enumerate().filter(|&(_, &word)| word != 0).flat_map(|(at, &word)| {
109            Bits(word).map(move |bit| Value::new((at * 64 + bit as usize) as u32))
110        })
111    }
112}
113
114/// The set bits of one word, lowest first.
115///
116/// `trailing_zeros` finds the next one and clearing the lowest set bit moves past it, so the work
117/// is one step per bit that is there rather than one per bit there could be.
118struct Bits(u64);
119
120impl Iterator for Bits {
121    type Item = u32;
122
123    fn next(&mut self) -> Option<u32> {
124        if self.0 == 0 {
125            return None;
126        }
127        let bit = self.0.trailing_zeros();
128        self.0 &= self.0 - 1;
129        Some(bit)
130    }
131}
132
133/// What is live at the edges of every block.
134///
135/// Per block rather than per instruction, because the sets inside a block are recoverable from the
136/// live-out by walking the block backwards and nothing wants to pay for storing them.
137/// [`Liveness::through`] is that walk, and the pressure model is its first caller.
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct Liveness {
140    live_in: Vec<Set>,
141    live_out: Vec<Set>,
142}
143
144impl Liveness {
145    /// Works out what is live where.
146    #[must_use]
147    pub fn of(func: &Func, cfg: &Cfg) -> Self {
148        let blocks = cfg.capacity();
149        let values = func.counts().values;
150        let empty = Set::with_room_for(values);
151        let mut live_in = vec![empty.clone(); blocks];
152        let mut live_out = vec![empty; blocks];
153
154        // Postorder, so a block is reached after the blocks it branches to wherever the graph
155        // allows one order to do that. A loop is what makes a second round necessary.
156        let order: Vec<Block> = cfg.postorder().to_vec();
157        let mut again = true;
158        while again {
159            again = false;
160            for &block in &order {
161                let mut out = Set::with_room_for(values);
162                for &successor in cfg.successors(block) {
163                    out.union_with(&live_in[successor.index()]);
164                }
165                let mut set = out.clone();
166                walk(func, block, &mut set, |_, _, _| {});
167                for &param in &func[block].params {
168                    set.remove(param);
169                }
170                again |= live_out[block.index()].union_with(&out);
171                again |= live_in[block.index()].union_with(&set);
172            }
173        }
174
175        Self { live_in, live_out }
176    }
177
178    /// What is live when control arrives at the block, which excludes its own parameters.
179    pub fn live_in(&self, block: Block) -> impl Iterator<Item = Value> + use<'_> {
180        self.live_in[block.index()].iter()
181    }
182
183    /// What is live when control leaves it.
184    pub fn live_out(&self, block: Block) -> impl Iterator<Item = Value> + use<'_> {
185        self.live_out[block.index()].iter()
186    }
187
188    /// Whether that value is live on the way in.
189    #[must_use]
190    pub fn is_live_in(&self, block: Block, value: Value) -> bool {
191        self.live_in[block.index()].contains(value)
192    }
193
194    /// Whether that value is live on the way out.
195    #[must_use]
196    pub fn is_live_out(&self, block: Block, value: Value) -> bool {
197        self.live_out[block.index()].contains(value)
198    }
199
200    /// How many values are live on the way in.
201    #[must_use]
202    pub fn count_in(&self, block: Block) -> usize {
203        self.live_in[block.index()].len()
204    }
205
206    /// How many are live on the way out.
207    #[must_use]
208    pub fn count_out(&self, block: Block) -> usize {
209        self.live_out[block.index()].len()
210    }
211
212    /// Walks the block backwards from its live-out, calling `at` before each instruction with what
213    /// is live there.
214    ///
215    /// This is where the per instruction sets come from, for the callers that want them. The set
216    /// handed to `at` is what is live just before that instruction runs, so it holds the
217    /// instruction's operands and not its results.
218    pub fn through(&self, func: &Func, block: Block, mut at: impl FnMut(Inst, &LiveHere<'_>)) {
219        let mut set = self.live_out[block.index()].clone();
220        walk(func, block, &mut set, |inst, set, _| at(inst, &LiveHere { set }));
221    }
222
223    /// The same walk, reporting what each instruction changes rather than what is live.
224    ///
225    /// [`Liveness::through`] hands out the whole set at every instruction, and a caller that only
226    /// wants to count what is in it pays the size of the set per instruction. In a function of a
227    /// hundred and ninety thousand instructions the set is thousands of values wide and that is
228    /// quadratic. What actually changes at an instruction is its results and its operands, so a
229    /// caller keeping a running count can be handed those instead and stay linear.
230    /// tamnd/rucc#1015.
231    pub fn changes(&self, func: &Func, block: Block, mut at: impl FnMut(Inst, &Change)) {
232        let mut set = self.live_out[block.index()].clone();
233        walk(func, block, &mut set, |inst, _, change| at(inst, change));
234    }
235}
236
237/// What one instruction does to the live set, seen walking the block backwards.
238///
239/// Both lists hold each value once, because they record the bits that moved rather than the names
240/// the instruction wrote: a value an instruction names twice is one bit and arrives once.
241#[derive(Debug, Default)]
242pub struct Change {
243    /// Values the instruction defines, which are live after it and not before it.
244    pub gone: Vec<Value>,
245    /// Values it names, which are live before it and were not after it.
246    pub arrived: Vec<Value>,
247}
248
249/// What is live at one point inside a block.
250///
251/// A borrowed view rather than a set the caller keeps, because the walk reuses one set and handing
252/// out a copy per instruction is the whole cost of the walk.
253#[derive(Debug)]
254pub struct LiveHere<'a> {
255    set: &'a Set,
256}
257
258impl LiveHere<'_> {
259    /// Whether that value is live here.
260    #[must_use]
261    pub fn contains(&self, value: Value) -> bool {
262        self.set.contains(value)
263    }
264
265    /// How many values are live here.
266    #[must_use]
267    pub fn len(&self) -> usize {
268        self.set.len()
269    }
270
271    /// Whether nothing is.
272    #[must_use]
273    pub fn is_empty(&self) -> bool {
274        self.len() == 0
275    }
276
277    /// Them, in order.
278    pub fn iter(&self) -> impl Iterator<Item = Value> + use<'_> {
279        self.set.iter()
280    }
281}
282
283/// Walks one block backwards, taking out what each instruction defines and putting in what it
284/// uses, and calling `at` with the set as it stands before each instruction.
285///
286/// The order matters and is the reason this is one function rather than two loops at each caller.
287/// The results go out before the operands come in, so an instruction whose operand is also its
288/// result leaves the value live, which is what a use before a redefinition means.
289fn walk(func: &Func, block: Block, set: &mut Set, mut at: impl FnMut(Inst, &Set, &Change)) {
290    let mut change = Change::default();
291    for this in func.insts_backwards(block) {
292        change.gone.clear();
293        change.arrived.clear();
294        let data = &func[this];
295        for result in data.results() {
296            if set.remove(result) {
297                change.gone.push(result);
298            }
299        }
300        for &arg in &func[data.args] {
301            if set.insert(arg) {
302                change.arrived.push(arg);
303            }
304        }
305        // A branch's arguments are used by the branch, in the block holding it, which is the whole
306        // reason block parameters are easier to be right about than phi nodes.
307        for call in func.successors(this) {
308            for &arg in &func[call.args] {
309                if set.insert(arg) {
310                    change.arrived.push(arg);
311                }
312            }
313        }
314        at(this, set, &change);
315    }
316}
317
318#[cfg(test)]
319mod tests {
320    use rucc_base::Interner;
321    use rucc_ir::{Block, Builder, Flags, Func, Opcode, Signature, Type};
322
323    use super::Liveness;
324    use crate::cfg::Cfg;
325
326    const I32: Type = Type::int(32);
327
328    fn blank(count: usize) -> (Func, Vec<Block>) {
329        let mut names = Interner::new();
330        let mut func = Func::new(names.intern("f"), Signature::new());
331        let blocks: Vec<Block> = (0..count).map(|_| func.create_block()).collect();
332        (func, blocks)
333    }
334
335    fn liveness(func: &Func) -> (Cfg, Liveness) {
336        let cfg = Cfg::new(func);
337        let live = Liveness::of(func, &cfg);
338        (cfg, live)
339    }
340
341    #[test]
342    fn a_value_made_and_read_in_one_block_never_crosses_an_edge() {
343        let (mut func, blocks) = blank(1);
344        let mut build = Builder::new(&mut func, blocks[0]);
345        let one = build.iconst(I32, 1);
346        let two = build.iconst(I32, 2);
347        let sum = build.binary(Opcode::Add, one, two, Flags::NONE);
348        build.ret(&[sum]);
349
350        let (_, live) = liveness(&func);
351        assert_eq!(live.count_in(blocks[0]), 0);
352        assert_eq!(live.count_out(blocks[0]), 0);
353    }
354
355    #[test]
356    fn a_value_read_in_a_later_block_is_live_on_the_edge_between_them() {
357        let (mut func, blocks) = blank(2);
358        let mut build = Builder::new(&mut func, blocks[0]);
359        let kept = build.iconst(I32, 7);
360        build.jump(blocks[1], &[]);
361        let mut build = Builder::new(&mut func, blocks[1]);
362        build.ret(&[kept]);
363
364        let (_, live) = liveness(&func);
365        assert!(live.is_live_out(blocks[0], kept), "it is read after the branch");
366        assert!(live.is_live_in(blocks[1], kept), "and it has to arrive there to be read");
367        assert!(!live.is_live_in(blocks[0], kept), "it does not exist before it is made");
368    }
369
370    #[test]
371    fn a_value_passed_on_the_branch_is_used_by_the_branch_and_not_by_the_block_it_arrives_at() {
372        // The whole reason block parameters are easier to be right about than phi nodes. The
373        // argument is live in the predecessor, and the parameter it becomes is defined by
374        // arriving, so it is not live-in of the block that holds it.
375        let (mut func, blocks) = blank(2);
376        let param = func.append_param(blocks[1], I32);
377        let mut build = Builder::new(&mut func, blocks[0]);
378        let sent = build.iconst(I32, 7);
379        build.jump(blocks[1], &[sent]);
380        let mut build = Builder::new(&mut func, blocks[1]);
381        build.ret(&[param]);
382
383        let (_, live) = liveness(&func);
384        // It is live at the branch and dead on the edge, which is the point. Live-out is what
385        // survives the edge, and what the argument becomes on the other side is the parameter.
386        let mut at_the_jump = false;
387        live.through(&func, blocks[0], |inst, here| {
388            if func[inst].opcode == Opcode::Jump {
389                at_the_jump = here.contains(sent);
390            }
391        });
392        assert!(at_the_jump, "the branch uses it");
393        assert!(!live.is_live_out(blocks[0], sent), "and it does not survive the edge");
394        assert!(!live.is_live_in(blocks[1], param), "a parameter is defined by arriving");
395        assert!(!live.is_live_in(blocks[1], sent), "nor does it arrive under its own name");
396        assert_eq!(live.count_in(blocks[1]), 0);
397    }
398
399    #[test]
400    fn a_value_read_on_one_arm_only_is_live_on_that_arm_and_not_the_other() {
401        let (mut func, blocks) = blank(4);
402        let mut build = Builder::new(&mut func, blocks[0]);
403        let kept = build.iconst(I32, 7);
404        let cond = build.iconst(Type::I1, 1);
405        build.br_if(cond, blocks[1], &[], blocks[2], &[]);
406        let mut build = Builder::new(&mut func, blocks[1]);
407        build.jump(blocks[3], &[]);
408        let mut build = Builder::new(&mut func, blocks[2]);
409        build.ret(&[kept]);
410        let mut build = Builder::new(&mut func, blocks[3]);
411        build.ret(&[]);
412
413        let (_, live) = liveness(&func);
414        assert!(live.is_live_out(blocks[0], kept), "one arm reads it, so it survives the branch");
415        assert!(live.is_live_in(blocks[2], kept));
416        assert!(!live.is_live_in(blocks[1], kept), "this arm never mentions it");
417    }
418
419    #[test]
420    fn a_value_read_after_the_loop_stays_live_all_the_way_round_it() {
421        // Block 0 makes it, block 1 is the loop and does not touch it, block 2 reads it. The
422        // fixpoint is what gets this right: one backwards pass over the blocks in postorder puts
423        // it live-in of the loop, and the second round is what carries that back to the latch.
424        let (mut func, blocks) = blank(3);
425        let mut build = Builder::new(&mut func, blocks[0]);
426        let kept = build.iconst(I32, 7);
427        let cond = build.iconst(Type::I1, 1);
428        build.jump(blocks[1], &[]);
429        let mut build = Builder::new(&mut func, blocks[1]);
430        build.br_if(cond, blocks[1], &[], blocks[2], &[]);
431        let mut build = Builder::new(&mut func, blocks[2]);
432        build.ret(&[kept]);
433
434        let (_, live) = liveness(&func);
435        assert!(live.is_live_in(blocks[1], kept), "it has to survive the loop to be read after it");
436        assert!(live.is_live_out(blocks[1], kept), "including round the back edge");
437        assert!(live.is_live_in(blocks[2], kept));
438    }
439
440    #[test]
441    fn nothing_is_live_in_a_block_control_never_reaches() {
442        let (mut func, blocks) = blank(2);
443        let mut build = Builder::new(&mut func, blocks[0]);
444        let kept = build.iconst(I32, 7);
445        build.ret(&[kept]);
446        let mut build = Builder::new(&mut func, blocks[1]);
447        build.ret(&[]);
448
449        let (cfg, live) = liveness(&func);
450        assert!(!cfg.reaches(blocks[1]));
451        assert_eq!(live.count_in(blocks[1]), 0);
452        assert_eq!(live.count_out(blocks[1]), 0);
453    }
454
455    #[test]
456    fn the_walk_through_a_block_says_what_is_live_before_each_instruction() {
457        let (mut func, blocks) = blank(2);
458        let mut build = Builder::new(&mut func, blocks[0]);
459        let one = build.iconst(I32, 1);
460        let two = build.iconst(I32, 2);
461        let sum = build.binary(Opcode::Add, one, two, Flags::NONE);
462        let jump = build.jump(blocks[1], &[sum]);
463        let param = func.append_param(blocks[1], I32);
464        let mut build = Builder::new(&mut func, blocks[1]);
465        build.ret(&[param]);
466
467        let (_, live) = liveness(&func);
468        let mut counts = Vec::new();
469        live.through(&func, blocks[0], |inst, here| counts.push((inst, here.len())));
470        // Backwards: before the jump only the sum is live, before the add both operands are,
471        // before the second constant only the first is, and before the first nothing is.
472        assert_eq!(counts.len(), 4);
473        assert_eq!(counts[0], (jump, 1));
474        assert_eq!(counts[1].1, 2, "the add's two operands");
475        assert_eq!(counts[2].1, 1);
476        assert_eq!(counts[3].1, 0);
477        assert!(counts[0].1 <= counts[1].1, "the sum replaces the two it was made from");
478    }
479
480    #[test]
481    fn a_value_that_is_its_own_operand_stays_live_across_the_instruction_that_redefines_nothing() {
482        // Results go out before operands come in, which is what makes a use of a value the
483        // instruction also produces read as a use rather than as a definition.
484        let (mut func, blocks) = blank(1);
485        let mut build = Builder::new(&mut func, blocks[0]);
486        let start = build.iconst(I32, 1);
487        let doubled = build.binary(Opcode::Add, start, start, Flags::NONE);
488        build.ret(&[doubled]);
489
490        let (_, live) = liveness(&func);
491        let mut most = 0;
492        live.through(&func, blocks[0], |_, here| most = most.max(here.len()));
493        assert_eq!(most, 1, "one value used twice is one value");
494    }
495}