Skip to main content

rucc_codegen/
frame.rs

1//! The frame: what a function's stack looks like while it runs.
2//!
3//! Design: `spec/10-backend.md` section 10.7.
4//!
5//! This is worked out after register allocation and not before, because the largest area in most
6//! frames is the spill slots and nothing knows how many of those there are until the allocator has
7//! finished running out of registers. It is worked out from the rewritten function rather than
8//! from the assignment alone, because the rewrite is what decides which scratch registers a reload
9//! uses, and a scratch register a call preserves is one the prologue has to save.
10//!
11//! # What is in one
12//!
13//! Section 10.7 lists the areas and this is the order they are in, from the stack pointer upward,
14//! which is the order of increasing address on every machine here.
15//!
16//! ```text
17//!   incoming stack arguments      the caller wrote these and they are above everything
18//!   return address                the call instruction pushed it, on a machine that does
19//!   saved frame pointer           when the function keeps one
20//!   saved general purpose regs    pushed, one word each
21//!   saved vector registers        stored rather than pushed, since no machine here pushes one
22//!   locals                        what an alloca becomes, widest alignment first
23//!   spill slots                   one for every value the allocator ran out of registers for
24//!   outgoing argument area        at the bottom, because a call reads its stack arguments from
25//!                                 the stack pointer upward
26//! ```
27//!
28//! Every offset reported here is from the stack pointer as it stands in the body of the function,
29//! which is after the prologue and before the epilogue. That is the one base register always
30//! available. A frame pointer is a second way to reach the same bytes and the prologue is what
31//! knows the distance between the two, so nothing here reports an offset from it. The one exception
32//! is [`Frame::incoming`], and it is an exception because the bytes it reports are the caller's
33//! rather than this function's, which is the one part of the picture a realigned frame loses sight
34//! of. It says which register it counted from.
35//!
36//! # Where the alignment comes from
37//!
38//! A call has to leave the stack pointer on a multiple of the convention's alignment, so a
39//! function's own frame is what puts it back: the call that reached this function pushed a return
40//! address and left the stack pointer one word off, and the prologue's pushes either fix that or
41//! make it worse depending on how many there are. The size the prologue subtracts is therefore not
42//! the size of the areas. It is whatever brings the stack pointer back to a multiple of the
43//! alignment given the pushes in front of it, which is the arithmetic in [`Frame::of`].
44//!
45//! # The red zone
46//!
47//! A leaf function may use the bytes below the stack pointer without moving it, which is what
48//! `red_zone` on a convention says and what makes a small leaf function's prologue and epilogue
49//! empty. Then the offsets are negative, which is why they are signed, and the areas are in the
50//! same order as ever, below the line rather than above it. Anything that calls, or is too big for
51//! the zone, or wants more alignment than the stack pointer has for free, moves the stack pointer.
52//!
53//! # Realignment
54//!
55//! A local wanting more alignment than a call leaves the stack pointer with cannot be placed by
56//! arithmetic, because nothing in the frame knows what the caller's stack pointer was a multiple
57//! of. The prologue has to force it, and forcing it destroys the only record of where the caller's
58//! stack was, so a realigned frame needs a frame pointer and the distance from the body's stack
59//! pointer to the incoming arguments stops being a constant. [`Frame::realign`] is where that is
60//! reported and it is why [`Frame::incoming`] answers from the frame pointer in such a frame and
61//! from the stack pointer in every other one.
62
63use rucc_mir::Func;
64use rucc_regalloc::Allocation;
65use rucc_regalloc::assign::Place;
66use rucc_target::{CallRegs, PhysReg, RegClass, RegFile};
67
68/// One register the prologue puts away in the frame, and where in the frame it goes.
69///
70/// A pushed register does not need one of these, because where it goes is wherever the stack
71/// pointer had reached, and the epilogue pops them back in the opposite order without having to
72/// know. A register that is stored rather than pushed does need one.
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub struct Save {
75    /// The register.
76    pub reg: PhysReg,
77    /// Where it goes, from the stack pointer in the body of the function.
78    pub at: i32,
79}
80
81/// Where the arguments the caller passed on the stack are, and which register reaches them.
82///
83/// Two fields rather than one number because a realigned frame has no constant distance from its
84/// stack pointer to the caller's. Forcing the alignment threw that distance away, and the frame
85/// pointer is what still reaches the caller's stack afterwards, which is why a realigned frame is
86/// made to keep one. So there is always an answer, and which register it is counted from is part of
87/// it rather than something the reader is left to work out.
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub struct Incoming {
90    /// How far above that register the first argument passed on the stack is.
91    pub at: i32,
92    /// Whether the register is the frame pointer rather than the stack pointer.
93    pub through_frame_pointer: bool,
94}
95
96impl Incoming {
97    /// That far above the stack pointer as it stands in the body of the function, which is where
98    /// every other offset in a frame is from.
99    #[must_use]
100    pub fn from_stack(at: i32) -> Self {
101        Self { at, through_frame_pointer: false }
102    }
103
104    /// That far above the frame pointer, which is the only way a realigned frame reaches back.
105    #[must_use]
106    pub fn from_frame(at: i32) -> Self {
107        Self { at, through_frame_pointer: true }
108    }
109}
110
111/// A piece of memory the function needs for its own use, which is what an `alloca` becomes.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub struct Local {
114    /// How many bytes of it there are.
115    pub size: u32,
116    /// What its address has to be a multiple of.
117    pub align: u32,
118}
119
120/// Everything about a function's frame that does not come out of its allocation.
121#[derive(Debug, Clone, Copy)]
122pub struct Layout<'a> {
123    /// Where the convention this function is compiled for puts things.
124    pub conv: &'a CallRegs,
125    /// The registers the target has, which is what says how wide a spill slot of a class is.
126    pub file: RegFile,
127    /// The memory the function asked for itself, in the order it wants it reported back.
128    pub locals: &'a [Local],
129    /// How many bytes the widest call in the function needs for arguments it passes on the stack.
130    pub outgoing: u32,
131    /// Whether the function calls nothing, which is what the alignment and the red zone turn on.
132    pub leaf: bool,
133    /// Whether the function keeps a frame pointer, which `-fno-omit-frame-pointer` asks for and
134    /// which a realigned or a dynamically grown frame requires whatever the flags say.
135    pub frame_pointer: bool,
136    /// Whether the red zone may be used at all, which `-mno-red-zone` and every kernel turns off.
137    pub red_zone: bool,
138}
139
140impl<'a> Layout<'a> {
141    /// A layout for a function with nothing in it but what its allocation says: a leaf with no
142    /// locals and no calls, which is what every function is until the pieces that produce those
143    /// exist.
144    #[must_use]
145    pub fn new(conv: &'a CallRegs, file: RegFile) -> Self {
146        Self {
147            conv,
148            file,
149            locals: &[],
150            outgoing: 0,
151            leaf: true,
152            frame_pointer: false,
153            red_zone: true,
154        }
155    }
156}
157
158/// What a function's stack looks like while it runs.
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub struct Frame {
161    saved_int: Vec<PhysReg>,
162    saved_sse: Vec<Save>,
163    slots: Vec<i32>,
164    locals: Vec<i32>,
165    outgoing: u32,
166    size: u32,
167    realign: Option<u32>,
168    incoming: Incoming,
169    frame_pointer: bool,
170}
171
172impl Frame {
173    /// Works out the frame of a function the allocator has finished with.
174    ///
175    /// # Panics
176    ///
177    /// Panics on a frame of two gigabytes or more, which is a stack no machine here gives a
178    /// thread, and on a local whose alignment is not a power of two.
179    #[must_use]
180    pub fn of(func: &Func, allocation: &Allocation, layout: &Layout<'_>) -> Self {
181        let conv = layout.conv;
182        let word = conv.word;
183        let (saved_int, vectors) = saved(func, allocation, layout);
184
185        // The vector registers are saved in the frame rather than pushed, because no machine here
186        // has an instruction that pushes one.
187        let vector = width(layout, conv.sse_class);
188        let mut top = 0;
189        let mut align = word;
190        let mut saved_sse = Vec::with_capacity(vectors.len());
191        for reg in vectors {
192            align = align.max(vector);
193            saved_sse.push(Save { reg, at: offset(top) });
194            top += vector;
195        }
196
197        let mut locals = vec![0; layout.locals.len()];
198        let mut order: Vec<usize> = (0..layout.locals.len()).collect();
199        // Widest alignment first, so that placing each one straight after the last never leaves a
200        // hole bigger than the alignment the next one asked for.
201        order.sort_by_key(|&local| std::cmp::Reverse(layout.locals[local].align));
202        for local in order {
203            let Local { size, align: want } = layout.locals[local];
204            assert!(
205                want.is_power_of_two(),
206                "a local aligned to something that is not a power of 2"
207            );
208            align = align.max(want);
209            top = top.next_multiple_of(want);
210            locals[local] = offset(top);
211            top += size;
212        }
213
214        let mut slots = Vec::with_capacity(allocation.assignment.slots().len());
215        for &class in allocation.assignment.slots() {
216            let size = width(layout, class);
217            align = align.max(size);
218            top = top.next_multiple_of(size);
219            slots.push(offset(top));
220            top += size;
221        }
222
223        // A call reads its stack arguments from the stack pointer upward, so the outgoing area is
224        // at the bottom of the frame and its size is what shifts everything else.
225        let outgoing = if layout.leaf { 0 } else { layout.outgoing.max(conv.shadow) };
226        // Everything above it was placed as though it were not there, so moving it up by the size
227        // of the area is what would break its alignment. The area is padded to the widest
228        // alignment anything above it asked for, which costs at most that many bytes once and
229        // costs nothing at all in the usual frame, where the area is a multiple of it already.
230        // What the padding must not do is move the area itself: the callee reads its arguments
231        // from the stack pointer, so the bottom of the area is the stack pointer whatever is
232        // above it.
233        let shifted = outgoing.next_multiple_of(align);
234        let body = (top + shifted).next_multiple_of(word);
235
236        // Where the stack pointer sits once the prologue has finished pushing: one return address
237        // short of aligned when the function starts, and one word further off for every push.
238        let pushed =
239            u32::from(layout.frame_pointer) + u32::try_from(saved_int.len()).expect("a frame");
240        let entry = wrap(conv.stack_align, conv.return_address);
241        let after = (entry + wrap(conv.stack_align, word * pushed)) % conv.stack_align;
242
243        let realign = (align > conv.stack_align).then_some(align);
244        let free = layout.leaf
245            && layout.red_zone
246            && realign.is_none()
247            && align <= word
248            && body <= conv.red_zone;
249        let size = match realign {
250            _ if free => 0,
251            // Once the prologue has forced the alignment, keeping the frame a multiple of it keeps
252            // everything in the frame aligned too.
253            Some(to) => body.next_multiple_of(to),
254            // A leaf owes nobody an aligned stack pointer, so it takes exactly what it uses.
255            None if layout.leaf && align <= word => body,
256            // The smallest frame that lands the stack pointer back on a multiple of the alignment
257            // given where the pushes left it.
258            None => body + (after + conv.stack_align - body % conv.stack_align) % conv.stack_align,
259        };
260
261        // With the stack pointer left where it was, the areas are the same areas in the same order
262        // and they are below it rather than above it.
263        let shift = if free { -offset(body) } else { offset(shifted) };
264        for at in slots
265            .iter_mut()
266            .chain(locals.iter_mut())
267            .chain(saved_sse.iter_mut().map(|save| &mut save.at))
268        {
269            *at += shift;
270        }
271
272        Self {
273            saved_int,
274            saved_sse,
275            slots,
276            locals,
277            outgoing,
278            size,
279            realign,
280            incoming: match realign {
281                // The prologue saves the frame pointer before it does anything else and points it
282                // at where it saved it, so the caller's stack is one word for that and one return
283                // address above it, whatever the prologue did to the stack pointer afterwards.
284                Some(_) => Incoming::from_frame(offset(word + conv.return_address)),
285                None => Incoming::from_stack(offset(size + word * pushed + conv.return_address)),
286            },
287            frame_pointer: layout.frame_pointer || realign.is_some(),
288        }
289    }
290
291    /// The general purpose registers the prologue pushes, in the order it pushes them.
292    ///
293    /// The frame pointer is not among them even when the convention calls it a saved register,
294    /// because a function that keeps one saves it as part of setting it up.
295    #[must_use]
296    pub fn saved_int(&self) -> &[PhysReg] {
297        &self.saved_int
298    }
299
300    /// The vector registers the prologue stores into the frame, and where each of them goes.
301    #[must_use]
302    pub fn saved_sse(&self) -> &[Save] {
303        &self.saved_sse
304    }
305
306    /// Where a spill slot is, from the stack pointer in the body of the function.
307    #[must_use]
308    pub fn slot(&self, slot: u32) -> Option<i32> {
309        self.slots.get(usize::try_from(slot).ok()?).copied()
310    }
311
312    /// Where a local is, from the stack pointer in the body of the function.
313    #[must_use]
314    pub fn local(&self, local: usize) -> Option<i32> {
315        self.locals.get(local).copied()
316    }
317
318    /// How many bytes the prologue takes off the stack pointer, which is nothing for a function
319    /// small enough and quiet enough to live in the red zone.
320    #[must_use]
321    pub fn size(&self) -> u32 {
322        self.size
323    }
324
325    /// How many bytes at the bottom of the frame belong to the arguments of calls this function
326    /// makes, which is where the shadow space goes on Windows.
327    #[must_use]
328    pub fn outgoing(&self) -> u32 {
329        self.outgoing
330    }
331
332    /// What the prologue has to force the stack pointer to be a multiple of, when a local wants
333    /// more alignment than a call leaves it with.
334    #[must_use]
335    pub fn realign(&self) -> Option<u32> {
336        self.realign
337    }
338
339    /// Where the first argument the caller passed on the stack is, and which register reaches it.
340    ///
341    /// The only offset here that is not always from the stack pointer. A realigned frame counts
342    /// from the frame pointer instead, because forcing the alignment threw away however far the
343    /// caller's stack pointer was from where the prologue wanted it, and the frame pointer is what
344    /// reaches the caller's stack afterwards.
345    #[must_use]
346    pub fn incoming(&self) -> Incoming {
347        self.incoming
348    }
349
350    /// Whether the function keeps a frame pointer.
351    #[must_use]
352    pub fn frame_pointer(&self) -> bool {
353        self.frame_pointer
354    }
355}
356
357/// The registers a call preserves that this function writes anyway, so the prologue has to put
358/// them back.
359///
360/// The rewritten function is what is read here rather than the assignment, because a spilled value
361/// is reloaded into a scratch register that no assignment mentions, and a scratch register the
362/// convention preserves is one this has to find.
363fn saved(
364    func: &Func,
365    allocation: &Allocation,
366    layout: &Layout<'_>,
367) -> (Vec<PhysReg>, Vec<PhysReg>) {
368    let mut used: Vec<(RegClass, PhysReg)> = Vec::new();
369    let mut note = |class: RegClass, at: PhysReg| {
370        if !used.contains(&(class, at)) {
371            used.push((class, at));
372        }
373    };
374    for block in func.blocks() {
375        for inst in func.insts(block) {
376            for operand in &func[func[inst].operands] {
377                if let Some(at) = operand.reg.phys() {
378                    note(operand.class, at);
379                }
380            }
381        }
382    }
383    for edit in &allocation.edits {
384        for place in [edit.mov.from, edit.mov.to] {
385            if let Place::Reg(at) = place {
386                note(edit.class, at);
387            }
388        }
389    }
390
391    let conv = layout.conv;
392    let wanted = |class: RegClass, at: PhysReg| used.contains(&(class, at));
393    // In the convention's order rather than the order the function happened to reach for them, so
394    // that two functions saving the same registers get the same prologue.
395    let saved_int = conv
396        .int_saved
397        .iter()
398        .copied()
399        .filter(|&at| wanted(conv.int_class, at))
400        .filter(|&at| !(layout.frame_pointer && at == conv.frame_pointer))
401        .collect();
402    let saved_sse =
403        conv.sse_saved.iter().copied().filter(|&at| wanted(conv.sse_class, at)).collect();
404    (saved_int, saved_sse)
405}
406
407/// How many bytes a value of a class takes on the stack.
408///
409/// A power of two at least a word wide, because a slot is addressed and an address that is not a
410/// multiple of the size of the thing at it is a fault on some machines and slow on the rest. An
411/// eighty bit `long double` takes sixteen bytes for that reason, which is what every compiler
412/// does with one.
413fn width(layout: &Layout<'_>, class: RegClass) -> u32 {
414    let bits = layout.file.class(class).map_or(0, |info| info.bits);
415    bits.div_ceil(8).max(layout.conv.word).next_power_of_two()
416}
417
418/// How far past a multiple of an alignment a number is, counted the other way: what has to be
419/// added to it to reach the next one.
420fn wrap(align: u32, value: u32) -> u32 {
421    (align - value % align) % align
422}
423
424/// A distance in a frame, as the signed number every offset out of here is.
425fn offset(bytes: u32) -> i32 {
426    i32::try_from(bytes).expect("a frame under two gigabytes")
427}
428
429#[cfg(test)]
430mod tests {
431    use rucc_base::Interner;
432    use rucc_mir::{Opcode, Operand, Reg};
433    use rucc_regalloc::assign::Env;
434    use rucc_target::x86_64::{GPR, RBP, REGS, SYSV, WIN64, XMM};
435
436    use super::*;
437
438    /// An environment offering that many of the convention's registers, with everything after
439    /// them held back as scratch.
440    fn env(conv: &CallRegs, count: usize) -> Env {
441        Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
442    }
443
444    /// A function of that many values, every one of them written before any is read, allocated
445    /// with that many registers to hand out.
446    ///
447    /// Every value is live at the first read, so a count below the number of values is what puts
448    /// the function under enough pressure to spill, and each read wants one value so a reload
449    /// never needs more than one scratch register.
450    fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation) {
451        let mut names = Interner::new();
452        let mut func = Func::new(names.intern("f"));
453        let opcode = Opcode::new(names.intern("x64.nop"));
454        let block = func.create_block();
455        let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
456        for &reg in &regs {
457            func.build(block, opcode).def(reg, GPR).finish();
458        }
459        for &reg in &regs {
460            func.build(block, opcode).uses(reg, GPR).finish();
461        }
462        let allocation = rucc_regalloc::run(&mut func, &env(conv, count));
463        (func, allocation)
464    }
465
466    /// What a list of registers is called, which is what an assertion reads.
467    fn named(regs: &[PhysReg]) -> Vec<&'static str> {
468        regs.iter().map(|&reg| REGS.name(GPR, reg).expect("a register")).collect()
469    }
470
471    #[test]
472    fn a_function_that_needs_nothing_of_the_stack_has_no_frame_at_all() {
473        let (func, allocation) = pressure(&SYSV, 2, 4);
474        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
475
476        assert_eq!(frame.size(), 0);
477        assert_eq!(named(frame.saved_int()), Vec::<&str>::new());
478        assert_eq!(frame.slot(0), None);
479        // Nothing between the stack pointer and the return address the call pushed.
480        assert_eq!(frame.incoming(), Incoming::from_stack(8));
481    }
482
483    #[test]
484    fn a_small_leaf_function_puts_its_spills_in_the_red_zone_and_moves_nothing() {
485        let (func, allocation) = pressure(&SYSV, 4, 2);
486        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
487
488        // Two registers for four values that are all live at once, so two are on the stack, and a
489        // leaf function small enough is entitled to the bytes below the stack pointer.
490        assert_eq!(frame.size(), 0);
491        assert_eq!((frame.slot(0), frame.slot(1)), (Some(-16), Some(-8)));
492        assert_eq!(frame.slot(2), None);
493        assert_eq!(frame.incoming(), Incoming::from_stack(8));
494    }
495
496    #[test]
497    fn a_leaf_function_told_it_has_no_red_zone_takes_the_bytes_instead() {
498        let (func, allocation) = pressure(&SYSV, 4, 2);
499        let base = Layout::new(&SYSV, REGS);
500        let frame = Frame::of(&func, &allocation, &Layout { red_zone: false, ..base });
501
502        assert_eq!(frame.size(), 16);
503        assert_eq!((frame.slot(0), frame.slot(1)), (Some(0), Some(8)));
504        assert_eq!(frame.incoming(), Incoming::from_stack(24));
505    }
506
507    #[test]
508    fn a_frame_too_big_for_the_red_zone_takes_the_bytes_whatever_else_is_true() {
509        let (func, allocation) = pressure(&SYSV, 40, 2);
510        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
511
512        // Thirty eight values on the stack is three hundred and four bytes, and the red zone is a
513        // hundred and twenty eight.
514        assert_eq!(frame.size(), 304);
515        assert_eq!(frame.slot(0), Some(0));
516        assert_eq!(frame.slot(37), Some(296));
517    }
518
519    #[test]
520    fn a_function_that_calls_something_leaves_the_stack_pointer_where_a_call_wants_it() {
521        let (func, allocation) = pressure(&SYSV, 4, 2);
522        let base = Layout::new(&SYSV, REGS);
523        let frame = Frame::of(&func, &allocation, &Layout { leaf: false, ..base });
524
525        // Sixteen bytes of spills, and the call that reached this function left the stack pointer
526        // eight bytes off, so the frame is eight bytes wider than the spills need and every call
527        // this function makes is correctly aligned.
528        assert_eq!(frame.size(), 24);
529        assert_eq!((frame.slot(0), frame.slot(1)), (Some(0), Some(8)));
530        assert_eq!(frame.incoming(), Incoming::from_stack(32));
531    }
532
533    #[test]
534    fn a_push_is_counted_in_the_alignment_the_frame_has_to_produce() {
535        let (func, allocation) = pressure(&SYSV, 12, 12);
536        let base = Layout::new(&SYSV, REGS);
537        let frame = Frame::of(&func, &allocation, &Layout { leaf: false, ..base });
538
539        // Twelve values reach into the preserved end of the allocation order, so three registers
540        // are pushed, and three pushes plus the return address is a multiple of sixteen already.
541        // The frame is empty and stays empty rather than being padded for the sake of it.
542        assert_eq!(named(frame.saved_int()), ["rbx", "r12", "r13"]);
543        assert_eq!(frame.size(), 0);
544        assert_eq!(frame.incoming(), Incoming::from_stack(32));
545    }
546
547    #[test]
548    fn the_registers_a_call_leaves_alone_are_saved_in_the_order_the_convention_lists_them() {
549        let (func, allocation) = pressure(&SYSV, 13, 13);
550        let frame = Frame::of(&func, &allocation, &Layout::new(&SYSV, REGS));
551
552        // Four of them now, in the convention's order rather than the order the allocator handed
553        // them out in, so that two functions saving the same registers get the same prologue.
554        assert_eq!(named(frame.saved_int()), ["rbx", "r12", "r13", "r14"]);
555    }
556
557    #[test]
558    fn a_function_that_keeps_a_frame_pointer_does_not_save_it_twice() {
559        let mut names = Interner::new();
560        let mut func = Func::new(names.intern("f"));
561        let opcode = Opcode::new(names.intern("x64.nop"));
562        let block = func.create_block();
563        // An instruction that names the frame pointer register outright, which is what a lowering
564        // rule for something that has to use it produces.
565        func.build(block, opcode).operand(Operand::write(Reg::physical(RBP), GPR)).finish();
566        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4));
567        let base = Layout::new(&SYSV, REGS);
568
569        let kept = Frame::of(&func, &allocation, &Layout { frame_pointer: true, ..base });
570        let dropped = Frame::of(&func, &allocation, &base);
571
572        // `rbp` is a register SysV preserves, so a function that leaves it alone saves it in the
573        // ordinary way, and a function that keeps a frame pointer in it saves it as part of
574        // setting the frame pointer up instead.
575        assert_eq!(named(dropped.saved_int()), ["rbp"]);
576        assert_eq!(named(kept.saved_int()), Vec::<&str>::new());
577        assert!(kept.frame_pointer());
578    }
579
580    #[test]
581    fn locals_are_placed_widest_alignment_first_and_reported_in_the_order_they_arrived() {
582        let (func, allocation) = pressure(&SYSV, 2, 4);
583        let locals = [
584            Local { size: 1, align: 1 },
585            Local { size: 16, align: 16 },
586            Local { size: 8, align: 8 },
587        ];
588        let base = Layout::new(&SYSV, REGS);
589        let frame = Frame::of(&func, &allocation, &Layout { locals: &locals, ..base });
590
591        // The sixteen byte one is placed first, so nothing is padded to reach it, and the one
592        // byte one goes last where the padding after it costs nothing.
593        assert_eq!((frame.local(1), frame.local(2), frame.local(0)), (Some(0), Some(16), Some(24)));
594        assert_eq!(frame.local(3), None);
595        // A local wanting sixteen byte alignment is more than the stack pointer has for free, so
596        // the frame is taken rather than the red zone used, and it is padded to keep the local
597        // where it was put.
598        assert_eq!(frame.size(), 40);
599        assert_eq!(frame.realign(), None);
600    }
601
602    #[test]
603    fn a_local_wanting_more_alignment_than_a_call_gives_makes_the_prologue_force_it() {
604        let (func, allocation) = pressure(&SYSV, 2, 4);
605        let locals = [Local { size: 64, align: 32 }];
606        let base = Layout::new(&SYSV, REGS);
607        let frame = Frame::of(&func, &allocation, &Layout { locals: &locals, ..base });
608
609        assert_eq!(frame.realign(), Some(32));
610        assert_eq!(frame.local(0), Some(0));
611        assert_eq!(frame.size(), 64);
612        // Forcing the alignment throws away how far the caller's stack pointer was from where the
613        // prologue wanted it, so a frame pointer is needed and the caller's stack is reached
614        // through it instead: one word for the saved frame pointer and one for the return address.
615        assert!(frame.frame_pointer());
616        assert_eq!(frame.incoming(), Incoming::from_frame(16));
617    }
618
619    #[test]
620    fn a_call_reads_its_stack_arguments_from_the_bottom_of_the_frame() {
621        let (func, allocation) = pressure(&SYSV, 4, 2);
622        let base = Layout::new(&SYSV, REGS);
623        let frame = Frame::of(&func, &allocation, &Layout { leaf: false, outgoing: 24, ..base });
624
625        // The outgoing area is at the stack pointer, because that is where the callee will look
626        // for it, and the spills sit above it.
627        assert_eq!(frame.outgoing(), 24);
628        assert_eq!((frame.slot(0), frame.slot(1)), (Some(24), Some(32)));
629        assert_eq!(frame.size(), 40);
630    }
631
632    /// Moving everything up by the size of the outgoing area is what would break its alignment,
633    /// so the area is padded to the widest alignment anything above it wanted. The area itself
634    /// still starts at the stack pointer, because that is the one thing about it that is not this
635    /// frame's to choose.
636    #[test]
637    fn what_is_above_the_outgoing_area_keeps_the_alignment_it_asked_for() {
638        let (func, allocation) = pressure(&SYSV, 2, 4);
639        let locals = [Local { size: 16, align: 16 }];
640        let base = Layout::new(&SYSV, REGS);
641        let there = Layout { leaf: false, outgoing: 8, locals: &locals, ..base };
642        let frame = Frame::of(&func, &allocation, &there);
643
644        assert_eq!(frame.outgoing(), 8);
645        assert_eq!(frame.local(0), Some(16));
646        assert_eq!(frame.size(), 40);
647        // A call leaves the stack pointer one return address short of aligned and nothing was
648        // pushed on top of that, so the frame is what puts it back and the local lands aligned.
649        assert_eq!((frame.size() + SYSV.return_address) % SYSV.stack_align, 0);
650    }
651
652    #[test]
653    fn a_windows_call_gets_the_thirty_two_bytes_below_it_even_when_it_passes_nothing() {
654        let (func, allocation) = pressure(&WIN64, 2, 4);
655        let base = Layout::new(&WIN64, REGS);
656        let frame = Frame::of(&func, &allocation, &Layout { leaf: false, ..base });
657
658        // Windows has no red zone and every caller reserves thirty two bytes below the call for
659        // the callee to spill its register arguments into.
660        assert_eq!(frame.outgoing(), 32);
661        assert_eq!(frame.size(), 40);
662        assert_eq!(frame.incoming(), Incoming::from_stack(48));
663    }
664
665    #[test]
666    fn a_slot_is_as_wide_as_the_widest_thing_of_its_class() {
667        let base = Layout::new(&SYSV, REGS);
668
669        assert_eq!(width(&base, GPR), 8);
670        assert_eq!(width(&base, XMM), 16);
671        // A long double is eighty bits and takes sixteen bytes, because an address has to be a
672        // multiple of the size of what is at it.
673        assert_eq!(width(&base, REGS.class_named("x87").expect("a class")), 16);
674    }
675}