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//! # What is left out
28//!
29//! A function whose instructions moved about inside a block after it was allocated gets nothing.
30//! The liveness is counted along the order the allocator laid the function out in, and a scheduler
31//! makes that order no longer the order the block is in, so a stretch worked out from it would name
32//! two instructions that are no longer either side of the value. The check is the walk below, which
33//! notices the moment a surviving instruction is out of order.
34//!
35//! That is `-O2` and above, where the scheduler runs, and `-O0` is what M8 is about. Carrying the
36//! liveness across a schedule is what would lift it, and the register allocator of M4 will want the
37//! same thing, since one that splits a live range has to say where the pieces went too.
38//!
39//! A value the allocator spilled is in the frame over its stretch rather than in a register, which
40//! is as much an answer as the other and is written the same way. A value it spilled in a function
41//! whose alignment the prologue had to force has no answer, because the distance from the call
42//! frame address is not a constant there, which is what `crate::frame` says about a local in the
43//! same function.
44
45use rucc_mir::{Func, Inst, Kept, Where};
46use rucc_regalloc::Allocation;
47use rucc_regalloc::assign::Place;
48use rucc_regalloc::order::Point;
49
50use crate::frame::Frame;
51
52/// Every instruction of a function, in the order they are in, which is what the allocator's
53/// liveness is counted along while that is still the order.
54///
55/// Taken before the allocator rewrites the function, because afterwards the spills, the reloads
56/// and the edge moves are in among them and none of those is an instruction the liveness knows a
57/// point for.
58#[must_use]
59pub fn before(func: &Func) -> Vec<Inst> {
60    func.blocks().flat_map(|block| func.insts(block)).collect()
61}
62
63/// Which declaration is where, over which instructions, or nothing at all for a function the
64/// answer cannot be given about. See the module documentation for which those are.
65#[must_use]
66pub fn of(func: &Func, before: &[Inst], allocation: &Allocation, frame: &Frame) -> Vec<Kept> {
67    if func.named.is_empty() {
68        return Vec::new();
69    }
70    let Some(line) = line(func, before, allocation) else { return Vec::new() };
71    let mut out = Vec::new();
72    for &(decl, reg) in &func.named {
73        let Some(class) = func.class_of(reg) else { continue };
74        let at = match allocation.assignment.place(reg) {
75            Some(Place::Reg(reg)) => Where::Reg { reg, class },
76            Some(Place::Slot(slot)) => match frame.slot_from_frame_base(slot) {
77                Some(at) => Where::Frame(at),
78                None => continue,
79            },
80            None => continue,
81        };
82        let Some(area) = allocation.live.area(reg) else { continue };
83        for piece in area.pieces() {
84            for run in &line {
85                // Strictly after where the value is written and up to and including where it is
86                // last read. Both ends of a piece are points the value is live at, and the front
87                // one is the instruction writing it, which is the one instruction in the piece the
88                // register does not hold the value at the start of.
89                let lo = run.partition_point(|&(point, _)| point <= piece.start);
90                let hi = run.partition_point(|&(point, _)| point <= piece.end);
91                if lo >= hi {
92                    continue;
93                }
94                out.push(Kept { decl, at, from: run[lo].1, to: run[hi - 1].1 });
95            }
96        }
97    }
98    out
99}
100
101/// The instructions the function still has that the liveness knows a point for, one list per block
102/// and each in the order that block is in, or `None` if a block is no longer in the order the
103/// allocator saw it in.
104///
105/// The point is where the instruction reads its operands, which is the smaller of its two, so each
106/// list is sorted by it and can be searched rather than scanned.
107///
108/// A block at a time rather than the whole function at once, because the pass that lays the blocks
109/// out runs between the allocator and here and is free to put them in any order it likes. A block
110/// it moved is still a block whose instructions are in the order they were and are contiguous in
111/// the addresses to come, so the question the liveness answers is still answerable about each of
112/// them on its own. What is not answerable is a stretch that runs from one block into another,
113/// which is why a piece of a live range turns into a stretch per block rather than into one
114/// stretch.
115fn line(func: &Func, before: &[Inst], allocation: &Allocation) -> Option<Vec<Vec<(Point, Inst)>>> {
116    let mut known = vec![false; func.inst_count()];
117    for &inst in before {
118        known[inst.index()] = true;
119    }
120    let mut out = Vec::with_capacity(func.block_count());
121    for block in func.blocks() {
122        let mut run: Vec<(Point, Inst)> = Vec::new();
123        for inst in func.insts(block) {
124            if !known[inst.index()] {
125                continue;
126            }
127            let point = allocation.order.early(inst);
128            if run.last().is_some_and(|&(last, _)| last >= point) {
129                return None;
130            }
131            run.push((point, inst));
132        }
133        if !run.is_empty() {
134            out.push(run);
135        }
136    }
137    Some(out)
138}
139
140#[cfg(test)]
141mod tests {
142    use rucc_base::Interner;
143    use rucc_mir::{BlockCall, Func, Opcode, Reg};
144    use rucc_regalloc::assign::Env;
145    use rucc_target::x86_64::{GPR, REGS, SYSV};
146
147    use super::*;
148    use crate::frame::Layout;
149
150    /// A function of three instructions: two that write a value and one that reads the first of
151    /// them, with the declarations the caller asks for named against its registers.
152    ///
153    /// Three of them rather than two so that the stretch of the first value has an instruction in
154    /// it either side of the one that wrote it, and the second value is one nothing reads.
155    fn three(named: &[(u32, u32)]) -> (Func, Vec<Inst>) {
156        let mut names = Interner::new();
157        let mut func = Func::new(names.intern("f"));
158        let opcode = Opcode::new(names.intern("x64.nop"));
159        let block = func.create_block();
160        let first = func.new_vreg(GPR);
161        let second = func.new_vreg(GPR);
162        func.build(block, opcode).def(first, GPR).finish();
163        func.build(block, opcode).def(second, GPR).finish();
164        func.build(block, opcode).uses(first, GPR).finish();
165        func.named = named.iter().map(|&(decl, reg)| (decl, Reg::virtual_reg(reg))).collect();
166        let line = before(&func);
167        (func, line)
168    }
169
170    /// That function allocated with enough registers to spill nothing, and what this says about it.
171    fn about(func: &mut Func, line: &[Inst]) -> Vec<Kept> {
172        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
173        let allocation = rucc_regalloc::run(func, &env, "test", true);
174        let frame = Frame::of(func, &allocation, &Layout::new(&SYSV, REGS));
175        of(func, line, &allocation, &frame)
176    }
177
178    #[test]
179    fn a_register_holding_a_local_says_so_from_the_instruction_after_the_one_that_wrote_it() {
180        let (mut func, line) = three(&[(41, 0)]);
181        let kept = about(&mut func, &line);
182
183        // Written by the first instruction and read by the third, so the stretch is the second and
184        // the third: the register does not hold the value until the first has run, and the last
185        // instruction that reads it is in the stretch rather than one past the end of it.
186        assert_eq!(kept.len(), 1, "one stretch: {kept:?}");
187        assert_eq!(kept[0].decl, 41);
188        assert_eq!(kept[0].from, line[1], "from the instruction after the one that wrote it");
189        assert_eq!(kept[0].to, line[2], "to the last one that reads it");
190        assert!(matches!(kept[0].at, Where::Reg { .. }), "in a register: {:?}", kept[0].at);
191    }
192
193    #[test]
194    fn a_value_nothing_reads_is_nowhere_worth_saying() {
195        // The second instruction's result is never read, so the value is live only where it is
196        // written and the stretch that would begin after that has nothing in it.
197        let (mut func, line) = three(&[(41, 1)]);
198        let kept = about(&mut func, &line);
199        assert!(kept.is_empty(), "nothing to say: {kept:?}");
200    }
201
202    #[test]
203    fn a_declaration_two_registers_hold_gets_a_stretch_for_each_of_them() {
204        let (mut func, line) = three(&[(41, 0), (41, 1)]);
205        let kept = about(&mut func, &line);
206
207        // The one nothing reads still says nothing, so what is left is the one stretch, and the
208        // point of the case is that one declaration being asked about twice is allowed.
209        assert_eq!(kept.iter().map(|kept| kept.decl).collect::<Vec<u32>>(), vec![41]);
210    }
211
212    #[test]
213    fn a_local_live_from_one_block_into_the_next_gets_a_stretch_in_each_of_them() {
214        let mut names = Interner::new();
215        let mut func = Func::new(names.intern("f"));
216        let opcode = Opcode::new(names.intern("x64.nop"));
217        let head = func.create_block();
218        let tail = func.create_block();
219        let value = func.new_vreg(GPR);
220        func.build(head, opcode).def(value, GPR).finish();
221        let across = func.build(head, opcode).finish();
222        *func.succs_mut(head) = vec![BlockCall::to(tail)];
223        let read = func.build(tail, opcode).uses(value, GPR).finish();
224        func.named = vec![(41, value)];
225        let line = before(&func);
226        let kept = about(&mut func, &line);
227
228        // Live from where it is written to where it is read, and a stretch in each of the two
229        // blocks rather than one that would cover whatever the layout later puts in between.
230        assert_eq!(kept.len(), 2, "one stretch per block: {kept:?}");
231        assert_eq!((kept[0].from, kept[0].to), (across, across), "the rest of the first block");
232        assert_eq!((kept[1].from, kept[1].to), (read, read), "and into the second");
233    }
234
235    #[test]
236    fn a_function_the_front_end_named_nothing_in_says_nothing() {
237        let (mut func, line) = three(&[]);
238        let kept = about(&mut func, &line);
239        assert!(kept.is_empty(), "nothing to say: {kept:?}");
240    }
241
242    #[test]
243    fn a_function_whose_instructions_moved_inside_a_block_after_allocation_says_nothing() {
244        let (mut func, line) = three(&[(41, 0)]);
245        let env = Env::new().with(GPR, &SYSV.int_order[..4], &SYSV.int_order[4..]);
246        let allocation = rucc_regalloc::run(&mut func, &env, "test", true);
247        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
248        assert!(!of(&func, &line, &allocation, &frame).is_empty(), "something to say first");
249
250        // The same function with its first two instructions the other way round, which is what a
251        // scheduler leaves behind and is the shape the allocator's liveness can no longer be read
252        // against, since it is counted along the order the function was in.
253        func.remove_inst(line[0]);
254        func.insert_after(line[1], line[0]);
255        assert!(of(&func, &line, &allocation, &frame).is_empty(), "no longer the order");
256    }
257}