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//! What a block adds to the set passing through it and what it takes out are the same every round,
26//! so they are worked out once rather than by walking its instructions each time. A round only
27//! looks again at a block when the live-in of something it branches to changed in the round
28//! before, since otherwise it would get the same answer. A function of thirty thousand blocks and
29//! two thousand loops took seconds when every block was redone every round.
30//!
31//! A value passed as a branch argument is live at the branch and not on the edge, because what
32//! crosses the edge is the parameter it becomes. [`Liveness::through`] is where a caller sees it,
33//! and it is the walk the pressure model counts along, so the argument is counted where it is
34//! actually held.
35//!
36//! # What is not counted
37//!
38//! Values of type `mem` are the memory dependence chain and are not data. They are live in the
39//! same sense as anything else and [`Liveness`] reports them, because a pass asking whether a
40//! store is still needed wants them. The pressure model is what drops them, because memory is not
41//! held in a register, and that decision belongs where the registers are being counted rather than
42//! here.
43
44use std::cmp::Ordering;
45
46use rucc_ir::{Block, Func, Inst, Value};
47
48use crate::cfg::Cfg;
49
50/// A set of values, kept as the words of a bitmap that have something in them.
51///
52/// A bitmap because the fixpoint unions one of these per edge per round, and a union of two
53/// bitmaps is a loop over words. Only the words with a bit set are kept, because what is live at
54/// one place is a few runs of neighbouring values out of the whole function. On jtckdint's `main`,
55/// with 200000 values and 30000 blocks, a whole bitmap per block was 1.3 GB for the live-ins and
56/// live-outs together, and fewer than one word in a hundred had anything in it. Allocating that,
57/// clearing it and copying it round the fixpoint was most of what working out liveness cost.
58#[derive(Debug, Clone, Default, PartialEq, Eq)]
59struct Set {
60    /// Which word of the bitmap each is, and the word. In order and never zero, so two sets with
61    /// the same values in them are the same list.
62    words: Vec<(u32, u64)>,
63}
64
65impl Set {
66    /// Where the word holding that value is, or where it would go.
67    fn find(&self, value: Value) -> (Result<usize, usize>, u64) {
68        let at = value.index();
69        let word = u32::try_from(at / 64).expect("a value number fits in 32 bits");
70        (self.words.binary_search_by_key(&word, |&(word, _)| word), 1 << (at % 64))
71    }
72
73    fn contains(&self, value: Value) -> bool {
74        match self.find(value) {
75            (Ok(at), bit) => self.words[at].1 & bit != 0,
76            (Err(_), _) => false,
77        }
78    }
79
80    /// Puts it in, and answers whether it was not already there.
81    fn insert(&mut self, value: Value) -> bool {
82        match self.find(value) {
83            (Ok(at), bit) => {
84                let word = &mut self.words[at].1;
85                let had = *word & bit != 0;
86                *word |= bit;
87                !had
88            }
89            (Err(at), bit) => {
90                let word = u32::try_from(value.index() / 64).expect("checked by find");
91                self.words.insert(at, (word, bit));
92                true
93            }
94        }
95    }
96
97    /// Takes it out, and answers whether it was there.
98    fn remove(&mut self, value: Value) -> bool {
99        let (Ok(at), bit) = self.find(value) else {
100            return false;
101        };
102        let word = &mut self.words[at].1;
103        let had = *word & bit != 0;
104        *word &= !bit;
105        if *word == 0 {
106            self.words.remove(at);
107        }
108        had
109    }
110
111    /// Adds everything in the other.
112    fn union_with(&mut self, other: &Self) {
113        if other.words.is_empty() {
114            return;
115        }
116        if self.words.is_empty() {
117            self.words.clone_from(&other.words);
118            return;
119        }
120        let (mine, theirs) = (&self.words, &other.words);
121        let mut both = Vec::with_capacity(mine.len() + theirs.len());
122        let (mut left, mut right) = (0, 0);
123        while left < mine.len() && right < theirs.len() {
124            let ((at, word), (other_at, other_word)) = (mine[left], theirs[right]);
125            match at.cmp(&other_at) {
126                Ordering::Less => {
127                    both.push((at, word));
128                    left += 1;
129                }
130                Ordering::Greater => {
131                    both.push((other_at, other_word));
132                    right += 1;
133                }
134                Ordering::Equal => {
135                    both.push((at, word | other_word));
136                    left += 1;
137                    right += 1;
138                }
139            }
140        }
141        both.extend_from_slice(&mine[left..]);
142        both.extend_from_slice(&theirs[right..]);
143        self.words = both;
144    }
145
146    /// Takes everything out.
147    fn clear(&mut self) {
148        self.words.clear();
149    }
150
151    fn len(&self) -> usize {
152        self.words.iter().map(|&(_, word)| word.count_ones() as usize).sum()
153    }
154
155    /// Them, in order.
156    ///
157    /// The set bits of each word are taken one at a time rather than by testing all sixty four.
158    fn iter(&self) -> impl Iterator<Item = Value> + use<'_> {
159        self.words
160            .iter()
161            .flat_map(|&(at, word)| Bits(word).map(move |bit| Value::new(at * 64 + bit)))
162    }
163}
164
165/// The set bits of one word, lowest first.
166///
167/// `trailing_zeros` finds the next one and clearing the lowest set bit moves past it, so the work
168/// is one step per bit that is there rather than one per bit there could be.
169struct Bits(u64);
170
171impl Iterator for Bits {
172    type Item = u32;
173
174    fn next(&mut self) -> Option<u32> {
175        if self.0 == 0 {
176            return None;
177        }
178        let bit = self.0.trailing_zeros();
179        self.0 &= self.0 - 1;
180        Some(bit)
181    }
182}
183
184/// What is live at the edges of every block.
185///
186/// Per block rather than per instruction, because the sets inside a block are recoverable from the
187/// live-out by walking the block backwards and nothing wants to pay for storing them.
188/// [`Liveness::through`] is that walk, and the pressure model is its first caller.
189#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct Liveness {
191    live_in: Vec<Set>,
192    live_out: Vec<Set>,
193}
194
195impl Liveness {
196    /// Works out what is live where.
197    #[must_use]
198    pub fn of(func: &Func, cfg: &Cfg) -> Self {
199        let blocks = cfg.capacity();
200        let mut live_in = vec![Set::default(); blocks];
201        let mut live_out = vec![Set::default(); blocks];
202
203        // What each block reads before it writes, and what it writes, parameters included. Live in
204        // is then live out with the second taken out and the first put in, which is what walking
205        // the block backwards gives without walking it.
206        let order: Vec<Block> = cfg.postorder().to_vec();
207        let mut reads: Vec<Vec<Value>> = vec![Vec::new(); blocks];
208        let mut writes: Vec<Vec<Value>> = vec![Vec::new(); blocks];
209        let mut defined = Set::default();
210        let mut read = Set::default();
211        for &block in &order {
212            let at = block.index();
213            for &param in &func[block].params {
214                defined.insert(param);
215                writes[at].push(param);
216            }
217            for inst in func.insts(block) {
218                let data = &func[inst];
219                let branches = func.successors(inst).flat_map(|call| &func[call.args]);
220                for &arg in func[data.args].iter().chain(branches) {
221                    if !defined.contains(arg) && read.insert(arg) {
222                        reads[at].push(arg);
223                    }
224                }
225                for result in data.results() {
226                    defined.insert(result);
227                    writes[at].push(result);
228                }
229            }
230            for &value in &writes[at] {
231                defined.remove(value);
232            }
233            for &value in &reads[at] {
234                read.remove(value);
235            }
236        }
237
238        // Postorder, so a block is reached after the blocks it branches to wherever the graph
239        // allows one order to do that. A loop is what makes a second round necessary, and the
240        // second round is only the blocks something changed under.
241        let mut stale = vec![true; blocks];
242        let mut set = Set::default();
243        let mut again = true;
244        while again {
245            again = false;
246            for &block in &order {
247                let at = block.index();
248                if !std::mem::take(&mut stale[at]) {
249                    continue;
250                }
251                set.clear();
252                for &successor in cfg.successors(block) {
253                    set.union_with(&live_in[successor.index()]);
254                }
255                live_out[at].clone_from(&set);
256                for &value in &writes[at] {
257                    set.remove(value);
258                }
259                for &value in &reads[at] {
260                    set.insert(value);
261                }
262                if live_in[at] != set {
263                    live_in[at].clone_from(&set);
264                    for &pred in cfg.predecessors(block) {
265                        stale[pred.index()] = true;
266                        again = true;
267                    }
268                }
269            }
270        }
271
272        Self { live_in, live_out }
273    }
274
275    /// What is live when control arrives at the block, which excludes its own parameters.
276    pub fn live_in(&self, block: Block) -> impl Iterator<Item = Value> + use<'_> {
277        self.live_in[block.index()].iter()
278    }
279
280    /// What is live when control leaves it.
281    pub fn live_out(&self, block: Block) -> impl Iterator<Item = Value> + use<'_> {
282        self.live_out[block.index()].iter()
283    }
284
285    /// Whether that value is live on the way in.
286    #[must_use]
287    pub fn is_live_in(&self, block: Block, value: Value) -> bool {
288        self.live_in[block.index()].contains(value)
289    }
290
291    /// Whether that value is live on the way out.
292    #[must_use]
293    pub fn is_live_out(&self, block: Block, value: Value) -> bool {
294        self.live_out[block.index()].contains(value)
295    }
296
297    /// How many values are live on the way in.
298    #[must_use]
299    pub fn count_in(&self, block: Block) -> usize {
300        self.live_in[block.index()].len()
301    }
302
303    /// How many are live on the way out.
304    #[must_use]
305    pub fn count_out(&self, block: Block) -> usize {
306        self.live_out[block.index()].len()
307    }
308
309    /// Walks the block backwards from its live-out, calling `at` before each instruction with what
310    /// is live there.
311    ///
312    /// This is where the per instruction sets come from, for the callers that want them. The set
313    /// handed to `at` is what is live just before that instruction runs, so it holds the
314    /// instruction's operands and not its results.
315    pub fn through(&self, func: &Func, block: Block, mut at: impl FnMut(Inst, &LiveHere<'_>)) {
316        let mut set = self.live_out[block.index()].clone();
317        walk(func, block, &mut set, |inst, set, _| at(inst, &LiveHere { set }));
318    }
319
320    /// The same walk, reporting what each instruction changes rather than what is live.
321    ///
322    /// [`Liveness::through`] hands out the whole set at every instruction, and a caller that only
323    /// wants to count what is in it pays the size of the set per instruction. In a function of a
324    /// hundred and ninety thousand instructions the set is thousands of values wide and that is
325    /// quadratic. What actually changes at an instruction is its results and its operands, so a
326    /// caller keeping a running count can be handed those instead and stay linear.
327    /// tamnd/rucc#1015.
328    pub fn changes(&self, func: &Func, block: Block, mut at: impl FnMut(Inst, &Change)) {
329        let mut set = self.live_out[block.index()].clone();
330        walk(func, block, &mut set, |inst, _, change| at(inst, change));
331    }
332}
333
334/// What one instruction does to the live set, seen walking the block backwards.
335///
336/// Both lists hold each value once, because they record the bits that moved rather than the names
337/// the instruction wrote: a value an instruction names twice is one bit and arrives once.
338#[derive(Debug, Default)]
339pub struct Change {
340    /// Values the instruction defines, which are live after it and not before it.
341    pub gone: Vec<Value>,
342    /// Values it names, which are live before it and were not after it.
343    pub arrived: Vec<Value>,
344}
345
346/// What is live at one point inside a block.
347///
348/// A borrowed view rather than a set the caller keeps, because the walk reuses one set and handing
349/// out a copy per instruction is the whole cost of the walk.
350#[derive(Debug)]
351pub struct LiveHere<'a> {
352    set: &'a Set,
353}
354
355impl LiveHere<'_> {
356    /// Whether that value is live here.
357    #[must_use]
358    pub fn contains(&self, value: Value) -> bool {
359        self.set.contains(value)
360    }
361
362    /// How many values are live here.
363    #[must_use]
364    pub fn len(&self) -> usize {
365        self.set.len()
366    }
367
368    /// Whether nothing is.
369    #[must_use]
370    pub fn is_empty(&self) -> bool {
371        self.len() == 0
372    }
373
374    /// Them, in order.
375    pub fn iter(&self) -> impl Iterator<Item = Value> + use<'_> {
376        self.set.iter()
377    }
378}
379
380/// Walks one block backwards, taking out what each instruction defines and putting in what it
381/// uses, and calling `at` with the set as it stands before each instruction.
382///
383/// The order matters and is the reason this is one function rather than two loops at each caller.
384/// The results go out before the operands come in, so an instruction whose operand is also its
385/// result leaves the value live, which is what a use before a redefinition means.
386fn walk(func: &Func, block: Block, set: &mut Set, mut at: impl FnMut(Inst, &Set, &Change)) {
387    let mut change = Change::default();
388    for this in func.insts_backwards(block) {
389        change.gone.clear();
390        change.arrived.clear();
391        let data = &func[this];
392        for result in data.results() {
393            if set.remove(result) {
394                change.gone.push(result);
395            }
396        }
397        for &arg in &func[data.args] {
398            if set.insert(arg) {
399                change.arrived.push(arg);
400            }
401        }
402        // A branch's arguments are used by the branch, in the block holding it, which is the whole
403        // reason block parameters are easier to be right about than phi nodes.
404        for call in func.successors(this) {
405            for &arg in &func[call.args] {
406                if set.insert(arg) {
407                    change.arrived.push(arg);
408                }
409            }
410        }
411        at(this, set, &change);
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use rucc_base::Interner;
418    use rucc_ir::{Block, Builder, Flags, Func, Opcode, Signature, Type, Value};
419
420    use super::{Liveness, Set};
421    use crate::cfg::Cfg;
422
423    const I32: Type = Type::int(32);
424
425    fn blank(count: usize) -> (Func, Vec<Block>) {
426        let mut names = Interner::new();
427        let mut func = Func::new(names.intern("f"), Signature::new());
428        let blocks: Vec<Block> = (0..count).map(|_| func.create_block()).collect();
429        (func, blocks)
430    }
431
432    fn liveness(func: &Func) -> (Cfg, Liveness) {
433        let cfg = Cfg::new(func);
434        let live = Liveness::of(func, &cfg);
435        (cfg, live)
436    }
437
438    #[test]
439    fn a_set_keeps_only_the_words_with_something_in_them() {
440        let value = Value::new;
441        let mut first = Set::default();
442        assert!(first.insert(value(3)));
443        assert!(first.insert(value(200)));
444        assert!(!first.insert(value(3)), "it was already there");
445        assert!(first.insert(value(70)));
446        assert!(first.remove(value(70)));
447        assert!(!first.remove(value(70)), "it went the first time");
448        assert!(!first.remove(value(5000)), "nothing was ever near it");
449        assert_eq!(first.words.len(), 2, "the word 70 was in went with it");
450
451        let mut second = Set::default();
452        second.insert(value(64));
453        second.insert(value(200));
454        second.insert(value(201));
455        second.insert(value(9000));
456        first.union_with(&second);
457        let all: Vec<u32> = first.iter().map(|value| value.raw()).collect();
458        assert_eq!(all, [3, 64, 200, 201, 9000]);
459        assert_eq!(first.len(), 5);
460        assert!(first.contains(value(201)) && !first.contains(value(202)));
461
462        // Put in the other way round and taken out again, it is the same list, which is what the
463        // fixpoint compares to know it is done.
464        let mut again = Set::default();
465        for number in [9000, 201, 5, 200, 64, 3] {
466            again.insert(value(number));
467        }
468        again.remove(value(5));
469        assert_eq!(again, first);
470    }
471
472    #[test]
473    fn a_value_made_and_read_in_one_block_never_crosses_an_edge() {
474        let (mut func, blocks) = blank(1);
475        let mut build = Builder::new(&mut func, blocks[0]);
476        let one = build.iconst(I32, 1);
477        let two = build.iconst(I32, 2);
478        let sum = build.binary(Opcode::Add, one, two, Flags::NONE);
479        build.ret(&[sum]);
480
481        let (_, live) = liveness(&func);
482        assert_eq!(live.count_in(blocks[0]), 0);
483        assert_eq!(live.count_out(blocks[0]), 0);
484    }
485
486    #[test]
487    fn a_value_read_in_a_later_block_is_live_on_the_edge_between_them() {
488        let (mut func, blocks) = blank(2);
489        let mut build = Builder::new(&mut func, blocks[0]);
490        let kept = build.iconst(I32, 7);
491        build.jump(blocks[1], &[]);
492        let mut build = Builder::new(&mut func, blocks[1]);
493        build.ret(&[kept]);
494
495        let (_, live) = liveness(&func);
496        assert!(live.is_live_out(blocks[0], kept), "it is read after the branch");
497        assert!(live.is_live_in(blocks[1], kept), "and it has to arrive there to be read");
498        assert!(!live.is_live_in(blocks[0], kept), "it does not exist before it is made");
499    }
500
501    #[test]
502    fn a_value_passed_on_the_branch_is_used_by_the_branch_and_not_by_the_block_it_arrives_at() {
503        // The whole reason block parameters are easier to be right about than phi nodes. The
504        // argument is live in the predecessor, and the parameter it becomes is defined by
505        // arriving, so it is not live-in of the block that holds it.
506        let (mut func, blocks) = blank(2);
507        let param = func.append_param(blocks[1], I32);
508        let mut build = Builder::new(&mut func, blocks[0]);
509        let sent = build.iconst(I32, 7);
510        build.jump(blocks[1], &[sent]);
511        let mut build = Builder::new(&mut func, blocks[1]);
512        build.ret(&[param]);
513
514        let (_, live) = liveness(&func);
515        // It is live at the branch and dead on the edge, which is the point. Live-out is what
516        // survives the edge, and what the argument becomes on the other side is the parameter.
517        let mut at_the_jump = false;
518        live.through(&func, blocks[0], |inst, here| {
519            if func[inst].opcode == Opcode::Jump {
520                at_the_jump = here.contains(sent);
521            }
522        });
523        assert!(at_the_jump, "the branch uses it");
524        assert!(!live.is_live_out(blocks[0], sent), "and it does not survive the edge");
525        assert!(!live.is_live_in(blocks[1], param), "a parameter is defined by arriving");
526        assert!(!live.is_live_in(blocks[1], sent), "nor does it arrive under its own name");
527        assert_eq!(live.count_in(blocks[1]), 0);
528    }
529
530    #[test]
531    fn a_value_read_on_one_arm_only_is_live_on_that_arm_and_not_the_other() {
532        let (mut func, blocks) = blank(4);
533        let mut build = Builder::new(&mut func, blocks[0]);
534        let kept = build.iconst(I32, 7);
535        let cond = build.iconst(Type::I1, 1);
536        build.br_if(cond, blocks[1], &[], blocks[2], &[]);
537        let mut build = Builder::new(&mut func, blocks[1]);
538        build.jump(blocks[3], &[]);
539        let mut build = Builder::new(&mut func, blocks[2]);
540        build.ret(&[kept]);
541        let mut build = Builder::new(&mut func, blocks[3]);
542        build.ret(&[]);
543
544        let (_, live) = liveness(&func);
545        assert!(live.is_live_out(blocks[0], kept), "one arm reads it, so it survives the branch");
546        assert!(live.is_live_in(blocks[2], kept));
547        assert!(!live.is_live_in(blocks[1], kept), "this arm never mentions it");
548    }
549
550    #[test]
551    fn a_value_read_after_the_loop_stays_live_all_the_way_round_it() {
552        // Block 0 makes it, block 1 is the loop and does not touch it, block 2 reads it. The
553        // fixpoint is what gets this right: one backwards pass over the blocks in postorder puts
554        // it live-in of the loop, and the second round is what carries that back to the latch.
555        let (mut func, blocks) = blank(3);
556        let mut build = Builder::new(&mut func, blocks[0]);
557        let kept = build.iconst(I32, 7);
558        let cond = build.iconst(Type::I1, 1);
559        build.jump(blocks[1], &[]);
560        let mut build = Builder::new(&mut func, blocks[1]);
561        build.br_if(cond, blocks[1], &[], blocks[2], &[]);
562        let mut build = Builder::new(&mut func, blocks[2]);
563        build.ret(&[kept]);
564
565        let (_, live) = liveness(&func);
566        assert!(live.is_live_in(blocks[1], kept), "it has to survive the loop to be read after it");
567        assert!(live.is_live_out(blocks[1], kept), "including round the back edge");
568        assert!(live.is_live_in(blocks[2], kept));
569    }
570
571    #[test]
572    fn nothing_is_live_in_a_block_control_never_reaches() {
573        let (mut func, blocks) = blank(2);
574        let mut build = Builder::new(&mut func, blocks[0]);
575        let kept = build.iconst(I32, 7);
576        build.ret(&[kept]);
577        let mut build = Builder::new(&mut func, blocks[1]);
578        build.ret(&[]);
579
580        let (cfg, live) = liveness(&func);
581        assert!(!cfg.reaches(blocks[1]));
582        assert_eq!(live.count_in(blocks[1]), 0);
583        assert_eq!(live.count_out(blocks[1]), 0);
584    }
585
586    #[test]
587    fn the_walk_through_a_block_says_what_is_live_before_each_instruction() {
588        let (mut func, blocks) = blank(2);
589        let mut build = Builder::new(&mut func, blocks[0]);
590        let one = build.iconst(I32, 1);
591        let two = build.iconst(I32, 2);
592        let sum = build.binary(Opcode::Add, one, two, Flags::NONE);
593        let jump = build.jump(blocks[1], &[sum]);
594        let param = func.append_param(blocks[1], I32);
595        let mut build = Builder::new(&mut func, blocks[1]);
596        build.ret(&[param]);
597
598        let (_, live) = liveness(&func);
599        let mut counts = Vec::new();
600        live.through(&func, blocks[0], |inst, here| counts.push((inst, here.len())));
601        // Backwards: before the jump only the sum is live, before the add both operands are,
602        // before the second constant only the first is, and before the first nothing is.
603        assert_eq!(counts.len(), 4);
604        assert_eq!(counts[0], (jump, 1));
605        assert_eq!(counts[1].1, 2, "the add's two operands");
606        assert_eq!(counts[2].1, 1);
607        assert_eq!(counts[3].1, 0);
608        assert!(counts[0].1 <= counts[1].1, "the sum replaces the two it was made from");
609    }
610
611    #[test]
612    fn a_value_that_is_its_own_operand_stays_live_across_the_instruction_that_redefines_nothing() {
613        // Results go out before operands come in, which is what makes a use of a value the
614        // instruction also produces read as a use rather than as a definition.
615        let (mut func, blocks) = blank(1);
616        let mut build = Builder::new(&mut func, blocks[0]);
617        let start = build.iconst(I32, 1);
618        let doubled = build.binary(Opcode::Add, start, start, Flags::NONE);
619        build.ret(&[doubled]);
620
621        let (_, live) = liveness(&func);
622        let mut most = 0;
623        live.through(&func, blocks[0], |_, here| most = most.max(here.len()));
624        assert_eq!(most, 1, "one value used twice is one value");
625    }
626}