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//! After this the function is one an encoder can read: every register is physical, every offset
19//! into the frame is a constant, and the stack pointer is where the convention says it should be
20//! at every instruction that could look.
21//!
22//! # Why the moves go in first
23//!
24//! Every offset the frame reports is from the stack pointer as it stands in the body of the
25//! function. A spill written before the prologue exists would be written in front of the
26//! instruction it belongs to and behind nothing, which is where the prologue then goes, so the
27//! prologue ends up in front of it and the offsets stay true. Writing them the other way round
28//! would put the first reload above the instruction that takes the frame, and it would read from
29//! an address that is one frame out.
30//!
31//! # Where a return is
32//!
33//! A block that goes nowhere is a block the function returns from. There is no other kind: a
34//! block with no successors and no return would be one that falls off the end of the function,
35//! which is a function that was mis-lowered rather than one this has an opinion about. So the
36//! epilogue goes at the end of every block with an empty successor list, and there may be several,
37//! because nothing here insists a function has one exit.
38//!
39//! # What is target-specific here
40//!
41//! The names, and only the names. Which instruction pushes a register and which one moves the
42//! stack pointer is [`rucc_target::FrameInsts`], which the target says and this reads, so what
43//! is written below is the shape of a prologue rather than any particular machine's. That is
44//! `spec/10-backend.md` section 10.8 as it applies to the one pass that would otherwise be full
45//! of `x64.` by hand.
46
47use rucc_base::Interner;
48use rucc_mir::{Block, Func, Inst, Mem, Opcode, Operand, Reg};
49use rucc_regalloc::Allocation;
50use rucc_regalloc::assign::Place;
51use rucc_regalloc::rewrite::{At, Edit};
52use rucc_target::{CallRegs, FrameInsts, PhysReg, RegClass};
53
54use crate::frame::Frame;
55
56/// Writes the moves, the prologue and the epilogue into a function the allocator has finished
57/// with.
58///
59/// # Panics
60///
61/// Panics on a function with no blocks in it, on a frame whose slots the allocation does not
62/// match, and on a move of a class the target did not say how to move. All three are the caller
63/// handing it a frame and a function that were not worked out from each other.
64pub fn finish(
65    func: &mut Func,
66    allocation: &Allocation,
67    frame: &Frame,
68    conv: &CallRegs,
69    insts: &FrameInsts,
70    names: &mut Interner,
71) {
72    let entry = func.entry().expect("a function with a block in it");
73    let returns: Vec<Block> = func.blocks().filter(|&block| func[block].succs.is_empty()).collect();
74    let mut writer = Writer { func, conv, insts, names };
75
76    let mut cursors: Vec<(At, Inst)> = Vec::new();
77    for edit in &allocation.edits {
78        let inst = writer.mov(edit, frame);
79        writer.put(&mut cursors, edit.at, inst);
80    }
81
82    let prologue = writer.prologue(frame);
83    for &inst in prologue.iter().rev() {
84        writer.func.prepend_inst(entry, inst);
85    }
86    for block in returns {
87        let epilogue = writer.epilogue(frame);
88        for inst in epilogue {
89            writer.func.append_inst(block, inst);
90        }
91    }
92}
93
94/// One function having its frame written into it.
95struct Writer<'a> {
96    func: &'a mut Func,
97    conv: &'a CallRegs,
98    insts: &'a FrameInsts,
99    names: &'a mut Interner,
100}
101
102impl Writer<'_> {
103    /// The instructions the prologue is, in the order they run.
104    ///
105    /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
106    /// before anything else, so that it points at a fixed place whatever else happens. The
107    /// registers are pushed before the alignment is forced, so that the epilogue can find them
108    /// again from the frame pointer, since after the alignment is forced nothing else can. And the
109    /// vector registers are stored last, because until the frame has been taken there is nowhere
110    /// to store them.
111    fn prologue(&mut self, frame: &Frame) -> Vec<Inst> {
112        let sp = self.conv.stack_pointer;
113        let fp = self.conv.frame_pointer;
114        let mut out = Vec::new();
115        if frame.frame_pointer() {
116            out.push(self.push(fp));
117            let mov = self.opcode(self.insts.moves(self.conv.int_class).expect("a move").mov);
118            out.push(self.two(mov, fp, sp));
119        }
120        for &reg in frame.saved_int() {
121            out.push(self.push(reg));
122        }
123        if let Some(to) = frame.realign() {
124            let and = self.opcode(self.insts.align);
125            out.push(self.arith(and, -i64::from(to)));
126        }
127        if frame.size() > 0 {
128            let sub = self.opcode(self.insts.sub);
129            out.push(self.arith(sub, i64::from(frame.size())));
130        }
131        for save in frame.saved_sse() {
132            out.push(self.store(self.conv.sse_class, save.reg, save.at));
133        }
134        out
135    }
136
137    /// The instructions the epilogue is, in the order they run.
138    ///
139    /// The vector registers are read back while the stack pointer is still where the body left it,
140    /// because that is what their offsets are from. Then the stack pointer goes back to the last
141    /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
142    /// moved and a read of the frame pointer when it did not.
143    fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
144        let sp = self.conv.stack_pointer;
145        let fp = self.conv.frame_pointer;
146        let word = self.conv.word;
147        let mut out = Vec::new();
148        for save in frame.saved_sse() {
149            out.push(self.load(self.conv.sse_class, save.reg, save.at));
150        }
151        let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
152        if frame.frame_pointer() {
153            if pushed == 0 {
154                let mov = self.opcode(self.insts.moves(self.conv.int_class).expect("a move").mov);
155                out.push(self.two(mov, sp, fp));
156            } else {
157                let lea = self.opcode(self.insts.lea);
158                let back = -offset(word * pushed);
159                out.push(self.address(lea, sp, fp, back));
160            }
161        } else if frame.size() > 0 {
162            let add = self.opcode(self.insts.add);
163            out.push(self.arith(add, i64::from(frame.size())));
164        }
165        for &reg in frame.saved_int().iter().rev() {
166            out.push(self.pop(reg));
167        }
168        if frame.frame_pointer() {
169            out.push(self.pop(fp));
170        }
171        let ret = self.opcode(self.insts.ret);
172        out.push(self.func.build_loose(ret).finish());
173        out
174    }
175
176    /// One edit as the instruction that makes it true.
177    fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
178        let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
179        match (edit.mov.to, edit.mov.from) {
180            (Place::Reg(to), Place::Reg(from)) => {
181                let mov = self.opcode(moves.mov);
182                self.func
183                    .build_loose(mov)
184                    .def(Reg::physical(to), edit.class)
185                    .uses(Reg::physical(from), edit.class)
186                    .finish()
187            }
188            (Place::Reg(to), Place::Slot(slot)) => {
189                let at = self.slot(frame, slot);
190                self.load(edit.class, to, at)
191            }
192            (Place::Slot(slot), Place::Reg(from)) => {
193                let at = self.slot(frame, slot);
194                self.store(edit.class, from, at)
195            }
196            // The allocator expands this into two moves through a register of its own, because a
197            // machine that could do it in one is not a machine any of this is written for.
198            (Place::Slot(_), Place::Slot(_)) => {
199                unreachable!("a move from one stack slot straight into another")
200            }
201        }
202    }
203
204    /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
205    ///
206    /// The edits at one place are in the order they have to be made in, so each one goes behind
207    /// the last, and the first of them is what the place itself means.
208    fn put(&mut self, cursors: &mut Vec<(At, Inst)>, at: At, inst: Inst) {
209        if let Some(cursor) = cursors.iter_mut().find(|(place, _)| *place == at) {
210            self.func.insert_after(cursor.1, inst);
211            cursor.1 = inst;
212            return;
213        }
214        match at {
215            At::Before(before) => self.func.insert_before(before, inst),
216            At::After(after) => self.func.insert_after(after, inst),
217            At::StartOf(block) => self.func.prepend_inst(block, inst),
218            // Behind everything in the block. A block the allocator puts an edge's moves at the
219            // end of is one with a single edge out of it, and an edge like that is not an
220            // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
221            // So the last instruction is an ordinary one, which may still be waiting on moves of
222            // its own that have to be made before the edge's are.
223            At::EndOf(block) => self.func.append_inst(block, inst),
224        }
225        cursors.push((at, inst));
226    }
227
228    /// Where a spill slot is, from the stack pointer in the body of the function.
229    fn slot(&self, frame: &Frame, slot: u32) -> i32 {
230        frame.slot(slot).expect("a slot the frame was worked out from")
231    }
232
233    /// Reads a register out of the frame.
234    fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
235        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
236        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
237        self.func
238            .build_loose(load)
239            .def(Reg::physical(reg), class)
240            .mem(Mem::at(base).plus(at))
241            .finish()
242    }
243
244    /// Writes a register into the frame.
245    fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
246        let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
247        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
248        self.func
249            .build_loose(store)
250            .uses(Reg::physical(reg), class)
251            .mem(Mem::at(base).plus(at))
252            .finish()
253    }
254
255    /// Puts a general purpose register on the stack.
256    fn push(&mut self, reg: PhysReg) -> Inst {
257        let push = self.opcode(self.insts.push);
258        self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
259    }
260
261    /// Takes a general purpose register back off the stack.
262    fn pop(&mut self, reg: PhysReg) -> Inst {
263        let pop = self.opcode(self.insts.pop);
264        self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
265    }
266
267    /// One general purpose register written with another.
268    fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
269        let class = self.conv.int_class;
270        self.func
271            .build_loose(opcode)
272            .def(Reg::physical(to), class)
273            .uses(Reg::physical(from), class)
274            .finish()
275    }
276
277    /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
278    fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
279        let class = self.conv.int_class;
280        let sp = Reg::physical(self.conv.stack_pointer);
281        self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
282    }
283
284    /// One register written with an address rather than with what is at it.
285    fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
286        let class = self.conv.int_class;
287        let base = Operand::read(Reg::physical(base), class);
288        self.func
289            .build_loose(opcode)
290            .def(Reg::physical(to), class)
291            .mem(Mem::at(base).plus(disp))
292            .finish()
293    }
294
295    /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
296    /// then the name the target gave.
297    fn opcode(&mut self, name: &str) -> Opcode {
298        Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
299    }
300}
301
302/// A distance in a frame, as the signed number every offset is.
303fn offset(bytes: u32) -> i32 {
304    i32::try_from(bytes).expect("a frame under two gigabytes")
305}
306
307#[cfg(test)]
308mod tests {
309    use rucc_base::Interner;
310    use rucc_mir::{BlockCall, print_func};
311    use rucc_regalloc::assign::Env;
312    use rucc_target::x86_64::{FRAME, GPR, REGS, SYSV, WIN64, XMM, xmm};
313
314    use super::*;
315    use crate::frame::{Layout, Local};
316
317    /// An environment offering that many of the convention's registers, with everything after
318    /// them held back as scratch.
319    fn env(conv: &CallRegs, count: usize) -> Env {
320        Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
321    }
322
323    /// A function of that many values, every one written before any is read, allocated with that
324    /// many registers to hand out. The same shape the frame layout's own tests are written
325    /// against, so that a frame here is one that has already been checked there.
326    fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
327        let mut names = Interner::new();
328        let mut func = Func::new(names.intern("f"));
329        let opcode = Opcode::new(names.intern("x64.nop"));
330        let block = func.create_block();
331        let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
332        for &reg in &regs {
333            func.build(block, opcode).def(reg, GPR).finish();
334        }
335        for &reg in &regs {
336            func.build(block, opcode).uses(reg, GPR).finish();
337        }
338        let allocation = rucc_regalloc::run(&mut func, &env(conv, count));
339        (func, allocation, names)
340    }
341
342    /// The function with its frame written into it, as the lines a dump would show.
343    fn written(
344        func: &mut Func,
345        allocation: &Allocation,
346        layout: &Layout<'_>,
347        names: &mut Interner,
348    ) -> Vec<String> {
349        let frame = Frame::of(func, allocation, layout);
350        finish(func, allocation, &frame, layout.conv, &FRAME, names);
351        print_func(func, names, &REGS)
352            .lines()
353            .filter(|line| !line.is_empty())
354            .map(|line| line.trim().to_string())
355            .collect()
356    }
357
358    /// Just the lines the frame put in, which is every line that is not the function it was
359    /// given and not the shape of the dump around it.
360    fn added(lines: &[String]) -> Vec<&str> {
361        lines
362            .iter()
363            .map(String::as_str)
364            .filter(|line| !line.contains("x64.nop"))
365            .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
366            .collect()
367    }
368
369    #[test]
370    fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
371        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
372        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
373
374        // Two values and four registers, so nothing is spilled, nothing is saved and the stack
375        // pointer never moves. A prologue of nothing is the right prologue for that.
376        assert_eq!(added(&lines), ["x64.ret"]);
377    }
378
379    #[test]
380    fn a_spill_is_a_store_and_a_reload_is_a_load() {
381        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
382        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
383
384        // Two registers for four values, so two of them go to the stack. The store goes behind the
385        // instruction that wrote the value and the load in front of the one that wants it, both at
386        // the offsets the frame gave, which are below the stack pointer because a small leaf
387        // function is entitled to the red zone.
388        assert_eq!(
389            lines,
390            [
391                "mfunc @f {",
392                "block0:",
393                "$rax = x64.nop",
394                "$rcx = x64.nop",
395                "$rdx = x64.nop",
396                "x64.mov_mr_64 $rdx, [$rsp - 16]",
397                "$rdx = x64.nop",
398                "x64.mov_mr_64 $rdx, [$rsp - 8]",
399                "x64.nop $rax",
400                "x64.nop $rcx",
401                "$rdx = x64.mov_rm_64 [$rsp - 16]",
402                "x64.nop $rdx",
403                "$rdx = x64.mov_rm_64 [$rsp - 8]",
404                "x64.nop $rdx",
405                "x64.ret",
406                "}",
407            ]
408        );
409    }
410
411    #[test]
412    fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
413        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
414        let base = Layout::new(&SYSV, REGS);
415        let layout = Layout { red_zone: false, ..base };
416        let lines = written(&mut func, &allocation, &layout, &mut names);
417
418        // The same function told it may not use the red zone takes sixteen bytes instead, and
419        // every offset moves above the stack pointer to match.
420        assert_eq!(
421            added(&lines),
422            [
423                "$rsp = x64.sub_ri_64 $rsp, 16",
424                "x64.mov_mr_64 $rdx, [$rsp]",
425                "x64.mov_mr_64 $rdx, [$rsp + 8]",
426                "$rdx = x64.mov_rm_64 [$rsp]",
427                "$rdx = x64.mov_rm_64 [$rsp + 8]",
428                "$rsp = x64.add_ri_64 $rsp, 16",
429                "x64.ret",
430            ]
431        );
432    }
433
434    #[test]
435    fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
436        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
437        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
438
439        // Four registers a call leaves alone, pushed in the convention's order and popped in the
440        // other one, which is the only order that gets each of them its own value back.
441        assert_eq!(
442            added(&lines),
443            [
444                "x64.push_64 $rbx",
445                "x64.push_64 $r12",
446                "x64.push_64 $r13",
447                "x64.push_64 $r14",
448                "$r14 = x64.pop_64",
449                "$r13 = x64.pop_64",
450                "$r12 = x64.pop_64",
451                "$rbx = x64.pop_64",
452                "x64.ret",
453            ]
454        );
455    }
456
457    #[test]
458    fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
459        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
460        let base = Layout::new(&SYSV, REGS);
461        let layout = Layout { frame_pointer: true, red_zone: false, ..base };
462        let lines = written(&mut func, &allocation, &layout, &mut names);
463
464        // The frame pointer is saved before anything else and points at where it was saved, so the
465        // epilogue reaches the stack pointer through it rather than by counting the frame back.
466        assert_eq!(
467            added(&lines),
468            [
469                "x64.push_64 $rbp",
470                "$rbp = x64.mov_rr_64 $rsp",
471                "$rsp = x64.sub_ri_64 $rsp, 16",
472                "x64.mov_mr_64 $rdx, [$rsp]",
473                "x64.mov_mr_64 $rdx, [$rsp + 8]",
474                "$rdx = x64.mov_rm_64 [$rsp]",
475                "$rdx = x64.mov_rm_64 [$rsp + 8]",
476                "$rsp = x64.mov_rr_64 $rbp",
477                "$rbp = x64.pop_64",
478                "x64.ret",
479            ]
480        );
481    }
482
483    #[test]
484    fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
485        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
486        let locals = [Local { size: 64, align: 32 }];
487        let base = Layout::new(&SYSV, REGS);
488        let layout = Layout { locals: &locals, ..base };
489        let lines = written(&mut func, &allocation, &layout, &mut names);
490
491        // Forcing the alignment throws away how far the stack pointer had moved, so the registers
492        // are pushed before it happens and the epilogue counts back from the frame pointer to find
493        // them. The frame pointer is required here whatever the flags said.
494        assert_eq!(
495            added(&lines),
496            [
497                "x64.push_64 $rbp",
498                "$rbp = x64.mov_rr_64 $rsp",
499                "x64.push_64 $rbx",
500                "x64.push_64 $r12",
501                "x64.push_64 $r13",
502                "x64.push_64 $r14",
503                "$rsp = x64.and_ri_64 $rsp, -32",
504                "$rsp = x64.sub_ri_64 $rsp, 64",
505                "$rsp = x64.lea_64 [$rbp - 32]",
506                "$r14 = x64.pop_64",
507                "$r13 = x64.pop_64",
508                "$r12 = x64.pop_64",
509                "$rbx = x64.pop_64",
510                "$rbp = x64.pop_64",
511                "x64.ret",
512            ]
513        );
514    }
515
516    #[test]
517    fn every_block_the_function_returns_from_gets_an_epilogue() {
518        let mut names = Interner::new();
519        let mut func = Func::new(names.intern("f"));
520        let opcode = Opcode::new(names.intern("x64.nop"));
521        let head = func.create_block();
522        let left = func.create_block();
523        let right = func.create_block();
524        func.build(head, opcode).finish();
525        *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
526        func.build(left, opcode).finish();
527        func.build(right, opcode).finish();
528        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4));
529        let base = Layout::new(&SYSV, REGS);
530        let layout = Layout { leaf: false, ..base };
531        let lines = written(&mut func, &allocation, &layout, &mut names);
532
533        // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
534        // because a block with an edge out of it is not a block anything returns from.
535        assert_eq!(
536            lines,
537            [
538                "mfunc @f {",
539                "block0:",
540                "$rsp = x64.sub_ri_64 $rsp, 8",
541                "x64.nop block1, block2",
542                "block1:",
543                "x64.nop",
544                "$rsp = x64.add_ri_64 $rsp, 8",
545                "x64.ret",
546                "block2:",
547                "x64.nop",
548                "$rsp = x64.add_ri_64 $rsp, 8",
549                "x64.ret",
550                "}",
551            ]
552        );
553    }
554
555    #[test]
556    fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
557        let mut names = Interner::new();
558        let mut func = Func::new(names.intern("f"));
559        let opcode = Opcode::new(names.intern("x64.nop"));
560        let block = func.create_block();
561        // An instruction that writes one of the vector registers Windows preserves, which is what
562        // a rule for something that has to use it produces.
563        func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
564        let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4));
565        let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
566
567        // No machine here pushes a vector register, so it is stored into the frame rather than
568        // pushed, and the frame has to be taken before there is anywhere to put it.
569        assert_eq!(
570            added(&lines),
571            [
572                "$rsp = x64.sub_ri_64 $rsp, 24",
573                "x64.movaps_mr $xmm6, [$rsp]",
574                "$xmm6 = x64.movaps_rm [$rsp]",
575                "$rsp = x64.add_ri_64 $rsp, 24",
576                "x64.ret",
577            ]
578        );
579    }
580}