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, BlockCall, 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::{BranchInsts, CallRegs, FrameInsts, Guard, PhysReg, RegClass};
69
70use crate::frame::Frame;
71use crate::lower::Stack;
72
73/// What the stack protector's check needs beyond the frame, in a function that has one.
74///
75/// Three things that come from three places, which is why they arrive together rather than being
76/// looked up here. Where the word the canary is copied from lives is a fact about the runtime the
77/// code is linked against. What a branch on a register is is a fact about the machine. And the two
78/// registers are neither: they are the ones the allocator was told to hold back, which is a
79/// decision about the allocator, and they are free at a return for exactly that reason.
80#[derive(Debug, Clone, Copy)]
81pub struct Protect<'a> {
82    /// Where the word the canary is a copy of lives, and what to call when the copy has changed.
83    pub guard: &'a Guard,
84    /// What a branch on a register is, which is what the check ends its block with.
85    pub branch: &'a BranchInsts,
86    /// The two registers the check may use, which are two the allocator never handed out.
87    pub scratch: [PhysReg; 2],
88}
89
90/// What the convention this function is compiled for says a frame is.
91///
92/// Three answers to the one question, which is why they travel together: where it puts things,
93/// which instructions build one, and whether this function's carries a protector. The last is the
94/// only one that is about this function rather than about every function on the target, and it is
95/// here because what it needs is the other two and nothing else.
96#[derive(Debug, Clone, Copy)]
97pub struct Convention<'a> {
98    /// Where the convention puts things.
99    pub regs: &'a CallRegs,
100    /// The instructions a prologue, an epilogue, a spill and a reload are made of on it.
101    pub insts: &'a FrameInsts,
102    /// What this function's stack protector needs, or `None` in a function with none.
103    pub protect: Option<Protect<'a>>,
104}
105
106impl<'a> Convention<'a> {
107    /// That convention, for a function with no stack protector, which is most of them.
108    #[must_use]
109    pub fn new(regs: &'a CallRegs, insts: &'a FrameInsts) -> Self {
110        Self { regs, insts, protect: None }
111    }
112}
113
114/// Writes the moves, the prologue and the epilogue into a function the allocator has finished
115/// with.
116///
117/// # Panics
118///
119/// Panics on a function with no blocks in it, on a frame whose slots or locals the allocation and
120/// the lowering do not match, and on a move of a class the target did not say how to move. All of
121/// them are the caller handing it a frame and a function that were not worked out from each other.
122pub fn finish(
123    func: &mut Func,
124    allocation: &Allocation,
125    frame: &Frame,
126    stack: &Stack,
127    convention: Convention<'_>,
128    names: &mut Interner,
129) {
130    let Convention { regs: conv, insts, protect } = convention;
131    let entry = func.entry().expect("a function with a block in it");
132    let returns: Vec<Block> = func.blocks().filter(|&block| func[block].succs.is_empty()).collect();
133
134    // Before anything is written, because these are instructions the lowering already put in the
135    // function and every one of them is somewhere the prologue is about to go in front of, which
136    // is what makes an offset from the stack pointer the right thing to write into them.
137    for &(inst, local) in &stack.addresses {
138        let at = frame.local(local).expect("a local the frame was worked out from");
139        let mem = func[inst].mem.expect("the address of a local is an address");
140        func[mem].disp = at;
141    }
142
143    // The same, one area further up, and through the frame pointer when that is what reaches it.
144    // These are in the entry block ahead of everything, so the prologue still goes in front of
145    // them, which is what makes both registers hold what these offsets are counted from.
146    let incoming = frame.incoming();
147    for &(inst, up) in &stack.arguments {
148        let mem = func[inst].mem.expect("an argument read out of memory is read from an address");
149        func[mem].disp = incoming.at + offset(up);
150        if incoming.through_frame_pointer {
151            // The base register is an operand of the instruction and the addressing mode holds
152            // where in the operand vector it is, so the register is changed there and not here.
153            let at = func[mem].base.expect("an address the lowering wrote a base register into");
154            let operands = func[inst].operands;
155            func[operands][usize::from(at)].reg = Reg::physical(conv.frame_pointer);
156        }
157    }
158
159    let mut writer = Writer { func, conv, insts, names };
160
161    let mut cursors: Vec<(At, Inst)> = Vec::new();
162    for edit in &allocation.edits {
163        let inst = writer.mov(edit, frame);
164        writer.put(&mut cursors, edit.at, inst);
165    }
166
167    let prologue = writer.prologue(frame, protect);
168    for &inst in prologue.iter().rev() {
169        writer.func.prepend_inst(entry, inst);
170    }
171    for block in returns {
172        // The check goes in front of the epilogue and takes the return with it. What is left in
173        // the block the function used to return from is the check, and the block the epilogue then
174        // goes in is the arm the canary was unchanged on.
175        let block = match protect {
176            Some(protect) => writer.check(block, frame, protect),
177            None => block,
178        };
179        let epilogue = writer.epilogue(frame);
180        for inst in epilogue {
181            writer.func.append_inst(block, inst);
182        }
183    }
184}
185
186/// One function having its frame written into it.
187struct Writer<'a> {
188    func: &'a mut Func,
189    conv: &'a CallRegs,
190    insts: &'a FrameInsts,
191    names: &'a mut Interner,
192}
193
194impl Writer<'_> {
195    /// The instructions the prologue is, in the order they run.
196    ///
197    /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
198    /// before anything else, so that it points at a fixed place whatever else happens. The
199    /// registers are pushed before the alignment is forced, so that the epilogue can find them
200    /// again from the frame pointer, since after the alignment is forced nothing else can. And the
201    /// vector registers are stored last, because until the frame has been taken there is nowhere
202    /// to store them.
203    fn prologue(&mut self, frame: &Frame, protect: Option<Protect<'_>>) -> Vec<Inst> {
204        let sp = self.conv.stack_pointer;
205        let fp = self.conv.frame_pointer;
206        let int = self.conv.int_class;
207        let sse = self.conv.sse_class;
208        let word = offset(self.conv.word);
209        let mut out = Vec::new();
210        // How far the stack pointer is below the canonical frame address, and whether the address
211        // is still counted from the stack pointer at all. It starts at the return address the
212        // call itself pushed, which is the rule the CIE already states, so the first row here is
213        // the first thing this function does on top of that.
214        let mut below = offset(self.conv.return_address);
215        let mut from_sp = true;
216        if frame.frame_pointer() {
217            let inst = self.push(fp);
218            out.push(inst);
219            below += word;
220            self.row(inst, CfiOp::DefCfaOffset(below));
221            self.saved(inst, int, fp, -below);
222            let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
223            let inst = self.two(mov, fp, sp);
224            out.push(inst);
225            let number = self.dwarf(int, fp);
226            self.row(inst, CfiOp::DefCfaRegister(number));
227            from_sp = false;
228        }
229        for &reg in frame.saved_int() {
230            let inst = self.push(reg);
231            out.push(inst);
232            below += word;
233            if from_sp {
234                self.row(inst, CfiOp::DefCfaOffset(below));
235            }
236            self.saved(inst, int, reg, -below);
237        }
238        if let Some(to) = frame.realign() {
239            // Nothing is written for this and nothing can be. After it the stack pointer is a
240            // rounded-down version of where it was rather than a fixed distance from it, which is
241            // exactly what a rule cannot say. It is also why a frame that realigns is a frame
242            // with a frame pointer: by here the address is already counted from that instead.
243            assert!(!from_sp, "a frame that forces its own alignment has a frame pointer");
244            let and = self.opcode(self.insts.align);
245            out.push(self.arith(and, -i64::from(to)));
246        }
247        if frame.size() > 0 {
248            let sub = self.opcode(self.insts.sub);
249            let inst = self.arith(sub, i64::from(frame.size()));
250            out.push(inst);
251            below += offset(frame.size());
252            if from_sp {
253                self.row(inst, CfiOp::DefCfaOffset(below));
254            }
255        }
256        for save in frame.saved_sse() {
257            let inst = self.store(sse, save.reg, save.at);
258            out.push(inst);
259            // Where it went is an offset from the stack pointer in the body, and the address is
260            // `below` above that, so the two make one constant. Unless the frame realigned, in
261            // which case there is no such constant and the rule is left out rather than guessed;
262            // the one convention that realigns and the one that preserves a vector register are
263            // not the same convention, so nothing reaches this today.
264            if frame.realign().is_none() {
265                self.saved(inst, sse, save.reg, save.at - below);
266            }
267        }
268        // Last of everything, because it writes into the frame and there is no frame to write into
269        // until the stack pointer has moved. Nothing is described for either instruction: they
270        // write a slot rather than save a register, and no unwinder wants to put a canary back.
271        if let Some(protect) = protect {
272            let at = frame.canary().expect("a protected function has a slot for its canary");
273            let [into, _] = protect.scratch;
274            out.push(self.read_guard(into, protect.guard));
275            out.push(self.store(self.conv.int_class, into, at));
276        }
277        // The rules the body runs under, kept so that each epilogue can put them back rather than
278        // leaving the next block reading whatever the last one ended on. See `epilogue`.
279        if let Some(&last) = out.last() {
280            self.row(last, CfiOp::RememberState);
281        }
282        out
283    }
284
285    /// The stack protector's check, written at the end of a block the function returns from.
286    ///
287    /// Gives back the block the epilogue goes in, which is a new one: the check has to be the last
288    /// thing the old block does, and what follows it is one of two arms rather than the return.
289    ///
290    /// ```text
291    ///   block that returned      reload the slot, read the word again, compare, branch
292    ///   the arm it changed on    call the function that does not come back, and nothing after
293    ///   the arm it did not       the epilogue, which the caller writes into what this gives back
294    /// ```
295    ///
296    /// The two registers are the ones the allocator was told to hold back, so nothing here has to
297    /// ask what is live: a scratch register holds nothing at the end of a block, because the only
298    /// thing that writes one is a move the rewriter put in and every one of those is read by the
299    /// instruction it was put in front of.
300    fn check(&mut self, block: Block, frame: &Frame, protect: Protect<'_>) -> Block {
301        let class = self.conv.int_class;
302        let at = frame.canary().expect("a protected function has a slot for its canary");
303        let [ours, theirs] = protect.scratch;
304
305        let inst = self.load(class, ours, at);
306        self.func.append_inst(block, inst);
307        let inst = self.read_guard(theirs, protect.guard);
308        self.func.append_inst(block, inst);
309        let differ = self.opcode(self.insts.differ);
310        let inst = self
311            .func
312            .build_loose(differ)
313            .def(Reg::physical(theirs), class)
314            .uses(Reg::physical(ours), class)
315            .uses(Reg::physical(theirs), class)
316            .finish();
317        self.func.append_inst(block, inst);
318
319        let failed = self.func.create_block();
320        let ok = self.func.create_block();
321        let cond = Opcode::new(
322            self.names.intern(&format!("{}{}", protect.branch.prefix, protect.branch.cond)),
323        );
324        let inst = self.func.build_loose(cond).uses(Reg::physical(theirs), class).finish();
325        self.func.append_inst(block, inst);
326        // The first arm is the one taken when the condition held, and the condition is that the
327        // two words differ, so the first arm is the one the canary was overwritten on.
328        *self.func.succs_mut(block) = vec![BlockCall::to(failed), BlockCall::to(ok)];
329
330        let call = self.opcode(self.insts.call);
331        let symbol = self.names.intern(protect.guard.fail);
332        self.func.build(failed, call).symbol(symbol).finish();
333        ok
334    }
335
336    /// Reads the word the canary is a copy of into a register.
337    ///
338    /// The address is a constant and names no register at all, because where the block a thread
339    /// has to itself begins is something only the machine knows and the segment register is what
340    /// holds it.
341    fn read_guard(&mut self, into: PhysReg, guard: &Guard) -> Inst {
342        let class = self.conv.int_class;
343        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
344        self.func
345            .build_loose(load)
346            .def(Reg::physical(into), class)
347            .mem(Mem::in_segment(guard.segment, guard.at))
348            .finish()
349    }
350
351    /// The instructions the epilogue is, in the order they run.
352    ///
353    /// The vector registers are read back while the stack pointer is still where the body left it,
354    /// because that is what their offsets are from. Then the stack pointer goes back to the last
355    /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
356    /// moved and a read of the frame pointer when it did not.
357    fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
358        let sp = self.conv.stack_pointer;
359        let fp = self.conv.frame_pointer;
360        let int = self.conv.int_class;
361        let sse = self.conv.sse_class;
362        let word = self.conv.word;
363        let described = !self.func.cfi.is_empty();
364        let mut out = Vec::new();
365        // Where the body left things, which is where every epilogue starts from.
366        let mut below = offset(self.conv.return_address)
367            + offset(word) * self.pushes(frame)
368            + offset(frame.size());
369        let from_sp = !frame.frame_pointer();
370        for save in frame.saved_sse() {
371            let inst = self.load(sse, save.reg, save.at);
372            out.push(inst);
373            if frame.realign().is_none() {
374                self.restored(inst, sse, save.reg);
375            }
376        }
377        let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
378        if frame.frame_pointer() {
379            // No row for either of these. The address is counted from the frame pointer here and
380            // this is what moves the stack pointer rather than the frame pointer, so the rule that
381            // was true before it is still true after it.
382            if pushed == 0 {
383                let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
384                out.push(self.two(mov, sp, fp));
385            } else {
386                let lea = self.opcode(self.insts.lea);
387                let back = -offset(word * pushed);
388                out.push(self.address(lea, sp, fp, back));
389            }
390        } else if frame.size() > 0 {
391            let add = self.opcode(self.insts.add);
392            let inst = self.arith(add, i64::from(frame.size()));
393            out.push(inst);
394            below -= offset(frame.size());
395            self.row(inst, CfiOp::DefCfaOffset(below));
396        }
397        for &reg in frame.saved_int().iter().rev() {
398            let inst = self.pop(reg);
399            out.push(inst);
400            self.restored(inst, int, reg);
401            below -= offset(word);
402            if from_sp {
403                self.row(inst, CfiOp::DefCfaOffset(below));
404            }
405        }
406        if frame.frame_pointer() {
407            let inst = self.pop(fp);
408            out.push(inst);
409            self.restored(inst, int, fp);
410            // The frame pointer holds the caller's value again, so the address goes back to being
411            // counted from the stack pointer, which by now is at the return address.
412            let number = self.dwarf(int, sp);
413            self.row(inst, CfiOp::DefCfa { reg: number, offset: offset(self.conv.return_address) });
414        }
415        let ret = self.opcode(self.insts.ret);
416        let inst = self.func.build_loose(ret).finish();
417        out.push(inst);
418        // These take effect at the address just past the return, which is where the next block
419        // begins, and the next block is body again. Popping the body's rules and pushing them
420        // straight back leaves the stack one deep however many blocks the function returns from,
421        // which is what makes one remembering in the prologue enough for all of them.
422        if described {
423            self.row(inst, CfiOp::RestoreState);
424            self.row(inst, CfiOp::RememberState);
425        }
426        out
427    }
428
429    /// How many general purpose registers the prologue put on the stack, the frame pointer
430    /// included.
431    fn pushes(&self, frame: &Frame) -> i32 {
432        let saved = i32::try_from(frame.saved_int().len()).expect("a frame");
433        saved + i32::from(frame.frame_pointer())
434    }
435
436    /// One row of the unwind table, taking effect after that instruction.
437    fn row(&mut self, inst: Inst, op: CfiOp) {
438        self.func.cfi.push((inst, op));
439    }
440
441    /// A row saying the caller's copy of that register is that far from the canonical frame
442    /// address, which is below it and so is negative.
443    fn saved(&mut self, inst: Inst, class: RegClass, reg: PhysReg, from_cfa: i32) {
444        let number = self.dwarf(class, reg);
445        self.row(inst, CfiOp::Offset { reg: number, offset: from_cfa });
446    }
447
448    /// A row saying that register holds what the caller left in it again.
449    fn restored(&mut self, inst: Inst, class: RegClass, reg: PhysReg) {
450        let number = self.dwarf(class, reg);
451        self.row(inst, CfiOp::Restore(number));
452    }
453
454    /// What an unwind table calls that register.
455    fn dwarf(&self, class: RegClass, reg: PhysReg) -> u16 {
456        self.conv.dwarf(class, reg).expect("a register a frame saves is one the table can name")
457    }
458
459    /// One edit as the instruction that makes it true.
460    fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
461        let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
462        match (edit.mov.to, edit.mov.from) {
463            (Place::Reg(to), Place::Reg(from)) => {
464                let mov = self.opcode(moves.mov);
465                self.func
466                    .build_loose(mov)
467                    .def(Reg::physical(to), edit.class)
468                    .uses(Reg::physical(from), edit.class)
469                    .finish()
470            }
471            (Place::Reg(to), Place::Slot(slot)) => {
472                let at = self.slot(frame, slot);
473                self.load(edit.class, to, at)
474            }
475            (Place::Slot(slot), Place::Reg(from)) => {
476                let at = self.slot(frame, slot);
477                self.store(edit.class, from, at)
478            }
479            // The allocator expands this into two moves through a register of its own, because a
480            // machine that could do it in one is not a machine any of this is written for.
481            (Place::Slot(_), Place::Slot(_)) => {
482                unreachable!("a move from one stack slot straight into another")
483            }
484        }
485    }
486
487    /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
488    ///
489    /// The edits at one place are in the order they have to be made in, so each one goes behind
490    /// the last, and the first of them is what the place itself means.
491    fn put(&mut self, cursors: &mut Vec<(At, Inst)>, at: At, inst: Inst) {
492        if let Some(cursor) = cursors.iter_mut().find(|(place, _)| *place == at) {
493            self.func.insert_after(cursor.1, inst);
494            cursor.1 = inst;
495            return;
496        }
497        match at {
498            At::Before(before) => self.func.insert_before(before, inst),
499            At::After(after) => self.func.insert_after(after, inst),
500            At::StartOf(block) => self.func.prepend_inst(block, inst),
501            // Behind everything in the block. A block the allocator puts an edge's moves at the
502            // end of is one with a single edge out of it, and an edge like that is not an
503            // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
504            // So the last instruction is an ordinary one, which may still be waiting on moves of
505            // its own that have to be made before the edge's are.
506            At::EndOf(block) => self.func.append_inst(block, inst),
507        }
508        cursors.push((at, inst));
509    }
510
511    /// Where a spill slot is, from the stack pointer in the body of the function.
512    fn slot(&self, frame: &Frame, slot: u32) -> i32 {
513        frame.slot(slot).expect("a slot the frame was worked out from")
514    }
515
516    /// Reads a register out of the frame.
517    fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
518        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
519        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
520        self.func
521            .build_loose(load)
522            .def(Reg::physical(reg), class)
523            .mem(Mem::at(base).plus(at))
524            .finish()
525    }
526
527    /// Writes a register into the frame.
528    fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
529        let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
530        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
531        self.func
532            .build_loose(store)
533            .uses(Reg::physical(reg), class)
534            .mem(Mem::at(base).plus(at))
535            .finish()
536    }
537
538    /// Puts a general purpose register on the stack.
539    fn push(&mut self, reg: PhysReg) -> Inst {
540        let push = self.opcode(self.insts.push);
541        self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
542    }
543
544    /// Takes a general purpose register back off the stack.
545    fn pop(&mut self, reg: PhysReg) -> Inst {
546        let pop = self.opcode(self.insts.pop);
547        self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
548    }
549
550    /// One general purpose register written with another.
551    fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
552        let class = self.conv.int_class;
553        self.func
554            .build_loose(opcode)
555            .def(Reg::physical(to), class)
556            .uses(Reg::physical(from), class)
557            .finish()
558    }
559
560    /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
561    fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
562        let class = self.conv.int_class;
563        let sp = Reg::physical(self.conv.stack_pointer);
564        self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
565    }
566
567    /// One register written with an address rather than with what is at it.
568    fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
569        let class = self.conv.int_class;
570        let base = Operand::read(Reg::physical(base), class);
571        self.func
572            .build_loose(opcode)
573            .def(Reg::physical(to), class)
574            .mem(Mem::at(base).plus(disp))
575            .finish()
576    }
577
578    /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
579    /// then the name the target gave.
580    fn opcode(&mut self, name: &str) -> Opcode {
581        Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
582    }
583}
584
585/// A distance in a frame, as the signed number every offset is.
586fn offset(bytes: u32) -> i32 {
587    i32::try_from(bytes).expect("a frame under two gigabytes")
588}
589
590#[cfg(test)]
591mod tests {
592    use rucc_base::Interner;
593    use rucc_mir::{BlockCall, print_func};
594    use rucc_regalloc::assign::Env;
595    use rucc_target::x86_64::{BRANCH, FRAME, GPR, R10, R11, REGS, SYSV, WIN64, XMM, xmm};
596
597    use super::*;
598    use crate::frame::{Layout, Local};
599
600    /// An environment offering that many of the convention's registers, with everything after
601    /// them held back as scratch.
602    fn env(conv: &CallRegs, count: usize) -> Env {
603        Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
604    }
605
606    /// A function of that many values, every one written before any is read, allocated with that
607    /// many registers to hand out. The same shape the frame layout's own tests are written
608    /// against, so that a frame here is one that has already been checked there.
609    fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
610        let mut names = Interner::new();
611        let mut func = Func::new(names.intern("f"));
612        let opcode = Opcode::new(names.intern("x64.nop"));
613        let block = func.create_block();
614        let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
615        for &reg in &regs {
616            func.build(block, opcode).def(reg, GPR).finish();
617        }
618        for &reg in &regs {
619            func.build(block, opcode).uses(reg, GPR).finish();
620        }
621        let allocation = rucc_regalloc::run(&mut func, &env(conv, count), "test");
622        (func, allocation, names)
623    }
624
625    /// The function with its frame written into it, as the lines a dump would show.
626    fn written(
627        func: &mut Func,
628        allocation: &Allocation,
629        layout: &Layout<'_>,
630        names: &mut Interner,
631    ) -> Vec<String> {
632        with_protector(func, allocation, layout, None, names)
633    }
634
635    /// The same, for a function the caller has decided is protected or is not.
636    fn with_protector(
637        func: &mut Func,
638        allocation: &Allocation,
639        layout: &Layout<'_>,
640        protect: Option<Protect<'_>>,
641        names: &mut Interner,
642    ) -> Vec<String> {
643        let frame = Frame::of(func, allocation, layout);
644        let convention = Convention { protect, ..Convention::new(layout.conv, &FRAME) };
645        finish(func, allocation, &frame, &Stack::default(), convention, names);
646        print_func(func, names, &REGS)
647            .lines()
648            .filter(|line| !line.is_empty())
649            .map(|line| line.trim().to_string())
650            .collect()
651    }
652
653    /// Just the lines the frame put in, which is every line that is not the function it was
654    /// given and not the shape of the dump around it.
655    fn added(lines: &[String]) -> Vec<&str> {
656        lines
657            .iter()
658            .map(String::as_str)
659            .filter(|line| !line.contains("x64.nop"))
660            .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
661            .collect()
662    }
663
664    #[test]
665    fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
666        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
667        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
668
669        // Two values and four registers, so nothing is spilled, nothing is saved and the stack
670        // pointer never moves. A prologue of nothing is the right prologue for that.
671        assert_eq!(added(&lines), ["x64.ret"]);
672    }
673
674    #[test]
675    fn a_spill_is_a_store_and_a_reload_is_a_load() {
676        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
677        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
678
679        // Two registers for four values, so two of them go to the stack. The store goes behind the
680        // instruction that wrote the value and the load in front of the one that wants it, both at
681        // the offsets the frame gave, which are below the stack pointer because a small leaf
682        // function is entitled to the red zone.
683        assert_eq!(
684            lines,
685            [
686                "mfunc @f {",
687                "block0:",
688                "$rax = x64.nop",
689                "$rcx = x64.nop",
690                "$rdx = x64.nop",
691                "x64.mov_mr_64 $rdx, [$rsp - 16]",
692                "$rdx = x64.nop",
693                "x64.mov_mr_64 $rdx, [$rsp - 8]",
694                "x64.nop $rax",
695                "x64.nop $rcx",
696                "$rdx = x64.mov_rm_64 [$rsp - 16]",
697                "x64.nop $rdx",
698                "$rdx = x64.mov_rm_64 [$rsp - 8]",
699                "x64.nop $rdx",
700                "x64.ret",
701                "}",
702            ]
703        );
704    }
705
706    #[test]
707    fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
708        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
709        let base = Layout::new(&SYSV, REGS);
710        let layout = Layout { red_zone: false, ..base };
711        let lines = written(&mut func, &allocation, &layout, &mut names);
712
713        // The same function told it may not use the red zone takes sixteen bytes instead, and
714        // every offset moves above the stack pointer to match.
715        assert_eq!(
716            added(&lines),
717            [
718                "$rsp = x64.sub_ri_64 $rsp, 16",
719                "x64.mov_mr_64 $rdx, [$rsp]",
720                "x64.mov_mr_64 $rdx, [$rsp + 8]",
721                "$rdx = x64.mov_rm_64 [$rsp]",
722                "$rdx = x64.mov_rm_64 [$rsp + 8]",
723                "$rsp = x64.add_ri_64 $rsp, 16",
724                "x64.ret",
725            ]
726        );
727    }
728
729    #[test]
730    fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
731        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
732        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
733
734        // Four registers a call leaves alone, pushed in the convention's order and popped in the
735        // other one, which is the only order that gets each of them its own value back.
736        assert_eq!(
737            added(&lines),
738            [
739                "x64.push_64 $rbx",
740                "x64.push_64 $r12",
741                "x64.push_64 $r13",
742                "x64.push_64 $r14",
743                "$r14 = x64.pop_64",
744                "$r13 = x64.pop_64",
745                "$r12 = x64.pop_64",
746                "$rbx = x64.pop_64",
747                "x64.ret",
748            ]
749        );
750    }
751
752    #[test]
753    fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
754        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
755        let base = Layout::new(&SYSV, REGS);
756        let layout = Layout { frame_pointer: true, red_zone: false, ..base };
757        let lines = written(&mut func, &allocation, &layout, &mut names);
758
759        // The frame pointer is saved before anything else and points at where it was saved, so the
760        // epilogue reaches the stack pointer through it rather than by counting the frame back.
761        assert_eq!(
762            added(&lines),
763            [
764                "x64.push_64 $rbp",
765                "$rbp = x64.mov_rr_64 $rsp",
766                "$rsp = x64.sub_ri_64 $rsp, 16",
767                "x64.mov_mr_64 $rdx, [$rsp]",
768                "x64.mov_mr_64 $rdx, [$rsp + 8]",
769                "$rdx = x64.mov_rm_64 [$rsp]",
770                "$rdx = x64.mov_rm_64 [$rsp + 8]",
771                "$rsp = x64.mov_rr_64 $rbp",
772                "$rbp = x64.pop_64",
773                "x64.ret",
774            ]
775        );
776    }
777
778    #[test]
779    fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
780        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
781        let locals = [Local { size: 64, align: 32 }];
782        let base = Layout::new(&SYSV, REGS);
783        let layout = Layout { locals: &locals, ..base };
784        let lines = written(&mut func, &allocation, &layout, &mut names);
785
786        // Forcing the alignment throws away how far the stack pointer had moved, so the registers
787        // are pushed before it happens and the epilogue counts back from the frame pointer to find
788        // them. The frame pointer is required here whatever the flags said.
789        assert_eq!(
790            added(&lines),
791            [
792                "x64.push_64 $rbp",
793                "$rbp = x64.mov_rr_64 $rsp",
794                "x64.push_64 $rbx",
795                "x64.push_64 $r12",
796                "x64.push_64 $r13",
797                "x64.push_64 $r14",
798                "$rsp = x64.and_ri_64 $rsp, -32",
799                "$rsp = x64.sub_ri_64 $rsp, 64",
800                "$rsp = x64.lea_64 [$rbp - 32]",
801                "$r14 = x64.pop_64",
802                "$r13 = x64.pop_64",
803                "$r12 = x64.pop_64",
804                "$rbx = x64.pop_64",
805                "$rbp = x64.pop_64",
806                "x64.ret",
807            ]
808        );
809    }
810
811    #[test]
812    fn every_block_the_function_returns_from_gets_an_epilogue() {
813        let mut names = Interner::new();
814        let mut func = Func::new(names.intern("f"));
815        let opcode = Opcode::new(names.intern("x64.nop"));
816        let head = func.create_block();
817        let left = func.create_block();
818        let right = func.create_block();
819        func.build(head, opcode).finish();
820        *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
821        func.build(left, opcode).finish();
822        func.build(right, opcode).finish();
823        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test");
824        let base = Layout::new(&SYSV, REGS);
825        let layout = Layout { leaf: false, ..base };
826        let lines = written(&mut func, &allocation, &layout, &mut names);
827
828        // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
829        // because a block with an edge out of it is not a block anything returns from.
830        assert_eq!(
831            lines,
832            [
833                "mfunc @f {",
834                "block0:",
835                "$rsp = x64.sub_ri_64 $rsp, 8",
836                "x64.nop block1, block2",
837                "block1:",
838                "x64.nop",
839                "$rsp = x64.add_ri_64 $rsp, 8",
840                "x64.ret",
841                "block2:",
842                "x64.nop",
843                "$rsp = x64.add_ri_64 $rsp, 8",
844                "x64.ret",
845                "}",
846            ]
847        );
848    }
849
850    #[test]
851    fn a_protected_function_writes_the_canary_last_and_checks_it_before_it_returns() {
852        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
853        let base = Layout::new(&SYSV, REGS);
854        let layout = Layout { leaf: false, protect: true, ..base };
855        let guard = SYSV.guard.as_ref().expect("this convention has somewhere to keep the word");
856        // The two the real pipeline holds back, which are held back in the environment above too:
857        // it hands out the first two of the convention's order and keeps everything after them.
858        let protect = Protect { guard, branch: &BRANCH, scratch: [R10, R11] };
859        let lines = with_protector(&mut func, &allocation, &layout, Some(protect), &mut names);
860
861        // The read of the word and the store into the slot come after the stack pointer has moved,
862        // because there is no slot to store into until it has. The check is the last thing the
863        // block that returned does and the epilogue is on the arm the canary was unchanged on, so
864        // a function whose canary changed never gives its frame back and never returns.
865        assert_eq!(
866            added(&lines),
867            [
868                "$rsp = x64.sub_ri_64 $rsp, 24",
869                "$r10 = x64.mov_rm_64 [fs:40]",
870                "x64.mov_mr_64 $r10, [$rsp + 16]",
871                "x64.mov_mr_64 $rdx, [$rsp]",
872                "x64.mov_mr_64 $rdx, [$rsp + 8]",
873                "$rdx = x64.mov_rm_64 [$rsp]",
874                "$rdx = x64.mov_rm_64 [$rsp + 8]",
875                "$r10 = x64.mov_rm_64 [$rsp + 16]",
876                "$r11 = x64.mov_rm_64 [fs:40]",
877                "$r11 = x64.cmp_set_ne_64 $r10, $r11",
878                "x64.br_cond_8 $r11, block1, block2",
879                "x64.call @__stack_chk_fail",
880                "$rsp = x64.add_ri_64 $rsp, 24",
881                "x64.ret",
882            ]
883        );
884    }
885
886    #[test]
887    fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
888        let mut names = Interner::new();
889        let mut func = Func::new(names.intern("f"));
890        let opcode = Opcode::new(names.intern("x64.nop"));
891        let block = func.create_block();
892        // An instruction that writes one of the vector registers Windows preserves, which is what
893        // a rule for something that has to use it produces.
894        func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
895        let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4), "test");
896        let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
897
898        // No machine here pushes a vector register, so it is stored into the frame rather than
899        // pushed, and the frame has to be taken before there is anywhere to put it.
900        assert_eq!(
901            added(&lines),
902            [
903                "$rsp = x64.sub_ri_64 $rsp, 24",
904                "x64.movaps_mr $xmm6, [$rsp]",
905                "$xmm6 = x64.movaps_rm [$rsp]",
906                "$rsp = x64.add_ri_64 $rsp, 24",
907                "x64.ret",
908            ]
909        );
910    }
911}