Skip to main content

rucc_codegen/
finish.rs

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