use zhc_utils::{FastMap, Store, small::SmallVec};
use super::{Dialect, DialectInstructionSet, IR, ValId, ValueNumber, dce::eliminate_dead_code};
pub trait AllowCse: Dialect {
fn op_to_exprs(
op: Self::InstructionSet,
args: impl Iterator<Item = ValueNumber>,
) -> impl Iterator<Item = Expr<Self>> {
let args = args.collect::<SmallVec<_>>();
(0..op.get_signature().get_returns_arity()).map(move |i| Expr {
op: op.clone(),
args: args.clone(),
ret_pos: i as u8,
})
}
}
#[derive(Hash, PartialEq, Eq)]
pub struct Expr<D: Dialect> {
pub op: D::InstructionSet,
pub args: SmallVec<ValueNumber>,
pub ret_pos: u8,
}
pub struct CanReplace {
old: ValId,
new: ValId,
}
pub struct CommonSubexpressionAnalysis {
replacements: SmallVec<CanReplace>,
}
impl CommonSubexpressionAnalysis {
pub fn from_ir<D: AllowCse>(ir: &IR<D>) -> Self {
let mut replacements = SmallVec::new();
let mut vn_to_valid: Store<ValueNumber, ValId> = Store::empty();
let mut valid_to_vn: FastMap<ValId, ValueNumber> = FastMap::new();
let mut expr_to_vn: FastMap<Expr<D>, ValueNumber> = FastMap::new();
for op in ir.walk_ops_topological() {
let arg_vns: SmallVec<_> = op
.get_args_iter()
.map(|a| valid_to_vn.get(&a.get_id()).unwrap().to_owned())
.collect();
for (expr, val) in
D::op_to_exprs(op.get_instruction(), arg_vns.into_iter()).zip(op.get_returns_iter())
{
let vn = if expr_to_vn.contains_key(&expr) {
let vn = expr_to_vn.get(&expr).unwrap();
let vn_valid = vn_to_valid[vn];
replacements.push(CanReplace {
old: val.get_id(),
new: vn_valid,
});
vn.to_owned()
} else {
let vn = vn_to_valid.push(val.get_id());
expr_to_vn.insert(expr, vn);
vn
};
valid_to_vn.insert(val.get_id(), vn);
}
}
CommonSubexpressionAnalysis { replacements }
}
pub fn into_iter(self) -> impl Iterator<Item = CanReplace> {
self.replacements.into_iter()
}
}
pub fn eliminate_common_subexpressions<D: AllowCse>(ir: &mut IR<D>) {
let analysis = CommonSubexpressionAnalysis::from_ir(ir);
for CanReplace { old, new } in analysis.into_iter() {
ir.replace_val_use(old, new);
}
eliminate_dead_code(ir);
}
#[cfg(test)]
mod test {
use zhc_utils::{assert_display_is, svec};
use super::ValueNumber;
use super::*;
use crate::{testlang::*, *};
impl AllowCse for TestLang {
fn op_to_exprs(
op: Self::InstructionSet,
args: impl Iterator<Item = ValueNumber>,
) -> impl Iterator<Item = Expr<Self>> {
let args = args.collect::<SmallVec<_>>();
let args_norm = match op {
TestInstructionSet::Add => {
let mut a = args.clone();
a.sort_unstable();
a
}
_ => args.clone(),
};
let arity = op.get_signature().get_returns_arity();
let exprs = (0..arity)
.map(move |i| Expr {
op: op.clone(),
args: args_norm.clone(),
ret_pos: i as u8,
})
.collect::<Vec<_>>();
exprs.into_iter()
}
}
#[test]
fn test_empty_cse() {
let mut ir = IR::<TestLang>::empty();
eliminate_common_subexpressions(&mut ir);
assert_eq!(ir.n_ops(), 0);
assert_display_is!(ir.format(), r#""#);
}
#[test]
fn test_duplicate_inc() {
let mut ir = IR::<TestLang>::empty();
let (_input_op, input_vals) = ir.add_op(TestInstructionSet::IntInput { pos: 0 }, svec![]);
let (inc1, _inc1_vals) = ir.add_op(TestInstructionSet::Inc, input_vals.clone());
let (inc2, inc2_vals) = ir.add_op(TestInstructionSet::Inc, input_vals.clone());
let (_ret, _ret_vals) = ir.add_op(TestInstructionSet::Return, inc2_vals);
assert_display_is!(
ir.format(),
r#"
%0 = int_input<pos: 0>();
%1 = inc(%0);
%2 = inc(%0);
return(%2);
"#
);
eliminate_common_subexpressions(&mut ir);
assert!(ir.has_opid(inc1));
assert!(!ir.has_opid(inc2));
assert_eq!(ir.n_ops(), 3);
assert_display_is!(
ir.format(),
r#"
%0 = int_input<pos: 0>();
%1 = inc(%0);
return(%1);
"#
);
}
#[test]
fn test_commutative_add() {
let mut ir = IR::<TestLang>::empty();
let (_in1, v1) = ir.add_op(TestInstructionSet::IntInput { pos: 0 }, svec![]);
let (_in2, v2) = ir.add_op(TestInstructionSet::IntInput { pos: 1 }, svec![]);
let (add1, _a1_vals) = ir.add_op(TestInstructionSet::Add, svec![v1[0], v2[0]]);
let (add2, a2_vals) = ir.add_op(TestInstructionSet::Add, svec![v2[0], v1[0]]);
let (_ret, _ret_vals) = ir.add_op(TestInstructionSet::Return, a2_vals);
assert_display_is!(
ir.format(),
r#"
%0 = int_input<pos: 0>();
%1 = int_input<pos: 1>();
%2 = add(%0, %1);
%3 = add(%1, %0);
return(%3);
"#
);
eliminate_common_subexpressions(&mut ir);
assert!(ir.has_opid(add1));
assert!(!ir.has_opid(add2));
assert_display_is!(
ir.format(),
r#"
%0 = int_input<pos: 0>();
%1 = int_input<pos: 1>();
%2 = add(%0, %1);
return(%2);
"#
);
}
#[test]
fn test_multi_return_divrem() {
let mut ir = IR::<TestLang>::empty();
let (_, in1_vals) = ir.add_op(TestInstructionSet::IntInput { pos: 0 }, svec![]);
let (_, in2_vals) = ir.add_op(TestInstructionSet::IntInput { pos: 1 }, svec![]);
let (div1_op, div1_vals) =
ir.add_op(TestInstructionSet::DivRem, svec![in1_vals[0], in2_vals[0]]);
let (div2_op, div2_vals) =
ir.add_op(TestInstructionSet::DivRem, svec![in1_vals[0], in2_vals[0]]);
let (add_op, add_vals) =
ir.add_op(TestInstructionSet::Add, svec![div2_vals[0], div1_vals[1]]);
let (_, _) = ir.add_op(TestInstructionSet::Return, add_vals);
assert!(ir.has_opid(div1_op));
assert!(ir.has_opid(div2_op));
assert_display_is!(
ir.format(),
r#"
%0 = int_input<pos: 0>();
%1 = int_input<pos: 1>();
%2, %3 = div_rem(%0, %1);
%4, %5 = div_rem(%0, %1);
%6 = add(%4, %3);
return(%6);
"#
);
eliminate_common_subexpressions(&mut ir);
assert!(ir.has_opid(div1_op));
assert!(!ir.has_opid(div2_op));
let args: Vec<_> = ir
.get_op(add_op)
.get_args_iter()
.map(|v| v.get_id())
.collect();
assert_eq!(args.len(), 2);
assert_eq!(args[0], div1_vals[0]);
assert_eq!(args[1], div1_vals[1]);
assert_display_is!(
ir.format(),
r#"
%0 = int_input<pos: 0>();
%1 = int_input<pos: 1>();
%2, %3 = div_rem(%0, %1);
%6 = add(%2, %3);
return(%6);
"#
);
}
}