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}