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