use std::collections::HashMap;
use rucc_base::Interner;
use rucc_mir::{self as mir, Role};
use rucc_target::{Compare, FlagInsts, Reads, RegClass, Zeroing};
type Place = (RegClass, mir::Reg);
pub fn redundant(func: &mut mir::Func, insts: &FlagInsts, names: &mut Interner) -> usize {
let opcodes: HashMap<&str, mir::Opcode> = insts
.compares
.iter()
.filter_map(|entry| entry.kept)
.map(|kept| (kept, mir::Opcode::new(names.intern(&format!("{}{kept}", insts.prefix)))))
.collect();
let names = &*names;
let mut gone = 0;
for block in func.blocks().collect::<Vec<_>>() {
let sequence: Vec<mir::Inst> = func.insts(block).collect();
let mut left: Option<Left> = None;
for at in 0..sequence.len() {
let inst = sequence[at];
let Some(name) = opcode(func, insts, names, inst) else {
left = None;
continue;
};
left = if let Some(entry) = insts.compare(name) {
let already = left
.as_ref()
.is_some_and(|had| had.answers(func, insts, names, &sequence, at, entry));
if already {
let after = stale(func, inst, left);
take(func, &opcodes, inst, entry);
gone += 1;
after
} else {
stale(func, inst, Some(Left::made(func, entry, inst)))
}
} else if let Some(zeroing) = insts.zeroed(name) {
Left::zeroed(func, insts, name, zeroing, inst)
} else if (insts.writes)(name) {
None
} else {
stale(func, inst, left)
};
}
}
gone
}
struct Left {
how: How,
about: Vec<Place>,
}
enum How {
Made {
asks: &'static str,
read: Vec<Place>,
imm: Option<i64>,
},
Zeroed {
width: u32,
covers: Zeroing,
},
}
impl Left {
fn made(func: &mir::Func, entry: &Compare, inst: mir::Inst) -> Self {
let read: Vec<Place> = reads(func, inst).into_iter().map(|(_, place)| place).collect();
Self {
how: How::Made {
asks: entry.asks,
read: read.clone(),
imm: func[inst].imm.map(|at| func[at].0),
},
about: read,
}
}
fn zeroed(
func: &mir::Func,
insts: &FlagInsts,
name: &str,
zeroing: &Zeroing,
inst: mir::Inst,
) -> Option<Self> {
let written = writes(func, inst);
let [(at, def)] = written[..] else { return None };
let width = (insts.width)(name, at)?;
Some(Self { how: How::Zeroed { width, covers: *zeroing }, about: vec![def] })
}
fn answers(
&self,
func: &mir::Func,
insts: &FlagInsts,
names: &Interner,
sequence: &[mir::Inst],
at: usize,
entry: &Compare,
) -> bool {
let inst = sequence[at];
let asked: Vec<Place> = reads(func, inst).into_iter().map(|(_, place)| place).collect();
let against = func[inst].imm.map(|at| func[at].0);
match &self.how {
How::Made { asks, read, imm } => {
*asks == entry.asks && *read == asked && *imm == against
}
How::Zeroed { width, covers } => {
if against != Some(0) || self.about != asked {
return false;
}
let [(index, _)] = reads(func, inst)[..] else { return false };
let Some(name) = opcode(func, insts, names, inst) else { return false };
if (insts.width)(name, index) != Some(*width) {
return false;
}
let conditions = conditions(func, insts, names, sequence, at);
!conditions.is_empty() && conditions.iter().all(|&reads| covers.covers(reads))
}
}
}
}
fn conditions(
func: &mir::Func,
insts: &FlagInsts,
names: &Interner,
sequence: &[mir::Inst],
at: usize,
) -> Vec<Reads> {
let mut found = Vec::new();
let Some(name) = opcode(func, insts, names, sequence[at]) else { return found };
found.extend(insts.reads(name));
for &inst in &sequence[at + 1..] {
let Some(name) = opcode(func, insts, names, inst) else { break };
if (insts.writes)(name) {
break;
}
found.extend(insts.reads(name));
}
found
}
fn stale(func: &mir::Func, inst: mir::Inst, left: Option<Left>) -> Option<Left> {
let left = left?;
let touched = writes(func, inst).iter().any(|&(_, place)| left.about.contains(&place));
(!touched).then_some(left)
}
fn reads(func: &mir::Func, inst: mir::Inst) -> Vec<(u8, Place)> {
picked(func, inst, Role::Use)
}
fn writes(func: &mir::Func, inst: mir::Inst) -> Vec<(u8, Place)> {
let mut found = picked(func, inst, Role::Def);
found.extend(picked(func, inst, Role::EarlyDef));
found
}
fn picked(func: &mir::Func, inst: mir::Inst, role: Role) -> Vec<(u8, Place)> {
func[func[inst].operands]
.iter()
.enumerate()
.filter(|(_, operand)| operand.role == role)
.filter_map(|(at, operand)| Some((u8::try_from(at).ok()?, (operand.class, operand.reg))))
.collect()
}
fn take(
func: &mut mir::Func,
opcodes: &HashMap<&str, mir::Opcode>,
inst: mir::Inst,
entry: &Compare,
) {
let Some(kept) = entry.kept else {
func.remove_inst(inst);
return;
};
let Some(&opcode) = opcodes.get(kept) else { return };
let byte: Vec<mir::Operand> = func[func[inst].operands]
.iter()
.filter(|operand| operand.role != Role::Use)
.copied()
.collect();
let operands = func.push_operands(&byte);
func[inst].opcode = opcode;
func[inst].operands = operands;
func[inst].imm = None;
}
fn opcode<'a>(
func: &mir::Func,
insts: &FlagInsts,
names: &'a Interner,
inst: mir::Inst,
) -> Option<&'a str> {
names.resolve(func[inst].opcode.name()).strip_prefix(insts.prefix)
}
#[cfg(test)]
mod tests {
use rucc_target::x86_64::{FLAGS, GPR};
use super::*;
fn empty() -> (Interner, mir::Func, mir::Block) {
let mut names = Interner::new();
let mut func = mir::Func::new(names.intern("f"));
let block = func.create_block();
(names, func, block)
}
fn op(names: &mut Interner, name: &str) -> mir::Opcode {
mir::Opcode::new(names.intern(&format!("{}{name}", FLAGS.prefix)))
}
fn takes(func: &mut mir::Func, names: &mut Interner) -> usize {
redundant(func, &FLAGS, names)
}
fn shape(func: &mir::Func, names: &Interner, block: mir::Block) -> Vec<String> {
func.insts(block)
.map(|inst| {
names
.resolve(func[inst].opcode.name())
.strip_prefix(FLAGS.prefix)
.unwrap_or("")
.to_owned()
})
.collect()
}
#[test]
fn the_same_comparison_twice_leaves_one_comparison_and_two_bytes() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
let ne = op(&mut names, "cmp_set_ne_ri_32");
let e = op(&mut names, "cmp_set_e_ri_32");
func.build(block, ne).def(first, GPR).uses(value, GPR).imm(0).finish();
func.build(block, e).def(second, GPR).uses(value, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["cmp_set_ne_ri_32", "set_e"]);
}
#[test]
fn a_comparison_of_a_register_something_wrote_in_between_stays() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let first = func.new_vreg(GPR);
let second = func.new_vreg(GPR);
let ne = op(&mut names, "cmp_set_ne_ri_32");
let e = op(&mut names, "cmp_set_e_ri_32");
let copy = op(&mut names, "mov_rr_64");
func.build(block, ne).def(first, GPR).uses(value, GPR).imm(0).finish();
func.build(block, copy).def(value, GPR).uses(other, GPR).finish();
func.build(block, e).def(second, GPR).uses(value, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 3);
}
#[test]
fn a_comparison_against_zero_after_a_bitwise_operation_goes() {
for condition in ["e", "l", "b"] {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let and = op(&mut names, "and_ri_32");
let cmp = op(&mut names, &format!("cmp_set_{condition}_ri_32"));
func.build(block, and).def(value, GPR).uses(value, GPR).imm(255).finish();
func.build(block, cmp).def(byte, GPR).uses(value, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 1, "set{condition}");
assert_eq!(
shape(&func, &names, block),
["and_ri_32".to_owned(), format!("set_{condition}")]
);
}
}
#[test]
fn a_comparison_against_zero_after_a_subtraction_goes_only_for_the_zero_conditions() {
for (condition, left) in [("e", 1), ("ne", 1), ("l", 0), ("ge", 0), ("a", 0)] {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let sub = op(&mut names, "sub_rr_32");
let cmp = op(&mut names, &format!("cmp_set_{condition}_ri_32"));
func.build(block, sub).def(value, GPR).uses(value, GPR).uses(other, GPR).finish();
func.build(block, cmp).def(byte, GPR).uses(value, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), left, "set{condition}");
}
}
#[test]
fn a_comparison_that_keeps_nothing_is_taken_out_and_the_jump_reads_what_is_there() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let and = op(&mut names, "and_ri_32");
let cmp = op(&mut names, "cmp_ri_32");
let jump = op(&mut names, "jcc_l");
func.build(block, and).def(value, GPR).uses(value, GPR).imm(255).finish();
func.build(block, cmp).uses(value, GPR).imm(0).finish();
func.build(block, jump).finish();
assert_eq!(takes(&mut func, &mut names), 1);
assert_eq!(shape(&func, &names, block), ["and_ri_32", "jcc_l"]);
}
#[test]
fn a_comparison_that_keeps_nothing_is_refused_on_the_condition_behind_it() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let sub = op(&mut names, "sub_rr_32");
let cmp = op(&mut names, "cmp_ri_32");
let jump = op(&mut names, "jcc_l");
func.build(block, sub).def(value, GPR).uses(value, GPR).uses(other, GPR).finish();
func.build(block, cmp).uses(value, GPR).imm(0).finish();
func.build(block, jump).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 3);
}
#[test]
fn a_comparison_wider_than_the_arithmetic_in_front_of_it_stays() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let and = op(&mut names, "and_ri_32");
let cmp = op(&mut names, "cmp_set_e_ri_64");
func.build(block, and).def(value, GPR).uses(value, GPR).imm(255).finish();
func.build(block, cmp).def(byte, GPR).uses(value, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
}
#[test]
fn anything_that_writes_the_condition_state_in_between_makes_the_comparison_stay() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let other = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let and = op(&mut names, "and_ri_32");
let mul = op(&mut names, "imul_rr_32");
let cmp = op(&mut names, "cmp_set_e_ri_32");
func.build(block, and).def(value, GPR).uses(value, GPR).imm(255).finish();
func.build(block, mul).def(other, GPR).uses(other, GPR).uses(other, GPR).finish();
func.build(block, cmp).def(byte, GPR).uses(value, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 3);
}
#[test]
fn a_comparison_nothing_is_found_to_read_stays() {
let (mut names, mut func, block) = empty();
let value = func.new_vreg(GPR);
let and = op(&mut names, "and_ri_32");
let cmp = op(&mut names, "cmp_ri_32");
func.build(block, and).def(value, GPR).uses(value, GPR).imm(255).finish();
func.build(block, cmp).uses(value, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, block).len(), 2);
}
#[test]
fn a_comparison_in_another_block_is_not_one_the_arithmetic_answers() {
let (mut names, mut func, block) = empty();
let next = func.create_block();
let value = func.new_vreg(GPR);
let byte = func.new_vreg(GPR);
let and = op(&mut names, "and_ri_32");
let cmp = op(&mut names, "cmp_set_e_ri_32");
func.build(block, and).def(value, GPR).uses(value, GPR).imm(255).finish();
*func.succs_mut(block) = vec![mir::BlockCall::to(next)];
func.build(next, cmp).def(byte, GPR).uses(value, GPR).imm(0).finish();
assert_eq!(takes(&mut func, &mut names), 0);
assert_eq!(shape(&func, &names, next).len(), 1);
}
}