use rucc_base::Interner;
use rucc_mir::{Block, BlockCall, CfiOp, Func, Inst, Mem, Opcode, Operand, Reg};
use rucc_regalloc::Allocation;
use rucc_regalloc::assign::Place;
use rucc_regalloc::rewrite::{At, Edit};
use rucc_target::{BranchInsts, CallRegs, FrameInsts, Guard, PhysReg, RegClass};
use crate::frame::Frame;
use crate::lower::Stack;
#[derive(Debug, Clone, Copy)]
pub struct Protect<'a> {
pub guard: &'a Guard,
pub branch: &'a BranchInsts,
pub scratch: [PhysReg; 2],
}
#[derive(Debug, Clone, Copy)]
pub struct Convention<'a> {
pub regs: &'a CallRegs,
pub insts: &'a FrameInsts,
pub protect: Option<Protect<'a>>,
}
impl<'a> Convention<'a> {
#[must_use]
pub fn new(regs: &'a CallRegs, insts: &'a FrameInsts) -> Self {
Self { regs, insts, protect: None }
}
}
pub fn finish(
func: &mut Func,
allocation: &Allocation,
frame: &Frame,
stack: &Stack,
convention: Convention<'_>,
names: &mut Interner,
) {
let Convention { regs: conv, insts, protect } = convention;
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, protect);
for &inst in prologue.iter().rev() {
writer.func.prepend_inst(entry, inst);
}
for block in returns {
let block = match protect {
Some(protect) => writer.check(block, frame, protect),
None => block,
};
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, protect: Option<Protect<'_>>) -> Vec<Inst> {
let sp = self.conv.stack_pointer;
let fp = self.conv.frame_pointer;
let int = self.conv.int_class;
let sse = self.conv.sse_class;
let word = offset(self.conv.word);
let mut out = Vec::new();
let mut below = offset(self.conv.return_address);
let mut from_sp = true;
if frame.frame_pointer() {
let inst = self.push(fp);
out.push(inst);
below += word;
self.row(inst, CfiOp::DefCfaOffset(below));
self.saved(inst, int, fp, -below);
let mov = self.opcode(self.insts.moves(int).expect("a move").mov);
let inst = self.two(mov, fp, sp);
out.push(inst);
let number = self.dwarf(int, fp);
self.row(inst, CfiOp::DefCfaRegister(number));
from_sp = false;
}
for ® in frame.saved_int() {
let inst = self.push(reg);
out.push(inst);
below += word;
if from_sp {
self.row(inst, CfiOp::DefCfaOffset(below));
}
self.saved(inst, int, reg, -below);
}
if let Some(to) = frame.realign() {
assert!(!from_sp, "a frame that forces its own alignment has a frame pointer");
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);
let inst = self.arith(sub, i64::from(frame.size()));
out.push(inst);
below += offset(frame.size());
if from_sp {
self.row(inst, CfiOp::DefCfaOffset(below));
}
}
for save in frame.saved_sse() {
let inst = self.store(sse, save.reg, save.at);
out.push(inst);
if frame.realign().is_none() {
self.saved(inst, sse, save.reg, save.at - below);
}
}
if let Some(protect) = protect {
let at = frame.canary().expect("a protected function has a slot for its canary");
let [into, _] = protect.scratch;
out.push(self.read_guard(into, protect.guard));
out.push(self.store(self.conv.int_class, into, at));
}
if let Some(&last) = out.last() {
self.row(last, CfiOp::RememberState);
}
out
}
fn check(&mut self, block: Block, frame: &Frame, protect: Protect<'_>) -> Block {
let class = self.conv.int_class;
let at = frame.canary().expect("a protected function has a slot for its canary");
let [ours, theirs] = protect.scratch;
let inst = self.load(class, ours, at);
self.func.append_inst(block, inst);
let inst = self.read_guard(theirs, protect.guard);
self.func.append_inst(block, inst);
let differ = self.opcode(self.insts.differ);
let inst = self
.func
.build_loose(differ)
.def(Reg::physical(theirs), class)
.uses(Reg::physical(ours), class)
.uses(Reg::physical(theirs), class)
.finish();
self.func.append_inst(block, inst);
let failed = self.func.create_block();
let ok = self.func.create_block();
let cond = Opcode::new(
self.names.intern(&format!("{}{}", protect.branch.prefix, protect.branch.cond)),
);
let inst = self.func.build_loose(cond).uses(Reg::physical(theirs), class).finish();
self.func.append_inst(block, inst);
*self.func.succs_mut(block) = vec![BlockCall::to(failed), BlockCall::to(ok)];
let call = self.opcode(self.insts.call);
let symbol = self.names.intern(protect.guard.fail);
self.func.build(failed, call).symbol(symbol).finish();
ok
}
fn read_guard(&mut self, into: PhysReg, guard: &Guard) -> Inst {
let class = self.conv.int_class;
let load = self.opcode(self.insts.moves(class).expect("a class to load").load);
self.func
.build_loose(load)
.def(Reg::physical(into), class)
.mem(Mem::in_segment(guard.segment, guard.at))
.finish()
}
fn epilogue(&mut self, frame: &Frame) -> Vec<Inst> {
let sp = self.conv.stack_pointer;
let fp = self.conv.frame_pointer;
let int = self.conv.int_class;
let sse = self.conv.sse_class;
let word = self.conv.word;
let described = !self.func.cfi.is_empty();
let mut out = Vec::new();
let mut below = offset(self.conv.return_address)
+ offset(word) * self.pushes(frame)
+ offset(frame.size());
let from_sp = !frame.frame_pointer();
for save in frame.saved_sse() {
let inst = self.load(sse, save.reg, save.at);
out.push(inst);
if frame.realign().is_none() {
self.restored(inst, sse, save.reg);
}
}
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(int).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);
let inst = self.arith(add, i64::from(frame.size()));
out.push(inst);
below -= offset(frame.size());
self.row(inst, CfiOp::DefCfaOffset(below));
}
for ® in frame.saved_int().iter().rev() {
let inst = self.pop(reg);
out.push(inst);
self.restored(inst, int, reg);
below -= offset(word);
if from_sp {
self.row(inst, CfiOp::DefCfaOffset(below));
}
}
if frame.frame_pointer() {
let inst = self.pop(fp);
out.push(inst);
self.restored(inst, int, fp);
let number = self.dwarf(int, sp);
self.row(inst, CfiOp::DefCfa { reg: number, offset: offset(self.conv.return_address) });
}
let ret = self.opcode(self.insts.ret);
let inst = self.func.build_loose(ret).finish();
out.push(inst);
if described {
self.row(inst, CfiOp::RestoreState);
self.row(inst, CfiOp::RememberState);
}
out
}
fn pushes(&self, frame: &Frame) -> i32 {
let saved = i32::try_from(frame.saved_int().len()).expect("a frame");
saved + i32::from(frame.frame_pointer())
}
fn row(&mut self, inst: Inst, op: CfiOp) {
self.func.cfi.push((inst, op));
}
fn saved(&mut self, inst: Inst, class: RegClass, reg: PhysReg, from_cfa: i32) {
let number = self.dwarf(class, reg);
self.row(inst, CfiOp::Offset { reg: number, offset: from_cfa });
}
fn restored(&mut self, inst: Inst, class: RegClass, reg: PhysReg) {
let number = self.dwarf(class, reg);
self.row(inst, CfiOp::Restore(number));
}
fn dwarf(&self, class: RegClass, reg: PhysReg) -> u16 {
self.conv.dwarf(class, reg).expect("a register a frame saves is one the table can name")
}
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::{BRANCH, FRAME, GPR, R10, R11, 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), "test");
(func, allocation, names)
}
fn written(
func: &mut Func,
allocation: &Allocation,
layout: &Layout<'_>,
names: &mut Interner,
) -> Vec<String> {
with_protector(func, allocation, layout, None, names)
}
fn with_protector(
func: &mut Func,
allocation: &Allocation,
layout: &Layout<'_>,
protect: Option<Protect<'_>>,
names: &mut Interner,
) -> Vec<String> {
let frame = Frame::of(func, allocation, layout);
let convention = Convention { protect, ..Convention::new(layout.conv, &FRAME) };
finish(func, allocation, &frame, &Stack::default(), convention, 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), "test");
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_protected_function_writes_the_canary_last_and_checks_it_before_it_returns() {
let (mut func, allocation, mut names) = pressure(&SYSV, 4, 2);
let base = Layout::new(&SYSV, REGS);
let layout = Layout { leaf: false, protect: true, ..base };
let guard = SYSV.guard.as_ref().expect("this convention has somewhere to keep the word");
let protect = Protect { guard, branch: &BRANCH, scratch: [R10, R11] };
let lines = with_protector(&mut func, &allocation, &layout, Some(protect), &mut names);
assert_eq!(
added(&lines),
[
"$rsp = x64.sub_ri_64 $rsp, 24",
"$r10 = x64.mov_rm_64 [fs:40]",
"x64.mov_mr_64 $r10, [$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]",
"$r10 = x64.mov_rm_64 [$rsp + 16]",
"$r11 = x64.mov_rm_64 [fs:40]",
"$r11 = x64.cmp_set_ne_64 $r10, $r11",
"x64.br_cond_8 $r11, block1, block2",
"x64.call @__stack_chk_fail",
"$rsp = x64.add_ri_64 $rsp, 24",
"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), "test");
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",
]
);
}
}