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