use super::eval::{eval, literal, width_of};
use super::ir::{FactPred, Literal, NodeId, ParamKind, RNode, Rule};
use super::matcher::{Bindings, Match};
use crate::expr::Context;
use crate::facts::known::bv_and;
use crate::facts::{Assumptions, Facts, Reliance};
use crate::ops::CmpOp;
use crate::{BitVec, Width};
pub(crate) fn const_params(cx: &Context, rule: &Rule, b: &Bindings) -> Option<Vec<BitVec>> {
let widths = b.width_values()?;
rule.params
.iter()
.zip(&b.params)
.map(|(p, node)| {
let node = (*node)?;
if p.kind == ParamKind::Const {
cx.const_val(node)
} else {
let w = u16::try_from(p.width.eval(&widths)).ok()?;
Some(BitVec::zero(Width::new(w).ok()?))
}
})
.collect()
}
pub(crate) type OneUse<'a> = &'a mut dyn FnMut(&Context, u32) -> bool;
#[derive(Default)]
pub(crate) struct ApplyEnv<'a> {
pub(crate) assumptions: Option<&'a Assumptions>,
pub(crate) fact_cap: Option<u32>,
pub(crate) out_of_facts: bool,
pub(crate) degraded: bool,
pub(crate) steps: u32,
pub(crate) matched: bool,
pub(crate) instantiating: bool,
pub(crate) rel: Reliance,
pub(crate) one_use: Option<OneUse<'a>>,
pub(crate) shared: bool,
}
impl ApplyEnv<'_> {
pub(crate) fn new() -> Self {
ApplyEnv {
steps: super::matcher::MATCH_STEPS,
..ApplyEnv::default()
}
}
}
#[allow(clippy::too_many_arguments)]
fn operand_facts(
env: &mut ApplyEnv<'_>,
cx: &mut Context,
rule: &Rule,
b: &Bindings,
n: NodeId,
widths: &[u16],
consts: &[BitVec],
lets: &[Option<BitVec>],
) -> Option<Facts> {
match &rule.nodes[n as usize] {
RNode::Param(i) => {
let node = b.params[*i as usize]?;
if let Some(v) = cx.const_val(node) {
return Some(Facts::constant(&v));
}
let cap = env.fact_cap.unwrap_or(u32::MAX);
if cap == 0 {
env.degraded = true;
env.out_of_facts = true;
return None;
}
let e = cx.handle(node);
let work0 = cx.facts.work;
let f = operand_query(env, cx, e, cap);
if let Some(c) = &mut env.fact_cap {
let used = u32::try_from(cx.facts.work - work0).unwrap_or(u32::MAX);
*c = c.saturating_sub(used);
}
f
}
_ => {
let v = eval(rule, n, widths, consts, lets)?.bv()?;
Some(Facts::constant(&v))
}
}
}
fn operand_query(
env: &mut ApplyEnv<'_>,
cx: &mut Context,
e: crate::Expr,
cap: u32,
) -> Option<Facts> {
match env.assumptions {
Some(a) => match cx.facts_under_cap(e, a, cap) {
Ok(Ok((f, r))) => {
env.rel |= r;
Some(f)
}
Ok(Err(_)) => None,
Err(_) => {
env.degraded = true;
None
}
},
None => {
let f = cx.try_facts_cap(e, cap).ok().flatten();
if f.is_none() {
env.degraded = true;
}
f
}
}
}
pub(crate) fn guard_holds(
env: &mut ApplyEnv<'_>,
cx: &mut Context,
rule: &Rule,
b: &Bindings,
consts: &[BitVec],
lets: &[Option<BitVec>],
) -> bool {
let Some(g) = rule.guard else {
return true;
};
let Some(widths) = b.width_values() else {
return false;
};
holds(env, cx, rule, b, g, &widths, consts, lets) == Some(true)
}
#[allow(clippy::too_many_arguments)]
fn holds(
env: &mut ApplyEnv<'_>,
cx: &mut Context,
rule: &Rule,
b: &Bindings,
n: NodeId,
widths: &[u16],
consts: &[BitVec],
lets: &[Option<BitVec>],
) -> Option<bool> {
match &rule.nodes[n as usize] {
RNode::And(x, y) => Some(
holds(env, cx, rule, b, *x, widths, consts, lets)?
&& holds(env, cx, rule, b, *y, widths, consts, lets)?,
),
RNode::Or(x, y) => Some(
holds(env, cx, rule, b, *x, widths, consts, lets)?
|| holds(env, cx, rule, b, *y, widths, consts, lets)?,
),
RNode::Not(x) => Some(!holds(env, cx, rule, b, *x, widths, consts, lets)?),
RNode::Fact(FactPred::OneUse, x, _) => {
let Some(one_use) = env.one_use.as_deref_mut() else {
return Some(true);
};
let before = cx.len();
let node = build(cx, rule, *x, widths, b, lets)?;
let one = cx.len() == before && one_use(cx, node);
env.shared |= !one;
Some(one)
}
RNode::Fact(p, x, m) => {
let fx = operand_facts(env, cx, rule, b, *x, widths, consts, lets)?;
Some(match p {
FactPred::OneUse => true,
FactPred::NonZero => {
let z = Facts::constant(&BitVec::zero(fx.width()));
crate::facts::decide_cmp(CmpOp::Eq, &fx, &z) == Some(false)
}
FactPred::ZeroBits => {
let mv = eval(rule, (*m)?, widths, consts, lets)?.bv()?;
bv_and(&fx.known().maybe_one(), &mv).is_zero()
}
FactPred::OneBits => {
let mv = eval(rule, (*m)?, widths, consts, lets)?.bv()?;
bv_and(&fx.known().known_one(), &mv) == mv
}
FactPred::Disjoint => {
let fy = operand_facts(env, cx, rule, b, (*m)?, widths, consts, lets)?;
bv_and(&fx.known().maybe_one(), &fy.known().maybe_one()).is_zero()
}
FactPred::FpNotNan | FactPred::FpFinite | FactPred::FpNonZero => {
let RNode::Lit(Literal::Float { eb, .. }) = &rule.nodes[(*m)? as usize] else {
return None;
};
let eb = u32::try_from(eb.eval(widths)).ok()?;
let w = u32::from(fx.width().bits());
let f = crate::fp::FpFormat::new(eb, w.checked_sub(eb)?).ok()?;
let (nan, inf, zero) = crate::facts::fp_may_be(f, &fx);
match p {
FactPred::FpNotNan => !nan,
FactPred::FpFinite => !nan && !inf,
_ => !zero,
}
}
FactPred::Proves => {
let RNode::Cmp(op, l, r) = &rule.nodes[*x as usize] else {
return None;
};
let (stored, swap) = op.canonical();
let (l, r) = if swap { (*r, *l) } else { (*l, *r) };
if let (RNode::Param(i), RNode::Param(j)) =
(&rule.nodes[l as usize], &rule.nodes[r as usize])
{
let (x, y) = (b.params[*i as usize]?, b.params[*j as usize]?);
if x == y {
return Some(matches!(stored, CmpOp::Eq | CmpOp::Ule | CmpOp::Sle));
}
if let Some(a) = env.assumptions
&& let Some((v, rel)) = cx.assumed_order(a, stored, x, y)
{
if v {
env.rel |= rel;
}
return Some(v);
}
}
let fl = operand_facts(env, cx, rule, b, l, widths, consts, lets)?;
let fr = operand_facts(env, cx, rule, b, r, widths, consts, lets)?;
crate::facts::decide_cmp(stored, &fl, &fr) == Some(true)
}
})
}
_ => Some(eval(rule, n, widths, consts, lets)?.truthy()),
}
}
pub(crate) fn instantiate(
cx: &mut Context,
rule: &Rule,
b: &Bindings,
lets: &[Option<BitVec>],
) -> Option<u32> {
let widths = b.width_values()?;
build(cx, rule, rule.rhs, &widths, b, lets)
}
fn build(
cx: &mut Context,
rule: &Rule,
n: NodeId,
widths: &[u16],
b: &Bindings,
lets: &[Option<BitVec>],
) -> Option<u32> {
let w = width_of(rule, n, widths);
let rec = |cx: &mut Context, m: NodeId| build(cx, rule, m, widths, b, lets);
Some(match &rule.nodes[n as usize] {
RNode::Param(i) => b.params[*i as usize]?,
RNode::Let(i) => cx
.mk_const(&lets.get(*i as usize).copied().flatten()?)
.ok()?,
RNode::Lit(l) => cx.mk_const(&literal(l, w?, widths)?).ok()?,
RNode::Un(op, a) => {
let a = rec(cx, *a)?;
cx.c_un(*op, a).ok()?
}
RNode::Bin(op, x, y) => {
let (x, y) = (rec(cx, *x)?, rec(cx, *y)?);
cx.c_bin(*op, x, y).ok()?
}
RNode::Cmp(op, x, y) => {
let (x, y) = (rec(cx, *x)?, rec(cx, *y)?);
let (stored, swap) = op.canonical();
let (x, y) = if swap { (y, x) } else { (x, y) };
cx.c_cmp(stored, x, y).ok()?
}
RNode::Zext(a) => {
let a = rec(cx, *a)?;
cx.c_zext(a, w?.bits()).ok()?
}
RNode::Sext(a) => {
let a = rec(cx, *a)?;
cx.c_sext(a, w?.bits()).ok()?
}
RNode::Extract(lo, a) => {
let a = rec(cx, *a)?;
let lo = u16::try_from(lo.eval(widths)).ok()?;
cx.c_extract(a, lo, w?.bits()).ok()?
}
RNode::Concat(h, l) => {
let (h, l) = (rec(cx, *h)?, rec(cx, *l)?);
cx.c_concat(h, l).ok()?
}
RNode::Select(c, t, f) => {
let (c, t, f) = (rec(cx, *c)?, rec(cx, *t)?, rec(cx, *f)?);
cx.c_select(c, t, f).ok()?
}
RNode::Fp(f) => {
let d = super::eval::fp_desc(rule, n, f, widths)?;
let args: Vec<u32> = f.args.iter().map(|&a| rec(cx, a)).collect::<Option<_>>()?;
cx.c_fp(d, &args).ok()?
}
_ => return None,
})
}
pub(crate) fn try_apply(cx: &mut Context, rule: &Rule, node: u32) -> Option<u32> {
try_apply_with(&mut ApplyEnv::new(), cx, rule, node)
}
pub(crate) fn try_apply_with(
env: &mut ApplyEnv<'_>,
cx: &mut Context,
rule: &Rule,
node: u32,
) -> Option<u32> {
let b = match super::matcher::match_rule_steps(cx, rule, node, &mut env.steps) {
Match::Yes(b) => {
env.matched = true;
b
}
Match::No => return None,
Match::OutOfSteps => {
env.degraded = true;
return None;
}
};
let consts = const_params(cx, rule, &b)?;
let widths = b.width_values()?;
let lets = super::eval::eval_lets(rule, &widths, &consts);
if lets.iter().any(Option::is_none) && !rule.lets.is_empty() {
return None;
}
if !guard_holds(env, cx, rule, &b, &consts, &lets) {
return None;
}
env.instantiating = true;
instantiate(cx, rule, &b, &lets)
}