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