Skip to main content

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 &reg 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 &reg 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 &reg in &regs {
493            func.build(block, opcode).def(reg, GPR).finish();
494        }
495        for &reg in &regs {
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, &REGS)
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}