rucc_codegen/slots.rs
1//! One stack slot allocator: every byte a function asks for itself, placed together.
2//!
3//! Design: `spec/optimizer/36-lowering-and-isel.md` section 36.7.
4//!
5//! A frame holds two kinds of thing the function asked for. Locals are what an `alloca` becomes and
6//! the lowering knows about them before anything else runs. Spill slots are what the allocator
7//! gives a value it ran out of registers for, and nothing knows how many of those there are until
8//! it has finished. Placed apart, the frame is the sum of the two areas. Placed together it is the
9//! most either of them needs at any one moment, because two things that are never both wanted can
10//! be the same bytes. That is the same answer the allocator gives about registers and it is the
11//! same reason.
12//!
13//! This runs after allocation and reads the allocator's own liveness rather than working one out.
14//! Running before it would mean guessing which values are spilled, and a guess has to be either
15//! conservative or wrong. Asking again afterwards would mean two answers about one function that
16//! are free to disagree, and the one the machine runs is the allocator's.
17//!
18//! # What a cell is
19//!
20//! A [`Cell`] is a run of bytes in the frame, as wide and as aligned as the widest and strictest
21//! thing in it. [`Slots`] says which cell every local and every spill slot went in, and
22//! [`crate::frame`] is what turns cells into offsets. Nothing else changes: an instruction reading
23//! a local still asks the frame where that local is and gets back an offset, and two locals sharing
24//! a cell get the same one.
25//!
26//! # What may share
27//!
28//! A spill slot holds one value, so where the slot is wanted is where that value is live, and the
29//! allocator has already said where that is.
30//!
31//! A local is harder, because what a local is wanted over is not the live range of anything. The
32//! bytes are reached through an address, the address is a value like any other, and the bytes go on
33//! meaning something for exactly as long as anything can still come by that address. So the
34//! question asked here is where the address gets to, and the answer has to be the whole of it or
35//! the local does not share at all. [`reach`] asks it. An address read as the base of a load or a
36//! store is a read of the local at that instruction and goes no further. An address read by another
37//! address computation is the same local under a second name and is followed. An address read any
38//! other way is one this pass cannot follow to the end, and the local it belongs to is left out.
39//!
40//! Left out is therefore the answer for every local whose address is handed to a call, stored into
41//! memory, or carried between blocks as an argument. That is what section 36.7 means by an address
42//! taken local: not one the program wrote an `&` in front of, which is a question the types
43//! answered and the types are gone by here, but one whose bytes something can reach at a moment
44//! liveness does not know about.
45//!
46//! # Where a local is wanted is not where its address is live
47//!
48//! Knowing which instructions reach a local is only half of it. The address that reaches it is a
49//! value and the object is not, so an address register that dies right after the store through it
50//! says nothing about how long those bytes have to go on holding what was stored. A local written
51//! at one point and read at another has to hold its contents through everything in between, however
52//! little of what is in between mentions the local at all.
53//!
54//! So the area of a local is worked out as its own question over the control flow graph: its bytes
55//! matter at every point that has a touch behind it and a touch in front of it. A point with
56//! nothing in front is one where the object is finished with, and a point with nothing behind is
57//! one where it holds nothing anybody may read, since the contents of a local nothing has written
58//! yet are not contents. The two halves of that question are reachability over the graph rather
59//! than over the line the function was laid out in. Over the line would be wrong for a loop: a
60//! local written at the bottom of a body and read at the top of the next turn is one whose bytes
61//! matter across the header too, and the header is laid out before either of the two touches.
62//!
63//! # The moves count too
64//!
65//! Where a spilled value is live is not quite everywhere its slot is touched. The store that fills
66//! the slot goes after the instruction that wrote the value, the reload that empties it goes before
67//! the instruction that reads it, and the moves an edge turns into go at the end of a block or the
68//! start of one, none of which is a point the value is live at. The edge moves are the ones that
69//! matter: the sequencer put them in an order that works because it was told every place in them
70//! was a different place, and two slots it was told apart are two this pass must not put together
71//! behind its back.
72//!
73//! So the moves are read as well as the liveness. Every edit that names a slot puts a point either
74//! side of where it stands into that slot's area, which is the gap between two points the edit
75//! really sits in, and after that the question is the same question everywhere else in this file.
76//!
77//! # A spill slot is not a variable
78//!
79//! The two kinds are asked about separately, because the reason a frame ever lays two things out
80//! apart that could share is the debugger, and that reason covers one kind and not the other. A
81//! local is a variable somebody wrote down and can ask the value of, so two locals sharing bytes
82//! means a variable that is out of scope reads as whatever took its place, which is what `-O0`
83//! exists not to do and what `-fstack-reuse=none` turns off at every level. A spill slot holds a
84//! value the allocator ran out of registers for, it has no name, nothing can ask for it, and the
85//! only thing that ever reads it is the instruction the allocator wrote. Laying those out one
86//! each buys a debugger nothing and costs a frame everything, since most frames are mostly spill
87//! slots.
88//!
89//! So spill slots share at every level and locals share only where the level says they may. What
90//! says which is whether this pass is handed a [`Reach`]: with one, the locals it followed join
91//! in, and without one every local gets bytes of its own and the spill slots are fitted around
92//! them.
93//!
94//! # How big it is allowed to get
95//!
96//! Fitting each thing into the first cell it does not clash with compares it against the cells so
97//! far, so a function whose things mostly cannot share costs the square of how many there are.
98//! What bounds that is a budget of comparisons rather than a count of things: the fit spends
99//! [`BUDGET`] of them and lays out whatever is left one cell each. A function whose things do
100//! share never comes near it, because what each one is compared against is the cells and not the
101//! things, and the whole point of sharing is that there are far fewer cells than things. lua's
102//! interpreter, which is 2802 slots fitted into 144 cells and the largest function in the corpus,
103//! spends an eighth of the budget and adds a seventh of a second to the file it is in. A function
104//! with that many slots that are all live at once would spend the lot, and it gets the layout it
105//! would have got anyway.
106
107use std::collections::{HashMap, HashSet};
108
109use rucc_base::Interner;
110use rucc_mir::{Func, Inst, Opcode, Reg};
111use rucc_regalloc::Allocation;
112use rucc_regalloc::assign::Place;
113use rucc_regalloc::live::{Area, Live, Range};
114use rucc_regalloc::order::Order;
115use rucc_regalloc::rewrite::At;
116use rucc_target::FrameInsts;
117
118use crate::frame::Local;
119
120/// How many cells the fit may look at before it stops pairing things up and gives everything left
121/// a cell of its own.
122///
123/// See the note on how big it is allowed to get in the module documentation. One unit is one thing
124/// compared against one cell, which is what costs. The largest function in the corpus spends an
125/// eighth of this, so the budget is a guard against a generated file rather than something the
126/// ordinary path meets.
127pub const BUDGET: usize = 1 << 20;
128
129/// One run of bytes in the frame, holding one local, one spill slot, or several of each.
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub struct Cell {
132 /// How many bytes of it there are, which is as many as the largest thing in it needs.
133 pub size: u32,
134 /// What its address has to be a multiple of, which is the strictest thing in it.
135 pub align: u32,
136}
137
138/// Which cell of the frame every local and every spill slot of a function is in.
139#[derive(Debug, Clone, Default, PartialEq, Eq)]
140pub struct Slots {
141 cells: Vec<Cell>,
142 locals: Vec<usize>,
143 slots: Vec<usize>,
144 /// Where each local that went in beside something else is wanted, and `None` for the rest.
145 shared: Vec<Option<Vec<Range>>>,
146}
147
148impl Slots {
149 /// The frame with nothing sharing anything: a cell of its own for every local and every spill
150 /// slot, in the order the two lists are in.
151 ///
152 /// This is the layout there was before this pass, and it is what a frame gets when nothing has
153 /// asked for sharing and what it falls back to on a function with too many slots to pair up
154 /// cheaply.
155 #[must_use]
156 pub fn apart(locals: &[Local], widths: &[u32]) -> Self {
157 let mut cells = Vec::with_capacity(locals.len() + widths.len());
158 for &Local { size, align } in locals {
159 cells.push(Cell { size, align });
160 }
161 for &width in widths {
162 cells.push(Cell { size: width, align: width });
163 }
164 Self {
165 locals: (0..locals.len()).collect(),
166 slots: (locals.len()..cells.len()).collect(),
167 shared: vec![None; locals.len()],
168 cells,
169 }
170 }
171
172 /// The frame with everything that can share sharing, worked out from the allocator's liveness.
173 ///
174 /// `reach` is what [`reach`] said about this function before the allocator ran, or `None` for a
175 /// build whose locals keep bytes of their own, which is `-O0` and `-fstack-reuse=none`. The
176 /// spill slots share either way, for the reason in the module documentation. `widths` is how
177 /// many bytes a slot of each of the allocation's spill slots takes, and `locals` is the
178 /// function's own objects in the order the lowering recorded them. `func` is the function the
179 /// allocator has finished with, which is asked for the shape of its control flow and nothing
180 /// else: the rewrite took the values away but it left every block and every edge where it was.
181 #[must_use]
182 pub fn share(
183 func: &Func,
184 reach: Option<&Reach>,
185 allocation: &Allocation,
186 locals: &[Local],
187 widths: &[u32],
188 ) -> Self {
189 let mut wants = Vec::with_capacity(locals.len() + widths.len());
190 let mut reached = reach
191 .map(|reach| areas(func, reach, &allocation.live, &allocation.order))
192 .unwrap_or_default();
193 for (local, &Local { size, align }) in locals.iter().enumerate() {
194 let area = reached.get_mut(local).and_then(Option::take);
195 wants.push(Want { what: What::Local(local), size, align, area });
196 }
197 let held = spilled(allocation, widths.len());
198 let moved = moved(allocation, widths.len());
199 for (slot, &width) in widths.iter().enumerate() {
200 let area = held[slot]
201 .and_then(|reg| allocation.live.area(reg))
202 .map(|live| merged(live.pieces().chain(moved[slot].iter().copied())));
203 wants.push(Want { what: What::Slot(slot), size: width, align: width, area });
204 }
205 fit(wants, locals.len(), widths.len(), BUDGET)
206 }
207
208 /// The cells the frame is made of, which is what [`crate::frame`] places.
209 #[must_use]
210 pub fn cells(&self) -> &[Cell] {
211 &self.cells
212 }
213
214 /// Which cell a local is in.
215 #[must_use]
216 pub fn local(&self, local: usize) -> Option<usize> {
217 self.locals.get(local).copied()
218 }
219
220 /// Which cell a spill slot is in.
221 #[must_use]
222 pub fn slot(&self, slot: u32) -> Option<usize> {
223 self.slots.get(usize::try_from(slot).ok()?).copied()
224 }
225
226 /// Where a local is wanted, in the allocator's points, if its cell holds something else too,
227 /// and `None` for a local whose bytes are its own.
228 ///
229 /// The bytes of a local that shares are only its over this area. Outside it they hold
230 /// whatever else went in the cell, which is why the debugging information asks: a place given
231 /// for the whole function would have a debugger print the other thing under this one's name.
232 #[must_use]
233 pub fn shared(&self, local: usize) -> Option<&[Range]> {
234 self.shared.get(local)?.as_deref()
235 }
236
237 /// How many cells were saved by sharing, which is how many things went in beside something
238 /// else.
239 ///
240 /// This is a count rather than a number of bytes, because how many bytes it saved is the
241 /// difference between two frames and a frame is not worked out here.
242 #[must_use]
243 pub fn saved(&self) -> usize {
244 self.locals.len() + self.slots.len() - self.cells.len()
245 }
246}
247
248/// One thing that wants bytes in the frame, and everywhere it wants them.
249#[derive(Debug)]
250struct Want {
251 what: What,
252 size: u32,
253 align: u32,
254 /// Where it is wanted, or `None` for one this pass could not follow, which shares with nothing.
255 area: Option<Vec<Range>>,
256}
257
258/// Which of the two lists a want came off.
259#[derive(Debug, Clone, Copy)]
260enum What {
261 Local(usize),
262 Slot(usize),
263}
264
265/// Fits every want into the fewest cells, largest and strictest first.
266///
267/// Largest first because a cell only ever grows to hold what goes in it, and starting with the
268/// small ones means growing a cell to several times the size of the thing that opened it, which
269/// leaves the same bytes taken and a worse chance for everything after. The order is settled
270/// entirely by the want rather than partly by which came first, so the same function lays out the
271/// same way every time.
272///
273/// What `budget` is is comparisons of one thing against one cell, which is what costs. Past that
274/// everything left opens a cell of its own, which is the layout a frame had before this pass
275/// existed, and the wants are in a settled order so which ones those are is settled too.
276fn fit(mut wants: Vec<Want>, locals: usize, slots: usize, mut budget: usize) -> Slots {
277 let mut order: Vec<usize> = (0..wants.len()).collect();
278 order.sort_by_key(|&want| {
279 let Want { size, align, .. } = wants[want];
280 (std::cmp::Reverse(align), std::cmp::Reverse(size), want)
281 });
282
283 let mut cells: Vec<Cell> = Vec::new();
284 // `None` is a cell nothing else may go in, which is what a thing this pass could not follow
285 // opens. A cell with an area is one anything that does not clash with that area may join.
286 let mut busy: Vec<Option<Vec<Range>>> = Vec::new();
287 let mut of_local = vec![0; locals];
288 let mut of_slot = vec![0; slots];
289 let mut areas = vec![None; locals];
290 let mut held = Vec::new();
291 for want in order {
292 let Want { what, size, align, area } = std::mem::replace(
293 &mut wants[want],
294 Want { what: What::Local(0), size: 0, align: 0, area: None },
295 );
296 let mut into = None;
297 if let Some(area) = &area {
298 for (cell, held) in busy.iter().enumerate() {
299 if budget == 0 {
300 break;
301 }
302 budget -= 1;
303 if held.as_ref().is_some_and(|held| !clashes(held, area)) {
304 into = Some(cell);
305 break;
306 }
307 }
308 }
309 // Kept for a local as well as handed to the cell, since whether it shared is only known once
310 // everything has been fitted, and one that did is asked about again. See [`Slots::shared`].
311 let mine = if let What::Local(_) = what { area.clone() } else { None };
312 let cell = match into {
313 Some(cell) => {
314 cells[cell].size = cells[cell].size.max(size);
315 cells[cell].align = cells[cell].align.max(align);
316 let held = busy[cell].take().unwrap_or_default();
317 busy[cell] = Some(merged(held.into_iter().chain(area.into_iter().flatten())));
318 cell
319 }
320 None => {
321 cells.push(Cell { size, align });
322 busy.push(area);
323 cells.len() - 1
324 }
325 };
326 match what {
327 What::Local(local) => {
328 of_local[local] = cell;
329 areas[local] = mine;
330 }
331 What::Slot(slot) => of_slot[slot] = cell,
332 }
333 if held.len() <= cell {
334 held.resize(cell + 1, 0);
335 }
336 held[cell] += 1;
337 }
338 let shared = areas
339 .into_iter()
340 .zip(&of_local)
341 .map(|(area, &cell)| area.filter(|_| held[cell] > 1))
342 .collect();
343 Slots { cells, locals: of_local, slots: of_slot, shared }
344}
345
346/// Which value the allocator put in each spill slot, by slot number.
347///
348/// A slot holds one value, because the allocator takes a fresh one every time it spills, so this is
349/// the assignment read the other way round.
350fn spilled(allocation: &Allocation, slots: usize) -> Vec<Option<Reg>> {
351 let mut held = vec![None; slots];
352 for (reg, place) in allocation.assignment.placed() {
353 if let Place::Slot(slot) = place {
354 if let Some(at) = usize::try_from(slot).ok().and_then(|slot| held.get_mut(slot)) {
355 *at = Some(reg);
356 }
357 }
358 }
359 held
360}
361
362/// What carries the address of each of a function's locals, or `None` for one whose address gets
363/// away somewhere this pass cannot follow.
364///
365/// Worked out before allocation, because it is a question about values and a value is written once
366/// only until the allocator's rewrite has been through. Read after it, because that is when the
367/// liveness these names are looked up in exists.
368#[derive(Debug, Clone, Default)]
369pub struct Reach {
370 through: Vec<Option<Carried>>,
371}
372
373impl Reach {
374 /// Every point one local is touched at, which is where its address is live and where an
375 /// instruction that swallowed the address stands.
376 fn touches(&self, local: usize, live: &Live, order: &Order) -> Option<Vec<Range>> {
377 let held = self.through.get(local)?.as_ref()?;
378 let mut spots: Vec<Range> = Vec::new();
379 for ® in &held.regs {
380 spots.extend(live.area(reg).into_iter().flat_map(Area::pieces));
381 }
382 for &inst in &held.at {
383 spots.push(Range { start: order.early(inst), end: order.late(inst) });
384 }
385 Some(spots)
386 }
387
388 /// Whether a local may share its bytes with anything, which is what the tests ask.
389 #[must_use]
390 pub fn shares(&self, local: usize) -> bool {
391 self.through.get(local).is_some_and(Option::is_some)
392 }
393}
394
395/// Everywhere the bytes of each local have to go on holding what was put in them.
396///
397/// A point counts if a touch of that local can have happened before it and another can still
398/// happen after it. Before is reachability forward through the graph from the blocks that touch
399/// the local, after is the same walk backwards, and the bytes matter where the two meet. See the
400/// note in the module documentation on why this is asked over the graph and not over the line the
401/// function was laid out in.
402///
403/// A local this pass could not follow the address of comes back `None`, which is the answer that
404/// shares with nothing.
405fn areas(func: &Func, reach: &Reach, live: &Live, order: &Order) -> Vec<Option<Vec<Range>>> {
406 let blocks = order.blocks();
407 let count = reach.through.len();
408 let words = count.div_ceil(64);
409
410 // Where each block starts, which is ascending, so the block a point is in is a search.
411 let starts: Vec<u32> = blocks.iter().map(|&block| order.start(block)).collect();
412 let holding = |point: u32| starts.partition_point(|&start| start <= point).saturating_sub(1);
413
414 // Which locals each block touches, as bits for the walk and as a range for the answer.
415 let mut touched = vec![vec![0u64; words]; blocks.len()];
416 let mut inside: Vec<Vec<(usize, Range)>> = vec![Vec::new(); blocks.len()];
417 for local in 0..count {
418 let Some(spots) = reach.touches(local, live, order) else { continue };
419 for spot in spots {
420 for at in holding(spot.start)..=holding(spot.end) {
421 let block = blocks[at];
422 let start = spot.start.max(order.start(block));
423 let end = spot.end.min(order.end(block));
424 touched[at][local / 64] |= 1 << (local % 64);
425 inside[at].push((local, Range { start, end }));
426 }
427 }
428 }
429
430 // One range per block per local, from the first touch in the block to the last. A block runs
431 // top to bottom, so whatever sits between two touches of the same local is between them in the
432 // run as well, and the bytes have to have held what they hold all the way through it.
433 for spots in inside.iter_mut() {
434 spots.sort_unstable_by_key(|&(local, Range { start, .. })| (local, start));
435 let mut kept = 0;
436 for at in 1..spots.len() {
437 if spots[at].0 == spots[kept].0 {
438 spots[kept].1.end = spots[kept].1.end.max(spots[at].1.end);
439 } else {
440 kept += 1;
441 spots[kept] = spots[at];
442 }
443 }
444 spots.truncate(spots.len().min(kept + 1));
445 }
446
447 // The graph, by position in the line rather than by block, because everything else here is.
448 let mut place = vec![0usize; func.block_count()];
449 for (at, &block) in blocks.iter().enumerate() {
450 place[block.index()] = at;
451 }
452 let mut ahead: Vec<Vec<usize>> = vec![Vec::new(); blocks.len()];
453 let mut behind: Vec<Vec<usize>> = vec![Vec::new(); blocks.len()];
454 for (at, &block) in blocks.iter().enumerate() {
455 for call in &func[block].succs {
456 let to = place[call.block.index()];
457 ahead[at].push(to);
458 behind[to].push(at);
459 }
460 }
461
462 let written = spread(&behind, &touched, words, true);
463 let read = spread(&ahead, &touched, words, false);
464
465 let mut out = vec![None; count];
466 for (local, pieces) in out.iter_mut().enumerate() {
467 if reach.shares(local) {
468 *pieces = Some(Vec::new());
469 }
470 }
471 for (at, &block) in blocks.iter().enumerate() {
472 let whole = Range { start: order.start(block), end: order.end(block) };
473 for word in 0..words {
474 let mut bits = written[at][word] & read[at][word];
475 while bits != 0 {
476 let local = word * 64 + bits.trailing_zeros() as usize;
477 bits &= bits - 1;
478 if let Some(pieces) = out[local].as_mut() {
479 pieces.push(whole);
480 }
481 }
482 }
483 // A block that touches the local is covered from the touch, or from the top of the block
484 // if something above already wrote it, and to the touch, or to the bottom if something
485 // below still reads it.
486 for &(local, spot) in &inside[at] {
487 let held = |bits: &[Vec<u64>]| bits[at][local / 64] & (1 << (local % 64)) != 0;
488 let start = if held(&written) { whole.start } else { spot.start };
489 let end = if held(&read) { whole.end } else { spot.end };
490 if let Some(pieces) = out[local].as_mut() {
491 pieces.push(Range { start, end });
492 }
493 }
494 }
495 for pieces in out.iter_mut().flatten() {
496 *pieces = merged(std::mem::take(pieces));
497 }
498 out
499}
500
501/// Which locals a touch of can reach the start of each block, following the given edges.
502///
503/// One walk stands for both directions. Handed the edges into each block it says which locals were
504/// touched somewhere above, and handed the edges out of each block it says which are touched
505/// somewhere below. The sweep goes the way the edges point so that a straight line settles in one
506/// pass and only a loop costs a second.
507///
508/// A block is allowed to be its own neighbour, which is what a loop of one block is, and the row it
509/// is working on is a copy for that reason. Reading a block's own answer back is a no change either
510/// way, since the answer being built is the one being read, but what a block touches does come back
511/// to itself around a back edge and that is the half that has to arrive. tamnd/rucc#1207.
512fn spread(
513 edges: &[Vec<usize>],
514 touched: &[Vec<u64>],
515 words: usize,
516 forward: bool,
517) -> Vec<Vec<u64>> {
518 let mut out = vec![vec![0u64; words]; edges.len()];
519 let mut going = true;
520 while going {
521 going = false;
522 for at in 0..edges.len() {
523 let at = if forward { at } else { edges.len() - 1 - at };
524 let mut row = out[at].clone();
525 for &from in &edges[at] {
526 for word in 0..words {
527 let had = row[word];
528 row[word] |= out[from][word] | touched[from][word];
529 going |= row[word] != had;
530 }
531 }
532 out[at] = row;
533 }
534 }
535 out
536}
537
538/// Everywhere one local is reached from.
539#[derive(Debug, Clone, Default)]
540struct Carried {
541 /// The values that hold its address.
542 regs: Vec<Reg>,
543 /// The instructions that reach it with no value in between, which is what an address folded
544 /// into its reader leaves behind.
545 at: Vec<Inst>,
546}
547
548/// Follows the address of every local of a function as far as it goes.
549///
550/// `addresses` is the list [`crate::lower`] built and [`crate::fold`] rewrote, which says which
551/// instruction carries the address of which local. `count` is how many locals there are, since a
552/// local nothing on that list names is one this has no account of rather than one nothing touches.
553///
554/// Run after the fold and before allocation. After the fold because an address that ended up inside
555/// its reader is an address no value holds and this has to see it that way. Before allocation
556/// because every answer here is about a virtual register, and the rewrite the allocator ends with
557/// is what stops there being one.
558#[must_use]
559pub fn reach(
560 func: &Func,
561 addresses: &[(Inst, usize)],
562 count: usize,
563 insts: &FrameInsts,
564 names: &mut Interner,
565) -> Reach {
566 let lea = Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.lea)));
567 let mut through: Vec<Option<Carried>> = vec![None; count];
568 for &(inst, local) in addresses {
569 let Some(held) = through.get_mut(local) else { continue };
570 let held = held.get_or_insert_with(Carried::default);
571 // Either the `lea` the lowering wrote, whose result is the address and goes on from here,
572 // or a reader the fold put the address inside, which touches the local where it stands and
573 // hands nothing on. The opcode is the whole of the difference: a reader that is itself a
574 // `lea` really does hand an address on, and this reads it as one.
575 if func[inst].opcode == lea {
576 match def(func, inst) {
577 Some(reg) => held.regs.push(reg),
578 None => {
579 through[local] = None;
580 continue;
581 }
582 }
583 }
584 // On the list either way, so that a local whose address nothing reads is still wanted where
585 // the address of it was taken rather than nowhere at all.
586 held.at.push(inst);
587 }
588
589 let readers = readers(func);
590 let crossing = crossing(func);
591 for held in &mut through {
592 if let Some(carried) = held.take() {
593 *held = follow(func, lea, &readers, &crossing, carried);
594 }
595 }
596 Reach { through }
597}
598
599/// Follows every address a local is reached through to every value that address becomes.
600///
601/// Gives back nothing for a local whose address is read some way this cannot account for, which is
602/// any way but as the base or the index of a memory operand. A call argument is one of those, a
603/// value stored into memory is another, and so is a value carried into a block as an argument,
604/// which is the one that is not an operand at all.
605fn follow(
606 func: &Func,
607 lea: Opcode,
608 readers: &HashMap<Reg, Vec<Inst>>,
609 crossing: &HashSet<Reg>,
610 mut held: Carried,
611) -> Option<Carried> {
612 let mut seen: HashSet<Reg> = held.regs.iter().copied().collect();
613 let mut queue = held.regs.clone();
614 while let Some(reg) = queue.pop() {
615 if crossing.contains(®) {
616 return None;
617 }
618 for &inst in readers.get(®).map(Vec::as_slice).unwrap_or_default() {
619 if !addressed(func, inst, reg) {
620 return None;
621 }
622 if func[inst].opcode == lea {
623 let next = def(func, inst)?;
624 if seen.insert(next) {
625 held.regs.push(next);
626 queue.push(next);
627 }
628 }
629 }
630 }
631 Some(held)
632}
633
634/// Whether every read of a value by an instruction is as part of the address it works on.
635///
636/// Anything else is a read this pass cannot follow: the value has gone somewhere that is not an
637/// address into this frame any more, and where its bytes are reached from afterwards is no longer a
638/// question about liveness.
639fn addressed(func: &Func, inst: Inst, reg: Reg) -> bool {
640 let data = &func[inst];
641 let Some(mem) = data.mem else { return false };
642 let amode = func[mem];
643 func[data.operands].iter().enumerate().all(|(at, operand)| {
644 if operand.reg != reg || operand.role.is_def() {
645 return true;
646 }
647 let at = u8::try_from(at).ok();
648 at.is_some() && (amode.base == at || amode.index == at)
649 })
650}
651
652/// The one virtual register an instruction writes, or nothing when it writes none or several.
653fn def(func: &Func, inst: Inst) -> Option<Reg> {
654 let mut found = None;
655 for operand in &func[func[inst].operands] {
656 if !operand.role.is_def() {
657 continue;
658 }
659 if operand.reg.number().is_none() || found.is_some() {
660 return None;
661 }
662 found = Some(operand.reg);
663 }
664 found
665}
666
667/// Which instructions read each virtual register.
668fn readers(func: &Func) -> HashMap<Reg, Vec<Inst>> {
669 let mut readers: HashMap<Reg, Vec<Inst>> = HashMap::new();
670 for block in func.blocks() {
671 for inst in func.insts(block) {
672 for operand in &func[func[inst].operands] {
673 if operand.role.is_def() || operand.reg.number().is_none() {
674 continue;
675 }
676 let at = readers.entry(operand.reg).or_default();
677 if at.last() != Some(&inst) {
678 at.push(inst);
679 }
680 }
681 }
682 }
683 readers
684}
685
686/// Every virtual register that goes between blocks, as an argument an edge carries or as a
687/// parameter one arrives in.
688///
689/// These are the reads that are not operands, so the walk above would not see them, and an address
690/// that goes round a loop this way is one whose local is left out rather than one followed into a
691/// second name.
692fn crossing(func: &Func) -> HashSet<Reg> {
693 let mut crossing = HashSet::new();
694 for block in func.blocks() {
695 crossing.extend(func[block].params.iter().map(|param| param.reg));
696 for call in &func[block].succs {
697 crossing.extend(call.args.iter().copied());
698 }
699 }
700 crossing
701}
702
703/// Where the moves the allocator handed back touch each slot of the frame.
704///
705/// A point either side of where each of them stands, which is the gap between two points the move
706/// really goes in. See the note on the moves in the module documentation.
707fn moved(allocation: &Allocation, slots: usize) -> Vec<Vec<Range>> {
708 let order = &allocation.order;
709 let mut moved = vec![Vec::new(); slots];
710 for edit in &allocation.edits {
711 let at = match edit.at {
712 At::Before(inst) => order.early(inst),
713 At::After(inst) => order.late(inst),
714 At::StartOf(block) => order.start(block),
715 At::EndOf(block) => order.end(block),
716 };
717 let around =
718 Range { start: at.saturating_sub(1), end: at.saturating_add(1).min(order.points()) };
719 for place in [edit.mov.to, edit.mov.from] {
720 if let Place::Slot(slot) = place {
721 if let Some(at) = usize::try_from(slot).ok().and_then(|slot| moved.get_mut(slot)) {
722 at.push(around);
723 }
724 }
725 }
726 }
727 moved
728}
729
730/// The same stretches of the function, in order, with everything that touches joined up.
731fn merged(pieces: impl IntoIterator<Item = Range>) -> Vec<Range> {
732 let mut pieces: Vec<Range> = pieces.into_iter().collect();
733 pieces.sort_by_key(|piece| (piece.start, piece.end));
734 let mut merged: Vec<Range> = Vec::with_capacity(pieces.len());
735 for piece in pieces {
736 match merged.last_mut() {
737 Some(last) if piece.start <= last.end => last.end = last.end.max(piece.end),
738 _ => merged.push(piece),
739 }
740 }
741 merged
742}
743
744/// Whether two stretches of a function are both wanted anywhere, which is what stops two things
745/// sharing a cell.
746///
747/// Both lists are in order and neither is long, so this walks them together and stops at the first
748/// pair that touches rather than comparing every piece with every other.
749fn clashes(one: &[Range], two: &[Range]) -> bool {
750 let (mut mine, mut theirs) = (0, 0);
751 while mine < one.len() && theirs < two.len() {
752 if one[mine].overlaps(two[theirs]) {
753 return true;
754 }
755 if one[mine].end < two[theirs].end {
756 mine += 1;
757 } else {
758 theirs += 1;
759 }
760 }
761 false
762}
763
764#[cfg(test)]
765mod tests {
766 use rucc_base::Interner;
767 use rucc_mir::{Block, BlockCall, Mem, Operand};
768 use rucc_regalloc::assign::Env;
769 use rucc_regalloc::order::Point;
770 use rucc_target::x86_64::{FRAME, GPR, REGS, SYSV};
771
772 use super::*;
773 use crate::frame::{Frame, Layout};
774
775 /// A function being built, with the names and the opcodes a test needs to hand.
776 struct Building {
777 names: Interner,
778 func: Func,
779 lea: Opcode,
780 nop: Opcode,
781 addresses: Vec<(Inst, usize)>,
782 }
783
784 impl Building {
785 /// An empty function of one block.
786 fn new() -> (Self, Block) {
787 let mut names = Interner::new();
788 let func = Func::new(names.intern("f"));
789 let lea = Opcode::new(names.intern(&format!("{}{}", FRAME.prefix, FRAME.lea)));
790 let nop = Opcode::new(names.intern("x64.nop"));
791 let mut building = Self { names, func, lea, nop, addresses: Vec::new() };
792 let block = building.func.create_block();
793 (building, block)
794 }
795
796 /// The address of a local, taken the way the lowering takes one: a `lea` off the stack
797 /// pointer with nothing in its displacement yet.
798 fn local(&mut self, block: Block, which: usize) -> Reg {
799 let sp = Operand::read(Reg::physical(SYSV.stack_pointer), GPR);
800 let reg = self.func.new_vreg(GPR);
801 let inst = self.func.build(block, self.lea).def(reg, GPR).mem(Mem::at(sp)).finish();
802 self.addresses.push((inst, which));
803 reg
804 }
805
806 /// An instruction that reads a local through its address, which is every ordinary use of
807 /// one.
808 fn through(&mut self, block: Block, addr: Reg) {
809 let at = Operand::read(addr, GPR);
810 self.func.build(block, self.nop).mem(Mem::at(at)).finish();
811 }
812
813 /// An instruction that reads a value as a value, which is what handing an address to a
814 /// call looks like from here.
815 fn held(&mut self, block: Block, reg: Reg) {
816 self.func.build(block, self.nop).uses(reg, GPR).finish();
817 }
818
819 /// A value written and then read, which is one more thing wanting a register in between.
820 fn value(&mut self, block: Block) -> Reg {
821 let reg = self.func.new_vreg(GPR);
822 self.func.build(block, self.nop).def(reg, GPR).finish();
823 reg
824 }
825
826 /// What this pass says about the function, and then what the allocator says, in that
827 /// order because the first question is about values and the second takes them away.
828 fn allocate(&mut self, locals: usize, registers: usize) -> (Reach, Allocation) {
829 let reach = reach(&self.func, &self.addresses, locals, &FRAME, &mut self.names);
830 let env =
831 Env::new().with(GPR, &SYSV.int_order[..registers], &SYSV.int_order[registers..]);
832 let allocation = rucc_regalloc::run(&mut self.func, &env, "test", true);
833 (reach, allocation)
834 }
835 }
836
837 /// A local of one word, which is what most of them are.
838 const WORD: Local = Local { size: 8, align: 8 };
839
840 #[test]
841 fn two_locals_that_are_never_both_wanted_are_the_same_bytes() {
842 let (mut building, block) = Building::new();
843 let first = building.local(block, 0);
844 building.through(block, first);
845 let second = building.local(block, 1);
846 building.through(block, second);
847 let (reach, allocation) = building.allocate(2, 4);
848
849 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
850 assert_eq!(plan.cells().len(), 1, "one run of bytes for the two of them");
851 assert_eq!(plan.local(0), plan.local(1));
852 assert_eq!(plan.saved(), 1);
853 }
854
855 #[test]
856 fn a_local_that_went_in_beside_another_says_where_it_is_wanted_and_one_alone_does_not() {
857 let (mut building, block) = Building::new();
858 let first = building.local(block, 0);
859 building.through(block, first);
860 let second = building.local(block, 1);
861 building.through(block, second);
862 let (reach, allocation) = building.allocate(2, 4);
863
864 // Each of the two is wanted over a stretch the other is not, and it is those stretches the
865 // debugging information gives each of them a place over rather than the whole function.
866 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
867 let (one, two) = (plan.shared(0).expect("shares"), plan.shared(1).expect("shares"));
868 assert!(!one.is_empty() && !two.is_empty());
869 assert!(!clashes(one, two), "wanted apart: {one:?} and {two:?}");
870
871 // The same function with nothing allowed to share, where each local's bytes are its own
872 // over the whole of it.
873 let plan = Slots::share(&building.func, None, &allocation, &[WORD, WORD], &[]);
874 assert_eq!((plan.shared(0), plan.shared(1)), (None, None));
875 }
876
877 #[test]
878 fn two_locals_that_are_both_wanted_at_once_are_not() {
879 let (mut building, block) = Building::new();
880 let first = building.local(block, 0);
881 let second = building.local(block, 1);
882 // Both addresses are live at this point, which is the whole of the difference from the
883 // test above.
884 building.through(block, first);
885 building.through(block, second);
886 let (reach, allocation) = building.allocate(2, 4);
887
888 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
889 assert_eq!(plan.cells().len(), 2);
890 assert_ne!(plan.local(0), plan.local(1));
891 assert_eq!(plan.saved(), 0);
892 }
893
894 #[test]
895 fn a_local_and_a_spilled_value_that_do_not_meet_share_one_run_of_bytes() {
896 let (mut building, block) = Building::new();
897 let addr = building.local(block, 0);
898 building.through(block, addr);
899 // Three values wanted at once with two registers to hand out, after the local is finished
900 // with, so what spills is spilled over a stretch the local is not wanted over.
901 let values: Vec<Reg> = (0..3).map(|_| building.value(block)).collect();
902 for ® in &values {
903 building.held(block, reg);
904 }
905 let (reach, allocation) = building.allocate(1, 2);
906
907 assert_eq!(allocation.assignment.spilled(), 1, "one value went to the stack");
908 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD], &[8]);
909 assert_eq!(plan.cells().len(), 1);
910 assert_eq!(plan.local(0), plan.slot(0));
911 }
912
913 #[test]
914 fn a_local_whose_address_is_handed_to_something_shares_with_nothing() {
915 let (mut building, block) = Building::new();
916 let first = building.local(block, 0);
917 // Read as a value rather than as an address, which is what a call argument is and is the
918 // point past which this pass cannot say where the bytes are reached from.
919 building.held(block, first);
920 let second = building.local(block, 1);
921 building.through(block, second);
922 let (reach, allocation) = building.allocate(2, 4);
923
924 assert!(!reach.shares(0), "an address that got away");
925 assert!(reach.shares(1));
926 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
927 assert_eq!(plan.cells().len(), 2);
928 assert_ne!(plan.local(0), plan.local(1));
929 }
930
931 #[test]
932 fn a_local_whose_address_is_carried_into_a_block_shares_with_nothing() {
933 let (mut building, block) = Building::new();
934 let addr = building.local(block, 0);
935 let next = building.func.create_block();
936 let param = building.func.append_param(next, GPR);
937 building.func.build(block, building.nop).finish();
938 building.func.succs_mut(block).push(BlockCall::with(next, vec![addr]));
939 building.through(next, param);
940 let (reach, _) = building.allocate(1, 4);
941
942 assert!(!reach.shares(0), "an address that goes between blocks");
943 }
944
945 #[test]
946 fn a_local_touched_again_later_keeps_its_bytes_over_everything_in_between() {
947 let (mut building, block) = Building::new();
948 let first = building.local(block, 0);
949 building.through(block, first);
950 // Another local in the stretch between the two touches of the first one. Nothing mentions
951 // the first local in here, which is exactly the case: it is not being read, but what it
952 // holds is still wanted below, so these cannot be the same bytes.
953 let second = building.local(block, 1);
954 building.through(block, second);
955 // The first local again, reached through an address worked out a second time.
956 let again = building.local(block, 0);
957 building.through(block, again);
958 let (reach, allocation) = building.allocate(2, 4);
959
960 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
961 assert_ne!(plan.local(0), plan.local(1));
962 assert_eq!(plan.saved(), 0);
963 }
964
965 #[test]
966 fn a_local_touched_in_a_loop_keeps_its_bytes_over_the_rest_of_the_loop() {
967 let (mut building, block) = Building::new();
968 let header = building.func.create_block();
969 let body = building.func.create_block();
970 building.func.build(block, building.nop).finish();
971 building.func.succs_mut(block).push(BlockCall::to(header));
972
973 // The header is laid out before the body and touches a local of its own.
974 let held = building.local(header, 1);
975 building.through(header, held);
976 building.func.build(header, building.nop).finish();
977 building.func.succs_mut(header).push(BlockCall::to(body));
978
979 // The body touches the other one, every turn of the loop, and the header runs between one
980 // turn and the next. So the body's local is wanted over the header as well, which is a
981 // thing only the edges say: in the line the function is laid out in, the header is above
982 // the only touch there is.
983 let addr = building.local(body, 0);
984 building.through(body, addr);
985 building.func.build(body, building.nop).finish();
986 building.func.succs_mut(body).push(BlockCall::to(header));
987 let (reach, allocation) = building.allocate(2, 4);
988
989 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
990 assert_ne!(plan.local(0), plan.local(1));
991 }
992
993 /// A loop of one block, which is a block that is its own predecessor and its own successor.
994 /// The walk over the graph has to take that rather than fall over it, and what comes back is
995 /// the same answer the two block loop above gets: the body runs again, so a local touched at
996 /// the bottom of it is wanted at the top. tamnd/rucc#1207.
997 #[test]
998 fn a_block_that_is_its_own_neighbour_is_a_loop_like_any_other() {
999 let (mut building, block) = Building::new();
1000 let loops = building.func.create_block();
1001 building.func.build(block, building.nop).finish();
1002 building.func.succs_mut(block).push(BlockCall::to(loops));
1003
1004 // One local touched at the top of the block and the other at the bottom. The edge back to
1005 // the top is what puts the second one over the first.
1006 let held = building.local(loops, 1);
1007 building.through(loops, held);
1008 let addr = building.local(loops, 0);
1009 building.through(loops, addr);
1010 building.func.build(loops, building.nop).finish();
1011 building.func.succs_mut(loops).push(BlockCall::to(loops));
1012 let (reach, allocation) = building.allocate(2, 4);
1013
1014 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
1015 assert_ne!(plan.local(0), plan.local(1));
1016 }
1017
1018 #[test]
1019 fn an_address_a_second_address_computation_reads_is_the_same_local_followed_on() {
1020 let (mut building, block) = Building::new();
1021 let first = building.local(block, 0);
1022 // `lea` off a `lea`, which is what the address of a field of a local is. The local is
1023 // wanted wherever the second address is, not only where the first one is.
1024 let derived = building.func.new_vreg(GPR);
1025 let at = Operand::read(first, GPR);
1026 building.func.build(block, building.lea).def(derived, GPR).mem(Mem::at(at)).finish();
1027 let second = building.local(block, 1);
1028 building.through(block, second);
1029 building.through(block, derived);
1030 let (reach, allocation) = building.allocate(2, 4);
1031
1032 assert!(reach.shares(0), "a derived address is still an address into this frame");
1033 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
1034 assert_eq!(plan.cells().len(), 2, "the two locals are wanted at once after all");
1035 }
1036
1037 #[test]
1038 fn a_cell_two_things_share_is_as_wide_and_as_strict_as_both_of_them() {
1039 let (mut building, block) = Building::new();
1040 let first = building.local(block, 0);
1041 building.through(block, first);
1042 let second = building.local(block, 1);
1043 building.through(block, second);
1044 let (reach, allocation) = building.allocate(2, 4);
1045
1046 let narrow = Local { size: 4, align: 4 };
1047 let wide = Local { size: 16, align: 16 };
1048 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[narrow, wide], &[]);
1049 assert_eq!(plan.cells(), [Cell { size: 16, align: 16 }]);
1050 assert_eq!(plan.local(0), plan.local(1));
1051 }
1052
1053 #[test]
1054 fn a_local_nothing_on_the_address_list_names_shares_with_nothing() {
1055 let (mut building, block) = Building::new();
1056 let addr = building.local(block, 0);
1057 building.through(block, addr);
1058 let (reach, allocation) = building.allocate(2, 4);
1059
1060 // A list with nothing on it for a local is this pass having no account of it rather than
1061 // a local nothing touches, so it keeps bytes of its own.
1062 assert!(!reach.shares(1));
1063 let plan = Slots::share(&building.func, Some(&reach), &allocation, &[WORD, WORD], &[]);
1064 assert_eq!(plan.cells().len(), 2);
1065 }
1066
1067 #[test]
1068 fn the_frame_with_nothing_sharing_gives_every_local_and_every_slot_a_run_of_its_own() {
1069 let plan = Slots::apart(&[WORD, Local { size: 4, align: 4 }], &[8, 16]);
1070
1071 assert_eq!(plan.cells().len(), 4);
1072 assert_eq!(plan.saved(), 0);
1073 assert_eq!((plan.local(0), plan.local(1)), (Some(0), Some(1)));
1074 assert_eq!((plan.slot(0), plan.slot(1)), (Some(2), Some(3)));
1075 assert_eq!(plan.cells()[3], Cell { size: 16, align: 16 });
1076 }
1077
1078 #[test]
1079 fn a_frame_whose_locals_share_is_smaller_and_puts_them_at_the_same_offset() {
1080 let (mut building, block) = Building::new();
1081 let first = building.local(block, 0);
1082 building.through(block, first);
1083 let second = building.local(block, 1);
1084 building.through(block, second);
1085 let (reach, allocation) = building.allocate(2, 4);
1086
1087 // Not a leaf, so the frame is taken rather than kept in the red zone and its size is a
1088 // number rather than nothing, and big enough that the convention's alignment does not
1089 // round the difference away.
1090 let locals = [Local { size: 64, align: 8 }; 2];
1091 let base = Layout { leaf: false, locals: &locals, ..Layout::new(&SYSV, REGS) };
1092 let apart = Frame::of(&building.func, &allocation, &base);
1093 let plan = Slots::share(&building.func, Some(&reach), &allocation, &locals, &[]);
1094 let layout = Layout { share: Some(&plan), ..base };
1095 let together = Frame::of(&building.func, &allocation, &layout);
1096
1097 assert_ne!(apart.local(0), apart.local(1));
1098 assert_eq!(together.local(0), together.local(1));
1099 // Sixty four bytes of frame gone, and eight more in each of them for the word that lands
1100 // the stack pointer back where a call wants it.
1101 assert_eq!((apart.size(), together.size()), (136, 72));
1102 }
1103
1104 #[test]
1105 fn a_run_of_bytes_that_ends_part_way_through_its_alignment_costs_the_frame_nothing() {
1106 let (mut building, block) = Building::new();
1107 let addr = building.local(block, 0);
1108 building.through(block, addr);
1109 let (_, allocation) = building.allocate(1, 4);
1110
1111 // Twenty four bytes asking for sixteen is what a cell shared by a wide thing and a strict
1112 // one looks like, and it ends eight bytes into an alignment. Which way round the two are
1113 // given is not allowed to matter, because the order they are placed in is this pass's
1114 // business and the order they were declared in is not.
1115 let ragged = Local { size: 24, align: 16 };
1116 let whole = Local { size: 32, align: 16 };
1117 let size = |locals: &[Local]| {
1118 let layout = Layout { leaf: false, locals, ..Layout::new(&SYSV, REGS) };
1119 Frame::of(&building.func, &allocation, &layout).size()
1120 };
1121
1122 assert_eq!(size(&[ragged, whole]), size(&[whole, ragged]));
1123 // The two of them end to end with no hole between, which with the return address on top
1124 // of it is already where a call wants the stack pointer, so nothing is added for that.
1125 assert_eq!(size(&[ragged, whole]), 56);
1126 }
1127
1128 #[test]
1129 fn a_build_whose_locals_keep_their_own_bytes_still_shares_the_spill_slots() {
1130 let (mut building, block) = Building::new();
1131 let first = building.local(block, 0);
1132 building.through(block, first);
1133 let second = building.local(block, 1);
1134 building.through(block, second);
1135 // Two stretches of three values with two registers to hand out, one after the other, so
1136 // what goes to the stack in the first is finished with before the second starts.
1137 for _ in 0..2 {
1138 let values: Vec<Reg> = (0..3).map(|_| building.value(block)).collect();
1139 for ® in &values {
1140 building.held(block, reg);
1141 }
1142 }
1143 let (_, allocation) = building.allocate(2, 2);
1144
1145 let widths = vec![8; allocation.assignment.spilled()];
1146 let plan = Slots::share(&building.func, None, &allocation, &[WORD, WORD], &widths);
1147 assert_ne!(plan.local(0), plan.local(1), "a variable somebody can ask for keeps its bytes");
1148 assert_eq!(plan.slot(0), plan.slot(1), "and two spilled values that never meet share");
1149 }
1150
1151 /// A spill slot wanting bytes over one stretch of the line.
1152 fn slot(number: usize, start: Point, end: Point) -> Want {
1153 Want { what: What::Slot(number), size: 8, align: 8, area: Some(vec![Range { start, end }]) }
1154 }
1155
1156 #[test]
1157 fn what_is_left_when_the_budget_runs_out_gets_bytes_of_its_own() {
1158 // Three that are never both wanted, which is one run of bytes for the three of them when
1159 // there is anything to spend on finding that out.
1160 let three = || vec![slot(0, 0, 10), slot(1, 20, 30), slot(2, 40, 50)];
1161 assert_eq!(fit(three(), 0, 3, BUDGET).cells().len(), 1);
1162 // One comparison puts the second beside the first and leaves nothing for the third.
1163 assert_eq!(fit(three(), 0, 3, 1).cells().len(), 2);
1164 assert_eq!(fit(three(), 0, 3, 0).cells().len(), 3);
1165 }
1166}