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