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