Skip to main content

rucc_codegen/
kept.rs

1//! Where each local the program kept in a value ended up, and over which instructions.
2//!
3//! Design: `spec/11-asm-objects-debug.md` section 11.4.
4//!
5//! Selection says which declaration each virtual register holds a value of, and the allocator says
6//! where each virtual register went. Putting the two together is all this is, and the only thing
7//! that makes it more than a join is the stretch: a frame slot belongs to its local for as long as
8//! the frame exists, and a register is handed to the next value the moment this one is done with,
9//! so where a register holds a local is a question about part of a function rather than about the
10//! whole of it. The allocator's own liveness is the answer, read rather than worked out again for
11//! the reason `crate::slots` gives for reading it: two answers about one function are free to
12//! disagree, and the one the machine runs is the allocator's.
13//!
14//! A stretch runs from the instruction after the one that wrote the value to the last instruction
15//! that reads it, both ends included, and it stops at the end of the block either way. The front is
16//! one instruction along because a register does not hold a value until the instruction writing it
17//! has run, and the back is where it is because nothing reads the value afterwards, so whatever the
18//! allocator puts in the register next cannot be seen by anybody asking. A value nothing reads at
19//! all gets no stretch, which is the same sentence read the other way: the two ends cross.
20//!
21//! The block is where it stops because the pass that lays the blocks out runs after the allocator
22//! and can put them in any order it likes. Inside a block nothing has moved, so a run of
23//! instructions there is a run of addresses to come, and a value live from one block into the next
24//! gets a stretch in each of them rather than one stretch that would cover whatever the layout
25//! happened to put in between.
26//!
27//! # A block the scheduler reordered
28//!
29//! The liveness is counted along the order the allocator laid the function out in, and the
30//! scheduler runs after it and moves instructions about inside a block, so in a block it touched
31//! a run of points is no longer a run of instructions. What is still true there is what the
32//! scheduler has to keep true for the program to mean the same thing: it runs after the registers
33//! are handed out, so every write of a register stays in order with every read of it and every
34//! other write of it, and every access to memory stays in the order it was in. So a value is in
35//! its register from the instruction after the one that wrote it to the last one that reads it,
36//! wherever the schedule put those two, and a local in the frame is in its bytes from the first
37//! touch of them to the last. A stretch in such a block is found by where its two ends went rather
38//! than by a search along the points.
39//!
40//! That needs both ends to be an instruction the liveness knows, or the edge of the block for a
41//! value live into it or out of it. A piece that starts or ends at a spill, a reload or an edge
42//! move has an end that is neither, and it gets no stretch in that block rather than a guess.
43//!
44//! # A declaration that took a value part of the way through
45//!
46//! `int m = a;` computes nothing, so `m` is handed a value `a` already holds, and the value's live
47//! range says nothing about where the assignment was. Selection says it instead, as the first
48//! instruction after it in [`Func::starts`]. In that instruction's block the stretch starts no
49//! earlier than it, and in any other block the declaration holds the value only where every path
50//! from the entry goes through the assignment's block first, which is where it is sure to have
51//! run. A block the other arm of a branch reaches as well is left out, since there the
52//! declaration may never have been given the value at all.
53//!
54//! # A declaration with two values live into a block
55//!
56//! A local written in a loop is the value from the last trip until the new one is computed, and
57//! the old one can still be read after that, so both are live into the blocks between the write and
58//! the back edge. Their stretches there start at the same address and say different things. Which
59//! one the local is follows from the order the assignments ran in, and [`Func::entries`] is that
60//! answer, worked out from the program before selection. A piece that comes into a block the entry
61//! names another register for gets no stretch there. A piece that starts inside the block starts at
62//! an assignment, and is kept either way.
63//!
64//! # What is left out
65//!
66//! A value the allocator spilled is in the frame over its stretch rather than in a register, which
67//! is as much an answer as the other and is written the same way. A value it spilled in a function
68//! whose alignment the prologue had to force has no answer, because the distance from the call
69//! frame address is not a constant there, which is what `crate::frame` says about a local in the
70//! same function.
71
72use rucc_mir::{Block, Func, Inst, Kept, Reg, Where};
73use rucc_regalloc::Allocation;
74use rucc_regalloc::assign::Place;
75use rucc_regalloc::live::Range;
76use rucc_regalloc::order::Point;
77
78use crate::frame::Frame;
79
80/// Every instruction of a function, in the order they are in, which is what the allocator's
81/// liveness is counted along while that is still the order.
82///
83/// Taken before the allocator rewrites the function, because afterwards the spills, the reloads
84/// and the edge moves are in among them and none of those is an instruction the liveness knows a
85/// point for.
86#[must_use]
87pub fn before(func: &Func) -> Vec<Inst> {
88    func.blocks().flat_map(|block| func.insts(block)).collect()
89}
90
91/// Which declaration is where, over which instructions.
92///
93/// `framed` is the locals in the frame whose bytes they share with something else, as the
94/// declaration, how far the bytes are from the call frame address and where the local is wanted.
95/// Each of them is in the frame over that area and nowhere outside it, which is the same question
96/// as a spilled value and gets the same answer.
97#[must_use]
98pub fn of(
99    func: &Func,
100    before: &[Inst],
101    allocation: &Allocation,
102    frame: &Frame,
103    framed: &[(u32, i32, &[Range])],
104) -> Vec<Kept> {
105    if func.named.is_empty() && func.starts.is_empty() && framed.is_empty() {
106        return Vec::new();
107    }
108    let line = line(func, before, allocation);
109    let mut out = Vec::new();
110    for &(decl, reg) in &func.named {
111        let Some(at) = place(func, allocation, frame, reg) else { continue };
112        let Some(area) = allocation.live.area(reg) else { continue };
113        let held = |run: &Run, piece: Range| !other(func, decl, reg, run, piece);
114        over(decl, at, area.pieces(), &line, held, &mut out);
115    }
116    for &(decl, at, area) in framed {
117        over(decl, Where::Frame(at), area.iter().copied(), &line, |_, _| true, &mut out);
118    }
119    // A declaration that took a value another one already held, from the instruction the
120    // assignment became onward. An instruction something took out since is nowhere to start from.
121    let mut tree: Option<Tree> = None;
122    for &(decl, reg, first) in &func.starts {
123        let Some(block) = func.block_of(first) else { continue };
124        let Some(at) = place(func, allocation, frame, reg) else { continue };
125        let Some(area) = allocation.live.area(reg) else { continue };
126        let tree = tree.get_or_insert_with(|| Tree::of(func));
127        for piece in area.pieces() {
128            for run in line.reached(piece) {
129                let stretch = if run.block == block {
130                    run.stretch_from(piece, first)
131                } else if tree.strictly(block, run.block) && !other(func, decl, reg, run, piece) {
132                    run.stretch(piece)
133                } else {
134                    None
135                };
136                if let Some((from, to)) = stretch {
137                    out.push(Kept { decl, at, from, to });
138                }
139            }
140        }
141    }
142    out
143}
144
145/// Where the allocator put a register, as a place a debugger can read, or `None` for a register
146/// it put nowhere or in a slot this frame cannot name.
147fn place(func: &Func, allocation: &Allocation, frame: &Frame, reg: Reg) -> Option<Where> {
148    let class = func.class_of(reg)?;
149    match allocation.assignment.place(reg)? {
150        Place::Reg(reg) => Some(Where::Reg { reg, class }),
151        Place::Slot(slot) => frame.slot_from_frame_base(slot).map(Where::Frame),
152    }
153}
154
155/// A function's dominator tree, numbered so that whether one block dominates another is two
156/// comparisons.
157///
158/// Built once for the function. What this did before was read the definition straight off for
159/// each block an assignment started in: walk every block the entry reaches, then walk them again
160/// with that block taken away. That is two walks of the whole function per block, and a function
161/// with tens of thousands of blocks has thousands of them.
162struct Tree {
163    /// When a depth first walk of the tree gets to each block and when it leaves it, or `None` for
164    /// a block the entry does not reach.
165    span: Vec<Option<(usize, usize)>>,
166}
167
168impl Tree {
169    /// The tree of every block the entry reaches, by the iteration Cooper, Harvey and Kennedy
170    /// describe in "A Simple, Fast Dominance Algorithm", over the blocks in reverse postorder.
171    fn of(func: &Func) -> Self {
172        let count = func.block_count();
173        let mut order: Vec<Block> = Vec::new();
174        if let Some(entry) = func.entry() {
175            let mut seen = vec![false; count];
176            seen[entry.index()] = true;
177            let mut stack: Vec<(Block, usize)> = vec![(entry, 0)];
178            while let Some(top) = stack.last_mut() {
179                let (block, next) = *top;
180                if let Some(call) = func[block].succs.get(next) {
181                    top.1 += 1;
182                    if !seen[call.block.index()] {
183                        seen[call.block.index()] = true;
184                        stack.push((call.block, 0));
185                    }
186                } else {
187                    order.push(block);
188                    stack.pop();
189                }
190            }
191        }
192        order.reverse();
193        let mut rank = vec![usize::MAX; count];
194        for (at, &block) in order.iter().enumerate() {
195            rank[block.index()] = at;
196        }
197        let mut preds: Vec<Vec<usize>> = vec![Vec::new(); order.len()];
198        for (at, &block) in order.iter().enumerate() {
199            for call in &func[block].succs {
200                preds[rank[call.block.index()]].push(at);
201            }
202        }
203
204        // Each block's immediate dominator, by its place in the order. The entry is its own, and a
205        // block none of whose predecessors has one yet waits for a later round.
206        let mut idom = vec![usize::MAX; order.len()];
207        if let Some(entry) = idom.first_mut() {
208            *entry = 0;
209        }
210        let mut changed = true;
211        while changed {
212            changed = false;
213            for at in 1..order.len() {
214                let mut new = usize::MAX;
215                for &pred in &preds[at] {
216                    if idom[pred] == usize::MAX {
217                        continue;
218                    }
219                    new = if new == usize::MAX { pred } else { meet(&idom, pred, new) };
220                }
221                if idom[at] != new {
222                    idom[at] = new;
223                    changed = true;
224                }
225            }
226        }
227
228        let mut children: Vec<Vec<usize>> = vec![Vec::new(); order.len()];
229        for (at, &parent) in idom.iter().enumerate().skip(1) {
230            children[parent].push(at);
231        }
232        let mut span = vec![None; count];
233        let mut enter = vec![0; order.len()];
234        let mut clock = 0;
235        let mut stack: Vec<(usize, usize)> =
236            if order.is_empty() { Vec::new() } else { vec![(0, 0)] };
237        while let Some(top) = stack.last_mut() {
238            let (node, next) = *top;
239            if next == 0 {
240                enter[node] = clock;
241                clock += 1;
242            }
243            if let Some(&child) = children[node].get(next) {
244                top.1 += 1;
245                stack.push((child, 0));
246            } else {
247                span[order[node].index()] = Some((enter[node], clock));
248                clock += 1;
249                stack.pop();
250            }
251        }
252        Self { span }
253    }
254
255    /// Whether every path from the entry to `below` goes through `above`, and they are two blocks.
256    ///
257    /// `above` itself is not, because what holds in it holds from part of the way through and is
258    /// asked separately. A block the entry does not reach is dominated by nothing and dominates
259    /// nothing.
260    fn strictly(&self, above: Block, below: Block) -> bool {
261        match (self.span[above.index()], self.span[below.index()]) {
262            (Some((in_above, out_above)), Some((in_below, out_below))) => {
263                in_above < in_below && out_below < out_above
264            }
265            _ => false,
266        }
267    }
268}
269
270/// The nearest block that dominates both, by place in reverse postorder, which is where the two
271/// walks up the tree meet.
272fn meet(idom: &[usize], mut one: usize, mut other: usize) -> usize {
273    while one != other {
274        while one > other {
275            one = idom[one];
276        }
277        while other > one {
278            other = idom[other];
279        }
280    }
281    one
282}
283
284/// Whether a piece of a register's live range that comes into a run's block from the blocks before
285/// it is a value of the declaration other than the one [`Func::entries`] says it holds there.
286///
287/// Only the stretch of a piece live into the block is in question. One that starts inside it
288/// starts at an assignment in the block, which is later than whatever the declaration came in
289/// with, and a block with no entry has nothing to choose by.
290fn other(func: &Func, decl: u32, reg: Reg, run: &Run, piece: Range) -> bool {
291    let Some((start, _)) = run.bounds else { return false };
292    if piece.start > start {
293        return false;
294    }
295    let at = func.entries.partition_point(|&(have, block, _)| (have, block) < (decl, run.block));
296    func.entries
297        .get(at)
298        .is_some_and(|&(have, block, held)| have == decl && block == run.block && held != reg)
299}
300
301/// The stretches one declaration is in one place over, a piece of where it is wanted at a time,
302/// leaving out the ones `held` says it is not holding that piece over.
303fn over(
304    decl: u32,
305    at: Where,
306    pieces: impl Iterator<Item = Range>,
307    line: &Line,
308    held: impl Fn(&Run, Range) -> bool,
309    out: &mut Vec<Kept>,
310) {
311    for piece in pieces {
312        for run in line.reached(piece) {
313            if !held(run, piece) {
314                continue;
315            }
316            if let Some((from, to)) = run.stretch(piece) {
317                out.push(Kept { decl, at, from, to });
318            }
319        }
320    }
321}
322
323/// The runs of a function, and the same runs in the order of the points they span.
324///
325/// Asking every block about every piece was a third of jtckdint's build at O0, as its test has one
326/// function of 22000 blocks. Each block spans points no other block has any of, so a piece only
327/// needs the few blocks its own points fall in, and sorting them by where they start finds those.
328struct Line {
329    runs: Vec<Run>,
330    /// Where each run starts and ends, and which it is, sorted by where it starts. A run the
331    /// liveness cannot say anything about is left out, as no piece covers any of it.
332    by_start: Vec<(Point, Point, usize)>,
333    /// The furthest any run up to and including this one in `by_start` ends, which goes up along
334    /// it even if two runs were ever to overlap.
335    reach: Vec<Point>,
336}
337
338impl Line {
339    /// The runs a piece has any points in, in the order the blocks are laid out in, which is the
340    /// order the stretches were always written in. Every run before `first` ends before the piece
341    /// starts, and the walk stops at the first run starting after it ends.
342    fn reached(&self, piece: Range) -> impl Iterator<Item = &Run> {
343        let first = self.reach.partition_point(|&last| last < piece.start);
344        let mut found: Vec<usize> = self.by_start[first..]
345            .iter()
346            .take_while(|&&(start, _, _)| start <= piece.end)
347            .map(|&(_, _, index)| index)
348            .collect();
349        found.sort_unstable();
350        found.into_iter().map(|index| &self.runs[index])
351    }
352}
353
354/// One block's instructions the liveness knows a point for, in the order the block is in now.
355struct Run {
356    /// Which block it is.
357    block: Block,
358    /// Each instruction, with the point it reads its operands at and the one it writes at.
359    insts: Vec<(Point, Point, Inst)>,
360    /// Whether the points go up along the block, which is every block the scheduler left alone.
361    sorted: bool,
362    /// Where the block's parameters arrive, which is before everything in it, and where its
363    /// outgoing arguments are read, which is after everything in it. `None` for a block made
364    /// after the allocator ran, which has no points of its own.
365    bounds: Option<(Point, Point)>,
366}
367
368impl Run {
369    /// The first and the last point a piece has to reach for [`Run::span`] to find anything in
370    /// this block, or `None` for a block it never does.
371    fn extent(&self) -> Option<(Point, Point)> {
372        match (self.bounds, self.sorted) {
373            (Some(bounds), _) => Some(bounds),
374            (None, true) => Some((self.insts.first()?.0, self.insts.last()?.0)),
375            (None, false) => None,
376        }
377    }
378
379    /// The first and the last instruction of this block a piece of a live range covers, or `None`
380    /// for a piece that covers none of them or one this cannot say about.
381    fn stretch(&self, piece: Range) -> Option<(Inst, Inst)> {
382        let (lo, hi) = self.span(piece)?;
383        Some((self.insts[lo].2, self.insts[hi].2))
384    }
385
386    /// The same stretch, starting no earlier than `first`, for a declaration that only holds the
387    /// value from there on. `None` as well for a `first` the liveness has no point for.
388    fn stretch_from(&self, piece: Range, first: Inst) -> Option<(Inst, Inst)> {
389        let (lo, hi) = self.span(piece)?;
390        let lo = lo.max(self.insts.iter().position(|&(_, _, inst)| inst == first)?);
391        (lo <= hi).then(|| (self.insts[lo].2, self.insts[hi].2))
392    }
393
394    /// Where in [`Run::insts`] the first and the last instruction of a stretch are.
395    fn span(&self, piece: Range) -> Option<(usize, usize)> {
396        if self.sorted {
397            // Strictly after where the value is written and up to and including where it is last
398            // read. Both ends of a piece are points the value is live at, and the front one is the
399            // instruction writing it, which is the one instruction in the piece the register does
400            // not hold the value at the start of.
401            let lo = self.insts.partition_point(|&(early, _, _)| early <= piece.start);
402            let hi = self.insts.partition_point(|&(early, _, _)| early <= piece.end);
403            return (lo < hi).then(|| (lo, hi - 1));
404        }
405        let (start, end) = self.bounds?;
406        if piece.end < start || piece.start > end {
407            return None;
408        }
409        // The same two ends, found by where the instructions at them went. See the module
410        // documentation on a block the scheduler reordered.
411        let at = |point: Point| {
412            self.insts.iter().position(|&(early, late, _)| early == point || late == point)
413        };
414        let lo = if piece.start <= start { 0 } else { at(piece.start)? + 1 };
415        let hi = if piece.end >= end { self.insts.len().checked_sub(1)? } else { at(piece.end)? };
416        (lo <= hi).then_some((lo, hi))
417    }
418}
419
420/// The instructions the function still has that the liveness knows a point for, one run per
421/// block and each in the order that block is in now.
422///
423/// A block at a time rather than the whole function at once, because the pass that lays the blocks
424/// out runs between the allocator and here and is free to put them in any order it likes. A block
425/// it moved is still a block whose instructions are contiguous in the addresses to come, so the
426/// question the liveness answers is still answerable about each of them on its own. What is not
427/// answerable is a stretch that runs from one block into another, which is why a piece of a live
428/// range turns into a stretch per block rather than into one stretch.
429fn line(func: &Func, before: &[Inst], allocation: &Allocation) -> Line {
430    let order = &allocation.order;
431    let mut known = vec![false; func.inst_count()];
432    for &inst in before {
433        known[inst.index()] = true;
434    }
435    let mut out = Vec::with_capacity(func.block_count());
436    for block in func.blocks() {
437        let insts: Vec<(Point, Point, Inst)> = func
438            .insts(block)
439            .filter(|inst| known[inst.index()])
440            .map(|inst| (order.early(inst), order.late(inst), inst))
441            .collect();
442        if insts.is_empty() {
443            continue;
444        }
445        let sorted = insts.windows(2).all(|pair| pair[0].0 < pair[1].0);
446        out.push(Run { block, insts, sorted, bounds: order.bounds(block) });
447    }
448    let mut by_start: Vec<(Point, Point, usize)> = out
449        .iter()
450        .enumerate()
451        .filter_map(|(index, run)| run.extent().map(|(start, end)| (start, end, index)))
452        .collect();
453    by_start.sort_unstable();
454    let reach = by_start
455        .iter()
456        .scan(0, |furthest, &(_, end, _)| {
457            *furthest = end.max(*furthest);
458            Some(*furthest)
459        })
460        .collect();
461    Line { runs: out, by_start, reach }
462}
463
464#[cfg(test)]
465mod tests {
466    use rucc_base::Interner;
467    use rucc_mir::{BlockCall, Func, Opcode, Reg};
468    use rucc_regalloc::assign::Env;
469    use rucc_target::x86_64::{GPR, REGS, SYSV};
470
471    use super::*;
472    use crate::frame::Layout;
473
474    /// A function of three instructions: two that write a value and one that reads the first of
475    /// them, with the declarations the caller asks for named against its registers.
476    ///
477    /// Three of them rather than two so that the stretch of the first value has an instruction in
478    /// it either side of the one that wrote it, and the second value is one nothing reads.
479    fn three(named: &[(u32, u32)]) -> (Func, Vec<Inst>) {
480        let mut names = Interner::new();
481        let mut func = Func::new(names.intern("f"));
482        let opcode = Opcode::new(names.intern("x64.nop"));
483        let block = func.create_block();
484        let first = func.new_vreg(GPR);
485        let second = func.new_vreg(GPR);
486        func.build(block, opcode).def(first, GPR).finish();
487        func.build(block, opcode).def(second, GPR).finish();
488        func.build(block, opcode).uses(first, GPR).finish();
489        func.named = named.iter().map(|&(decl, reg)| (decl, Reg::virtual_reg(reg))).collect();
490        let line = before(&func);
491        (func, line)
492    }
493
494    /// That function allocated with enough registers to spill nothing, and what this says about it.
495    fn about(func: &mut Func, line: &[Inst]) -> Vec<Kept> {
496        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
497        let allocation = rucc_regalloc::run(func, &env, "test", true);
498        let frame = Frame::of(func, &allocation, &Layout::new(&SYSV, REGS));
499        of(func, line, &allocation, &frame, &[])
500    }
501
502    #[test]
503    fn a_register_holding_a_local_says_so_from_the_instruction_after_the_one_that_wrote_it() {
504        let (mut func, line) = three(&[(41, 0)]);
505        let kept = about(&mut func, &line);
506
507        // Written by the first instruction and read by the third, so the stretch is the second and
508        // the third: the register does not hold the value until the first has run, and the last
509        // instruction that reads it is in the stretch rather than one past the end of it.
510        assert_eq!(kept.len(), 1, "one stretch: {kept:?}");
511        assert_eq!(kept[0].decl, 41);
512        assert_eq!(kept[0].from, line[1], "from the instruction after the one that wrote it");
513        assert_eq!(kept[0].to, line[2], "to the last one that reads it");
514        assert!(matches!(kept[0].at, Where::Reg { .. }), "in a register: {:?}", kept[0].at);
515    }
516
517    #[test]
518    fn a_value_nothing_reads_is_nowhere_worth_saying() {
519        // The second instruction's result is never read, so the value is live only where it is
520        // written and the stretch that would begin after that has nothing in it.
521        let (mut func, line) = three(&[(41, 1)]);
522        let kept = about(&mut func, &line);
523        assert!(kept.is_empty(), "nothing to say: {kept:?}");
524    }
525
526    #[test]
527    fn a_declaration_two_registers_hold_gets_a_stretch_for_each_of_them() {
528        let (mut func, line) = three(&[(41, 0), (41, 1)]);
529        let kept = about(&mut func, &line);
530
531        // The one nothing reads still says nothing, so what is left is the one stretch, and the
532        // point of the case is that one declaration being asked about twice is allowed.
533        assert_eq!(kept.iter().map(|kept| kept.decl).collect::<Vec<u32>>(), vec![41]);
534    }
535
536    #[test]
537    fn a_local_live_from_one_block_into_the_next_gets_a_stretch_in_each_of_them() {
538        let mut names = Interner::new();
539        let mut func = Func::new(names.intern("f"));
540        let opcode = Opcode::new(names.intern("x64.nop"));
541        let head = func.create_block();
542        let tail = func.create_block();
543        let value = func.new_vreg(GPR);
544        func.build(head, opcode).def(value, GPR).finish();
545        let across = func.build(head, opcode).finish();
546        *func.succs_mut(head) = vec![BlockCall::to(tail)];
547        let read = func.build(tail, opcode).uses(value, GPR).finish();
548        func.named = vec![(41, value)];
549        let line = before(&func);
550        let kept = about(&mut func, &line);
551
552        // Live from where it is written to where it is read, and a stretch in each of the two
553        // blocks rather than one that would cover whatever the layout later puts in between.
554        assert_eq!(kept.len(), 2, "one stretch per block: {kept:?}");
555        assert_eq!((kept[0].from, kept[0].to), (across, across), "the rest of the first block");
556        assert_eq!((kept[1].from, kept[1].to), (read, read), "and into the second");
557    }
558
559    #[test]
560    fn a_block_two_values_of_one_local_come_into_gets_the_one_it_holds_there() {
561        // `i = i + 1;` with the old `i` still read after it: both values are live into the second
562        // block, and the entry says the local is the new one there.
563        let mut names = Interner::new();
564        let mut func = Func::new(names.intern("f"));
565        let opcode = Opcode::new(names.intern("x64.nop"));
566        let head = func.create_block();
567        let tail = func.create_block();
568        let old = func.new_vreg(GPR);
569        let new = func.new_vreg(GPR);
570        func.build(head, opcode).def(old, GPR).finish();
571        func.build(head, opcode).def(new, GPR).finish();
572        func.build(head, opcode).finish();
573        *func.succs_mut(head) = vec![BlockCall::to(tail)];
574        let first = func.build(tail, opcode).uses(old, GPR).finish();
575        let second = func.build(tail, opcode).uses(new, GPR).finish();
576        func.named = vec![(41, old), (41, new)];
577        func.entries = vec![(41, tail, new)];
578        let line = before(&func);
579        let kept = about(&mut func, &line);
580
581        // Both in the first block, each from where it was written, and only the new one in the
582        // second, where without the entry the two would start at the same address.
583        let into: Vec<(Inst, Inst)> = kept
584            .iter()
585            .filter(|kept| func.block_of(kept.from) == Some(tail))
586            .map(|kept| (kept.from, kept.to))
587            .collect();
588        assert_eq!(into, [(first, second)], "{kept:?}");
589        let before_it = kept.iter().filter(|kept| func.block_of(kept.from) == Some(head)).count();
590        assert_eq!(before_it, 2, "{kept:?}");
591    }
592
593    #[test]
594    fn a_local_that_shares_its_frame_bytes_is_there_over_its_area_and_nowhere_else() {
595        let (mut func, line) = three(&[]);
596        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
597        let allocation = rucc_regalloc::run(&mut func, &env, "test", true);
598        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
599
600        // Wanted from the first instruction to the second, so in its bytes over the second only,
601        // and the third is where whatever it shares them with may have written over it.
602        let order = &allocation.order;
603        let area = [Range { start: order.early(line[0]), end: order.late(line[1]) }];
604        let kept = of(&func, &line, &allocation, &frame, &[(41, -24, &area)]);
605        assert_eq!(kept, [Kept { decl: 41, at: Where::Frame(-24), from: line[1], to: line[1] }]);
606    }
607
608    #[test]
609    fn a_declaration_that_took_a_value_part_of_the_way_through_holds_it_from_there() {
610        // `int m = a;` with the assignment in front of the third instruction: `a` holds the value
611        // over the whole of its stretch and `m` only from there.
612        let (mut func, line) = three(&[(41, 0)]);
613        func.starts = vec![(42, Reg::virtual_reg(0), line[2])];
614        let kept = about(&mut func, &line);
615        let said: Vec<(u32, Inst, Inst)> =
616            kept.iter().map(|kept| (kept.decl, kept.from, kept.to)).collect();
617        assert_eq!(said, [(41, line[1], line[2]), (42, line[2], line[2])]);
618    }
619
620    #[test]
621    fn a_declaration_that_took_a_value_holds_it_in_the_blocks_its_own_dominates_only() {
622        let mut names = Interner::new();
623        let mut func = Func::new(names.intern("f"));
624        let opcode = Opcode::new(names.intern("x64.nop"));
625        let [head, left, below, right, tail] = std::array::from_fn(|_| func.create_block());
626        let value = func.new_vreg(GPR);
627        func.build(head, opcode).def(value, GPR).finish();
628        let first = func.build(left, opcode).uses(value, GPR).finish();
629        let under = func.build(below, opcode).uses(value, GPR).finish();
630        func.build(right, opcode).uses(value, GPR).finish();
631        func.build(tail, opcode).uses(value, GPR).finish();
632        *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
633        *func.succs_mut(left) = vec![BlockCall::to(below)];
634        *func.succs_mut(below) = vec![BlockCall::to(tail)];
635        *func.succs_mut(right) = vec![BlockCall::to(tail)];
636        func.starts = vec![(42, value, first)];
637        let line = before(&func);
638        let kept = about(&mut func, &line);
639
640        // The block the assignment is in and the one only it leads to. Not the other arm, which
641        // never ran the assignment, and not the join, which the other arm reaches too.
642        let said: Vec<(Inst, Inst)> = kept.iter().map(|kept| (kept.from, kept.to)).collect();
643        assert_eq!(said, [(first, first), (under, under)]);
644    }
645
646    #[test]
647    fn a_function_the_front_end_named_nothing_in_says_nothing() {
648        let (mut func, line) = three(&[]);
649        let kept = about(&mut func, &line);
650        assert!(kept.is_empty(), "nothing to say: {kept:?}");
651    }
652
653    #[test]
654    fn a_block_the_scheduler_reordered_is_read_by_where_the_two_ends_went() {
655        let (mut func, line) = three(&[(41, 0)]);
656        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
657        let allocation = rucc_regalloc::run(&mut func, &env, "test", true);
658        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
659
660        // The first two instructions the other way round, which is what a scheduler leaves behind.
661        // The value is written by what is now the second instruction and read by the third, so the
662        // register holds it over the third only, and the first is before it was written.
663        func.remove_inst(line[0]);
664        func.insert_after(line[1], line[0]);
665        let kept = of(&func, &line, &allocation, &frame, &[]);
666        assert_eq!(kept.len(), 1, "one stretch: {kept:?}");
667        assert_eq!((kept[0].from, kept[0].to), (line[2], line[2]));
668    }
669
670    #[test]
671    fn a_local_live_across_the_whole_of_a_reordered_block_covers_all_of_it() {
672        let (mut func, line) = three(&[]);
673        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
674        let allocation = rucc_regalloc::run(&mut func, &env, "test", true);
675        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
676        func.remove_inst(line[0]);
677        func.insert_after(line[1], line[0]);
678
679        // Live into the block and out of it, so its ends are the block's edges rather than any
680        // instruction, and the stretch is from whatever is first now to whatever is last.
681        let block = func.blocks().next().expect("one block");
682        let (start, end) = allocation.order.bounds(block).expect("laid out");
683        let area = [Range { start, end }];
684        let kept = of(&func, &line, &allocation, &frame, &[(41, -8, &area)]);
685        assert_eq!(kept, [Kept { decl: 41, at: Where::Frame(-8), from: line[1], to: line[2] }]);
686    }
687}