use rucc_ir::{Block, Def, Func, Inst, Opcode, Value};
use crate::{Fuel, Pass};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Dce;
impl Pass for Dce {
fn name(&self) -> &'static str {
"dce"
}
fn describe(&self) -> &'static str {
"an instruction with no effects whose results nothing uses is removed"
}
fn run(&self, func: &mut Func, fuel: &mut Fuel) -> bool {
let mut uses = count(func);
let mut work: Vec<Inst> = Vec::new();
for block in func.blocks().collect::<Vec<Block>>() {
for inst in func.insts(block) {
if dead(func, inst, &uses) {
work.push(inst);
}
}
}
let mut changed = false;
while let Some(inst) = work.pop() {
if func.block_of(inst).is_none() {
continue;
}
if !dead(func, inst, &uses) {
continue;
}
if !fuel.take() {
continue;
}
operands(func, inst, |value| {
let count = &mut uses[value.index()];
*count -= 1;
if *count == 0 {
if let Def::Result { inst: def, .. } = func[value].def {
work.push(def);
}
}
});
func.remove_inst(inst);
changed = true;
}
changed
}
}
fn count(func: &Func) -> Vec<u32> {
let mut uses = vec![0u32; func.counts().values];
for block in func.blocks().collect::<Vec<Block>>() {
for inst in func.insts(block).collect::<Vec<Inst>>() {
operands(func, inst, |value| uses[value.index()] += 1);
}
}
uses
}
fn operands(func: &Func, inst: Inst, mut each: impl FnMut(Value)) {
for &value in &func[func[inst].args] {
each(value);
}
for call in func.successors(inst) {
for &value in &func[call.args] {
each(value);
}
}
}
fn dead(func: &Func, inst: Inst, uses: &[u32]) -> bool {
let data = &func[inst];
if func.is_terminator(inst) || data.opcode.has_effects() {
return false;
}
debug_assert!(
data.opcode != Opcode::InlineAsm,
"inline assembly has effects and cannot reach here"
);
data.results().all(|value| uses[value.index()] == 0)
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{Block, Builder, Flags, Func, MemInfo, MemOrder, Opcode, Signature, Type};
use crate::{Fuel, Pass, dce::Dce};
fn blank() -> (Interner, Func, Block) {
let mut names = Interner::new();
let name = names.intern("f");
let mut func = Func::new(name, Signature::new().with_returns(&[Type::int(32)]));
let block = func.create_block();
(names, func, block)
}
fn left(func: &Func, block: Block) -> usize {
func.insts(block).count()
}
#[test]
fn arithmetic_nothing_reads_goes_away() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let a = build.iconst(Type::int(32), 2);
let b = build.iconst(Type::int(32), 3);
build.binary(Opcode::Add, a, b, Flags::NONE);
build.ret(&[a]);
assert!(Dce.run(&mut func, &mut Fuel::unlimited()));
assert_eq!(left(&func, block), 2);
}
#[test]
fn arithmetic_something_reads_stays() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let a = build.iconst(Type::int(32), 2);
let b = build.iconst(Type::int(32), 3);
let sum = build.binary(Opcode::Add, a, b, Flags::NONE);
build.ret(&[sum]);
assert!(!Dce.run(&mut func, &mut Fuel::unlimited()));
assert_eq!(left(&func, block), 4);
}
#[test]
fn a_value_used_twice_is_not_dead_when_one_use_goes() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 7);
let kept = build.binary(Opcode::Add, x, x, Flags::NONE);
build.binary(Opcode::Add, x, x, Flags::NONE);
build.ret(&[kept]);
assert!(Dce.run(&mut func, &mut Fuel::unlimited()));
assert_eq!(left(&func, block), 3);
}
#[test]
fn a_store_stays_however_dead_it_looks() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let value = build.iconst(Type::int(32), 1);
let address = build.iconst(Type::int(64), 0);
let address = build.unary(Opcode::IntToPtr, address, Type::PTR);
let info = MemInfo { size: 4, align: 4, order: MemOrder::NotAtomic, tbaa: None };
build.store(value, address, info, Flags::NONE);
build.ret(&[value]);
assert!(!Dce.run(&mut func, &mut Fuel::unlimited()));
assert_eq!(left(&func, block), 5);
}
#[test]
fn a_value_a_branch_passes_on_is_used_by_the_branch() {
let (_, mut func, block) = blank();
let target = func.create_block();
let param = func.append_param(target, Type::int(32));
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 9);
build.jump(target, &[x]);
let mut build = Builder::new(&mut func, target);
build.ret(&[param]);
assert!(!Dce.run(&mut func, &mut Fuel::unlimited()));
assert_eq!(left(&func, block), 2);
}
#[test]
fn a_result_a_removed_instruction_read_is_looked_at_again() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let a = build.iconst(Type::int(32), 2);
let b = build.iconst(Type::int(32), 3);
let sum = build.binary(Opcode::Add, a, b, Flags::NONE);
let doubled = build.binary(Opcode::Add, sum, sum, Flags::NONE);
build.unary(Opcode::SExt, doubled, Type::int(64));
let kept = build.iconst(Type::int(32), 1);
build.ret(&[kept]);
assert!(Dce.run(&mut func, &mut Fuel::unlimited()));
assert_eq!(left(&func, block), 2);
}
#[test]
fn fuel_stops_the_removing_and_not_the_looking() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let a = build.iconst(Type::int(32), 2);
let b = build.iconst(Type::int(32), 3);
build.binary(Opcode::Add, a, b, Flags::NONE);
build.ret(&[a]);
let mut fuel = Fuel::of(1);
assert!(Dce.run(&mut func, &mut fuel));
assert_eq!(left(&func, block), 3);
}
}