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