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