use std::collections::HashMap;
use rucc_base::Interner;
use rucc_mir::{Block, Func, Inst, Opcode, Reg};
use rucc_regalloc::assign::Place;
use rucc_regalloc::rewrite::Edit;
use rucc_target::{CallRegs, FrameInsts, MachineInsts, PhysReg, RegClass};
use crate::finish::Moves;
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Cleaned {
pub gone: usize,
pub copied: usize,
}
pub fn clean(
func: &mut Func,
moves: &Moves,
machine: &MachineInsts,
frame: &FrameInsts,
conv: &CallRegs,
names: &mut Interner,
) -> Cleaned {
let blocks: Vec<Block> = func.blocks().collect();
let mut cleaned = Cleaned::default();
for block in blocks {
let insts: Vec<Inst> = func.insts(block).collect();
let mut holds = Holds::default();
let mut gone: Vec<Inst> = Vec::new();
for inst in insts {
if let Some(edit) = moves.at(inst) {
if holds.same(edit.class, edit.mov.to, edit.mov.from) {
gone.push(inst);
cleaned.gone += 1;
continue;
}
if let Some((to, from)) = instead(&holds, &edit) {
let copy = copy(func, names, frame, edit.class, to, from);
func.insert_before(inst, copy);
gone.push(inst);
cleaned.copied += 1;
}
holds.moved(edit.class, edit.mov.to, edit.mov.from);
continue;
}
let name = names.resolve(func[inst].opcode.name());
if machine.calls(name) || !machine.has(name) {
holds.nothing();
continue;
}
let mut addressing = false;
for operand in &func[func[inst].operands] {
if !operand.role.is_def() {
continue;
}
let Some(reg) = operand.reg.phys() else { continue };
addressing |= addresses(conv, operand.class, reg);
holds.wrote(operand.class, Place::Reg(reg));
}
if addressing {
holds.nothing();
}
}
for inst in gone {
func.remove_inst(inst);
}
}
cleaned
}
fn addresses(conv: &CallRegs, class: RegClass, reg: PhysReg) -> bool {
class == conv.int_class && (reg == conv.stack_pointer || reg == conv.frame_pointer)
}
fn instead(holds: &Holds, edit: &Edit) -> Option<(PhysReg, PhysReg)> {
let (Place::Reg(to), Place::Slot(_)) = (edit.mov.to, edit.mov.from) else { return None };
Some((to, holds.register(edit.class, edit.mov.from)?))
}
fn copy(
func: &mut Func,
names: &mut Interner,
frame: &FrameInsts,
class: RegClass,
to: PhysReg,
from: PhysReg,
) -> Inst {
let moves = frame.moves(class).expect("a class the target says how to move");
let mov = Opcode::new(names.intern(&format!("{}{}", frame.prefix, moves.mov)));
func.build_loose(mov).def(Reg::physical(to), class).uses(Reg::physical(from), class).finish()
}
#[derive(Debug, Default)]
struct Holds {
what: HashMap<(u8, Place), u32>,
named: u32,
}
impl Holds {
fn same(&self, class: RegClass, to: Place, from: Place) -> bool {
let read = self.what.get(&(class.number(), from));
read.is_some() && read == self.what.get(&(class.number(), to))
}
fn register(&self, class: RegClass, place: Place) -> Option<PhysReg> {
let value = *self.what.get(&(class.number(), place))?;
self.what
.iter()
.filter(|&(&(number, _), &held)| number == class.number() && held == value)
.filter_map(|(&(_, place), _)| match place {
Place::Reg(reg) => Some(reg),
Place::Slot(_) => None,
})
.min_by_key(|reg| reg.number())
}
fn moved(&mut self, class: RegClass, to: Place, from: Place) {
let value = match self.what.get(&(class.number(), from)) {
Some(&value) => value,
None => {
self.named += 1;
self.what.insert((class.number(), from), self.named);
self.named
}
};
self.what.insert((class.number(), to), value);
}
fn wrote(&mut self, class: RegClass, place: Place) {
self.what.remove(&(class.number(), place));
}
fn nothing(&mut self) {
self.what.clear();
}
}
#[cfg(test)]
mod tests {
use rucc_mir::{Mem, Opcode, Operand, Reg};
use rucc_regalloc::moves::Move;
use rucc_regalloc::rewrite::{At, Edit};
use rucc_target::x86_64::{FRAME, GPR, MACHINE, RAX, SYSV, XMM};
use super::*;
const R10: PhysReg = PhysReg::new(10);
const R11: PhysReg = PhysReg::new(11);
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 clean(func: &mut Func, moves: &Moves, names: &mut Interner) -> Cleaned {
super::clean(func, moves, &MACHINE, &FRAME, &SYSV, names)
}
fn gone(func: &mut Func, moves: &Moves, names: &mut Interner) -> usize {
clean(func, moves, names).gone
}
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 copy(
func: &mut Func,
names: &mut Interner,
block: Block,
to: PhysReg,
from: PhysReg,
) -> Inst {
let copy = op(names, "mov_rr_64");
func.build(block, copy).def(Reg::physical(to), GPR).uses(Reg::physical(from), GPR).finish()
}
fn add(
func: &mut Func,
names: &mut Interner,
block: Block,
to: PhysReg,
from: PhysReg,
) -> Inst {
let add = op(names, "add_rr_64");
func.build(block, add).def(Reg::physical(to), GPR).uses(Reg::physical(from), GPR).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 across(block: Block, to: PhysReg, from: PhysReg) -> Edit {
Edit {
at: At::StartOf(block),
mov: Move::new(Place::Reg(to), Place::Reg(from)),
class: GPR,
}
}
fn left(func: &Func, block: Block) -> usize {
func.insts(block).count()
}
fn written(func: &Func, block: Block, names: &Interner) -> Vec<String> {
func.insts(block).map(|inst| names.resolve(func[inst].opcode.name()).to_owned()).collect()
}
fn reads(func: &Func, block: Block, at: usize) -> Vec<PhysReg> {
let inst = func.insts(block).nth(at).expect("an instruction at that position");
func[func[inst].operands]
.iter()
.filter(|operand| !operand.role.is_def())
.filter_map(|operand| operand.reg.phys())
.collect()
}
#[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!(gone(&mut func, &moves, &mut names), 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!(gone(&mut func, &moves, &mut names), 2);
assert_eq!(left(&func, block), 1);
}
#[test]
fn a_reload_with_an_instruction_between_that_writes_neither_end_goes() {
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);
add(&mut func, &mut names, block, RAX, R10);
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!(gone(&mut func, &moves, &mut names), 2);
assert_eq!(left(&func, block), 2, "the spill and the instruction between are the two");
}
#[test]
fn a_reload_behind_an_instruction_that_writes_the_register_stays() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
add(&mut func, &mut names, block, R10, RAX);
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!(gone(&mut func, &moves, &mut names), 0);
assert_eq!(left(&func, block), 3);
}
#[test]
fn a_spill_of_a_word_the_slot_still_holds_goes() {
let (mut names, mut func, block) = empty();
let reload = load(&mut func, &mut names, block, R10, 16);
let spill = store(&mut func, &mut names, block, R10, 16);
let mut moves = Moves::default();
moves.record(reload, back(block, 0, R10));
moves.record(spill, out(block, 0, R10));
assert_eq!(gone(&mut func, &moves, &mut names), 1);
assert_eq!(left(&func, block), 1);
assert_eq!(func.insts(block).next(), Some(reload));
}
#[test]
fn a_copy_of_a_word_the_register_already_holds_goes() {
let (mut names, mut func, block) = empty();
let first = copy(&mut func, &mut names, block, RAX, R10);
let second = copy(&mut func, &mut names, block, RAX, R10);
let mut moves = Moves::default();
moves.record(first, across(block, RAX, R10));
moves.record(second, across(block, RAX, R10));
assert_eq!(gone(&mut func, &moves, &mut names), 1);
assert_eq!(left(&func, block), 1);
}
#[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!(gone(&mut func, &moves, &mut names), 0);
assert_eq!(left(&func, block), 2);
}
#[test]
fn a_reload_into_a_different_register_becomes_a_copy() {
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, R11, 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(reload, back(block, 0, R11));
assert_eq!(clean(&mut func, &moves, &mut names), Cleaned { gone: 0, copied: 1 });
assert_eq!(written(&func, block, &names), ["x64.mov_mr_64", "x64.mov_rr_64"]);
assert_eq!(reads(&func, block, 1), vec![R10]);
}
#[test]
fn a_reload_no_register_holds_the_word_of_stays_a_load() {
let (mut names, mut func, block) = empty();
let reload = load(&mut func, &mut names, block, R11, 16);
let mut moves = Moves::default();
moves.record(reload, back(block, 0, R11));
assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
assert_eq!(written(&func, block, &names), ["x64.mov_rm_64"]);
}
#[test]
fn the_copy_is_written_out_of_the_lowest_numbered_register_that_has_the_word() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let first = copy(&mut func, &mut names, block, R11, R10);
let second = copy(&mut func, &mut names, block, RAX, R10);
let reload = load(&mut func, &mut names, block, PhysReg::new(12), 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(first, across(block, R11, R10));
moves.record(second, across(block, RAX, R10));
moves.record(reload, back(block, 0, PhysReg::new(12)));
assert_eq!(clean(&mut func, &moves, &mut names), Cleaned { gone: 0, copied: 1 });
assert_eq!(reads(&func, block, 3), vec![RAX], "rax is register zero");
}
#[test]
fn a_reload_behind_a_call_is_not_written_as_a_copy() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let call = op(&mut names, "call");
func.build(block, call).uses(Reg::physical(RAX), GPR).finish();
let reload = load(&mut func, &mut names, block, R11, 16);
let mut moves = Moves::default();
moves.record(spill, out(block, 0, R10));
moves.record(reload, back(block, 0, R11));
assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
assert_eq!(written(&func, block, &names), ["x64.mov_mr_64", "x64.call", "x64.mov_rm_64"]);
}
#[test]
fn a_spill_of_a_word_another_register_holds_stays_a_store() {
let (mut names, mut func, block) = empty();
let copied = copy(&mut func, &mut names, block, R11, R10);
let spill = store(&mut func, &mut names, block, R11, 16);
let mut moves = Moves::default();
moves.record(copied, across(block, R11, R10));
moves.record(spill, out(block, 0, R11));
assert_eq!(clean(&mut func, &moves, &mut names), Cleaned::default());
assert_eq!(written(&func, block, &names), ["x64.mov_rr_64", "x64.mov_mr_64"]);
}
#[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!(gone(&mut func, &moves, &mut names), 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!(gone(&mut func, &Moves::default(), &mut names), 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!(gone(&mut func, &moves, &mut names), 0);
assert_eq!(left(&func, second), 1);
}
#[test]
fn a_copy_out_of_the_register_between_the_two_does_not_stop_it() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let copied = copy(&mut func, &mut names, block, RAX, R10);
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, across(block, RAX, R10));
moves.record(reload, back(block, 0, R10));
assert_eq!(gone(&mut func, &moves, &mut names), 1);
assert_eq!(left(&func, block), 2);
}
#[test]
fn a_reload_behind_a_call_stays() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let call = op(&mut names, "call");
func.build(block, call).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!(gone(&mut func, &moves, &mut names), 0);
assert_eq!(left(&func, block), 3);
}
#[test]
fn a_reload_behind_a_write_of_the_stack_pointer_stays() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let sub = op(&mut names, "sub_ri_64");
func.build(block, sub)
.def(Reg::physical(SYSV.stack_pointer), GPR)
.uses(Reg::physical(SYSV.stack_pointer), GPR)
.imm(32)
.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!(gone(&mut func, &moves, &mut names), 0);
assert_eq!(left(&func, block), 3);
}
#[test]
fn a_reload_behind_an_instruction_the_target_does_not_have_stays() {
let (mut names, mut func, block) = empty();
let spill = store(&mut func, &mut names, block, R10, 16);
let strange = op(&mut names, "nothing_of_that_name");
func.build(block, strange).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!(gone(&mut func, &moves, &mut names), 0);
assert_eq!(left(&func, block), 3);
}
}