use rucc_ir::{Block, Def, Extra, Flags, Func, Inst, Opcode, Type, Value};
use crate::{Fuel, Pass};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Simplify;
impl Pass for Simplify {
fn name(&self) -> &'static str {
"simplify"
}
fn describe(&self) -> &'static str {
"a negated comparison becomes the comparison with the opposite predicate"
}
fn run(&self, func: &mut Func, fuel: &mut Fuel) -> bool {
let mut changed = false;
for block in func.blocks().collect::<Vec<Block>>() {
for inst in func.insts(block).collect::<Vec<Inst>>() {
let Some(flip) = negated_comparison(func, inst) else { continue };
if !fuel.take() {
continue;
}
let args = func.push_values(&[flip.lhs, flip.rhs]);
let data = &mut func[inst];
data.opcode = flip.opcode;
data.flags = flip.flags;
data.args = args;
data.extra = flip.extra;
changed = true;
}
}
changed
}
}
struct Flip {
opcode: Opcode,
flags: Flags,
extra: Extra,
lhs: Value,
rhs: Value,
}
fn negated_comparison(func: &Func, inst: Inst) -> Option<Flip> {
let data = &func[inst];
if data.opcode != Opcode::Xor {
return None;
}
let args = &func[data.args];
let (&first, &second) = (args.first()?, args.get(1)?);
if func[first].ty != Type::int(1) {
return None;
}
let cmp = match (all_ones(func, first), all_ones(func, second)) {
(true, false) => second,
(false, true) => first,
_ => return None,
};
let Def::Result { inst: cmp, .. } = func[cmp].def else { return None };
let data = &func[cmp];
let extra = match (data.opcode, data.extra) {
(Opcode::ICmp, Extra::IntPred(pred)) => Extra::IntPred(pred.inverse()),
(Opcode::FCmp, Extra::FloatPred(pred)) => Extra::FloatPred(pred.inverse()),
_ => return None,
};
let args = &func[data.args];
Some(Flip {
opcode: data.opcode,
flags: data.flags,
extra,
lhs: *args.first()?,
rhs: *args.get(1)?,
})
}
fn all_ones(func: &Func, value: Value) -> bool {
let ty = func[value].ty;
let Def::Result { inst, .. } = func[value].def else { return false };
let data = &func[inst];
let Extra::Imm(at) = data.extra else { return false };
if data.opcode != Opcode::IConst {
return false;
}
func[at].signed(ty) == -1
}
#[cfg(test)]
mod tests {
use rucc_base::Interner;
use rucc_ir::{
Block, Builder, Extra, Flags, Float, FloatPred, Func, IntPred, Opcode, Signature, Type,
};
use crate::{Fuel, Pass, simplify::Simplify};
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(1)]));
let block = func.create_block();
(names, func, block)
}
fn simplify(func: &mut Func) -> bool {
Simplify.run(func, &mut Fuel::unlimited())
}
fn came_from(func: &Func, value: rucc_ir::Value) -> (Opcode, Extra) {
let rucc_ir::Def::Result { inst, .. } = func[value].def else { panic!("not a result") };
(func[inst].opcode, func[inst].extra)
}
#[test]
fn a_negated_float_comparison_becomes_the_opposite_predicate() {
for pred in FloatPred::all() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(64), 0);
let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
let cmp = build.fcmp(pred, x, x, Flags::NONE);
let ones = build.iconst(Type::int(1), -1);
let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
build.ret(&[not]);
assert!(simplify(&mut func), "{pred:?}");
assert_eq!(
came_from(&func, not),
(Opcode::FCmp, Extra::FloatPred(pred.inverse())),
"{pred:?}"
);
}
}
#[test]
fn a_negated_integer_comparison_becomes_the_opposite_predicate() {
for pred in IntPred::all() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 3);
let cmp = build.icmp(pred, x, x);
let ones = build.iconst(Type::int(1), -1);
let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
build.ret(&[not]);
assert!(simplify(&mut func), "{pred:?}");
assert_eq!(
came_from(&func, not),
(Opcode::ICmp, Extra::IntPred(pred.inverse())),
"{pred:?}"
);
}
}
#[test]
fn the_constant_is_found_on_either_side() {
for swapped in [false, true] {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 3);
let cmp = build.icmp(IntPred::Slt, x, x);
let ones = build.iconst(Type::int(1), -1);
let (lhs, rhs) = if swapped { (ones, cmp) } else { (cmp, ones) };
let not = build.binary(Opcode::Xor, lhs, rhs, Flags::NONE);
build.ret(&[not]);
assert!(simplify(&mut func), "swapped {swapped}");
assert_eq!(came_from(&func, not).1, Extra::IntPred(IntPred::Sge));
}
}
#[test]
fn an_exclusive_or_of_two_comparisons_is_left_alone() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 3);
let a = build.icmp(IntPred::Slt, x, x);
let b = build.icmp(IntPred::Sgt, x, x);
let differ = build.binary(Opcode::Xor, a, b, Flags::NONE);
build.ret(&[differ]);
assert!(!simplify(&mut func));
assert_eq!(came_from(&func, differ).0, Opcode::Xor);
}
#[test]
fn an_exclusive_or_of_something_that_is_not_a_comparison_is_left_alone() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 3);
let narrow = build.unary(Opcode::Trunc, x, Type::int(1));
let ones = build.iconst(Type::int(1), -1);
let not = build.binary(Opcode::Xor, narrow, ones, Flags::NONE);
build.ret(&[not]);
assert!(!simplify(&mut func));
assert_eq!(came_from(&func, not).0, Opcode::Xor);
}
#[test]
fn a_wider_exclusive_or_with_one_is_not_a_negation_and_is_left_alone() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 3);
let cmp = build.icmp(IntPred::Slt, x, x);
let wide = build.unary(Opcode::ZExt, cmp, Type::int(32));
let one = build.iconst(Type::int(32), 1);
let flipped = build.binary(Opcode::Xor, wide, one, Flags::NONE);
let narrow = build.unary(Opcode::Trunc, flipped, Type::int(1));
build.ret(&[narrow]);
assert!(!simplify(&mut func), "an i32 xor 1 flips one bit of thirty two");
assert_eq!(came_from(&func, flipped).0, Opcode::Xor);
}
#[test]
fn the_comparisons_flags_travel_with_the_predicate() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(64), 0);
let x = build.unary(Opcode::Bitcast, x, Type::float(Float::F64));
let cmp = build.fcmp(FloatPred::Olt, x, x, Flags::FAST);
let ones = build.iconst(Type::int(1), -1);
let not = build.binary(Opcode::Xor, cmp, ones, Flags::NONE);
build.ret(&[not]);
assert!(simplify(&mut func));
let rucc_ir::Def::Result { inst, .. } = func[not].def else { panic!("not a result") };
assert_eq!(func[inst].flags, Flags::FAST);
}
#[test]
fn fuel_stops_the_transformation_and_not_the_walk() {
let (_, mut func, block) = blank();
let mut build = Builder::new(&mut func, block);
let x = build.iconst(Type::int(32), 3);
let a = build.icmp(IntPred::Slt, x, x);
let b = build.icmp(IntPred::Sgt, x, x);
let ones = build.iconst(Type::int(1), -1);
let first = build.binary(Opcode::Xor, a, ones, Flags::NONE);
let second = build.binary(Opcode::Xor, b, ones, Flags::NONE);
let both = build.binary(Opcode::And, first, second, Flags::NONE);
build.ret(&[both]);
assert!(Simplify.run(&mut func, &mut Fuel::of(1)));
assert_eq!(came_from(&func, first).0, Opcode::ICmp);
assert_eq!(came_from(&func, second).0, Opcode::Xor);
}
}