Skip to main content

rucc_codegen/
finish.rs

1//! The prologue, the epilogue, and the moves the allocator asked for.
2//!
3//! Design: `spec/10-backend.md` sections 10.4 and 10.7.
4//!
5//! [`crate::frame`] works out what a function's stack looks like and writes nothing. This is what
6//! writes it. Three things are still missing from a function the allocator has finished with, and
7//! all three of them are instructions no lowering rule chose:
8//!
9//! ```text
10//!   the prologue     takes the frame the layout worked out, and puts away the registers a call
11//!                    leaves alone that this function writes anyway
12//!   the moves        every spill, every reload and every copy the allocator handed back as an
13//!                    edit, in the place it said and in the order it said
14//!   the epilogue     gives the frame back and puts the registers back, at the end of every block
15//!                    the function returns from
16//! ```
17//!
18//! There is a fourth thing and it is not an instruction but a number. The lowering wrote an
19//! instruction for every `alloca` that computes the address of the memory it asked for, and could
20//! not write how far into the frame that memory is, because when it ran there was no frame. So
21//! the displacement of each of those is filled in here, out of the same [`Frame`] everything else
22//! here reads, and off the same stack pointer every other offset in it is from.
23//!
24//! The loads that read the arguments the caller passed on the stack are waiting on the same number
25//! and on one more. Those bytes are the caller's rather than this function's, and a frame that had
26//! to force its own alignment cannot say how far away the caller's stack pointer was, so it reaches
27//! back through the frame pointer instead. Which register a load reads through is therefore settled
28//! here too, and it is the only base register in a finished function that was not settled by
29//! whoever wrote the instruction.
30//!
31//! After this the function is one an encoder can read: every register is physical, every offset
32//! into the frame is a constant, and the stack pointer is where the convention says it should be
33//! at every instruction that could look.
34//!
35//! # Why the moves go in first
36//!
37//! Every offset the frame reports is from the stack pointer as it stands in the body of the
38//! function. A spill written before the prologue exists would be written in front of the
39//! instruction it belongs to and behind nothing, which is where the prologue then goes, so the
40//! prologue ends up in front of it and the offsets stay true. Writing them the other way round
41//! would put the first reload above the instruction that takes the frame, and it would read from
42//! an address that is one frame out.
43//!
44//! # Where a return is
45//!
46//! A block that goes nowhere is a block the function leaves from. Mostly that is a return, and
47//! the other kind is a block ending in `unreachable`, which is a point the front end says control
48//! does not arrive at and which the lowering writes no instruction for. Both want the same thing
49//! here. A return wants the epilogue because that is what a return is once the frame is known,
50//! and an unreachable block wants it because the alternative is a function whose last instruction
51//! falls into whatever the assembler put after it, which is worse than an epilogue nothing runs.
52//! So the epilogue goes at the end of every block with an empty successor list, and there may be
53//! several, because nothing here insists a function has one exit.
54//!
55//! # What is target-specific here
56//!
57//! The names, and only the names. Which instruction pushes a register and which one moves the
58//! stack pointer is [`rucc_target::FrameInsts`], which the target says and this reads, so what
59//! is written below is the shape of a prologue rather than any particular machine's. That is
60//! `spec/10-backend.md` section 10.8 as it applies to the one pass that would otherwise be full
61//! of `x64.` by hand.
62
63use rucc_base::Interner;
64use rucc_mir::{Block, Func, Inst, Mem, Opcode, Operand, Reg};
65use rucc_regalloc::Allocation;
66use rucc_regalloc::assign::Place;
67use rucc_regalloc::rewrite::{At, Edit};
68use rucc_target::{CallRegs, FrameInsts, PhysReg, RegClass};
69
70use crate::frame::Frame;
71use crate::lower::Stack;
72
73/// Writes the moves, the prologue and the epilogue into a function the allocator has finished
74/// with.
75///
76/// # Panics
77///
78/// Panics on a function with no blocks in it, on a frame whose slots or locals the allocation and
79/// the lowering do not match, and on a move of a class the target did not say how to move. All of
80/// them are the caller handing it a frame and a function that were not worked out from each other.
81pub fn finish(
82    func: &mut Func,
83    allocation: &Allocation,
84    frame: &Frame,
85    stack: &Stack,
86    conv: &CallRegs,
87    insts: &FrameInsts,
88    names: &mut Interner,
89) {
90    let entry = func.entry().expect("a function with a block in it");
91    let returns: Vec<Block> = func.blocks().filter(|&block| func[block].succs.is_empty()).collect();
92
93    // Before anything is written, because these are instructions the lowering already put in the
94    // function and every one of them is somewhere the prologue is about to go in front of, which
95    // is what makes an offset from the stack pointer the right thing to write into them.
96    for &(inst, local) in &stack.addresses {
97        let at = frame.local(local).expect("a local the frame was worked out from");
98        let mem = func[inst].mem.expect("the address of a local is an address");
99        func[mem].disp = at;
100    }
101
102    // The same, one area further up, and through the frame pointer when that is what reaches it.
103    // These are in the entry block ahead of everything, so the prologue still goes in front of
104    // them, which is what makes both registers hold what these offsets are counted from.
105    let incoming = frame.incoming();
106    for &(inst, up) in &stack.arguments {
107        let mem = func[inst].mem.expect("an argument read out of memory is read from an address");
108        func[mem].disp = incoming.at + offset(up);
109        if incoming.through_frame_pointer {
110            // The base register is an operand of the instruction and the addressing mode holds
111            // where in the operand vector it is, so the register is changed there and not here.
112            let at = func[mem].base.expect("an address the lowering wrote a base register into");
113            let operands = func[inst].operands;
114            func[operands][usize::from(at)].reg = Reg::physical(conv.frame_pointer);
115        }
116    }
117
118    let mut writer = Writer { func, conv, insts, names };
119
120    let mut cursors: Vec<(At, Inst)> = Vec::new();
121    for edit in &allocation.edits {
122        let inst = writer.mov(edit, frame);
123        writer.put(&mut cursors, edit.at, inst);
124    }
125
126    let prologue = writer.prologue(frame);
127    for &inst in prologue.iter().rev() {
128        writer.func.prepend_inst(entry, inst);
129    }
130    for block in returns {
131        let epilogue = writer.epilogue(frame);
132        for inst in epilogue {
133            writer.func.append_inst(block, inst);
134        }
135    }
136}
137
138/// One function having its frame written into it.
139struct Writer<'a> {
140    func: &'a mut Func,
141    conv: &'a CallRegs,
142    insts: &'a FrameInsts,
143    names: &'a mut Interner,
144}
145
146impl Writer<'_> {
147    /// The instructions the prologue is, in the order they run.
148    ///
149    /// The order is the one the epilogue undoes and it is not free. The frame pointer is saved
150    /// before anything else, so that it points at a fixed place whatever else happens. The
151    /// registers are pushed before the alignment is forced, so that the epilogue can find them
152    /// again from the frame pointer, since after the alignment is forced nothing else can. And the
153    /// vector registers are stored last, because until the frame has been taken there is nowhere
154    /// to store them.
155    fn prologue(&mut self, frame: &Frame) -> Vec<Inst> {
156        let sp = self.conv.stack_pointer;
157        let fp = self.conv.frame_pointer;
158        let mut out = Vec::new();
159        if frame.frame_pointer() {
160            out.push(self.push(fp));
161            let mov = self.opcode(self.insts.moves(self.conv.int_class).expect("a move").mov);
162            out.push(self.two(mov, fp, sp));
163        }
164        for &reg in frame.saved_int() {
165            out.push(self.push(reg));
166        }
167        if let Some(to) = frame.realign() {
168            let and = self.opcode(self.insts.align);
169            out.push(self.arith(and, -i64::from(to)));
170        }
171        if frame.size() > 0 {
172            let sub = self.opcode(self.insts.sub);
173            out.push(self.arith(sub, i64::from(frame.size())));
174        }
175        for save in frame.saved_sse() {
176            out.push(self.store(self.conv.sse_class, save.reg, save.at));
177        }
178        out
179    }
180
181    /// The instructions the epilogue is, in the order they run.
182    ///
183    /// The vector registers are read back while the stack pointer is still where the body left it,
184    /// because that is what their offsets are from. Then the stack pointer goes back to the last
185    /// register the prologue pushed, which is arithmetic when the prologue knew how far it had
186    /// moved and a read of the frame pointer when it did not.
187    fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
188        let sp = self.conv.stack_pointer;
189        let fp = self.conv.frame_pointer;
190        let word = self.conv.word;
191        let mut out = Vec::new();
192        for save in frame.saved_sse() {
193            out.push(self.load(self.conv.sse_class, save.reg, save.at));
194        }
195        let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
196        if frame.frame_pointer() {
197            if pushed == 0 {
198                let mov = self.opcode(self.insts.moves(self.conv.int_class).expect("a move").mov);
199                out.push(self.two(mov, sp, fp));
200            } else {
201                let lea = self.opcode(self.insts.lea);
202                let back = -offset(word * pushed);
203                out.push(self.address(lea, sp, fp, back));
204            }
205        } else if frame.size() > 0 {
206            let add = self.opcode(self.insts.add);
207            out.push(self.arith(add, i64::from(frame.size())));
208        }
209        for &reg in frame.saved_int().iter().rev() {
210            out.push(self.pop(reg));
211        }
212        if frame.frame_pointer() {
213            out.push(self.pop(fp));
214        }
215        let ret = self.opcode(self.insts.ret);
216        out.push(self.func.build_loose(ret).finish());
217        out
218    }
219
220    /// One edit as the instruction that makes it true.
221    fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
222        let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
223        match (edit.mov.to, edit.mov.from) {
224            (Place::Reg(to), Place::Reg(from)) => {
225                let mov = self.opcode(moves.mov);
226                self.func
227                    .build_loose(mov)
228                    .def(Reg::physical(to), edit.class)
229                    .uses(Reg::physical(from), edit.class)
230                    .finish()
231            }
232            (Place::Reg(to), Place::Slot(slot)) => {
233                let at = self.slot(frame, slot);
234                self.load(edit.class, to, at)
235            }
236            (Place::Slot(slot), Place::Reg(from)) => {
237                let at = self.slot(frame, slot);
238                self.store(edit.class, from, at)
239            }
240            // The allocator expands this into two moves through a register of its own, because a
241            // machine that could do it in one is not a machine any of this is written for.
242            (Place::Slot(_), Place::Slot(_)) => {
243                unreachable!("a move from one stack slot straight into another")
244            }
245        }
246    }
247
248    /// Puts an instruction where an edit says it goes, after whatever earlier edits went there.
249    ///
250    /// The edits at one place are in the order they have to be made in, so each one goes behind
251    /// the last, and the first of them is what the place itself means.
252    fn put(&mut self, cursors: &mut Vec<(At, Inst)>, at: At, inst: Inst) {
253        if let Some(cursor) = cursors.iter_mut().find(|(place, _)| *place == at) {
254            self.func.insert_after(cursor.1, inst);
255            cursor.1 = inst;
256            return;
257        }
258        match at {
259            At::Before(before) => self.func.insert_before(before, inst),
260            At::After(after) => self.func.insert_after(after, inst),
261            At::StartOf(block) => self.func.prepend_inst(block, inst),
262            // Behind everything in the block. A block the allocator puts an edge's moves at the
263            // end of is one with a single edge out of it, and an edge like that is not an
264            // instruction here: [`crate::layout`] writes the jump it becomes after this has run.
265            // So the last instruction is an ordinary one, which may still be waiting on moves of
266            // its own that have to be made before the edge's are.
267            At::EndOf(block) => self.func.append_inst(block, inst),
268        }
269        cursors.push((at, inst));
270    }
271
272    /// Where a spill slot is, from the stack pointer in the body of the function.
273    fn slot(&self, frame: &Frame, slot: u32) -> i32 {
274        frame.slot(slot).expect("a slot the frame was worked out from")
275    }
276
277    /// Reads a register out of the frame.
278    fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
279        let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
280        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
281        self.func
282            .build_loose(load)
283            .def(Reg::physical(reg), class)
284            .mem(Mem::at(base).plus(at))
285            .finish()
286    }
287
288    /// Writes a register into the frame.
289    fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
290        let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
291        let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
292        self.func
293            .build_loose(store)
294            .uses(Reg::physical(reg), class)
295            .mem(Mem::at(base).plus(at))
296            .finish()
297    }
298
299    /// Puts a general purpose register on the stack.
300    fn push(&mut self, reg: PhysReg) -> Inst {
301        let push = self.opcode(self.insts.push);
302        self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
303    }
304
305    /// Takes a general purpose register back off the stack.
306    fn pop(&mut self, reg: PhysReg) -> Inst {
307        let pop = self.opcode(self.insts.pop);
308        self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
309    }
310
311    /// One general purpose register written with another.
312    fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
313        let class = self.conv.int_class;
314        self.func
315            .build_loose(opcode)
316            .def(Reg::physical(to), class)
317            .uses(Reg::physical(from), class)
318            .finish()
319    }
320
321    /// Two-address arithmetic on the stack pointer, which reads it and writes it back.
322    fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
323        let class = self.conv.int_class;
324        let sp = Reg::physical(self.conv.stack_pointer);
325        self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
326    }
327
328    /// One register written with an address rather than with what is at it.
329    fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
330        let class = self.conv.int_class;
331        let base = Operand::read(Reg::physical(base), class);
332        self.func
333            .build_loose(opcode)
334            .def(Reg::physical(to), class)
335            .mem(Mem::at(base).plus(disp))
336            .finish()
337    }
338
339    /// The opcode of that name, in the machine IR's spelling, which is the target's prefix and
340    /// then the name the target gave.
341    fn opcode(&mut self, name: &str) -> Opcode {
342        Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
343    }
344}
345
346/// A distance in a frame, as the signed number every offset is.
347fn offset(bytes: u32) -> i32 {
348    i32::try_from(bytes).expect("a frame under two gigabytes")
349}
350
351#[cfg(test)]
352mod tests {
353    use rucc_base::Interner;
354    use rucc_mir::{BlockCall, print_func};
355    use rucc_regalloc::assign::Env;
356    use rucc_target::x86_64::{FRAME, GPR, REGS, SYSV, WIN64, XMM, xmm};
357
358    use super::*;
359    use crate::frame::{Layout, Local};
360
361    /// An environment offering that many of the convention's registers, with everything after
362    /// them held back as scratch.
363    fn env(conv: &CallRegs, count: usize) -> Env {
364        Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
365    }
366
367    /// A function of that many values, every one written before any is read, allocated with that
368    /// many registers to hand out. The same shape the frame layout's own tests are written
369    /// against, so that a frame here is one that has already been checked there.
370    fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
371        let mut names = Interner::new();
372        let mut func = Func::new(names.intern("f"));
373        let opcode = Opcode::new(names.intern("x64.nop"));
374        let block = func.create_block();
375        let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
376        for &reg in &regs {
377            func.build(block, opcode).def(reg, GPR).finish();
378        }
379        for &reg in &regs {
380            func.build(block, opcode).uses(reg, GPR).finish();
381        }
382        let allocation = rucc_regalloc::run(&mut func, &env(conv, count));
383        (func, allocation, names)
384    }
385
386    /// The function with its frame written into it, as the lines a dump would show.
387    fn written(
388        func: &mut Func,
389        allocation: &Allocation,
390        layout: &Layout<'_>,
391        names: &mut Interner,
392    ) -> Vec<String> {
393        let frame = Frame::of(func, allocation, layout);
394        finish(func, allocation, &frame, &Stack::default(), layout.conv, &FRAME, names);
395        print_func(func, names, &REGS)
396            .lines()
397            .filter(|line| !line.is_empty())
398            .map(|line| line.trim().to_string())
399            .collect()
400    }
401
402    /// Just the lines the frame put in, which is every line that is not the function it was
403    /// given and not the shape of the dump around it.
404    fn added(lines: &[String]) -> Vec<&str> {
405        lines
406            .iter()
407            .map(String::as_str)
408            .filter(|line| !line.contains("x64.nop"))
409            .filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
410            .collect()
411    }
412
413    #[test]
414    fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
415        let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
416        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
417
418        // Two values and four registers, so nothing is spilled, nothing is saved and the stack
419        // pointer never moves. A prologue of nothing is the right prologue for that.
420        assert_eq!(added(&lines), ["x64.ret"]);
421    }
422
423    #[test]
424    fn a_spill_is_a_store_and_a_reload_is_a_load() {
425        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
426        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
427
428        // Two registers for four values, so two of them go to the stack. The store goes behind the
429        // instruction that wrote the value and the load in front of the one that wants it, both at
430        // the offsets the frame gave, which are below the stack pointer because a small leaf
431        // function is entitled to the red zone.
432        assert_eq!(
433            lines,
434            [
435                "mfunc @f {",
436                "block0:",
437                "$rax = x64.nop",
438                "$rcx = x64.nop",
439                "$rdx = x64.nop",
440                "x64.mov_mr_64 $rdx, [$rsp - 16]",
441                "$rdx = x64.nop",
442                "x64.mov_mr_64 $rdx, [$rsp - 8]",
443                "x64.nop $rax",
444                "x64.nop $rcx",
445                "$rdx = x64.mov_rm_64 [$rsp - 16]",
446                "x64.nop $rdx",
447                "$rdx = x64.mov_rm_64 [$rsp - 8]",
448                "x64.nop $rdx",
449                "x64.ret",
450                "}",
451            ]
452        );
453    }
454
455    #[test]
456    fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
457        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
458        let base = Layout::new(&SYSV, REGS);
459        let layout = Layout { red_zone: false, ..base };
460        let lines = written(&mut func, &allocation, &layout, &mut names);
461
462        // The same function told it may not use the red zone takes sixteen bytes instead, and
463        // every offset moves above the stack pointer to match.
464        assert_eq!(
465            added(&lines),
466            [
467                "$rsp = x64.sub_ri_64 $rsp, 16",
468                "x64.mov_mr_64 $rdx, [$rsp]",
469                "x64.mov_mr_64 $rdx, [$rsp + 8]",
470                "$rdx = x64.mov_rm_64 [$rsp]",
471                "$rdx = x64.mov_rm_64 [$rsp + 8]",
472                "$rsp = x64.add_ri_64 $rsp, 16",
473                "x64.ret",
474            ]
475        );
476    }
477
478    #[test]
479    fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
480        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
481        let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
482
483        // Four registers a call leaves alone, pushed in the convention's order and popped in the
484        // other one, which is the only order that gets each of them its own value back.
485        assert_eq!(
486            added(&lines),
487            [
488                "x64.push_64 $rbx",
489                "x64.push_64 $r12",
490                "x64.push_64 $r13",
491                "x64.push_64 $r14",
492                "$r14 = x64.pop_64",
493                "$r13 = x64.pop_64",
494                "$r12 = x64.pop_64",
495                "$rbx = x64.pop_64",
496                "x64.ret",
497            ]
498        );
499    }
500
501    #[test]
502    fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
503        let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
504        let base = Layout::new(&SYSV, REGS);
505        let layout = Layout { frame_pointer: true, red_zone: false, ..base };
506        let lines = written(&mut func, &allocation, &layout, &mut names);
507
508        // The frame pointer is saved before anything else and points at where it was saved, so the
509        // epilogue reaches the stack pointer through it rather than by counting the frame back.
510        assert_eq!(
511            added(&lines),
512            [
513                "x64.push_64 $rbp",
514                "$rbp = x64.mov_rr_64 $rsp",
515                "$rsp = x64.sub_ri_64 $rsp, 16",
516                "x64.mov_mr_64 $rdx, [$rsp]",
517                "x64.mov_mr_64 $rdx, [$rsp + 8]",
518                "$rdx = x64.mov_rm_64 [$rsp]",
519                "$rdx = x64.mov_rm_64 [$rsp + 8]",
520                "$rsp = x64.mov_rr_64 $rbp",
521                "$rbp = x64.pop_64",
522                "x64.ret",
523            ]
524        );
525    }
526
527    #[test]
528    fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
529        let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
530        let locals = [Local { size: 64, align: 32 }];
531        let base = Layout::new(&SYSV, REGS);
532        let layout = Layout { locals: &locals, ..base };
533        let lines = written(&mut func, &allocation, &layout, &mut names);
534
535        // Forcing the alignment throws away how far the stack pointer had moved, so the registers
536        // are pushed before it happens and the epilogue counts back from the frame pointer to find
537        // them. The frame pointer is required here whatever the flags said.
538        assert_eq!(
539            added(&lines),
540            [
541                "x64.push_64 $rbp",
542                "$rbp = x64.mov_rr_64 $rsp",
543                "x64.push_64 $rbx",
544                "x64.push_64 $r12",
545                "x64.push_64 $r13",
546                "x64.push_64 $r14",
547                "$rsp = x64.and_ri_64 $rsp, -32",
548                "$rsp = x64.sub_ri_64 $rsp, 64",
549                "$rsp = x64.lea_64 [$rbp - 32]",
550                "$r14 = x64.pop_64",
551                "$r13 = x64.pop_64",
552                "$r12 = x64.pop_64",
553                "$rbx = x64.pop_64",
554                "$rbp = x64.pop_64",
555                "x64.ret",
556            ]
557        );
558    }
559
560    #[test]
561    fn every_block_the_function_returns_from_gets_an_epilogue() {
562        let mut names = Interner::new();
563        let mut func = Func::new(names.intern("f"));
564        let opcode = Opcode::new(names.intern("x64.nop"));
565        let head = func.create_block();
566        let left = func.create_block();
567        let right = func.create_block();
568        func.build(head, opcode).finish();
569        *func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
570        func.build(left, opcode).finish();
571        func.build(right, opcode).finish();
572        let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4));
573        let base = Layout::new(&SYSV, REGS);
574        let layout = Layout { leaf: false, ..base };
575        let lines = written(&mut func, &allocation, &layout, &mut names);
576
577        // Both ways out get the frame given back, and the block that goes somewhere gets nothing,
578        // because a block with an edge out of it is not a block anything returns from.
579        assert_eq!(
580            lines,
581            [
582                "mfunc @f {",
583                "block0:",
584                "$rsp = x64.sub_ri_64 $rsp, 8",
585                "x64.nop block1, block2",
586                "block1:",
587                "x64.nop",
588                "$rsp = x64.add_ri_64 $rsp, 8",
589                "x64.ret",
590                "block2:",
591                "x64.nop",
592                "$rsp = x64.add_ri_64 $rsp, 8",
593                "x64.ret",
594                "}",
595            ]
596        );
597    }
598
599    #[test]
600    fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
601        let mut names = Interner::new();
602        let mut func = Func::new(names.intern("f"));
603        let opcode = Opcode::new(names.intern("x64.nop"));
604        let block = func.create_block();
605        // An instruction that writes one of the vector registers Windows preserves, which is what
606        // a rule for something that has to use it produces.
607        func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
608        let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4));
609        let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
610
611        // No machine here pushes a vector register, so it is stored into the frame rather than
612        // pushed, and the frame has to be taken before there is anywhere to put it.
613        assert_eq!(
614            added(&lines),
615            [
616                "$rsp = x64.sub_ri_64 $rsp, 24",
617                "x64.movaps_mr $xmm6, [$rsp]",
618                "$xmm6 = x64.movaps_rm [$rsp]",
619                "$rsp = x64.add_ri_64 $rsp, 24",
620                "x64.ret",
621            ]
622        );
623    }
624}