use rucc_mir::{Block, Func, Inst};
use rucc_regalloc::assign::Place;
use rucc_regalloc::rewrite::Edit;
use rucc_target::{PhysReg, RegClass};
use crate::finish::Moves;
pub fn dead(func: &mut Func, moves: &Moves) -> usize {
let blocks: Vec<Block> = func.blocks().collect();
let mut taken = 0;
for block in blocks {
let insts: Vec<Inst> = func.insts(block).collect();
let mut spilled: Option<(u32, PhysReg, RegClass)> = None;
let mut gone: Vec<Inst> = Vec::new();
for inst in insts {
let edit = moves.at(inst);
if spilled.is_some() && spilled == edit.and_then(reload) {
gone.push(inst);
continue;
}
spilled = edit.and_then(spill);
}
for inst in gone {
func.remove_inst(inst);
taken += 1;
}
}
taken
}
fn spill(edit: Edit) -> Option<(u32, PhysReg, RegClass)> {
match (edit.mov.to, edit.mov.from) {
(Place::Slot(slot), Place::Reg(reg)) => Some((slot, reg, edit.class)),
_ => None,
}
}
fn reload(edit: Edit) -> Option<(u32, PhysReg, RegClass)> {
match (edit.mov.to, edit.mov.from) {
(Place::Reg(reg), Place::Slot(slot)) => Some((slot, reg, edit.class)),
_ => None,
}
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_mir::{Mem, Opcode, Operand, Reg};
use rucc_regalloc::moves::Move;
use rucc_regalloc::rewrite::At;
use rucc_target::x86_64::{FRAME, GPR, RAX, XMM};
use super::*;
const R10: PhysReg = PhysReg::new(10);
fn empty() -> (Interner, Func, Block) {
let mut names = Interner::new();
let mut func = Func::new(names.intern("f"));
let block = func.create_block();
(names, func, block)
}
fn op(names: &mut Interner, name: &str) -> Opcode {
Opcode::new(names.intern(&format!("{}{name}", FRAME.prefix)))
}
fn store(func: &mut Func, names: &mut Interner, block: Block, reg: PhysReg, at: i32) -> Inst {
let store = op(names, "mov_mr_64");
let base = Operand::read(Reg::physical(RAX), GPR);
func.build(block, store).uses(Reg::physical(reg), GPR).mem(Mem::at(base).plus(at)).finish()
}
fn load(func: &mut Func, names: &mut Interner, block: Block, reg: PhysReg, at: i32) -> Inst {
let load = op(names, "mov_rm_64");
let base = Operand::read(Reg::physical(RAX), GPR);
func.build(block, load).def(Reg::physical(reg), GPR).mem(Mem::at(base).plus(at)).finish()
}
fn out(block: Block, slot: u32, reg: PhysReg) -> Edit {
Edit {
at: At::StartOf(block),
mov: Move::new(Place::Slot(slot), Place::Reg(reg)),
class: GPR,
}
}
fn back(block: Block, slot: u32, reg: PhysReg) -> Edit {
Edit {
at: At::StartOf(block),
mov: Move::new(Place::Reg(reg), Place::Slot(slot)),
class: GPR,
}
}
fn left(func: &Func, block: Block) -> usize {
func.insts(block).count()
}
#[test]
fn a_reload_of_the_slot_the_instruction_in_front_of_it_spilled_goes() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let reload = load(&mut func, &mut names, block, R10, 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(reload, back(block, 0, R10));
assert_eq!(dead(&mut func, &moves), 1);
assert_eq!(left(&func, block), 1, "the spill went too, or the reload stayed");
assert_eq!(func.insts(block).next(), Some(spill));
}
#[test]
fn a_run_of_reloads_of_one_slot_goes_in_a_single_pass() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let first = load(&mut func, &mut names, block, R10, 16);
let second = load(&mut func, &mut names, block, R10, 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(first, back(block, 0, R10));
moves.record(second, back(block, 0, R10));
assert_eq!(dead(&mut func, &moves), 2);
assert_eq!(left(&func, block), 1);
}
#[test]
fn a_reload_with_an_instruction_between_it_and_the_spill_stays() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let between = op(&mut names, "add_rr_64");
func.build(block, between)
.def(Reg::physical(R10), GPR)
.uses(Reg::physical(RAX), GPR)
.finish();
let reload = load(&mut func, &mut names, block, R10, 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(reload, back(block, 0, R10));
assert_eq!(dead(&mut func, &moves), 0);
assert_eq!(left(&func, block), 3);
}
#[test]
fn a_reload_of_a_different_slot_stays() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let reload = load(&mut func, &mut names, block, R10, 24);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(reload, back(block, 1, R10));
assert_eq!(dead(&mut func, &moves), 0);
assert_eq!(left(&func, block), 2);
}
#[test]
fn a_reload_into_a_different_register_stays() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let reload = load(&mut func, &mut names, block, PhysReg::new(11), 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(reload, back(block, 0, PhysReg::new(11)));
assert_eq!(dead(&mut func, &moves), 0);
assert_eq!(left(&func, block), 2);
}
#[test]
fn a_reload_of_another_class_stays() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let reload = load(&mut func, &mut names, block, R10, 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(reload, Edit { class: XMM, ..back(block, 0, R10) });
assert_eq!(dead(&mut func, &moves), 0);
assert_eq!(left(&func, block), 2);
}
#[test]
fn a_store_and_a_load_the_allocator_did_not_write_stay() {
let (mut names, mut func, block) = empty();
store(&mut func, &mut names, block, R10, 16);
load(&mut func, &mut names, block, R10, 16);
assert_eq!(dead(&mut func, &Moves::default()), 0);
assert_eq!(left(&func, block), 2);
}
#[test]
fn a_reload_at_the_top_of_another_block_stays() {
let (mut names, mut func, first) = empty();
let second = func.create_block();
let spill = store(&mut func, &mut names, first, R10, 16);
let reload = load(&mut func, &mut names, second, R10, 16);
let mut moves = Moves::default();
moves.record(spill, out(first, 0, R10));
moves.record(reload, back(first, 0, R10));
assert_eq!(dead(&mut func, &moves), 0);
assert_eq!(left(&func, second), 1);
}
#[test]
fn a_copy_between_the_two_is_not_a_spill_to_read_back() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let copy = op(&mut names, "mov_rr_64");
let copied = func
.build(block, copy)
.def(Reg::physical(RAX), GPR)
.uses(Reg::physical(R10), GPR)
.finish();
let reload = load(&mut func, &mut names, block, R10, 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(
copied,
Edit {
at: At::StartOf(block),
mov: Move::new(Place::Reg(RAX), Place::Reg(R10)),
class: GPR,
},
);
moves.record(reload, back(block, 0, R10));
assert_eq!(dead(&mut func, &moves), 0);
assert_eq!(left(&func, block), 3);
}
}