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 Some(&into) = scratch.iter().find(|&&reg| !read(reg)) else { continue };
417        let sign = amode.disp.signum();
418        let mut steps: Vec<i32> = insts
419            .steps(amode.disp.unsigned_abs())
420            .into_iter()
421            .map(|step| offset(step) * sign)
422            .collect();
423        let last = steps.last().copied().unwrap_or(0);
424        let keep = if steps.len() > 1 && reaches(name, last) {
425            steps.pop();
426            last
427        } else {
428            0
429        };
430        let class = conv.int_class;
431        let mut base = from;
432        for step in steps {
433            let address = func
434                .build_loose(lea)
435                .def(Reg::physical(into), class)
436                .mem(Mem::at(Operand::read(Reg::physical(base), class)).plus(step))
437                .finish();
438            func.insert_before(inst, address);
439            base = into;
440        }
441        func[operands][usize::from(at)].reg = Reg::physical(into);
442        func[mem].disp = keep;
443    }
444}
445
446/// How many pages a probing prologue touches one after another before it writes a loop instead.
447///
448/// Three, which is what gcc unrolls to. The loop is four instructions however many pages it walks
449/// and a page written out is two, so three is the last size at which the straight line is no
450/// longer than the loop, and the straight line has no branch in it and needs no register.
451const UNROLLED: u32 = 3;
452
453/// One function having its frame written into it.
454/// Points an address the lowering left counted from the stack pointer at another register.
455///
456/// The base register is an operand of the instruction and the addressing mode holds where in the
457/// operand vector it is, so the register is changed there and not in the mode.
458fn rebase(func: &mut Func, inst: Inst, to: PhysReg) {
459    let mem = func[inst].mem.expect("an address");
460    let at = func[mem].base.expect("an address the lowering wrote a base register into");
461    let operands = func[inst].operands;
462    func[operands][usize::from(at)].reg = Reg::physical(to);
463}
464
465struct Writer<'a> {
466    func: &'a mut Func,
467    conv: &'a CallRegs,
468    insts: &'a FrameInsts,
469    names: &'a mut Interner,
470    /// Which register every offset into the frame is counted from, which is the stack pointer
471    /// unless the function moves it while it runs. See `Growing` in [`crate::frame`].
472    base: PhysReg,
473    /// The blocks a probing prologue made, which go in front of the one the function began with.
474    ///
475    /// Empty in every function whose frame is taken in one subtraction, which is every function
476    /// on a command line that did not ask for the stack to be touched a page at a time and most
477    /// of them on one that did. See [`Writer::pages`].
478    ahead: Option<[Block; 2]>,
479}
480
481impl Writer<'_> {
482    /// The instructions the prologue is, in the order they run.
483    ///
484    /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
485    /// before anything else, so that it points at a fixed place whatever else happens. The
486    /// registers are pushed before the alignment is forced, so that the epilogue can find them
487    /// again from the frame pointer, since after the alignment is forced nothing else can. And the
488    /// vector registers are stored last, because until the frame has been taken there is nowhere
489    /// to store them.
490    ///
491    /// Where the pointer is pointed at the frame is the one part of that order the platform gets a
492    /// say in. Windows wants it after the frame has been taken rather than before, because the
493    /// unwind record it reads has no way to describe the other order, so on that target the move
494    /// goes between the frame and the vector stores instead. See `Late` in [`crate::frame`].
495    ///
496    /// The landing pad is in front of all of it, because the address it makes reachable is the
497    /// address of the function and the address of the function is where the first instruction is.
498    /// It has to be written here rather than after the fact, since a probing prologue moves the
499    /// instructions written so far into a block of its own and the pad has to move with them.
500    ///
501    /// The room a patcher was promised goes after the pad, because a patcher wants somewhere it can
502    /// write a call that happens before anything else, and the pad is the one instruction that has
503    /// to come first for a reason of its own.
504    ///
505    /// A profiler's hook goes next, or at the end when it is the kind that reads the frame pointer.
506    /// The early one is in front of everything the frame does for a reason of its own: what makes
507    /// it worth replacing while the program runs is that the stack at that instruction is exactly
508    /// what a call leaves, and a prologue that had already run would have changed it.
509    fn prologue(
510        &mut self,
511        frame: &Frame,
512        protect: Option<Protect<'_>>,
513        probe: Option<Probing<'_>>,
514        landing: Option<&'static str>,
515        trace: Option<Tracing>,
516        pad: Option<Padding>,
517    ) -> Vec<Inst> {
518        let sp = self.conv.stack_pointer;
519        let fp = self.conv.frame_pointer;
520        let int = self.conv.int_class;
521        let sse = self.conv.sse_class;
522        let word = offset(self.conv.word);
523        let push = offset(self.conv.push);
524        let mut out = Vec::new();
525        // What the prologue wrote before it had described anything, which is what decides whether
526        // there is a rule to remember at the end of it. Neither of these moves a register or takes
527        // a frame, so a function whose whole prologue is one of them has no rows and must not be
528        // given a pair of them that cancel out.
529        let mut quiet = Vec::new();
530        if let Some(name) = landing {
531            let opcode = self.opcode(name);
532            let inst = self.func.build_loose(opcode).finish();
533            out.push(inst);
534            quiet.push(inst);
535        }
536        // After the pad and in front of everything else, which is where gcc puts it. The pad is the
537        // function's first instruction because the address an indirect branch may arrive at is the
538        // address of the function, and the room comes next because what gets written over it is a
539        // call and the point of that call is that it happens before the function has done anything.
540        //
541        // Nothing is described for any of it. A byte that does nothing does not move the stack
542        // pointer, and what a patcher writes over it later is its own problem rather than this
543        // function's: the rules here say what this function did, and it did nothing.
544        if let Some(pad) = pad {
545            let opcode = self.opcode(pad.name);
546            let mut first = None;
547            for _ in 0..pad.after {
548                let inst = self.func.build_loose(opcode).finish();
549                out.push(inst);
550                quiet.push(inst);
551                first.get_or_insert(inst);
552            }
553            self.func.patch = Some(Patch { before: pad.before, pad: opcode, after: first });
554        }
555        // Nothing is described for it and nothing needs to be: the call pushes a return address and
556        // the hook pops it, so the frame is the same on both sides, and the hook preserves every
557        // register because it is written in assembly for exactly this. That is also why the
558        // allocator, which ran before any of this, never saw the call and did not have to.
559        if let Some(trace) = trace.filter(|trace| trace.early) {
560            let inst = self.hook(trace);
561            out.push(inst);
562            quiet.push(inst);
563        }
564        // How far the stack pointer is below the canonical frame address, and whether the address
565        // is still counted from the stack pointer at all. It starts at the return address the
566        // call itself pushed, which is the rule the CIE already states, so the first row here is
567        // the first thing this function does on top of that.
568        let mut below = offset(self.conv.return_address);
569        let mut from_sp = true;
570        if frame.frame_pointer() {
571            let inst = self.push_frame();
572            out.push(inst);
573            below += push;
574            self.row(inst, CfiOp::DefCfaOffset(below));
575            self.saved(inst, int, fp, -below);
576            // The frame record, where the return address a call left in a register goes on with
577            // the frame pointer and sits the word above it.
578            if let Some(link) = self.record() {
579                self.saved(inst, int, link, word - below);
580            }
581            // Straight away unless the platform wants it after the frame, where the same two
582            // instructions go at the bottom of this function instead. See `Late` in
583            // [`crate::frame`].
584            if !frame.late() {
585                let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
586                let inst = self.two(mov, fp, sp);
587                out.push(inst);
588                let number = self.dwarf(int, fp);
589                self.row(inst, CfiOp::DefCfaRegister(number));
590                from_sp = false;
591            }
592        }
593        for &reg in frame.saved_int() {
594            let inst = self.push(reg);
595            out.push(inst);
596            below += push;
597            if from_sp {
598                self.row(inst, CfiOp::DefCfaOffset(below));
599            }
600            self.saved(inst, int, reg, -below);
601        }
602        if let Some(to) = frame.realign() {
603            // Nothing is written for this and nothing can be. After it the stack pointer is a
604            // rounded-down version of where it was rather than a fixed distance from it, which is
605            // exactly what a rule cannot say. It is also why a frame that realigns is a frame
606            // with a frame pointer: by here the address is already counted from that instead.
607            assert!(!from_sp, "a frame that forces its own alignment has a frame pointer");
608            let and = self.opcode(self.insts.align);
609            out.push(self.arith(and, -i64::from(to)));
610        }
611        if frame.size() > 0 {
612            self.take(&mut out, frame.size(), &mut below, from_sp, probe);
613        }
614        // The other half of the pair above, for the platform whose record counts everything from
615        // where the stack pointer ends the prologue. Here it is that register the pointer is a copy
616        // of, so what the row says is the whole distance rather than that nothing has changed, and
617        // the frame is described before it rather than after, which is the whole of what the record
618        // could not say about the early order.
619        if frame.late() && frame.frame_pointer() {
620            let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
621            let inst = self.two(mov, fp, sp);
622            out.push(inst);
623            let number = self.dwarf(int, fp);
624            self.row(inst, CfiOp::DefCfa { reg: number, offset: below });
625        }
626        for save in frame.saved_sse() {
627            let inst = self.store(sse, save.reg, save.at);
628            out.push(inst);
629            // Where it went is an offset from whichever register the frame counts from, and the
630            // address is a constant above that register, so the two make one constant. In an
631            // ordinary frame that register is the stack pointer and the constant is `below`. In one
632            // that grows it is the frame pointer, which the address has been counted from since the
633            // prologue pointed it at where it saved the caller's copy, so the constant is the two
634            // words above it and nothing the prologue did afterwards changes it. Unless the pointer
635            // went up late, where it holds what the stack pointer holds and the constant is `below`
636            // again, which is why the question is about both. A realigned frame has no such constant
637            // at all and the rule is left out rather than guessed. On SysV that costs nothing, since
638            // it preserves no vector register for a prologue to save. On Windows it would be a save
639            // with no row, and what stops that reaching an object file is that a realigned frame
640            // there keeps the early order and is refused whole by the unwind writer, which is
641            // `tamnd/rucc#1422`.
642            if frame.realign().is_none() {
643                let above = if frame.grows() && !frame.late() {
644                    push + offset(self.conv.return_address)
645                } else {
646                    below
647                };
648                self.saved(inst, sse, save.reg, save.at - above);
649            }
650        }
651        // Before the canary and after the frame, which is where gcc puts it. The hook reads the
652        // frame pointer to find out who called this function, so it has to run once there is one,
653        // and it is a call, so it has to run before anything the function is keeping in the frame
654        // could be read back.
655        if let Some(trace) = trace.filter(|trace| !trace.early) {
656            let inst = self.hook(trace);
657            out.push(inst);
658        }
659        // Last of everything, because it writes into the frame and there is no frame to write into
660        // until the stack pointer has moved. Nothing is described for either instruction: they
661        // write a slot rather than save a register, and no unwinder wants to put a canary back.
662        if let Some(protect) = protect {
663            let at = frame.canary().expect("a protected function has a slot for its canary");
664            let [into, _] = protect.scratch;
665            out.push(self.read_guard(into, protect.guard));
666            out.push(self.store(self.conv.int_class, into, at));
667        }
668        // The rules the body runs under, kept so that each epilogue can put them back rather than
669        // leaving the next block reading whatever the last one ended on. See `epilogue`.
670        //
671        // Nothing is kept in a function whose whole prologue is the pieces that describe nothing.
672        // See `quiet` above.
673        if let Some(&last) = out.last() {
674            if !quiet.contains(&last) {
675                self.row(last, CfiOp::RememberState);
676            }
677        }
678        out
679    }
680
681    /// The call to a profiler's hook.
682    ///
683    /// No arguments and no result. Which function is being entered is not passed, because the hook
684    /// reads its own return address to find out, and that is the whole reason the call is written
685    /// rather than something cheaper.
686    fn hook(&mut self, trace: Tracing) -> Inst {
687        let call = self.opcode(self.insts.call);
688        let symbol = self.names.intern(trace.name);
689        self.func.build_loose(call).symbol(symbol).finish()
690    }
691
692    /// Takes the frame, which is one subtraction unless the command line asked for the stack to be
693    /// touched a page at a time.
694    ///
695    /// `below` is how far the canonical frame address is above the stack pointer, and it comes
696    /// back as what it is once the frame has been taken.
697    fn take(
698        &mut self,
699        out: &mut Vec<Inst>,
700        size: u32,
701        below: &mut i32,
702        from_sp: bool,
703        probe: Option<Probing<'_>>,
704    ) {
705        // In front of everything else, because a platform with a routine for this has it for every
706        // frame rather than for the ones a flag was passed about, and because the routine does the
707        // whole of what the walk below would have done. See [`rucc_target::Chkstk`].
708        let page = self.insts.probe.map_or(u32::MAX, |probe| probe.interval);
709        if let Some(chkstk) = self.conv.chkstk.filter(|_| size > page) {
710            let inst = self.reach(out, chkstk, size);
711            *below += offset(size);
712            if from_sp {
713                self.row(inst, CfiOp::DefCfaOffset(*below));
714            }
715            return;
716        }
717        let Some(probing) = probe.filter(|probing| size > probing.probe.interval) else {
718            for step in self.insts.steps(size) {
719                let inst = self.sub(step);
720                out.push(inst);
721                *below += offset(step);
722                if from_sp {
723                    self.row(inst, CfiOp::DefCfaOffset(*below));
724                }
725            }
726            return;
727        };
728        // Every step but the last is a whole page and is followed by a touch, and the last is
729        // whatever is left over, which is between one byte and one whole page. So the stack
730        // pointer never moves further than a page without something being written where it landed,
731        // and the unmapped page an operating system leaves below a stack cannot be stepped over.
732        //
733        // That is why the count is worked out from one less than the size. A frame that is an
734        // exact number of pages gets one fewer touch than it has pages, and the step left over is
735        // a whole page, which is a step that lands on the next page boundary rather than past it.
736        // gcc touches that last page as well, so this is one instruction shorter on a frame whose
737        // size is a multiple of the page and the same everywhere else.
738        let interval = probing.probe.interval;
739        let pages = (size - 1) / interval;
740        let rest = size - pages * interval;
741        let mut walked = false;
742        if pages <= UNROLLED {
743            for _ in 0..pages {
744                let inst = self.sub(interval);
745                out.push(inst);
746                *below += offset(interval);
747                if from_sp {
748                    self.row(inst, CfiOp::DefCfaOffset(*below));
749                }
750                let touch = self.touch(probing.probe);
751                out.push(touch);
752            }
753        } else {
754            self.pages(out, pages, below, from_sp, probing);
755            walked = from_sp;
756        }
757        let inst = self.sub(rest);
758        out.push(inst);
759        *below += offset(rest);
760        if from_sp {
761            // A loop leaves the address counted from the register the stack pointer was compared
762            // against, since that is the one thing in it that holds still. This is where it goes
763            // back to being counted from the stack pointer, and it is written behind this
764            // instruction rather than behind the branch because a row is written behind an
765            // instruction and the branch is not one that survives [`crate::layout`].
766            let op = if walked {
767                let number = self.dwarf(self.conv.int_class, self.conv.stack_pointer);
768                CfiOp::DefCfa { reg: number, offset: *below }
769            } else {
770                CfiOp::DefCfaOffset(*below)
771            };
772            self.row(inst, op);
773        }
774    }
775
776    /// Reaches the pages of a frame by calling the routine this platform has for it, and then takes
777    /// the frame.
778    ///
779    /// Three instructions, and the third is the one that moves anything:
780    ///
781    /// ```text
782    ///   the size into a register    which register is the platform's answer rather than ours
783    ///   the call                    touches every page from here down to that many bytes below
784    ///   the subtraction             takes the frame, of the register the size is still in
785    /// ```
786    ///
787    /// The routine comes back having moved nothing, which is what makes the third instruction
788    /// necessary and is also what makes it a subtraction of a register rather than of the constant
789    /// written again. Writing the constant twice would be the same number of bytes of code and one
790    /// more place for the two to disagree.
791    ///
792    /// Nothing is described to the unwinder for the first two. The call pushes a return address and
793    /// the routine pops it, so the frame is the same on both sides of it, which is the same argument
794    /// the profiler's hook makes a few lines above. The row goes behind the subtraction, where the
795    /// stack pointer has actually moved.
796    ///
797    /// No register here has to be asked about. The size goes in one the convention passes no
798    /// argument in, which is what lets the platform name it at all, and the routine destroys two
799    /// that are exactly the two the allocator was told to hold back. A prologue is also the one
800    /// place in a function where the only live values are the ones that arrived in the convention's
801    /// own registers.
802    fn reach(&mut self, out: &mut Vec<Inst>, chkstk: Chkstk, size: u32) -> Inst {
803        let class = self.conv.int_class;
804        let sp = Reg::physical(self.conv.stack_pointer);
805        let count = Reg::physical(chkstk.size);
806
807        let imm = self.opcode(self.insts.imm);
808        let inst = self.func.build_loose(imm).def(count, class).imm(i64::from(size)).finish();
809        out.push(inst);
810        let call = self.opcode(self.insts.call);
811        let symbol = self.names.intern(chkstk.name);
812        let inst = self.func.build_loose(call).symbol(symbol).finish();
813        out.push(inst);
814        let grow = self.opcode(self.insts.grow);
815        let inst =
816            self.func.build_loose(grow).def(sp, class).uses(sp, class).uses(count, class).finish();
817        out.push(inst);
818        inst
819    }
820
821    /// The loop that takes a frame too large for the touches to be written one after another.
822    ///
823    /// Three blocks, and the first two are new and go in front of the one the function began with:
824    ///
825    /// ```text
826    ///   what the function is entered at   everything the prologue did before this, and then the
827    ///                                     address the stack pointer is walking down to
828    ///   the loop                          one page, the touch, and the question of whether the
829    ///                                     stack pointer has got there yet
830    ///   what the function began with      the rest of the prologue, and then the body
831    /// ```
832    ///
833    /// The instructions the prologue has written so far move into the first of them, because a
834    /// block is entered at the top and they have to run before the loop does. Nothing is laid out
835    /// here: which block comes first in memory is [`crate::layout`]'s answer, and all this decides
836    /// is which one the function is entered at.
837    fn pages(
838        &mut self,
839        out: &mut Vec<Inst>,
840        pages: u32,
841        below: &mut i32,
842        from_sp: bool,
843        probing: Probing<'_>,
844    ) {
845        let class = self.conv.int_class;
846        let sp = self.conv.stack_pointer;
847        let all = offset(pages * probing.probe.interval);
848        let [limit, byte] = probing.scratch;
849
850        let head = self.func.create_block();
851        for &inst in out.iter() {
852            self.func.append_inst(head, inst);
853        }
854        out.clear();
855        // Where the stack pointer is walking down to, worked out before it starts moving. A loop
856        // that counted down instead would need somewhere to keep the count, and this is somewhere
857        // to keep it that the comparison can read without arithmetic.
858        let lea = self.opcode(self.insts.lea);
859        let inst = self.address(lea, limit, sp, -all);
860        self.func.append_inst(head, inst);
861        if from_sp {
862            // The address is counted from that register for as long as the loop runs, and it has
863            // to be: the stack pointer moves once an iteration, so no fixed distance from it is
864            // true twice, and this register was written so that one distance is.
865            let number = self.dwarf(class, limit);
866            self.row(inst, CfiOp::DefCfa { reg: number, offset: *below + all });
867        }
868
869        let body = self.func.create_block();
870        *self.func.succs_mut(head) = vec![BlockCall::to(body)];
871        let inst = self.sub(probing.probe.interval);
872        self.func.append_inst(body, inst);
873        let touch = self.touch(probing.probe);
874        self.func.append_inst(body, touch);
875        let differ = self.opcode(self.insts.differ);
876        let inst = self
877            .func
878            .build_loose(differ)
879            .def(Reg::physical(byte), class)
880            .uses(Reg::physical(sp), class)
881            .uses(Reg::physical(limit), class)
882            .finish();
883        self.func.append_inst(body, inst);
884        let cond = Opcode::new(
885            self.names.intern(&format!("{}{}", probing.branch.prefix, probing.branch.cond)),
886        );
887        let inst = self.func.build_loose(cond).uses(Reg::physical(byte), class).finish();
888        self.func.append_inst(body, inst);
889        // The first arm is the one taken when the condition held, and the condition is that the
890        // stack pointer and the address it is walking down to still differ, so the first arm is
891        // another page.
892        let began = self.func.entry().expect("a function with a block in it");
893        *self.func.succs_mut(body) = vec![BlockCall::to(body), BlockCall::to(began)];
894        *below += all;
895        self.ahead = Some([head, body]);
896    }
897
898    /// Walks the pages a variable length array takes, at the declaration that takes them.
899    ///
900    /// The prologue's own pages are counted when it is written, so it can step down to an address
901    /// it worked out in advance and stop when it gets there. A declaration in the body cannot: how
902    /// many bytes it asked for arrives in a register, so where it is going is arithmetic rather than
903    /// a constant, and how many pages that is is a number nothing has. What is written instead is a
904    /// loop that steps a page and asks whether it has arrived yet, which is the same walk with the
905    /// count taken out of it.
906    ///
907    /// The one instruction the lowering wrote becomes four blocks:
908    ///
909    /// ```text
910    ///   what the block was          everything it did before the declaration, and then where the
911    ///                               stack pointer is going, worked out before it starts moving
912    ///   the step                    one page, and whether the stack pointer is still above there
913    ///   the page it stepped onto    the touch, and round again
914    ///   the rest of the block       the stack pointer put where it was going, and then the body
915    /// ```
916    ///
917    /// The touch is behind the question rather than in front of it, so the only page ever written
918    /// is one the array reaches. The last step down is a whole page whatever is left, which puts the
919    /// stack pointer at or past the end of the array, and the block that follows puts it back on the
920    /// end. Nothing is touched there and nothing has to be: that is a move of less than a page from
921    /// a page this loop has already been to, which is the whole of what a guard page asks.
922    ///
923    /// Nothing is described to the unwinder for any of it. A function with a variable length array
924    /// in it keeps a frame pointer, because its own stack pointer is not a fixed distance from
925    /// anything, and by here the frame is already counted from that register rather than from the
926    /// stack pointer. So the rule that was true before the walk is still true after it.
927    fn walk(&mut self, took: Inst, probing: Probing<'_>) {
928        let class = self.conv.int_class;
929        let sp = self.conv.stack_pointer;
930        let span = self.func.span(took);
931        let block = self.func.block_of(took).expect("an instruction the lowering put in a block");
932
933        // Which register the bytes arrived in, and which two the walk may use. The bytes may be in
934        // one of the two, because a reload the rewriter wrote is written into one of them, and a
935        // value that arrived that way is read by the one instruction it was written in front of and
936        // is dead after it. So the limit goes in whichever of the pair the bytes are not in, and the
937        // other one is free from the moment the limit has been worked out.
938        let operands = self.func[took].operands;
939        let bytes = self.func[operands][2].reg.phys().expect("a register the allocator settled");
940        let [first, second] = probing.scratch;
941        let (limit, flag) = if bytes == first { (second, first) } else { (first, second) };
942
943        let tail: Vec<Inst> = {
944            let mut rest = self.func.insts(block).skip_while(|&inst| inst != took);
945            rest.next();
946            rest.collect()
947        };
948        let step = self.func.create_block();
949        let onto = self.func.create_block();
950        let done = self.func.create_block();
951
952        let mov = self.opcode(self.insts.moves(class).expect("a class the target can move").mov);
953        let inst = self.two(mov, sp, limit);
954        self.func.append_inst(done, inst);
955        for inst in tail {
956            self.func.remove_inst(inst);
957            self.func.append_inst(done, inst);
958        }
959        let succs = std::mem::take(self.func.succs_mut(block));
960        *self.func.succs_mut(done) = succs;
961
962        // The subtraction the lowering wrote is what the loop is instead of, so it goes. What is
963        // left in the block it was in is where the stack pointer is walking down to.
964        self.func.remove_inst(took);
965        let inst = self.two(mov, limit, sp);
966        self.func.append_inst(block, inst);
967        let grow = self.opcode(self.insts.grow);
968        let inst = self
969            .func
970            .build_loose(grow)
971            .at(span)
972            .def(Reg::physical(limit), class)
973            .uses(Reg::physical(limit), class)
974            .uses(Reg::physical(bytes), class)
975            .finish();
976        self.func.append_inst(block, inst);
977        *self.func.succs_mut(block) = vec![BlockCall::to(step)];
978
979        let inst = self.sub(probing.probe.interval);
980        self.func.append_inst(step, inst);
981        let above = self.opcode(self.insts.above);
982        let inst = self
983            .func
984            .build_loose(above)
985            .def(Reg::physical(flag), class)
986            .uses(Reg::physical(sp), class)
987            .uses(Reg::physical(limit), class)
988            .finish();
989        self.func.append_inst(step, inst);
990        let cond = Opcode::new(
991            self.names.intern(&format!("{}{}", probing.branch.prefix, probing.branch.cond)),
992        );
993        let inst = self.func.build_loose(cond).uses(Reg::physical(flag), class).finish();
994        self.func.append_inst(step, inst);
995        // The first arm is the one taken when the condition held, and the condition is that the
996        // stack pointer is still above where the array ends, so the first arm is the page it has
997        // just stepped onto being written and another time round.
998        *self.func.succs_mut(step) = vec![BlockCall::to(onto), BlockCall::to(done)];
999
1000        let touch = self.touch(probing.probe);
1001        self.func.append_inst(onto, touch);
1002        *self.func.succs_mut(onto) = vec![BlockCall::to(step)];
1003    }
1004
1005    /// Writes the page the stack pointer is on without changing what is there.
1006    fn touch(&mut self, probe: &Probe) -> Inst {
1007        let opcode = self.opcode(probe.inst);
1008        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
1009        self.func.build_loose(opcode).imm(0).mem(Mem::at(base)).finish()
1010    }
1011
1012    /// Takes that many bytes off the stack pointer.
1013    fn sub(&mut self, bytes: u32) -> Inst {
1014        let sub = self.opcode(self.insts.sub);
1015        self.arith(sub, i64::from(bytes))
1016    }
1017
1018    /// The stack protector's check, written at the end of a block the function returns from.
1019    ///
1020    /// Gives back the block the epilogue goes in, which is a new one: the check has to be the last
1021    /// thing the old block does, and what follows it is one of two arms rather than the return.
1022    ///
1023    /// ```text
1024    ///   block that returned      reload the slot, read the word again, compare, branch
1025    ///   the arm it changed on    call the function that does not come back, and nothing after
1026    ///   the arm it did not       the epilogue, which the caller writes into what this gives back
1027    /// ```
1028    ///
1029    /// The two registers are the ones the allocator was told to hold back, so nothing here has to
1030    /// ask what is live: a scratch register holds nothing at the end of a block, because the only
1031    /// thing that writes one is a move the rewriter put in and every one of those is read by the
1032    /// instruction it was put in front of.
1033    fn check(&mut self, block: Block, frame: &Frame, protect: Protect<'_>) -> Block {
1034        let class = self.conv.int_class;
1035        let at = frame.canary().expect("a protected function has a slot for its canary");
1036        let [ours, theirs] = protect.scratch;
1037
1038        let inst = self.load(class, ours, at);
1039        self.func.append_inst(block, inst);
1040        let inst = self.read_guard(theirs, protect.guard);
1041        self.func.append_inst(block, inst);
1042        let differ = self.opcode(self.insts.differ);
1043        let inst = self
1044            .func
1045            .build_loose(differ)
1046            .def(Reg::physical(theirs), class)
1047            .uses(Reg::physical(ours), class)
1048            .uses(Reg::physical(theirs), class)
1049            .finish();
1050        self.func.append_inst(block, inst);
1051
1052        let failed = self.func.create_block();
1053        let ok = self.func.create_block();
1054        let cond = Opcode::new(
1055            self.names.intern(&format!("{}{}", protect.branch.prefix, protect.branch.cond)),
1056        );
1057        let inst = self.func.build_loose(cond).uses(Reg::physical(theirs), class).finish();
1058        self.func.append_inst(block, inst);
1059        // The first arm is the one taken when the condition held, and the condition is that the
1060        // two words differ, so the first arm is the one the canary was overwritten on.
1061        *self.func.succs_mut(block) = vec![BlockCall::to(failed), BlockCall::to(ok)];
1062
1063        let call = self.opcode(self.insts.call);
1064        let symbol = self.names.intern(protect.guard.fail);
1065        self.func.build(failed, call).symbol(symbol).finish();
1066        ok
1067    }
1068
1069    /// Reads the word the canary is a copy of into a register.
1070    ///
1071    /// The address is a constant and names no register at all, because where the block a thread
1072    /// has to itself begins is something only the machine knows and the segment register is what
1073    /// holds it.
1074    fn read_guard(&mut self, into: PhysReg, guard: &Guard) -> Inst {
1075        let class = self.conv.int_class;
1076        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
1077        self.func
1078            .build_loose(load)
1079            .def(Reg::physical(into), class)
1080            .mem(Mem::in_segment(guard.segment, guard.at))
1081            .finish()
1082    }
1083
1084    /// The instructions the epilogue is, in the order they run.
1085    ///
1086    /// The vector registers are read back while the stack pointer is still where the body left it,
1087    /// because that is what their offsets are from. Then the stack pointer goes back to the last
1088    /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
1089    /// moved and a read of the frame pointer when it did not.
1090    fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
1091        let sp = self.conv.stack_pointer;
1092        let fp = self.conv.frame_pointer;
1093        let int = self.conv.int_class;
1094        let sse = self.conv.sse_class;
1095        let push = self.conv.push;
1096        let described = !self.func.cfi.is_empty();
1097        let mut out = Vec::new();
1098        // Where the body left things, which is where every epilogue starts from.
1099        let mut below = offset(self.conv.return_address)
1100            + offset(push) * self.pushes(frame)
1101            + offset(frame.size());
1102        let from_sp = !frame.frame_pointer();
1103        for save in frame.saved_sse() {
1104            let inst = self.load(sse, save.reg, save.at);
1105            out.push(inst);
1106            if frame.realign().is_none() {
1107                self.restored(inst, sse, save.reg);
1108            }
1109        }
1110        let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
1111        if frame.frame_pointer() {
1112            // No row for either of these. The address is counted from the frame pointer here and
1113            // this is what moves the stack pointer rather than the frame pointer, so the rule that
1114            // was true before it is still true after it.
1115            //
1116            // Where the pointer is decides how far back this has to go. The early order left it one
1117            // push above the first push, so the pops start that many pushes below it. The late one
1118            // left it where the body's stack pointer was, so they start the whole frame above it,
1119            // and in both cases the distance is a constant even in a frame that grew while it ran,
1120            // which is why this is written rather than an addition to the stack pointer.
1121            let back = if frame.late() { offset(frame.size()) } else { -offset(push * pushed) };
1122            if back == 0 {
1123                let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
1124                out.push(self.two(mov, sp, fp));
1125            } else {
1126                let lea = self.opcode(self.insts.lea);
1127                out.push(self.address(lea, sp, fp, back));
1128            }
1129        } else if frame.size() > 0 {
1130            let add = self.opcode(self.insts.add);
1131            for step in self.insts.steps(frame.size()) {
1132                let inst = self.arith(add, i64::from(step));
1133                out.push(inst);
1134                below -= offset(step);
1135                self.row(inst, CfiOp::DefCfaOffset(below));
1136            }
1137        }
1138        for &reg in frame.saved_int().iter().rev() {
1139            let inst = self.pop(reg);
1140            out.push(inst);
1141            self.restored(inst, int, reg);
1142            below -= offset(push);
1143            if from_sp {
1144                self.row(inst, CfiOp::DefCfaOffset(below));
1145            }
1146        }
1147        if frame.frame_pointer() {
1148            let inst = self.pop_frame();
1149            out.push(inst);
1150            self.restored(inst, int, fp);
1151            if let Some(link) = self.record() {
1152                self.restored(inst, int, link);
1153            }
1154            // The frame pointer holds the caller's value again, so the address goes back to being
1155            // counted from the stack pointer, which by now is at the return address.
1156            let number = self.dwarf(int, sp);
1157            self.row(inst, CfiOp::DefCfa { reg: number, offset: offset(self.conv.return_address) });
1158        }
1159        let ret = self.opcode(self.insts.ret);
1160        let inst = self.func.build_loose(ret).finish();
1161        out.push(inst);
1162        // These take effect at the address just past the return, which is where the next block
1163        // begins, and the next block is body again. Popping the body's rules and pushing them
1164        // straight back leaves the stack one deep however many blocks the function returns from,
1165        // which is what makes one remembering in the prologue enough for all of them.
1166        if described {
1167            self.row(inst, CfiOp::RestoreState);
1168            self.row(inst, CfiOp::RememberState);
1169        }
1170        // And where all of it came from, which is the closing brace. Nothing here has a span of its
1171        // own: an epilogue is the frame going back the way it came and no expression in the source
1172        // asked for any of it, so without this the bytes are covered by whatever the last statement
1173        // of the body was. That is the hole the prologue used to have, at the other end, and gcc
1174        // fills it the same way it fills the other one, with the brace. A function whose body this
1175        // does not know is left alone and keeps covering those bytes with the last row before them.
1176        let closing = ending(self.func.declared);
1177        if !closing.is_dummy() {
1178            for &inst in &out {
1179                self.func.set_span(inst, closing);
1180            }
1181        }
1182        out
1183    }
1184
1185    /// How many general purpose registers the prologue put on the stack, the frame pointer
1186    /// included.
1187    fn pushes(&self, frame: &Frame) -> i32 {
1188        let saved = i32::try_from(frame.saved_int().len()).expect("a frame");
1189        saved + i32::from(frame.frame_pointer())
1190    }
1191
1192    /// One row of the unwind table, taking effect after that instruction.
1193    fn row(&mut self, inst: Inst, op: CfiOp) {
1194        self.func.cfi.push((inst, op));
1195    }
1196
1197    /// A row saying the caller's copy of that register is that far from the canonical frame
1198    /// address, which is below it and so is negative.
1199    fn saved(&mut self, inst: Inst, class: RegClass, reg: PhysReg, from_cfa: i32) {
1200        let number = self.dwarf(class, reg);
1201        self.row(inst, CfiOp::Offset { reg: number, offset: from_cfa });
1202    }
1203
1204    /// A row saying that register holds what the caller left in it again.
1205    fn restored(&mut self, inst: Inst, class: RegClass, reg: PhysReg) {
1206        let number = self.dwarf(class, reg);
1207        self.row(inst, CfiOp::Restore(number));
1208    }
1209
1210    /// What an unwind table calls that register.
1211    fn dwarf(&self, class: RegClass, reg: PhysReg) -> u16 {
1212        self.conv.dwarf(class, reg).expect("a register a frame saves is one the table can name")
1213    }
1214
1215    /// One edit as the instruction that makes it true.
1216    fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
1217        let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
1218        match (edit.mov.to, edit.mov.from) {
1219            (Place::Reg(to), Place::Reg(from)) => {
1220                let mov = self.opcode(moves.mov);
1221                self.func
1222                    .build_loose(mov)
1223                    .def(Reg::physical(to), edit.class)
1224                    .uses(Reg::physical(from), edit.class)
1225                    .finish()
1226            }
1227            (Place::Reg(to), Place::Slot(slot)) => {
1228                let at = self.slot(frame, slot);
1229                self.load(edit.class, to, at)
1230            }
1231            (Place::Slot(slot), Place::Reg(from)) => {
1232                let at = self.slot(frame, slot);
1233                self.store(edit.class, from, at)
1234            }
1235            // The allocator expands this into two moves through a register of its own, because a
1236            // machine that could do it in one is not a machine any of this is written for.
1237            (Place::Slot(_), Place::Slot(_)) => {
1238                unreachable!("a move from one stack slot straight into another")
1239            }
1240        }
1241    }
1242
1243    /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
1244    ///
1245    /// The edits at one place are in the order they have to be made in, so each one goes behind
1246    /// the last, and the first of them is what the place itself means.
1247    fn put(&mut self, cursors: &mut HashMap<At, Inst>, at: At, inst: Inst) {
1248        if let Some(cursor) = cursors.get_mut(&at) {
1249            self.func.insert_after(*cursor, inst);
1250            *cursor = inst;
1251            return;
1252        }
1253        match at {
1254            At::Before(before) => self.func.insert_before(before, inst),
1255            At::After(after) => self.func.insert_after(after, inst),
1256            At::StartOf(block) => self.func.prepend_inst(block, inst),
1257            // Behind everything in the block. A block the allocator puts an edge's moves at the
1258            // end of is one with a single edge out of it, and an edge like that is not an
1259            // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
1260            // So the last instruction is an ordinary one, which may still be waiting on moves of
1261            // its own that have to be made before the edge's are.
1262            At::EndOf(block) => self.func.append_inst(block, inst),
1263        }
1264        cursors.insert(at, inst);
1265    }
1266
1267    /// Where a spill slot is, from the stack pointer in the body of the function.
1268    fn slot(&self, frame: &Frame, slot: u32) -> i32 {
1269        frame.slot(slot).expect("a slot the frame was worked out from")
1270    }
1271
1272    /// Reads a register out of the frame.
1273    fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
1274        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
1275        let base = Operand::read(Reg::physical(self.base), self.conv.int_class);
1276        self.func
1277            .build_loose(load)
1278            .def(Reg::physical(reg), class)
1279            .mem(Mem::at(base).plus(at))
1280            .finish()
1281    }
1282
1283    /// Writes a register into the frame.
1284    fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
1285        let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
1286        let base = Operand::read(Reg::physical(self.base), self.conv.int_class);
1287        self.func
1288            .build_loose(store)
1289            .uses(Reg::physical(reg), class)
1290            .mem(Mem::at(base).plus(at))
1291            .finish()
1292    }
1293
1294    /// Puts a general purpose register on the stack.
1295    fn push(&mut self, reg: PhysReg) -> Inst {
1296        let push = self.opcode(self.insts.push);
1297        self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
1298    }
1299
1300    /// Takes a general purpose register back off the stack.
1301    fn pop(&mut self, reg: PhysReg) -> Inst {
1302        let pop = self.opcode(self.insts.pop);
1303        self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
1304    }
1305
1306    /// The register that goes on the stack with the frame pointer, which is the one a call leaves
1307    /// the return address in on a machine that pushes the two together, and nothing anywhere else.
1308    fn record(&self) -> Option<PhysReg> {
1309        self.conv.link.filter(|_| self.insts.pair.is_some())
1310    }
1311
1312    /// Puts the caller's frame pointer on the stack, together with the return address on a machine
1313    /// that keeps it in a register. The frame pointer goes at the lower address, so the pointer set
1314    /// to it straight after names the caller's copy and the return address is the word above.
1315    fn push_frame(&mut self) -> Inst {
1316        let fp = self.conv.frame_pointer;
1317        let (Some(link), Some(pair)) = (self.record(), self.insts.pair) else {
1318            return self.push(fp);
1319        };
1320        let push = self.opcode(pair.push);
1321        let class = self.conv.int_class;
1322        self.func
1323            .build_loose(push)
1324            .uses(Reg::physical(fp), class)
1325            .uses(Reg::physical(link), class)
1326            .finish()
1327    }
1328
1329    /// Takes back what [`Self::push_frame`] put on the stack.
1330    fn pop_frame(&mut self) -> Inst {
1331        let fp = self.conv.frame_pointer;
1332        let (Some(link), Some(pair)) = (self.record(), self.insts.pair) else {
1333            return self.pop(fp);
1334        };
1335        let pop = self.opcode(pair.pop);
1336        let class = self.conv.int_class;
1337        self.func
1338            .build_loose(pop)
1339            .def(Reg::physical(fp), class)
1340            .def(Reg::physical(link), class)
1341            .finish()
1342    }
1343
1344    /// One general purpose register written with another.
1345    fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
1346        let class = self.conv.int_class;
1347        self.func
1348            .build_loose(opcode)
1349            .def(Reg::physical(to), class)
1350            .uses(Reg::physical(from), class)
1351            .finish()
1352    }
1353
1354    /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
1355    fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
1356        let class = self.conv.int_class;
1357        let sp = Reg::physical(self.conv.stack_pointer);
1358        self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
1359    }
1360
1361    /// One register written with an address rather than with what is at it.
1362    fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
1363        let class = self.conv.int_class;
1364        let base = Operand::read(Reg::physical(base), class);
1365        self.func
1366            .build_loose(opcode)
1367            .def(Reg::physical(to), class)
1368            .mem(Mem::at(base).plus(disp))
1369            .finish()
1370    }
1371
1372    /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
1373    /// then the name the target gave.
1374    fn opcode(&mut self, name: &str) -> Opcode {
1375        Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
1376    }
1377}
1378
1379/// A distance in a frame, as the signed number every offset is.
1380fn offset(bytes: u32) -> i32 {
1381    i32::try_from(bytes).expect("a frame under two gigabytes")
1382}
1383
1384/// The last character of a span, which for the span of a function body is its closing brace.
1385///
1386/// A span runs from the first byte to one past the last, so the brace is the byte before the end
1387/// rather than the end. [`Span::DUMMY`] for a function that came from no C source, which is what
1388/// the tests and the IR parser build, and for the empty span that cannot have a last character.
1389fn ending(body: Span) -> Span {
1390    if body.is_dummy() || body.hi <= body.lo {
1391        return Span::DUMMY;
1392    }
1393    Span::new(body.hi - 1, body.hi)
1394}
1395
1396#[cfg(test)]
1397mod tests {
1398    use rucc_base::Interner;
1399    use rucc_mir::{BlockCall, print_func};
1400    use rucc_regalloc::assign::Env;
1401    use rucc_target::x86_64::{
1402        BRANCH, FRAME, GPR, PROBE, R10, R11, RAX, REGS, SYSV, WIN64, XMM, xmm,
1403    };
1404
1405    use super::*;
1406    use crate::frame::{Layout, Local};
1407
1408    /// The closing brace of a body is the last character of its span and not the end of it, since a
1409    /// span runs to one past what it covers. A function that came from no source has no brace and
1410    /// asks for no row, which is what keeps the epilogue of one the IR parser built covered by the
1411    /// row before it rather than by a position in a file that is not there.
1412    #[test]
1413    fn the_end_of_a_body_is_its_closing_brace_and_not_one_past_it() {
1414        assert_eq!(ending(Span::new(10, 40)), Span::new(39, 40));
1415        assert_eq!(ending(Span::DUMMY), Span::DUMMY);
1416        assert_eq!(ending(Span::new(7, 7)), Span::DUMMY);
1417    }
1418
1419    /// An environment offering that many of the convention's registers, with everything after
1420    /// them held back as scratch.
1421    fn env(conv: &CallRegs, count: usize) -> Env {
1422        Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
1423    }
1424
1425    /// A function of that many values, every one written before any is read, allocated with that
1426    /// many registers to hand out. The same shape the frame layout's own tests are written
1427    /// against, so that a frame here is one that has already been checked there.
1428    fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
1429        let mut names = Interner::new();
1430        let mut func = Func::new(names.intern("f"));
1431        let opcode = Opcode::new(names.intern("x64.nop"));
1432        let block = func.create_block();
1433        let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
1434        for &reg in &regs {
1435            func.build(block, opcode).def(reg, GPR).finish();
1436        }
1437        for &reg in &regs {
1438            func.build(block, opcode).uses(reg, GPR).finish();
1439        }
1440        let allocation = rucc_regalloc::run(&mut func, &env(conv, count), "test", true);
1441        (func, allocation, names)
1442    }
1443
1444    /// The function with its frame written into it, as the lines a dump would show.
1445    fn written(
1446        func: &mut Func,
1447        allocation: &Allocation,
1448        layout: &Layout<'_>,
1449        names: &mut Interner,
1450    ) -> Vec<String> {
1451        with_protector(func, allocation, layout, None, names)
1452    }
1453
1454    /// The same, for a function the caller has decided is protected or is not.
1455    fn with_protector(
1456        func: &mut Func,
1457        allocation: &Allocation,
1458        layout: &Layout<'_>,
1459        protect: Option<Protect<'_>>,
1460        names: &mut Interner,
1461    ) -> Vec<String> {
1462        let convention = Convention { protect, ..Convention::new(layout.conv, &FRAME) };
1463        under(func, allocation, layout, &Stack::default(), convention, names)
1464    }
1465
1466    /// The same, for a function whose frame the caller has decided is taken a page at a time.
1467    fn with_probing(
1468        func: &mut Func,
1469        allocation: &Allocation,
1470        layout: &Layout<'_>,
1471        probe: Option<Probing<'_>>,
1472        names: &mut Interner,
1473    ) -> Vec<String> {
1474        let convention = Convention { probe, ..Convention::new(layout.conv, &FRAME) };
1475        under(func, allocation, layout, &Stack::default(), convention, names)
1476    }
1477
1478    /// A function whose one block takes a run of bytes off the stack pointer, which is what the
1479    /// lowering writes for a variable length array, with the count already in the register given.
1480    fn growing(count: PhysReg) -> (Func, Allocation, Interner, Stack) {
1481        let mut names = Interner::new();
1482        let mut func = Func::new(names.intern("f"));
1483        let block = func.create_block();
1484        let sp = Reg::physical(SYSV.stack_pointer);
1485        let grow = Opcode::new(names.intern("x64.sub_rr_64"));
1486        let took = func
1487            .build(block, grow)
1488            .def(sp, GPR)
1489            .uses(sp, GPR)
1490            .uses(Reg::physical(count), GPR)
1491            .finish();
1492        let nop = Opcode::new(names.intern("x64.nop"));
1493        func.build(block, nop).finish();
1494        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test", true);
1495        (func, allocation, names, Stack { grown: vec![took], ..Stack::default() })
1496    }
1497
1498    /// The function with its frame written into it under that convention.
1499    fn under(
1500        func: &mut Func,
1501        allocation: &Allocation,
1502        layout: &Layout<'_>,
1503        stack: &Stack,
1504        convention: Convention<'_>,
1505        names: &mut Interner,
1506    ) -> Vec<String> {
1507        let frame = Frame::of(func, allocation, layout);
1508        finish(func, allocation, &frame, stack, convention, names);
1509        print_func(func, names, &REGS)
1510            .lines()
1511            .filter(|line| !line.is_empty())
1512            .map(|line| line.trim().to_string())
1513            .collect()
1514    }
1515
1516    /// Just the lines the frame put in, which is every line that is not the function it was
1517    /// given and not the shape of the dump around it.
1518    fn added(lines: &[String]) -> Vec<&str> {
1519        lines
1520            .iter()
1521            .map(String::as_str)
1522            .filter(|line| !line.contains("x64.nop"))
1523            .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
1524            .collect()
1525    }
1526
1527    #[test]
1528    fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
1529        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1530        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1531
1532        // Two values and four registers, so nothing is spilled, nothing is saved and the stack
1533        // pointer never moves. A prologue of nothing is the right prologue for that.
1534        assert_eq!(added(&lines), ["x64.ret"]);
1535    }
1536
1537    /// The bytes that give a frame back are filed under the closing brace, which is where a
1538    /// debugger says a function ends and which nothing in an epilogue could say for itself.
1539    #[test]
1540    fn an_epilogue_is_filed_under_the_closing_brace_of_the_body() {
1541        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1542        func.declared = Span::new(100, 140);
1543        let base = Layout::new(&SYSV, REGS);
1544        let layout = Layout { red_zone: false, ..base };
1545        written(&mut func, &allocation, &layout, &mut names);
1546
1547        // Everything from the first instruction of the epilogue to the return, and nothing above
1548        // it: the body's own instructions keep the spans they arrived with, which here is none.
1549        let ends: Vec<Span> = func
1550            .blocks()
1551            .flat_map(|block| func.insts(block).collect::<Vec<_>>())
1552            .map(|inst| func.span(inst))
1553            .filter(|span| !span.is_dummy())
1554            .collect();
1555        assert!(!ends.is_empty(), "an epilogue was written");
1556        assert!(ends.iter().all(|&span| span == Span::new(139, 140)), "{ends:?}");
1557    }
1558
1559    #[test]
1560    fn a_spill_is_a_store_and_a_reload_is_a_load() {
1561        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1562        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1563
1564        // Two registers for four values, so two of them go to the stack. The store goes behind the
1565        // instruction that wrote the value and the load in front of the one that wants it, both at
1566        // the offsets the frame gave, which are below the stack pointer because a small leaf
1567        // function is entitled to the red zone.
1568        assert_eq!(
1569            lines,
1570            [
1571                "mfunc @f {",
1572                "block0:",
1573                "$rax = x64.nop",
1574                "$rcx = x64.nop",
1575                "$rdx = x64.nop",
1576                "x64.mov_mr_64 $rdx, [$rsp - 16]",
1577                "$rdx = x64.nop",
1578                "x64.mov_mr_64 $rdx, [$rsp - 8]",
1579                "x64.nop $rax",
1580                "x64.nop $rcx",
1581                "$rdx = x64.mov_rm_64 [$rsp - 16]",
1582                "x64.nop $rdx",
1583                "$rdx = x64.mov_rm_64 [$rsp - 8]",
1584                "x64.nop $rdx",
1585                "x64.ret",
1586                "}",
1587            ]
1588        );
1589    }
1590
1591    #[test]
1592    fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
1593        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1594        let base = Layout::new(&SYSV, REGS);
1595        let layout = Layout { red_zone: false, ..base };
1596        let lines = written(&mut func, &allocation, &layout, &mut names);
1597
1598        // The same function told it may not use the red zone takes sixteen bytes instead, and
1599        // every offset moves above the stack pointer to match.
1600        assert_eq!(
1601            added(&lines),
1602            [
1603                "$rsp = x64.sub_ri_64 $rsp, 16",
1604                "x64.mov_mr_64 $rdx, [$rsp]",
1605                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1606                "$rdx = x64.mov_rm_64 [$rsp]",
1607                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1608                "$rsp = x64.add_ri_64 $rsp, 16",
1609                "x64.ret",
1610            ]
1611        );
1612    }
1613
1614    #[test]
1615    fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
1616        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
1617        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1618
1619        // Four registers a call leaves alone, pushed in the convention's order and popped in the
1620        // other one, which is the only order that gets each of them its own value back.
1621        assert_eq!(
1622            added(&lines),
1623            [
1624                "x64.push_64 $rbx",
1625                "x64.push_64 $r12",
1626                "x64.push_64 $r13",
1627                "x64.push_64 $r14",
1628                "$r14 = x64.pop_64",
1629                "$r13 = x64.pop_64",
1630                "$r12 = x64.pop_64",
1631                "$rbx = x64.pop_64",
1632                "x64.ret",
1633            ]
1634        );
1635    }
1636
1637    #[test]
1638    fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
1639        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1640        let base = Layout::new(&SYSV, REGS);
1641        let layout = Layout { frame_pointer: true, red_zone: false, ..base };
1642        let lines = written(&mut func, &allocation, &layout, &mut names);
1643
1644        // The frame pointer is saved before anything else and points at where it was saved, so the
1645        // epilogue reaches the stack pointer through it rather than by counting the frame back.
1646        assert_eq!(
1647            added(&lines),
1648            [
1649                "x64.push_64 $rbp",
1650                "$rbp = x64.mov_rr_64 $rsp",
1651                "$rsp = x64.sub_ri_64 $rsp, 16",
1652                "x64.mov_mr_64 $rdx, [$rsp]",
1653                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1654                "$rdx = x64.mov_rm_64 [$rsp]",
1655                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1656                "$rsp = x64.mov_rr_64 $rbp",
1657                "$rbp = x64.pop_64",
1658                "x64.ret",
1659            ]
1660        );
1661    }
1662
1663    #[test]
1664    fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
1665        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
1666        let locals = [Local { size: 64, align: 32 }];
1667        let base = Layout::new(&SYSV, REGS);
1668        let layout = Layout { locals: &locals, ..base };
1669        let lines = written(&mut func, &allocation, &layout, &mut names);
1670
1671        // Forcing the alignment throws away how far the stack pointer had moved, so the registers
1672        // are pushed before it happens and the epilogue counts back from the frame pointer to find
1673        // them. The frame pointer is required here whatever the flags said.
1674        assert_eq!(
1675            added(&lines),
1676            [
1677                "x64.push_64 $rbp",
1678                "$rbp = x64.mov_rr_64 $rsp",
1679                "x64.push_64 $rbx",
1680                "x64.push_64 $r12",
1681                "x64.push_64 $r13",
1682                "x64.push_64 $r14",
1683                "$rsp = x64.and_ri_64 $rsp, -32",
1684                "$rsp = x64.sub_ri_64 $rsp, 64",
1685                "$rsp = x64.lea_64 [$rbp - 32]",
1686                "$r14 = x64.pop_64",
1687                "$r13 = x64.pop_64",
1688                "$r12 = x64.pop_64",
1689                "$rbx = x64.pop_64",
1690                "$rbp = x64.pop_64",
1691                "x64.ret",
1692            ]
1693        );
1694    }
1695
1696    #[test]
1697    fn every_block_the_function_returns_from_gets_an_epilogue() {
1698        let mut names = Interner::new();
1699        let mut func = Func::new(names.intern("f"));
1700        let opcode = Opcode::new(names.intern("x64.nop"));
1701        let head = func.create_block();
1702        let left = func.create_block();
1703        let right = func.create_block();
1704        func.build(head, opcode).finish();
1705        *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
1706        func.build(left, opcode).finish();
1707        func.build(right, opcode).finish();
1708        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test", true);
1709        let base = Layout::new(&SYSV, REGS);
1710        let layout = Layout { leaf: false, ..base };
1711        let lines = written(&mut func, &allocation, &layout, &mut names);
1712
1713        // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
1714        // because a block with an edge out of it is not a block anything returns from.
1715        assert_eq!(
1716            lines,
1717            [
1718                "mfunc @f {",
1719                "block0:",
1720                "$rsp = x64.sub_ri_64 $rsp, 8",
1721                "x64.nop block1, block2",
1722                "block1:",
1723                "x64.nop",
1724                "$rsp = x64.add_ri_64 $rsp, 8",
1725                "x64.ret",
1726                "block2:",
1727                "x64.nop",
1728                "$rsp = x64.add_ri_64 $rsp, 8",
1729                "x64.ret",
1730                "}",
1731            ]
1732        );
1733    }
1734
1735    #[test]
1736    fn a_protected_function_writes_the_canary_last_and_checks_it_before_it_returns() {
1737        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1738        let base = Layout::new(&SYSV, REGS);
1739        let layout = Layout { leaf: false, protect: true, ..base };
1740        let guard = SYSV.guard.as_ref().expect("this convention has somewhere to keep the word");
1741        // The two the real pipeline holds back, which are held back in the environment above too:
1742        // it hands out the first two of the convention's order and keeps everything after them.
1743        let protect = Protect { guard, branch: &BRANCH, scratch: [R10, R11] };
1744        let lines = with_protector(&mut func, &allocation, &layout, Some(protect), &mut names);
1745
1746        // The read of the word and the store into the slot come after the stack pointer has moved,
1747        // because there is no slot to store into until it has. The check is the last thing the
1748        // block that returned does and the epilogue is on the arm the canary was unchanged on, so
1749        // a function whose canary changed never gives its frame back and never returns.
1750        assert_eq!(
1751            added(&lines),
1752            [
1753                "$rsp = x64.sub_ri_64 $rsp, 24",
1754                "$r10 = x64.mov_rm_64 [fs:40]",
1755                "x64.mov_mr_64 $r10, [$rsp + 16]",
1756                "x64.mov_mr_64 $rdx, [$rsp]",
1757                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1758                "$rdx = x64.mov_rm_64 [$rsp]",
1759                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1760                "$r10 = x64.mov_rm_64 [$rsp + 16]",
1761                "$r11 = x64.mov_rm_64 [fs:40]",
1762                "$r11 = x64.cmp_set_ne_64 $r10, $r11",
1763                "x64.br_cond_8 $r11, block1, block2",
1764                "x64.call @__stack_chk_fail",
1765                "$rsp = x64.add_ri_64 $rsp, 24",
1766                "x64.ret",
1767            ]
1768        );
1769    }
1770
1771    #[test]
1772    fn a_frame_that_fits_in_one_page_is_taken_in_one_subtraction_even_when_pages_are_touched() {
1773        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1774        let locals = [Local { size: 4088, align: 16 }];
1775        let base = Layout::new(&SYSV, REGS);
1776        let layout = Layout { leaf: false, locals: &locals, ..base };
1777        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1778        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1779
1780        // A frame of one page cannot step over the page below it, because the far end of it is the
1781        // near end of that page and anything written there is written to a page that is there. So
1782        // the flag costs such a function nothing, which is most functions.
1783        assert_eq!(
1784            added(&lines),
1785            ["$rsp = x64.sub_ri_64 $rsp, 4088", "$rsp = x64.add_ri_64 $rsp, 4088", "x64.ret",]
1786        );
1787    }
1788
1789    #[test]
1790    fn a_probing_prologue_touches_every_page_of_a_frame_a_few_pages_deep() {
1791        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1792        let locals = [Local { size: 9000, align: 16 }];
1793        let base = Layout::new(&SYSV, REGS);
1794        let layout = Layout { leaf: false, locals: &locals, ..base };
1795        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1796        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1797
1798        // A page of the stack pointer's own, then the touch that says the page is there, and only
1799        // then the next one, which is the whole of the defence: nothing here ever moves the stack
1800        // pointer further than one page without writing where it landed. The last subtraction is
1801        // the remainder and is smaller than a page, so it needs no touch of its own, and it exists
1802        // in every frame because the count of pages is taken off one less than the size.
1803        assert_eq!(
1804            added(&lines),
1805            [
1806                "$rsp = x64.sub_ri_64 $rsp, 4096",
1807                "x64.or_mi_8 [$rsp], 0",
1808                "$rsp = x64.sub_ri_64 $rsp, 4096",
1809                "x64.or_mi_8 [$rsp], 0",
1810                "$rsp = x64.sub_ri_64 $rsp, 808",
1811                "$rsp = x64.add_ri_64 $rsp, 9000",
1812                "x64.ret",
1813            ]
1814        );
1815    }
1816
1817    #[test]
1818    fn a_variable_length_array_walks_its_pages_where_the_declaration_stands() {
1819        let (mut func, allocation, mut names, stack) = growing(RAX);
1820        let base = Layout::new(&SYSV, REGS);
1821        let layout = Layout { leaf: false, grows: true, ..base };
1822        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1823        let convention = Convention { probe: Some(probing), ..Convention::new(&SYSV, &FRAME) };
1824        let lines = under(&mut func, &allocation, &layout, &stack, convention, &mut names);
1825
1826        // The whole listing, because what the walk is cannot be read off the instructions alone.
1827        // The one subtraction the lowering wrote is gone and four blocks stand where its block was:
1828        // where the stack pointer is going, the step, the page the step landed on, and the rest of
1829        // what the block was doing with the stack pointer put back where it was going.
1830        assert_eq!(
1831            lines,
1832            [
1833                "mfunc @f {",
1834                "block0:",
1835                "x64.push_64 $rbp",
1836                "$rbp = x64.mov_rr_64 $rsp",
1837                "$r10 = x64.mov_rr_64 $rsp",
1838                "$r10 = x64.sub_rr_64 $r10, $rax, block1",
1839                "block1:",
1840                "$rsp = x64.sub_ri_64 $rsp, 4096",
1841                "$r11 = x64.cmp_set_a_64 $rsp, $r10",
1842                "x64.br_cond_8 $r11, block2, block3",
1843                "block2:",
1844                "x64.or_mi_8 [$rsp], 0, block1",
1845                "block3:",
1846                "$rsp = x64.mov_rr_64 $r10",
1847                "x64.nop",
1848                "$rsp = x64.mov_rr_64 $rbp",
1849                "$rbp = x64.pop_64",
1850                "x64.ret",
1851                "}",
1852            ]
1853        );
1854    }
1855
1856    #[test]
1857    fn the_walk_keeps_the_register_the_count_arrived_in() {
1858        let (mut func, allocation, mut names, stack) = growing(R10);
1859        let base = Layout::new(&SYSV, REGS);
1860        let layout = Layout { leaf: false, grows: true, ..base };
1861        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1862        let convention = Convention { probe: Some(probing), ..Convention::new(&SYSV, &FRAME) };
1863        let lines = under(&mut func, &allocation, &layout, &stack, convention, &mut names);
1864
1865        // The count is in the first of the two registers the walk was given, which is where a
1866        // reload the rewriter wrote would have put it, so the limit goes in the other one and the
1867        // comparison writes the first one back only once the count has been read for the last time.
1868        let added = added(&lines);
1869        assert!(added.contains(&"$r11 = x64.mov_rr_64 $rsp"), "{added:?}");
1870        assert!(added.contains(&"$r11 = x64.sub_rr_64 $r11, $r10, block1"), "{added:?}");
1871        assert!(added.contains(&"$r10 = x64.cmp_set_a_64 $rsp, $r11"), "{added:?}");
1872    }
1873
1874    #[test]
1875    fn a_variable_length_array_takes_its_bytes_in_one_subtraction_when_nothing_asked() {
1876        let (mut func, allocation, mut names, stack) = growing(RAX);
1877        let base = Layout::new(&SYSV, REGS);
1878        let layout = Layout { leaf: false, grows: true, ..base };
1879        let convention = Convention::new(&SYSV, &FRAME);
1880        let lines = under(&mut func, &allocation, &layout, &stack, convention, &mut names);
1881
1882        // The instruction the lowering wrote, where it wrote it, and one block still.
1883        assert!(lines.contains(&"$rsp = x64.sub_rr_64 $rsp, $rax".to_owned()), "{lines:?}");
1884        assert_eq!(lines.iter().filter(|line| line.starts_with("block")).count(), 1, "{lines:?}");
1885    }
1886
1887    #[test]
1888    fn a_probing_prologue_deeper_than_that_walks_the_pages_in_a_loop() {
1889        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1890        let locals = [Local { size: 100_000, align: 16 }];
1891        let base = Layout::new(&SYSV, REGS);
1892        let layout = Layout { leaf: false, locals: &locals, ..base };
1893        let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1894        let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1895
1896        // Twenty-four pages, which is more than a straight line is worth, so the prologue works out
1897        // where it is going first and then walks there. The whole listing rather than the added
1898        // lines, because what matters as much as the instructions is that the two blocks the walk
1899        // is made of come in front of the block the function began with: the body the allocator
1900        // filled is block2 here and it was block0 before this ran.
1901        assert_eq!(
1902            lines,
1903            [
1904                "mfunc @f {",
1905                "block0:",
1906                "$r10 = x64.lea_64 [$rsp - 98304], block1",
1907                "block1:",
1908                "$rsp = x64.sub_ri_64 $rsp, 4096",
1909                "x64.or_mi_8 [$rsp], 0",
1910                "$r11 = x64.cmp_set_ne_64 $rsp, $r10",
1911                "x64.br_cond_8 $r11, block1, block2",
1912                "block2:",
1913                "$rsp = x64.sub_ri_64 $rsp, 1704",
1914                "$rax = x64.nop",
1915                "$rcx = x64.nop",
1916                "x64.nop $rax",
1917                "x64.nop $rcx",
1918                "$rsp = x64.add_ri_64 $rsp, 100008",
1919                "x64.ret",
1920                "}",
1921            ]
1922        );
1923    }
1924
1925    #[test]
1926    fn a_large_frame_on_a_platform_with_a_routine_for_its_pages_calls_the_routine() {
1927        let (mut func, allocation, mut names) = pressure(&WIN64, 2, 4);
1928        let locals = [Local { size: 100_000, align: 16 }];
1929        let base = Layout::new(&WIN64, REGS);
1930        let layout = Layout { leaf: false, locals: &locals, ..base };
1931        let lines = written(&mut func, &allocation, &layout, &mut names);
1932
1933        // Nothing asked for this on the command line, which is the point: Windows commits a stack
1934        // by having the pages touched in order, so a frame this size has to reach them whatever the
1935        // flags said. The size goes in the register the platform names, the routine touches every
1936        // page down to there, and the frame is taken afterwards, because the routine comes back
1937        // having moved nothing. What the epilogue gives back is what the register was given, which
1938        // is the one thing worth tying together here.
1939        let added = added(&lines);
1940        assert_eq!(added.len(), 5, "{added:?}");
1941        let size = added[0].strip_prefix("$rax = x64.mov_ri_64 ").expect("a size in a register");
1942        assert_eq!(added[1], "x64.call @__chkstk");
1943        assert_eq!(added[2], "$rsp = x64.sub_rr_64 $rsp, $rax");
1944        assert_eq!(added[3], format!("$rsp = x64.add_ri_64 $rsp, {size}"));
1945        assert_eq!(added[4], "x64.ret");
1946    }
1947
1948    #[test]
1949    fn a_frame_of_one_page_calls_nothing_on_that_platform_either() {
1950        let (mut func, allocation, mut names) = pressure(&WIN64, 2, 4);
1951        let locals = [Local { size: 4000, align: 16 }];
1952        let base = Layout::new(&WIN64, REGS);
1953        let layout = Layout { leaf: false, locals: &locals, ..base };
1954        let lines = written(&mut func, &allocation, &layout, &mut names);
1955
1956        // The same reason a frame of one page is taken in one subtraction under the flag. The far
1957        // end of such a frame is inside the page below the stack pointer, and touching that page is
1958        // what the function does on its way to using the frame at all, so there is nothing for a
1959        // routine to do and a call to it would be a call in every function that declares an array.
1960        let added = added(&lines);
1961        assert!(added.iter().all(|line| !line.contains("chkstk")), "{added:?}");
1962        assert_eq!(added.len(), 3, "{added:?}");
1963    }
1964
1965    #[test]
1966    fn a_windows_prologue_points_its_frame_pointer_at_the_frame_once_the_frame_is_whole() {
1967        let (mut func, allocation, mut names) = pressure(&WIN64, 4, 2);
1968        let base = Layout::new(&WIN64, REGS);
1969        let layout = Layout { frame_pointer: true, ..base };
1970        let lines = written(&mut func, &allocation, &layout, &mut names);
1971
1972        // The other order, which is what every other platform here writes, has no unwind record on
1973        // this one: the record counts its slots from where the stack pointer ends the prologue and
1974        // gets there by taking a constant off the frame pointer, so a register pushed after the
1975        // pointer was established sits below the place the record counts from. Pushing first and
1976        // pointing last is the order that has a record, and it leaves the pointer holding a copy of
1977        // the stack pointer, so the spills stay where they were and the epilogue counts the frame
1978        // back off the pointer rather than moving the pointer into the stack pointer.
1979        assert_eq!(
1980            added(&lines),
1981            [
1982                "x64.push_64 $rbp",
1983                "$rsp = x64.sub_ri_64 $rsp, 16",
1984                "$rbp = x64.mov_rr_64 $rsp",
1985                "x64.mov_mr_64 $rdx, [$rsp]",
1986                "x64.mov_mr_64 $rdx, [$rsp + 8]",
1987                "$rdx = x64.mov_rm_64 [$rsp]",
1988                "$rdx = x64.mov_rm_64 [$rsp + 8]",
1989                "$rsp = x64.lea_64 [$rbp + 16]",
1990                "$rbp = x64.pop_64",
1991                "x64.ret",
1992            ]
1993        );
1994    }
1995
1996    #[test]
1997    fn a_windows_prologue_that_saves_registers_too_pushes_all_of_them_before_the_frame() {
1998        let (mut func, allocation, mut names) = pressure(&WIN64, 9, 8);
1999        let base = Layout::new(&WIN64, REGS);
2000        let layout = Layout { leaf: false, frame_pointer: true, ..base };
2001        let lines = written(&mut func, &allocation, &layout, &mut names);
2002
2003        // The shape that made the order necessary. All three pushes are above the frame, so every
2004        // one of them has a row the record can write, and the pointer is the last thing the
2005        // prologue does. Forty eight bytes is the thirty two every Windows caller reserves below a
2006        // call, eight for the one value that did not fit in a register, and eight that put the
2007        // stack pointer back where a call wants it given three pushes and the return address.
2008        assert_eq!(
2009            added(&lines),
2010            [
2011                "x64.push_64 $rbp",
2012                "x64.push_64 $rbx",
2013                "x64.push_64 $rsi",
2014                "$rsp = x64.sub_ri_64 $rsp, 48",
2015                "$rbp = x64.mov_rr_64 $rsp",
2016                "x64.mov_mr_64 $rsi, [$rsp + 32]",
2017                "$rsi = x64.mov_rm_64 [$rsp + 32]",
2018                "$rsp = x64.lea_64 [$rbp + 48]",
2019                "$rsi = x64.pop_64",
2020                "$rbx = x64.pop_64",
2021                "$rbp = x64.pop_64",
2022                "x64.ret",
2023            ]
2024        );
2025    }
2026
2027    #[test]
2028    fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
2029        let mut names = Interner::new();
2030        let mut func = Func::new(names.intern("f"));
2031        let opcode = Opcode::new(names.intern("x64.nop"));
2032        let block = func.create_block();
2033        // An instruction that writes one of the vector registers Windows preserves, which is what
2034        // a rule for something that has to use it produces.
2035        func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
2036        let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4), "test", true);
2037        let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
2038
2039        // No machine here pushes a vector register, so it is stored into the frame rather than
2040        // pushed, and the frame has to be taken before there is anywhere to put it.
2041        assert_eq!(
2042            added(&lines),
2043            [
2044                "$rsp = x64.sub_ri_64 $rsp, 24",
2045                "x64.movaps_mr $xmm6, [$rsp]",
2046                "$xmm6 = x64.movaps_rm [$rsp]",
2047                "$rsp = x64.add_ri_64 $rsp, 24",
2048                "x64.ret",
2049            ]
2050        );
2051    }
2052}