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 ® in ®s {
457 func.build(block, opcode).def(reg, GPR).finish();
458 }
459 for ® in ®s {
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(|®| 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}