Skip to main content

backend_lang/target/
frame.rs

1//! Stack-frame layout: where each stack slot lives, which registers the
2//! prologue saves, how much stack it allocates, and the prologue and
3//! epilogue as a list of steps.
4//!
5//! The model is the x86-64 push frame, shared by SysV and Win64:
6//!
7//! ```text
8//!   higher addresses
9//!   ┌──────────────────────────┐
10//!   │ incoming stack arguments │  CFA + offset   (SlotKind::Incoming)
11//!   ├──────────────────────────┤  <- CFA (the stack pointer before the call)
12//!   │ return address           │
13//!   │ saved frame pointer      │  push rbp; mov rbp, rsp   (with a frame pointer)
14//!   │ saved callee-saved GPRs  │  push rbx ...
15//!   │ padding to 16            │
16//!   │ locals and spill slots   │  largest alignment first
17//!   │ saved callee-saved XMMs  │  (Win64 xmm6-xmm15; 16-byte slots)
18//!   │ outgoing argument area   │  shadow space + stack arguments of calls
19//!   └──────────────────────────┘  <- SP after the prologue, 16-aligned
20//! ```
21//!
22//! The stack pointer does not move between the prologue and the epilogue, so
23//! every slot has a fixed offset from it, and stack arguments of calls are
24//! stored at `[sp + offset]` directly. A function that makes no calls and
25//! needs no more than the ABI's red zone keeps its locals below the stack
26//! pointer and allocates nothing. A frame of at least the ABI's probe
27//! interval is allocated one page at a time, touching each page, so the
28//! guard page below the stack is never jumped over (Windows requires this;
29//! on Linux it is stack-clash protection).
30//!
31//! AArch64's frame (`stp`/`ldp` pairs, frame record of `fp` and `lr`) is a
32//! different shape and arrives with the AArch64 target (backend-lang 0.5).
33
34use alloc::vec::Vec;
35use core::fmt;
36
37use super::abi::Abi;
38use crate::mir::{SlotKind, StackSlot};
39use crate::regs::{PReg, RegSet};
40
41/// Bytes of a return address and of one pushed register on x86-64.
42const WORD: u64 = 8;
43
44/// Everything about a function the frame depends on.
45///
46/// [`Function::frame_layout`](crate::mir::Function::frame_layout) fills this
47/// in from a function; build one by hand to lay out a frame for code that is
48/// not MIR.
49///
50/// # Examples
51///
52/// ```
53/// use backend_lang::RegSet;
54/// use backend_lang::mir::SlotKind;
55/// use backend_lang::target::frame::FrameInput;
56/// use backend_lang::x86_64::{SYSV, RBX};
57///
58/// let slots = [SlotKind::Local { size: 8, align: 8 }];
59/// let input = FrameInput {
60///     slots: &slots,
61///     written: RegSet::of(&[RBX]),
62///     has_calls: true,
63///     outgoing: 0,
64///     frame_pointer: true,
65/// };
66/// let frame = SYSV.layout_frame(&input)?;
67/// assert_eq!(frame.pushed(), [RBX]);
68/// # Ok::<(), backend_lang::target::frame::FrameError>(())
69/// ```
70#[derive(Clone, Copy, Debug)]
71pub struct FrameInput<'a> {
72    /// The function's stack slots, indexed by [`StackSlot::index`].
73    pub slots: &'a [SlotKind],
74    /// The registers the function writes; the callee-saved ones among them
75    /// are saved and restored.
76    pub written: RegSet,
77    /// Whether the function calls anything (then the stack is 16-aligned at
78    /// each call, the red zone is off, and Win64 shadow space is reserved).
79    pub has_calls: bool,
80    /// Bytes of outgoing argument area the function's calls need (the
81    /// largest [`Abi::assign_args`] result over its calls).
82    pub outgoing: u32,
83    /// Whether to keep a frame pointer.
84    pub frame_pointer: bool,
85}
86
87/// Why a frame could not be laid out.
88///
89/// # Examples
90///
91/// ```
92/// use backend_lang::mir::{Function, SlotKind};
93/// use backend_lang::target::frame::FrameError;
94/// use backend_lang::x86_64::SYSV;
95///
96/// let mut f = Function::new("f", &SYSV);
97/// let slot = f.stack_slot(SlotKind::Local { size: 32, align: 32 })?;
98/// assert_eq!(f.frame_layout().unwrap_err(), FrameError::Alignment { slot, align: 32 });
99/// # Ok::<(), backend_lang::mir::MirError>(())
100/// ```
101#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
102#[non_exhaustive]
103pub enum FrameError {
104    /// The frame, or an offset into it, exceeds the 32-bit displacement
105    /// instructions address it with. Move large data to the heap.
106    TooLarge,
107    /// A slot's alignment is zero, not a power of two, or larger than the
108    /// ABI's stack alignment (16), which the fixed frame cannot provide
109    /// without realigning the stack.
110    Alignment {
111        /// The slot.
112        slot: StackSlot,
113        /// Its alignment.
114        align: u32,
115    },
116    /// Two incoming slots overlap; each names a distinct stack argument.
117    OverlappingIncoming {
118        /// The second of the two slots.
119        slot: StackSlot,
120    },
121}
122
123impl fmt::Display for FrameError {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        match self {
126            FrameError::TooLarge => {
127                f.write_str("the stack frame exceeds the 2 GiB a 32-bit displacement reaches")
128            }
129            FrameError::Alignment { slot, align } => write!(
130                f,
131                "stack slot ss{} asks for alignment {align}; slots take a power of two up to the stack alignment",
132                slot.index()
133            ),
134            FrameError::OverlappingIncoming { slot } => write!(
135                f,
136                "incoming stack slot ss{} overlaps another incoming slot",
137                slot.index()
138            ),
139        }
140    }
141}
142
143impl core::error::Error for FrameError {}
144
145/// One step of a prologue or epilogue, in execution order.
146///
147/// # Examples
148///
149/// ```
150/// use backend_lang::mir::{Function, SlotKind};
151/// use backend_lang::target::frame::FrameStep;
152/// use backend_lang::x86_64::{WIN64, RBP};
153///
154/// let mut f = Function::new("f", &WIN64);
155/// let _big = f.stack_slot(SlotKind::Local { size: 10000, align: 8 })?;
156/// let frame = f.frame_layout().unwrap();
157/// assert_eq!(frame.prologue()[0], FrameStep::Push(RBP));
158/// assert_eq!(frame.prologue()[1], FrameStep::SetFramePointer);
159/// // Win64 probes frames of a page or more.
160/// assert!(matches!(frame.prologue()[2], FrameStep::Alloc { probe: 4096, .. }));
161/// # Ok::<(), backend_lang::mir::MirError>(())
162/// ```
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
164#[non_exhaustive]
165pub enum FrameStep {
166    /// Push a register (`push r`).
167    Push(PReg),
168    /// Point the frame pointer at the stack pointer (`mov rbp, rsp`).
169    SetFramePointer,
170    /// Lower the stack pointer by `bytes`. With `probe` non-zero, one page of
171    /// `probe` bytes at a time, touching each.
172    Alloc {
173        /// Bytes to allocate.
174        bytes: u32,
175        /// The probe interval, or `0` for one adjustment.
176        probe: u32,
177    },
178    /// Store a vector register into the frame at `[sp + offset]`.
179    SaveVec {
180        /// The register.
181        reg: PReg,
182        /// Its save slot's offset from the stack pointer.
183        offset: i32,
184    },
185    /// Reload a vector register saved by [`FrameStep::SaveVec`].
186    RestoreVec {
187        /// The register.
188        reg: PReg,
189        /// Its save slot's offset from the stack pointer.
190        offset: i32,
191    },
192    /// Raise the stack pointer by `bytes` (`add rsp, bytes`).
193    Free(u32),
194    /// Pop a register (`pop r`).
195    Pop(PReg),
196}
197
198/// A laid-out frame: slot offsets and the prologue and epilogue.
199///
200/// Offsets are from the stack pointer after the prologue; they are negative
201/// for locals kept in the red zone.
202///
203/// # Examples
204///
205/// ```
206/// use backend_lang::mir::{Function, SlotKind};
207/// use backend_lang::x86_64::SYSV;
208///
209/// let mut f = Function::new("leaf", &SYSV);
210/// let a = f.stack_slot(SlotKind::Local { size: 8, align: 8 })?;
211/// let frame = f.frame_layout().unwrap();
212/// // A leaf with 8 bytes of locals uses the red zone: nothing is allocated.
213/// assert!(frame.uses_red_zone());
214/// assert_eq!(frame.alloc(), 0);
215/// assert!(frame.slot_offset(a).unwrap() < 0);
216/// # Ok::<(), backend_lang::mir::MirError>(())
217/// ```
218#[derive(Clone, Debug, PartialEq, Eq)]
219pub struct FrameLayout {
220    offsets: Vec<i32>,
221    pushed: Vec<PReg>,
222    vec_saves: Vec<(PReg, i32)>,
223    prologue: Vec<FrameStep>,
224    epilogue: Vec<FrameStep>,
225    alloc: u32,
226    cfa_offset: u32,
227    frame_pointer: bool,
228    red_zone: bool,
229}
230
231impl FrameLayout {
232    /// A slot's offset from the stack pointer after the prologue, or `None`
233    /// for a slot the layout does not have.
234    ///
235    /// # Examples
236    ///
237    /// ```
238    /// use backend_lang::mir::{Function, SlotKind};
239    /// use backend_lang::x86_64::SYSV;
240    ///
241    /// let mut f = Function::new("f", &SYSV);
242    /// let arg = f.stack_slot(SlotKind::Incoming { offset: 0, size: 8 })?;
243    /// let frame = f.frame_layout().unwrap();
244    /// // rbp is pushed: [rsp] = saved rbp, [rsp+8] = return address, [rsp+16] = argument.
245    /// assert_eq!(frame.slot_offset(arg), Some(16));
246    /// # Ok::<(), backend_lang::mir::MirError>(())
247    /// ```
248    #[inline]
249    #[must_use]
250    pub fn slot_offset(&self, slot: StackSlot) -> Option<i32> {
251        self.offsets.get(slot.index()).copied()
252    }
253
254    /// The general-purpose callee-saved registers pushed after the frame
255    /// pointer, in push order.
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use backend_lang::mir::Function;
261    /// use backend_lang::x86_64::SYSV;
262    ///
263    /// assert!(Function::new("f", &SYSV).frame_layout().unwrap().pushed().is_empty());
264    /// ```
265    #[inline]
266    #[must_use]
267    pub fn pushed(&self) -> &[PReg] {
268        &self.pushed
269    }
270
271    /// Vector callee-saved registers saved into the frame, with their save
272    /// slots' offsets.
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// use backend_lang::mir::Function;
278    /// use backend_lang::x86_64::SYSV;
279    ///
280    /// assert!(Function::new("f", &SYSV).frame_layout().unwrap().vec_saves().is_empty());
281    /// ```
282    #[inline]
283    #[must_use]
284    pub fn vec_saves(&self) -> &[(PReg, i32)] {
285        &self.vec_saves
286    }
287
288    /// The prologue, in execution order.
289    ///
290    /// # Examples
291    ///
292    /// ```
293    /// use backend_lang::mir::Function;
294    /// use backend_lang::target::frame::FrameStep;
295    /// use backend_lang::x86_64::{SYSV, RBP};
296    ///
297    /// let frame = Function::new("f", &SYSV).frame_layout().unwrap();
298    /// assert_eq!(frame.prologue(), [FrameStep::Push(RBP), FrameStep::SetFramePointer]);
299    /// ```
300    #[inline]
301    #[must_use]
302    pub fn prologue(&self) -> &[FrameStep] {
303        &self.prologue
304    }
305
306    /// The epilogue, in execution order (the return itself is not part of
307    /// it).
308    ///
309    /// # Examples
310    ///
311    /// ```
312    /// use backend_lang::mir::Function;
313    /// use backend_lang::target::frame::FrameStep;
314    /// use backend_lang::x86_64::{SYSV, RBP};
315    ///
316    /// let frame = Function::new("f", &SYSV).frame_layout().unwrap();
317    /// assert_eq!(frame.epilogue(), [FrameStep::Pop(RBP)]);
318    /// ```
319    #[inline]
320    #[must_use]
321    pub fn epilogue(&self) -> &[FrameStep] {
322        &self.epilogue
323    }
324
325    /// Bytes the prologue subtracts from the stack pointer after its pushes.
326    ///
327    /// # Examples
328    ///
329    /// ```
330    /// use backend_lang::mir::Function;
331    /// use backend_lang::x86_64::SYSV;
332    ///
333    /// assert_eq!(Function::new("f", &SYSV).frame_layout().unwrap().alloc(), 0);
334    /// ```
335    #[inline]
336    #[must_use]
337    pub const fn alloc(&self) -> u32 {
338        self.alloc
339    }
340
341    /// The canonical frame address (the stack pointer before the call) minus
342    /// the stack pointer after the prologue: return address, pushes, and
343    /// allocation.
344    ///
345    /// # Examples
346    ///
347    /// ```
348    /// use backend_lang::mir::Function;
349    /// use backend_lang::x86_64::SYSV;
350    ///
351    /// // Return address plus the saved rbp.
352    /// assert_eq!(Function::new("f", &SYSV).frame_layout().unwrap().cfa_offset(), 16);
353    /// ```
354    #[inline]
355    #[must_use]
356    pub const fn cfa_offset(&self) -> u32 {
357        self.cfa_offset
358    }
359
360    /// Whether the frame keeps a frame pointer.
361    ///
362    /// # Examples
363    ///
364    /// ```
365    /// use backend_lang::mir::Function;
366    /// use backend_lang::x86_64::SYSV;
367    ///
368    /// assert!(Function::new("f", &SYSV).frame_layout().unwrap().frame_pointer());
369    /// ```
370    #[inline]
371    #[must_use]
372    pub const fn frame_pointer(&self) -> bool {
373        self.frame_pointer
374    }
375
376    /// Whether the locals live in the red zone below the stack pointer.
377    ///
378    /// # Examples
379    ///
380    /// ```
381    /// use backend_lang::mir::Function;
382    /// use backend_lang::x86_64::SYSV;
383    ///
384    /// assert!(!Function::new("f", &SYSV).frame_layout().unwrap().uses_red_zone());
385    /// ```
386    #[inline]
387    #[must_use]
388    pub const fn uses_red_zone(&self) -> bool {
389        self.red_zone
390    }
391}
392
393/// The smallest `n >= at_least` with `n ≡ residue (mod align)`.
394fn round_to_residue(at_least: u64, residue: u64, align: u64) -> u64 {
395    let rem = at_least % align;
396    let add = (residue + align - rem) % align;
397    at_least + add
398}
399
400fn fits_i32(v: u64) -> Result<i32, FrameError> {
401    i32::try_from(v).map_err(|_| FrameError::TooLarge)
402}
403
404impl Abi {
405    /// Lays out a frame under this convention.
406    ///
407    /// # Errors
408    ///
409    /// [`FrameError::Alignment`] for a slot alignment that is not a power of
410    /// two up to the stack alignment, [`FrameError::OverlappingIncoming`] for
411    /// incoming slots that overlap, and [`FrameError::TooLarge`] when any
412    /// offset would not fit a 32-bit displacement.
413    ///
414    /// # Examples
415    ///
416    /// ```
417    /// use backend_lang::RegSet;
418    /// use backend_lang::mir::SlotKind;
419    /// use backend_lang::target::frame::{FrameInput, FrameStep};
420    /// use backend_lang::x86_64::{SYSV, RBP, R12};
421    ///
422    /// let slots = [SlotKind::Local { size: 4, align: 4 }, SlotKind::Local { size: 16, align: 16 }];
423    /// let frame = SYSV.layout_frame(&FrameInput {
424    ///     slots: &slots,
425    ///     written: RegSet::of(&[R12]),
426    ///     has_calls: true,
427    ///     outgoing: 0,
428    ///     frame_pointer: true,
429    /// })?;
430    /// // ret + rbp + r12 = 24 bytes above; 20 bytes of locals round to 24 so the
431    /// // stack pointer ends 16-aligned.
432    /// assert_eq!(frame.alloc(), 24);
433    /// assert_eq!(frame.prologue(), [
434    ///     FrameStep::Push(RBP),
435    ///     FrameStep::SetFramePointer,
436    ///     FrameStep::Push(R12),
437    ///     FrameStep::Alloc { bytes: 24, probe: 0 },
438    /// ]);
439    /// // The 16-aligned slot comes first, at the bottom.
440    /// assert_eq!(frame.slot_offset(backend_lang::mir::StackSlot::new(1)), Some(0));
441    /// assert_eq!(frame.slot_offset(backend_lang::mir::StackSlot::new(0)), Some(16));
442    /// # Ok::<(), backend_lang::target::frame::FrameError>(())
443    /// ```
444    pub fn layout_frame(&self, input: &FrameInput<'_>) -> Result<FrameLayout, FrameError> {
445        let target = self.target();
446        let align = u64::from(self.stack().align()).max(1);
447        let sp = target.stack_pointer();
448        let fp = target.frame_pointer();
449
450        // Which registers to preserve: the callee-saved ones the body writes.
451        // The frame pointer is handled by its own push when it is kept.
452        let mut saved = input.written.intersect(self.callee_saved()).without(sp);
453        if input.frame_pointer {
454            saved = saved.without(fp);
455        }
456        let push_class = target.addr_class();
457        let mut pushed = Vec::new();
458        let mut vec_regs = Vec::new();
459        for reg in saved.iter() {
460            if target.class_of(reg) == Some(push_class) {
461                pushed.push(reg);
462            } else {
463                vec_regs.push(reg);
464            }
465        }
466        let pushes = pushed.len() as u64 + u64::from(input.frame_pointer);
467        let above = WORD + WORD * pushes;
468
469        // Locals (and vector save slots), largest alignment first, then by
470        // slot number, so the packing is tight and the order deterministic.
471        let mut locals: Vec<(u32, u64, usize)> = Vec::new(); // (align, size, id); id >= slots.len() is a save slot
472        for (i, kind) in input.slots.iter().enumerate() {
473            if let SlotKind::Local { size, align: a } = *kind {
474                if a == 0 || !a.is_power_of_two() || u64::from(a) > align {
475                    return Err(FrameError::Alignment {
476                        slot: StackSlot::from_index(i),
477                        align: a,
478                    });
479                }
480                locals.push((a, u64::from(size), i));
481            }
482        }
483        for (k, &reg) in vec_regs.iter().enumerate() {
484            let class = target.class(target.class_of(reg).unwrap_or(push_class));
485            let a = class.spill_align().clamp(1, 16);
486            locals.push((a, u64::from(class.spill_size()), input.slots.len() + k));
487        }
488        locals.sort_unstable_by(|x, y| y.0.cmp(&x.0).then(x.2.cmp(&y.2)));
489        let mut local_off = Vec::with_capacity(locals.len());
490        let mut cursor = 0u64;
491        for &(a, size, id) in &locals {
492            let a = u64::from(a);
493            cursor = cursor.div_ceil(a) * a;
494            local_off.push((id, cursor));
495            cursor += size;
496        }
497        let locals_size = cursor;
498
499        let mut outgoing = u64::from(input.outgoing);
500        if input.has_calls {
501            outgoing = outgoing.max(u64::from(self.stack().shadow_space()));
502        }
503        let outgoing = outgoing.div_ceil(align) * align;
504        // Residue the allocation must have so the stack pointer ends aligned
505        // (the CFA is aligned: the caller's stack pointer at the call was).
506        let residue = (align - above % align) % align;
507
508        let red_zone_bytes = round_to_residue(locals_size, residue, align);
509        let (alloc, base, red_zone) = if !input.has_calls
510            && outgoing == 0
511            && locals_size > 0
512            && red_zone_bytes <= u64::from(self.stack().red_zone())
513        {
514            // Locals sit below the stack pointer: base offset is negative.
515            (0u64, -(red_zone_bytes as i64), true)
516        } else if !input.has_calls && outgoing == 0 && locals_size == 0 {
517            (0, 0, false)
518        } else {
519            (
520                round_to_residue(outgoing + locals_size, residue, align),
521                outgoing as i64,
522                false,
523            )
524        };
525        if alloc > i32::MAX as u64 {
526            return Err(FrameError::TooLarge);
527        }
528        let cfa = alloc + above;
529        let cfa_i = fits_i32(cfa)?;
530
531        let mut offsets = alloc::vec![0i32; input.slots.len()];
532        let mut vec_saves = Vec::with_capacity(vec_regs.len());
533        for &(id, off) in &local_off {
534            let at = base + off as i64;
535            let at = i32::try_from(at).map_err(|_| FrameError::TooLarge)?;
536            match offsets.get_mut(id) {
537                Some(o) => *o = at,
538                None => {
539                    if let Some(&reg) = vec_regs.get(id - input.slots.len()) {
540                        vec_saves.push((reg, at));
541                    }
542                }
543            }
544        }
545        // Incoming slots: CFA + offset. Checked for overlap by sorting.
546        let mut incoming: Vec<(u64, u64, usize)> = Vec::new();
547        for (i, kind) in input.slots.iter().enumerate() {
548            if let SlotKind::Incoming { offset, size } = *kind {
549                let at = cfa + u64::from(offset);
550                offsets[i] = fits_i32(at)?;
551                let _end = fits_i32(at + u64::from(size))?;
552                incoming.push((u64::from(offset), u64::from(size.max(1)), i));
553            }
554        }
555        incoming.sort_unstable();
556        for pair in incoming.windows(2) {
557            let (a, b) = (pair[0], pair[1]);
558            if a.0 + a.1 > b.0 {
559                return Err(FrameError::OverlappingIncoming {
560                    slot: StackSlot::from_index(b.2),
561                });
562            }
563        }
564
565        let alloc = alloc as u32;
566        let probe = self.stack().probe_interval();
567        let probe = if probe > 0 && alloc >= probe {
568            probe
569        } else {
570            0
571        };
572        let mut prologue = Vec::with_capacity(pushed.len() + vec_saves.len() + 3);
573        let mut epilogue = Vec::with_capacity(pushed.len() + vec_saves.len() + 2);
574        if input.frame_pointer {
575            prologue.push(FrameStep::Push(fp));
576            prologue.push(FrameStep::SetFramePointer);
577        }
578        prologue.extend(pushed.iter().map(|&r| FrameStep::Push(r)));
579        if alloc > 0 {
580            prologue.push(FrameStep::Alloc {
581                bytes: alloc,
582                probe,
583            });
584        }
585        prologue.extend(
586            vec_saves
587                .iter()
588                .map(|&(reg, offset)| FrameStep::SaveVec { reg, offset }),
589        );
590        epilogue.extend(
591            vec_saves
592                .iter()
593                .map(|&(reg, offset)| FrameStep::RestoreVec { reg, offset }),
594        );
595        if alloc > 0 {
596            epilogue.push(FrameStep::Free(alloc));
597        }
598        epilogue.extend(pushed.iter().rev().map(|&r| FrameStep::Pop(r)));
599        if input.frame_pointer {
600            epilogue.push(FrameStep::Pop(fp));
601        }
602        Ok(FrameLayout {
603            offsets,
604            pushed,
605            vec_saves,
606            prologue,
607            epilogue,
608            alloc,
609            cfa_offset: cfa_i as u32,
610            frame_pointer: input.frame_pointer,
611            red_zone,
612        })
613    }
614}
615
616#[cfg(test)]
617mod tests {
618    use alloc::string::ToString;
619    use alloc::vec;
620
621    use super::*;
622    use crate::x86_64::*;
623
624    fn input(slots: &[SlotKind], written: RegSet, calls: bool, fp: bool) -> FrameInput<'_> {
625        FrameInput {
626            slots,
627            written,
628            has_calls: calls,
629            outgoing: 0,
630            frame_pointer: fp,
631        }
632    }
633
634    /// Simulates the prologue from an aligned CFA and checks the invariants
635    /// every frame must hold.
636    fn check(abi: &Abi, frame: &FrameLayout, slots: &[SlotKind], calls: bool) {
637        let cfa: i64 = 0x10000;
638        let mut sp = cfa - 8; // return address
639        for step in frame.prologue() {
640            match *step {
641                FrameStep::Push(_) => sp -= 8,
642                FrameStep::Alloc { bytes, .. } => sp -= i64::from(bytes),
643                _ => {}
644            }
645        }
646        assert_eq!(cfa - sp, i64::from(frame.cfa_offset()));
647        if calls || frame.alloc() > 0 {
648            assert_eq!(sp % 16, 0, "stack pointer aligned after the prologue");
649        }
650        // Locals: aligned, disjoint, inside the frame (or the red zone), and
651        // clear of the outgoing area and the saved registers.
652        let mut spans = vec![];
653        for (i, s) in slots.iter().enumerate() {
654            let off = i64::from(
655                frame
656                    .slot_offset(StackSlot::from_index(i))
657                    .unwrap_or(i32::MIN),
658            );
659            let addr = sp + off;
660            match *s {
661                SlotKind::Local { size, align } => {
662                    assert_eq!(addr % i64::from(align), 0, "slot {i} aligned");
663                    let lo = if frame.uses_red_zone() {
664                        sp - i64::from(abi.stack().red_zone())
665                    } else {
666                        sp
667                    };
668                    assert!(addr >= lo);
669                    let pushes_bottom = cfa
670                        - 8
671                        - 8 * (frame.pushed().len() as i64 + i64::from(frame.frame_pointer()));
672                    assert!(addr + i64::from(size) <= pushes_bottom);
673                    spans.push((addr, addr + i64::from(size)));
674                }
675                SlotKind::Incoming { offset, .. } => assert_eq!(addr, cfa + i64::from(offset)),
676            }
677        }
678        spans.sort_unstable();
679        for w in spans.windows(2) {
680            assert!(w[0].1 <= w[1].0, "locals overlap");
681        }
682        // The epilogue undoes the prologue.
683        let mut back = sp;
684        for step in frame.epilogue() {
685            match *step {
686                FrameStep::Pop(_) => back += 8,
687                FrameStep::Free(b) => back += i64::from(b),
688                _ => {}
689            }
690        }
691        assert_eq!(back, cfa - 8);
692    }
693
694    #[test]
695    fn test_layout_invariants_over_mixed_shapes() {
696        let shapes: &[&[SlotKind]] = &[
697            &[],
698            &[SlotKind::Local { size: 1, align: 1 }],
699            &[
700                SlotKind::Local { size: 3, align: 1 },
701                SlotKind::Local {
702                    size: 16,
703                    align: 16,
704                },
705                SlotKind::Local { size: 8, align: 8 },
706                SlotKind::Incoming { offset: 0, size: 8 },
707                SlotKind::Local { size: 2, align: 2 },
708            ],
709            &[SlotKind::Local {
710                size: 200,
711                align: 8,
712            }],
713            &[SlotKind::Local {
714                size: 9000,
715                align: 16,
716            }],
717        ];
718        let writes = [
719            RegSet::EMPTY,
720            RegSet::of(&[RBX]),
721            RegSet::of(&[RBX, R12, R15]),
722            RegSet::of(&[RBX, RSI, RDI, XMM6, XMM9]),
723        ];
724        for abi in [&SYSV, &WIN64] {
725            for slots in shapes {
726                for &written in &writes {
727                    for calls in [false, true] {
728                        for fp in [false, true] {
729                            let frame = abi.layout_frame(&input(slots, written, calls, fp));
730                            let frame = frame.unwrap_or_else(|e| panic!("{e}"));
731                            check(abi, &frame, slots, calls);
732                        }
733                    }
734                }
735            }
736        }
737    }
738
739    #[test]
740    fn test_red_zone_only_for_sysv_leaves_that_fit() {
741        let small = [SlotKind::Local {
742            size: 120,
743            align: 8,
744        }];
745        let f = SYSV
746            .layout_frame(&input(&small, RegSet::EMPTY, false, true))
747            .unwrap_or_else(|e| panic!("{e}"));
748        assert!(f.uses_red_zone());
749        assert_eq!(f.alloc(), 0);
750        let f = SYSV
751            .layout_frame(&input(&small, RegSet::EMPTY, true, true))
752            .unwrap_or_else(|e| panic!("{e}"));
753        assert!(!f.uses_red_zone());
754        let f = WIN64
755            .layout_frame(&input(&small, RegSet::EMPTY, false, true))
756            .unwrap_or_else(|e| panic!("{e}"));
757        assert!(!f.uses_red_zone());
758        let big = [SlotKind::Local {
759            size: 129,
760            align: 8,
761        }];
762        let f = SYSV
763            .layout_frame(&input(&big, RegSet::EMPTY, false, true))
764            .unwrap_or_else(|e| panic!("{e}"));
765        assert!(!f.uses_red_zone());
766    }
767
768    #[test]
769    fn test_win64_shadow_space_and_vector_saves() {
770        let f = WIN64
771            .layout_frame(&input(&[], RegSet::of(&[XMM6, RSI]), true, false))
772            .unwrap_or_else(|e| panic!("{e}"));
773        // rsi pushed; xmm6 saved in a 16-byte slot above the 32-byte shadow area.
774        assert_eq!(f.pushed(), [RSI]);
775        assert_eq!(f.vec_saves(), [(XMM6, 32)]);
776        assert!(f.alloc() >= 48);
777        assert!(f.prologue().contains(&FrameStep::SaveVec {
778            reg: XMM6,
779            offset: 32
780        }));
781        // SysV preserves no vector registers.
782        let f = SYSV
783            .layout_frame(&input(&[], RegSet::of(&[XMM6]), true, false))
784            .unwrap_or_else(|e| panic!("{e}"));
785        assert!(f.vec_saves().is_empty());
786    }
787
788    #[test]
789    fn test_probe_threshold() {
790        let page = [SlotKind::Local {
791            size: 4096,
792            align: 8,
793        }];
794        for abi in [&SYSV, &WIN64] {
795            let f = abi
796                .layout_frame(&input(&page, RegSet::EMPTY, true, true))
797                .unwrap_or_else(|e| panic!("{e}"));
798            assert!(matches!(
799                f.prologue().last(),
800                Some(FrameStep::Alloc { probe: 4096, .. })
801            ));
802        }
803        let small = [SlotKind::Local {
804            size: 4000,
805            align: 8,
806        }];
807        let f = WIN64
808            .layout_frame(&input(&small, RegSet::EMPTY, true, true))
809            .unwrap_or_else(|e| panic!("{e}"));
810        assert!(matches!(
811            f.prologue().last(),
812            Some(FrameStep::Alloc { probe: 0, .. })
813        ));
814    }
815
816    #[test]
817    fn test_errors() {
818        let bad = [SlotKind::Local { size: 8, align: 3 }];
819        assert_eq!(
820            SYSV.layout_frame(&input(&bad, RegSet::EMPTY, false, true)),
821            Err(FrameError::Alignment {
822                slot: StackSlot::from_index(0),
823                align: 3
824            })
825        );
826        let zero = [SlotKind::Local { size: 8, align: 0 }];
827        assert!(
828            SYSV.layout_frame(&input(&zero, RegSet::EMPTY, false, true))
829                .is_err()
830        );
831        let huge = [SlotKind::Local {
832            size: u32::MAX,
833            align: 8,
834        }];
835        assert_eq!(
836            SYSV.layout_frame(&input(&huge, RegSet::EMPTY, false, true)),
837            Err(FrameError::TooLarge)
838        );
839        let many = vec![
840            SlotKind::Local {
841                size: 1 << 30,
842                align: 8
843            };
844            3
845        ];
846        assert_eq!(
847            SYSV.layout_frame(&input(&many, RegSet::EMPTY, false, true)),
848            Err(FrameError::TooLarge)
849        );
850        let far = [SlotKind::Incoming {
851            offset: u32::MAX - 4,
852            size: 8,
853        }];
854        assert_eq!(
855            SYSV.layout_frame(&input(&far, RegSet::EMPTY, false, true)),
856            Err(FrameError::TooLarge)
857        );
858        let overlap = [
859            SlotKind::Incoming { offset: 0, size: 8 },
860            SlotKind::Incoming { offset: 4, size: 8 },
861        ];
862        assert_eq!(
863            SYSV.layout_frame(&input(&overlap, RegSet::EMPTY, false, true)),
864            Err(FrameError::OverlappingIncoming {
865                slot: StackSlot::from_index(1)
866            })
867        );
868        assert!(FrameError::TooLarge.to_string().contains("2 GiB"));
869    }
870}