use super::{Fin, PassKind, Runner, Step, Stop, discard, finish, shrinks};
use crate::engine::Exhausted;
use crate::engine::budget::Counter;
use crate::expr::{Context, OpCode};
use crate::hash::combine;
use crate::mba::certify;
use crate::mba::{
CacheEntry, CacheKey, Claim, LOWERING_VERSION, MbaAnswer, MbaConfig, MbaExpr, Refusal, Verdict,
lift_id, lower_id,
};
const SAMPLE_POINTS: u32 = certify::SAMPLE_POINTS as u32;
fn mixed_op(op: OpCode) -> bool {
matches!(
op,
OpCode::Add
| OpCode::Sub
| OpCode::Mul
| OpCode::Neg
| OpCode::And
| OpCode::Or
| OpCode::Xor
| OpCode::Not
)
}
fn charge(r: &mut Runner<'_, '_>, a: &MbaExpr, b: &MbaExpr, evals: u64) -> Result<(), Stop> {
let per = (a.nodes().len() + b.nodes().len()) as u64;
r.meter
.charge(Counter::PassWork, per.saturating_mul(evals))
.map_err(Stop::Exhausted)
}
fn sampled(r: &mut Runner<'_, '_>, a: &MbaExpr, b: &MbaExpr, seed: u64) -> Result<bool, Stop> {
charge(r, a, b, u64::from(SAMPLE_POINTS))?;
let points = certify::sample_points(a.vars(), &[a, b], seed);
Ok(certify::refute(a, b, &points).is_none())
}
struct Gate<'x, 'r, 'a>(&'x mut Runner<'r, 'a>);
impl certify::Meter for Gate<'_, '_, '_> {
type Err = Stop;
fn left(&self) -> u64 {
self.0.meter.left().pass_work
}
fn charge(&mut self, units: u64) -> Result<(), Stop> {
self.0
.meter
.charge(Counter::PassWork, units)
.map_err(Stop::Exhausted)
}
}
fn exact(r: &mut Runner<'_, '_>, a: &MbaExpr, b: &MbaExpr) -> Result<certify::Report, Stop> {
let report = certify::prove(a, b, &mut Gate(r))?;
count(r).certificates.record(&report);
Ok(report)
}
fn count<'x>(r: &'x mut Runner<'_, '_>) -> &'x mut crate::engine::MbaStats {
&mut r.stats.mba
}
pub(super) fn asks(
r: &mut Runner<'_, '_>,
cx: &Context,
cfg: &MbaConfig,
n: u32,
) -> Result<bool, Stop> {
if let Some(&a) = r.asks.get(&n) {
return Ok(a);
}
let poly = r.inner.mba.solver.polynomial_fragments();
let op = cx.node(n).op;
let a = if mixed_op(op) || (poly && op == OpCode::Shl) {
match crate::mba::fits(cx, n, &cfg.limits) {
Ok(nodes) => {
r.meter.charge(Counter::PassWork, u64::from(nodes))?;
true
}
Err(_) => false,
}
} else {
false
};
r.asks.insert(n, a);
Ok(a)
}
pub(super) fn step(
r: &mut Runner<'_, '_>,
cx: &mut Context,
cfg: &MbaConfig,
n: u32,
) -> Result<Step, Stop> {
let poly = r.inner.mba.solver.polynomial_fragments();
let op = cx.node(n).op;
if !(mixed_op(op) || (poly && op == OpCode::Shl)) {
return Ok(Step::Normal(Fin::FINAL));
}
let (m, bindings) = match lower_id(cx, n, &cfg.limits) {
Ok(x) => x,
Err(why) => {
count(r).refused(&why);
return Ok(Step::Normal(Fin::FINAL));
}
};
let shape = m.shape();
if !shape.mixed && !(shape.degree >= 2 && poly) {
return Ok(Step::Normal(Fin::FINAL));
}
if shape.nodes < cfg.limits.min_nodes {
count(r).refused(&Refusal::TooSmall);
return Ok(Step::Normal(Fin::FINAL));
}
r.meter.charge(Counter::PassWork, u64::from(shape.nodes))?;
let inner = r.inner;
let prover_id = inner.mba.prover.as_ref().map_or("", |p| p.id());
let mk = m.key();
let mut kh = combine(mk[0], mk[1]);
for b in inner.mba.solver.id().bytes().chain(prover_id.bytes()) {
kh = combine(kh, u64::from(b));
}
kh = combine(
combine(kh, u64::from(cfg.trust.backend_certificates)),
u64::from(cfg.trust.sampled),
);
let key = CacheKey([
combine(kh, LOWERING_VERSION),
combine(kh ^ 0x6361_6368, mk[1]),
]);
let (cand, lifted) = match inner.mba.cache.get(&key) {
Some(CacheEntry::NoSimpler) => {
count(r).cache_hits += 1;
return Ok(Step::Normal(Fin::FINAL));
}
Some(CacheEntry::Simplified(e)) if e.vars() == m.vars() => {
count(r).cache_hits += 1;
(e, None)
}
_ => {
let answer = match r.mba_answers.get(&key).cloned() {
Some(a) => {
count(r).cache_hits += 1;
a
}
None => {
r.meter.charge(Counter::MbaCalls, 1)?;
count(r).calls += 1;
let budget = cfg
.budget
.with_evidence(cfg.budget.evidence && cfg.trust.backend_certificates);
let a = inner.mba.solver.solve(&m, &budget);
if a != MbaAnswer::Exhausted {
r.mba_answers.insert(key, a.clone());
}
a
}
};
match answer {
MbaAnswer::Simplified { expr, claim } => {
if expr.vars() != m.vars() || expr.width() != m.width() {
count(r).refuted += 1;
return Ok(Step::Normal(Fin::FINAL));
}
if !sampled(r, &m, &expr, mk[0])? {
count(r).refuted += 1;
return Ok(Step::Normal(Fin::FINAL));
}
let before = cx.len() as u32;
let e = r.build(cx, |cx| lift_id(cx, &expr, &bindings))?;
if e == n {
return Ok(Step::Normal(Fin::FINAL));
}
if let Err(fin) = shrinks(r, cx, n, e, &bindings.atoms)? {
count(r).not_smaller += 1;
r.stats.passes.entry("mba").or_default().rejected_cost += 1;
discard(r, cx, before);
return Ok(Step::Normal(fin));
}
let own = exact(r, &m, &expr)?;
if own.verdict == Verdict::Refuted {
count(r).refuted += 1;
discard(r, cx, before);
return Ok(Step::Normal(Fin::FINAL));
}
let proved = own.verdict == Verdict::Proved
|| inner.mba.prover.as_ref().is_some_and(|p| {
p.prove_equal(&m, &expr, &cfg.budget) == Verdict::Proved
})
|| (cfg.trust.backend_certificates && claim >= Claim::Proved)
|| cfg.trust.sampled;
if !proved {
count(r).proof_unknown += 1;
discard(r, cx, before);
let fin = if own.over_budget {
Fin::capped(Exhausted::PassWork)
} else {
Fin::FINAL
};
return Ok(Step::Normal(fin));
}
inner
.mba
.cache
.put(&key, &CacheEntry::Simplified(expr.clone()));
(expr, Some((e, before)))
}
MbaAnswer::NoSimpler => {
count(r).no_simpler += 1;
inner.mba.cache.put(&key, &CacheEntry::NoSimpler);
return Ok(Step::Normal(Fin::FINAL));
}
MbaAnswer::Unsupported(_) => {
count(r).unsupported += 1;
return Ok(Step::Normal(Fin::FINAL));
}
MbaAnswer::Exhausted => {
count(r).exhausted += 1;
return Ok(Step::Normal(Fin::capped(Exhausted::MbaCalls)));
}
}
}
};
let (e, before) = match lifted {
Some(x) => x,
None => {
let before = cx.len() as u32;
(r.build(cx, |cx| lift_id(cx, &cand, &bindings))?, before)
}
};
if e != n {
r.sample(cx, n)?;
r.sample(cx, e)?;
if r.samples[&n] != r.samples[&e] {
count(r).refuted += 1;
discard(r, cx, before);
return Ok(Step::Normal(Fin::FINAL));
}
}
let out = finish(
r,
cx,
PassKind::Mba,
n,
e,
before,
&bindings.atoms,
Fin::FINAL,
)?;
if matches!(out, Step::To(..)) {
count(r).simplified += 1;
} else if e != n {
count(r).not_smaller += 1;
}
Ok(out)
}