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//! There is a fifth thing on a command line that asked for the stack to be touched a page at a
25//! time, and it is the only one of them that is written into the middle of a block rather than at
26//! one end of the function. A variable length array moves the stack pointer by a number that is not
27//! known until the declaration runs, so walking it a page at a time is a loop written around the one
28//! instruction the lowering left, and that turns the block the declaration was in into four.
29//!
30//! The loads that read the arguments the caller passed on the stack are waiting on the same number
31//! and on one more. Those bytes are the caller's rather than this function's, and a frame that had
32//! to force its own alignment cannot say how far away the caller's stack pointer was, so it reaches
33//! back through the frame pointer instead. Which register a load reads through is therefore settled
34//! here too, and it is the only base register in a finished function that was not settled by
35//! whoever wrote the instruction.
36//!
37//! After this the function is one an encoder can read: every register is physical, every offset
38//! into the frame is a constant, and the stack pointer is where the convention says it should be
39//! at every instruction that could look.
40//!
41//! # Why the moves go in first
42//!
43//! Every offset the frame reports is from the stack pointer as it stands in the body of the
44//! function. A spill written before the prologue exists would be written in front of the
45//! instruction it belongs to and behind nothing, which is where the prologue then goes, so the
46//! prologue ends up in front of it and the offsets stay true. Writing them the other way round
47//! would put the first reload above the instruction that takes the frame, and it would read from
48//! an address that is one frame out.
49//!
50//! # Where a return is
51//!
52//! A block that goes nowhere is a block the function leaves from. Mostly that is a return, and
53//! the other kind is a block ending in `unreachable`, which is a point the front end says control
54//! does not arrive at and which the lowering writes no instruction for. Both want the same thing
55//! here. A return wants the epilogue because that is what a return is once the frame is known,
56//! and an unreachable block wants it because the alternative is a function whose last instruction
57//! falls into whatever the assembler put after it, which is worse than an epilogue nothing runs.
58//! So the epilogue goes at the end of every block with an empty successor list, and there may be
59//! several, because nothing here insists a function has one exit.
60//!
61//! # What is target-specific here
62//!
63//! The names, and only the names. Which instruction pushes a register and which one moves the
64//! stack pointer is [`rucc_target::FrameInsts`], which the target says and this reads, so what
65//! is written below is the shape of a prologue rather than any particular machine's. That is
66//! `spec/10-backend.md` section 10.8 as it applies to the one pass that would otherwise be full
67//! of `x64.` by hand.
68
69use std::collections::HashMap;
70
71use rucc_base::Interner;
72use rucc_diag::Span;
73use rucc_mir::{Block, BlockCall, CfiOp, Func, Inst, Mem, Opcode, Operand, Patch, Reg, Role};
74use rucc_regalloc::Allocation;
75use rucc_regalloc::assign::Place;
76use rucc_regalloc::rewrite::{At, Edit};
77use rucc_target::{BranchInsts, CallRegs, Chkstk, FrameInsts, Guard, PhysReg, Probe, RegClass};
78
79use crate::frame::Frame;
80use crate::lower::Stack;
81
82/// What the stack protector's check needs beyond the frame, in a function that has one.
83///
84/// Three things that come from three places, which is why they arrive together rather than being
85/// looked up here. Where the word the canary is copied from lives is a fact about the runtime the
86/// code is linked against. What a branch on a register is is a fact about the machine. And the two
87/// registers are neither: they are the ones the allocator was told to hold back, which is a
88/// decision about the allocator, and they are free at a return for exactly that reason.
89#[derive(Debug, Clone, Copy)]
90pub struct Protect<'a> {
91    /// Where the word the canary is a copy of lives, and what to call when the copy has changed.
92    pub guard: &'a Guard,
93    /// What a branch on a register is, which is what the check ends its block with.
94    pub branch: &'a BranchInsts,
95    /// The two registers the check may use, which are two the allocator never handed out.
96    pub scratch: [PhysReg; 2],
97}
98
99/// What a function that takes its stack a page at a time needs beyond the frame.
100///
101/// What `-fstack-clash-protection` asks for, and the same three kinds of thing [`Protect`] is:
102/// one fact about the platform, one about the machine, and two registers that are neither. See
103/// [`rucc_target::Probe`] for what the sequence is defending against.
104///
105/// Read in two places, because a function has two ways of moving its stack pointer and the flag is
106/// about both of them. The prologue takes the frame the layout worked out, and a variable length
107/// array takes however many bytes its declaration asked for while the function runs. The same three
108/// things answer both.
109#[derive(Debug, Clone, Copy)]
110pub struct Probing<'a> {
111    /// What touches a page and how far apart the pages are.
112    pub probe: &'a Probe,
113    /// What a branch on a register is, which is what the loop under a large frame ends with.
114    pub branch: &'a BranchInsts,
115    /// The two registers the sequence may use, which are two the allocator never handed out.
116    pub scratch: [PhysReg; 2],
117}
118
119/// What a profiler's hook at the top of a function is, in a function that has one.
120///
121/// What `-pg` asks for. See [`rucc_target::Trace`] for why there are two of these and what each of
122/// them lets the hook see. Only the name survives to here, because by this point the flag has been
123/// read against the target and a prologue that has the name has everything it needs.
124#[derive(Debug, Clone, Copy)]
125pub struct Tracing {
126    /// What is called, which is a routine the runtime provides and not one the program wrote.
127    pub name: &'static str,
128    /// Whether the call goes in front of the prologue rather than once the frame is taken.
129    pub early: bool,
130}
131
132/// The room at the top of a function for something to be written over later, in a function that
133/// was promised any.
134///
135/// What `-fpatchable-function-entry=` asks for. The room is a run of the shortest instruction the
136/// machine has that does nothing, and what makes it worth reserving is that it is never run for
137/// long: a tracer or a live patcher writes a jump or a call over it once the program is up, and
138/// what it needs from the compiler is a known address and a known number of bytes.
139///
140/// Two counts because the room can be on either side of the function's own label. Only the half
141/// after it is written here, since the stream starts at the label and there is nowhere in it to put
142/// the other half; the half in front is carried through so that whatever lays the function down can
143/// lay that many bytes ahead of the symbol.
144#[derive(Debug, Clone, Copy)]
145pub struct Padding {
146    /// What the instruction that does nothing is called on this target.
147    pub name: &'static str,
148    /// How many of them go in front of the function's own label.
149    pub before: u32,
150    /// How many go after it.
151    pub after: u32,
152}
153
154/// What the convention this function is compiled for says a frame is.
155///
156/// Seven answers to the one question, which is why they travel together: where it puts things,
157/// which instructions build one, whether this function's carries a protector, whether it is taken a
158/// page at a time, whether the function opens with a landing pad, whether it calls a profiler on
159/// the way in, and how much room it opens with for a patcher. The last five are the only ones about
160/// this function rather than about every function on the target, and they are here because what
161/// they need is the other two and nothing else.
162#[derive(Debug, Clone, Copy)]
163pub struct Convention<'a> {
164    /// Where the convention puts things.
165    pub regs: &'a CallRegs,
166    /// The instructions a prologue, an epilogue, a spill and a reload are made of on it.
167    pub insts: &'a FrameInsts,
168    /// What this function's stack protector needs, or `None` in a function with none.
169    pub protect: Option<Protect<'a>>,
170    /// What this function's probing prologue needs, or `None` when the frame is taken in one
171    /// subtraction, which is what a command line that did not ask asks for.
172    pub probe: Option<Probing<'a>>,
173    /// What says an indirect branch may arrive at the top of this function, or `None` when the
174    /// command line did not ask for one and on a target that has no such instruction.
175    ///
176    /// See [`rucc_target::FrameInsts::landing`]. A name rather than a flag because the flag has
177    /// already been read against the target by the time this is built, and because a prologue that
178    /// has the name has everything it needs.
179    pub landing: Option<&'static str>,
180    /// What this function's call to a profiler is, or `None` in one that makes none, which is every
181    /// function on a command line that did not ask.
182    pub trace: Option<Tracing>,
183    /// What room this function opens with for a patcher, or `None` in one that was promised none,
184    /// which is every function on a command line that did not ask.
185    pub pad: Option<Padding>,
186}
187
188impl<'a> Convention<'a> {
189    /// That convention, for a function with no stack protector, no probing, no landing pad, no
190    /// call to a profiler and no room for a patcher, which is most of them.
191    #[must_use]
192    pub fn new(regs: &'a CallRegs, insts: &'a FrameInsts) -> Self {
193        Self { regs, insts, protect: None, probe: None, landing: None, trace: None, pad: None }
194    }
195}
196
197/// Which instruction each of the allocator's moves became.
198///
199/// A spill and a copy are both a `mov` once they are written, and so is an instruction the lowering
200/// wrote that happens to move the same register to the same address. Telling them apart afterwards
201/// by looking at them is guesswork, and a pass that guesses wrong about a store to a volatile
202/// variable deletes a read the program insisted on. So what the allocator asked for is recorded as
203/// it is written, and a later pass that is only allowed to touch the allocator's own moves has the
204/// list rather than a heuristic. See [`crate::copies`], which is the one pass that reads this.
205#[derive(Debug, Default)]
206pub struct Moves(HashMap<Inst, Edit>);
207
208impl Moves {
209    /// What the allocator asked for at this instruction, or `None` at an instruction that is not
210    /// one of its moves.
211    #[must_use]
212    pub fn at(&self, inst: Inst) -> Option<Edit> {
213        self.0.get(&inst).copied()
214    }
215
216    /// Records that this instruction is what that move came to.
217    pub fn record(&mut self, inst: Inst, edit: Edit) {
218        self.0.insert(inst, edit);
219    }
220}
221
222/// Writes the moves, the prologue and the epilogue into a function the allocator has finished
223/// with.
224///
225/// Hands back which instruction each of the allocator's moves became, for the one pass that is
226/// allowed to take one of them out again.
227///
228/// # Panics
229///
230/// Panics on a function with no blocks in it, on a frame whose slots or locals the allocation and
231/// the lowering do not match, and on a move of a class the target did not say how to move. All of
232/// them are the caller handing it a frame and a function that were not worked out from each other.
233pub fn finish(
234    func: &mut Func,
235    allocation: &Allocation,
236    frame: &Frame,
237    stack: &Stack,
238    convention: Convention<'_>,
239    names: &mut Interner,
240) -> Moves {
241    let Convention { regs: conv, insts, protect, probe, landing, trace, pad } = convention;
242    let entry = func.entry().expect("a function with a block in it");
243
244    // Before anything is written, because these are instructions the lowering already put in the
245    // function and every one of them is somewhere the prologue is about to go in front of, which
246    // is what makes an offset from the stack pointer the right thing to write into them. In a
247    // frame that grows it is an offset from the frame pointer instead, so the base register is
248    // rewritten the way an incoming argument's is, and for a version of the same reason.
249    //
250    // Added rather than assigned. The instruction named here is the `lea` the lowering wrote, or
251    // whatever [`crate::fold`] folded that `lea` into, and a reader that took it brought a
252    // displacement of its own: the address of a local is where the object starts and reading a
253    // field of it is some way past that. Assigning would throw the field offset away and read the
254    // front of the object every time.
255    for &(inst, local) in &stack.addresses {
256        let at = frame.local(local).expect("a local the frame was worked out from");
257        let mem = func[inst].mem.expect("the address of a local is an address");
258        func[mem].disp += at;
259        if frame.grows() {
260            rebase(func, inst, conv.frame_pointer);
261        }
262    }
263
264    // The bytes a variable length array takes are already off the stack pointer by the time one of
265    // these runs, so what is left to write is how far above the new stack pointer the array starts,
266    // which is however much of the bottom of the frame belongs to the arguments of a call. That
267    // area stays at the bottom wherever the bottom has moved to. Added rather than assigned for the
268    // reason the loop above is: one of these folds into its readers like any other address, and a
269    // reader that took it brought a displacement of its own.
270    for &inst in &stack.dynamic {
271        let mem = func[inst].mem.expect("the address of a growable local is an address");
272        func[mem].disp += offset(frame.below());
273    }
274
275    // The same, one area further up, and through the frame pointer when that is what reaches it.
276    // These are in the entry block ahead of everything, so the prologue still goes in front of
277    // them, which is what makes both registers hold what these offsets are counted from.
278    let incoming = frame.incoming();
279    for &(inst, up) in &stack.arguments {
280        let mem = func[inst].mem.expect("an argument read out of memory is read from an address");
281        func[mem].disp += incoming.at + offset(up);
282        if incoming.through_frame_pointer {
283            rebase(func, inst, conv.frame_pointer);
284        }
285    }
286
287    // Every offset the frame reports is from this one register, which is the stack pointer in an
288    // ordinary frame and the frame pointer in one that moves the stack pointer while it runs.
289    let base = if frame.grows() { conv.frame_pointer } else { conv.stack_pointer };
290    let mut writer = Writer { func, conv, insts, names, base, ahead: None };
291
292    let mut cursors: HashMap<At, Inst> = HashMap::new();
293    let mut moves = Moves::default();
294    for edit in &allocation.edits {
295        let inst = writer.mov(edit, frame);
296        writer.put(&mut cursors, edit.at, inst);
297        moves.record(inst, *edit);
298    }
299
300    // Before the epilogues, because this is what turns one block into four and the last of the four
301    // is the one the function goes on to return from. A block that went nowhere before a variable
302    // length array was walked in the middle of it is not the block that goes nowhere afterwards, and
303    // an epilogue written into the wrong one of them gives the frame back before the body has run.
304    if let Some(probing) = probe {
305        for &took in &stack.grown {
306            writer.walk(took, probing);
307        }
308    }
309
310    // Almost nothing of either in a naked function, which is the whole of what the attribute asks
311    // for and the only place in this file that knows the word. The frame is empty by the time one
312    // gets here, since [`crate::pipeline`] refuses a naked function that wanted bytes, so what is
313    // left to leave out is the part of the prologue that is written on somebody's instruction
314    // rather than on the frame's: the room a patcher was promised and the profiler's hook, both of
315    // which are a call or a run of bytes in front of a body that said it is the whole of the
316    // function. The protector and the probe are already off, the first because the pipeline turned
317    // it off and the second because it only fires on a frame there is none of.
318    //
319    // The landing pad stays. It is not the compiler adding something to the function, it is the
320    // address of the function being made one an indirect branch may arrive at, and gcc writes it
321    // in a naked function too.
322    //
323    // The epilogue is the one that matters. It is what writes the `ret`, and a naked function ends
324    // where its own text ends, which for micropython's `nlr_push` is a jump somewhere else and for
325    // everything else is the `ud2` the lowering put there.
326    let bare = frame.naked();
327    let prologue = writer.prologue(
328        frame,
329        protect,
330        probe.filter(|_| !bare),
331        landing,
332        trace.filter(|_| !bare),
333        pad.filter(|_| !bare),
334    );
335    for &inst in prologue.iter().rev() {
336        writer.func.prepend_inst(entry, inst);
337    }
338    let returns: Vec<Block> = if bare {
339        Vec::new()
340    } else {
341        writer.func.blocks().filter(|&block| writer.func[block].succs.is_empty()).collect()
342    };
343    for block in returns {
344        // The check goes in front of the epilogue and takes the return with it. What is left in
345        // the block the function used to return from is the check, and the block the epilogue then
346        // goes in is the arm the canary was unchanged on.
347        let block = match protect {
348            Some(protect) => writer.check(block, frame, protect),
349            None => block,
350        };
351        let epilogue = writer.epilogue(frame);
352        for inst in epilogue {
353            writer.func.append_inst(block, inst);
354        }
355    }
356
357    // Last of everything, because the blocks a probing prologue made have to come in front of the
358    // block the function used to begin with and the ones the protector's check makes are made
359    // after that. Nothing has been laid out yet: `crate::layout` runs after this and puts every
360    // block in its own order, and all this decides is which block the function is entered at.
361    if let Some(ahead) = writer.ahead {
362        let rest: Vec<Block> =
363            writer.func.blocks().filter(|block| !ahead.contains(block)).collect();
364        let order: Vec<Block> = ahead.into_iter().chain(rest).collect();
365        writer.func.set_block_order(&order);
366    }
367    moves
368}
369
370/// Points every access to the frame that its instruction cannot carry the offset of at a scratch
371/// register holding most of the address.
372///
373/// Nothing to do on x86, where every offset a frame has fits in the instruction. An AArch64 load
374/// reaches a few kilobytes up from the stack pointer and `add` reaches four, so a local deep in a
375/// large frame is written as `add x16, sp, #4096` and then the access four thousand and some bytes
376/// closer, which is what gcc writes. The part left in the instruction is the low bits when the
377/// instruction can carry those and nothing when it cannot, which is the unaligned offset a scaled
378/// load refuses.
379///
380/// After the allocator's moves have been cleaned up rather than here in [`finish`], because that
381/// pass reads what each scratch register holds between one move and the next and an address
382/// written into one in the middle is not a move it knows about. The scratch register is one the
383/// instruction does not read, and one of the two always is, since an access through the stack
384/// pointer has no base register of its own to have been reloaded into a scratch.
385pub fn far(
386    func: &mut Func,
387    insts: &FrameInsts,
388    conv: &CallRegs,
389    scratch: &[PhysReg],
390    names: &mut Interner,
391) {
392    let Some(reaches) = insts.reaches else { return };
393    let lea = Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.lea)));
394    let frame = [conv.stack_pointer, conv.frame_pointer];
395    let all: Vec<Inst> = func.blocks().flat_map(|block| func.insts(block)).collect();
396    for inst in all {
397        let Some(mem) = func[inst].mem else { continue };
398        let amode = func[mem];
399        let plain = amode.index.is_none()
400            && amode.symbol.is_none()
401            && amode.block.is_none()
402            && amode.table.is_none()
403            && amode.segment.is_none();
404        let Some(at) = amode.base.filter(|_| plain) else { continue };
405        let operands = func[inst].operands;
406        let Some(from) = func[operands][usize::from(at)].reg.phys() else { continue };
407        let Some(name) = names.resolve(func[inst].opcode.name()).strip_prefix(insts.prefix) else {
408            continue;
409        };
410        if !frame.contains(&from) || reaches(name, amode.disp) {
411            continue;
412        }
413        let read = |reg: PhysReg| {
414            func[operands].iter().any(|op| op.role != Role::Def && op.reg.phys() == Some(reg))
415        };
416        let written = |reg: PhysReg| {
417            func[operands].iter().any(|op| op.role == Role::Def && op.reg.phys() == Some(reg))
418        };
419        // A scratch register the instruction writes is free for the address, and so is one
420        // nothing reads again. The cleanup keeps a value in scratch from one instruction to the
421        // next, so a register this instruction does not read can still be holding one. Taking
422        // that register put the frame address in `x16` just before a store read the value it had.
423        let free = |reg: PhysReg| !read(reg) && (written(reg) || !live_after(func, inst, reg));
424        let loaded = || {
425            func[operands]
426                .iter()
427                .filter(|op| op.role == Role::Def && op.class == conv.int_class)
428                .filter_map(|op| op.reg.phys())
429                .find(|&reg| !read(reg) && !frame.contains(&reg))
430        };
431        // With neither free, one the instruction does not read is pushed around it and popped
432        // back after, which moves the stack pointer and so every offset counted from it. That is
433        // a store of one scratch register while the other is still wanted, which is rare. The
434        // unwind table is not told, so a backtrace taken on one of those few instructions in a
435        // function with no frame pointer is sixteen bytes off.
436        let (into, saved) = match scratch.iter().copied().find(|&reg| free(reg)).or_else(loaded) {
437            Some(reg) => (reg, false),
438            None => match scratch.iter().copied().find(|&reg| !read(reg)) {
439                Some(reg) => (reg, true),
440                None => continue,
441            },
442        };
443        let moved = if saved && from == conv.stack_pointer { offset(conv.push) } else { 0 };
444        let disp = amode.disp + moved;
445        let sign = disp.signum();
446        let mut steps: Vec<i32> =
447            insts.steps(disp.unsigned_abs()).into_iter().map(|step| offset(step) * sign).collect();
448        let last = steps.last().copied().unwrap_or(0);
449        let keep = if steps.len() > 1 && reaches(name, last) {
450            steps.pop();
451            last
452        } else {
453            0
454        };
455        let class = conv.int_class;
456        if saved {
457            let push = Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.push)));
458            let pop = Opcode::new(names.intern(&format!("{}{}", insts.prefix, insts.pop)));
459            let push = func.build_loose(push).uses(Reg::physical(into), class).finish();
460            func.insert_before(inst, push);
461            let pop = func.build_loose(pop).def(Reg::physical(into), class).finish();
462            func.insert_after(inst, pop);
463        }
464        let mut base = from;
465        for step in steps {
466            let address = func
467                .build_loose(lea)
468                .def(Reg::physical(into), class)
469                .mem(Mem::at(Operand::read(Reg::physical(base), class)).plus(step))
470                .finish();
471            func.insert_before(inst, address);
472            base = into;
473        }
474        func[operands][usize::from(at)].reg = Reg::physical(into);
475        func[mem].disp = keep;
476    }
477}
478
479/// Whether something after that instruction in its block reads the register before anything
480/// writes it again.
481///
482/// Only the block, because a scratch register is not live into another one. The cleanup follows
483/// what one holds from a move to its reader and starts again at the top of every block.
484fn live_after(func: &Func, inst: Inst, reg: PhysReg) -> bool {
485    let Some(block) = func.block_of(inst) else { return true };
486    for next in func.insts(block).skip_while(|&at| at != inst).skip(1) {
487        let operands = func[next].operands;
488        let holds = |def: bool| {
489            func[operands].iter().any(|op| op.role.is_def() == def && op.reg.phys() == Some(reg))
490        };
491        if holds(false) {
492            return true;
493        }
494        if holds(true) {
495            return false;
496        }
497    }
498    false
499}
500
501/// How many pages a probing prologue touches one after another before it writes a loop instead.
502///
503/// Three, which is what gcc unrolls to. The loop is four instructions however many pages it walks
504/// and a page written out is two, so three is the last size at which the straight line is no
505/// longer than the loop, and the straight line has no branch in it and needs no register.
506const UNROLLED: u32 = 3;
507
508/// One function having its frame written into it.
509/// Points an address the lowering left counted from the stack pointer at another register.
510///
511/// The base register is an operand of the instruction and the addressing mode holds where in the
512/// operand vector it is, so the register is changed there and not in the mode.
513fn rebase(func: &mut Func, inst: Inst, to: PhysReg) {
514    let mem = func[inst].mem.expect("an address");
515    let at = func[mem].base.expect("an address the lowering wrote a base register into");
516    let operands = func[inst].operands;
517    func[operands][usize::from(at)].reg = Reg::physical(to);
518}
519
520struct Writer<'a> {
521    func: &'a mut Func,
522    conv: &'a CallRegs,
523    insts: &'a FrameInsts,
524    names: &'a mut Interner,
525    /// Which register every offset into the frame is counted from, which is the stack pointer
526    /// unless the function moves it while it runs. See `Growing` in [`crate::frame`].
527    base: PhysReg,
528    /// The blocks a probing prologue made, which go in front of the one the function began with.
529    ///
530    /// Empty in every function whose frame is taken in one subtraction, which is every function
531    /// on a command line that did not ask for the stack to be touched a page at a time and most
532    /// of them on one that did. See [`Writer::pages`].
533    ahead: Option<[Block; 2]>,
534}
535
536impl Writer<'_> {
537    /// The instructions the prologue is, in the order they run.
538    ///
539    /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
540    /// before anything else, so that it points at a fixed place whatever else happens. The
541    /// registers are pushed before the alignment is forced, so that the epilogue can find them
542    /// again from the frame pointer, since after the alignment is forced nothing else can. And the
543    /// vector registers are stored last, because until the frame has been taken there is nowhere
544    /// to store them.
545    ///
546    /// Where the pointer is pointed at the frame is the one part of that order the platform gets a
547    /// say in. Windows wants it after the frame has been taken rather than before, because the
548    /// unwind record it reads has no way to describe the other order, so on that target the move
549    /// goes between the frame and the vector stores instead. See `Late` in [`crate::frame`].
550    ///
551    /// The landing pad is in front of all of it, because the address it makes reachable is the
552    /// address of the function and the address of the function is where the first instruction is.
553    /// It has to be written here rather than after the fact, since a probing prologue moves the
554    /// instructions written so far into a block of its own and the pad has to move with them.
555    ///
556    /// The room a patcher was promised goes after the pad, because a patcher wants somewhere it can
557    /// write a call that happens before anything else, and the pad is the one instruction that has
558    /// to come first for a reason of its own.
559    ///
560    /// A profiler's hook goes next, or at the end when it is the kind that reads the frame pointer.
561    /// The early one is in front of everything the frame does for a reason of its own: what makes
562    /// it worth replacing while the program runs is that the stack at that instruction is exactly
563    /// what a call leaves, and a prologue that had already run would have changed it.
564    fn prologue(
565        &mut self,
566        frame: &Frame,
567        protect: Option<Protect<'_>>,
568        probe: Option<Probing<'_>>,
569        landing: Option<&'static str>,
570        trace: Option<Tracing>,
571        pad: Option<Padding>,
572    ) -> Vec<Inst> {
573        let sp = self.conv.stack_pointer;
574        let fp = self.conv.frame_pointer;
575        let int = self.conv.int_class;
576        let sse = self.conv.sse_class;
577        let word = offset(self.conv.word);
578        let push = offset(self.conv.push);
579        let mut out = Vec::new();
580        // What the prologue wrote before it had described anything, which is what decides whether
581        // there is a rule to remember at the end of it. Neither of these moves a register or takes
582        // a frame, so a function whose whole prologue is one of them has no rows and must not be
583        // given a pair of them that cancel out.
584        let mut quiet = Vec::new();
585        if let Some(name) = landing {
586            let opcode = self.opcode(name);
587            let inst = self.func.build_loose(opcode).finish();
588            out.push(inst);
589            quiet.push(inst);
590        }
591        // After the pad and in front of everything else, which is where gcc puts it. The pad is the
592        // function's first instruction because the address an indirect branch may arrive at is the
593        // address of the function, and the room comes next because what gets written over it is a
594        // call and the point of that call is that it happens before the function has done anything.
595        //
596        // Nothing is described for any of it. A byte that does nothing does not move the stack
597        // pointer, and what a patcher writes over it later is its own problem rather than this
598        // function's: the rules here say what this function did, and it did nothing.
599        if let Some(pad) = pad {
600            let opcode = self.opcode(pad.name);
601            let mut first = None;
602            for _ in 0..pad.after {
603                let inst = self.func.build_loose(opcode).finish();
604                out.push(inst);
605                quiet.push(inst);
606                first.get_or_insert(inst);
607            }
608            self.func.patch = Some(Patch { before: pad.before, pad: opcode, after: first });
609        }
610        // Nothing is described for it and nothing needs to be: the call pushes a return address and
611        // the hook pops it, so the frame is the same on both sides, and the hook preserves every
612        // register because it is written in assembly for exactly this. That is also why the
613        // allocator, which ran before any of this, never saw the call and did not have to.
614        if let Some(trace) = trace.filter(|trace| trace.early) {
615            let inst = self.hook(trace);
616            out.push(inst);
617            quiet.push(inst);
618        }
619        // How far the stack pointer is below the canonical frame address, and whether the address
620        // is still counted from the stack pointer at all. It starts at the return address the
621        // call itself pushed, which is the rule the CIE already states, so the first row here is
622        // the first thing this function does on top of that.
623        let mut below = offset(self.conv.return_address);
624        let mut from_sp = true;
625        if frame.frame_pointer() {
626            let inst = self.push_frame();
627            out.push(inst);
628            below += push;
629            self.row(inst, CfiOp::DefCfaOffset(below));
630            self.saved(inst, int, fp, -below);
631            // The frame record, where the return address a call left in a register goes on with
632            // the frame pointer and sits the word above it.
633            if let Some(link) = self.record() {
634                self.saved(inst, int, link, word - below);
635            }
636            // Straight away unless the platform wants it after the frame, where the same two
637            // instructions go at the bottom of this function instead. See `Late` in
638            // [`crate::frame`].
639            if !frame.late() {
640                let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
641                let inst = self.two(mov, fp, sp);
642                out.push(inst);
643                let number = self.dwarf(int, fp);
644                self.row(inst, CfiOp::DefCfaRegister(number));
645                from_sp = false;
646            }
647        }
648        for &reg in frame.saved_int() {
649            let inst = self.push(reg);
650            out.push(inst);
651            below += push;
652            if from_sp {
653                self.row(inst, CfiOp::DefCfaOffset(below));
654            }
655            self.saved(inst, int, reg, -below);
656        }
657        if let Some(to) = frame.realign() {
658            // Nothing is written for this and nothing can be. After it the stack pointer is a
659            // rounded-down version of where it was rather than a fixed distance from it, which is
660            // exactly what a rule cannot say. It is also why a frame that realigns is a frame
661            // with a frame pointer: by here the address is already counted from that instead.
662            assert!(!from_sp, "a frame that forces its own alignment has a frame pointer");
663            let and = self.opcode(self.insts.align);
664            out.push(self.arith(and, -i64::from(to)));
665        }
666        if frame.size() > 0 {
667            self.take(&mut out, frame.size(), &mut below, from_sp, probe);
668        }
669        // The other half of the pair above, for the platform whose record counts everything from
670        // where the stack pointer ends the prologue. Here it is that register the pointer is a copy
671        // of, so what the row says is the whole distance rather than that nothing has changed, and
672        // the frame is described before it rather than after, which is the whole of what the record
673        // could not say about the early order.
674        if frame.late() && frame.frame_pointer() {
675            let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
676            let inst = self.two(mov, fp, sp);
677            out.push(inst);
678            let number = self.dwarf(int, fp);
679            self.row(inst, CfiOp::DefCfa { reg: number, offset: below });
680        }
681        for save in frame.saved_sse() {
682            let inst = self.store(sse, save.reg, save.at);
683            out.push(inst);
684            // Where it went is an offset from whichever register the frame counts from, and the
685            // address is a constant above that register, so the two make one constant. In an
686            // ordinary frame that register is the stack pointer and the constant is `below`. In one
687            // that grows it is the frame pointer, which the address has been counted from since the
688            // prologue pointed it at where it saved the caller's copy, so the constant is the two
689            // words above it and nothing the prologue did afterwards changes it. Unless the pointer
690            // went up late, where it holds what the stack pointer holds and the constant is `below`
691            // again, which is why the question is about both. A realigned frame has no such constant
692            // at all and the rule is left out rather than guessed. On SysV that costs nothing, since
693            // it preserves no vector register for a prologue to save. On Windows it would be a save
694            // with no row, and what stops that reaching an object file is that a realigned frame
695            // there keeps the early order and is refused whole by the unwind writer, which is
696            // `tamnd/rucc#1422`.
697            if frame.realign().is_none() {
698                let above = if frame.grows() && !frame.late() {
699                    push + offset(self.conv.return_address)
700                } else {
701                    below
702                };
703                self.saved(inst, sse, save.reg, save.at - above);
704            }
705        }
706        // Before the canary and after the frame, which is where gcc puts it. The hook reads the
707        // frame pointer to find out who called this function, so it has to run once there is one,
708        // and it is a call, so it has to run before anything the function is keeping in the frame
709        // could be read back.
710        if let Some(trace) = trace.filter(|trace| !trace.early) {
711            let inst = self.hook(trace);
712            out.push(inst);
713        }
714        // Last of everything, because it writes into the frame and there is no frame to write into
715        // until the stack pointer has moved. Nothing is described for either instruction: they
716        // write a slot rather than save a register, and no unwinder wants to put a canary back.
717        if let Some(protect) = protect {
718            let at = frame.canary().expect("a protected function has a slot for its canary");
719            let [into, _] = protect.scratch;
720            out.push(self.read_guard(into, protect.guard));
721            out.push(self.store(self.conv.int_class, into, at));
722        }
723        // The rules the body runs under, kept so that each epilogue can put them back rather than
724        // leaving the next block reading whatever the last one ended on. See `epilogue`.
725        //
726        // Nothing is kept in a function whose whole prologue is the pieces that describe nothing.
727        // See `quiet` above.
728        if let Some(&last) = out.last() {
729            if !quiet.contains(&last) {
730                self.row(last, CfiOp::RememberState);
731            }
732        }
733        out
734    }
735
736    /// The call to a profiler's hook.
737    ///
738    /// No arguments and no result. Which function is being entered is not passed, because the hook
739    /// reads its own return address to find out, and that is the whole reason the call is written
740    /// rather than something cheaper.
741    fn hook(&mut self, trace: Tracing) -> Inst {
742        let call = self.opcode(self.insts.call);
743        let symbol = self.names.intern(trace.name);
744        self.func.build_loose(call).symbol(symbol).finish()
745    }
746
747    /// Takes the frame, which is one subtraction unless the command line asked for the stack to be
748    /// touched a page at a time.
749    ///
750    /// `below` is how far the canonical frame address is above the stack pointer, and it comes
751    /// back as what it is once the frame has been taken.
752    fn take(
753        &mut self,
754        out: &mut Vec<Inst>,
755        size: u32,
756        below: &mut i32,
757        from_sp: bool,
758        probe: Option<Probing<'_>>,
759    ) {
760        // In front of everything else, because a platform with a routine for this has it for every
761        // frame rather than for the ones a flag was passed about, and because the routine does the
762        // whole of what the walk below would have done. See [`rucc_target::Chkstk`].
763        let page = self.insts.probe.map_or(u32::MAX, |probe| probe.interval);
764        if let Some(chkstk) = self.conv.chkstk.filter(|_| size > page) {
765            let inst = self.reach(out, chkstk, size);
766            *below += offset(size);
767            if from_sp {
768                self.row(inst, CfiOp::DefCfaOffset(*below));
769            }
770            return;
771        }
772        let Some(probing) = probe.filter(|probing| size > probing.probe.interval) else {
773            for step in self.insts.steps(size) {
774                let inst = self.sub(step);
775                out.push(inst);
776                *below += offset(step);
777                if from_sp {
778                    self.row(inst, CfiOp::DefCfaOffset(*below));
779                }
780            }
781            return;
782        };
783        // Every step but the last is a whole page and is followed by a touch, and the last is
784        // whatever is left over, which is between one byte and one whole page. So the stack
785        // pointer never moves further than a page without something being written where it landed,
786        // and the unmapped page an operating system leaves below a stack cannot be stepped over.
787        //
788        // That is why the count is worked out from one less than the size. A frame that is an
789        // exact number of pages gets one fewer touch than it has pages, and the step left over is
790        // a whole page, which is a step that lands on the next page boundary rather than past it.
791        // gcc touches that last page as well, so this is one instruction shorter on a frame whose
792        // size is a multiple of the page and the same everywhere else.
793        let interval = probing.probe.interval;
794        let pages = (size - 1) / interval;
795        let rest = size - pages * interval;
796        let mut walked = false;
797        if pages <= UNROLLED {
798            for _ in 0..pages {
799                let inst = self.sub(interval);
800                out.push(inst);
801                *below += offset(interval);
802                if from_sp {
803                    self.row(inst, CfiOp::DefCfaOffset(*below));
804                }
805                let touch = self.touch(probing.probe);
806                out.push(touch);
807            }
808        } else {
809            self.pages(out, pages, below, from_sp, probing);
810            walked = from_sp;
811        }
812        let inst = self.sub(rest);
813        out.push(inst);
814        *below += offset(rest);
815        if from_sp {
816            // A loop leaves the address counted from the register the stack pointer was compared
817            // against, since that is the one thing in it that holds still. This is where it goes
818            // back to being counted from the stack pointer, and it is written behind this
819            // instruction rather than behind the branch because a row is written behind an
820            // instruction and the branch is not one that survives [`crate::layout`].
821            let op = if walked {
822                let number = self.dwarf(self.conv.int_class, self.conv.stack_pointer);
823                CfiOp::DefCfa { reg: number, offset: *below }
824            } else {
825                CfiOp::DefCfaOffset(*below)
826            };
827            self.row(inst, op);
828        }
829    }
830
831    /// Reaches the pages of a frame by calling the routine this platform has for it, and then takes
832    /// the frame.
833    ///
834    /// Three instructions, and the third is the one that moves anything:
835    ///
836    /// ```text
837    ///   the size into a register    which register is the platform's answer rather than ours
838    ///   the call                    touches every page from here down to that many bytes below
839    ///   the subtraction             takes the frame, of the register the size is still in
840    /// ```
841    ///
842    /// The routine comes back having moved nothing, which is what makes the third instruction
843    /// necessary and is also what makes it a subtraction of a register rather than of the constant
844    /// written again. Writing the constant twice would be the same number of bytes of code and one
845    /// more place for the two to disagree.
846    ///
847    /// Nothing is described to the unwinder for the first two. The call pushes a return address and
848    /// the routine pops it, so the frame is the same on both sides of it, which is the same argument
849    /// the profiler's hook makes a few lines above. The row goes behind the subtraction, where the
850    /// stack pointer has actually moved.
851    ///
852    /// No register here has to be asked about. The size goes in one the convention passes no
853    /// argument in, which is what lets the platform name it at all, and the routine destroys two
854    /// that are exactly the two the allocator was told to hold back. A prologue is also the one
855    /// place in a function where the only live values are the ones that arrived in the convention's
856    /// own registers.
857    fn reach(&mut self, out: &mut Vec<Inst>, chkstk: Chkstk, size: u32) -> Inst {
858        let class = self.conv.int_class;
859        let sp = Reg::physical(self.conv.stack_pointer);
860        let count = Reg::physical(chkstk.size);
861
862        let imm = self.opcode(self.insts.imm);
863        let inst = self.func.build_loose(imm).def(count, class).imm(i64::from(size)).finish();
864        out.push(inst);
865        let call = self.opcode(self.insts.call);
866        let symbol = self.names.intern(chkstk.name);
867        let inst = self.func.build_loose(call).symbol(symbol).finish();
868        out.push(inst);
869        let grow = self.opcode(self.insts.grow);
870        let inst =
871            self.func.build_loose(grow).def(sp, class).uses(sp, class).uses(count, class).finish();
872        out.push(inst);
873        inst
874    }
875
876    /// The loop that takes a frame too large for the touches to be written one after another.
877    ///
878    /// Three blocks, and the first two are new and go in front of the one the function began with:
879    ///
880    /// ```text
881    ///   what the function is entered at   everything the prologue did before this, and then the
882    ///                                     address the stack pointer is walking down to
883    ///   the loop                          one page, the touch, and the question of whether the
884    ///                                     stack pointer has got there yet
885    ///   what the function began with      the rest of the prologue, and then the body
886    /// ```
887    ///
888    /// The instructions the prologue has written so far move into the first of them, because a
889    /// block is entered at the top and they have to run before the loop does. Nothing is laid out
890    /// here: which block comes first in memory is [`crate::layout`]'s answer, and all this decides
891    /// is which one the function is entered at.
892    fn pages(
893        &mut self,
894        out: &mut Vec<Inst>,
895        pages: u32,
896        below: &mut i32,
897        from_sp: bool,
898        probing: Probing<'_>,
899    ) {
900        let class = self.conv.int_class;
901        let sp = self.conv.stack_pointer;
902        let all = offset(pages * probing.probe.interval);
903        let [limit, byte] = probing.scratch;
904
905        let head = self.func.create_block();
906        for &inst in out.iter() {
907            self.func.append_inst(head, inst);
908        }
909        out.clear();
910        // Where the stack pointer is walking down to, worked out before it starts moving. A loop
911        // that counted down instead would need somewhere to keep the count, and this is somewhere
912        // to keep it that the comparison can read without arithmetic.
913        let lea = self.opcode(self.insts.lea);
914        let inst = self.address(lea, limit, sp, -all);
915        self.func.append_inst(head, inst);
916        if from_sp {
917            // The address is counted from that register for as long as the loop runs, and it has
918            // to be: the stack pointer moves once an iteration, so no fixed distance from it is
919            // true twice, and this register was written so that one distance is.
920            let number = self.dwarf(class, limit);
921            self.row(inst, CfiOp::DefCfa { reg: number, offset: *below + all });
922        }
923
924        let body = self.func.create_block();
925        *self.func.succs_mut(head) = vec![BlockCall::to(body)];
926        let inst = self.sub(probing.probe.interval);
927        self.func.append_inst(body, inst);
928        let touch = self.touch(probing.probe);
929        self.func.append_inst(body, touch);
930        let differ = self.opcode(self.insts.differ);
931        let inst = self
932            .func
933            .build_loose(differ)
934            .def(Reg::physical(byte), class)
935            .uses(Reg::physical(sp), class)
936            .uses(Reg::physical(limit), class)
937            .finish();
938        self.func.append_inst(body, inst);
939        let cond = Opcode::new(
940            self.names.intern(&format!("{}{}", probing.branch.prefix, probing.branch.cond)),
941        );
942        let inst = self.func.build_loose(cond).uses(Reg::physical(byte), class).finish();
943        self.func.append_inst(body, inst);
944        // The first arm is the one taken when the condition held, and the condition is that the
945        // stack pointer and the address it is walking down to still differ, so the first arm is
946        // another page.
947        let began = self.func.entry().expect("a function with a block in it");
948        *self.func.succs_mut(body) = vec![BlockCall::to(body), BlockCall::to(began)];
949        *below += all;
950        self.ahead = Some([head, body]);
951    }
952
953    /// Walks the pages a variable length array takes, at the declaration that takes them.
954    ///
955    /// The prologue's own pages are counted when it is written, so it can step down to an address
956    /// it worked out in advance and stop when it gets there. A declaration in the body cannot: how
957    /// many bytes it asked for arrives in a register, so where it is going is arithmetic rather than
958    /// a constant, and how many pages that is is a number nothing has. What is written instead is a
959    /// loop that steps a page and asks whether it has arrived yet, which is the same walk with the
960    /// count taken out of it.
961    ///
962    /// The one instruction the lowering wrote becomes four blocks:
963    ///
964    /// ```text
965    ///   what the block was          everything it did before the declaration, and then where the
966    ///                               stack pointer is going, worked out before it starts moving
967    ///   the step                    one page, and whether the stack pointer is still above there
968    ///   the page it stepped onto    the touch, and round again
969    ///   the rest of the block       the stack pointer put where it was going, and then the body
970    /// ```
971    ///
972    /// The touch is behind the question rather than in front of it, so the only page ever written
973    /// is one the array reaches. The last step down is a whole page whatever is left, which puts the
974    /// stack pointer at or past the end of the array, and the block that follows puts it back on the
975    /// end. Nothing is touched there and nothing has to be: that is a move of less than a page from
976    /// a page this loop has already been to, which is the whole of what a guard page asks.
977    ///
978    /// Nothing is described to the unwinder for any of it. A function with a variable length array
979    /// in it keeps a frame pointer, because its own stack pointer is not a fixed distance from
980    /// anything, and by here the frame is already counted from that register rather than from the
981    /// stack pointer. So the rule that was true before the walk is still true after it.
982    fn walk(&mut self, took: Inst, probing: Probing<'_>) {
983        let class = self.conv.int_class;
984        let sp = self.conv.stack_pointer;
985        let span = self.func.span(took);
986        let block = self.func.block_of(took).expect("an instruction the lowering put in a block");
987
988        // Which register the bytes arrived in, and which two the walk may use. The bytes may be in
989        // one of the two, because a reload the rewriter wrote is written into one of them, and a
990        // value that arrived that way is read by the one instruction it was written in front of and
991        // is dead after it. So the limit goes in whichever of the pair the bytes are not in, and the
992        // other one is free from the moment the limit has been worked out.
993        let operands = self.func[took].operands;
994        let bytes = self.func[operands][2].reg.phys().expect("a register the allocator settled");
995        let [first, second] = probing.scratch;
996        let (limit, flag) = if bytes == first { (second, first) } else { (first, second) };
997
998        let tail: Vec<Inst> = {
999            let mut rest = self.func.insts(block).skip_while(|&inst| inst != took);
1000            rest.next();
1001            rest.collect()
1002        };
1003        let step = self.func.create_block();
1004        let onto = self.func.create_block();
1005        let done = self.func.create_block();
1006
1007        let mov = self.opcode(self.insts.moves(class).expect("a class the target can move").mov);
1008        let inst = self.two(mov, sp, limit);
1009        self.func.append_inst(done, inst);
1010        for inst in tail {
1011            self.func.remove_inst(inst);
1012            self.func.append_inst(done, inst);
1013        }
1014        let succs = std::mem::take(self.func.succs_mut(block));
1015        *self.func.succs_mut(done) = succs;
1016
1017        // The subtraction the lowering wrote is what the loop is instead of, so it goes. What is
1018        // left in the block it was in is where the stack pointer is walking down to.
1019        self.func.remove_inst(took);
1020        let inst = self.two(mov, limit, sp);
1021        self.func.append_inst(block, inst);
1022        let grow = self.opcode(self.insts.grow);
1023        let inst = self
1024            .func
1025            .build_loose(grow)
1026            .at(span)
1027            .def(Reg::physical(limit), class)
1028            .uses(Reg::physical(limit), class)
1029            .uses(Reg::physical(bytes), class)
1030            .finish();
1031        self.func.append_inst(block, inst);
1032        *self.func.succs_mut(block) = vec![BlockCall::to(step)];
1033
1034        let inst = self.sub(probing.probe.interval);
1035        self.func.append_inst(step, inst);
1036        let above = self.opcode(self.insts.above);
1037        let inst = self
1038            .func
1039            .build_loose(above)
1040            .def(Reg::physical(flag), class)
1041            .uses(Reg::physical(sp), class)
1042            .uses(Reg::physical(limit), class)
1043            .finish();
1044        self.func.append_inst(step, inst);
1045        let cond = Opcode::new(
1046            self.names.intern(&format!("{}{}", probing.branch.prefix, probing.branch.cond)),
1047        );
1048        let inst = self.func.build_loose(cond).uses(Reg::physical(flag), class).finish();
1049        self.func.append_inst(step, inst);
1050        // The first arm is the one taken when the condition held, and the condition is that the
1051        // stack pointer is still above where the array ends, so the first arm is the page it has
1052        // just stepped onto being written and another time round.
1053        *self.func.succs_mut(step) = vec![BlockCall::to(onto), BlockCall::to(done)];
1054
1055        let touch = self.touch(probing.probe);
1056        self.func.append_inst(onto, touch);
1057        *self.func.succs_mut(onto) = vec![BlockCall::to(step)];
1058    }
1059
1060    /// Writes the page the stack pointer is on without changing what is there.
1061    fn touch(&mut self, probe: &Probe) -> Inst {
1062        let opcode = self.opcode(probe.inst);
1063        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
1064        self.func.build_loose(opcode).imm(0).mem(Mem::at(base)).finish()
1065    }
1066
1067    /// Takes that many bytes off the stack pointer.
1068    fn sub(&mut self, bytes: u32) -> Inst {
1069        let sub = self.opcode(self.insts.sub);
1070        self.arith(sub, i64::from(bytes))
1071    }
1072
1073    /// The stack protector's check, written at the end of a block the function returns from.
1074    ///
1075    /// Gives back the block the epilogue goes in, which is a new one: the check has to be the last
1076    /// thing the old block does, and what follows it is one of two arms rather than the return.
1077    ///
1078    /// ```text
1079    ///   block that returned      reload the slot, read the word again, compare, branch
1080    ///   the arm it changed on    call the function that does not come back, and nothing after
1081    ///   the arm it did not       the epilogue, which the caller writes into what this gives back
1082    /// ```
1083    ///
1084    /// The two registers are the ones the allocator was told to hold back, so nothing here has to
1085    /// ask what is live: a scratch register holds nothing at the end of a block, because the only
1086    /// thing that writes one is a move the rewriter put in and every one of those is read by the
1087    /// instruction it was put in front of.
1088    fn check(&mut self, block: Block, frame: &Frame, protect: Protect<'_>) -> Block {
1089        let class = self.conv.int_class;
1090        let at = frame.canary().expect("a protected function has a slot for its canary");
1091        let [ours, theirs] = protect.scratch;
1092
1093        let inst = self.load(class, ours, at);
1094        self.func.append_inst(block, inst);
1095        let inst = self.read_guard(theirs, protect.guard);
1096        self.func.append_inst(block, inst);
1097        let differ = self.opcode(self.insts.differ);
1098        let inst = self
1099            .func
1100            .build_loose(differ)
1101            .def(Reg::physical(theirs), class)
1102            .uses(Reg::physical(ours), class)
1103            .uses(Reg::physical(theirs), class)
1104            .finish();
1105        self.func.append_inst(block, inst);
1106
1107        let failed = self.func.create_block();
1108        let ok = self.func.create_block();
1109        let cond = Opcode::new(
1110            self.names.intern(&format!("{}{}", protect.branch.prefix, protect.branch.cond)),
1111        );
1112        let inst = self.func.build_loose(cond).uses(Reg::physical(theirs), class).finish();
1113        self.func.append_inst(block, inst);
1114        // The first arm is the one taken when the condition held, and the condition is that the
1115        // two words differ, so the first arm is the one the canary was overwritten on.
1116        *self.func.succs_mut(block) = vec![BlockCall::to(failed), BlockCall::to(ok)];
1117
1118        let call = self.opcode(self.insts.call);
1119        let symbol = self.names.intern(protect.guard.fail);
1120        self.func.build(failed, call).symbol(symbol).finish();
1121        ok
1122    }
1123
1124    /// Reads the word the canary is a copy of into a register.
1125    ///
1126    /// The address is a constant and names no register at all, because where the block a thread
1127    /// has to itself begins is something only the machine knows and the segment register is what
1128    /// holds it.
1129    fn read_guard(&mut self, into: PhysReg, guard: &Guard) -> Inst {
1130        let class = self.conv.int_class;
1131        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
1132        self.func
1133            .build_loose(load)
1134            .def(Reg::physical(into), class)
1135            .mem(Mem::in_segment(guard.segment, guard.at))
1136            .finish()
1137    }
1138
1139    /// The instructions the epilogue is, in the order they run.
1140    ///
1141    /// The vector registers are read back while the stack pointer is still where the body left it,
1142    /// because that is what their offsets are from. Then the stack pointer goes back to the last
1143    /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
1144    /// moved and a read of the frame pointer when it did not.
1145    fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
1146        let sp = self.conv.stack_pointer;
1147        let fp = self.conv.frame_pointer;
1148        let int = self.conv.int_class;
1149        let sse = self.conv.sse_class;
1150        let push = self.conv.push;
1151        let described = !self.func.cfi.is_empty();
1152        let mut out = Vec::new();
1153        // Where the body left things, which is where every epilogue starts from.
1154        let mut below = offset(self.conv.return_address)
1155            + offset(push) * self.pushes(frame)
1156            + offset(frame.size());
1157        let from_sp = !frame.frame_pointer();
1158        for save in frame.saved_sse() {
1159            let inst = self.load(sse, save.reg, save.at);
1160            out.push(inst);
1161            if frame.realign().is_none() {
1162                self.restored(inst, sse, save.reg);
1163            }
1164        }
1165        let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
1166        if frame.frame_pointer() {
1167            // No row for either of these. The address is counted from the frame pointer here and
1168            // this is what moves the stack pointer rather than the frame pointer, so the rule that
1169            // was true before it is still true after it.
1170            //
1171            // Where the pointer is decides how far back this has to go. The early order left it one
1172            // push above the first push, so the pops start that many pushes below it. The late one
1173            // left it where the body's stack pointer was, so they start the whole frame above it,
1174            // and in both cases the distance is a constant even in a frame that grew while it ran,
1175            // which is why this is written rather than an addition to the stack pointer.
1176            let back = if frame.late() { offset(frame.size()) } else { -offset(push * pushed) };
1177            if back == 0 {
1178                let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
1179                out.push(self.two(mov, sp, fp));
1180            } else {
1181                let lea = self.opcode(self.insts.lea);
1182                out.push(self.address(lea, sp, fp, back));
1183            }
1184        } else if frame.size() > 0 {
1185            let add = self.opcode(self.insts.add);
1186            for step in self.insts.steps(frame.size()) {
1187                let inst = self.arith(add, i64::from(step));
1188                out.push(inst);
1189                below -= offset(step);
1190                self.row(inst, CfiOp::DefCfaOffset(below));
1191            }
1192        }
1193        for &reg in frame.saved_int().iter().rev() {
1194            let inst = self.pop(reg);
1195            out.push(inst);
1196            self.restored(inst, int, reg);
1197            below -= offset(push);
1198            if from_sp {
1199                self.row(inst, CfiOp::DefCfaOffset(below));
1200            }
1201        }
1202        if frame.frame_pointer() {
1203            let inst = self.pop_frame();
1204            out.push(inst);
1205            self.restored(inst, int, fp);
1206            if let Some(link) = self.record() {
1207                self.restored(inst, int, link);
1208            }
1209            // The frame pointer holds the caller's value again, so the address goes back to being
1210            // counted from the stack pointer, which by now is at the return address.
1211            let number = self.dwarf(int, sp);
1212            self.row(inst, CfiOp::DefCfa { reg: number, offset: offset(self.conv.return_address) });
1213        }
1214        let ret = self.opcode(self.insts.ret);
1215        let inst = self.func.build_loose(ret).finish();
1216        out.push(inst);
1217        // These take effect at the address just past the return, which is where the next block
1218        // begins, and the next block is body again. Popping the body's rules and pushing them
1219        // straight back leaves the stack one deep however many blocks the function returns from,
1220        // which is what makes one remembering in the prologue enough for all of them.
1221        if described {
1222            self.row(inst, CfiOp::RestoreState);
1223            self.row(inst, CfiOp::RememberState);
1224        }
1225        // And where all of it came from, which is the closing brace. Nothing here has a span of its
1226        // own: an epilogue is the frame going back the way it came and no expression in the source
1227        // asked for any of it, so without this the bytes are covered by whatever the last statement
1228        // of the body was. That is the hole the prologue used to have, at the other end, and gcc
1229        // fills it the same way it fills the other one, with the brace. A function whose body this
1230        // does not know is left alone and keeps covering those bytes with the last row before them.
1231        let closing = ending(self.func.declared);
1232        if !closing.is_dummy() {
1233            for &inst in &out {
1234                self.func.set_span(inst, closing);
1235            }
1236        }
1237        out
1238    }
1239
1240    /// How many general purpose registers the prologue put on the stack, the frame pointer
1241    /// included.
1242    fn pushes(&self, frame: &Frame) -> i32 {
1243        let saved = i32::try_from(frame.saved_int().len()).expect("a frame");
1244        saved + i32::from(frame.frame_pointer())
1245    }
1246
1247    /// One row of the unwind table, taking effect after that instruction.
1248    fn row(&mut self, inst: Inst, op: CfiOp) {
1249        self.func.cfi.push((inst, op));
1250    }
1251
1252    /// A row saying the caller's copy of that register is that far from the canonical frame
1253    /// address, which is below it and so is negative.
1254    fn saved(&mut self, inst: Inst, class: RegClass, reg: PhysReg, from_cfa: i32) {
1255        let number = self.dwarf(class, reg);
1256        self.row(inst, CfiOp::Offset { reg: number, offset: from_cfa });
1257    }
1258
1259    /// A row saying that register holds what the caller left in it again.
1260    fn restored(&mut self, inst: Inst, class: RegClass, reg: PhysReg) {
1261        let number = self.dwarf(class, reg);
1262        self.row(inst, CfiOp::Restore(number));
1263    }
1264
1265    /// What an unwind table calls that register.
1266    fn dwarf(&self, class: RegClass, reg: PhysReg) -> u16 {
1267        self.conv.dwarf(class, reg).expect("a register a frame saves is one the table can name")
1268    }
1269
1270    /// One edit as the instruction that makes it true.
1271    fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
1272        let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
1273        match (edit.mov.to, edit.mov.from) {
1274            (Place::Reg(to), Place::Reg(from)) => {
1275                let mov = self.opcode(moves.mov);
1276                self.func
1277                    .build_loose(mov)
1278                    .def(Reg::physical(to), edit.class)
1279                    .uses(Reg::physical(from), edit.class)
1280                    .finish()
1281            }
1282            (Place::Reg(to), Place::Slot(slot)) => {
1283                let at = self.slot(frame, slot);
1284                self.load(edit.class, to, at)
1285            }
1286            (Place::Slot(slot), Place::Reg(from)) => {
1287                let at = self.slot(frame, slot);
1288                self.store(edit.class, from, at)
1289            }
1290            // The allocator expands this into two moves through a register of its own, because a
1291            // machine that could do it in one is not a machine any of this is written for.
1292            (Place::Slot(_), Place::Slot(_)) => {
1293                unreachable!("a move from one stack slot straight into another")
1294            }
1295        }
1296    }
1297
1298    /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
1299    ///
1300    /// The edits at one place are in the order they have to be made in, so each one goes behind
1301    /// the last, and the first of them is what the place itself means.
1302    fn put(&mut self, cursors: &mut HashMap<At, Inst>, at: At, inst: Inst) {
1303        if let Some(cursor) = cursors.get_mut(&at) {
1304            self.func.insert_after(*cursor, inst);
1305            *cursor = inst;
1306            return;
1307        }
1308        match at {
1309            At::Before(before) => self.func.insert_before(before, inst),
1310            At::After(after) => self.func.insert_after(after, inst),
1311            At::StartOf(block) => self.func.prepend_inst(block, inst),
1312            // Behind everything in the block. A block the allocator puts an edge's moves at the
1313            // end of is one with a single edge out of it, and an edge like that is not an
1314            // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
1315            // So the last instruction is an ordinary one, which may still be waiting on moves of
1316            // its own that have to be made before the edge's are.
1317            At::EndOf(block) => self.func.append_inst(block, inst),
1318        }
1319        cursors.insert(at, inst);
1320    }
1321
1322    /// Where a spill slot is, from the stack pointer in the body of the function.
1323    fn slot(&self, frame: &Frame, slot: u32) -> i32 {
1324        frame.slot(slot).expect("a slot the frame was worked out from")
1325    }
1326
1327    /// Reads a register out of the frame.
1328    fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
1329        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
1330        let base = Operand::read(Reg::physical(self.base), self.conv.int_class);
1331        self.func
1332            .build_loose(load)
1333            .def(Reg::physical(reg), class)
1334            .mem(Mem::at(base).plus(at))
1335            .finish()
1336    }
1337
1338    /// Writes a register into the frame.
1339    fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
1340        let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
1341        let base = Operand::read(Reg::physical(self.base), self.conv.int_class);
1342        self.func
1343            .build_loose(store)
1344            .uses(Reg::physical(reg), class)
1345            .mem(Mem::at(base).plus(at))
1346            .finish()
1347    }
1348
1349    /// Puts a general purpose register on the stack.
1350    fn push(&mut self, reg: PhysReg) -> Inst {
1351        let push = self.opcode(self.insts.push);
1352        self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
1353    }
1354
1355    /// Takes a general purpose register back off the stack.
1356    fn pop(&mut self, reg: PhysReg) -> Inst {
1357        let pop = self.opcode(self.insts.pop);
1358        self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
1359    }
1360
1361    /// The register that goes on the stack with the frame pointer, which is the one a call leaves
1362    /// the return address in on a machine that pushes the two together, and nothing anywhere else.
1363    fn record(&self) -> Option<PhysReg> {
1364        self.conv.link.filter(|_| self.insts.pair.is_some())
1365    }
1366
1367    /// Puts the caller's frame pointer on the stack, together with the return address on a machine
1368    /// that keeps it in a register. The frame pointer goes at the lower address, so the pointer set
1369    /// to it straight after names the caller's copy and the return address is the word above.
1370    fn push_frame(&mut self) -> Inst {
1371        let fp = self.conv.frame_pointer;
1372        let (Some(link), Some(pair)) = (self.record(), self.insts.pair) else {
1373            return self.push(fp);
1374        };
1375        let push = self.opcode(pair.push);
1376        let class = self.conv.int_class;
1377        self.func
1378            .build_loose(push)
1379            .uses(Reg::physical(fp), class)
1380            .uses(Reg::physical(link), class)
1381            .finish()
1382    }
1383
1384    /// Takes back what [`Self::push_frame`] put on the stack.
1385    fn pop_frame(&mut self) -> Inst {
1386        let fp = self.conv.frame_pointer;
1387        let (Some(link), Some(pair)) = (self.record(), self.insts.pair) else {
1388            return self.pop(fp);
1389        };
1390        let pop = self.opcode(pair.pop);
1391        let class = self.conv.int_class;
1392        self.func
1393            .build_loose(pop)
1394            .def(Reg::physical(fp), class)
1395            .def(Reg::physical(link), class)
1396            .finish()
1397    }
1398
1399    /// One general purpose register written with another.
1400    fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
1401        let class = self.conv.int_class;
1402        self.func
1403            .build_loose(opcode)
1404            .def(Reg::physical(to), class)
1405            .uses(Reg::physical(from), class)
1406            .finish()
1407    }
1408
1409    /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
1410    fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
1411        let class = self.conv.int_class;
1412        let sp = Reg::physical(self.conv.stack_pointer);
1413        self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
1414    }
1415
1416    /// One register written with an address rather than with what is at it.
1417    fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
1418        let class = self.conv.int_class;
1419        let base = Operand::read(Reg::physical(base), class);
1420        self.func
1421            .build_loose(opcode)
1422            .def(Reg::physical(to), class)
1423            .mem(Mem::at(base).plus(disp))
1424            .finish()
1425    }
1426
1427    /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
1428    /// then the name the target gave.
1429    fn opcode(&mut self, name: &str) -> Opcode {
1430        Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
1431    }
1432}
1433
1434/// A distance in a frame, as the signed number every offset is.
1435fn offset(bytes: u32) -> i32 {
1436    i32::try_from(bytes).expect("a frame under two gigabytes")
1437}
1438
1439/// The last character of a span, which for the span of a function body is its closing brace.
1440///
1441/// A span runs from the first byte to one past the last, so the brace is the byte before the end
1442/// rather than the end. [`Span::DUMMY`] for a function that came from no C source, which is what
1443/// the tests and the IR parser build, and for the empty span that cannot have a last character.
1444fn ending(body: Span) -> Span {
1445    if body.is_dummy() || body.hi <= body.lo {
1446        return Span::DUMMY;
1447    }
1448    Span::new(body.hi - 1, body.hi)
1449}
1450
1451#[cfg(test)]
1452mod tests {
1453    use rucc_base::Interner;
1454    use rucc_mir::{BlockCall, print_func};
1455    use rucc_regalloc::assign::Env;
1456    use rucc_target::x86_64::{
1457        BRANCH, FRAME, GPR, PROBE, R10, R11, RAX, REGS, SYSV, WIN64, XMM, xmm,
1458    };
1459
1460    use super::*;
1461    use crate::frame::{Layout, Local};
1462
1463    /// The closing brace of a body is the last character of its span and not the end of it, since a
1464    /// span runs to one past what it covers. A function that came from no source has no brace and
1465    /// asks for no row, which is what keeps the epilogue of one the IR parser built covered by the
1466    /// row before it rather than by a position in a file that is not there.
1467    #[test]
1468    fn the_end_of_a_body_is_its_closing_brace_and_not_one_past_it() {
1469        assert_eq!(ending(Span::new(10, 40)), Span::new(39, 40));
1470        assert_eq!(ending(Span::DUMMY), Span::DUMMY);
1471        assert_eq!(ending(Span::new(7, 7)), Span::DUMMY);
1472    }
1473
1474    /// An environment offering that many of the convention's registers, with everything after
1475    /// them held back as scratch.
1476    fn env(conv: &CallRegs, count: usize) -> Env {
1477        Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
1478    }
1479
1480    /// A function of that many values, every one written before any is read, allocated with that
1481    /// many registers to hand out. The same shape the frame layout's own tests are written
1482    /// against, so that a frame here is one that has already been checked there.
1483    fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
1484        let mut names = Interner::new();
1485        let mut func = Func::new(names.intern("f"));
1486        let opcode = Opcode::new(names.intern("x64.nop"));
1487        let block = func.create_block();
1488        let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
1489        for &reg in &regs {
1490            func.build(block, opcode).def(reg, GPR).finish();
1491        }
1492        for &reg in &regs {
1493            func.build(block, opcode).uses(reg, GPR).finish();
1494        }
1495        let allocation = rucc_regalloc::run(&mut func, &env(conv, count), "test", true);
1496        (func, allocation, names)
1497    }
1498
1499    /// The function with its frame written into it, as the lines a dump would show.
1500    fn written(
1501        func: &mut Func,
1502        allocation: &Allocation,
1503        layout: &Layout<'_>,
1504        names: &mut Interner,
1505    ) -> Vec<String> {
1506        with_protector(func, allocation, layout, None, names)
1507    }
1508
1509    /// The same, for a function the caller has decided is protected or is not.
1510    fn with_protector(
1511        func: &mut Func,
1512        allocation: &Allocation,
1513        layout: &Layout<'_>,
1514        protect: Option<Protect<'_>>,
1515        names: &mut Interner,
1516    ) -> Vec<String> {
1517        let convention = Convention { protect, ..Convention::new(layout.conv, &FRAME) };
1518        under(func, allocation, layout, &Stack::default(), convention, names)
1519    }
1520
1521    /// The same, for a function whose frame the caller has decided is taken a page at a time.
1522    fn with_probing(
1523        func: &mut Func,
1524        allocation: &Allocation,
1525        layout: &Layout<'_>,
1526        probe: Option<Probing<'_>>,
1527        names: &mut Interner,
1528    ) -> Vec<String> {
1529        let convention = Convention { probe, ..Convention::new(layout.conv, &FRAME) };
1530        under(func, allocation, layout, &Stack::default(), convention, names)
1531    }
1532
1533    /// A function whose one block takes a run of bytes off the stack pointer, which is what the
1534    /// lowering writes for a variable length array, with the count already in the register given.
1535    fn growing(count: PhysReg) -> (Func, Allocation, Interner, Stack) {
1536        let mut names = Interner::new();
1537        let mut func = Func::new(names.intern("f"));
1538        let block = func.create_block();
1539        let sp = Reg::physical(SYSV.stack_pointer);
1540        let grow = Opcode::new(names.intern("x64.sub_rr_64"));
1541        let took = func
1542            .build(block, grow)
1543            .def(sp, GPR)
1544            .uses(sp, GPR)
1545            .uses(Reg::physical(count), GPR)
1546            .finish();
1547        let nop = Opcode::new(names.intern("x64.nop"));
1548        func.build(block, nop).finish();
1549        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test", true);
1550        (func, allocation, names, Stack { grown: vec![took], ..Stack::default() })
1551    }
1552
1553    /// The function with its frame written into it under that convention.
1554    fn under(
1555        func: &mut Func,
1556        allocation: &Allocation,
1557        layout: &Layout<'_>,
1558        stack: &Stack,
1559        convention: Convention<'_>,
1560        names: &mut Interner,
1561    ) -> Vec<String> {
1562        let frame = Frame::of(func, allocation, layout);
1563        finish(func, allocation, &frame, stack, convention, names);
1564        print_func(func, names, &REGS)
1565            .lines()
1566            .filter(|line| !line.is_empty())
1567            .map(|line| line.trim().to_string())
1568            .collect()
1569    }
1570
1571    /// Just the lines the frame put in, which is every line that is not the function it was
1572    /// given and not the shape of the dump around it.
1573    fn added(lines: &[String]) -> Vec<&str> {
1574        lines
1575            .iter()
1576            .map(String::as_str)
1577            .filter(|line| !line.contains("x64.nop"))
1578            .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
1579            .collect()
1580    }
1581
1582    #[test]
1583    fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
1584        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1585        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1586
1587        // Two values and four registers, so nothing is spilled, nothing is saved and the stack
1588        // pointer never moves. A prologue of nothing is the right prologue for that.
1589        assert_eq!(added(&lines), ["x64.ret"]);
1590    }
1591
1592    /// The bytes that give a frame back are filed under the closing brace, which is where a
1593    /// debugger says a function ends and which nothing in an epilogue could say for itself.
1594    #[test]
1595    fn an_epilogue_is_filed_under_the_closing_brace_of_the_body() {
1596        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1597        func.declared = Span::new(100, 140);
1598        let base = Layout::new(&SYSV, REGS);
1599        let layout = Layout { red_zone: false, ..base };
1600        written(&mut func, &allocation, &layout, &mut names);
1601
1602        // Everything from the first instruction of the epilogue to the return, and nothing above
1603        // it: the body's own instructions keep the spans they arrived with, which here is none.
1604        let ends: Vec<Span> = func
1605            .blocks()
1606            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1607            .map(|inst| func.span(inst))
1608            .filter(|span| !span.is_dummy())
1609            .collect();
1610        assert!(!ends.is_empty(), "an epilogue was written");
1611        assert!(ends.iter().all(|&span| span == Span::new(139, 140)), "{ends:?}");
1612    }
1613
1614    #[test]
1615    fn a_spill_is_a_store_and_a_reload_is_a_load() {
1616        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1617        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1618
1619        // Two registers for four values, so two of them go to the stack. The store goes behind the
1620        // instruction that wrote the value and the load in front of the one that wants it, both at
1621        // the offsets the frame gave, which are below the stack pointer because a small leaf
1622        // function is entitled to the red zone.
1623        assert_eq!(
1624            lines,
1625            [
1626                "mfunc @f {",
1627                "block0:",
1628                "$rax = x64.nop",
1629                "$rcx = x64.nop",
1630                "$rdx = x64.nop",
1631                "x64.mov_mr_64 $rdx, [$rsp - 16]",
1632                "$rdx = x64.nop",
1633                "x64.mov_mr_64 $rdx, [$rsp - 8]",
1634                "x64.nop $rax",
1635                "x64.nop $rcx",
1636                "$rdx = x64.mov_rm_64 [$rsp - 16]",
1637                "x64.nop $rdx",
1638                "$rdx = x64.mov_rm_64 [$rsp - 8]",
1639                "x64.nop $rdx",
1640                "x64.ret",
1641                "}",
1642            ]
1643        );
1644    }
1645
1646    #[test]
1647    fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
1648        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1649        let base = Layout::new(&SYSV, REGS);
1650        let layout = Layout { red_zone: false, ..base };
1651        let lines = written(&mut func, &allocation, &layout, &mut names);
1652
1653        // The same function told it may not use the red zone takes sixteen bytes instead, and
1654        // every offset moves above the stack pointer to match.
1655        assert_eq!(
1656            added(&lines),
1657            [
1658                "$rsp = x64.sub_ri_64 $rsp, 16",
1659                "x64.mov_mr_64 $rdx, [$rsp]",
1660                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1661                "$rdx = x64.mov_rm_64 [$rsp]",
1662                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1663                "$rsp = x64.add_ri_64 $rsp, 16",
1664                "x64.ret",
1665            ]
1666        );
1667    }
1668
1669    #[test]
1670    fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
1671        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
1672        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1673
1674        // Four registers a call leaves alone, pushed in the convention's order and popped in the
1675        // other one, which is the only order that gets each of them its own value back.
1676        assert_eq!(
1677            added(&lines),
1678            [
1679                "x64.push_64 $rbx",
1680                "x64.push_64 $r12",
1681                "x64.push_64 $r13",
1682                "x64.push_64 $r14",
1683                "$r14 = x64.pop_64",
1684                "$r13 = x64.pop_64",
1685                "$r12 = x64.pop_64",
1686                "$rbx = x64.pop_64",
1687                "x64.ret",
1688            ]
1689        );
1690    }
1691
1692    #[test]
1693    fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
1694        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1695        let base = Layout::new(&SYSV, REGS);
1696        let layout = Layout { frame_pointer: true, red_zone: false, ..base };
1697        let lines = written(&mut func, &allocation, &layout, &mut names);
1698
1699        // The frame pointer is saved before anything else and points at where it was saved, so the
1700        // epilogue reaches the stack pointer through it rather than by counting the frame back.
1701        assert_eq!(
1702            added(&lines),
1703            [
1704                "x64.push_64 $rbp",
1705                "$rbp = x64.mov_rr_64 $rsp",
1706                "$rsp = x64.sub_ri_64 $rsp, 16",
1707                "x64.mov_mr_64 $rdx, [$rsp]",
1708                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1709                "$rdx = x64.mov_rm_64 [$rsp]",
1710                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1711                "$rsp = x64.mov_rr_64 $rbp",
1712                "$rbp = x64.pop_64",
1713                "x64.ret",
1714            ]
1715        );
1716    }
1717
1718    #[test]
1719    fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
1720        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
1721        let locals = [Local { size: 64, align: 32 }];
1722        let base = Layout::new(&SYSV, REGS);
1723        let layout = Layout { locals: &locals, ..base };
1724        let lines = written(&mut func, &allocation, &layout, &mut names);
1725
1726        // Forcing the alignment throws away how far the stack pointer had moved, so the registers
1727        // are pushed before it happens and the epilogue counts back from the frame pointer to find
1728        // them. The frame pointer is required here whatever the flags said.
1729        assert_eq!(
1730            added(&lines),
1731            [
1732                "x64.push_64 $rbp",
1733                "$rbp = x64.mov_rr_64 $rsp",
1734                "x64.push_64 $rbx",
1735                "x64.push_64 $r12",
1736                "x64.push_64 $r13",
1737                "x64.push_64 $r14",
1738                "$rsp = x64.and_ri_64 $rsp, -32",
1739                "$rsp = x64.sub_ri_64 $rsp, 64",
1740                "$rsp = x64.lea_64 [$rbp - 32]",
1741                "$r14 = x64.pop_64",
1742                "$r13 = x64.pop_64",
1743                "$r12 = x64.pop_64",
1744                "$rbx = x64.pop_64",
1745                "$rbp = x64.pop_64",
1746                "x64.ret",
1747            ]
1748        );
1749    }
1750
1751    #[test]
1752    fn every_block_the_function_returns_from_gets_an_epilogue() {
1753        let mut names = Interner::new();
1754        let mut func = Func::new(names.intern("f"));
1755        let opcode = Opcode::new(names.intern("x64.nop"));
1756        let head = func.create_block();
1757        let left = func.create_block();
1758        let right = func.create_block();
1759        func.build(head, opcode).finish();
1760        *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
1761        func.build(left, opcode).finish();
1762        func.build(right, opcode).finish();
1763        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test", true);
1764        let base = Layout::new(&SYSV, REGS);
1765        let layout = Layout { leaf: false, ..base };
1766        let lines = written(&mut func, &allocation, &layout, &mut names);
1767
1768        // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
1769        // because a block with an edge out of it is not a block anything returns from.
1770        assert_eq!(
1771            lines,
1772            [
1773                "mfunc @f {",
1774                "block0:",
1775                "$rsp = x64.sub_ri_64 $rsp, 8",
1776                "x64.nop block1, block2",
1777                "block1:",
1778                "x64.nop",
1779                "$rsp = x64.add_ri_64 $rsp, 8",
1780                "x64.ret",
1781                "block2:",
1782                "x64.nop",
1783                "$rsp = x64.add_ri_64 $rsp, 8",
1784                "x64.ret",
1785                "}",
1786            ]
1787        );
1788    }
1789
1790    #[test]
1791    fn a_protected_function_writes_the_canary_last_and_checks_it_before_it_returns() {
1792        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1793        let base = Layout::new(&SYSV, REGS);
1794        let layout = Layout { leaf: false, protect: true, ..base };
1795        let guard = SYSV.guard.as_ref().expect("this convention has somewhere to keep the word");
1796        // The two the real pipeline holds back, which are held back in the environment above too:
1797        // it hands out the first two of the convention's order and keeps everything after them.
1798        let protect = Protect { guard, branch: &BRANCH, scratch: [R10, R11] };
1799        let lines = with_protector(&mut func, &allocation, &layout, Some(protect), &mut names);
1800
1801        // The read of the word and the store into the slot come after the stack pointer has moved,
1802        // because there is no slot to store into until it has. The check is the last thing the
1803        // block that returned does and the epilogue is on the arm the canary was unchanged on, so
1804        // a function whose canary changed never gives its frame back and never returns.
1805        assert_eq!(
1806            added(&lines),
1807            [
1808                "$rsp = x64.sub_ri_64 $rsp, 24",
1809                "$r10 = x64.mov_rm_64 [fs:40]",
1810                "x64.mov_mr_64 $r10, [$rsp + 16]",
1811                "x64.mov_mr_64 $rdx, [$rsp]",
1812                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1813                "$rdx = x64.mov_rm_64 [$rsp]",
1814                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1815                "$r10 = x64.mov_rm_64 [$rsp + 16]",
1816                "$r11 = x64.mov_rm_64 [fs:40]",
1817                "$r11 = x64.cmp_set_ne_64 $r10, $r11",
1818                "x64.br_cond_8 $r11, block1, block2",
1819                "x64.call @__stack_chk_fail",
1820                "$rsp = x64.add_ri_64 $rsp, 24",
1821                "x64.ret",
1822            ]
1823        );
1824    }
1825
1826    #[test]
1827    fn a_frame_that_fits_in_one_page_is_taken_in_one_subtraction_even_when_pages_are_touched() {
1828        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1829        let locals = [Local { size: 4088, align: 16 }];
1830        let base = Layout::new(&SYSV, REGS);
1831        let layout = Layout { leaf: false, locals: &locals, ..base };
1832        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1833        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1834
1835        // A frame of one page cannot step over the page below it, because the far end of it is the
1836        // near end of that page and anything written there is written to a page that is there. So
1837        // the flag costs such a function nothing, which is most functions.
1838        assert_eq!(
1839            added(&lines),
1840            ["$rsp = x64.sub_ri_64 $rsp, 4088", "$rsp = x64.add_ri_64 $rsp, 4088", "x64.ret",]
1841        );
1842    }
1843
1844    #[test]
1845    fn a_probing_prologue_touches_every_page_of_a_frame_a_few_pages_deep() {
1846        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1847        let locals = [Local { size: 9000, align: 16 }];
1848        let base = Layout::new(&SYSV, REGS);
1849        let layout = Layout { leaf: false, locals: &locals, ..base };
1850        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1851        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1852
1853        // A page of the stack pointer's own, then the touch that says the page is there, and only
1854        // then the next one, which is the whole of the defence: nothing here ever moves the stack
1855        // pointer further than one page without writing where it landed. The last subtraction is
1856        // the remainder and is smaller than a page, so it needs no touch of its own, and it exists
1857        // in every frame because the count of pages is taken off one less than the size.
1858        assert_eq!(
1859            added(&lines),
1860            [
1861                "$rsp = x64.sub_ri_64 $rsp, 4096",
1862                "x64.or_mi_8 [$rsp], 0",
1863                "$rsp = x64.sub_ri_64 $rsp, 4096",
1864                "x64.or_mi_8 [$rsp], 0",
1865                "$rsp = x64.sub_ri_64 $rsp, 808",
1866                "$rsp = x64.add_ri_64 $rsp, 9000",
1867                "x64.ret",
1868            ]
1869        );
1870    }
1871
1872    #[test]
1873    fn a_variable_length_array_walks_its_pages_where_the_declaration_stands() {
1874        let (mut func, allocation, mut names, stack) = growing(RAX);
1875        let base = Layout::new(&SYSV, REGS);
1876        let layout = Layout { leaf: false, grows: true, ..base };
1877        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1878        let convention = Convention { probe: Some(probing), ..Convention::new(&SYSV, &FRAME) };
1879        let lines = under(&mut func, &allocation, &layout, &stack, convention, &mut names);
1880
1881        // The whole listing, because what the walk is cannot be read off the instructions alone.
1882        // The one subtraction the lowering wrote is gone and four blocks stand where its block was:
1883        // where the stack pointer is going, the step, the page the step landed on, and the rest of
1884        // what the block was doing with the stack pointer put back where it was going.
1885        assert_eq!(
1886            lines,
1887            [
1888                "mfunc @f {",
1889                "block0:",
1890                "x64.push_64 $rbp",
1891                "$rbp = x64.mov_rr_64 $rsp",
1892                "$r10 = x64.mov_rr_64 $rsp",
1893                "$r10 = x64.sub_rr_64 $r10, $rax, block1",
1894                "block1:",
1895                "$rsp = x64.sub_ri_64 $rsp, 4096",
1896                "$r11 = x64.cmp_set_a_64 $rsp, $r10",
1897                "x64.br_cond_8 $r11, block2, block3",
1898                "block2:",
1899                "x64.or_mi_8 [$rsp], 0, block1",
1900                "block3:",
1901                "$rsp = x64.mov_rr_64 $r10",
1902                "x64.nop",
1903                "$rsp = x64.mov_rr_64 $rbp",
1904                "$rbp = x64.pop_64",
1905                "x64.ret",
1906                "}",
1907            ]
1908        );
1909    }
1910
1911    #[test]
1912    fn the_walk_keeps_the_register_the_count_arrived_in() {
1913        let (mut func, allocation, mut names, stack) = growing(R10);
1914        let base = Layout::new(&SYSV, REGS);
1915        let layout = Layout { leaf: false, grows: true, ..base };
1916        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1917        let convention = Convention { probe: Some(probing), ..Convention::new(&SYSV, &FRAME) };
1918        let lines = under(&mut func, &allocation, &layout, &stack, convention, &mut names);
1919
1920        // The count is in the first of the two registers the walk was given, which is where a
1921        // reload the rewriter wrote would have put it, so the limit goes in the other one and the
1922        // comparison writes the first one back only once the count has been read for the last time.
1923        let added = added(&lines);
1924        assert!(added.contains(&"$r11 = x64.mov_rr_64 $rsp"), "{added:?}");
1925        assert!(added.contains(&"$r11 = x64.sub_rr_64 $r11, $r10, block1"), "{added:?}");
1926        assert!(added.contains(&"$r10 = x64.cmp_set_a_64 $rsp, $r11"), "{added:?}");
1927    }
1928
1929    #[test]
1930    fn a_variable_length_array_takes_its_bytes_in_one_subtraction_when_nothing_asked() {
1931        let (mut func, allocation, mut names, stack) = growing(RAX);
1932        let base = Layout::new(&SYSV, REGS);
1933        let layout = Layout { leaf: false, grows: true, ..base };
1934        let convention = Convention::new(&SYSV, &FRAME);
1935        let lines = under(&mut func, &allocation, &layout, &stack, convention, &mut names);
1936
1937        // The instruction the lowering wrote, where it wrote it, and one block still.
1938        assert!(lines.contains(&"$rsp = x64.sub_rr_64 $rsp, $rax".to_owned()), "{lines:?}");
1939        assert_eq!(lines.iter().filter(|line| line.starts_with("block")).count(), 1, "{lines:?}");
1940    }
1941
1942    #[test]
1943    fn a_probing_prologue_deeper_than_that_walks_the_pages_in_a_loop() {
1944        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1945        let locals = [Local { size: 100_000, align: 16 }];
1946        let base = Layout::new(&SYSV, REGS);
1947        let layout = Layout { leaf: false, locals: &locals, ..base };
1948        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1949        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1950
1951        // Twenty-four pages, which is more than a straight line is worth, so the prologue works out
1952        // where it is going first and then walks there. The whole listing rather than the added
1953        // lines, because what matters as much as the instructions is that the two blocks the walk
1954        // is made of come in front of the block the function began with: the body the allocator
1955        // filled is block2 here and it was block0 before this ran.
1956        assert_eq!(
1957            lines,
1958            [
1959                "mfunc @f {",
1960                "block0:",
1961                "$r10 = x64.lea_64 [$rsp - 98304], block1",
1962                "block1:",
1963                "$rsp = x64.sub_ri_64 $rsp, 4096",
1964                "x64.or_mi_8 [$rsp], 0",
1965                "$r11 = x64.cmp_set_ne_64 $rsp, $r10",
1966                "x64.br_cond_8 $r11, block1, block2",
1967                "block2:",
1968                "$rsp = x64.sub_ri_64 $rsp, 1704",
1969                "$rax = x64.nop",
1970                "$rcx = x64.nop",
1971                "x64.nop $rax",
1972                "x64.nop $rcx",
1973                "$rsp = x64.add_ri_64 $rsp, 100008",
1974                "x64.ret",
1975                "}",
1976            ]
1977        );
1978    }
1979
1980    #[test]
1981    fn a_large_frame_on_a_platform_with_a_routine_for_its_pages_calls_the_routine() {
1982        let (mut func, allocation, mut names) = pressure(&WIN64, 2, 4);
1983        let locals = [Local { size: 100_000, align: 16 }];
1984        let base = Layout::new(&WIN64, REGS);
1985        let layout = Layout { leaf: false, locals: &locals, ..base };
1986        let lines = written(&mut func, &allocation, &layout, &mut names);
1987
1988        // Nothing asked for this on the command line, which is the point: Windows commits a stack
1989        // by having the pages touched in order, so a frame this size has to reach them whatever the
1990        // flags said. The size goes in the register the platform names, the routine touches every
1991        // page down to there, and the frame is taken afterwards, because the routine comes back
1992        // having moved nothing. What the epilogue gives back is what the register was given, which
1993        // is the one thing worth tying together here.
1994        let added = added(&lines);
1995        assert_eq!(added.len(), 5, "{added:?}");
1996        let size = added[0].strip_prefix("$rax = x64.mov_ri_64 ").expect("a size in a register");
1997        assert_eq!(added[1], "x64.call @__chkstk");
1998        assert_eq!(added[2], "$rsp = x64.sub_rr_64 $rsp, $rax");
1999        assert_eq!(added[3], format!("$rsp = x64.add_ri_64 $rsp, {size}"));
2000        assert_eq!(added[4], "x64.ret");
2001    }
2002
2003    #[test]
2004    fn a_frame_of_one_page_calls_nothing_on_that_platform_either() {
2005        let (mut func, allocation, mut names) = pressure(&WIN64, 2, 4);
2006        let locals = [Local { size: 4000, align: 16 }];
2007        let base = Layout::new(&WIN64, REGS);
2008        let layout = Layout { leaf: false, locals: &locals, ..base };
2009        let lines = written(&mut func, &allocation, &layout, &mut names);
2010
2011        // The same reason a frame of one page is taken in one subtraction under the flag. The far
2012        // end of such a frame is inside the page below the stack pointer, and touching that page is
2013        // what the function does on its way to using the frame at all, so there is nothing for a
2014        // routine to do and a call to it would be a call in every function that declares an array.
2015        let added = added(&lines);
2016        assert!(added.iter().all(|line| !line.contains("chkstk")), "{added:?}");
2017        assert_eq!(added.len(), 3, "{added:?}");
2018    }
2019
2020    #[test]
2021    fn a_windows_prologue_points_its_frame_pointer_at_the_frame_once_the_frame_is_whole() {
2022        let (mut func, allocation, mut names) = pressure(&WIN64, 4, 2);
2023        let base = Layout::new(&WIN64, REGS);
2024        let layout = Layout { frame_pointer: true, ..base };
2025        let lines = written(&mut func, &allocation, &layout, &mut names);
2026
2027        // The other order, which is what every other platform here writes, has no unwind record on
2028        // this one: the record counts its slots from where the stack pointer ends the prologue and
2029        // gets there by taking a constant off the frame pointer, so a register pushed after the
2030        // pointer was established sits below the place the record counts from. Pushing first and
2031        // pointing last is the order that has a record, and it leaves the pointer holding a copy of
2032        // the stack pointer, so the spills stay where they were and the epilogue counts the frame
2033        // back off the pointer rather than moving the pointer into the stack pointer.
2034        assert_eq!(
2035            added(&lines),
2036            [
2037                "x64.push_64 $rbp",
2038                "$rsp = x64.sub_ri_64 $rsp, 16",
2039                "$rbp = x64.mov_rr_64 $rsp",
2040                "x64.mov_mr_64 $rdx, [$rsp]",
2041                "x64.mov_mr_64 $rdx, [$rsp + 8]",
2042                "$rdx = x64.mov_rm_64 [$rsp]",
2043                "$rdx = x64.mov_rm_64 [$rsp + 8]",
2044                "$rsp = x64.lea_64 [$rbp + 16]",
2045                "$rbp = x64.pop_64",
2046                "x64.ret",
2047            ]
2048        );
2049    }
2050
2051    #[test]
2052    fn a_windows_prologue_that_saves_registers_too_pushes_all_of_them_before_the_frame() {
2053        let (mut func, allocation, mut names) = pressure(&WIN64, 9, 8);
2054        let base = Layout::new(&WIN64, REGS);
2055        let layout = Layout { leaf: false, frame_pointer: true, ..base };
2056        let lines = written(&mut func, &allocation, &layout, &mut names);
2057
2058        // The shape that made the order necessary. All three pushes are above the frame, so every
2059        // one of them has a row the record can write, and the pointer is the last thing the
2060        // prologue does. Forty eight bytes is the thirty two every Windows caller reserves below a
2061        // call, eight for the one value that did not fit in a register, and eight that put the
2062        // stack pointer back where a call wants it given three pushes and the return address.
2063        assert_eq!(
2064            added(&lines),
2065            [
2066                "x64.push_64 $rbp",
2067                "x64.push_64 $rbx",
2068                "x64.push_64 $rsi",
2069                "$rsp = x64.sub_ri_64 $rsp, 48",
2070                "$rbp = x64.mov_rr_64 $rsp",
2071                "x64.mov_mr_64 $rsi, [$rsp + 32]",
2072                "$rsi = x64.mov_rm_64 [$rsp + 32]",
2073                "$rsp = x64.lea_64 [$rbp + 48]",
2074                "$rsi = x64.pop_64",
2075                "$rbx = x64.pop_64",
2076                "$rbp = x64.pop_64",
2077                "x64.ret",
2078            ]
2079        );
2080    }
2081
2082    #[test]
2083    fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
2084        let mut names = Interner::new();
2085        let mut func = Func::new(names.intern("f"));
2086        let opcode = Opcode::new(names.intern("x64.nop"));
2087        let block = func.create_block();
2088        // An instruction that writes one of the vector registers Windows preserves, which is what
2089        // a rule for something that has to use it produces.
2090        func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
2091        let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4), "test", true);
2092        let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
2093
2094        // No machine here pushes a vector register, so it is stored into the frame rather than
2095        // pushed, and the frame has to be taken before there is anywhere to put it.
2096        assert_eq!(
2097            added(&lines),
2098            [
2099                "$rsp = x64.sub_ri_64 $rsp, 24",
2100                "x64.movaps_mr $xmm6, [$rsp]",
2101                "$xmm6 = x64.movaps_rm [$rsp]",
2102                "$rsp = x64.add_ri_64 $rsp, 24",
2103                "x64.ret",
2104            ]
2105        );
2106    }
2107
2108    /// A reload from deep in the frame takes the scratch register it loads into for the address,
2109    /// and not the other one, which the store after it reads. This is the pair the cleanup leaves
2110    /// when it keeps a value in `x16`, and taking `x16` stored the frame address in its place.
2111    #[test]
2112    fn a_far_access_leaves_alone_the_scratch_register_read_after_it() {
2113        use rucc_target::aarch64::{self, AAPCS64, X16, X17};
2114        let mut names = Interner::new();
2115        let mut func = Func::new(names.intern("f"));
2116        let block = func.create_block();
2117        let gpr = aarch64::GPR;
2118        let sp = Mem::at(Operand::read(Reg::physical(AAPCS64.stack_pointer), gpr)).plus(40_000);
2119        let load = Opcode::new(names.intern("a64.ldr_64"));
2120        let store = Opcode::new(names.intern("a64.str_64"));
2121        let reload = func.build(block, load).def(Reg::physical(X17), gpr).mem(sp).finish();
2122        let into = Mem::at(Operand::read(Reg::physical(X17), gpr));
2123        func.build(block, store).uses(Reg::physical(X16), gpr).mem(into).finish();
2124
2125        far(&mut func, &aarch64::FRAME, &AAPCS64, &[X16, X17], &mut names);
2126        let insts: Vec<Inst> = func.insts(block).collect();
2127        assert!(insts.len() > 2, "the offset is out of reach and wants an address");
2128        for &inst in &insts[..insts.iter().position(|&at| at == reload).expect("still there")] {
2129            let written = func[func[inst].operands].iter().filter(|op| op.role.is_def());
2130            assert!(written.map(|op| op.reg.phys()).all(|reg| reg == Some(X17)));
2131        }
2132    }
2133
2134    /// A store of one scratch register deep in the frame while the other is still wanted after it
2135    /// has neither to build the address in, so the other goes on the stack for the length of the
2136    /// store, and the offset grows by the sixteen bytes the push moved the stack pointer.
2137    #[test]
2138    fn a_far_store_with_no_free_scratch_register_saves_one_around_itself() {
2139        use rucc_target::aarch64::{self, AAPCS64, X16, X17};
2140        let mut names = Interner::new();
2141        let mut func = Func::new(names.intern("f"));
2142        let block = func.create_block();
2143        let gpr = aarch64::GPR;
2144        let sp = Mem::at(Operand::read(Reg::physical(AAPCS64.stack_pointer), gpr)).plus(40_000);
2145        let store = Opcode::new(names.intern("a64.str_64"));
2146        let spill = func.build(block, store).uses(Reg::physical(X16), gpr).mem(sp).finish();
2147        let into = Mem::at(Operand::read(Reg::physical(AAPCS64.stack_pointer), gpr));
2148        func.build(block, store).uses(Reg::physical(X17), gpr).mem(into).finish();
2149
2150        far(&mut func, &aarch64::FRAME, &AAPCS64, &[X16, X17], &mut names);
2151        let text = print_func(&func, &names, &aarch64::REGS);
2152        let lines: Vec<&str> =
2153            text.lines().map(str::trim).filter(|line| line.contains("a64.")).collect();
2154        assert!(lines[0].starts_with("a64.push_64 $x17"), "{text}");
2155        assert!(lines.last().unwrap().starts_with("a64.str_64 $x17"), "{text}");
2156        assert!(lines[lines.len() - 2].contains("a64.pop_64"), "{text}");
2157        let spilled = func.insts(block).position(|at| at == spill).expect("still there");
2158        let base = func[func[spill].mem.expect("an address")].base.expect("a base");
2159        assert_eq!(func[func[spill].operands][usize::from(base)].reg, Reg::physical(X17), "{text}");
2160        let whole: i32 = func
2161            .insts(block)
2162            .take(spilled)
2163            .filter_map(|at| func[at].mem.map(|mem| func[mem].disp))
2164            .sum::<i32>()
2165            + func[func[spill].mem.expect("an address")].disp;
2166        assert_eq!(whole, 40_016, "{text}");
2167    }
2168}