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