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}