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