rucc_codegen/finish.rs
1//! The prologue, the epilogue, and the moves the allocator asked for.
2//!
3//! Design: `spec/10-backend.md` sections 10.4 and 10.7.
4//!
5//! [`crate::frame`] works out what a function's stack looks like and writes nothing. This is what
6//! writes it. Three things are still missing from a function the allocator has finished with, and
7//! all three of them are instructions no lowering rule chose:
8//!
9//! ```text
10//! the prologue takes the frame the layout worked out, and puts away the registers a call
11//! leaves alone that this function writes anyway
12//! the moves every spill, every reload and every copy the allocator handed back as an
13//! edit, in the place it said and in the order it said
14//! the epilogue gives the frame back and puts the registers back, at the end of every block
15//! the function returns from
16//! ```
17//!
18//! There is a fourth thing and it is not an instruction but a number. The lowering wrote an
19//! instruction for every `alloca` that computes the address of the memory it asked for, and could
20//! not write how far into the frame that memory is, because when it ran there was no frame. So
21//! the displacement of each of those is filled in here, out of the same [`Frame`] everything else
22//! here reads, and off the same stack pointer every other offset in it is from.
23//!
24//! The loads that read the arguments the caller passed on the stack are waiting on the same number
25//! and on one more. Those bytes are the caller's rather than this function's, and a frame that had
26//! to force its own alignment cannot say how far away the caller's stack pointer was, so it reaches
27//! back through the frame pointer instead. Which register a load reads through is therefore settled
28//! here too, and it is the only base register in a finished function that was not settled by
29//! whoever wrote the instruction.
30//!
31//! After this the function is one an encoder can read: every register is physical, every offset
32//! into the frame is a constant, and the stack pointer is where the convention says it should be
33//! at every instruction that could look.
34//!
35//! # Why the moves go in first
36//!
37//! Every offset the frame reports is from the stack pointer as it stands in the body of the
38//! function. A spill written before the prologue exists would be written in front of the
39//! instruction it belongs to and behind nothing, which is where the prologue then goes, so the
40//! prologue ends up in front of it and the offsets stay true. Writing them the other way round
41//! would put the first reload above the instruction that takes the frame, and it would read from
42//! an address that is one frame out.
43//!
44//! # Where a return is
45//!
46//! A block that goes nowhere is a block the function leaves from. Mostly that is a return, and
47//! the other kind is a block ending in `unreachable`, which is a point the front end says control
48//! does not arrive at and which the lowering writes no instruction for. Both want the same thing
49//! here. A return wants the epilogue because that is what a return is once the frame is known,
50//! and an unreachable block wants it because the alternative is a function whose last instruction
51//! falls into whatever the assembler put after it, which is worse than an epilogue nothing runs.
52//! So the epilogue goes at the end of every block with an empty successor list, and there may be
53//! several, because nothing here insists a function has one exit.
54//!
55//! # What is target-specific here
56//!
57//! The names, and only the names. Which instruction pushes a register and which one moves the
58//! stack pointer is [`rucc_target::FrameInsts`], which the target says and this reads, so what
59//! is written below is the shape of a prologue rather than any particular machine's. That is
60//! `spec/10-backend.md` section 10.8 as it applies to the one pass that would otherwise be full
61//! of `x64.` by hand.
62
63use rucc_base::Interner;
64use rucc_mir::{Block, CfiOp, Func, Inst, Mem, Opcode, Operand, Reg};
65use rucc_regalloc::Allocation;
66use rucc_regalloc::assign::Place;
67use rucc_regalloc::rewrite::{At, Edit};
68use rucc_target::{CallRegs, FrameInsts, PhysReg, RegClass};
69
70use crate::frame::Frame;
71use crate::lower::Stack;
72
73/// Writes the moves, the prologue and the epilogue into a function the allocator has finished
74/// with.
75///
76/// # Panics
77///
78/// Panics on a function with no blocks in it, on a frame whose slots or locals the allocation and
79/// the lowering do not match, and on a move of a class the target did not say how to move. All of
80/// them are the caller handing it a frame and a function that were not worked out from each other.
81pub fn finish(
82 func: &mut Func,
83 allocation: &Allocation,
84 frame: &Frame,
85 stack: &Stack,
86 conv: &CallRegs,
87 insts: &FrameInsts,
88 names: &mut Interner,
89) {
90 let entry = func.entry().expect("a function with a block in it");
91 let returns: Vec<Block> = func.blocks().filter(|&block| func[block].succs.is_empty()).collect();
92
93 // Before anything is written, because these are instructions the lowering already put in the
94 // function and every one of them is somewhere the prologue is about to go in front of, which
95 // is what makes an offset from the stack pointer the right thing to write into them.
96 for &(inst, local) in &stack.addresses {
97 let at = frame.local(local).expect("a local the frame was worked out from");
98 let mem = func[inst].mem.expect("the address of a local is an address");
99 func[mem].disp = at;
100 }
101
102 // The same, one area further up, and through the frame pointer when that is what reaches it.
103 // These are in the entry block ahead of everything, so the prologue still goes in front of
104 // them, which is what makes both registers hold what these offsets are counted from.
105 let incoming = frame.incoming();
106 for &(inst, up) in &stack.arguments {
107 let mem = func[inst].mem.expect("an argument read out of memory is read from an address");
108 func[mem].disp = incoming.at + offset(up);
109 if incoming.through_frame_pointer {
110 // The base register is an operand of the instruction and the addressing mode holds
111 // where in the operand vector it is, so the register is changed there and not here.
112 let at = func[mem].base.expect("an address the lowering wrote a base register into");
113 let operands = func[inst].operands;
114 func[operands][usize::from(at)].reg = Reg::physical(conv.frame_pointer);
115 }
116 }
117
118 let mut writer = Writer { func, conv, insts, names };
119
120 let mut cursors: Vec<(At, Inst)> = Vec::new();
121 for edit in &allocation.edits {
122 let inst = writer.mov(edit, frame);
123 writer.put(&mut cursors, edit.at, inst);
124 }
125
126 let prologue = writer.prologue(frame);
127 for &inst in prologue.iter().rev() {
128 writer.func.prepend_inst(entry, inst);
129 }
130 for block in returns {
131 let epilogue = writer.epilogue(frame);
132 for inst in epilogue {
133 writer.func.append_inst(block, inst);
134 }
135 }
136}
137
138/// One function having its frame written into it.
139struct Writer<'a> {
140 func: &'a mut Func,
141 conv: &'a CallRegs,
142 insts: &'a FrameInsts,
143 names: &'a mut Interner,
144}
145
146impl Writer<'_> {
147 /// The instructions the prologue is, in the order they run.
148 ///
149 /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
150 /// before anything else, so that it points at a fixed place whatever else happens. The
151 /// registers are pushed before the alignment is forced, so that the epilogue can find them
152 /// again from the frame pointer, since after the alignment is forced nothing else can. And the
153 /// vector registers are stored last, because until the frame has been taken there is nowhere
154 /// to store them.
155 fn prologue(&mut self, frame: &Frame) -> Vec<Inst> {
156 let sp = self.conv.stack_pointer;
157 let fp = self.conv.frame_pointer;
158 let int = self.conv.int_class;
159 let sse = self.conv.sse_class;
160 let word = offset(self.conv.word);
161 let mut out = Vec::new();
162 // How far the stack pointer is below the canonical frame address, and whether the address
163 // is still counted from the stack pointer at all. It starts at the return address the
164 // call itself pushed, which is the rule the CIE already states, so the first row here is
165 // the first thing this function does on top of that.
166 let mut below = offset(self.conv.return_address);
167 let mut from_sp = true;
168 if frame.frame_pointer() {
169 let inst = self.push(fp);
170 out.push(inst);
171 below += word;
172 self.row(inst, CfiOp::DefCfaOffset(below));
173 self.saved(inst, int, fp, -below);
174 let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
175 let inst = self.two(mov, fp, sp);
176 out.push(inst);
177 let number = self.dwarf(int, fp);
178 self.row(inst, CfiOp::DefCfaRegister(number));
179 from_sp = false;
180 }
181 for ® in frame.saved_int() {
182 let inst = self.push(reg);
183 out.push(inst);
184 below += word;
185 if from_sp {
186 self.row(inst, CfiOp::DefCfaOffset(below));
187 }
188 self.saved(inst, int, reg, -below);
189 }
190 if let Some(to) = frame.realign() {
191 // Nothing is written for this and nothing can be. After it the stack pointer is a
192 // rounded-down version of where it was rather than a fixed distance from it, which is
193 // exactly what a rule cannot say. It is also why a frame that realigns is a frame
194 // with a frame pointer: by here the address is already counted from that instead.
195 assert!(!from_sp, "a frame that forces its own alignment has a frame pointer");
196 let and = self.opcode(self.insts.align);
197 out.push(self.arith(and, -i64::from(to)));
198 }
199 if frame.size() > 0 {
200 let sub = self.opcode(self.insts.sub);
201 let inst = self.arith(sub, i64::from(frame.size()));
202 out.push(inst);
203 below += offset(frame.size());
204 if from_sp {
205 self.row(inst, CfiOp::DefCfaOffset(below));
206 }
207 }
208 for save in frame.saved_sse() {
209 let inst = self.store(sse, save.reg, save.at);
210 out.push(inst);
211 // Where it went is an offset from the stack pointer in the body, and the address is
212 // `below` above that, so the two make one constant. Unless the frame realigned, in
213 // which case there is no such constant and the rule is left out rather than guessed;
214 // the one convention that realigns and the one that preserves a vector register are
215 // not the same convention, so nothing reaches this today.
216 if frame.realign().is_none() {
217 self.saved(inst, sse, save.reg, save.at - below);
218 }
219 }
220 // The rules the body runs under, kept so that each epilogue can put them back rather than
221 // leaving the next block reading whatever the last one ended on. See `epilogue`.
222 if let Some(&last) = out.last() {
223 self.row(last, CfiOp::RememberState);
224 }
225 out
226 }
227
228 /// The instructions the epilogue is, in the order they run.
229 ///
230 /// The vector registers are read back while the stack pointer is still where the body left it,
231 /// because that is what their offsets are from. Then the stack pointer goes back to the last
232 /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
233 /// moved and a read of the frame pointer when it did not.
234 fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
235 let sp = self.conv.stack_pointer;
236 let fp = self.conv.frame_pointer;
237 let int = self.conv.int_class;
238 let sse = self.conv.sse_class;
239 let word = self.conv.word;
240 let described = !self.func.cfi.is_empty();
241 let mut out = Vec::new();
242 // Where the body left things, which is where every epilogue starts from.
243 let mut below = offset(self.conv.return_address)
244 + offset(word) * self.pushes(frame)
245 + offset(frame.size());
246 let from_sp = !frame.frame_pointer();
247 for save in frame.saved_sse() {
248 let inst = self.load(sse, save.reg, save.at);
249 out.push(inst);
250 if frame.realign().is_none() {
251 self.restored(inst, sse, save.reg);
252 }
253 }
254 let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
255 if frame.frame_pointer() {
256 // No row for either of these. The address is counted from the frame pointer here and
257 // this is what moves the stack pointer rather than the frame pointer, so the rule that
258 // was true before it is still true after it.
259 if pushed == 0 {
260 let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
261 out.push(self.two(mov, sp, fp));
262 } else {
263 let lea = self.opcode(self.insts.lea);
264 let back = -offset(word * pushed);
265 out.push(self.address(lea, sp, fp, back));
266 }
267 } else if frame.size() > 0 {
268 let add = self.opcode(self.insts.add);
269 let inst = self.arith(add, i64::from(frame.size()));
270 out.push(inst);
271 below -= offset(frame.size());
272 self.row(inst, CfiOp::DefCfaOffset(below));
273 }
274 for ® in frame.saved_int().iter().rev() {
275 let inst = self.pop(reg);
276 out.push(inst);
277 self.restored(inst, int, reg);
278 below -= offset(word);
279 if from_sp {
280 self.row(inst, CfiOp::DefCfaOffset(below));
281 }
282 }
283 if frame.frame_pointer() {
284 let inst = self.pop(fp);
285 out.push(inst);
286 self.restored(inst, int, fp);
287 // The frame pointer holds the caller's value again, so the address goes back to being
288 // counted from the stack pointer, which by now is at the return address.
289 let number = self.dwarf(int, sp);
290 self.row(inst, CfiOp::DefCfa { reg: number, offset: offset(self.conv.return_address) });
291 }
292 let ret = self.opcode(self.insts.ret);
293 let inst = self.func.build_loose(ret).finish();
294 out.push(inst);
295 // These take effect at the address just past the return, which is where the next block
296 // begins, and the next block is body again. Popping the body's rules and pushing them
297 // straight back leaves the stack one deep however many blocks the function returns from,
298 // which is what makes one remembering in the prologue enough for all of them.
299 if described {
300 self.row(inst, CfiOp::RestoreState);
301 self.row(inst, CfiOp::RememberState);
302 }
303 out
304 }
305
306 /// How many general purpose registers the prologue put on the stack, the frame pointer
307 /// included.
308 fn pushes(&self, frame: &Frame) -> i32 {
309 let saved = i32::try_from(frame.saved_int().len()).expect("a frame");
310 saved + i32::from(frame.frame_pointer())
311 }
312
313 /// One row of the unwind table, taking effect after that instruction.
314 fn row(&mut self, inst: Inst, op: CfiOp) {
315 self.func.cfi.push((inst, op));
316 }
317
318 /// A row saying the caller's copy of that register is that far from the canonical frame
319 /// address, which is below it and so is negative.
320 fn saved(&mut self, inst: Inst, class: RegClass, reg: PhysReg, from_cfa: i32) {
321 let number = self.dwarf(class, reg);
322 self.row(inst, CfiOp::Offset { reg: number, offset: from_cfa });
323 }
324
325 /// A row saying that register holds what the caller left in it again.
326 fn restored(&mut self, inst: Inst, class: RegClass, reg: PhysReg) {
327 let number = self.dwarf(class, reg);
328 self.row(inst, CfiOp::Restore(number));
329 }
330
331 /// What an unwind table calls that register.
332 fn dwarf(&self, class: RegClass, reg: PhysReg) -> u16 {
333 self.conv.dwarf(class, reg).expect("a register a frame saves is one the table can name")
334 }
335
336 /// One edit as the instruction that makes it true.
337 fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
338 let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
339 match (edit.mov.to, edit.mov.from) {
340 (Place::Reg(to), Place::Reg(from)) => {
341 let mov = self.opcode(moves.mov);
342 self.func
343 .build_loose(mov)
344 .def(Reg::physical(to), edit.class)
345 .uses(Reg::physical(from), edit.class)
346 .finish()
347 }
348 (Place::Reg(to), Place::Slot(slot)) => {
349 let at = self.slot(frame, slot);
350 self.load(edit.class, to, at)
351 }
352 (Place::Slot(slot), Place::Reg(from)) => {
353 let at = self.slot(frame, slot);
354 self.store(edit.class, from, at)
355 }
356 // The allocator expands this into two moves through a register of its own, because a
357 // machine that could do it in one is not a machine any of this is written for.
358 (Place::Slot(_), Place::Slot(_)) => {
359 unreachable!("a move from one stack slot straight into another")
360 }
361 }
362 }
363
364 /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
365 ///
366 /// The edits at one place are in the order they have to be made in, so each one goes behind
367 /// the last, and the first of them is what the place itself means.
368 fn put(&mut self, cursors: &mut Vec<(At, Inst)>, at: At, inst: Inst) {
369 if let Some(cursor) = cursors.iter_mut().find(|(place, _)| *place == at) {
370 self.func.insert_after(cursor.1, inst);
371 cursor.1 = inst;
372 return;
373 }
374 match at {
375 At::Before(before) => self.func.insert_before(before, inst),
376 At::After(after) => self.func.insert_after(after, inst),
377 At::StartOf(block) => self.func.prepend_inst(block, inst),
378 // Behind everything in the block. A block the allocator puts an edge's moves at the
379 // end of is one with a single edge out of it, and an edge like that is not an
380 // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
381 // So the last instruction is an ordinary one, which may still be waiting on moves of
382 // its own that have to be made before the edge's are.
383 At::EndOf(block) => self.func.append_inst(block, inst),
384 }
385 cursors.push((at, inst));
386 }
387
388 /// Where a spill slot is, from the stack pointer in the body of the function.
389 fn slot(&self, frame: &Frame, slot: u32) -> i32 {
390 frame.slot(slot).expect("a slot the frame was worked out from")
391 }
392
393 /// Reads a register out of the frame.
394 fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
395 let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
396 let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
397 self.func
398 .build_loose(load)
399 .def(Reg::physical(reg), class)
400 .mem(Mem::at(base).plus(at))
401 .finish()
402 }
403
404 /// Writes a register into the frame.
405 fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
406 let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
407 let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
408 self.func
409 .build_loose(store)
410 .uses(Reg::physical(reg), class)
411 .mem(Mem::at(base).plus(at))
412 .finish()
413 }
414
415 /// Puts a general purpose register on the stack.
416 fn push(&mut self, reg: PhysReg) -> Inst {
417 let push = self.opcode(self.insts.push);
418 self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
419 }
420
421 /// Takes a general purpose register back off the stack.
422 fn pop(&mut self, reg: PhysReg) -> Inst {
423 let pop = self.opcode(self.insts.pop);
424 self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
425 }
426
427 /// One general purpose register written with another.
428 fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
429 let class = self.conv.int_class;
430 self.func
431 .build_loose(opcode)
432 .def(Reg::physical(to), class)
433 .uses(Reg::physical(from), class)
434 .finish()
435 }
436
437 /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
438 fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
439 let class = self.conv.int_class;
440 let sp = Reg::physical(self.conv.stack_pointer);
441 self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
442 }
443
444 /// One register written with an address rather than with what is at it.
445 fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
446 let class = self.conv.int_class;
447 let base = Operand::read(Reg::physical(base), class);
448 self.func
449 .build_loose(opcode)
450 .def(Reg::physical(to), class)
451 .mem(Mem::at(base).plus(disp))
452 .finish()
453 }
454
455 /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
456 /// then the name the target gave.
457 fn opcode(&mut self, name: &str) -> Opcode {
458 Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
459 }
460}
461
462/// A distance in a frame, as the signed number every offset is.
463fn offset(bytes: u32) -> i32 {
464 i32::try_from(bytes).expect("a frame under two gigabytes")
465}
466
467#[cfg(test)]
468mod tests {
469 use rucc_base::Interner;
470 use rucc_mir::{BlockCall, print_func};
471 use rucc_regalloc::assign::Env;
472 use rucc_target::x86_64::{FRAME, GPR, REGS, SYSV, WIN64, XMM, xmm};
473
474 use super::*;
475 use crate::frame::{Layout, Local};
476
477 /// An environment offering that many of the convention's registers, with everything after
478 /// them held back as scratch.
479 fn env(conv: &CallRegs, count: usize) -> Env {
480 Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
481 }
482
483 /// A function of that many values, every one written before any is read, allocated with that
484 /// many registers to hand out. The same shape the frame layout's own tests are written
485 /// against, so that a frame here is one that has already been checked there.
486 fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
487 let mut names = Interner::new();
488 let mut func = Func::new(names.intern("f"));
489 let opcode = Opcode::new(names.intern("x64.nop"));
490 let block = func.create_block();
491 let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
492 for ® in ®s {
493 func.build(block, opcode).def(reg, GPR).finish();
494 }
495 for ® in ®s {
496 func.build(block, opcode).uses(reg, GPR).finish();
497 }
498 let allocation = rucc_regalloc::run(&mut func, &env(conv, count), "test");
499 (func, allocation, names)
500 }
501
502 /// The function with its frame written into it, as the lines a dump would show.
503 fn written(
504 func: &mut Func,
505 allocation: &Allocation,
506 layout: &Layout<'_>,
507 names: &mut Interner,
508 ) -> Vec<String> {
509 let frame = Frame::of(func, allocation, layout);
510 finish(func, allocation, &frame, &Stack::default(), layout.conv, &FRAME, names);
511 print_func(func, names, ®S)
512 .lines()
513 .filter(|line| !line.is_empty())
514 .map(|line| line.trim().to_string())
515 .collect()
516 }
517
518 /// Just the lines the frame put in, which is every line that is not the function it was
519 /// given and not the shape of the dump around it.
520 fn added(lines: &[String]) -> Vec<&str> {
521 lines
522 .iter()
523 .map(String::as_str)
524 .filter(|line| !line.contains("x64.nop"))
525 .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
526 .collect()
527 }
528
529 #[test]
530 fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
531 let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
532 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
533
534 // Two values and four registers, so nothing is spilled, nothing is saved and the stack
535 // pointer never moves. A prologue of nothing is the right prologue for that.
536 assert_eq!(added(&lines), ["x64.ret"]);
537 }
538
539 #[test]
540 fn a_spill_is_a_store_and_a_reload_is_a_load() {
541 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
542 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
543
544 // Two registers for four values, so two of them go to the stack. The store goes behind the
545 // instruction that wrote the value and the load in front of the one that wants it, both at
546 // the offsets the frame gave, which are below the stack pointer because a small leaf
547 // function is entitled to the red zone.
548 assert_eq!(
549 lines,
550 [
551 "mfunc @f {",
552 "block0:",
553 "$rax = x64.nop",
554 "$rcx = x64.nop",
555 "$rdx = x64.nop",
556 "x64.mov_mr_64 $rdx, [$rsp - 16]",
557 "$rdx = x64.nop",
558 "x64.mov_mr_64 $rdx, [$rsp - 8]",
559 "x64.nop $rax",
560 "x64.nop $rcx",
561 "$rdx = x64.mov_rm_64 [$rsp - 16]",
562 "x64.nop $rdx",
563 "$rdx = x64.mov_rm_64 [$rsp - 8]",
564 "x64.nop $rdx",
565 "x64.ret",
566 "}",
567 ]
568 );
569 }
570
571 #[test]
572 fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
573 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
574 let base = Layout::new(&SYSV, REGS);
575 let layout = Layout { red_zone: false, ..base };
576 let lines = written(&mut func, &allocation, &layout, &mut names);
577
578 // The same function told it may not use the red zone takes sixteen bytes instead, and
579 // every offset moves above the stack pointer to match.
580 assert_eq!(
581 added(&lines),
582 [
583 "$rsp = x64.sub_ri_64 $rsp, 16",
584 "x64.mov_mr_64 $rdx, [$rsp]",
585 "x64.mov_mr_64 $rdx, [$rsp + 8]",
586 "$rdx = x64.mov_rm_64 [$rsp]",
587 "$rdx = x64.mov_rm_64 [$rsp + 8]",
588 "$rsp = x64.add_ri_64 $rsp, 16",
589 "x64.ret",
590 ]
591 );
592 }
593
594 #[test]
595 fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
596 let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
597 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
598
599 // Four registers a call leaves alone, pushed in the convention's order and popped in the
600 // other one, which is the only order that gets each of them its own value back.
601 assert_eq!(
602 added(&lines),
603 [
604 "x64.push_64 $rbx",
605 "x64.push_64 $r12",
606 "x64.push_64 $r13",
607 "x64.push_64 $r14",
608 "$r14 = x64.pop_64",
609 "$r13 = x64.pop_64",
610 "$r12 = x64.pop_64",
611 "$rbx = x64.pop_64",
612 "x64.ret",
613 ]
614 );
615 }
616
617 #[test]
618 fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
619 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
620 let base = Layout::new(&SYSV, REGS);
621 let layout = Layout { frame_pointer: true, red_zone: false, ..base };
622 let lines = written(&mut func, &allocation, &layout, &mut names);
623
624 // The frame pointer is saved before anything else and points at where it was saved, so the
625 // epilogue reaches the stack pointer through it rather than by counting the frame back.
626 assert_eq!(
627 added(&lines),
628 [
629 "x64.push_64 $rbp",
630 "$rbp = x64.mov_rr_64 $rsp",
631 "$rsp = x64.sub_ri_64 $rsp, 16",
632 "x64.mov_mr_64 $rdx, [$rsp]",
633 "x64.mov_mr_64 $rdx, [$rsp + 8]",
634 "$rdx = x64.mov_rm_64 [$rsp]",
635 "$rdx = x64.mov_rm_64 [$rsp + 8]",
636 "$rsp = x64.mov_rr_64 $rbp",
637 "$rbp = x64.pop_64",
638 "x64.ret",
639 ]
640 );
641 }
642
643 #[test]
644 fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
645 let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
646 let locals = [Local { size: 64, align: 32 }];
647 let base = Layout::new(&SYSV, REGS);
648 let layout = Layout { locals: &locals, ..base };
649 let lines = written(&mut func, &allocation, &layout, &mut names);
650
651 // Forcing the alignment throws away how far the stack pointer had moved, so the registers
652 // are pushed before it happens and the epilogue counts back from the frame pointer to find
653 // them. The frame pointer is required here whatever the flags said.
654 assert_eq!(
655 added(&lines),
656 [
657 "x64.push_64 $rbp",
658 "$rbp = x64.mov_rr_64 $rsp",
659 "x64.push_64 $rbx",
660 "x64.push_64 $r12",
661 "x64.push_64 $r13",
662 "x64.push_64 $r14",
663 "$rsp = x64.and_ri_64 $rsp, -32",
664 "$rsp = x64.sub_ri_64 $rsp, 64",
665 "$rsp = x64.lea_64 [$rbp - 32]",
666 "$r14 = x64.pop_64",
667 "$r13 = x64.pop_64",
668 "$r12 = x64.pop_64",
669 "$rbx = x64.pop_64",
670 "$rbp = x64.pop_64",
671 "x64.ret",
672 ]
673 );
674 }
675
676 #[test]
677 fn every_block_the_function_returns_from_gets_an_epilogue() {
678 let mut names = Interner::new();
679 let mut func = Func::new(names.intern("f"));
680 let opcode = Opcode::new(names.intern("x64.nop"));
681 let head = func.create_block();
682 let left = func.create_block();
683 let right = func.create_block();
684 func.build(head, opcode).finish();
685 *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
686 func.build(left, opcode).finish();
687 func.build(right, opcode).finish();
688 let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test");
689 let base = Layout::new(&SYSV, REGS);
690 let layout = Layout { leaf: false, ..base };
691 let lines = written(&mut func, &allocation, &layout, &mut names);
692
693 // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
694 // because a block with an edge out of it is not a block anything returns from.
695 assert_eq!(
696 lines,
697 [
698 "mfunc @f {",
699 "block0:",
700 "$rsp = x64.sub_ri_64 $rsp, 8",
701 "x64.nop block1, block2",
702 "block1:",
703 "x64.nop",
704 "$rsp = x64.add_ri_64 $rsp, 8",
705 "x64.ret",
706 "block2:",
707 "x64.nop",
708 "$rsp = x64.add_ri_64 $rsp, 8",
709 "x64.ret",
710 "}",
711 ]
712 );
713 }
714
715 #[test]
716 fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
717 let mut names = Interner::new();
718 let mut func = Func::new(names.intern("f"));
719 let opcode = Opcode::new(names.intern("x64.nop"));
720 let block = func.create_block();
721 // An instruction that writes one of the vector registers Windows preserves, which is what
722 // a rule for something that has to use it produces.
723 func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
724 let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4), "test");
725 let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
726
727 // No machine here pushes a vector register, so it is stored into the frame rather than
728 // pushed, and the frame has to be taken before there is anywhere to put it.
729 assert_eq!(
730 added(&lines),
731 [
732 "$rsp = x64.sub_ri_64 $rsp, 24",
733 "x64.movaps_mr $xmm6, [$rsp]",
734 "$xmm6 = x64.movaps_rm [$rsp]",
735 "$rsp = x64.add_ri_64 $rsp, 24",
736 "x64.ret",
737 ]
738 );
739 }
740}