use rucc_base::Interner;
use rucc_mir::{Block, Func, Inst, Mem, Opcode, Operand, Reg};
use rucc_regalloc::Allocation;
use rucc_regalloc::assign::Place;
use rucc_regalloc::rewrite::{At, Edit};
use rucc_target::{CallRegs, FrameInsts, PhysReg, RegClass};
use crate::frame::Frame;
use crate::lower::Stack;
pub fn finish(
func: &mut Func,
allocation: &Allocation,
frame: &Frame,
stack: &Stack,
conv: &CallRegs,
insts: &FrameInsts,
names: &mut Interner,
) {
let entry = func.entry().expect("a function with a block in it");
let returns: Vec<Block> = func.blocks().filter(|&block| func[block].succs.is_empty()).collect();
for &(inst, local) in &stack.addresses {
let at = frame.local(local).expect("a local the frame was worked out from");
let mem = func[inst].mem.expect("the address of a local is an address");
func[mem].disp = at;
}
let incoming = frame.incoming();
for &(inst, up) in &stack.arguments {
let mem = func[inst].mem.expect("an argument read out of memory is read from an address");
func[mem].disp = incoming.at + offset(up);
if incoming.through_frame_pointer {
let at = func[mem].base.expect("an address the lowering wrote a base register into");
let operands = func[inst].operands;
func[operands][usize::from(at)].reg = Reg::physical(conv.frame_pointer);
}
}
let mut writer = Writer { func, conv, insts, names };
let mut cursors: Vec<(At, Inst)> = Vec::new();
for edit in &allocation.edits {
let inst = writer.mov(edit, frame);
writer.put(&mut cursors, edit.at, inst);
}
let prologue = writer.prologue(frame);
for &inst in prologue.iter().rev() {
writer.func.prepend_inst(entry, inst);
}
for block in returns {
let epilogue = writer.epilogue(frame);
for inst in epilogue {
writer.func.append_inst(block, inst);
}
}
}
struct Writer<'a> {
func: &'a mut Func,
conv: &'a CallRegs,
insts: &'a FrameInsts,
names: &'a mut Interner,
}
impl Writer<'_> {
fn prologue(&mut self, frame: &Frame) -> Vec<Inst> {
let sp = self.conv.stack_pointer;
let fp = self.conv.frame_pointer;
let mut out = Vec::new();
if frame.frame_pointer() {
out.push(self.push(fp));
let mov = self.opcode(self.insts.moves(self.conv.int_class).expect("a move").mov);
out.push(self.two(mov, fp, sp));
}
for ® in frame.saved_int() {
out.push(self.push(reg));
}
if let Some(to) = frame.realign() {
let and = self.opcode(self.insts.align);
out.push(self.arith(and, -i64::from(to)));
}
if frame.size() > 0 {
let sub = self.opcode(self.insts.sub);
out.push(self.arith(sub, i64::from(frame.size())));
}
for save in frame.saved_sse() {
out.push(self.store(self.conv.sse_class, save.reg, save.at));
}
out
}
fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
let sp = self.conv.stack_pointer;
let fp = self.conv.frame_pointer;
let word = self.conv.word;
let mut out = Vec::new();
for save in frame.saved_sse() {
out.push(self.load(self.conv.sse_class, save.reg, save.at));
}
let pushed = u32::try_from(frame.saved_int().len()).expect("a frame");
if frame.frame_pointer() {
if pushed == 0 {
let mov = self.opcode(self.insts.moves(self.conv.int_class).expect("a move").mov);
out.push(self.two(mov, sp, fp));
} else {
let lea = self.opcode(self.insts.lea);
let back = -offset(word * pushed);
out.push(self.address(lea, sp, fp, back));
}
} else if frame.size() > 0 {
let add = self.opcode(self.insts.add);
out.push(self.arith(add, i64::from(frame.size())));
}
for ® in frame.saved_int().iter().rev() {
out.push(self.pop(reg));
}
if frame.frame_pointer() {
out.push(self.pop(fp));
}
let ret = self.opcode(self.insts.ret);
out.push(self.func.build_loose(ret).finish());
out
}
fn mov(&mut self, edit: &Edit, frame: &Frame) -> Inst {
let moves = self.insts.moves(edit.class).expect("a class the target says how to move");
match (edit.mov.to, edit.mov.from) {
(Place::Reg(to), Place::Reg(from)) => {
let mov = self.opcode(moves.mov);
self.func
.build_loose(mov)
.def(Reg::physical(to), edit.class)
.uses(Reg::physical(from), edit.class)
.finish()
}
(Place::Reg(to), Place::Slot(slot)) => {
let at = self.slot(frame, slot);
self.load(edit.class, to, at)
}
(Place::Slot(slot), Place::Reg(from)) => {
let at = self.slot(frame, slot);
self.store(edit.class, from, at)
}
(Place::Slot(_), Place::Slot(_)) => {
unreachable!("a move from one stack slot straight into another")
}
}
}
fn put(&mut self, cursors: &mut Vec<(At, Inst)>, at: At, inst: Inst) {
if let Some(cursor) = cursors.iter_mut().find(|(place, _)| *place == at) {
self.func.insert_after(cursor.1, inst);
cursor.1 = inst;
return;
}
match at {
At::Before(before) => self.func.insert_before(before, inst),
At::After(after) => self.func.insert_after(after, inst),
At::StartOf(block) => self.func.prepend_inst(block, inst),
At::EndOf(block) => self.func.append_inst(block, inst),
}
cursors.push((at, inst));
}
fn slot(&self, frame: &Frame, slot: u32) -> i32 {
frame.slot(slot).expect("a slot the frame was worked out from")
}
fn load(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
self.func
.build_loose(load)
.def(Reg::physical(reg), class)
.mem(Mem::at(base).plus(at))
.finish()
}
fn store(&mut self, class: RegClass, reg: PhysReg, at: i32) -> Inst {
let store = self.opcode(self.insts.moves(class).expect("a class to store").store);
let base = Operand::read(Reg::physical(self.conv.stack_pointer), self.conv.int_class);
self.func
.build_loose(store)
.uses(Reg::physical(reg), class)
.mem(Mem::at(base).plus(at))
.finish()
}
fn push(&mut self, reg: PhysReg) -> Inst {
let push = self.opcode(self.insts.push);
self.func.build_loose(push).uses(Reg::physical(reg), self.conv.int_class).finish()
}
fn pop(&mut self, reg: PhysReg) -> Inst {
let pop = self.opcode(self.insts.pop);
self.func.build_loose(pop).def(Reg::physical(reg), self.conv.int_class).finish()
}
fn two(&mut self, opcode: Opcode, to: PhysReg, from: PhysReg) -> Inst {
let class = self.conv.int_class;
self.func
.build_loose(opcode)
.def(Reg::physical(to), class)
.uses(Reg::physical(from), class)
.finish()
}
fn arith(&mut self, opcode: Opcode, value: i64) -> Inst {
let class = self.conv.int_class;
let sp = Reg::physical(self.conv.stack_pointer);
self.func.build_loose(opcode).def(sp, class).uses(sp, class).imm(value).finish()
}
fn address(&mut self, opcode: Opcode, to: PhysReg, base: PhysReg, disp: i32) -> Inst {
let class = self.conv.int_class;
let base = Operand::read(Reg::physical(base), class);
self.func
.build_loose(opcode)
.def(Reg::physical(to), class)
.mem(Mem::at(base).plus(disp))
.finish()
}
fn opcode(&mut self, name: &str) -> Opcode {
Opcode::new(self.names.intern(&format!("{}{name}", self.insts.prefix)))
}
}
fn offset(bytes: u32) -> i32 {
i32::try_from(bytes).expect("a frame under two gigabytes")
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_mir::{BlockCall, print_func};
use rucc_regalloc::assign::Env;
use rucc_target::x86_64::{FRAME, GPR, REGS, SYSV, WIN64, XMM, xmm};
use super::*;
use crate::frame::{Layout, Local};
fn env(conv: &CallRegs, count: usize) -> Env {
Env::new().with(GPR, &conv.int_order[..count], &conv.int_order[count..])
}
fn pressure(conv: &CallRegs, values: usize, count: usize) -> (Func, Allocation, Interner) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let block = func.create_block();
let regs: Vec<Reg> = (0..values).map(|_| func.new_vreg(GPR)).collect();
for ® in ®s {
func.build(block, opcode).def(reg, GPR).finish();
}
for ® in ®s {
func.build(block, opcode).uses(reg, GPR).finish();
}
let allocation = rucc_regalloc::run(&mut func, &env(conv, count));
(func, allocation, names)
}
fn written(
func: &mut Func,
allocation: &Allocation,
layout: &Layout<'_>,
names: &mut Interner,
) -> Vec<String> {
let frame = Frame::of(func, allocation, layout);
finish(func, allocation, &frame, &Stack::default(), layout.conv, &FRAME, names);
print_func(func, names, ®S)
.lines()
.filter(|line| !line.is_empty())
.map(|line| line.trim().to_string())
.collect()
}
fn added(lines: &[String]) -> Vec<&str> {
lines
.iter()
.map(String::as_str)
.filter(|line| !line.contains("x64.nop"))
.filter(|line| !line.starts_with("mfunc") && !line.starts_with("block") && *line != "}")
.collect()
}
#[test]
fn a_function_that_needs_no_frame_is_given_a_return_and_nothing_else() {
let (mut func, allocation, mut names) = pressure(&SYSV, 2, 4);
let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
assert_eq!(added(&lines), ["x64.ret"]);
}
#[test]
fn a_spill_is_a_store_and_a_reload_is_a_load() {
let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
assert_eq!(
lines,
[
"mfunc @f {",
"block0:",
"$rax = x64.nop",
"$rcx = x64.nop",
"$rdx = x64.nop",
"x64.mov_mr_64 $rdx, [$rsp - 16]",
"$rdx = x64.nop",
"x64.mov_mr_64 $rdx, [$rsp - 8]",
"x64.nop $rax",
"x64.nop $rcx",
"$rdx = x64.mov_rm_64 [$rsp - 16]",
"x64.nop $rdx",
"$rdx = x64.mov_rm_64 [$rsp - 8]",
"x64.nop $rdx",
"x64.ret",
"}",
]
);
}
#[test]
fn the_frame_the_prologue_takes_is_the_frame_the_epilogue_gives_back() {
let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
let base = Layout::new(&SYSV, REGS);
let layout = Layout { red_zone: false, ..base };
let lines = written(&mut func, &allocation, &layout, &mut names);
assert_eq!(
added(&lines),
[
"$rsp = x64.sub_ri_64 $rsp, 16",
"x64.mov_mr_64 $rdx, [$rsp]",
"x64.mov_mr_64 $rdx, [$rsp + 8]",
"$rdx = x64.mov_rm_64 [$rsp]",
"$rdx = x64.mov_rm_64 [$rsp + 8]",
"$rsp = x64.add_ri_64 $rsp, 16",
"x64.ret",
]
);
}
#[test]
fn the_registers_the_prologue_pushes_come_back_in_the_opposite_order() {
let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
let lines = written(&mut func, &allocation, &Layout::new(&SYSV, REGS), &mut names);
assert_eq!(
added(&lines),
[
"x64.push_64 $rbx",
"x64.push_64 $r12",
"x64.push_64 $r13",
"x64.push_64 $r14",
"$r14 = x64.pop_64",
"$r13 = x64.pop_64",
"$r12 = x64.pop_64",
"$rbx = x64.pop_64",
"x64.ret",
]
);
}
#[test]
fn a_function_that_keeps_a_frame_pointer_sets_it_up_and_leaves_by_it() {
let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
let base = Layout::new(&SYSV, REGS);
let layout = Layout { frame_pointer: true, red_zone: false, ..base };
let lines = written(&mut func, &allocation, &layout, &mut names);
assert_eq!(
added(&lines),
[
"x64.push_64 $rbp",
"$rbp = x64.mov_rr_64 $rsp",
"$rsp = x64.sub_ri_64 $rsp, 16",
"x64.mov_mr_64 $rdx, [$rsp]",
"x64.mov_mr_64 $rdx, [$rsp + 8]",
"$rdx = x64.mov_rm_64 [$rsp]",
"$rdx = x64.mov_rm_64 [$rsp + 8]",
"$rsp = x64.mov_rr_64 $rbp",
"$rbp = x64.pop_64",
"x64.ret",
]
);
}
#[test]
fn a_realigned_frame_forces_the_alignment_after_it_has_pushed_what_it_saves() {
let (mut func, allocation, mut names) = pressure(&SYSV, 13, 13);
let locals = [Local { size: 64, align: 32 }];
let base = Layout::new(&SYSV, REGS);
let layout = Layout { locals: &locals, ..base };
let lines = written(&mut func, &allocation, &layout, &mut names);
assert_eq!(
added(&lines),
[
"x64.push_64 $rbp",
"$rbp = x64.mov_rr_64 $rsp",
"x64.push_64 $rbx",
"x64.push_64 $r12",
"x64.push_64 $r13",
"x64.push_64 $r14",
"$rsp = x64.and_ri_64 $rsp, -32",
"$rsp = x64.sub_ri_64 $rsp, 64",
"$rsp = x64.lea_64 [$rbp - 32]",
"$r14 = x64.pop_64",
"$r13 = x64.pop_64",
"$r12 = x64.pop_64",
"$rbx = x64.pop_64",
"$rbp = x64.pop_64",
"x64.ret",
]
);
}
#[test]
fn every_block_the_function_returns_from_gets_an_epilogue() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let head = func.create_block();
let left = func.create_block();
let right = func.create_block();
func.build(head, opcode).finish();
*func.succs_mut(head) = vec![BlockCall::to(left), BlockCall::to(right)];
func.build(left, opcode).finish();
func.build(right, opcode).finish();
let allocation = rucc_regalloc::run(&mut func, &env(&SYSV, 4));
let base = Layout::new(&SYSV, REGS);
let layout = Layout { leaf: false, ..base };
let lines = written(&mut func, &allocation, &layout, &mut names);
assert_eq!(
lines,
[
"mfunc @f {",
"block0:",
"$rsp = x64.sub_ri_64 $rsp, 8",
"x64.nop block1, block2",
"block1:",
"x64.nop",
"$rsp = x64.add_ri_64 $rsp, 8",
"x64.ret",
"block2:",
"x64.nop",
"$rsp = x64.add_ri_64 $rsp, 8",
"x64.ret",
"}",
]
);
}
#[test]
fn a_vector_register_a_windows_call_preserves_is_stored_and_read_back() {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let opcode = Opcode::new(names.intern("x64.nop"));
let block = func.create_block();
func.build(block, opcode).operand(Operand::write(Reg::physical(xmm(6)), XMM)).finish();
let allocation = rucc_regalloc::run(&mut func, &env(&WIN64, 4));
let lines = written(&mut func, &allocation, &Layout::new(&WIN64, REGS), &mut names);
assert_eq!(
added(&lines),
[
"$rsp = x64.sub_ri_64 $rsp, 24",
"x64.movaps_mr $xmm6, [$rsp]",
"$xmm6 = x64.movaps_rm [$rsp]",
"$rsp = x64.add_ri_64 $rsp, 24",
"x64.ret",
]
);
}
}