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