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