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 ® 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 ® 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 ® in ®s {
1245 func.build(block, opcode).def(reg, GPR).finish();
1246 }
1247 for ® in ®s {
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, ®S)
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}