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