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, 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 the convention this function is compiled for says a frame is.
91///
92/// Three answers to the one question, which is why they travel together: where it puts things,
93/// which instructions build one, and whether this function's carries a protector. The last is the
94/// only one that is about this function rather than about every function on the target, and it is
95/// here because what it needs is the other two and nothing else.
96#[derive(Debug, Clone, Copy)]
97pub struct Convention<'a> {
98 /// Where the convention puts things.
99 pub regs: &'a CallRegs,
100 /// The instructions a prologue, an epilogue, a spill and a reload are made of on it.
101 pub insts: &'a FrameInsts,
102 /// What this function's stack protector needs, or `None` in a function with none.
103 pub protect: Option<Protect<'a>>,
104}
105
106impl<'a> Convention<'a> {
107 /// That convention, for a function with no stack protector, which is most of them.
108 #[must_use]
109 pub fn new(regs: &'a CallRegs, insts: &'a FrameInsts) -> Self {
110 Self { regs, insts, protect: None }
111 }
112}
113
114/// Writes the moves, the prologue and the epilogue into a function the allocator has finished
115/// with.
116///
117/// # Panics
118///
119/// Panics on a function with no blocks in it, on a frame whose slots or locals the allocation and
120/// the lowering do not match, and on a move of a class the target did not say how to move. All of
121/// them are the caller handing it a frame and a function that were not worked out from each other.
122pub fn finish(
123 func: &mut Func,
124 allocation: &Allocation,
125 frame: &Frame,
126 stack: &Stack,
127 convention: Convention<'_>,
128 names: &mut Interner,
129) {
130 let Convention { regs: conv, insts, protect } = convention;
131 let entry = func.entry().expect("a function with a block in it");
132 let returns: Vec<Block> = func.blocks().filter(|&block| func[block].succs.is_empty()).collect();
133
134 // Before anything is written, because these are instructions the lowering already put in the
135 // function and every one of them is somewhere the prologue is about to go in front of, which
136 // is what makes an offset from the stack pointer the right thing to write into them.
137 for &(inst, local) in &stack.addresses {
138 let at = frame.local(local).expect("a local the frame was worked out from");
139 let mem = func[inst].mem.expect("the address of a local is an address");
140 func[mem].disp = at;
141 }
142
143 // The same, one area further up, and through the frame pointer when that is what reaches it.
144 // These are in the entry block ahead of everything, so the prologue still goes in front of
145 // them, which is what makes both registers hold what these offsets are counted from.
146 let incoming = frame.incoming();
147 for &(inst, up) in &stack.arguments {
148 let mem = func[inst].mem.expect("an argument read out of memory is read from an address");
149 func[mem].disp = incoming.at + offset(up);
150 if incoming.through_frame_pointer {
151 // The base register is an operand of the instruction and the addressing mode holds
152 // where in the operand vector it is, so the register is changed there and not here.
153 let at = func[mem].base.expect("an address the lowering wrote a base register into");
154 let operands = func[inst].operands;
155 func[operands][usize::from(at)].reg = Reg::physical(conv.frame_pointer);
156 }
157 }
158
159 let mut writer = Writer { func, conv, insts, names };
160
161 let mut cursors: Vec<(At, Inst)> = Vec::new();
162 for edit in &allocation.edits {
163 let inst = writer.mov(edit, frame);
164 writer.put(&mut cursors, edit.at, inst);
165 }
166
167 let prologue = writer.prologue(frame, protect);
168 for &inst in prologue.iter().rev() {
169 writer.func.prepend_inst(entry, inst);
170 }
171 for block in returns {
172 // The check goes in front of the epilogue and takes the return with it. What is left in
173 // the block the function used to return from is the check, and the block the epilogue then
174 // goes in is the arm the canary was unchanged on.
175 let block = match protect {
176 Some(protect) => writer.check(block, frame, protect),
177 None => block,
178 };
179 let epilogue = writer.epilogue(frame);
180 for inst in epilogue {
181 writer.func.append_inst(block, inst);
182 }
183 }
184}
185
186/// One function having its frame written into it.
187struct Writer<'a> {
188 func: &'a mut Func,
189 conv: &'a CallRegs,
190 insts: &'a FrameInsts,
191 names: &'a mut Interner,
192}
193
194impl Writer<'_> {
195 /// The instructions the prologue is, in the order they run.
196 ///
197 /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
198 /// before anything else, so that it points at a fixed place whatever else happens. The
199 /// registers are pushed before the alignment is forced, so that the epilogue can find them
200 /// again from the frame pointer, since after the alignment is forced nothing else can. And the
201 /// vector registers are stored last, because until the frame has been taken there is nowhere
202 /// to store them.
203 fn prologue(&mut self, frame: &Frame, protect: Option<Protect<'_>>) -> Vec<Inst> {
204 let sp = self.conv.stack_pointer;
205 let fp = self.conv.frame_pointer;
206 let int = self.conv.int_class;
207 let sse = self.conv.sse_class;
208 let word = offset(self.conv.word);
209 let mut out = Vec::new();
210 // How far the stack pointer is below the canonical frame address, and whether the address
211 // is still counted from the stack pointer at all. It starts at the return address the
212 // call itself pushed, which is the rule the CIE already states, so the first row here is
213 // the first thing this function does on top of that.
214 let mut below = offset(self.conv.return_address);
215 let mut from_sp = true;
216 if frame.frame_pointer() {
217 let inst = self.push(fp);
218 out.push(inst);
219 below += word;
220 self.row(inst, CfiOp::DefCfaOffset(below));
221 self.saved(inst, int, fp, -below);
222 let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
223 let inst = self.two(mov, fp, sp);
224 out.push(inst);
225 let number = self.dwarf(int, fp);
226 self.row(inst, CfiOp::DefCfaRegister(number));
227 from_sp = false;
228 }
229 for ® in frame.saved_int() {
230 let inst = self.push(reg);
231 out.push(inst);
232 below += word;
233 if from_sp {
234 self.row(inst, CfiOp::DefCfaOffset(below));
235 }
236 self.saved(inst, int, reg, -below);
237 }
238 if let Some(to) = frame.realign() {
239 // Nothing is written for this and nothing can be. After it the stack pointer is a
240 // rounded-down version of where it was rather than a fixed distance from it, which is
241 // exactly what a rule cannot say. It is also why a frame that realigns is a frame
242 // with a frame pointer: by here the address is already counted from that instead.
243 assert!(!from_sp, "a frame that forces its own alignment has a frame pointer");
244 let and = self.opcode(self.insts.align);
245 out.push(self.arith(and, -i64::from(to)));
246 }
247 if frame.size() > 0 {
248 let sub = self.opcode(self.insts.sub);
249 let inst = self.arith(sub, i64::from(frame.size()));
250 out.push(inst);
251 below += offset(frame.size());
252 if from_sp {
253 self.row(inst, CfiOp::DefCfaOffset(below));
254 }
255 }
256 for save in frame.saved_sse() {
257 let inst = self.store(sse, save.reg, save.at);
258 out.push(inst);
259 // Where it went is an offset from the stack pointer in the body, and the address is
260 // `below` above that, so the two make one constant. Unless the frame realigned, in
261 // which case there is no such constant and the rule is left out rather than guessed;
262 // the one convention that realigns and the one that preserves a vector register are
263 // not the same convention, so nothing reaches this today.
264 if frame.realign().is_none() {
265 self.saved(inst, sse, save.reg, save.at - below);
266 }
267 }
268 // Last of everything, because it writes into the frame and there is no frame to write into
269 // until the stack pointer has moved. Nothing is described for either instruction: they
270 // write a slot rather than save a register, and no unwinder wants to put a canary back.
271 if let Some(protect) = protect {
272 let at = frame.canary().expect("a protected function has a slot for its canary");
273 let [into, _] = protect.scratch;
274 out.push(self.read_guard(into, protect.guard));
275 out.push(self.store(self.conv.int_class, into, at));
276 }
277 // The rules the body runs under, kept so that each epilogue can put them back rather than
278 // leaving the next block reading whatever the last one ended on. See `epilogue`.
279 if let Some(&last) = out.last() {
280 self.row(last, CfiOp::RememberState);
281 }
282 out
283 }
284
285 /// The stack protector's check, written at the end of a block the function returns from.
286 ///
287 /// Gives back the block the epilogue goes in, which is a new one: the check has to be the last
288 /// thing the old block does, and what follows it is one of two arms rather than the return.
289 ///
290 /// ```text
291 /// block that returned reload the slot, read the word again, compare, branch
292 /// the arm it changed on call the function that does not come back, and nothing after
293 /// the arm it did not the epilogue, which the caller writes into what this gives back
294 /// ```
295 ///
296 /// The two registers are the ones the allocator was told to hold back, so nothing here has to
297 /// ask what is live: a scratch register holds nothing at the end of a block, because the only
298 /// thing that writes one is a move the rewriter put in and every one of those is read by the
299 /// instruction it was put in front of.
300 fn check(&mut self, block: Block, frame: &Frame, protect: Protect<'_>) -> Block {
301 let class = self.conv.int_class;
302 let at = frame.canary().expect("a protected function has a slot for its canary");
303 let [ours, theirs] = protect.scratch;
304
305 let inst = self.load(class, ours, at);
306 self.func.append_inst(block, inst);
307 let inst = self.read_guard(theirs, protect.guard);
308 self.func.append_inst(block, inst);
309 let differ = self.opcode(self.insts.differ);
310 let inst = self
311 .func
312 .build_loose(differ)
313 .def(Reg::physical(theirs), class)
314 .uses(Reg::physical(ours), class)
315 .uses(Reg::physical(theirs), class)
316 .finish();
317 self.func.append_inst(block, inst);
318
319 let failed = self.func.create_block();
320 let ok = self.func.create_block();
321 let cond = Opcode::new(
322 self.names.intern(&format!("{}{}", protect.branch.prefix, protect.branch.cond)),
323 );
324 let inst = self.func.build_loose(cond).uses(Reg::physical(theirs), class).finish();
325 self.func.append_inst(block, inst);
326 // The first arm is the one taken when the condition held, and the condition is that the
327 // two words differ, so the first arm is the one the canary was overwritten on.
328 *self.func.succs_mut(block) = vec![BlockCall::to(failed), BlockCall::to(ok)];
329
330 let call = self.opcode(self.insts.call);
331 let symbol = self.names.intern(protect.guard.fail);
332 self.func.build(failed, call).symbol(symbol).finish();
333 ok
334 }
335
336 /// Reads the word the canary is a copy of into a register.
337 ///
338 /// The address is a constant and names no register at all, because where the block a thread
339 /// has to itself begins is something only the machine knows and the segment register is what
340 /// holds it.
341 fn read_guard(&mut self, into: PhysReg, guard: &Guard) -> Inst {
342 let class = self.conv.int_class;
343 let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
344 self.func
345 .build_loose(load)
346 .def(Reg::physical(into), class)
347 .mem(Mem::in_segment(guard.segment, guard.at))
348 .finish()
349 }
350
351 /// The instructions the epilogue is, in the order they run.
352 ///
353 /// The vector registers are read back while the stack pointer is still where the body left it,
354 /// because that is what their offsets are from. Then the stack pointer goes back to the last
355 /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
356 /// moved and a read of the frame pointer when it did not.
357 fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
358 let sp = self.conv.stack_pointer;
359 let fp = self.conv.frame_pointer;
360 let int = self.conv.int_class;
361 let sse = self.conv.sse_class;
362 let word = self.conv.word;
363 let described = !self.func.cfi.is_empty();
364 let mut out = Vec::new();
365 // Where the body left things, which is where every epilogue starts from.
366 let mut below = offset(self.conv.return_address)
367 + offset(word) * self.pushes(frame)
368 + offset(frame.size());
369 let from_sp = !frame.frame_pointer();
370 for save in frame.saved_sse() {
371 let inst = self.load(sse, save.reg, save.at);
372 out.push(inst);
373 if frame.realign().is_none() {
374 self.restored(inst, sse, save.reg);
375 }
376 }
377 let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
378 if frame.frame_pointer() {
379 // No row for either of these. The address is counted from the frame pointer here and
380 // this is what moves the stack pointer rather than the frame pointer, so the rule that
381 // was true before it is still true after it.
382 if pushed == 0 {
383 let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
384 out.push(self.two(mov, sp, fp));
385 } else {
386 let lea = self.opcode(self.insts.lea);
387 let back = -offset(word * pushed);
388 out.push(self.address(lea, sp, fp, back));
389 }
390 } else if frame.size() > 0 {
391 let add = self.opcode(self.insts.add);
392 let inst = self.arith(add, i64::from(frame.size()));
393 out.push(inst);
394 below -= offset(frame.size());
395 self.row(inst, CfiOp::DefCfaOffset(below));
396 }
397 for ® in frame.saved_int().iter().rev() {
398 let inst = self.pop(reg);
399 out.push(inst);
400 self.restored(inst, int, reg);
401 below -= offset(word);
402 if from_sp {
403 self.row(inst, CfiOp::DefCfaOffset(below));
404 }
405 }
406 if frame.frame_pointer() {
407 let inst = self.pop(fp);
408 out.push(inst);
409 self.restored(inst, int, fp);
410 // The frame pointer holds the caller's value again, so the address goes back to being
411 // counted from the stack pointer, which by now is at the return address.
412 let number = self.dwarf(int, sp);
413 self.row(inst, CfiOp::DefCfa { reg: number, offset: offset(self.conv.return_address) });
414 }
415 let ret = self.opcode(self.insts.ret);
416 let inst = self.func.build_loose(ret).finish();
417 out.push(inst);
418 // These take effect at the address just past the return, which is where the next block
419 // begins, and the next block is body again. Popping the body's rules and pushing them
420 // straight back leaves the stack one deep however many blocks the function returns from,
421 // which is what makes one remembering in the prologue enough for all of them.
422 if described {
423 self.row(inst, CfiOp::RestoreState);
424 self.row(inst, CfiOp::RememberState);
425 }
426 out
427 }
428
429 /// How many general purpose registers the prologue put on the stack, the frame pointer
430 /// included.
431 fn pushes(&self, frame: &Frame) -> i32 {
432 let saved = i32::try_from(frame.saved_int().len()).expect("a frame");
433 saved + i32::from(frame.frame_pointer())
434 }
435
436 /// One row of the unwind table, taking effect after that instruction.
437 fn row(&mut self, inst: Inst, op: CfiOp) {
438 self.func.cfi.push((inst, op));
439 }
440
441 /// A row saying the caller's copy of that register is that far from the canonical frame
442 /// address, which is below it and so is negative.
443 fn saved(&mut self, inst: Inst, class: RegClass, reg: PhysReg, from_cfa: i32) {
444 let number = self.dwarf(class, reg);
445 self.row(inst, CfiOp::Offset { reg: number, offset: from_cfa });
446 }
447
448 /// A row saying that register holds what the caller left in it again.
449 fn restored(&mut self, inst: Inst, class: RegClass, reg: PhysReg) {
450 let number = self.dwarf(class, reg);
451 self.row(inst, CfiOp::Restore(number));
452 }
453
454 /// What an unwind table calls that register.
455 fn dwarf(&self, class: RegClass, reg: PhysReg) -> u16 {
456 self.conv.dwarf(class, reg).expect("a register a frame saves is one the table can name")
457 }
458
459 /// One edit as the instruction that makes it true.
460 fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
461 let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
462 match (edit.mov.to, edit.mov.from) {
463 (Place::Reg(to), Place::Reg(from)) => {
464 let mov = self.opcode(moves.mov);
465 self.func
466 .build_loose(mov)
467 .def(Reg::physical(to), edit.class)
468 .uses(Reg::physical(from), edit.class)
469 .finish()
470 }
471 (Place::Reg(to), Place::Slot(slot)) => {
472 let at = self.slot(frame, slot);
473 self.load(edit.class, to, at)
474 }
475 (Place::Slot(slot), Place::Reg(from)) => {
476 let at = self.slot(frame, slot);
477 self.store(edit.class, from, at)
478 }
479 // The allocator expands this into two moves through a register of its own, because a
480 // machine that could do it in one is not a machine any of this is written for.
481 (Place::Slot(_), Place::Slot(_)) => {
482 unreachable!("a move from one stack slot straight into another")
483 }
484 }
485 }
486
487 /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
488 ///
489 /// The edits at one place are in the order they have to be made in, so each one goes behind
490 /// the last, and the first of them is what the place itself means.
491 fn put(&mut self, cursors: &mut Vec<(At, Inst)>, at: At, inst: Inst) {
492 if let Some(cursor) = cursors.iter_mut().find(|(place, _)| *place == at) {
493 self.func.insert_after(cursor.1, inst);
494 cursor.1 = inst;
495 return;
496 }
497 match at {
498 At::Before(before) => self.func.insert_before(before, inst),
499 At::After(after) => self.func.insert_after(after, inst),
500 At::StartOf(block) => self.func.prepend_inst(block, inst),
501 // Behind everything in the block. A block the allocator puts an edge's moves at the
502 // end of is one with a single edge out of it, and an edge like that is not an
503 // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
504 // So the last instruction is an ordinary one, which may still be waiting on moves of
505 // its own that have to be made before the edge's are.
506 At::EndOf(block) => self.func.append_inst(block, inst),
507 }
508 cursors.push((at, inst));
509 }
510
511 /// Where a spill slot is, from the stack pointer in the body of the function.
512 fn slot(&self, frame: &Frame, slot: u32) -> i32 {
513 frame.slot(slot).expect("a slot the frame was worked out from")
514 }
515
516 /// Reads a register out of the frame.
517 fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
518 let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
519 let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
520 self.func
521 .build_loose(load)
522 .def(Reg::physical(reg), class)
523 .mem(Mem::at(base).plus(at))
524 .finish()
525 }
526
527 /// Writes a register into the frame.
528 fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
529 let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
530 let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
531 self.func
532 .build_loose(store)
533 .uses(Reg::physical(reg), class)
534 .mem(Mem::at(base).plus(at))
535 .finish()
536 }
537
538 /// Puts a general purpose register on the stack.
539 fn push(&mut self, reg: PhysReg) -> Inst {
540 let push = self.opcode(self.insts.push);
541 self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
542 }
543
544 /// Takes a general purpose register back off the stack.
545 fn pop(&mut self, reg: PhysReg) -> Inst {
546 let pop = self.opcode(self.insts.pop);
547 self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
548 }
549
550 /// One general purpose register written with another.
551 fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
552 let class = self.conv.int_class;
553 self.func
554 .build_loose(opcode)
555 .def(Reg::physical(to), class)
556 .uses(Reg::physical(from), class)
557 .finish()
558 }
559
560 /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
561 fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
562 let class = self.conv.int_class;
563 let sp = Reg::physical(self.conv.stack_pointer);
564 self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
565 }
566
567 /// One register written with an address rather than with what is at it.
568 fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
569 let class = self.conv.int_class;
570 let base = Operand::read(Reg::physical(base), class);
571 self.func
572 .build_loose(opcode)
573 .def(Reg::physical(to), class)
574 .mem(Mem::at(base).plus(disp))
575 .finish()
576 }
577
578 /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
579 /// then the name the target gave.
580 fn opcode(&mut self, name: &str) -> Opcode {
581 Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
582 }
583}
584
585/// A distance in a frame, as the signed number every offset is.
586fn offset(bytes: u32) -> i32 {
587 i32::try_from(bytes).expect("a frame under two gigabytes")
588}
589
590#[cfg(test)]
591mod tests {
592 use rucc_base::Interner;
593 use rucc_mir::{BlockCall, print_func};
594 use rucc_regalloc::assign::Env;
595 use rucc_target::x86_64::{BRANCH, FRAME, GPR, R10, R11, REGS, SYSV, WIN64, XMM, xmm};
596
597 use super::*;
598 use crate::frame::{Layout, Local};
599
600 /// An environment offering that many of the convention's registers, with everything after
601 /// them held back as scratch.
602 fn env(conv: &CallRegs, count: usize) -> Env {
603 Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
604 }
605
606 /// A function of that many values, every one written before any is read, allocated with that
607 /// many registers to hand out. The same shape the frame layout's own tests are written
608 /// against, so that a frame here is one that has already been checked there.
609 fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
610 let mut names = Interner::new();
611 let mut func = Func::new(names.intern("f"));
612 let opcode = Opcode::new(names.intern("x64.nop"));
613 let block = func.create_block();
614 let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
615 for ® in ®s {
616 func.build(block, opcode).def(reg, GPR).finish();
617 }
618 for ® in ®s {
619 func.build(block, opcode).uses(reg, GPR).finish();
620 }
621 let allocation = rucc_regalloc::run(&mut func, &env(conv, count), "test");
622 (func, allocation, names)
623 }
624
625 /// The function with its frame written into it, as the lines a dump would show.
626 fn written(
627 func: &mut Func,
628 allocation: &Allocation,
629 layout: &Layout<'_>,
630 names: &mut Interner,
631 ) -> Vec<String> {
632 with_protector(func, allocation, layout, None, names)
633 }
634
635 /// The same, for a function the caller has decided is protected or is not.
636 fn with_protector(
637 func: &mut Func,
638 allocation: &Allocation,
639 layout: &Layout<'_>,
640 protect: Option<Protect<'_>>,
641 names: &mut Interner,
642 ) -> Vec<String> {
643 let frame = Frame::of(func, allocation, layout);
644 let convention = Convention { protect, ..Convention::new(layout.conv, &FRAME) };
645 finish(func, allocation, &frame, &Stack::default(), convention, names);
646 print_func(func, names, ®S)
647 .lines()
648 .filter(|line| !line.is_empty())
649 .map(|line| line.trim().to_string())
650 .collect()
651 }
652
653 /// Just the lines the frame put in, which is every line that is not the function it was
654 /// given and not the shape of the dump around it.
655 fn added(lines: &[String]) -> Vec<&str> {
656 lines
657 .iter()
658 .map(String::as_str)
659 .filter(|line| !line.contains("x64.nop"))
660 .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
661 .collect()
662 }
663
664 #[test]
665 fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
666 let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
667 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
668
669 // Two values and four registers, so nothing is spilled, nothing is saved and the stack
670 // pointer never moves. A prologue of nothing is the right prologue for that.
671 assert_eq!(added(&lines), ["x64.ret"]);
672 }
673
674 #[test]
675 fn a_spill_is_a_store_and_a_reload_is_a_load() {
676 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
677 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
678
679 // Two registers for four values, so two of them go to the stack. The store goes behind the
680 // instruction that wrote the value and the load in front of the one that wants it, both at
681 // the offsets the frame gave, which are below the stack pointer because a small leaf
682 // function is entitled to the red zone.
683 assert_eq!(
684 lines,
685 [
686 "mfunc @f {",
687 "block0:",
688 "$rax = x64.nop",
689 "$rcx = x64.nop",
690 "$rdx = x64.nop",
691 "x64.mov_mr_64 $rdx, [$rsp - 16]",
692 "$rdx = x64.nop",
693 "x64.mov_mr_64 $rdx, [$rsp - 8]",
694 "x64.nop $rax",
695 "x64.nop $rcx",
696 "$rdx = x64.mov_rm_64 [$rsp - 16]",
697 "x64.nop $rdx",
698 "$rdx = x64.mov_rm_64 [$rsp - 8]",
699 "x64.nop $rdx",
700 "x64.ret",
701 "}",
702 ]
703 );
704 }
705
706 #[test]
707 fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
708 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
709 let base = Layout::new(&SYSV, REGS);
710 let layout = Layout { red_zone: false, ..base };
711 let lines = written(&mut func, &allocation, &layout, &mut names);
712
713 // The same function told it may not use the red zone takes sixteen bytes instead, and
714 // every offset moves above the stack pointer to match.
715 assert_eq!(
716 added(&lines),
717 [
718 "$rsp = x64.sub_ri_64 $rsp, 16",
719 "x64.mov_mr_64 $rdx, [$rsp]",
720 "x64.mov_mr_64 $rdx, [$rsp + 8]",
721 "$rdx = x64.mov_rm_64 [$rsp]",
722 "$rdx = x64.mov_rm_64 [$rsp + 8]",
723 "$rsp = x64.add_ri_64 $rsp, 16",
724 "x64.ret",
725 ]
726 );
727 }
728
729 #[test]
730 fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
731 let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
732 let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
733
734 // Four registers a call leaves alone, pushed in the convention's order and popped in the
735 // other one, which is the only order that gets each of them its own value back.
736 assert_eq!(
737 added(&lines),
738 [
739 "x64.push_64 $rbx",
740 "x64.push_64 $r12",
741 "x64.push_64 $r13",
742 "x64.push_64 $r14",
743 "$r14 = x64.pop_64",
744 "$r13 = x64.pop_64",
745 "$r12 = x64.pop_64",
746 "$rbx = x64.pop_64",
747 "x64.ret",
748 ]
749 );
750 }
751
752 #[test]
753 fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
754 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
755 let base = Layout::new(&SYSV, REGS);
756 let layout = Layout { frame_pointer: true, red_zone: false, ..base };
757 let lines = written(&mut func, &allocation, &layout, &mut names);
758
759 // The frame pointer is saved before anything else and points at where it was saved, so the
760 // epilogue reaches the stack pointer through it rather than by counting the frame back.
761 assert_eq!(
762 added(&lines),
763 [
764 "x64.push_64 $rbp",
765 "$rbp = x64.mov_rr_64 $rsp",
766 "$rsp = x64.sub_ri_64 $rsp, 16",
767 "x64.mov_mr_64 $rdx, [$rsp]",
768 "x64.mov_mr_64 $rdx, [$rsp + 8]",
769 "$rdx = x64.mov_rm_64 [$rsp]",
770 "$rdx = x64.mov_rm_64 [$rsp + 8]",
771 "$rsp = x64.mov_rr_64 $rbp",
772 "$rbp = x64.pop_64",
773 "x64.ret",
774 ]
775 );
776 }
777
778 #[test]
779 fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
780 let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
781 let locals = [Local { size: 64, align: 32 }];
782 let base = Layout::new(&SYSV, REGS);
783 let layout = Layout { locals: &locals, ..base };
784 let lines = written(&mut func, &allocation, &layout, &mut names);
785
786 // Forcing the alignment throws away how far the stack pointer had moved, so the registers
787 // are pushed before it happens and the epilogue counts back from the frame pointer to find
788 // them. The frame pointer is required here whatever the flags said.
789 assert_eq!(
790 added(&lines),
791 [
792 "x64.push_64 $rbp",
793 "$rbp = x64.mov_rr_64 $rsp",
794 "x64.push_64 $rbx",
795 "x64.push_64 $r12",
796 "x64.push_64 $r13",
797 "x64.push_64 $r14",
798 "$rsp = x64.and_ri_64 $rsp, -32",
799 "$rsp = x64.sub_ri_64 $rsp, 64",
800 "$rsp = x64.lea_64 [$rbp - 32]",
801 "$r14 = x64.pop_64",
802 "$r13 = x64.pop_64",
803 "$r12 = x64.pop_64",
804 "$rbx = x64.pop_64",
805 "$rbp = x64.pop_64",
806 "x64.ret",
807 ]
808 );
809 }
810
811 #[test]
812 fn every_block_the_function_returns_from_gets_an_epilogue() {
813 let mut names = Interner::new();
814 let mut func = Func::new(names.intern("f"));
815 let opcode = Opcode::new(names.intern("x64.nop"));
816 let head = func.create_block();
817 let left = func.create_block();
818 let right = func.create_block();
819 func.build(head, opcode).finish();
820 *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
821 func.build(left, opcode).finish();
822 func.build(right, opcode).finish();
823 let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4), "test");
824 let base = Layout::new(&SYSV, REGS);
825 let layout = Layout { leaf: false, ..base };
826 let lines = written(&mut func, &allocation, &layout, &mut names);
827
828 // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
829 // because a block with an edge out of it is not a block anything returns from.
830 assert_eq!(
831 lines,
832 [
833 "mfunc @f {",
834 "block0:",
835 "$rsp = x64.sub_ri_64 $rsp, 8",
836 "x64.nop block1, block2",
837 "block1:",
838 "x64.nop",
839 "$rsp = x64.add_ri_64 $rsp, 8",
840 "x64.ret",
841 "block2:",
842 "x64.nop",
843 "$rsp = x64.add_ri_64 $rsp, 8",
844 "x64.ret",
845 "}",
846 ]
847 );
848 }
849
850 #[test]
851 fn a_protected_function_writes_the_canary_last_and_checks_it_before_it_returns() {
852 let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
853 let base = Layout::new(&SYSV, REGS);
854 let layout = Layout { leaf: false, protect: true, ..base };
855 let guard = SYSV.guard.as_ref().expect("this convention has somewhere to keep the word");
856 // The two the real pipeline holds back, which are held back in the environment above too:
857 // it hands out the first two of the convention's order and keeps everything after them.
858 let protect = Protect { guard, branch: &BRANCH, scratch: [R10, R11] };
859 let lines = with_protector(&mut func, &allocation, &layout, Some(protect), &mut names);
860
861 // The read of the word and the store into the slot come after the stack pointer has moved,
862 // because there is no slot to store into until it has. The check is the last thing the
863 // block that returned does and the epilogue is on the arm the canary was unchanged on, so
864 // a function whose canary changed never gives its frame back and never returns.
865 assert_eq!(
866 added(&lines),
867 [
868 "$rsp = x64.sub_ri_64 $rsp, 24",
869 "$r10 = x64.mov_rm_64 [fs:40]",
870 "x64.mov_mr_64 $r10, [$rsp + 16]",
871 "x64.mov_mr_64 $rdx, [$rsp]",
872 "x64.mov_mr_64 $rdx, [$rsp + 8]",
873 "$rdx = x64.mov_rm_64 [$rsp]",
874 "$rdx = x64.mov_rm_64 [$rsp + 8]",
875 "$r10 = x64.mov_rm_64 [$rsp + 16]",
876 "$r11 = x64.mov_rm_64 [fs:40]",
877 "$r11 = x64.cmp_set_ne_64 $r10, $r11",
878 "x64.br_cond_8 $r11, block1, block2",
879 "x64.call @__stack_chk_fail",
880 "$rsp = x64.add_ri_64 $rsp, 24",
881 "x64.ret",
882 ]
883 );
884 }
885
886 #[test]
887 fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
888 let mut names = Interner::new();
889 let mut func = Func::new(names.intern("f"));
890 let opcode = Opcode::new(names.intern("x64.nop"));
891 let block = func.create_block();
892 // An instruction that writes one of the vector registers Windows preserves, which is what
893 // a rule for something that has to use it produces.
894 func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
895 let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4), "test");
896 let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
897
898 // No machine here pushes a vector register, so it is stored into the frame rather than
899 // pushed, and the frame has to be taken before there is anywhere to put it.
900 assert_eq!(
901 added(&lines),
902 [
903 "$rsp = x64.sub_ri_64 $rsp, 24",
904 "x64.movaps_mr $xmm6, [$rsp]",
905 "$xmm6 = x64.movaps_rm [$rsp]",
906 "$rsp = x64.add_ri_64 $rsp, 24",
907 "x64.ret",
908 ]
909 );
910 }
911}