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, ®) 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(®) = 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}