use rsleigh_decompile::fold::{fold_with_cc, CallingConv};
use rsleigh_decompile::ir::{
BlockId, Expr, InferredType, SsaBlock, SsaCfg, SsaTerminator, UnaryOpKind, VarDef, VarId,
};
fn vd(id: u32, expr: Expr, size: u32) -> VarDef {
VarDef {
id: VarId(id),
varnode: pcode_ir::Varnode {
space: pcode_ir::AddressSpaceId::Unique,
offset: id as u64,
size,
},
expr,
size,
use_count: 0,
param_name: None,
call_return: false,
inferred_type: InferredType::Unknown,
display_type: None,
}
}
fn run_fold(vars: Vec<VarDef>, return_id: u32) -> SsaCfg {
let mut ssa = SsaCfg {
blocks: vec![SsaBlock {
id: BlockId(0),
addr: 0x1000,
stmts: vars
.iter()
.map(|v| rsleigh_decompile::ir::Stmt::Assign(v.id))
.collect(),
terminator: SsaTerminator::Return(Some(VarId(return_id))),
}],
vars,
entry: BlockId(0),
diagnostics: Vec::new(),
};
fold_with_cc(&mut ssa, CallingConv::SysV);
ssa
}
#[test]
fn zero_minus_x_folds_to_neg_x() {
let vars = vec![
vd(0, Expr::Const(0, 4), 4),
vd(1, Expr::Unknown, 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Sub, VarId(0), VarId(1)),
4,
),
];
let ssa = run_fold(vars, 2);
let v2 = &ssa.vars[2].expr;
match v2 {
Expr::UnaryOp(UnaryOpKind::Neg, inner) => {
assert_eq!(inner.0, 1, "expected Neg(var1), got {:?}", v2);
}
other => panic!("expected UnaryOp(Neg, _), got {:?}", other),
}
}
#[test]
fn x_times_one_folds_away_the_mult() {
let vars = vec![
vd(0, Expr::Const(42, 4), 4),
vd(1, Expr::Const(1, 4), 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Mult, VarId(0), VarId(1)),
4,
),
];
let ssa = run_fold(vars, 2);
let ok = matches!(ssa.vars[2].expr, Expr::Var(VarId(0)) | Expr::Const(42, _));
assert!(ok, "x*1 must collapse; got {:?}", ssa.vars[2].expr);
}
#[test]
fn x_times_zero_folds_to_zero() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(1, Expr::Const(0, 4), 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Mult, VarId(0), VarId(1)),
4,
),
];
let ssa = run_fold(vars, 2);
match ssa.vars[2].expr {
Expr::Const(0, _) => {}
ref other => panic!("expected Const(0, _), got {:?}", other),
}
}
#[test]
fn neg_neg_x_folds_to_x() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(1, Expr::UnaryOp(UnaryOpKind::Neg, VarId(0)), 4),
vd(2, Expr::UnaryOp(UnaryOpKind::Neg, VarId(1)), 4),
];
let ssa = run_fold(vars, 2);
assert!(
!matches!(&ssa.vars[2].expr, Expr::UnaryOp(UnaryOpKind::Neg, _)),
"Neg(Neg(x)) survived fold: {:?}",
ssa.vars[2].expr
);
}
#[test]
fn not_not_x_folds_away_double_negation() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(1, Expr::UnaryOp(UnaryOpKind::Not, VarId(0)), 4),
vd(2, Expr::UnaryOp(UnaryOpKind::Not, VarId(1)), 4),
];
let ssa = run_fold(vars, 2);
assert!(
!matches!(&ssa.vars[2].expr, Expr::UnaryOp(UnaryOpKind::Not, _)),
"Not(Not(x)) survived fold: {:?}",
ssa.vars[2].expr
);
}
#[test]
fn boolnot_boolnot_x_folds_away_double_negation() {
let vars = vec![
vd(0, Expr::Unknown, 1),
vd(1, Expr::UnaryOp(UnaryOpKind::BoolNot, VarId(0)), 1),
vd(2, Expr::UnaryOp(UnaryOpKind::BoolNot, VarId(1)), 1),
];
let ssa = run_fold(vars, 2);
assert!(
!matches!(&ssa.vars[2].expr, Expr::UnaryOp(UnaryOpKind::BoolNot, _)),
"BoolNot(BoolNot(x)) survived fold: {:?}",
ssa.vars[2].expr
);
}
#[test]
fn x_div_one_folds_away_the_div() {
let vars = vec![
vd(0, Expr::Const(42, 4), 4),
vd(1, Expr::Const(1, 4), 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Div, VarId(0), VarId(1)),
4,
),
];
let ssa = run_fold(vars, 2);
let ok = matches!(ssa.vars[2].expr, Expr::Var(VarId(0)) | Expr::Const(42, _));
assert!(ok, "x/1 must collapse; got {:?}", ssa.vars[2].expr);
}
#[test]
fn x_rem_one_folds_to_zero() {
let vars = vec![
vd(0, Expr::Const(42, 4), 4),
vd(1, Expr::Const(1, 4), 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Rem, VarId(0), VarId(1)),
4,
),
];
let ssa = run_fold(vars, 2);
match ssa.vars[2].expr {
Expr::Const(0, _) => {}
ref other => panic!("expected Const(0, _), got {:?}", other),
}
}
#[test]
fn x_xor_all_ones_canonicalizes_to_not() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(1, Expr::Const(0xFFFF_FFFF, 4), 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Xor, VarId(0), VarId(1)),
4,
),
];
let ssa = run_fold(vars, 2);
assert!(
!matches!(
&ssa.vars[2].expr,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Xor, _, _)
),
"x ^ -1 must canonicalize away the Xor; got {:?}",
ssa.vars[2].expr
);
}
#[test]
fn x_eq_x_folds_to_one() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(
1,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Eq, VarId(0), VarId(0)),
1,
),
];
let ssa = run_fold(vars, 1);
match ssa.vars[1].expr {
Expr::Const(1, _) => {}
ref other => panic!("expected Const(1, _), got {:?}", other),
}
}
#[test]
fn x_neq_x_folds_to_zero() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(
1,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::NotEq, VarId(0), VarId(0)),
1,
),
];
let ssa = run_fold(vars, 1);
match ssa.vars[1].expr {
Expr::Const(0, _) => {}
ref other => panic!("expected Const(0, _), got {:?}", other),
}
}
#[test]
fn x_less_x_folds_to_zero() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(
1,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Less, VarId(0), VarId(0)),
1,
),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::SLess, VarId(0), VarId(0)),
1,
),
];
let ssa = run_fold(vars, 1);
match ssa.vars[1].expr {
Expr::Const(0, _) => {}
ref other => panic!("expected Const(0, _) for x < x, got {:?}", other),
}
match ssa.vars[2].expr {
Expr::Const(0, _) => {}
ref other => panic!("expected Const(0, _) for x <s x, got {:?}", other),
}
}
#[test]
fn x_lesseq_x_folds_to_one() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(
1,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::LessEq, VarId(0), VarId(0)),
1,
),
vd(
2,
Expr::BinOp(
rsleigh_decompile::ir::BinOpKind::SLessEq,
VarId(0),
VarId(0),
),
1,
),
];
let ssa = run_fold(vars, 1);
match ssa.vars[1].expr {
Expr::Const(1, _) => {}
ref other => panic!("expected Const(1, _) for x <= x, got {:?}", other),
}
match ssa.vars[2].expr {
Expr::Const(1, _) => {}
ref other => panic!("expected Const(1, _) for x <=s x, got {:?}", other),
}
}
#[test]
fn add_with_neg_canonicalizes_to_sub() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(1, Expr::UnaryOp(UnaryOpKind::Neg, VarId(0)), 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Add, VarId(0), VarId(1)),
4,
),
];
let ssa = run_fold(vars, 2);
let still_add_neg = matches!(
&ssa.vars[2].expr,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Add, _, _)
);
assert!(
!still_add_neg,
"Add with negated operand survived fold: {:?}",
ssa.vars[2].expr
);
}
#[test]
fn sub_with_neg_canonicalizes_to_add() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(1, Expr::Unknown, 4),
vd(2, Expr::UnaryOp(UnaryOpKind::Neg, VarId(1)), 4),
vd(
3,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Sub, VarId(0), VarId(2)),
4,
),
];
let ssa = run_fold(vars, 3);
let still_sub_neg = match &ssa.vars[3].expr {
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Sub, _, r) => matches!(
&ssa.vars[r.0 as usize].expr,
Expr::UnaryOp(UnaryOpKind::Neg, _)
),
_ => false,
};
assert!(
!still_sub_neg,
"Sub with negated rhs survived fold: {:?}",
ssa.vars[3].expr
);
}
#[test]
fn and_absorption_into_or() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(1, Expr::Unknown, 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Or, VarId(0), VarId(1)),
4,
),
vd(
3,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::And, VarId(2), VarId(0)),
4,
),
];
let ssa = run_fold(vars, 3);
assert!(
!matches!(
&ssa.vars[3].expr,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::And, _, _)
),
"(x|y) & x absorption did not fire: {:?}",
ssa.vars[3].expr
);
}
#[test]
fn bool_and_x_x_folds_to_x() {
let vars = vec![
vd(0, Expr::Unknown, 1),
vd(
1,
Expr::BinOp(
rsleigh_decompile::ir::BinOpKind::BoolAnd,
VarId(0),
VarId(0),
),
1,
),
];
let ssa = run_fold(vars, 1);
assert!(
!matches!(
&ssa.vars[1].expr,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::BoolAnd, _, _)
),
"BoolAnd(x, x) survived fold: {:?}",
ssa.vars[1].expr
);
}
#[test]
fn bool_or_x_zero_folds_to_x() {
let vars = vec![
vd(0, Expr::Const(7, 1), 1),
vd(1, Expr::Const(0, 1), 1),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::BoolOr, VarId(0), VarId(1)),
1,
),
];
let ssa = run_fold(vars, 2);
assert!(
!matches!(
&ssa.vars[2].expr,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::BoolOr, _, _)
),
"BoolOr(x, 0) survived fold: {:?}",
ssa.vars[2].expr
);
}
#[test]
fn bool_xor_x_x_folds_to_zero() {
let vars = vec![
vd(0, Expr::Unknown, 1),
vd(
1,
Expr::BinOp(
rsleigh_decompile::ir::BinOpKind::BoolXor,
VarId(0),
VarId(0),
),
1,
),
];
let ssa = run_fold(vars, 1);
match ssa.vars[1].expr {
Expr::Const(0, _) => {}
ref other => panic!("expected Const(0, _) for BoolXor(x,x), got {:?}", other),
}
}
#[test]
fn neg_sub_distributes_to_swapped_sub() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(1, Expr::Unknown, 4),
vd(
2,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Sub, VarId(0), VarId(1)),
4,
),
vd(3, Expr::UnaryOp(UnaryOpKind::Neg, VarId(2)), 4),
];
let ssa = run_fold(vars, 3);
assert!(
!matches!(&ssa.vars[3].expr, Expr::UnaryOp(UnaryOpKind::Neg, _)),
"Neg(Sub) distribution did not fire: {:?}",
ssa.vars[3].expr
);
}
#[test]
fn x_minus_x_folds_to_zero() {
let vars = vec![
vd(0, Expr::Unknown, 4),
vd(
1,
Expr::BinOp(rsleigh_decompile::ir::BinOpKind::Sub, VarId(0), VarId(0)),
4,
),
];
let ssa = run_fold(vars, 1);
match ssa.vars[1].expr {
Expr::Const(0, _) => {}
ref other => panic!("expected Const(0, _), got {:?}", other),
}
}