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//! The loads that read the arguments the caller passed on the stack are waiting on the same number
25//! and on one more. Those bytes are the caller's rather than this function's, and a frame that had
26//! to force its own alignment cannot say how far away the caller's stack pointer was, so it reaches
27//! back through the frame pointer instead. Which register a load reads through is therefore settled
28//! here too, and it is the only base register in a finished function that was not settled by
29//! whoever wrote the instruction.
30//!
31//! After this the function is one an encoder can read: every register is physical, every offset
32//! into the frame is a constant, and the stack pointer is where the convention says it should be
33//! at every instruction that could look.
34//!
35//! # Why the moves go in first
36//!
37//! Every offset the frame reports is from the stack pointer as it stands in the body of the
38//! function. A spill written before the prologue exists would be written in front of the
39//! instruction it belongs to and behind nothing, which is where the prologue then goes, so the
40//! prologue ends up in front of it and the offsets stay true. Writing them the other way round
41//! would put the first reload above the instruction that takes the frame, and it would read from
42//! an address that is one frame out.
43//!
44//! # Where a return is
45//!
46//! A block that goes nowhere is a block the function leaves from. Mostly that is a return, and
47//! the other kind is a block ending in `unreachable`, which is a point the front end says control
48//! does not arrive at and which the lowering writes no instruction for. Both want the same thing
49//! here. A return wants the epilogue because that is what a return is once the frame is known,
50//! and an unreachable block wants it because the alternative is a function whose last instruction
51//! falls into whatever the assembler put after it, which is worse than an epilogue nothing runs.
52//! So the epilogue goes at the end of every block with an empty successor list, and there may be
53//! several, because nothing here insists a function has one exit.
54//!
55//! # What is target-specific here
56//!
57//! The names, and only the names. Which instruction pushes a register and which one moves the
58//! stack pointer is [`rucc_target::FrameInsts`], which the target says and this reads, so what
59//! is written below is the shape of a prologue rather than any particular machine's. That is
60//! `spec/10-backend.md` section 10.8 as it applies to the one pass that would otherwise be full
61//! of `x64.` by hand.
62
63use rucc_base::Interner;
64use rucc_mir::{Block, BlockCall, CfiOp, Func, Inst, Mem, Opcode, Operand, Patch, Reg};
65use rucc_regalloc::Allocation;
66use rucc_regalloc::assign::Place;
67use rucc_regalloc::rewrite::{At, Edit};
68use rucc_target::{BranchInsts, CallRegs, FrameInsts, Guard, PhysReg, Probe, RegClass};
69
70use crate::frame::Frame;
71use crate::lower::Stack;
72
73/// What the stack protector's check needs beyond the frame, in a function that has one.
74///
75/// Three things that come from three places, which is why they arrive together rather than being
76/// looked up here. Where the word the canary is copied from lives is a fact about the runtime the
77/// code is linked against. What a branch on a register is is a fact about the machine. And the two
78/// registers are neither: they are the ones the allocator was told to hold back, which is a
79/// decision about the allocator, and they are free at a return for exactly that reason.
80#[derive(Debug, Clone, Copy)]
81pub struct Protect<'a> {
82 /// Where the word the canary is a copy of lives, and what to call when the copy has changed.
83 pub guard: &'a Guard,
84 /// What a branch on a register is, which is what the check ends its block with.
85 pub branch: &'a BranchInsts,
86 /// The two registers the check may use, which are two the allocator never handed out.
87 pub scratch: [PhysReg; 2],
88}
89
90/// What a prologue that takes its frame a page at a time needs beyond the frame.
91///
92/// What `-fstack-clash-protection` asks for, and the same three kinds of thing [`Protect`] is:
93/// one fact about the platform, one about the machine, and two registers that are neither. See
94/// [`rucc_target::Probe`] for what the sequence is defending against.
95#[derive(Debug, Clone, Copy)]
96pub struct Probing<'a> {
97 /// What touches a page and how far apart the pages are.
98 pub probe: &'a Probe,
99 /// What a branch on a register is, which is what the loop under a large frame ends with.
100 pub branch: &'a BranchInsts,
101 /// The two registers the sequence may use, which are two the allocator never handed out.
102 pub scratch: [PhysReg; 2],
103}
104
105/// What a profiler's hook at the top of a function is, in a function that has one.
106///
107/// What `-pg` asks for. See [`rucc_target::Trace`] for why there are two of these and what each of
108/// them lets the hook see. Only the name survives to here, because by this point the flag has been
109/// read against the target and a prologue that has the name has everything it needs.
110#[derive(Debug, Clone, Copy)]
111pub struct Tracing {
112 /// What is called, which is a routine the runtime provides and not one the program wrote.
113 pub name: &'static str,
114 /// Whether the call goes in front of the prologue rather than once the frame is taken.
115 pub early: bool,
116}
117
118/// The room at the top of a function for something to be written over later, in a function that
119/// was promised any.
120///
121/// What `-fpatchable-function-entry=` asks for. The room is a run of the shortest instruction the
122/// machine has that does nothing, and what makes it worth reserving is that it is never run for
123/// long: a tracer or a live patcher writes a jump or a call over it once the program is up, and
124/// what it needs from the compiler is a known address and a known number of bytes.
125///
126/// Two counts because the room can be on either side of the function's own label. Only the half
127/// after it is written here, since the stream starts at the label and there is nowhere in it to put
128/// the other half; the half in front is carried through so that whatever lays the function down can
129/// lay that many bytes ahead of the symbol.
130#[derive(Debug, Clone, Copy)]
131pub struct Padding {
132 /// What the instruction that does nothing is called on this target.
133 pub name: &'static str,
134 /// How many of them go in front of the function's own label.
135 pub before: u32,
136 /// How many go after it.
137 pub after: u32,
138}
139
140/// What the convention this function is compiled for says a frame is.
141///
142/// Seven answers to the one question, which is why they travel together: where it puts things,
143/// which instructions build one, whether this function's carries a protector, whether it is taken a
144/// page at a time, whether the function opens with a landing pad, whether it calls a profiler on
145/// the way in, and how much room it opens with for a patcher. The last five are the only ones about
146/// this function rather than about every function on the target, and they are here because what
147/// they need is the other two and nothing else.
148#[derive(Debug, Clone, Copy)]
149pub struct Convention<'a> {
150 /// Where the convention puts things.
151 pub regs: &'a CallRegs,
152 /// The instructions a prologue, an epilogue, a spill and a reload are made of on it.
153 pub insts: &'a FrameInsts,
154 /// What this function's stack protector needs, or `None` in a function with none.
155 pub protect: Option<Protect<'a>>,
156 /// What this function's probing prologue needs, or `None` when the frame is taken in one
157 /// subtraction, which is what a command line that did not ask asks for.
158 pub probe: Option<Probing<'a>>,
159 /// What says an indirect branch may arrive at the top of this function, or `None` when the
160 /// command line did not ask for one and on a target that has no such instruction.
161 ///
162 /// See [`rucc_target::FrameInsts::landing`]. A name rather than a flag because the flag has
163 /// already been read against the target by the time this is built, and because a prologue that
164 /// has the name has everything it needs.
165 pub landing: Option<&'static str>,
166 /// What this function's call to a profiler is, or `None` in one that makes none, which is every
167 /// function on a command line that did not ask.
168 pub trace: Option<Tracing>,
169 /// What room this function opens with for a patcher, or `None` in one that was promised none,
170 /// which is every function on a command line that did not ask.
171 pub pad: Option<Padding>,
172}
173
174impl<'a> Convention<'a> {
175 /// That convention, for a function with no stack protector, no probing, no landing pad, no
176 /// call to a profiler and no room for a patcher, which is most of them.
177 #[must_use]
178 pub fn new(regs: &'a CallRegs, insts: &'a FrameInsts) -> Self {
179 Self { regs, insts, protect: None, probe: None, landing: None, trace: None, pad: None }
180 }
181}
182
183/// Writes the moves, the prologue and the epilogue into a function the allocator has finished
184/// with.
185///
186/// # Panics
187///
188/// Panics on a function with no blocks in it, on a frame whose slots or locals the allocation and
189/// the lowering do not match, and on a move of a class the target did not say how to move. All of
190/// them are the caller handing it a frame and a function that were not worked out from each other.
191pub fn finish(
192 func: &mut Func,
193 allocation: &Allocation,
194 frame: &Frame,
195 stack: &Stack,
196 convention: Convention<'_>,
197 names: &mut Interner,
198) {
199 let Convention { regs: conv, insts, protect, probe, landing, trace, pad } = convention;
200 let entry = func.entry().expect("a function with a block in it");
201 let returns: Vec<Block> = func.blocks().filter(|&block| func[block].succs.is_empty()).collect();
202
203 // Before anything is written, because these are instructions the lowering already put in the
204 // function and every one of them is somewhere the prologue is about to go in front of, which
205 // is what makes an offset from the stack pointer the right thing to write into them.
206 for &(inst, local) in &stack.addresses {
207 let at = frame.local(local).expect("a local the frame was worked out from");
208 let mem = func[inst].mem.expect("the address of a local is an address");
209 func[mem].disp = at;
210 }
211
212 // The same, one area further up, and through the frame pointer when that is what reaches it.
213 // These are in the entry block ahead of everything, so the prologue still goes in front of
214 // them, which is what makes both registers hold what these offsets are counted from.
215 let incoming = frame.incoming();
216 for &(inst, up) in &stack.arguments {
217 let mem = func[inst].mem.expect("an argument read out of memory is read from an address");
218 func[mem].disp = incoming.at + offset(up);
219 if incoming.through_frame_pointer {
220 // The base register is an operand of the instruction and the addressing mode holds
221 // where in the operand vector it is, so the register is changed there and not here.
222 let at = func[mem].base.expect("an address the lowering wrote a base register into");
223 let operands = func[inst].operands;
224 func[operands][usize::from(at)].reg = Reg::physical(conv.frame_pointer);
225 }
226 }
227
228 let mut writer = Writer { func, conv, insts, names, ahead: None };
229
230 let mut cursors: Vec<(At, Inst)> = Vec::new();
231 for edit in &allocation.edits {
232 let inst = writer.mov(edit, frame);
233 writer.put(&mut cursors, edit.at, inst);
234 }
235
236 let prologue = writer.prologue(frame, protect, probe, landing, trace, pad);
237 for &inst in prologue.iter().rev() {
238 writer.func.prepend_inst(entry, inst);
239 }
240 for block in returns {
241 // The check goes in front of the epilogue and takes the return with it. What is left in
242 // the block the function used to return from is the check, and the block the epilogue then
243 // goes in is the arm the canary was unchanged on.
244 let block = match protect {
245 Some(protect) => writer.check(block, frame, protect),
246 None => block,
247 };
248 let epilogue = writer.epilogue(frame);
249 for inst in epilogue {
250 writer.func.append_inst(block, inst);
251 }
252 }
253
254 // Last of everything, because the blocks a probing prologue made have to come in front of the
255 // block the function used to begin with and the ones the protector's check makes are made
256 // after that. Nothing has been laid out yet: `crate::layout` runs after this and puts every
257 // block in its own order, and all this decides is which block the function is entered at.
258 if let Some(ahead) = writer.ahead {
259 let rest: Vec<Block> =
260 writer.func.blocks().filter(|block| !ahead.contains(block)).collect();
261 let order: Vec<Block> = ahead.into_iter().chain(rest).collect();
262 writer.func.set_block_order(&order);
263 }
264}
265
266/// How many pages a probing prologue touches one after another before it writes a loop instead.
267///
268/// Three, which is what gcc unrolls to. The loop is four instructions however many pages it walks
269/// and a page written out is two, so three is the last size at which the straight line is no
270/// longer than the loop, and the straight line has no branch in it and needs no register.
271const UNROLLED: u32 = 3;
272
273/// One function having its frame written into it.
274struct Writer<'a> {
275 func: &'a mut Func,
276 conv: &'a CallRegs,
277 insts: &'a FrameInsts,
278 names: &'a mut Interner,
279 /// The blocks a probing prologue made, which go in front of the one the function began with.
280 ///
281 /// Empty in every function whose frame is taken in one subtraction, which is every function
282 /// on a command line that did not ask for the stack to be touched a page at a time and most
283 /// of them on one that did. See [`Writer::pages`].
284 ahead: Option<[Block; 2]>,
285}
286
287impl Writer<'_> {
288 /// The instructions the prologue is, in the order they run.
289 ///
290 /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
291 /// before anything else, so that it points at a fixed place whatever else happens. The
292 /// registers are pushed before the alignment is forced, so that the epilogue can find them
293 /// again from the frame pointer, since after the alignment is forced nothing else can. And the
294 /// vector registers are stored last, because until the frame has been taken there is nowhere
295 /// to store them.
296 ///
297 /// The landing pad is in front of all of it, because the address it makes reachable is the
298 /// address of the function and the address of the function is where the first instruction is.
299 /// It has to be written here rather than after the fact, since a probing prologue moves the
300 /// instructions written so far into a block of its own and the pad has to move with them.
301 ///
302 /// The room a patcher was promised goes after the pad, because a patcher wants somewhere it can
303 /// write a call that happens before anything else, and the pad is the one instruction that has
304 /// to come first for a reason of its own.
305 ///
306 /// A profiler's hook goes next, or at the end when it is the kind that reads the frame pointer.
307 /// The early one is in front of everything the frame does for a reason of its own: what makes
308 /// it worth replacing while the program runs is that the stack at that instruction is exactly
309 /// what a call leaves, and a prologue that had already run would have changed it.
310 fn prologue(
311 &mut self,
312 frame: &Frame,
313 protect: Option<Protect<'_>>,
314 probe: Option<Probing<'_>>,
315 landing: Option<&'static str>,
316 trace: Option<Tracing>,
317 pad: Option<Padding>,
318 ) -> Vec<Inst> {
319 let sp = self.conv.stack_pointer;
320 let fp = self.conv.frame_pointer;
321 let int = self.conv.int_class;
322 let sse = self.conv.sse_class;
323 let word = offset(self.conv.word);
324 let mut out = Vec::new();
325 // What the prologue wrote before it had described anything, which is what decides whether
326 // there is a rule to remember at the end of it. Neither of these moves a register or takes
327 // a frame, so a function whose whole prologue is one of them has no rows and must not be
328 // given a pair of them that cancel out.
329 let mut quiet = Vec::new();
330 if let Some(name) = landing {
331 let opcode = self.opcode(name);
332 let inst = self.func.build_loose(opcode).finish();
333 out.push(inst);
334 quiet.push(inst);
335 }
336 // After the pad and in front of everything else, which is where gcc puts it. The pad is the
337 // function's first instruction because the address an indirect branch may arrive at is the
338 // address of the function, and the room comes next because what gets written over it is a
339 // call and the point of that call is that it happens before the function has done anything.
340 //
341 // Nothing is described for any of it. A byte that does nothing does not move the stack
342 // pointer, and what a patcher writes over it later is its own problem rather than this
343 // function's: the rules here say what this function did, and it did nothing.
344 if let Some(pad) = pad {
345 let opcode = self.opcode(pad.name);
346 let mut first = None;
347 for _ in 0..pad.after {
348 let inst = self.func.build_loose(opcode).finish();
349 out.push(inst);
350 quiet.push(inst);
351 first.get_or_insert(inst);
352 }
353 self.func.patch = Some(Patch { before: pad.before, pad: opcode, after: first });
354 }
355 // Nothing is described for it and nothing needs to be: the call pushes a return address and
356 // the hook pops it, so the frame is the same on both sides, and the hook preserves every
357 // register because it is written in assembly for exactly this. That is also why the
358 // allocator, which ran before any of this, never saw the call and did not have to.
359 if let Some(trace) = trace.filter(|trace| trace.early) {
360 let inst = self.hook(trace);
361 out.push(inst);
362 quiet.push(inst);
363 }
364 // How far the stack pointer is below the canonical frame address, and whether the address
365 // is still counted from the stack pointer at all. It starts at the return address the
366 // call itself pushed, which is the rule the CIE already states, so the first row here is
367 // the first thing this function does on top of that.
368 let mut below = offset(self.conv.return_address);
369 let mut from_sp = true;
370 if frame.frame_pointer() {
371 let inst = self.push(fp);
372 out.push(inst);
373 below += word;
374 self.row(inst, CfiOp::DefCfaOffset(below));
375 self.saved(inst, int, fp, -below);
376 let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
377 let inst = self.two(mov, fp, sp);
378 out.push(inst);
379 let number = self.dwarf(int, fp);
380 self.row(inst, CfiOp::DefCfaRegister(number));
381 from_sp = false;
382 }
383 for ® in frame.saved_int() {
384 let inst = self.push(reg);
385 out.push(inst);
386 below += word;
387 if from_sp {
388 self.row(inst, CfiOp::DefCfaOffset(below));
389 }
390 self.saved(inst, int, reg, -below);
391 }
392 if let Some(to) = frame.realign() {
393 // Nothing is written for this and nothing can be. After it the stack pointer is a
394 // rounded-down version of where it was rather than a fixed distance from it, which is
395 // exactly what a rule cannot say. It is also why a frame that realigns is a frame
396 // with a frame pointer: by here the address is already counted from that instead.
397 assert!(!from_sp, "a frame that forces its own alignment has a frame pointer");
398 let and = self.opcode(self.insts.align);
399 out.push(self.arith(and, -i64::from(to)));
400 }
401 if frame.size() > 0 {
402 self.take(&mut out, frame.size(), &mut below, from_sp, probe);
403 }
404 for save in frame.saved_sse() {
405 let inst = self.store(sse, save.reg, save.at);
406 out.push(inst);
407 // Where it went is an offset from the stack pointer in the body, and the address is
408 // `below` above that, so the two make one constant. Unless the frame realigned, in
409 // which case there is no such constant and the rule is left out rather than guessed;
410 // the one convention that realigns and the one that preserves a vector register are
411 // not the same convention, so nothing reaches this today.
412 if frame.realign().is_none() {
413 self.saved(inst, sse, save.reg, save.at - below);
414 }
415 }
416 // Before the canary and after the frame, which is where gcc puts it. The hook reads the
417 // frame pointer to find out who called this function, so it has to run once there is one,
418 // and it is a call, so it has to run before anything the function is keeping in the frame
419 // could be read back.
420 if let Some(trace) = trace.filter(|trace| !trace.early) {
421 let inst = self.hook(trace);
422 out.push(inst);
423 }
424 // Last of everything, because it writes into the frame and there is no frame to write into
425 // until the stack pointer has moved. Nothing is described for either instruction: they
426 // write a slot rather than save a register, and no unwinder wants to put a canary back.
427 if let Some(protect) = protect {
428 let at = frame.canary().expect("a protected function has a slot for its canary");
429 let [into, _] = protect.scratch;
430 out.push(self.read_guard(into, protect.guard));
431 out.push(self.store(self.conv.int_class, into, at));
432 }
433 // The rules the body runs under, kept so that each epilogue can put them back rather than
434 // leaving the next block reading whatever the last one ended on. See `epilogue`.
435 //
436 // Nothing is kept in a function whose whole prologue is the pieces that describe nothing.
437 // See `quiet` above.
438 if let Some(&last) = out.last() {
439 if !quiet.contains(&last) {
440 self.row(last, CfiOp::RememberState);
441 }
442 }
443 out
444 }
445
446 /// The call to a profiler's hook.
447 ///
448 /// No arguments and no result. Which function is being entered is not passed, because the hook
449 /// reads its own return address to find out, and that is the whole reason the call is written
450 /// rather than something cheaper.
451 fn hook(&mut self, trace: Tracing) -> Inst {
452 let call = self.opcode(self.insts.call);
453 let symbol = self.names.intern(trace.name);
454 self.func.build_loose(call).symbol(symbol).finish()
455 }
456
457 /// Takes the frame, which is one subtraction unless the command line asked for the stack to be
458 /// touched a page at a time.
459 ///
460 /// `below` is how far the canonical frame address is above the stack pointer, and it comes
461 /// back as what it is once the frame has been taken.
462 fn take(
463 &mut self,
464 out: &mut Vec<Inst>,
465 size: u32,
466 below: &mut i32,
467 from_sp: bool,
468 probe: Option<Probing<'_>>,
469 ) {
470 let Some(probing) = probe.filter(|probing| size > probing.probe.interval) else {
471 let inst = self.sub(size);
472 out.push(inst);
473 *below += offset(size);
474 if from_sp {
475 self.row(inst, CfiOp::DefCfaOffset(*below));
476 }
477 return;
478 };
479 // Every step but the last is a whole page and is followed by a touch, and the last is
480 // whatever is left over, which is between one byte and one whole page. So the stack
481 // pointer never moves further than a page without something being written where it landed,
482 // and the unmapped page an operating system leaves below a stack cannot be stepped over.
483 //
484 // That is why the count is worked out from one less than the size. A frame that is an
485 // exact number of pages gets one fewer touch than it has pages, and the step left over is
486 // a whole page, which is a step that lands on the next page boundary rather than past it.
487 // gcc touches that last page as well, so this is one instruction shorter on a frame whose
488 // size is a multiple of the page and the same everywhere else.
489 let interval = probing.probe.interval;
490 let pages = (size - 1) / interval;
491 let rest = size - pages * interval;
492 let mut walked = false;
493 if pages <= UNROLLED {
494 for _ in 0..pages {
495 let inst = self.sub(interval);
496 out.push(inst);
497 *below += offset(interval);
498 if from_sp {
499 self.row(inst, CfiOp::DefCfaOffset(*below));
500 }
501 let touch = self.touch(probing.probe);
502 out.push(touch);
503 }
504 } else {
505 self.pages(out, pages, below, from_sp, probing);
506 walked = from_sp;
507 }
508 let inst = self.sub(rest);
509 out.push(inst);
510 *below += offset(rest);
511 if from_sp {
512 // A loop leaves the address counted from the register the stack pointer was compared
513 // against, since that is the one thing in it that holds still. This is where it goes
514 // back to being counted from the stack pointer, and it is written behind this
515 // instruction rather than behind the branch because a row is written behind an
516 // instruction and the branch is not one that survives [`crate::layout`].
517 let op = if walked {
518 let number = self.dwarf(self.conv.int_class, self.conv.stack_pointer);
519 CfiOp::DefCfa { reg: number, offset: *below }
520 } else {
521 CfiOp::DefCfaOffset(*below)
522 };
523 self.row(inst, op);
524 }
525 }
526
527 /// The loop that takes a frame too large for the touches to be written one after another.
528 ///
529 /// Three blocks, and the first two are new and go in front of the one the function began with:
530 ///
531 /// ```text
532 /// what the function is entered at everything the prologue did before this, and then the
533 /// address the stack pointer is walking down to
534 /// the loop one page, the touch, and the question of whether the
535 /// stack pointer has got there yet
536 /// what the function began with the rest of the prologue, and then the body
537 /// ```
538 ///
539 /// The instructions the prologue has written so far move into the first of them, because a
540 /// block is entered at the top and they have to run before the loop does. Nothing is laid out
541 /// here: which block comes first in memory is [`crate::layout`]'s answer, and all this decides
542 /// is which one the function is entered at.
543 fn pages(
544 &mut self,
545 out: &mut Vec<Inst>,
546 pages: u32,
547 below: &mut i32,
548 from_sp: bool,
549 probing: Probing<'_>,
550 ) {
551 let class = self.conv.int_class;
552 let sp = self.conv.stack_pointer;
553 let all = offset(pages * probing.probe.interval);
554 let [limit, byte] = probing.scratch;
555
556 let head = self.func.create_block();
557 for &inst in out.iter() {
558 self.func.append_inst(head, inst);
559 }
560 out.clear();
561 // Where the stack pointer is walking down to, worked out before it starts moving. A loop
562 // that counted down instead would need somewhere to keep the count, and this is somewhere
563 // to keep it that the comparison can read without arithmetic.
564 let lea = self.opcode(self.insts.lea);
565 let inst = self.address(lea, limit, sp, -all);
566 self.func.append_inst(head, inst);
567 if from_sp {
568 // The address is counted from that register for as long as the loop runs, and it has
569 // to be: the stack pointer moves once an iteration, so no fixed distance from it is
570 // true twice, and this register was written so that one distance is.
571 let number = self.dwarf(class, limit);
572 self.row(inst, CfiOp::DefCfa { reg: number, offset: *below + all });
573 }
574
575 let body = self.func.create_block();
576 *self.func.succs_mut(head) = vec![BlockCall::to(body)];
577 let inst = self.sub(probing.probe.interval);
578 self.func.append_inst(body, inst);
579 let touch = self.touch(probing.probe);
580 self.func.append_inst(body, touch);
581 let differ = self.opcode(self.insts.differ);
582 let inst = self
583 .func
584 .build_loose(differ)
585 .def(Reg::physical(byte), class)
586 .uses(Reg::physical(sp), class)
587 .uses(Reg::physical(limit), class)
588 .finish();
589 self.func.append_inst(body, inst);
590 let cond = Opcode::new(
591 self.names.intern(&format!("{}{}", probing.branch.prefix, probing.branch.cond)),
592 );
593 let inst = self.func.build_loose(cond).uses(Reg::physical(byte), class).finish();
594 self.func.append_inst(body, inst);
595 // The first arm is the one taken when the condition held, and the condition is that the
596 // stack pointer and the address it is walking down to still differ, so the first arm is
597 // another page.
598 let began = self.func.entry().expect("a function with a block in it");
599 *self.func.succs_mut(body) = vec![BlockCall::to(body), BlockCall::to(began)];
600 *below += all;
601 self.ahead = Some([head, body]);
602 }
603
604 /// Writes the page the stack pointer is on without changing what is there.
605 fn touch(&mut self, probe: &Probe) -> Inst {
606 let opcode = self.opcode(probe.inst);
607 let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
608 self.func.build_loose(opcode).imm(0).mem(Mem::at(base)).finish()
609 }
610
611 /// Takes that many bytes off the stack pointer.
612 fn sub(&mut self, bytes: u32) -> Inst {
613 let sub = self.opcode(self.insts.sub);
614 self.arith(sub, i64::from(bytes))
615 }
616
617 /// The stack protector's check, written at the end of a block the function returns from.
618 ///
619 /// Gives back the block the epilogue goes in, which is a new one: the check has to be the last
620 /// thing the old block does, and what follows it is one of two arms rather than the return.
621 ///
622 /// ```text
623 /// block that returned reload the slot, read the word again, compare, branch
624 /// the arm it changed on call the function that does not come back, and nothing after
625 /// the arm it did not the epilogue, which the caller writes into what this gives back
626 /// ```
627 ///
628 /// The two registers are the ones the allocator was told to hold back, so nothing here has to
629 /// ask what is live: a scratch register holds nothing at the end of a block, because the only
630 /// thing that writes one is a move the rewriter put in and every one of those is read by the
631 /// instruction it was put in front of.
632 fn check(&mut self, block: Block, frame: &Frame, protect: Protect<'_>) -> Block {
633 let class = self.conv.int_class;
634 let at = frame.canary().expect("a protected function has a slot for its canary");
635 let [ours, theirs] = protect.scratch;
636
637 let inst = self.load(class, ours, at);
638 self.func.append_inst(block, inst);
639 let inst = self.read_guard(theirs, protect.guard);
640 self.func.append_inst(block, inst);
641 let differ = self.opcode(self.insts.differ);
642 let inst = self
643 .func
644 .build_loose(differ)
645 .def(Reg::physical(theirs), class)
646 .uses(Reg::physical(ours), class)
647 .uses(Reg::physical(theirs), class)
648 .finish();
649 self.func.append_inst(block, inst);
650
651 let failed = self.func.create_block();
652 let ok = self.func.create_block();
653 let cond = Opcode::new(
654 self.names.intern(&format!("{}{}", protect.branch.prefix, protect.branch.cond)),
655 );
656 let inst = self.func.build_loose(cond).uses(Reg::physical(theirs), class).finish();
657 self.func.append_inst(block, inst);
658 // The first arm is the one taken when the condition held, and the condition is that the
659 // two words differ, so the first arm is the one the canary was overwritten on.
660 *self.func.succs_mut(block) = vec![BlockCall::to(failed), BlockCall::to(ok)];
661
662 let call = self.opcode(self.insts.call);
663 let symbol = self.names.intern(protect.guard.fail);
664 self.func.build(failed, call).symbol(symbol).finish();
665 ok
666 }
667
668 /// Reads the word the canary is a copy of into a register.
669 ///
670 /// The address is a constant and names no register at all, because where the block a thread
671 /// has to itself begins is something only the machine knows and the segment register is what
672 /// holds it.
673 fn read_guard(&mut self, into: PhysReg, guard: &Guard) -> Inst {
674 let class = self.conv.int_class;
675 let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
676 self.func
677 .build_loose(load)
678 .def(Reg::physical(into), class)
679 .mem(Mem::in_segment(guard.segment, guard.at))
680 .finish()
681 }
682
683 /// The instructions the epilogue is, in the order they run.
684 ///
685 /// The vector registers are read back while the stack pointer is still where the body left it,
686 /// because that is what their offsets are from. Then the stack pointer goes back to the last
687 /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
688 /// moved and a read of the frame pointer when it did not.
689 fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
690 let sp = self.conv.stack_pointer;
691 let fp = self.conv.frame_pointer;
692 let int = self.conv.int_class;
693 let sse = self.conv.sse_class;
694 let word = self.conv.word;
695 let described = !self.func.cfi.is_empty();
696 let mut out = Vec::new();
697 // Where the body left things, which is where every epilogue starts from.
698 let mut below = offset(self.conv.return_address)
699 + offset(word) * self.pushes(frame)
700 + offset(frame.size());
701 let from_sp = !frame.frame_pointer();
702 for save in frame.saved_sse() {
703 let inst = self.load(sse, save.reg, save.at);
704 out.push(inst);
705 if frame.realign().is_none() {
706 self.restored(inst, sse, save.reg);
707 }
708 }
709 let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
710 if frame.frame_pointer() {
711 // No row for either of these. The address is counted from the frame pointer here and
712 // this is what moves the stack pointer rather than the frame pointer, so the rule that
713 // was true before it is still true after it.
714 if pushed == 0 {
715 let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
716 out.push(self.two(mov, sp, fp));
717 } else {
718 let lea = self.opcode(self.insts.lea);
719 let back = -offset(word * pushed);
720 out.push(self.address(lea, sp, fp, back));
721 }
722 } else if frame.size() > 0 {
723 let add = self.opcode(self.insts.add);
724 let inst = self.arith(add, i64::from(frame.size()));
725 out.push(inst);
726 below -= offset(frame.size());
727 self.row(inst, CfiOp::DefCfaOffset(below));
728 }
729 for ® in frame.saved_int().iter().rev() {
730 let inst = self.pop(reg);
731 out.push(inst);
732 self.restored(inst, int, reg);
733 below -= offset(word);
734 if from_sp {
735 self.row(inst, CfiOp::DefCfaOffset(below));
736 }
737 }
738 if frame.frame_pointer() {
739 let inst = self.pop(fp);
740 out.push(inst);
741 self.restored(inst, int, fp);
742 // The frame pointer holds the caller's value again, so the address goes back to being
743 // counted from the stack pointer, which by now is at the return address.
744 let number = self.dwarf(int, sp);
745 self.row(inst, CfiOp::DefCfa { reg: number, offset: offset(self.conv.return_address) });
746 }
747 let ret = self.opcode(self.insts.ret);
748 let inst = self.func.build_loose(ret).finish();
749 out.push(inst);
750 // These take effect at the address just past the return, which is where the next block
751 // begins, and the next block is body again. Popping the body's rules and pushing them
752 // straight back leaves the stack one deep however many blocks the function returns from,
753 // which is what makes one remembering in the prologue enough for all of them.
754 if described {
755 self.row(inst, CfiOp::RestoreState);
756 self.row(inst, CfiOp::RememberState);
757 }
758 out
759 }
760
761 /// How many general purpose registers the prologue put on the stack, the frame pointer
762 /// included.
763 fn pushes(&self, frame: &Frame) -> i32 {
764 let saved = i32::try_from(frame.saved_int().len()).expect("a frame");
765 saved + i32::from(frame.frame_pointer())
766 }
767
768 /// One row of the unwind table, taking effect after that instruction.
769 fn row(&mut self, inst: Inst, op: CfiOp) {
770 self.func.cfi.push((inst, op));
771 }
772
773 /// A row saying the caller's copy of that register is that far from the canonical frame
774 /// address, which is below it and so is negative.
775 fn saved(&mut self, inst: Inst, class: RegClass, reg: PhysReg, from_cfa: i32) {
776 let number = self.dwarf(class, reg);
777 self.row(inst, CfiOp::Offset { reg: number, offset: from_cfa });
778 }
779
780 /// A row saying that register holds what the caller left in it again.
781 fn restored(&mut self, inst: Inst, class: RegClass, reg: PhysReg) {
782 let number = self.dwarf(class, reg);
783 self.row(inst, CfiOp::Restore(number));
784 }
785
786 /// What an unwind table calls that register.
787 fn dwarf(&self, class: RegClass, reg: PhysReg) -> u16 {
788 self.conv.dwarf(class, reg).expect("a register a frame saves is one the table can name")
789 }
790
791 /// One edit as the instruction that makes it true.
792 fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
793 let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
794 match (edit.mov.to, edit.mov.from) {
795 (Place::Reg(to), Place::Reg(from)) => {
796 let mov = self.opcode(moves.mov);
797 self.func
798 .build_loose(mov)
799 .def(Reg::physical(to), edit.class)
800 .uses(Reg::physical(from), edit.class)
801 .finish()
802 }
803 (Place::Reg(to), Place::Slot(slot)) => {
804 let at = self.slot(frame, slot);
805 self.load(edit.class, to, at)
806 }
807 (Place::Slot(slot), Place::Reg(from)) => {
808 let at = self.slot(frame, slot);
809 self.store(edit.class, from, at)
810 }
811 // The allocator expands this into two moves through a register of its own, because a
812 // machine that could do it in one is not a machine any of this is written for.
813 (Place::Slot(_), Place::Slot(_)) => {
814 unreachable!("a move from one stack slot straight into another")
815 }
816 }
817 }
818
819 /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
820 ///
821 /// The edits at one place are in the order they have to be made in, so each one goes behind
822 /// the last, and the first of them is what the place itself means.
823 fn put(&mut self, cursors: &mut Vec<(At, Inst)>, at: At, inst: Inst) {
824 if let Some(cursor) = cursors.iter_mut().find(|(place, _)| *place == at) {
825 self.func.insert_after(cursor.1, inst);
826 cursor.1 = inst;
827 return;
828 }
829 match at {
830 At::Before(before) => self.func.insert_before(before, inst),
831 At::After(after) => self.func.insert_after(after, inst),
832 At::StartOf(block) => self.func.prepend_inst(block, inst),
833 // Behind everything in the block. A block the allocator puts an edge's moves at the
834 // end of is one with a single edge out of it, and an edge like that is not an
835 // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
836 // So the last instruction is an ordinary one, which may still be waiting on moves of
837 // its own that have to be made before the edge's are.
838 At::EndOf(block) => self.func.append_inst(block, inst),
839 }
840 cursors.push((at, inst));
841 }
842
843 /// Where a spill slot is, from the stack pointer in the body of the function.
844 fn slot(&self, frame: &Frame, slot: u32) -> i32 {
845 frame.slot(slot).expect("a slot the frame was worked out from")
846 }
847
848 /// Reads a register out of the frame.
849 fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
850 let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
851 let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
852 self.func
853 .build_loose(load)
854 .def(Reg::physical(reg), class)
855 .mem(Mem::at(base).plus(at))
856 .finish()
857 }
858
859 /// Writes a register into the frame.
860 fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
861 let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
862 let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
863 self.func
864 .build_loose(store)
865 .uses(Reg::physical(reg), class)
866 .mem(Mem::at(base).plus(at))
867 .finish()
868 }
869
870 /// Puts a general purpose register on the stack.
871 fn push(&mut self, reg: PhysReg) -> Inst {
872 let push = self.opcode(self.insts.push);
873 self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
874 }
875
876 /// Takes a general purpose register back off the stack.
877 fn pop(&mut self, reg: PhysReg) -> Inst {
878 let pop = self.opcode(self.insts.pop);
879 self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
880 }
881
882 /// One general purpose register written with another.
883 fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
884 let class = self.conv.int_class;
885 self.func
886 .build_loose(opcode)
887 .def(Reg::physical(to), class)
888 .uses(Reg::physical(from), class)
889 .finish()
890 }
891
892 /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
893 fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
894 let class = self.conv.int_class;
895 let sp = Reg::physical(self.conv.stack_pointer);
896 self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
897 }
898
899 /// One register written with an address rather than with what is at it.
900 fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
901 let class = self.conv.int_class;
902 let base = Operand::read(Reg::physical(base), class);
903 self.func
904 .build_loose(opcode)
905 .def(Reg::physical(to), class)
906 .mem(Mem::at(base).plus(disp))
907 .finish()
908 }
909
910 /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
911 /// then the name the target gave.
912 fn opcode(&mut self, name: &str) -> Opcode {
913 Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
914 }
915}
916
917/// A distance in a frame, as the signed number every offset is.
918fn offset(bytes: u32) -> i32 {
919 i32::try_from(bytes).expect("a frame under two gigabytes")
920}
921
922#[cfg(test)]
923mod tests {
924 use rucc_base::Interner;
925 use rucc_mir::{BlockCall, print_func};
926 use rucc_regalloc::assign::Env;
927 use rucc_target::x86_64::{BRANCH, FRAME, GPR, PROBE, R10, R11, REGS, SYSV, WIN64, XMM, xmm};
928
929 use super::*;
930 use crate::frame::{Layout, Local};
931
932 /// An environment offering that many of the convention's registers, with everything after
933 /// them held back as scratch.
934 fn env(conv: &CallRegs, count: usize) -> Env {
935 Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
936 }
937
938 /// A function of that many values, every one written before any is read, allocated with that
939 /// many registers to hand out. The same shape the frame layout's own tests are written
940 /// against, so that a frame here is one that has already been checked there.
941 fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
942 let mut names = Interner::new();
943 let mut func = Func::new(names.intern("f"));
944 let opcode = Opcode::new(names.intern("x64.nop"));
945 let block = func.create_block();
946 let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
947 for ® in ®s {
948 func.build(block, opcode).def(reg, GPR).finish();
949 }
950 for ® in ®s {
951 func.build(block, opcode).uses(reg, GPR).finish();
952 }
953 let allocation = rucc_regalloc::run(&mut func, &env(conv, count), "test");
954 (func, allocation, names)
955 }
956
957 /// The function with its frame written into it, as the lines a dump would show.
958 fn written(
959 func: &mut Func,
960 allocation: &Allocation,
961 layout: &Layout<'_>,
962 names: &mut Interner,
963 ) -> Vec<String> {
964 with_protector(func, allocation, layout, None, names)
965 }
966
967 /// The same, for a function the caller has decided is protected or is not.
968 fn with_protector(
969 func: &mut Func,
970 allocation: &Allocation,
971 layout: &Layout<'_>,
972 protect: Option<Protect<'_>>,
973 names: &mut Interner,
974 ) -> Vec<String> {
975 let convention = Convention { protect, ..Convention::new(layout.conv, &FRAME) };
976 under(func, allocation, layout, convention, names)
977 }
978
979 /// The same, for a function whose frame the caller has decided is taken a page at a time.
980 fn with_probing(
981 func: &mut Func,
982 allocation: &Allocation,
983 layout: &Layout<'_>,
984 probe: Option<Probing<'_>>,
985 names: &mut Interner,
986 ) -> Vec<String> {
987 let convention = Convention { probe, ..Convention::new(layout.conv, &FRAME) };
988 under(func, allocation, layout, convention, names)
989 }
990
991 /// The function with its frame written into it under that convention.
992 fn under(
993 func: &mut Func,
994 allocation: &Allocation,
995 layout: &Layout<'_>,
996 convention: Convention<'_>,
997 names: &mut Interner,
998 ) -> Vec<String> {
999 let frame = Frame::of(func, allocation, layout);
1000 finish(func, allocation, &frame, &Stack::default(), convention, names);
1001 print_func(func, names, ®S)
1002 .lines()
1003 .filter(|line| !line.is_empty())
1004 .map(|line| line.trim().to_string())
1005 .collect()
1006 }
1007
1008 /// Just the lines the frame put in, which is every line that is not the function it was
1009 /// given and not the shape of the dump around it.
1010 fn added(lines: &[String]) -> Vec<&str> {
1011 lines
1012 .iter()
1013 .map(String::as_str)
1014 .filter(|line| !line.contains("x64.nop"))
1015 .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
1016 .collect()
1017 }
1018
1019 #[test]
1020 fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
1021 let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1022 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1023
1024 // Two values and four registers, so nothing is spilled, nothing is saved and the stack
1025 // pointer never moves. A prologue of nothing is the right prologue for that.
1026 assert_eq!(added(&lines), ["x64.ret"]);
1027 }
1028
1029 #[test]
1030 fn a_spill_is_a_store_and_a_reload_is_a_load() {
1031 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1032 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1033
1034 // Two registers for four values, so two of them go to the stack. The store goes behind the
1035 // instruction that wrote the value and the load in front of the one that wants it, both at
1036 // the offsets the frame gave, which are below the stack pointer because a small leaf
1037 // function is entitled to the red zone.
1038 assert_eq!(
1039 lines,
1040 [
1041 "mfunc @f {",
1042 "block0:",
1043 "$rax = x64.nop",
1044 "$rcx = x64.nop",
1045 "$rdx = x64.nop",
1046 "x64.mov_mr_64 $rdx, [$rsp - 16]",
1047 "$rdx = x64.nop",
1048 "x64.mov_mr_64 $rdx, [$rsp - 8]",
1049 "x64.nop $rax",
1050 "x64.nop $rcx",
1051 "$rdx = x64.mov_rm_64 [$rsp - 16]",
1052 "x64.nop $rdx",
1053 "$rdx = x64.mov_rm_64 [$rsp - 8]",
1054 "x64.nop $rdx",
1055 "x64.ret",
1056 "}",
1057 ]
1058 );
1059 }
1060
1061 #[test]
1062 fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
1063 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1064 let base = Layout::new(&SYSV, REGS);
1065 let layout = Layout { red_zone: false, ..base };
1066 let lines = written(&mut func, &allocation, &layout, &mut names);
1067
1068 // The same function told it may not use the red zone takes sixteen bytes instead, and
1069 // every offset moves above the stack pointer to match.
1070 assert_eq!(
1071 added(&lines),
1072 [
1073 "$rsp = x64.sub_ri_64 $rsp, 16",
1074 "x64.mov_mr_64 $rdx, [$rsp]",
1075 "x64.mov_mr_64 $rdx, [$rsp + 8]",
1076 "$rdx = x64.mov_rm_64 [$rsp]",
1077 "$rdx = x64.mov_rm_64 [$rsp + 8]",
1078 "$rsp = x64.add_ri_64 $rsp, 16",
1079 "x64.ret",
1080 ]
1081 );
1082 }
1083
1084 #[test]
1085 fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
1086 let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
1087 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
1088
1089 // Four registers a call leaves alone, pushed in the convention's order and popped in the
1090 // other one, which is the only order that gets each of them its own value back.
1091 assert_eq!(
1092 added(&lines),
1093 [
1094 "x64.push_64 $rbx",
1095 "x64.push_64 $r12",
1096 "x64.push_64 $r13",
1097 "x64.push_64 $r14",
1098 "$r14 = x64.pop_64",
1099 "$r13 = x64.pop_64",
1100 "$r12 = x64.pop_64",
1101 "$rbx = x64.pop_64",
1102 "x64.ret",
1103 ]
1104 );
1105 }
1106
1107 #[test]
1108 fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
1109 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1110 let base = Layout::new(&SYSV, REGS);
1111 let layout = Layout { frame_pointer: true, red_zone: false, ..base };
1112 let lines = written(&mut func, &allocation, &layout, &mut names);
1113
1114 // The frame pointer is saved before anything else and points at where it was saved, so the
1115 // epilogue reaches the stack pointer through it rather than by counting the frame back.
1116 assert_eq!(
1117 added(&lines),
1118 [
1119 "x64.push_64 $rbp",
1120 "$rbp = x64.mov_rr_64 $rsp",
1121 "$rsp = x64.sub_ri_64 $rsp, 16",
1122 "x64.mov_mr_64 $rdx, [$rsp]",
1123 "x64.mov_mr_64 $rdx, [$rsp + 8]",
1124 "$rdx = x64.mov_rm_64 [$rsp]",
1125 "$rdx = x64.mov_rm_64 [$rsp + 8]",
1126 "$rsp = x64.mov_rr_64 $rbp",
1127 "$rbp = x64.pop_64",
1128 "x64.ret",
1129 ]
1130 );
1131 }
1132
1133 #[test]
1134 fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
1135 let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
1136 let locals = [Local { size: 64, align: 32 }];
1137 let base = Layout::new(&SYSV, REGS);
1138 let layout = Layout { locals: &locals, ..base };
1139 let lines = written(&mut func, &allocation, &layout, &mut names);
1140
1141 // Forcing the alignment throws away how far the stack pointer had moved, so the registers
1142 // are pushed before it happens and the epilogue counts back from the frame pointer to find
1143 // them. The frame pointer is required here whatever the flags said.
1144 assert_eq!(
1145 added(&lines),
1146 [
1147 "x64.push_64 $rbp",
1148 "$rbp = x64.mov_rr_64 $rsp",
1149 "x64.push_64 $rbx",
1150 "x64.push_64 $r12",
1151 "x64.push_64 $r13",
1152 "x64.push_64 $r14",
1153 "$rsp = x64.and_ri_64 $rsp, -32",
1154 "$rsp = x64.sub_ri_64 $rsp, 64",
1155 "$rsp = x64.lea_64 [$rbp - 32]",
1156 "$r14 = x64.pop_64",
1157 "$r13 = x64.pop_64",
1158 "$r12 = x64.pop_64",
1159 "$rbx = x64.pop_64",
1160 "$rbp = x64.pop_64",
1161 "x64.ret",
1162 ]
1163 );
1164 }
1165
1166 #[test]
1167 fn every_block_the_function_returns_from_gets_an_epilogue() {
1168 let mut names = Interner::new();
1169 let mut func = Func::new(names.intern("f"));
1170 let opcode = Opcode::new(names.intern("x64.nop"));
1171 let head = func.create_block();
1172 let left = func.create_block();
1173 let right = func.create_block();
1174 func.build(head, opcode).finish();
1175 *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
1176 func.build(left, opcode).finish();
1177 func.build(right, opcode).finish();
1178 let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test");
1179 let base = Layout::new(&SYSV, REGS);
1180 let layout = Layout { leaf: false, ..base };
1181 let lines = written(&mut func, &allocation, &layout, &mut names);
1182
1183 // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
1184 // because a block with an edge out of it is not a block anything returns from.
1185 assert_eq!(
1186 lines,
1187 [
1188 "mfunc @f {",
1189 "block0:",
1190 "$rsp = x64.sub_ri_64 $rsp, 8",
1191 "x64.nop block1, block2",
1192 "block1:",
1193 "x64.nop",
1194 "$rsp = x64.add_ri_64 $rsp, 8",
1195 "x64.ret",
1196 "block2:",
1197 "x64.nop",
1198 "$rsp = x64.add_ri_64 $rsp, 8",
1199 "x64.ret",
1200 "}",
1201 ]
1202 );
1203 }
1204
1205 #[test]
1206 fn a_protected_function_writes_the_canary_last_and_checks_it_before_it_returns() {
1207 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
1208 let base = Layout::new(&SYSV, REGS);
1209 let layout = Layout { leaf: false, protect: true, ..base };
1210 let guard = SYSV.guard.as_ref().expect("this convention has somewhere to keep the word");
1211 // The two the real pipeline holds back, which are held back in the environment above too:
1212 // it hands out the first two of the convention's order and keeps everything after them.
1213 let protect = Protect { guard, branch: &BRANCH, scratch: [R10, R11] };
1214 let lines = with_protector(&mut func, &allocation, &layout, Some(protect), &mut names);
1215
1216 // The read of the word and the store into the slot come after the stack pointer has moved,
1217 // because there is no slot to store into until it has. The check is the last thing the
1218 // block that returned does and the epilogue is on the arm the canary was unchanged on, so
1219 // a function whose canary changed never gives its frame back and never returns.
1220 assert_eq!(
1221 added(&lines),
1222 [
1223 "$rsp = x64.sub_ri_64 $rsp, 24",
1224 "$r10 = x64.mov_rm_64 [fs:40]",
1225 "x64.mov_mr_64 $r10, [$rsp + 16]",
1226 "x64.mov_mr_64 $rdx, [$rsp]",
1227 "x64.mov_mr_64 $rdx, [$rsp + 8]",
1228 "$rdx = x64.mov_rm_64 [$rsp]",
1229 "$rdx = x64.mov_rm_64 [$rsp + 8]",
1230 "$r10 = x64.mov_rm_64 [$rsp + 16]",
1231 "$r11 = x64.mov_rm_64 [fs:40]",
1232 "$r11 = x64.cmp_set_ne_64 $r10, $r11",
1233 "x64.br_cond_8 $r11, block1, block2",
1234 "x64.call @__stack_chk_fail",
1235 "$rsp = x64.add_ri_64 $rsp, 24",
1236 "x64.ret",
1237 ]
1238 );
1239 }
1240
1241 #[test]
1242 fn a_frame_that_fits_in_one_page_is_taken_in_one_subtraction_even_when_pages_are_touched() {
1243 let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1244 let locals = [Local { size: 4088, align: 16 }];
1245 let base = Layout::new(&SYSV, REGS);
1246 let layout = Layout { leaf: false, locals: &locals, ..base };
1247 let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1248 let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1249
1250 // A frame of one page cannot step over the page below it, because the far end of it is the
1251 // near end of that page and anything written there is written to a page that is there. So
1252 // the flag costs such a function nothing, which is most functions.
1253 assert_eq!(
1254 added(&lines),
1255 ["$rsp = x64.sub_ri_64 $rsp, 4088", "$rsp = x64.add_ri_64 $rsp, 4088", "x64.ret",]
1256 );
1257 }
1258
1259 #[test]
1260 fn a_probing_prologue_touches_every_page_of_a_frame_a_few_pages_deep() {
1261 let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1262 let locals = [Local { size: 9000, align: 16 }];
1263 let base = Layout::new(&SYSV, REGS);
1264 let layout = Layout { leaf: false, locals: &locals, ..base };
1265 let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1266 let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1267
1268 // A page of the stack pointer's own, then the touch that says the page is there, and only
1269 // then the next one, which is the whole of the defence: nothing here ever moves the stack
1270 // pointer further than one page without writing where it landed. The last subtraction is
1271 // the remainder and is smaller than a page, so it needs no touch of its own, and it exists
1272 // in every frame because the count of pages is taken off one less than the size.
1273 assert_eq!(
1274 added(&lines),
1275 [
1276 "$rsp = x64.sub_ri_64 $rsp, 4096",
1277 "x64.or_mi_8 [$rsp], 0",
1278 "$rsp = x64.sub_ri_64 $rsp, 4096",
1279 "x64.or_mi_8 [$rsp], 0",
1280 "$rsp = x64.sub_ri_64 $rsp, 808",
1281 "$rsp = x64.add_ri_64 $rsp, 9000",
1282 "x64.ret",
1283 ]
1284 );
1285 }
1286
1287 #[test]
1288 fn a_probing_prologue_deeper_than_that_walks_the_pages_in_a_loop() {
1289 let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
1290 let locals = [Local { size: 100_000, align: 16 }];
1291 let base = Layout::new(&SYSV, REGS);
1292 let layout = Layout { leaf: false, locals: &locals, ..base };
1293 let probing = Probing { probe: &PROBE, branch: &BRANCH, scratch: [R10, R11] };
1294 let lines = with_probing(&mut func, &allocation, &layout, Some(probing), &mut names);
1295
1296 // Twenty-four pages, which is more than a straight line is worth, so the prologue works out
1297 // where it is going first and then walks there. The whole listing rather than the added
1298 // lines, because what matters as much as the instructions is that the two blocks the walk
1299 // is made of come in front of the block the function began with: the body the allocator
1300 // filled is block2 here and it was block0 before this ran.
1301 assert_eq!(
1302 lines,
1303 [
1304 "mfunc @f {",
1305 "block0:",
1306 "$r10 = x64.lea_64 [$rsp - 98304], block1",
1307 "block1:",
1308 "$rsp = x64.sub_ri_64 $rsp, 4096",
1309 "x64.or_mi_8 [$rsp], 0",
1310 "$r11 = x64.cmp_set_ne_64 $rsp, $r10",
1311 "x64.br_cond_8 $r11, block1, block2",
1312 "block2:",
1313 "$rsp = x64.sub_ri_64 $rsp, 1704",
1314 "$rax = x64.nop",
1315 "$rcx = x64.nop",
1316 "x64.nop $rax",
1317 "x64.nop $rcx",
1318 "$rsp = x64.add_ri_64 $rsp, 100008",
1319 "x64.ret",
1320 "}",
1321 ]
1322 );
1323 }
1324
1325 #[test]
1326 fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
1327 let mut names = Interner::new();
1328 let mut func = Func::new(names.intern("f"));
1329 let opcode = Opcode::new(names.intern("x64.nop"));
1330 let block = func.create_block();
1331 // An instruction that writes one of the vector registers Windows preserves, which is what
1332 // a rule for something that has to use it produces.
1333 func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
1334 let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4), "test");
1335 let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
1336
1337 // No machine here pushes a vector register, so it is stored into the frame rather than
1338 // pushed, and the frame has to be taken before there is anywhere to put it.
1339 assert_eq!(
1340 added(&lines),
1341 [
1342 "$rsp = x64.sub_ri_64 $rsp, 24",
1343 "x64.movaps_mr $xmm6, [$rsp]",
1344 "$xmm6 = x64.movaps_rm [$rsp]",
1345 "$rsp = x64.add_ri_64 $rsp, 24",
1346 "x64.ret",
1347 ]
1348 );
1349 }
1350}