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, Patch, Reg};
65use rucc_regalloc::Allocation;
66use rucc_regalloc::assign::Place;
67use rucc_regalloc::rewrite::{At, Edit};
68use rucc_target::{BranchInsts, CallRegs, FrameInsts, Guard, PhysReg, Probe, 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 a prologue that takes its frame a page at a time needs beyond the frame.
91///
92/// What `-fstack-clash-protection` asks for, and the same three kinds of thing [`Protect`] is:
93/// one fact about the platform, one about the machine, and two registers that are neither. See
94/// [`rucc_target::Probe`] for what the sequence is defending against.
95#[derive(Debug, Clone, Copy)]
96pub struct Probing<'a> {
97    /// What touches a page and how far apart the pages are.
98    pub probe: &'a Probe,
99    /// What a branch on a register is, which is what the loop under a large frame ends with.
100    pub branch: &'a BranchInsts,
101    /// The two registers the sequence may use, which are two the allocator never handed out.
102    pub scratch: [PhysReg; 2],
103}
104
105/// What a profiler's hook at the top of a function is, in a function that has one.
106///
107/// What `-pg` asks for. See [`rucc_target::Trace`] for why there are two of these and what each of
108/// them lets the hook see. Only the name survives to here, because by this point the flag has been
109/// read against the target and a prologue that has the name has everything it needs.
110#[derive(Debug, Clone, Copy)]
111pub struct Tracing {
112    /// What is called, which is a routine the runtime provides and not one the program wrote.
113    pub name: &'static str,
114    /// Whether the call goes in front of the prologue rather than once the frame is taken.
115    pub early: bool,
116}
117
118/// The room at the top of a function for something to be written over later, in a function that
119/// was promised any.
120///
121/// What `-fpatchable-function-entry=` asks for. The room is a run of the shortest instruction the
122/// machine has that does nothing, and what makes it worth reserving is that it is never run for
123/// long: a tracer or a live patcher writes a jump or a call over it once the program is up, and
124/// what it needs from the compiler is a known address and a known number of bytes.
125///
126/// Two counts because the room can be on either side of the function's own label. Only the half
127/// after it is written here, since the stream starts at the label and there is nowhere in it to put
128/// the other half; the half in front is carried through so that whatever lays the function down can
129/// lay that many bytes ahead of the symbol.
130#[derive(Debug, Clone, Copy)]
131pub struct Padding {
132    /// What the instruction that does nothing is called on this target.
133    pub name: &'static str,
134    /// How many of them go in front of the function's own label.
135    pub before: u32,
136    /// How many go after it.
137    pub after: u32,
138}
139
140/// What the convention this function is compiled for says a frame is.
141///
142/// Seven answers to the one question, which is why they travel together: where it puts things,
143/// which instructions build one, whether this function's carries a protector, whether it is taken a
144/// page at a time, whether the function opens with a landing pad, whether it calls a profiler on
145/// the way in, and how much room it opens with for a patcher. The last five are the only ones about
146/// this function rather than about every function on the target, and they are here because what
147/// they need is the other two and nothing else.
148#[derive(Debug, Clone, Copy)]
149pub struct Convention<'a> {
150    /// Where the convention puts things.
151    pub regs: &'a CallRegs,
152    /// The instructions a prologue, an epilogue, a spill and a reload are made of on it.
153    pub insts: &'a FrameInsts,
154    /// What this function's stack protector needs, or `None` in a function with none.
155    pub protect: Option<Protect<'a>>,
156    /// What this function's probing prologue needs, or `None` when the frame is taken in one
157    /// subtraction, which is what a command line that did not ask asks for.
158    pub probe: Option<Probing<'a>>,
159    /// What says an indirect branch may arrive at the top of this function, or `None` when the
160    /// command line did not ask for one and on a target that has no such instruction.
161    ///
162    /// See [`rucc_target::FrameInsts::landing`]. A name rather than a flag because the flag has
163    /// already been read against the target by the time this is built, and because a prologue that
164    /// has the name has everything it needs.
165    pub landing: Option<&'static str>,
166    /// What this function's call to a profiler is, or `None` in one that makes none, which is every
167    /// function on a command line that did not ask.
168    pub trace: Option<Tracing>,
169    /// What room this function opens with for a patcher, or `None` in one that was promised none,
170    /// which is every function on a command line that did not ask.
171    pub pad: Option<Padding>,
172}
173
174impl<'a> Convention<'a> {
175    /// That convention, for a function with no stack protector, no probing, no landing pad, no
176    /// call to a profiler and no room for a patcher, which is most of them.
177    #[must_use]
178    pub fn new(regs: &'a CallRegs, insts: &'a FrameInsts) -> Self {
179        Self { regs, insts, protect: None, probe: None, landing: None, trace: None, pad: None }
180    }
181}
182
183/// Writes the moves, the prologue and the epilogue into a function the allocator has finished
184/// with.
185///
186/// # Panics
187///
188/// Panics on a function with no blocks in it, on a frame whose slots or locals the allocation and
189/// the lowering do not match, and on a move of a class the target did not say how to move. All of
190/// them are the caller handing it a frame and a function that were not worked out from each other.
191pub fn finish(
192    func: &mut Func,
193    allocation: &Allocation,
194    frame: &Frame,
195    stack: &Stack,
196    convention: Convention<'_>,
197    names: &mut Interner,
198) {
199    let Convention { regs: conv, insts, protect, probe, landing, trace, pad } = convention;
200    let entry = func.entry().expect("a function with a block in it");
201    let returns: Vec<Block> = func.blocks().filter(|&block| func[block].succs.is_empty()).collect();
202
203    // Before anything is written, because these are instructions the lowering already put in the
204    // function and every one of them is somewhere the prologue is about to go in front of, which
205    // is what makes an offset from the stack pointer the right thing to write into them. In a
206    // frame that grows it is an offset from the frame pointer instead, so the base register is
207    // rewritten the way an incoming argument's is, and for a version of the same reason.
208    //
209    // Added rather than assigned. The instruction named here is the `lea` the lowering wrote, or
210    // whatever [`crate::fold`] folded that `lea` into, and a reader that took it brought a
211    // displacement of its own: the address of a local is where the object starts and reading a
212    // field of it is some way past that. Assigning would throw the field offset away and read the
213    // front of the object every time.
214    for &(inst, local) in &stack.addresses {
215        let at = frame.local(local).expect("a local the frame was worked out from");
216        let mem = func[inst].mem.expect("the address of a local is an address");
217        func[mem].disp += at;
218        if frame.grows() {
219            rebase(func, inst, conv.frame_pointer);
220        }
221    }
222
223    // The bytes a variable length array takes are already off the stack pointer by the time one of
224    // these runs, so what is left to write is how far above the new stack pointer the array starts,
225    // which is however much of the bottom of the frame belongs to the arguments of a call. That
226    // area stays at the bottom wherever the bottom has moved to. Added rather than assigned for the
227    // reason the loop above is: one of these folds into its readers like any other address, and a
228    // reader that took it brought a displacement of its own.
229    for &inst in &stack.dynamic {
230        let mem = func[inst].mem.expect("the address of a growable local is an address");
231        func[mem].disp += offset(frame.below());
232    }
233
234    // The same, one area further up, and through the frame pointer when that is what reaches it.
235    // These are in the entry block ahead of everything, so the prologue still goes in front of
236    // them, which is what makes both registers hold what these offsets are counted from.
237    let incoming = frame.incoming();
238    for &(inst, up) in &stack.arguments {
239        let mem = func[inst].mem.expect("an argument read out of memory is read from an address");
240        func[mem].disp += incoming.at + offset(up);
241        if incoming.through_frame_pointer {
242            rebase(func, inst, conv.frame_pointer);
243        }
244    }
245
246    // Every offset the frame reports is from this one register, which is the stack pointer in an
247    // ordinary frame and the frame pointer in one that moves the stack pointer while it runs.
248    let base = if frame.grows() { conv.frame_pointer } else { conv.stack_pointer };
249    let mut writer = Writer { func, conv, insts, names, base, ahead: None };
250
251    let mut cursors: Vec<(At, Inst)> = Vec::new();
252    for edit in &allocation.edits {
253        let inst = writer.mov(edit, frame);
254        writer.put(&mut cursors, edit.at, inst);
255    }
256
257    let prologue = writer.prologue(frame, protect, probe, landing, trace, pad);
258    for &inst in prologue.iter().rev() {
259        writer.func.prepend_inst(entry, inst);
260    }
261    for block in returns {
262        // The check goes in front of the epilogue and takes the return with it. What is left in
263        // the block the function used to return from is the check, and the block the epilogue then
264        // goes in is the arm the canary was unchanged on.
265        let block = match protect {
266            Some(protect) => writer.check(block, frame, protect),
267            None => block,
268        };
269        let epilogue = writer.epilogue(frame);
270        for inst in epilogue {
271            writer.func.append_inst(block, inst);
272        }
273    }
274
275    // Last of everything, because the blocks a probing prologue made have to come in front of the
276    // block the function used to begin with and the ones the protector's check makes are made
277    // after that. Nothing has been laid out yet: `crate::layout` runs after this and puts every
278    // block in its own order, and all this decides is which block the function is entered at.
279    if let Some(ahead) = writer.ahead {
280        let rest: Vec<Block> =
281            writer.func.blocks().filter(|block| !ahead.contains(block)).collect();
282        let order: Vec<Block> = ahead.into_iter().chain(rest).collect();
283        writer.func.set_block_order(&order);
284    }
285}
286
287/// How many pages a probing prologue touches one after another before it writes a loop instead.
288///
289/// Three, which is what gcc unrolls to. The loop is four instructions however many pages it walks
290/// and a page written out is two, so three is the last size at which the straight line is no
291/// longer than the loop, and the straight line has no branch in it and needs no register.
292const UNROLLED: u32 = 3;
293
294/// One function having its frame written into it.
295/// Points an address the lowering left counted from the stack pointer at another register.
296///
297/// The base register is an operand of the instruction and the addressing mode holds where in the
298/// operand vector it is, so the register is changed there and not in the mode.
299fn rebase(func: &mut Func, inst: Inst, to: PhysReg) {
300    let mem = func[inst].mem.expect("an address");
301    let at = func[mem].base.expect("an address the lowering wrote a base register into");
302    let operands = func[inst].operands;
303    func[operands][usize::from(at)].reg = Reg::physical(to);
304}
305
306struct Writer<'a> {
307    func: &'a mut Func,
308    conv: &'a CallRegs,
309    insts: &'a FrameInsts,
310    names: &'a mut Interner,
311    /// Which register every offset into the frame is counted from, which is the stack pointer
312    /// unless the function moves it while it runs. See `Growing` in [`crate::frame`].
313    base: PhysReg,
314    /// The blocks a probing prologue made, which go in front of the one the function began with.
315    ///
316    /// Empty in every function whose frame is taken in one subtraction, which is every function
317    /// on a command line that did not ask for the stack to be touched a page at a time and most
318    /// of them on one that did. See [`Writer::pages`].
319    ahead: Option<[Block; 2]>,
320}
321
322impl Writer<'_> {
323    /// The instructions the prologue is, in the order they run.
324    ///
325    /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
326    /// before anything else, so that it points at a fixed place whatever else happens. The
327    /// registers are pushed before the alignment is forced, so that the epilogue can find them
328    /// again from the frame pointer, since after the alignment is forced nothing else can. And the
329    /// vector registers are stored last, because until the frame has been taken there is nowhere
330    /// to store them.
331    ///
332    /// The landing pad is in front of all of it, because the address it makes reachable is the
333    /// address of the function and the address of the function is where the first instruction is.
334    /// It has to be written here rather than after the fact, since a probing prologue moves the
335    /// instructions written so far into a block of its own and the pad has to move with them.
336    ///
337    /// The room a patcher was promised goes after the pad, because a patcher wants somewhere it can
338    /// write a call that happens before anything else, and the pad is the one instruction that has
339    /// to come first for a reason of its own.
340    ///
341    /// A profiler's hook goes next, or at the end when it is the kind that reads the frame pointer.
342    /// The early one is in front of everything the frame does for a reason of its own: what makes
343    /// it worth replacing while the program runs is that the stack at that instruction is exactly
344    /// what a call leaves, and a prologue that had already run would have changed it.
345    fn prologue(
346        &mut self,
347        frame: &Frame,
348        protect: Option<Protect<'_>>,
349        probe: Option<Probing<'_>>,
350        landing: Option<&'static str>,
351        trace: Option<Tracing>,
352        pad: Option<Padding>,
353    ) -> Vec<Inst> {
354        let sp = self.conv.stack_pointer;
355        let fp = self.conv.frame_pointer;
356        let int = self.conv.int_class;
357        let sse = self.conv.sse_class;
358        let word = offset(self.conv.word);
359        let mut out = Vec::new();
360        // What the prologue wrote before it had described anything, which is what decides whether
361        // there is a rule to remember at the end of it. Neither of these moves a register or takes
362        // a frame, so a function whose whole prologue is one of them has no rows and must not be
363        // given a pair of them that cancel out.
364        let mut quiet = Vec::new();
365        if let Some(name) = landing {
366            let opcode = self.opcode(name);
367            let inst = self.func.build_loose(opcode).finish();
368            out.push(inst);
369            quiet.push(inst);
370        }
371        // After the pad and in front of everything else, which is where gcc puts it. The pad is the
372        // function's first instruction because the address an indirect branch may arrive at is the
373        // address of the function, and the room comes next because what gets written over it is a
374        // call and the point of that call is that it happens before the function has done anything.
375        //
376        // Nothing is described for any of it. A byte that does nothing does not move the stack
377        // pointer, and what a patcher writes over it later is its own problem rather than this
378        // function's: the rules here say what this function did, and it did nothing.
379        if let Some(pad) = pad {
380            let opcode = self.opcode(pad.name);
381            let mut first = None;
382            for _ in 0..pad.after {
383                let inst = self.func.build_loose(opcode).finish();
384                out.push(inst);
385                quiet.push(inst);
386                first.get_or_insert(inst);
387            }
388            self.func.patch = Some(Patch { before: pad.before, pad: opcode, after: first });
389        }
390        // Nothing is described for it and nothing needs to be: the call pushes a return address and
391        // the hook pops it, so the frame is the same on both sides, and the hook preserves every
392        // register because it is written in assembly for exactly this. That is also why the
393        // allocator, which ran before any of this, never saw the call and did not have to.
394        if let Some(trace) = trace.filter(|trace| trace.early) {
395            let inst = self.hook(trace);
396            out.push(inst);
397            quiet.push(inst);
398        }
399        // How far the stack pointer is below the canonical frame address, and whether the address
400        // is still counted from the stack pointer at all. It starts at the return address the
401        // call itself pushed, which is the rule the CIE already states, so the first row here is
402        // the first thing this function does on top of that.
403        let mut below = offset(self.conv.return_address);
404        let mut from_sp = true;
405        if frame.frame_pointer() {
406            let inst = self.push(fp);
407            out.push(inst);
408            below += word;
409            self.row(inst, CfiOp::DefCfaOffset(below));
410            self.saved(inst, int, fp, -below);
411            let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
412            let inst = self.two(mov, fp, sp);
413            out.push(inst);
414            let number = self.dwarf(int, fp);
415            self.row(inst, CfiOp::DefCfaRegister(number));
416            from_sp = false;
417        }
418        for &reg in frame.saved_int() {
419            let inst = self.push(reg);
420            out.push(inst);
421            below += word;
422            if from_sp {
423                self.row(inst, CfiOp::DefCfaOffset(below));
424            }
425            self.saved(inst, int, reg, -below);
426        }
427        if let Some(to) = frame.realign() {
428            // Nothing is written for this and nothing can be. After it the stack pointer is a
429            // rounded-down version of where it was rather than a fixed distance from it, which is
430            // exactly what a rule cannot say. It is also why a frame that realigns is a frame
431            // with a frame pointer: by here the address is already counted from that instead.
432            assert!(!from_sp, "a frame that forces its own alignment has a frame pointer");
433            let and = self.opcode(self.insts.align);
434            out.push(self.arith(and, -i64::from(to)));
435        }
436        if frame.size() > 0 {
437            self.take(&mut out, frame.size(), &mut below, from_sp, probe);
438        }
439        for save in frame.saved_sse() {
440            let inst = self.store(sse, save.reg, save.at);
441            out.push(inst);
442            // Where it went is an offset from whichever register the frame counts from, and the
443            // address is a constant above that register, so the two make one constant. In an
444            // ordinary frame that register is the stack pointer and the constant is `below`. In one
445            // that grows it is the frame pointer, which the address has been counted from since the
446            // prologue pointed it at where it saved the caller's copy, so the constant is the two
447            // words above it and nothing the prologue did afterwards changes it. A realigned frame
448            // has no such constant at all and the rule is left out rather than guessed; the one
449            // convention that realigns and the one that preserves a vector register are not the
450            // same convention, so nothing reaches any of this today.
451            if frame.realign().is_none() {
452                let above =
453                    if frame.grows() { word + offset(self.conv.return_address) } else { below };
454                self.saved(inst, sse, save.reg, save.at - above);
455            }
456        }
457        // Before the canary and after the frame, which is where gcc puts it. The hook reads the
458        // frame pointer to find out who called this function, so it has to run once there is one,
459        // and it is a call, so it has to run before anything the function is keeping in the frame
460        // could be read back.
461        if let Some(trace) = trace.filter(|trace| !trace.early) {
462            let inst = self.hook(trace);
463            out.push(inst);
464        }
465        // Last of everything, because it writes into the frame and there is no frame to write into
466        // until the stack pointer has moved. Nothing is described for either instruction: they
467        // write a slot rather than save a register, and no unwinder wants to put a canary back.
468        if let Some(protect) = protect {
469            let at = frame.canary().expect("a protected function has a slot for its canary");
470            let [into, _] = protect.scratch;
471            out.push(self.read_guard(into, protect.guard));
472            out.push(self.store(self.conv.int_class, into, at));
473        }
474        // The rules the body runs under, kept so that each epilogue can put them back rather than
475        // leaving the next block reading whatever the last one ended on. See `epilogue`.
476        //
477        // Nothing is kept in a function whose whole prologue is the pieces that describe nothing.
478        // See `quiet` above.
479        if let Some(&last) = out.last() {
480            if !quiet.contains(&last) {
481                self.row(last, CfiOp::RememberState);
482            }
483        }
484        out
485    }
486
487    /// The call to a profiler's hook.
488    ///
489    /// No arguments and no result. Which function is being entered is not passed, because the hook
490    /// reads its own return address to find out, and that is the whole reason the call is written
491    /// rather than something cheaper.
492    fn hook(&mut self, trace: Tracing) -> Inst {
493        let call = self.opcode(self.insts.call);
494        let symbol = self.names.intern(trace.name);
495        self.func.build_loose(call).symbol(symbol).finish()
496    }
497
498    /// Takes the frame, which is one subtraction unless the command line asked for the stack to be
499    /// touched a page at a time.
500    ///
501    /// `below` is how far the canonical frame address is above the stack pointer, and it comes
502    /// back as what it is once the frame has been taken.
503    fn take(
504        &mut self,
505        out: &mut Vec<Inst>,
506        size: u32,
507        below: &mut i32,
508        from_sp: bool,
509        probe: Option<Probing<'_>>,
510    ) {
511        let Some(probing) = probe.filter(|probing| size > probing.probe.interval) else {
512            let inst = self.sub(size);
513            out.push(inst);
514            *below += offset(size);
515            if from_sp {
516                self.row(inst, CfiOp::DefCfaOffset(*below));
517            }
518            return;
519        };
520        // Every step but the last is a whole page and is followed by a touch, and the last is
521        // whatever is left over, which is between one byte and one whole page. So the stack
522        // pointer never moves further than a page without something being written where it landed,
523        // and the unmapped page an operating system leaves below a stack cannot be stepped over.
524        //
525        // That is why the count is worked out from one less than the size. A frame that is an
526        // exact number of pages gets one fewer touch than it has pages, and the step left over is
527        // a whole page, which is a step that lands on the next page boundary rather than past it.
528        // gcc touches that last page as well, so this is one instruction shorter on a frame whose
529        // size is a multiple of the page and the same everywhere else.
530        let interval = probing.probe.interval;
531        let pages = (size - 1) / interval;
532        let rest = size - pages * interval;
533        let mut walked = false;
534        if pages <= UNROLLED {
535            for _ in 0..pages {
536                let inst = self.sub(interval);
537                out.push(inst);
538                *below += offset(interval);
539                if from_sp {
540                    self.row(inst, CfiOp::DefCfaOffset(*below));
541                }
542                let touch = self.touch(probing.probe);
543                out.push(touch);
544            }
545        } else {
546            self.pages(out, pages, below, from_sp, probing);
547            walked = from_sp;
548        }
549        let inst = self.sub(rest);
550        out.push(inst);
551        *below += offset(rest);
552        if from_sp {
553            // A loop leaves the address counted from the register the stack pointer was compared
554            // against, since that is the one thing in it that holds still. This is where it goes
555            // back to being counted from the stack pointer, and it is written behind this
556            // instruction rather than behind the branch because a row is written behind an
557            // instruction and the branch is not one that survives [`crate::layout`].
558            let op = if walked {
559                let number = self.dwarf(self.conv.int_class, self.conv.stack_pointer);
560                CfiOp::DefCfa { reg: number, offset: *below }
561            } else {
562                CfiOp::DefCfaOffset(*below)
563            };
564            self.row(inst, op);
565        }
566    }
567
568    /// The loop that takes a frame too large for the touches to be written one after another.
569    ///
570    /// Three blocks, and the first two are new and go in front of the one the function began with:
571    ///
572    /// ```text
573    ///   what the function is entered at   everything the prologue did before this, and then the
574    ///                                     address the stack pointer is walking down to
575    ///   the loop                          one page, the touch, and the question of whether the
576    ///                                     stack pointer has got there yet
577    ///   what the function began with      the rest of the prologue, and then the body
578    /// ```
579    ///
580    /// The instructions the prologue has written so far move into the first of them, because a
581    /// block is entered at the top and they have to run before the loop does. Nothing is laid out
582    /// here: which block comes first in memory is [`crate::layout`]'s answer, and all this decides
583    /// is which one the function is entered at.
584    fn pages(
585        &mut self,
586        out: &mut Vec<Inst>,
587        pages: u32,
588        below: &mut i32,
589        from_sp: bool,
590        probing: Probing<'_>,
591    ) {
592        let class = self.conv.int_class;
593        let sp = self.conv.stack_pointer;
594        let all = offset(pages * probing.probe.interval);
595        let [limit, byte] = probing.scratch;
596
597        let head = self.func.create_block();
598        for &inst in out.iter() {
599            self.func.append_inst(head, inst);
600        }
601        out.clear();
602        // Where the stack pointer is walking down to, worked out before it starts moving. A loop
603        // that counted down instead would need somewhere to keep the count, and this is somewhere
604        // to keep it that the comparison can read without arithmetic.
605        let lea = self.opcode(self.insts.lea);
606        let inst = self.address(lea, limit, sp, -all);
607        self.func.append_inst(head, inst);
608        if from_sp {
609            // The address is counted from that register for as long as the loop runs, and it has
610            // to be: the stack pointer moves once an iteration, so no fixed distance from it is
611            // true twice, and this register was written so that one distance is.
612            let number = self.dwarf(class, limit);
613            self.row(inst, CfiOp::DefCfa { reg: number, offset: *below + all });
614        }
615
616        let body = self.func.create_block();
617        *self.func.succs_mut(head) = vec![BlockCall::to(body)];
618        let inst = self.sub(probing.probe.interval);
619        self.func.append_inst(body, inst);
620        let touch = self.touch(probing.probe);
621        self.func.append_inst(body, touch);
622        let differ = self.opcode(self.insts.differ);
623        let inst = self
624            .func
625            .build_loose(differ)
626            .def(Reg::physical(byte), class)
627            .uses(Reg::physical(sp), class)
628            .uses(Reg::physical(limit), class)
629            .finish();
630        self.func.append_inst(body, inst);
631        let cond = Opcode::new(
632            self.names.intern(&format!("{}{}", probing.branch.prefix, probing.branch.cond)),
633        );
634        let inst = self.func.build_loose(cond).uses(Reg::physical(byte), class).finish();
635        self.func.append_inst(body, inst);
636        // The first arm is the one taken when the condition held, and the condition is that the
637        // stack pointer and the address it is walking down to still differ, so the first arm is
638        // another page.
639        let began = self.func.entry().expect("a function with a block in it");
640        *self.func.succs_mut(body) = vec![BlockCall::to(body), BlockCall::to(began)];
641        *below += all;
642        self.ahead = Some([head, body]);
643    }
644
645    /// Writes the page the stack pointer is on without changing what is there.
646    fn touch(&mut self, probe: &Probe) -> Inst {
647        let opcode = self.opcode(probe.inst);
648        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
649        self.func.build_loose(opcode).imm(0).mem(Mem::at(base)).finish()
650    }
651
652    /// Takes that many bytes off the stack pointer.
653    fn sub(&mut self, bytes: u32) -> Inst {
654        let sub = self.opcode(self.insts.sub);
655        self.arith(sub, i64::from(bytes))
656    }
657
658    /// The stack protector's check, written at the end of a block the function returns from.
659    ///
660    /// Gives back the block the epilogue goes in, which is a new one: the check has to be the last
661    /// thing the old block does, and what follows it is one of two arms rather than the return.
662    ///
663    /// ```text
664    ///   block that returned      reload the slot, read the word again, compare, branch
665    ///   the arm it changed on    call the function that does not come back, and nothing after
666    ///   the arm it did not       the epilogue, which the caller writes into what this gives back
667    /// ```
668    ///
669    /// The two registers are the ones the allocator was told to hold back, so nothing here has to
670    /// ask what is live: a scratch register holds nothing at the end of a block, because the only
671    /// thing that writes one is a move the rewriter put in and every one of those is read by the
672    /// instruction it was put in front of.
673    fn check(&mut self, block: Block, frame: &Frame, protect: Protect<'_>) -> Block {
674        let class = self.conv.int_class;
675        let at = frame.canary().expect("a protected function has a slot for its canary");
676        let [ours, theirs] = protect.scratch;
677
678        let inst = self.load(class, ours, at);
679        self.func.append_inst(block, inst);
680        let inst = self.read_guard(theirs, protect.guard);
681        self.func.append_inst(block, inst);
682        let differ = self.opcode(self.insts.differ);
683        let inst = self
684            .func
685            .build_loose(differ)
686            .def(Reg::physical(theirs), class)
687            .uses(Reg::physical(ours), class)
688            .uses(Reg::physical(theirs), class)
689            .finish();
690        self.func.append_inst(block, inst);
691
692        let failed = self.func.create_block();
693        let ok = self.func.create_block();
694        let cond = Opcode::new(
695            self.names.intern(&format!("{}{}", protect.branch.prefix, protect.branch.cond)),
696        );
697        let inst = self.func.build_loose(cond).uses(Reg::physical(theirs), class).finish();
698        self.func.append_inst(block, inst);
699        // The first arm is the one taken when the condition held, and the condition is that the
700        // two words differ, so the first arm is the one the canary was overwritten on.
701        *self.func.succs_mut(block) = vec![BlockCall::to(failed), BlockCall::to(ok)];
702
703        let call = self.opcode(self.insts.call);
704        let symbol = self.names.intern(protect.guard.fail);
705        self.func.build(failed, call).symbol(symbol).finish();
706        ok
707    }
708
709    /// Reads the word the canary is a copy of into a register.
710    ///
711    /// The address is a constant and names no register at all, because where the block a thread
712    /// has to itself begins is something only the machine knows and the segment register is what
713    /// holds it.
714    fn read_guard(&mut self, into: PhysReg, guard: &Guard) -> Inst {
715        let class = self.conv.int_class;
716        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
717        self.func
718            .build_loose(load)
719            .def(Reg::physical(into), class)
720            .mem(Mem::in_segment(guard.segment, guard.at))
721            .finish()
722    }
723
724    /// The instructions the epilogue is, in the order they run.
725    ///
726    /// The vector registers are read back while the stack pointer is still where the body left it,
727    /// because that is what their offsets are from. Then the stack pointer goes back to the last
728    /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
729    /// moved and a read of the frame pointer when it did not.
730    fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
731        let sp = self.conv.stack_pointer;
732        let fp = self.conv.frame_pointer;
733        let int = self.conv.int_class;
734        let sse = self.conv.sse_class;
735        let word = self.conv.word;
736        let described = !self.func.cfi.is_empty();
737        let mut out = Vec::new();
738        // Where the body left things, which is where every epilogue starts from.
739        let mut below = offset(self.conv.return_address)
740            + offset(word) * self.pushes(frame)
741            + offset(frame.size());
742        let from_sp = !frame.frame_pointer();
743        for save in frame.saved_sse() {
744            let inst = self.load(sse, save.reg, save.at);
745            out.push(inst);
746            if frame.realign().is_none() {
747                self.restored(inst, sse, save.reg);
748            }
749        }
750        let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
751        if frame.frame_pointer() {
752            // No row for either of these. The address is counted from the frame pointer here and
753            // this is what moves the stack pointer rather than the frame pointer, so the rule that
754            // was true before it is still true after it.
755            if pushed == 0 {
756                let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
757                out.push(self.two(mov, sp, fp));
758            } else {
759                let lea = self.opcode(self.insts.lea);
760                let back = -offset(word * pushed);
761                out.push(self.address(lea, sp, fp, back));
762            }
763        } else if frame.size() > 0 {
764            let add = self.opcode(self.insts.add);
765            let inst = self.arith(add, i64::from(frame.size()));
766            out.push(inst);
767            below -= offset(frame.size());
768            self.row(inst, CfiOp::DefCfaOffset(below));
769        }
770        for &reg in frame.saved_int().iter().rev() {
771            let inst = self.pop(reg);
772            out.push(inst);
773            self.restored(inst, int, reg);
774            below -= offset(word);
775            if from_sp {
776                self.row(inst, CfiOp::DefCfaOffset(below));
777            }
778        }
779        if frame.frame_pointer() {
780            let inst = self.pop(fp);
781            out.push(inst);
782            self.restored(inst, int, fp);
783            // The frame pointer holds the caller's value again, so the address goes back to being
784            // counted from the stack pointer, which by now is at the return address.
785            let number = self.dwarf(int, sp);
786            self.row(inst, CfiOp::DefCfa { reg: number, offset: offset(self.conv.return_address) });
787        }
788        let ret = self.opcode(self.insts.ret);
789        let inst = self.func.build_loose(ret).finish();
790        out.push(inst);
791        // These take effect at the address just past the return, which is where the next block
792        // begins, and the next block is body again. Popping the body's rules and pushing them
793        // straight back leaves the stack one deep however many blocks the function returns from,
794        // which is what makes one remembering in the prologue enough for all of them.
795        if described {
796            self.row(inst, CfiOp::RestoreState);
797            self.row(inst, CfiOp::RememberState);
798        }
799        out
800    }
801
802    /// How many general purpose registers the prologue put on the stack, the frame pointer
803    /// included.
804    fn pushes(&self, frame: &Frame) -> i32 {
805        let saved = i32::try_from(frame.saved_int().len()).expect("a frame");
806        saved + i32::from(frame.frame_pointer())
807    }
808
809    /// One row of the unwind table, taking effect after that instruction.
810    fn row(&mut self, inst: Inst, op: CfiOp) {
811        self.func.cfi.push((inst, op));
812    }
813
814    /// A row saying the caller's copy of that register is that far from the canonical frame
815    /// address, which is below it and so is negative.
816    fn saved(&mut self, inst: Inst, class: RegClass, reg: PhysReg, from_cfa: i32) {
817        let number = self.dwarf(class, reg);
818        self.row(inst, CfiOp::Offset { reg: number, offset: from_cfa });
819    }
820
821    /// A row saying that register holds what the caller left in it again.
822    fn restored(&mut self, inst: Inst, class: RegClass, reg: PhysReg) {
823        let number = self.dwarf(class, reg);
824        self.row(inst, CfiOp::Restore(number));
825    }
826
827    /// What an unwind table calls that register.
828    fn dwarf(&self, class: RegClass, reg: PhysReg) -> u16 {
829        self.conv.dwarf(class, reg).expect("a register a frame saves is one the table can name")
830    }
831
832    /// One edit as the instruction that makes it true.
833    fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
834        let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
835        match (edit.mov.to, edit.mov.from) {
836            (Place::Reg(to), Place::Reg(from)) => {
837                let mov = self.opcode(moves.mov);
838                self.func
839                    .build_loose(mov)
840                    .def(Reg::physical(to), edit.class)
841                    .uses(Reg::physical(from), edit.class)
842                    .finish()
843            }
844            (Place::Reg(to), Place::Slot(slot)) => {
845                let at = self.slot(frame, slot);
846                self.load(edit.class, to, at)
847            }
848            (Place::Slot(slot), Place::Reg(from)) => {
849                let at = self.slot(frame, slot);
850                self.store(edit.class, from, at)
851            }
852            // The allocator expands this into two moves through a register of its own, because a
853            // machine that could do it in one is not a machine any of this is written for.
854            (Place::Slot(_), Place::Slot(_)) => {
855                unreachable!("a move from one stack slot straight into another")
856            }
857        }
858    }
859
860    /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
861    ///
862    /// The edits at one place are in the order they have to be made in, so each one goes behind
863    /// the last, and the first of them is what the place itself means.
864    fn put(&mut self, cursors: &mut Vec<(At, Inst)>, at: At, inst: Inst) {
865        if let Some(cursor) = cursors.iter_mut().find(|(place, _)| *place == at) {
866            self.func.insert_after(cursor.1, inst);
867            cursor.1 = inst;
868            return;
869        }
870        match at {
871            At::Before(before) => self.func.insert_before(before, inst),
872            At::After(after) => self.func.insert_after(after, inst),
873            At::StartOf(block) => self.func.prepend_inst(block, inst),
874            // Behind everything in the block. A block the allocator puts an edge's moves at the
875            // end of is one with a single edge out of it, and an edge like that is not an
876            // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
877            // So the last instruction is an ordinary one, which may still be waiting on moves of
878            // its own that have to be made before the edge's are.
879            At::EndOf(block) => self.func.append_inst(block, inst),
880        }
881        cursors.push((at, inst));
882    }
883
884    /// Where a spill slot is, from the stack pointer in the body of the function.
885    fn slot(&self, frame: &Frame, slot: u32) -> i32 {
886        frame.slot(slot).expect("a slot the frame was worked out from")
887    }
888
889    /// Reads a register out of the frame.
890    fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
891        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
892        let base = Operand::read(Reg::physical(self.base), self.conv.int_class);
893        self.func
894            .build_loose(load)
895            .def(Reg::physical(reg), class)
896            .mem(Mem::at(base).plus(at))
897            .finish()
898    }
899
900    /// Writes a register into the frame.
901    fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
902        let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
903        let base = Operand::read(Reg::physical(self.base), self.conv.int_class);
904        self.func
905            .build_loose(store)
906            .uses(Reg::physical(reg), class)
907            .mem(Mem::at(base).plus(at))
908            .finish()
909    }
910
911    /// Puts a general purpose register on the stack.
912    fn push(&mut self, reg: PhysReg) -> Inst {
913        let push = self.opcode(self.insts.push);
914        self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
915    }
916
917    /// Takes a general purpose register back off the stack.
918    fn pop(&mut self, reg: PhysReg) -> Inst {
919        let pop = self.opcode(self.insts.pop);
920        self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
921    }
922
923    /// One general purpose register written with another.
924    fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
925        let class = self.conv.int_class;
926        self.func
927            .build_loose(opcode)
928            .def(Reg::physical(to), class)
929            .uses(Reg::physical(from), class)
930            .finish()
931    }
932
933    /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
934    fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
935        let class = self.conv.int_class;
936        let sp = Reg::physical(self.conv.stack_pointer);
937        self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
938    }
939
940    /// One register written with an address rather than with what is at it.
941    fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
942        let class = self.conv.int_class;
943        let base = Operand::read(Reg::physical(base), class);
944        self.func
945            .build_loose(opcode)
946            .def(Reg::physical(to), class)
947            .mem(Mem::at(base).plus(disp))
948            .finish()
949    }
950
951    /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
952    /// then the name the target gave.
953    fn opcode(&mut self, name: &str) -> Opcode {
954        Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
955    }
956}
957
958/// A distance in a frame, as the signed number every offset is.
959fn offset(bytes: u32) -> i32 {
960    i32::try_from(bytes).expect("a frame under two gigabytes")
961}
962
963#[cfg(test)]
964mod tests {
965    use rucc_base::Interner;
966    use rucc_mir::{BlockCall, print_func};
967    use rucc_regalloc::assign::Env;
968    use rucc_target::x86_64::{BRANCH, FRAME, GPR, PROBE, R10, R11, REGS, SYSV, WIN64, XMM, xmm};
969
970    use super::*;
971    use crate::frame::{Layout, Local};
972
973    /// An environment offering that many of the convention's registers, with everything after
974    /// them held back as scratch.
975    fn env(conv: &CallRegs, count: usize) -> Env {
976        Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
977    }
978
979    /// A function of that many values, every one written before any is read, allocated with that
980    /// many registers to hand out. The same shape the frame layout's own tests are written
981    /// against, so that a frame here is one that has already been checked there.
982    fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
983        let mut names = Interner::new();
984        let mut func = Func::new(names.intern("f"));
985        let opcode = Opcode::new(names.intern("x64.nop"));
986        let block = func.create_block();
987        let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
988        for &reg in &regs {
989            func.build(block, opcode).def(reg, GPR).finish();
990        }
991        for &reg in &regs {
992            func.build(block, opcode).uses(reg, GPR).finish();
993        }
994        let allocation = rucc_regalloc::run(&mut func, &env(conv, count), "test");
995        (func, allocation, names)
996    }
997
998    /// The function with its frame written into it, as the lines a dump would show.
999    fn written(
1000        func: &mut Func,
1001        allocation: &Allocation,
1002        layout: &Layout<'_>,
1003        names: &mut Interner,
1004    ) -> Vec<String> {
1005        with_protector(func, allocation, layout, None, names)
1006    }
1007
1008    /// The same, for a function the caller has decided is protected or is not.
1009    fn with_protector(
1010        func: &mut Func,
1011        allocation: &Allocation,
1012        layout: &Layout<'_>,
1013        protect: Option<Protect<'_>>,
1014        names: &mut Interner,
1015    ) -> Vec<String> {
1016        let convention = Convention { protect, ..Convention::new(layout.conv, &FRAME) };
1017        under(func, allocation, layout, convention, names)
1018    }
1019
1020    /// The same, for a function whose frame the caller has decided is taken a page at a time.
1021    fn with_probing(
1022        func: &mut Func,
1023        allocation: &Allocation,
1024        layout: &Layout<'_>,
1025        probe: Option<Probing<'_>>,
1026        names: &mut Interner,
1027    ) -> Vec<String> {
1028        let convention = Convention { probe, ..Convention::new(layout.conv, &FRAME) };
1029        under(func, allocation, layout, convention, names)
1030    }
1031
1032    /// The function with its frame written into it under that convention.
1033    fn under(
1034        func: &mut Func,
1035        allocation: &Allocation,
1036        layout: &Layout<'_>,
1037        convention: Convention<'_>,
1038        names: &mut Interner,
1039    ) -> Vec<String> {
1040        let frame = Frame::of(func, allocation, layout);
1041        finish(func, allocation, &frame, &Stack::default(), convention, names);
1042        print_func(func, names, &REGS)
1043            .lines()
1044            .filter(|line| !line.is_empty())
1045            .map(|line| line.trim().to_string())
1046            .collect()
1047    }
1048
1049    /// Just the lines the frame put in, which is every line that is not the function it was
1050    /// given and not the shape of the dump around it.
1051    fn added(lines: &[String]) -> Vec<&str> {
1052        lines
1053            .iter()
1054            .map(String::as_str)
1055            .filter(|line| !line.contains("x64.nop"))
1056            .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
1057            .collect()
1058    }
1059
1060    #[test]
1061    fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
1062        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1063        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1064
1065        // Two values and four registers, so nothing is spilled, nothing is saved and the stack
1066        // pointer never moves. A prologue of nothing is the right prologue for that.
1067        assert_eq!(added(&lines), ["x64.ret"]);
1068    }
1069
1070    #[test]
1071    fn a_spill_is_a_store_and_a_reload_is_a_load() {
1072        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1073        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1074
1075        // Two registers for four values, so two of them go to the stack. The store goes behind the
1076        // instruction that wrote the value and the load in front of the one that wants it, both at
1077        // the offsets the frame gave, which are below the stack pointer because a small leaf
1078        // function is entitled to the red zone.
1079        assert_eq!(
1080            lines,
1081            [
1082                "mfunc @f {",
1083                "block0:",
1084                "$rax = x64.nop",
1085                "$rcx = x64.nop",
1086                "$rdx = x64.nop",
1087                "x64.mov_mr_64 $rdx, [$rsp - 16]",
1088                "$rdx = x64.nop",
1089                "x64.mov_mr_64 $rdx, [$rsp - 8]",
1090                "x64.nop $rax",
1091                "x64.nop $rcx",
1092                "$rdx = x64.mov_rm_64 [$rsp - 16]",
1093                "x64.nop $rdx",
1094                "$rdx = x64.mov_rm_64 [$rsp - 8]",
1095                "x64.nop $rdx",
1096                "x64.ret",
1097                "}",
1098            ]
1099        );
1100    }
1101
1102    #[test]
1103    fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
1104        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1105        let base = Layout::new(&SYSV, REGS);
1106        let layout = Layout { red_zone: false, ..base };
1107        let lines = written(&mut func, &allocation, &layout, &mut names);
1108
1109        // The same function told it may not use the red zone takes sixteen bytes instead, and
1110        // every offset moves above the stack pointer to match.
1111        assert_eq!(
1112            added(&lines),
1113            [
1114                "$rsp = x64.sub_ri_64 $rsp, 16",
1115                "x64.mov_mr_64 $rdx, [$rsp]",
1116                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1117                "$rdx = x64.mov_rm_64 [$rsp]",
1118                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1119                "$rsp = x64.add_ri_64 $rsp, 16",
1120                "x64.ret",
1121            ]
1122        );
1123    }
1124
1125    #[test]
1126    fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
1127        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
1128        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1129
1130        // Four registers a call leaves alone, pushed in the convention's order and popped in the
1131        // other one, which is the only order that gets each of them its own value back.
1132        assert_eq!(
1133            added(&lines),
1134            [
1135                "x64.push_64 $rbx",
1136                "x64.push_64 $r12",
1137                "x64.push_64 $r13",
1138                "x64.push_64 $r14",
1139                "$r14 = x64.pop_64",
1140                "$r13 = x64.pop_64",
1141                "$r12 = x64.pop_64",
1142                "$rbx = x64.pop_64",
1143                "x64.ret",
1144            ]
1145        );
1146    }
1147
1148    #[test]
1149    fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
1150        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1151        let base = Layout::new(&SYSV, REGS);
1152        let layout = Layout { frame_pointer: true, red_zone: false, ..base };
1153        let lines = written(&mut func, &allocation, &layout, &mut names);
1154
1155        // The frame pointer is saved before anything else and points at where it was saved, so the
1156        // epilogue reaches the stack pointer through it rather than by counting the frame back.
1157        assert_eq!(
1158            added(&lines),
1159            [
1160                "x64.push_64 $rbp",
1161                "$rbp = x64.mov_rr_64 $rsp",
1162                "$rsp = x64.sub_ri_64 $rsp, 16",
1163                "x64.mov_mr_64 $rdx, [$rsp]",
1164                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1165                "$rdx = x64.mov_rm_64 [$rsp]",
1166                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1167                "$rsp = x64.mov_rr_64 $rbp",
1168                "$rbp = x64.pop_64",
1169                "x64.ret",
1170            ]
1171        );
1172    }
1173
1174    #[test]
1175    fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
1176        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
1177        let locals = [Local { size: 64, align: 32 }];
1178        let base = Layout::new(&SYSV, REGS);
1179        let layout = Layout { locals: &locals, ..base };
1180        let lines = written(&mut func, &allocation, &layout, &mut names);
1181
1182        // Forcing the alignment throws away how far the stack pointer had moved, so the registers
1183        // are pushed before it happens and the epilogue counts back from the frame pointer to find
1184        // them. The frame pointer is required here whatever the flags said.
1185        assert_eq!(
1186            added(&lines),
1187            [
1188                "x64.push_64 $rbp",
1189                "$rbp = x64.mov_rr_64 $rsp",
1190                "x64.push_64 $rbx",
1191                "x64.push_64 $r12",
1192                "x64.push_64 $r13",
1193                "x64.push_64 $r14",
1194                "$rsp = x64.and_ri_64 $rsp, -32",
1195                "$rsp = x64.sub_ri_64 $rsp, 64",
1196                "$rsp = x64.lea_64 [$rbp - 32]",
1197                "$r14 = x64.pop_64",
1198                "$r13 = x64.pop_64",
1199                "$r12 = x64.pop_64",
1200                "$rbx = x64.pop_64",
1201                "$rbp = x64.pop_64",
1202                "x64.ret",
1203            ]
1204        );
1205    }
1206
1207    #[test]
1208    fn every_block_the_function_returns_from_gets_an_epilogue() {
1209        let mut names = Interner::new();
1210        let mut func = Func::new(names.intern("f"));
1211        let opcode = Opcode::new(names.intern("x64.nop"));
1212        let head = func.create_block();
1213        let left = func.create_block();
1214        let right = func.create_block();
1215        func.build(head, opcode).finish();
1216        *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
1217        func.build(left, opcode).finish();
1218        func.build(right, opcode).finish();
1219        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test");
1220        let base = Layout::new(&SYSV, REGS);
1221        let layout = Layout { leaf: false, ..base };
1222        let lines = written(&mut func, &allocation, &layout, &mut names);
1223
1224        // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
1225        // because a block with an edge out of it is not a block anything returns from.
1226        assert_eq!(
1227            lines,
1228            [
1229                "mfunc @f {",
1230                "block0:",
1231                "$rsp = x64.sub_ri_64 $rsp, 8",
1232                "x64.nop block1, block2",
1233                "block1:",
1234                "x64.nop",
1235                "$rsp = x64.add_ri_64 $rsp, 8",
1236                "x64.ret",
1237                "block2:",
1238                "x64.nop",
1239                "$rsp = x64.add_ri_64 $rsp, 8",
1240                "x64.ret",
1241                "}",
1242            ]
1243        );
1244    }
1245
1246    #[test]
1247    fn a_protected_function_writes_the_canary_last_and_checks_it_before_it_returns() {
1248        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1249        let base = Layout::new(&SYSV, REGS);
1250        let layout = Layout { leaf: false, protect: true, ..base };
1251        let guard = SYSV.guard.as_ref().expect("this convention has somewhere to keep the word");
1252        // The two the real pipeline holds back, which are held back in the environment above too:
1253        // it hands out the first two of the convention's order and keeps everything after them.
1254        let protect = Protect { guard, branch: &BRANCH, scratch: [R10, R11] };
1255        let lines = with_protector(&mut func, &allocation, &layout, Some(protect), &mut names);
1256
1257        // The read of the word and the store into the slot come after the stack pointer has moved,
1258        // because there is no slot to store into until it has. The check is the last thing the
1259        // block that returned does and the epilogue is on the arm the canary was unchanged on, so
1260        // a function whose canary changed never gives its frame back and never returns.
1261        assert_eq!(
1262            added(&lines),
1263            [
1264                "$rsp = x64.sub_ri_64 $rsp, 24",
1265                "$r10 = x64.mov_rm_64 [fs:40]",
1266                "x64.mov_mr_64 $r10, [$rsp + 16]",
1267                "x64.mov_mr_64 $rdx, [$rsp]",
1268                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1269                "$rdx = x64.mov_rm_64 [$rsp]",
1270                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1271                "$r10 = x64.mov_rm_64 [$rsp + 16]",
1272                "$r11 = x64.mov_rm_64 [fs:40]",
1273                "$r11 = x64.cmp_set_ne_64 $r10, $r11",
1274                "x64.br_cond_8 $r11, block1, block2",
1275                "x64.call @__stack_chk_fail",
1276                "$rsp = x64.add_ri_64 $rsp, 24",
1277                "x64.ret",
1278            ]
1279        );
1280    }
1281
1282    #[test]
1283    fn a_frame_that_fits_in_one_page_is_taken_in_one_subtraction_even_when_pages_are_touched() {
1284        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1285        let locals = [Local { size: 4088, align: 16 }];
1286        let base = Layout::new(&SYSV, REGS);
1287        let layout = Layout { leaf: false, locals: &locals, ..base };
1288        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1289        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1290
1291        // A frame of one page cannot step over the page below it, because the far end of it is the
1292        // near end of that page and anything written there is written to a page that is there. So
1293        // the flag costs such a function nothing, which is most functions.
1294        assert_eq!(
1295            added(&lines),
1296            ["$rsp = x64.sub_ri_64 $rsp, 4088", "$rsp = x64.add_ri_64 $rsp, 4088", "x64.ret",]
1297        );
1298    }
1299
1300    #[test]
1301    fn a_probing_prologue_touches_every_page_of_a_frame_a_few_pages_deep() {
1302        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1303        let locals = [Local { size: 9000, align: 16 }];
1304        let base = Layout::new(&SYSV, REGS);
1305        let layout = Layout { leaf: false, locals: &locals, ..base };
1306        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1307        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1308
1309        // A page of the stack pointer's own, then the touch that says the page is there, and only
1310        // then the next one, which is the whole of the defence: nothing here ever moves the stack
1311        // pointer further than one page without writing where it landed. The last subtraction is
1312        // the remainder and is smaller than a page, so it needs no touch of its own, and it exists
1313        // in every frame because the count of pages is taken off one less than the size.
1314        assert_eq!(
1315            added(&lines),
1316            [
1317                "$rsp = x64.sub_ri_64 $rsp, 4096",
1318                "x64.or_mi_8 [$rsp], 0",
1319                "$rsp = x64.sub_ri_64 $rsp, 4096",
1320                "x64.or_mi_8 [$rsp], 0",
1321                "$rsp = x64.sub_ri_64 $rsp, 808",
1322                "$rsp = x64.add_ri_64 $rsp, 9000",
1323                "x64.ret",
1324            ]
1325        );
1326    }
1327
1328    #[test]
1329    fn a_probing_prologue_deeper_than_that_walks_the_pages_in_a_loop() {
1330        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1331        let locals = [Local { size: 100_000, align: 16 }];
1332        let base = Layout::new(&SYSV, REGS);
1333        let layout = Layout { leaf: false, locals: &locals, ..base };
1334        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1335        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1336
1337        // Twenty-four pages, which is more than a straight line is worth, so the prologue works out
1338        // where it is going first and then walks there. The whole listing rather than the added
1339        // lines, because what matters as much as the instructions is that the two blocks the walk
1340        // is made of come in front of the block the function began with: the body the allocator
1341        // filled is block2 here and it was block0 before this ran.
1342        assert_eq!(
1343            lines,
1344            [
1345                "mfunc @f {",
1346                "block0:",
1347                "$r10 = x64.lea_64 [$rsp - 98304], block1",
1348                "block1:",
1349                "$rsp = x64.sub_ri_64 $rsp, 4096",
1350                "x64.or_mi_8 [$rsp], 0",
1351                "$r11 = x64.cmp_set_ne_64 $rsp, $r10",
1352                "x64.br_cond_8 $r11, block1, block2",
1353                "block2:",
1354                "$rsp = x64.sub_ri_64 $rsp, 1704",
1355                "$rax = x64.nop",
1356                "$rcx = x64.nop",
1357                "x64.nop $rax",
1358                "x64.nop $rcx",
1359                "$rsp = x64.add_ri_64 $rsp, 100008",
1360                "x64.ret",
1361                "}",
1362            ]
1363        );
1364    }
1365
1366    #[test]
1367    fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
1368        let mut names = Interner::new();
1369        let mut func = Func::new(names.intern("f"));
1370        let opcode = Opcode::new(names.intern("x64.nop"));
1371        let block = func.create_block();
1372        // An instruction that writes one of the vector registers Windows preserves, which is what
1373        // a rule for something that has to use it produces.
1374        func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
1375        let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4), "test");
1376        let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
1377
1378        // No machine here pushes a vector register, so it is stored into the frame rather than
1379        // pushed, and the frame has to be taken before there is anywhere to put it.
1380        assert_eq!(
1381            added(&lines),
1382            [
1383                "$rsp = x64.sub_ri_64 $rsp, 24",
1384                "x64.movaps_mr $xmm6, [$rsp]",
1385                "$xmm6 = x64.movaps_rm [$rsp]",
1386                "$rsp = x64.add_ri_64 $rsp, 24",
1387                "x64.ret",
1388            ]
1389        );
1390    }
1391}