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 std::collections::HashMap;
73
74use rucc_mir::{Block, Func, Inst, Kept, Reg, Where};
75use rucc_regalloc::Allocation;
76use rucc_regalloc::assign::Place;
77use rucc_regalloc::live::Range;
78use rucc_regalloc::order::Point;
79
80use crate::frame::Frame;
81
82/// Every instruction of a function, in the order they are in, which is what the allocator's
83/// liveness is counted along while that is still the order.
84///
85/// Taken before the allocator rewrites the function, because afterwards the spills, the reloads
86/// and the edge moves are in among them and none of those is an instruction the liveness knows a
87/// point for.
88#[must_use]
89pub fn before(func: &Func) -> Vec<Inst> {
90    func.blocks().flat_map(|block| func.insts(block)).collect()
91}
92
93/// Which declaration is where, over which instructions.
94///
95/// `framed` is the locals in the frame whose bytes they share with something else, as the
96/// declaration, how far the bytes are from the call frame address and where the local is wanted.
97/// Each of them is in the frame over that area and nowhere outside it, which is the same question
98/// as a spilled value and gets the same answer.
99#[must_use]
100pub fn of(
101    func: &Func,
102    before: &[Inst],
103    allocation: &Allocation,
104    frame: &Frame,
105    framed: &[(u32, i32, &[Range])],
106) -> Vec<Kept> {
107    if func.named.is_empty() && func.starts.is_empty() && framed.is_empty() {
108        return Vec::new();
109    }
110    let line = line(func, before, allocation);
111    let mut out = Vec::new();
112    for &(decl, reg) in &func.named {
113        let Some(at) = place(func, allocation, frame, reg) else { continue };
114        let Some(area) = allocation.live.area(reg) else { continue };
115        let held = |run: &Run, piece: Range| !other(func, decl, reg, run, piece);
116        over(decl, at, area.pieces(), &line, held, &mut out);
117    }
118    for &(decl, at, area) in framed {
119        over(decl, Where::Frame(at), area.iter().copied(), &line, |_, _| true, &mut out);
120    }
121    // A declaration that took a value another one already held, from the instruction the
122    // assignment became onward. An instruction something took out since is nowhere to start from.
123    let mut under: HashMap<Block, Vec<bool>> = HashMap::new();
124    for &(decl, reg, first) in &func.starts {
125        let Some(block) = func.block_of(first) else { continue };
126        let Some(at) = place(func, allocation, frame, reg) else { continue };
127        let Some(area) = allocation.live.area(reg) else { continue };
128        let dominated = under.entry(block).or_insert_with(|| dominated(func, block));
129        for piece in area.pieces() {
130            for run in line.reached(piece) {
131                let stretch = if run.block == block {
132                    run.stretch_from(piece, first)
133                } else if dominated[run.block.index()] && !other(func, decl, reg, run, piece) {
134                    run.stretch(piece)
135                } else {
136                    None
137                };
138                if let Some((from, to)) = stretch {
139                    out.push(Kept { decl, at, from, to });
140                }
141            }
142        }
143    }
144    out
145}
146
147/// Where the allocator put a register, as a place a debugger can read, or `None` for a register
148/// it put nowhere or in a slot this frame cannot name.
149fn place(func: &Func, allocation: &Allocation, frame: &Frame, reg: Reg) -> Option<Where> {
150    let class = func.class_of(reg)?;
151    match allocation.assignment.place(reg)? {
152        Place::Reg(reg) => Some(Where::Reg { reg, class }),
153        Place::Slot(slot) => frame.slot_from_frame_base(slot).map(Where::Frame),
154    }
155}
156
157/// Which blocks every path from the entry to them goes through `from` on, by block number.
158///
159/// A block is one of them when the entry reaches it and stops reaching it once `from` is taken
160/// away, which is the definition read straight off rather than a dominator tree, since the question
161/// is asked of a handful of blocks and a tree would answer it for all of them. `from` itself is
162/// not, because what holds in it holds from part of the way through and is asked separately.
163fn dominated(func: &Func, from: Block) -> Vec<bool> {
164    let reach = |skip: Option<Block>| {
165        let mut seen = vec![false; func.block_count()];
166        let mut stack: Vec<Block> =
167            func.entry().filter(|&entry| Some(entry) != skip).into_iter().collect();
168        for &block in &stack {
169            seen[block.index()] = true;
170        }
171        while let Some(block) = stack.pop() {
172            for call in &func[block].succs {
173                if Some(call.block) != skip && !seen[call.block.index()] {
174                    seen[call.block.index()] = true;
175                    stack.push(call.block);
176                }
177            }
178        }
179        seen
180    };
181    let all = reach(None);
182    let around = reach(Some(from));
183    all.iter()
184        .zip(&around)
185        .enumerate()
186        .map(|(index, (&all, &around))| all && !around && index != from.index())
187        .collect()
188}
189
190/// Whether a piece of a register's live range that comes into a run's block from the blocks before
191/// it is a value of the declaration other than the one [`Func::entries`] says it holds there.
192///
193/// Only the stretch of a piece live into the block is in question. One that starts inside it
194/// starts at an assignment in the block, which is later than whatever the declaration came in
195/// with, and a block with no entry has nothing to choose by.
196fn other(func: &Func, decl: u32, reg: Reg, run: &Run, piece: Range) -> bool {
197    let Some((start, _)) = run.bounds else { return false };
198    if piece.start > start {
199        return false;
200    }
201    let at = func.entries.partition_point(|&(have, block, _)| (have, block) < (decl, run.block));
202    func.entries
203        .get(at)
204        .is_some_and(|&(have, block, held)| have == decl && block == run.block && held != reg)
205}
206
207/// The stretches one declaration is in one place over, a piece of where it is wanted at a time,
208/// leaving out the ones `held` says it is not holding that piece over.
209fn over(
210    decl: u32,
211    at: Where,
212    pieces: impl Iterator<Item = Range>,
213    line: &Line,
214    held: impl Fn(&Run, Range) -> bool,
215    out: &mut Vec<Kept>,
216) {
217    for piece in pieces {
218        for run in line.reached(piece) {
219            if !held(run, piece) {
220                continue;
221            }
222            if let Some((from, to)) = run.stretch(piece) {
223                out.push(Kept { decl, at, from, to });
224            }
225        }
226    }
227}
228
229/// The runs of a function, and the same runs in the order of the points they span.
230///
231/// Asking every block about every piece was a third of jtckdint's build at O0, as its test has one
232/// function of 22000 blocks. Each block spans points no other block has any of, so a piece only
233/// needs the few blocks its own points fall in, and sorting them by where they start finds those.
234struct Line {
235    runs: Vec<Run>,
236    /// Where each run starts and ends, and which it is, sorted by where it starts. A run the
237    /// liveness cannot say anything about is left out, as no piece covers any of it.
238    by_start: Vec<(Point, Point, usize)>,
239    /// The furthest any run up to and including this one in `by_start` ends, which goes up along
240    /// it even if two runs were ever to overlap.
241    reach: Vec<Point>,
242}
243
244impl Line {
245    /// The runs a piece has any points in, in the order the blocks are laid out in, which is the
246    /// order the stretches were always written in. Every run before `first` ends before the piece
247    /// starts, and the walk stops at the first run starting after it ends.
248    fn reached(&self, piece: Range) -> impl Iterator<Item = &Run> {
249        let first = self.reach.partition_point(|&last| last < piece.start);
250        let mut found: Vec<usize> = self.by_start[first..]
251            .iter()
252            .take_while(|&&(start, _, _)| start <= piece.end)
253            .map(|&(_, _, index)| index)
254            .collect();
255        found.sort_unstable();
256        found.into_iter().map(|index| &self.runs[index])
257    }
258}
259
260/// One block's instructions the liveness knows a point for, in the order the block is in now.
261struct Run {
262    /// Which block it is.
263    block: Block,
264    /// Each instruction, with the point it reads its operands at and the one it writes at.
265    insts: Vec<(Point, Point, Inst)>,
266    /// Whether the points go up along the block, which is every block the scheduler left alone.
267    sorted: bool,
268    /// Where the block's parameters arrive, which is before everything in it, and where its
269    /// outgoing arguments are read, which is after everything in it. `None` for a block made
270    /// after the allocator ran, which has no points of its own.
271    bounds: Option<(Point, Point)>,
272}
273
274impl Run {
275    /// The first and the last point a piece has to reach for [`Run::span`] to find anything in
276    /// this block, or `None` for a block it never does.
277    fn extent(&self) -> Option<(Point, Point)> {
278        match (self.bounds, self.sorted) {
279            (Some(bounds), _) => Some(bounds),
280            (None, true) => Some((self.insts.first()?.0, self.insts.last()?.0)),
281            (None, false) => None,
282        }
283    }
284
285    /// The first and the last instruction of this block a piece of a live range covers, or `None`
286    /// for a piece that covers none of them or one this cannot say about.
287    fn stretch(&self, piece: Range) -> Option<(Inst, Inst)> {
288        let (lo, hi) = self.span(piece)?;
289        Some((self.insts[lo].2, self.insts[hi].2))
290    }
291
292    /// The same stretch, starting no earlier than `first`, for a declaration that only holds the
293    /// value from there on. `None` as well for a `first` the liveness has no point for.
294    fn stretch_from(&self, piece: Range, first: Inst) -> Option<(Inst, Inst)> {
295        let (lo, hi) = self.span(piece)?;
296        let lo = lo.max(self.insts.iter().position(|&(_, _, inst)| inst == first)?);
297        (lo <= hi).then(|| (self.insts[lo].2, self.insts[hi].2))
298    }
299
300    /// Where in [`Run::insts`] the first and the last instruction of a stretch are.
301    fn span(&self, piece: Range) -> Option<(usize, usize)> {
302        if self.sorted {
303            // Strictly after where the value is written and up to and including where it is last
304            // read. Both ends of a piece are points the value is live at, and the front one is the
305            // instruction writing it, which is the one instruction in the piece the register does
306            // not hold the value at the start of.
307            let lo = self.insts.partition_point(|&(early, _, _)| early <= piece.start);
308            let hi = self.insts.partition_point(|&(early, _, _)| early <= piece.end);
309            return (lo < hi).then(|| (lo, hi - 1));
310        }
311        let (start, end) = self.bounds?;
312        if piece.end < start || piece.start > end {
313            return None;
314        }
315        // The same two ends, found by where the instructions at them went. See the module
316        // documentation on a block the scheduler reordered.
317        let at = |point: Point| {
318            self.insts.iter().position(|&(early, late, _)| early == point || late == point)
319        };
320        let lo = if piece.start <= start { 0 } else { at(piece.start)? + 1 };
321        let hi = if piece.end >= end { self.insts.len().checked_sub(1)? } else { at(piece.end)? };
322        (lo <= hi).then_some((lo, hi))
323    }
324}
325
326/// The instructions the function still has that the liveness knows a point for, one run per
327/// block and each in the order that block is in now.
328///
329/// A block at a time rather than the whole function at once, because the pass that lays the blocks
330/// out runs between the allocator and here and is free to put them in any order it likes. A block
331/// it moved is still a block whose instructions are contiguous in the addresses to come, so the
332/// question the liveness answers is still answerable about each of them on its own. What is not
333/// answerable is a stretch that runs from one block into another, which is why a piece of a live
334/// range turns into a stretch per block rather than into one stretch.
335fn line(func: &Func, before: &[Inst], allocation: &Allocation) -> Line {
336    let order = &allocation.order;
337    let mut known = vec![false; func.inst_count()];
338    for &inst in before {
339        known[inst.index()] = true;
340    }
341    let mut out = Vec::with_capacity(func.block_count());
342    for block in func.blocks() {
343        let insts: Vec<(Point, Point, Inst)> = func
344            .insts(block)
345            .filter(|inst| known[inst.index()])
346            .map(|inst| (order.early(inst), order.late(inst), inst))
347            .collect();
348        if insts.is_empty() {
349            continue;
350        }
351        let sorted = insts.windows(2).all(|pair| pair[0].0 < pair[1].0);
352        out.push(Run { block, insts, sorted, bounds: order.bounds(block) });
353    }
354    let mut by_start: Vec<(Point, Point, usize)> = out
355        .iter()
356        .enumerate()
357        .filter_map(|(index, run)| run.extent().map(|(start, end)| (start, end, index)))
358        .collect();
359    by_start.sort_unstable();
360    let reach = by_start
361        .iter()
362        .scan(0, |furthest, &(_, end, _)| {
363            *furthest = end.max(*furthest);
364            Some(*furthest)
365        })
366        .collect();
367    Line { runs: out, by_start, reach }
368}
369
370#[cfg(test)]
371mod tests {
372    use rucc_base::Interner;
373    use rucc_mir::{BlockCall, Func, Opcode, Reg};
374    use rucc_regalloc::assign::Env;
375    use rucc_target::x86_64::{GPR, REGS, SYSV};
376
377    use super::*;
378    use crate::frame::Layout;
379
380    /// A function of three instructions: two that write a value and one that reads the first of
381    /// them, with the declarations the caller asks for named against its registers.
382    ///
383    /// Three of them rather than two so that the stretch of the first value has an instruction in
384    /// it either side of the one that wrote it, and the second value is one nothing reads.
385    fn three(named: &[(u32, u32)]) -> (Func, Vec<Inst>) {
386        let mut names = Interner::new();
387        let mut func = Func::new(names.intern("f"));
388        let opcode = Opcode::new(names.intern("x64.nop"));
389        let block = func.create_block();
390        let first = func.new_vreg(GPR);
391        let second = func.new_vreg(GPR);
392        func.build(block, opcode).def(first, GPR).finish();
393        func.build(block, opcode).def(second, GPR).finish();
394        func.build(block, opcode).uses(first, GPR).finish();
395        func.named = named.iter().map(|&(decl, reg)| (decl, Reg::virtual_reg(reg))).collect();
396        let line = before(&func);
397        (func, line)
398    }
399
400    /// That function allocated with enough registers to spill nothing, and what this says about it.
401    fn about(func: &mut Func, line: &[Inst]) -> Vec<Kept> {
402        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
403        let allocation = rucc_regalloc::run(func, &env, "test", true);
404        let frame = Frame::of(func, &allocation, &Layout::new(&SYSV, REGS));
405        of(func, line, &allocation, &frame, &[])
406    }
407
408    #[test]
409    fn a_register_holding_a_local_says_so_from_the_instruction_after_the_one_that_wrote_it() {
410        let (mut func, line) = three(&[(41, 0)]);
411        let kept = about(&mut func, &line);
412
413        // Written by the first instruction and read by the third, so the stretch is the second and
414        // the third: the register does not hold the value until the first has run, and the last
415        // instruction that reads it is in the stretch rather than one past the end of it.
416        assert_eq!(kept.len(), 1, "one stretch: {kept:?}");
417        assert_eq!(kept[0].decl, 41);
418        assert_eq!(kept[0].from, line[1], "from the instruction after the one that wrote it");
419        assert_eq!(kept[0].to, line[2], "to the last one that reads it");
420        assert!(matches!(kept[0].at, Where::Reg { .. }), "in a register: {:?}", kept[0].at);
421    }
422
423    #[test]
424    fn a_value_nothing_reads_is_nowhere_worth_saying() {
425        // The second instruction's result is never read, so the value is live only where it is
426        // written and the stretch that would begin after that has nothing in it.
427        let (mut func, line) = three(&[(41, 1)]);
428        let kept = about(&mut func, &line);
429        assert!(kept.is_empty(), "nothing to say: {kept:?}");
430    }
431
432    #[test]
433    fn a_declaration_two_registers_hold_gets_a_stretch_for_each_of_them() {
434        let (mut func, line) = three(&[(41, 0), (41, 1)]);
435        let kept = about(&mut func, &line);
436
437        // The one nothing reads still says nothing, so what is left is the one stretch, and the
438        // point of the case is that one declaration being asked about twice is allowed.
439        assert_eq!(kept.iter().map(|kept| kept.decl).collect::<Vec<u32>>(), vec![41]);
440    }
441
442    #[test]
443    fn a_local_live_from_one_block_into_the_next_gets_a_stretch_in_each_of_them() {
444        let mut names = Interner::new();
445        let mut func = Func::new(names.intern("f"));
446        let opcode = Opcode::new(names.intern("x64.nop"));
447        let head = func.create_block();
448        let tail = func.create_block();
449        let value = func.new_vreg(GPR);
450        func.build(head, opcode).def(value, GPR).finish();
451        let across = func.build(head, opcode).finish();
452        *func.succs_mut(head) = vec![BlockCall::to(tail)];
453        let read = func.build(tail, opcode).uses(value, GPR).finish();
454        func.named = vec![(41, value)];
455        let line = before(&func);
456        let kept = about(&mut func, &line);
457
458        // Live from where it is written to where it is read, and a stretch in each of the two
459        // blocks rather than one that would cover whatever the layout later puts in between.
460        assert_eq!(kept.len(), 2, "one stretch per block: {kept:?}");
461        assert_eq!((kept[0].from, kept[0].to), (across, across), "the rest of the first block");
462        assert_eq!((kept[1].from, kept[1].to), (read, read), "and into the second");
463    }
464
465    #[test]
466    fn a_block_two_values_of_one_local_come_into_gets_the_one_it_holds_there() {
467        // `i = i + 1;` with the old `i` still read after it: both values are live into the second
468        // block, and the entry says the local is the new one there.
469        let mut names = Interner::new();
470        let mut func = Func::new(names.intern("f"));
471        let opcode = Opcode::new(names.intern("x64.nop"));
472        let head = func.create_block();
473        let tail = func.create_block();
474        let old = func.new_vreg(GPR);
475        let new = func.new_vreg(GPR);
476        func.build(head, opcode).def(old, GPR).finish();
477        func.build(head, opcode).def(new, GPR).finish();
478        func.build(head, opcode).finish();
479        *func.succs_mut(head) = vec![BlockCall::to(tail)];
480        let first = func.build(tail, opcode).uses(old, GPR).finish();
481        let second = func.build(tail, opcode).uses(new, GPR).finish();
482        func.named = vec![(41, old), (41, new)];
483        func.entries = vec![(41, tail, new)];
484        let line = before(&func);
485        let kept = about(&mut func, &line);
486
487        // Both in the first block, each from where it was written, and only the new one in the
488        // second, where without the entry the two would start at the same address.
489        let into: Vec<(Inst, Inst)> = kept
490            .iter()
491            .filter(|kept| func.block_of(kept.from) == Some(tail))
492            .map(|kept| (kept.from, kept.to))
493            .collect();
494        assert_eq!(into, [(first, second)], "{kept:?}");
495        let before_it = kept.iter().filter(|kept| func.block_of(kept.from) == Some(head)).count();
496        assert_eq!(before_it, 2, "{kept:?}");
497    }
498
499    #[test]
500    fn a_local_that_shares_its_frame_bytes_is_there_over_its_area_and_nowhere_else() {
501        let (mut func, line) = three(&[]);
502        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
503        let allocation = rucc_regalloc::run(&mut func, &env, "test", true);
504        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
505
506        // Wanted from the first instruction to the second, so in its bytes over the second only,
507        // and the third is where whatever it shares them with may have written over it.
508        let order = &allocation.order;
509        let area = [Range { start: order.early(line[0]), end: order.late(line[1]) }];
510        let kept = of(&func, &line, &allocation, &frame, &[(41, -24, &area)]);
511        assert_eq!(kept, [Kept { decl: 41, at: Where::Frame(-24), from: line[1], to: line[1] }]);
512    }
513
514    #[test]
515    fn a_declaration_that_took_a_value_part_of_the_way_through_holds_it_from_there() {
516        // `int m = a;` with the assignment in front of the third instruction: `a` holds the value
517        // over the whole of its stretch and `m` only from there.
518        let (mut func, line) = three(&[(41, 0)]);
519        func.starts = vec![(42, Reg::virtual_reg(0), line[2])];
520        let kept = about(&mut func, &line);
521        let said: Vec<(u32, Inst, Inst)> =
522            kept.iter().map(|kept| (kept.decl, kept.from, kept.to)).collect();
523        assert_eq!(said, [(41, line[1], line[2]), (42, line[2], line[2])]);
524    }
525
526    #[test]
527    fn a_declaration_that_took_a_value_holds_it_in_the_blocks_its_own_dominates_only() {
528        let mut names = Interner::new();
529        let mut func = Func::new(names.intern("f"));
530        let opcode = Opcode::new(names.intern("x64.nop"));
531        let [head, left, below, right, tail] = std::array::from_fn(|_| func.create_block());
532        let value = func.new_vreg(GPR);
533        func.build(head, opcode).def(value, GPR).finish();
534        let first = func.build(left, opcode).uses(value, GPR).finish();
535        let under = func.build(below, opcode).uses(value, GPR).finish();
536        func.build(right, opcode).uses(value, GPR).finish();
537        func.build(tail, opcode).uses(value, GPR).finish();
538        *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
539        *func.succs_mut(left) = vec![BlockCall::to(below)];
540        *func.succs_mut(below) = vec![BlockCall::to(tail)];
541        *func.succs_mut(right) = vec![BlockCall::to(tail)];
542        func.starts = vec![(42, value, first)];
543        let line = before(&func);
544        let kept = about(&mut func, &line);
545
546        // The block the assignment is in and the one only it leads to. Not the other arm, which
547        // never ran the assignment, and not the join, which the other arm reaches too.
548        let said: Vec<(Inst, Inst)> = kept.iter().map(|kept| (kept.from, kept.to)).collect();
549        assert_eq!(said, [(first, first), (under, under)]);
550    }
551
552    #[test]
553    fn a_function_the_front_end_named_nothing_in_says_nothing() {
554        let (mut func, line) = three(&[]);
555        let kept = about(&mut func, &line);
556        assert!(kept.is_empty(), "nothing to say: {kept:?}");
557    }
558
559    #[test]
560    fn a_block_the_scheduler_reordered_is_read_by_where_the_two_ends_went() {
561        let (mut func, line) = three(&[(41, 0)]);
562        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
563        let allocation = rucc_regalloc::run(&mut func, &env, "test", true);
564        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
565
566        // The first two instructions the other way round, which is what a scheduler leaves behind.
567        // The value is written by what is now the second instruction and read by the third, so the
568        // register holds it over the third only, and the first is before it was written.
569        func.remove_inst(line[0]);
570        func.insert_after(line[1], line[0]);
571        let kept = of(&func, &line, &allocation, &frame, &[]);
572        assert_eq!(kept.len(), 1, "one stretch: {kept:?}");
573        assert_eq!((kept[0].from, kept[0].to), (line[2], line[2]));
574    }
575
576    #[test]
577    fn a_local_live_across_the_whole_of_a_reordered_block_covers_all_of_it() {
578        let (mut func, line) = three(&[]);
579        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
580        let allocation = rucc_regalloc::run(&mut func, &env, "test", true);
581        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
582        func.remove_inst(line[0]);
583        func.insert_after(line[1], line[0]);
584
585        // Live into the block and out of it, so its ends are the block's edges rather than any
586        // instruction, and the stretch is from whatever is first now to whatever is last.
587        let block = func.blocks().next().expect("one block");
588        let (start, end) = allocation.order.bounds(block).expect("laid out");
589        let area = [Range { start, end }];
590        let kept = of(&func, &line, &allocation, &frame, &[(41, -8, &area)]);
591        assert_eq!(kept, [Kept { decl: 41, at: Where::Frame(-8), from: line[1], to: line[2] }]);
592    }
593}