use core::cmp::Ordering;
use super::ir::ParamKind;
use super::ir::{NodeId, RNode, Rounding, Rule, Sort, WExpr};
use crate::expr::{Context, OpCode};
pub(crate) fn op_rank(op: OpCode) -> u8 {
match op {
OpCode::Const | OpCode::Sym => 0,
OpCode::Eq => 10,
OpCode::Ne => 11,
OpCode::Ult => 12,
OpCode::Ule => 13,
OpCode::Slt => 14,
OpCode::Sle => 15,
OpCode::Select => 34,
OpCode::And => 40,
OpCode::Or => 41,
OpCode::Xor => 42,
OpCode::Add => 43,
OpCode::Sub => 44,
OpCode::Not => 45,
OpCode::Neg => 46,
OpCode::Shl => 39,
OpCode::LShr => 48,
OpCode::AShr => 49,
OpCode::RotL => 50,
OpCode::RotR => 51,
OpCode::BitRev => 52,
OpCode::Bswap => 53,
OpCode::Ctz => 54,
OpCode::Clz => 55,
OpCode::Popcnt => 56,
OpCode::Pext => 57,
OpCode::Pdep => 58,
OpCode::Mul => 59,
OpCode::SMulHi => 60,
OpCode::UMulHi => 61,
OpCode::SRem => 62,
OpCode::SDiv => 63,
OpCode::URem => 64,
OpCode::UDiv => 65,
OpCode::Zext => 66,
OpCode::Sext => 67,
OpCode::Extract => 68,
OpCode::Concat => 69,
OpCode::FAdd => 70,
OpCode::FMul => 71,
OpCode::FFma => 72,
OpCode::FDiv => 73,
OpCode::FSqrt => 74,
OpCode::FRem => 75,
OpCode::FRound => 76,
OpCode::FMin => 77,
OpCode::FMax => 78,
OpCode::FEq => 79,
OpCode::FLt => 80,
OpCode::FLe => 81,
OpCode::FConvert => 82,
OpCode::FFromS => 83,
OpCode::FFromU => 84,
OpCode::FToS => 85,
OpCode::FToU => 86,
OpCode::Ext1o0
| OpCode::Ext1o1
| OpCode::Ext1o2
| OpCode::Ext1o3
| OpCode::Ext1o4
| OpCode::Ext1o5
| OpCode::Ext1o6
| OpCode::Ext1o7
| OpCode::Ext2o0
| OpCode::Ext2o1
| OpCode::Ext2o2
| OpCode::Ext2o3
| OpCode::Ext2o4
| OpCode::Ext2o5
| OpCode::Ext2o6
| OpCode::Ext2o7
| OpCode::Ext3o0
| OpCode::Ext3o1
| OpCode::Ext3o2
| OpCode::Ext3o3
| OpCode::Ext3o4
| OpCode::Ext3o5
| OpCode::Ext3o6
| OpCode::Ext3o7 => 100,
}
}
fn op_commutative(op: OpCode) -> bool {
matches!(
op,
OpCode::Add
| OpCode::Mul
| OpCode::UMulHi
| OpCode::SMulHi
| OpCode::And
| OpCode::Or
| OpCode::Xor
| OpCode::Eq
| OpCode::Ne
| OpCode::FAdd
| OpCode::FMul
| OpCode::FMin
| OpCode::FMax
| OpCode::FEq
)
}
#[derive(Clone, Debug)]
enum T {
Const,
Var(u16),
App {
op: OpCode,
params: (Option<WExpr>, Option<WExpr>, Option<Box<FpParams>>),
kids: Vec<T>,
},
}
type FpParams = (Option<Rounding>, WExpr, WExpr, Option<(WExpr, WExpr)>);
fn constant(rule: &Rule, n: NodeId) -> bool {
let mut stack = vec![n];
while let Some(k) = stack.pop() {
match &rule.nodes[k as usize] {
RNode::Param(i) if rule.params[usize::from(*i)].kind != ParamKind::Const => {
return false;
}
other => super::compile::push_children(other, &mut stack),
}
}
true
}
fn term(rule: &Rule, n: NodeId) -> T {
if constant(rule, n) {
return T::Const;
}
let rec = |m: NodeId| term(rule, m);
let width = || match &rule.sorts[n as usize] {
Sort::Bv(w) => Some(w.clone()),
Sort::Bool => None,
};
let app = |op, params, kids| T::App { op, params, kids };
match &rule.nodes[n as usize] {
RNode::Param(i) => T::Var(*i),
RNode::Lit(_) | RNode::Let(_) => T::Const,
RNode::Un(op, a) => app(OpCode::from_un(*op), (None, None, None), vec![rec(*a)]),
RNode::Bin(op, a, b) => app(
OpCode::from_bin(*op),
(None, None, None),
vec![rec(*a), rec(*b)],
),
RNode::Cmp(op, a, b) => {
let (stored, swap) = op.canonical();
let (a, b) = if swap { (*b, *a) } else { (*a, *b) };
app(
OpCode::from_cmp(stored),
(None, None, None),
vec![rec(a), rec(b)],
)
}
RNode::Zext(a) => app(OpCode::Zext, (width(), None, None), vec![rec(*a)]),
RNode::Sext(a) => app(OpCode::Sext, (width(), None, None), vec![rec(*a)]),
RNode::Extract(lo, a) => app(
OpCode::Extract,
(width(), Some(lo.clone()), None),
vec![rec(*a)],
),
RNode::Concat(h, l) => app(OpCode::Concat, (None, None, None), vec![rec(*h), rec(*l)]),
RNode::Select(c, t, f) => app(
OpCode::Select,
(None, None, None),
vec![rec(*c), rec(*t), rec(*f)],
),
RNode::Fp(f) => app(
f.kind.opcode(),
(
width(),
None,
Some(Box::new((
f.rounding,
f.eb.clone(),
f.sb.clone(),
f.to.clone(),
))),
),
f.args.iter().map(|&a| rec(a)).collect(),
),
_ => T::Const,
}
}
fn weight(t: &T) -> u64 {
match t {
T::Const | T::Var(_) => 1,
T::App { kids, .. } => 1 + kids.iter().map(weight).sum::<u64>(),
}
}
fn count_vars(t: &T, out: &mut Vec<u64>) {
match t {
T::Const => {}
T::Var(i) => {
let i = usize::from(*i);
if out.len() <= i {
out.resize(i + 1, 0);
}
out[i] += 1;
}
T::App { kids, .. } => kids.iter().for_each(|k| count_vars(k, out)),
}
}
fn same(a: &T, b: &T) -> bool {
match (a, b) {
(T::Const, T::Const) => true,
(T::Var(x), T::Var(y)) => x == y,
(
T::App {
op: oa,
params: pa,
kids: ka,
},
T::App {
op: ob,
params: pb,
kids: kb,
},
) => {
oa == ob
&& pa == pb
&& ka.len() == kb.len()
&& (ka.iter().zip(kb).all(|(x, y)| same(x, y))
|| (op_commutative(*oa)
&& ka.len() == 2
&& same(&ka[0], &kb[1])
&& same(&ka[1], &kb[0])))
}
_ => false,
}
}
fn greater(s: &T, t: &T) -> bool {
let (mut cs, mut ct) = (Vec::new(), Vec::new());
count_vars(s, &mut cs);
count_vars(t, &mut ct);
if ct
.iter()
.enumerate()
.any(|(i, &n)| n > cs.get(i).copied().unwrap_or(0))
{
return false;
}
match weight(s).cmp(&weight(t)) {
Ordering::Greater => return true,
Ordering::Less => return false,
Ordering::Equal => {}
}
let (
T::App {
op: os,
params: ps,
kids: ks,
},
T::App {
op: ot,
params: pt,
kids: kt,
},
) = (s, t)
else {
return false;
};
match op_rank(*os).cmp(&op_rank(*ot)) {
Ordering::Greater => return true,
Ordering::Less => return false,
Ordering::Equal => {}
}
if os != ot || ps != pt {
return false;
}
if op_commutative(*os) {
multiset_greater(ks, kt, &same, &greater)
} else {
for (a, b) in ks.iter().zip(kt) {
if !same(a, b) {
return greater(a, b);
}
}
false
}
}
fn multiset_greater<X>(
s: &[X],
t: &[X],
eq: &dyn Fn(&X, &X) -> bool,
gt: &dyn Fn(&X, &X) -> bool,
) -> bool {
let mut rs: Vec<&X> = s.iter().collect();
let mut rt: Vec<&X> = Vec::new();
for x in t {
match rs.iter().position(|y| eq(y, x)) {
Some(i) => {
rs.remove(i);
}
None => rt.push(x),
}
}
!rs.is_empty() && rt.iter().all(|x| rs.iter().any(|y| gt(y, x)))
}
pub(crate) fn kbo_greater(rule: &Rule, s: NodeId, t: NodeId) -> bool {
greater(&term(rule, s), &term(rule, t))
}
#[cfg_attr(not(all(test, feature = "check")), allow(dead_code))]
const GROUND_STEPS: u32 = 4096;
#[cfg_attr(not(all(test, feature = "check")), allow(dead_code))]
pub(crate) fn ground_greater(cx: &Context, s: u32, t: u32) -> Option<bool> {
let mut steps = GROUND_STEPS;
ground(cx, s, t, &mut steps)
}
fn ground(cx: &Context, mut s: u32, mut t: u32, steps: &mut u32) -> Option<bool> {
loop {
if s == t {
return Some(false);
}
if *steps == 0 {
return None;
}
*steps -= 1;
let (ws, wt) = (cx.meta[s as usize].tree, cx.meta[t as usize].tree);
if ws == u32::MAX && wt == u32::MAX {
return None;
}
match ws.cmp(&wt) {
Ordering::Greater => return Some(true),
Ordering::Less => return Some(false),
Ordering::Equal => {}
}
let (ns, nt) = (cx.node(s), cx.node(t));
if ns.op.arity() == 0 || nt.op.arity() == 0 {
return Some(false);
}
match op_rank(ns.op).cmp(&op_rank(nt.op)) {
Ordering::Greater => return Some(true),
Ordering::Less => return Some(false),
Ordering::Equal => {}
}
let param = |op: OpCode, width: u16, aux: u8, b: u32| match op {
OpCode::Zext | OpCode::Sext => (width, 0),
OpCode::Extract => (width, b),
OpCode::FConvert => (u16::from(aux), b),
op if op.is_fp() => (u16::from(aux), 0),
_ => (0, 0),
};
match param(ns.op, ns.width, ns.aux, ns.b).cmp(¶m(nt.op, nt.width, nt.aux, nt.b)) {
Ordering::Greater => return Some(true),
Ordering::Less => return Some(false),
Ordering::Equal => {}
}
let (ks, kt) = (
[ns.a, ns.b, ns.c][..ns.op.arity()].to_vec(),
[nt.a, nt.b, nt.c][..nt.op.arity()].to_vec(),
);
if op_commutative(ns.op) {
let budget = core::cell::Cell::new(*steps);
let undecided = core::cell::Cell::new(false);
let gt = |a: &u32, b: &u32| {
let mut st = budget.get();
let r = ground(cx, *a, *b, &mut st);
budget.set(st);
if r.is_none() {
undecided.set(true);
}
r == Some(true)
};
let r = multiset_greater(&ks, &kt, &|a, b| a == b, >);
*steps = budget.get();
return if r {
Some(true)
} else if undecided.get() {
None
} else {
Some(false)
};
}
match ks.iter().zip(&kt).find(|(a, b)| a != b) {
Some((&a, &b)) => {
s = a;
t = b;
}
None => return Some(false),
}
}
}