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