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