Skip to main content

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