pub(crate) mod budget;
mod dispatch;
pub(crate) mod pass;
mod stats;
#[cfg(test)]
pub(crate) mod tests;
use std::borrow::Cow;
use std::sync::{Arc, OnceLock};
use core::fmt;
pub use budget::{Admission, Allowance, Budget, Cap, Clock, Deadline, Exhausted};
pub use stats::{
By, CertStats, Event, Hooks, MbaStats, Observer, PassCounts, Reject, RuleCensus, RuleCounts,
Stats,
};
use budget::{Counter, Meter};
use dispatch::DispatchNet;
use crate::error::Error;
use crate::expr::{Context, Expr};
use crate::facts::{Assumptions, Reliance};
use crate::hash::{IdMap, combine};
use crate::rules::apply::{ApplyEnv, try_apply_with};
use crate::rules::matcher::MATCH_STEPS;
use crate::rules::{Ledger, Rule, RuleProgram};
use crate::{BitVec, Width};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Phase {
Local {
groups: Vec<String>,
},
FactFold,
Linear,
Bitwise,
Xor,
Compares,
Casts,
Demanded,
LinearMba,
Shuffle,
Invert,
#[cfg(feature = "mba")]
Mba(crate::mba::MbaConfig),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Strategy {
pub name: Cow<'static, str>,
pub phases: Vec<Phase>,
pub max_rounds: u8,
}
impl Strategy {
pub fn new(name: impl Into<Cow<'static, str>>, phases: Vec<Phase>) -> Strategy {
Strategy {
name: name.into(),
phases,
max_rounds: 4,
}
}
pub fn standard() -> Strategy {
let core = || Phase::Local {
groups: builtin()
.0
.groups()
.iter()
.map(|g| g.name.clone())
.collect(),
};
Strategy::new(
"standard",
vec![
Phase::FactFold,
core(),
Phase::Linear,
Phase::Xor,
Phase::Casts,
Phase::Invert,
Phase::Compares,
Phase::Bitwise,
Phase::Demanded,
core(),
],
)
}
pub fn deobfuscate() -> Strategy {
let core = || Phase::Local {
groups: builtin()
.0
.groups()
.iter()
.map(|g| g.name.clone())
.collect(),
};
Strategy::new(
"deobfuscate",
vec![
Phase::FactFold,
core(),
Phase::Linear,
Phase::Xor,
Phase::Casts,
Phase::Invert,
Phase::Compares,
Phase::Bitwise,
Phase::LinearMba,
Phase::Shuffle,
Phase::Demanded,
core(),
],
)
}
#[cfg(feature = "mba")]
pub fn with_mba(mut self, config: crate::mba::MbaConfig) -> Strategy {
let at = self.phases.len().saturating_sub(1);
self.phases.insert(at, Phase::Mba(config));
self
}
pub fn with_rule_groups(mut self, groups: &[&str]) -> Strategy {
let mut found = false;
for p in &mut self.phases {
if let Phase::Local { groups: g } = p {
found = true;
g.extend(groups.iter().map(|s| s.to_string()));
}
}
if !found {
self.phases.push(Phase::Local {
groups: groups.iter().map(|s| s.to_string()).collect(),
});
}
self
}
pub fn with_max_rounds(mut self, n: u8) -> Strategy {
self.max_rounds = n.max(1);
self
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Verify {
pub sampled_points: u32,
pub unproven_points: u32,
pub tripwire: bool,
pub termination: bool,
}
impl Default for Verify {
fn default() -> Self {
Verify {
sampled_points: 0,
unproven_points: 16,
tripwire: false,
termination: false,
}
}
}
setters!(Verify {
with_sampled_points: sampled_points: u32,
with_unproven_points: unproven_points: u32,
with_tripwire: tripwire: bool,
with_termination: termination: bool,
});
impl Verify {
pub fn strict() -> Verify {
Verify {
sampled_points: 8,
unproven_points: 16,
tripwire: true,
termination: true,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BuildError {
Unproven {
rule: String,
},
DuplicateGroup(String),
UnknownGroup(String),
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BuildError::Unproven { rule } => write!(
f,
"rule `{rule}` is not vouched for by its ledger (allow unproven rules to link it)"
),
BuildError::DuplicateGroup(g) => write!(f, "group `{g}` is defined twice"),
BuildError::UnknownGroup(g) => write!(f, "the strategy names unknown group `{g}`"),
}
}
}
impl std::error::Error for BuildError {}
struct Linked {
rule: Rule,
proven: bool,
}
enum PhaseImpl {
Local(DispatchNet),
Pass(pass::PassKind),
#[cfg(feature = "mba")]
Mba(crate::mba::MbaConfig),
}
#[cfg(feature = "mba")]
#[derive(Clone)]
struct MbaService {
solver: Arc<dyn crate::mba::MbaSolver>,
prover: Option<Arc<dyn crate::mba::EquivalenceProver>>,
cache: Arc<dyn crate::mba::MbaCacheStore>,
}
#[cfg(feature = "mba")]
impl Default for MbaService {
fn default() -> Self {
MbaService {
solver: Arc::new(crate::mba::SignatureSolver),
prover: None,
cache: Arc::new(crate::mba::NoCache),
}
}
}
struct Inner {
rules: Vec<Linked>,
strategy: Strategy,
phases: Vec<PhaseImpl>,
#[cfg(feature = "mba")]
mba: MbaService,
verify: Verify,
id: u128,
}
#[derive(Clone)]
pub struct Engine {
inner: Arc<Inner>,
}
impl fmt::Debug for Engine {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Engine")
.field("strategy", &self.inner.strategy.name)
.field("rules", &self.inner.rules.len())
.finish_non_exhaustive()
}
}
#[derive(Default)]
pub struct EngineBuilder {
programs: Vec<(RuleProgram, Option<Ledger>)>,
strategy: Option<Strategy>,
verify: Verify,
allow_unproven: bool,
#[cfg(feature = "mba")]
mba: MbaService,
}
impl fmt::Debug for EngineBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EngineBuilder")
.field("programs", &self.programs.len())
.finish_non_exhaustive()
}
}
fn builtin() -> &'static (RuleProgram, Ledger) {
static BUILTIN: OnceLock<(RuleProgram, Ledger)> = OnceLock::new();
BUILTIN.get_or_init(|| {
let program = RuleProgram::compile(crate::rules::corpus::CORE)
.unwrap_or_else(|e| panic!("the built-in corpus does not compile: {e}"));
let ledger = Ledger::parse(crate::rules::corpus::CORE_LEDGER)
.unwrap_or_else(|e| panic!("the built-in ledger does not parse: {e}"));
(program, ledger)
})
}
impl EngineBuilder {
pub fn builtin(mut self) -> Self {
let (p, l) = builtin();
self.programs.push((p.clone(), Some(l.clone())));
self
}
pub fn program(mut self, program: RuleProgram, ledger: &Ledger) -> Self {
self.programs.push((program, Some(ledger.clone())));
self
}
pub fn unproven_program(mut self, program: RuleProgram) -> Self {
self.programs.push((program, None));
self
}
pub fn allow_unproven(mut self, yes: bool) -> Self {
self.allow_unproven = yes;
self
}
pub fn strategy(mut self, s: Strategy) -> Self {
self.strategy = Some(s);
self
}
pub fn verify(mut self, v: Verify) -> Self {
self.verify = v;
self
}
#[cfg(feature = "mba")]
pub fn mba_solver(mut self, s: Arc<dyn crate::mba::MbaSolver>) -> Self {
self.mba.solver = s;
self
}
#[cfg(feature = "mba")]
pub fn mba_prover(mut self, p: Arc<dyn crate::mba::EquivalenceProver>) -> Self {
self.mba.prover = Some(p);
self
}
#[cfg(feature = "mba")]
pub fn mba_cache(mut self, c: Arc<dyn crate::mba::MbaCacheStore>) -> Self {
self.mba.cache = c;
self
}
pub fn build(self) -> Result<Engine, BuildError> {
let mut rules: Vec<Linked> = Vec::new();
let mut groups: Vec<(String, Vec<u32>)> = Vec::new();
for (program, ledger) in &self.programs {
let base = rules.len() as u32;
for rule in program.rules() {
let proven = ledger
.as_ref()
.is_some_and(|l| l.vouches_for(&rule.name, rule.id));
if !proven && !self.allow_unproven {
return Err(BuildError::Unproven {
rule: rule.name.clone(),
});
}
rules.push(Linked {
rule: rule.clone(),
proven,
});
}
for g in program.groups() {
if groups.iter().any(|(n, _)| *n == g.name) {
return Err(BuildError::DuplicateGroup(g.name.clone()));
}
groups.push((
g.name.clone(),
g.rules.iter().map(|&i| base + i as u32).collect(),
));
}
}
let strategy = self.strategy.unwrap_or_else(|| {
Strategy::new(
"default",
vec![Phase::Local {
groups: groups.iter().map(|(n, _)| n.clone()).collect(),
}],
)
});
let plain: Vec<Rule> = rules.iter().map(|l| l.rule.clone()).collect();
let mut phases = Vec::new();
let mut id = combine(0x656e_6769_6e65, u64::from(strategy.max_rounds));
for phase in &strategy.phases {
match phase {
Phase::Local { groups: names } => {
let mut order = Vec::new();
for name in names {
let Some((_, rs)) = groups.iter().find(|(n, _)| n == name) else {
return Err(BuildError::UnknownGroup(name.clone()));
};
order.extend(rs.iter().copied().filter(|&r| plain[r as usize].decreasing));
}
id = combine(id, 1);
for &r in &order {
let rid = plain[r as usize].id.0;
id = combine(combine(id, rid[0]), rid[1]);
id = combine(id, u64::from(rules[r as usize].proven));
}
phases.push(PhaseImpl::Local(DispatchNet::new(&plain, &order)));
}
Phase::FactFold => {
id = combine(id, 2);
phases.push(PhaseImpl::Pass(pass::PassKind::FactFold));
}
Phase::Linear => {
id = combine(id, 3);
phases.push(PhaseImpl::Pass(pass::PassKind::Linear));
}
Phase::Bitwise => {
id = combine(id, 4);
phases.push(PhaseImpl::Pass(pass::PassKind::Bitwise));
}
Phase::Xor => {
id = combine(id, 5);
phases.push(PhaseImpl::Pass(pass::PassKind::Xor));
}
Phase::Compares => {
id = combine(id, 6);
phases.push(PhaseImpl::Pass(pass::PassKind::Compares));
}
Phase::Casts => {
id = combine(id, 7);
phases.push(PhaseImpl::Pass(pass::PassKind::Casts));
}
Phase::Demanded => {
id = combine(id, 8);
phases.push(PhaseImpl::Pass(pass::PassKind::Demanded));
}
Phase::LinearMba => {
id = combine(id, 9);
phases.push(PhaseImpl::Pass(pass::PassKind::LinearMba));
}
Phase::Shuffle => {
id = combine(id, 10);
phases.push(PhaseImpl::Pass(pass::PassKind::Shuffle));
}
Phase::Invert => {
id = combine(id, 12);
phases.push(PhaseImpl::Pass(pass::PassKind::Invert));
}
#[cfg(feature = "mba")]
Phase::Mba(cfg) => {
use core::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
cfg.hash(&mut h);
id = combine(combine(id, 11), h.finish());
let prover = self.mba.prover.as_ref().map_or("", |p| p.id());
for b in self
.mba
.solver
.id()
.bytes()
.chain([0])
.chain(prover.bytes())
{
id = combine(id, u64::from(b));
}
phases.push(PhaseImpl::Mba(*cfg));
}
}
}
let v = &self.verify;
for x in [
u64::from(v.sampled_points),
u64::from(v.unproven_points),
u64::from(v.tripwire),
u64::from(v.termination),
] {
id = combine(id, x);
}
Ok(Engine {
inner: Arc::new(Inner {
rules,
strategy,
phases,
#[cfg(feature = "mba")]
mba: self.mba.clone(),
verify: self.verify,
id: (u128::from(id) << 64) | u128::from(combine(id, 0x9e37)),
}),
})
}
}
#[derive(Default)]
#[non_exhaustive]
pub struct Run<'a> {
pub allowance: Option<&'a mut Allowance>,
pub per_call: Budget,
pub admission: Admission,
pub observer: Option<&'a mut dyn Observer>,
pub hooks: Option<&'a dyn Hooks>,
pub assumptions: Option<&'a Assumptions>,
pub deadline: Option<Deadline<'a>>,
}
setters!(Run<'a> {
with_allowance: allowance ? &'a mut Allowance,
with_per_call: per_call: Budget,
with_admission: admission: Admission,
with_observer: observer ? &'a mut dyn Observer,
with_hooks: hooks ? &'a dyn Hooks,
with_assumptions: assumptions ? &'a Assumptions,
with_deadline: deadline ? Deadline<'a>,
});
impl fmt::Debug for Run<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Run")
.field("per_call", &self.per_call)
.field("admission", &self.admission)
.finish_non_exhaustive()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum End {
Completed,
BudgetTerminated(Exhausted),
Declined(Decline),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Decline {
AdmissionCap(Cap),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct RootOutcome {
pub expr: Expr,
pub changed: bool,
pub end: End,
pub relies_on: Reliance,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Outcome {
pub roots: Vec<RootOutcome>,
pub stats: Stats,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Memo {
epoch: u128,
assumptions: Option<Assumptions>,
phases: Vec<PhaseMemo>,
}
#[derive(Clone, Debug, Default)]
struct PhaseMemo {
result: NodeTable,
rel: IdMap<u32, Reliance>,
}
impl PhaseMemo {
fn get(&self, n: u32) -> Option<(u32, Reliance)> {
let r = self.result.get(n)?;
Some((r, self.rel.get(&n).copied().unwrap_or(Reliance::NONE)))
}
fn contains(&self, n: u32) -> bool {
self.result.get(n).is_some()
}
fn insert(&mut self, n: u32, r: u32, rel: Reliance) {
self.result.insert(n, r);
if rel.is_none() {
self.rel.remove(&n);
} else {
self.rel.insert(n, rel);
}
}
}
#[derive(Clone, Default)]
struct NodeTable {
pages: Vec<Option<Box<[u32; PAGE]>>>,
}
const PAGE: usize = 512;
impl NodeTable {
fn get(&self, n: u32) -> Option<u32> {
let n = n as usize;
let page = self.pages.get(n / PAGE)?.as_ref()?;
page[n % PAGE].checked_sub(1)
}
fn insert(&mut self, n: u32, v: u32) {
let n = n as usize;
if n / PAGE >= self.pages.len() {
self.pages.resize_with(n / PAGE + 1, || None);
}
let page = self.pages[n / PAGE].get_or_insert_with(|| Box::new([0; PAGE]));
page[n % PAGE] = v + 1;
}
}
impl fmt::Debug for NodeTable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let pages = self.pages.iter().filter(|p| p.is_some()).count();
write!(f, "NodeTable({pages} pages)")
}
}
impl Memo {
pub(crate) fn clear(&mut self) {
*self = Memo::default();
}
fn prepare(&mut self, epoch: u128, assumptions: Option<&Assumptions>, phases: usize) {
let same = self.epoch == epoch
&& self.assumptions.as_ref() == assumptions
&& self.phases.len() == phases;
if !same {
self.epoch = epoch;
self.assumptions = assumptions.cloned();
self.phases = (0..phases).map(|_| PhaseMemo::default()).collect();
}
}
}
enum Stop {
Exhausted(Exhausted),
Error(Error),
}
impl From<Exhausted> for Stop {
fn from(e: Exhausted) -> Self {
Stop::Exhausted(e)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
struct Fin {
done: bool,
why: Option<Exhausted>,
rel: Reliance,
}
impl Fin {
const FINAL: Fin = Fin {
done: true,
why: None,
rel: Reliance::NONE,
};
const PROVISIONAL: Fin = Fin {
done: false,
why: None,
rel: Reliance::NONE,
};
fn capped(why: Exhausted) -> Fin {
Fin {
done: false,
why: Some(why),
rel: Reliance::NONE,
}
}
fn relying(rel: Reliance) -> Fin {
Fin { rel, ..Fin::FINAL }
}
fn unchanged(self) -> Fin {
Fin {
rel: Reliance::NONE,
..self
}
}
fn and(self, o: Fin) -> Fin {
Fin {
done: self.done && o.done,
why: self.why.or(o.why),
rel: self.rel | o.rel,
}
}
}
enum Frame {
Visit(u32),
Finish(u32, u32, Fin),
}
enum Accept {
Yes,
Vetoed,
Rejected(Reject),
}
enum Step {
To(u32, Fin),
Normal(Fin),
}
struct Runner<'r, 'a> {
inner: &'r Inner,
meter: Meter<'a>,
stats: Stats,
observer: Option<&'a mut dyn Observer>,
hooks: Option<&'a dyn Hooks>,
assumptions: Option<&'a Assumptions>,
quarantined: Vec<bool>,
partial: Vec<IdMap<u32, (u32, Fin)>>,
cands: Vec<u32>,
samples: IdMap<u32, Box<[BitVec]>>,
linear: pass::forms::FormCache<pass::linear::Form>,
bitwise: IdMap<u32, pass::bitwise::Info>,
xor: pass::forms::FormCache<pass::xor::Form>,
compares: IdMap<u32, pass::compares::Info>,
shuffle: IdMap<u32, pass::shuffle::Bits>,
quarantined_passes: Vec<pass::PassKind>,
uses: Option<IdMap<u32, u32>>,
uses_upto: u32,
phase_start: u32,
dead: IdMap<u32, ()>,
live_roots: Vec<u32>,
active: usize,
counted: IdMap<u32, ()>,
}
impl Runner<'_, '_> {
fn emit(&mut self, e: Event<'_>) {
if let Some(o) = self.observer.as_deref_mut() {
o.event(e);
}
}
fn lookup(&self, cx: &Context, phase: usize, n: u32) -> Option<(u32, Fin)> {
if let Some((r, rel)) = cx.memo.phases[phase].get(n) {
return Some((r, Fin::relying(rel)));
}
self.partial[phase].get(&n).copied()
}
fn set(&mut self, cx: &mut Context, phase: usize, n: u32, r: u32, fin: Fin) {
if fin.done {
cx.memo.phases[phase].insert(n, r, fin.rel);
} else {
self.partial[phase].insert(n, (r, fin));
}
}
fn retire(&mut self, cx: &Context, n: u32) {
if self.dead.insert(n, ()).is_some() {
return;
}
if self.counted.remove(&n).is_some()
&& let Some(uses) = self.uses.as_mut()
{
for c in cx.node(n).children() {
if let Some(u) = uses.get_mut(&c) {
*u = u.saturating_sub(1);
}
}
}
}
fn build<T>(
&mut self,
cx: &mut Context,
f: impl FnOnce(&mut Context) -> Result<T, Error>,
) -> Result<T, Stop> {
let left = self.meter.left().new_nodes;
let before = cx.counters().engine_nodes;
let (r, refused) = cx.as_engine_limited(left, f);
let made = cx.counters().engine_nodes - before;
self.meter.spent.new_nodes += made;
if refused {
return Err(Stop::Exhausted(Exhausted::NewNodes));
}
match r {
Ok(v) => {
self.meter.check()?;
Ok(v)
}
Err(Error::ArenaFull { .. }) => Err(Stop::Exhausted(Exhausted::ArenaCapacity)),
Err(e) => Err(Stop::Error(e)),
}
}
fn local(&mut self, cx: &mut Context, phase: usize, root: u32) -> Result<(u32, Fin), Stop> {
if !matches!(self.inner.phases[phase], PhaseImpl::Local(_)) {
self.uses = None;
self.counted.clear();
self.dead.clear();
self.partial[phase].clear();
self.phase_start = cx.len() as u32;
if let Some(slot) = self.live_roots.get_mut(self.active) {
*slot = root;
}
}
let mut stack = vec![Frame::Visit(root)];
while let Some(top) = stack.last() {
match *top {
Frame::Finish(n, t, own) => {
stack.pop();
let (r, fin) = self.lookup(cx, phase, t).unwrap_or((t, Fin::PROVISIONAL));
if r != n {
self.retire(cx, n);
}
self.set(cx, phase, n, r, own.and(fin));
}
Frame::Visit(n) => {
if self.lookup(cx, phase, n).is_some() {
if cx.memo.phases[phase].contains(n) {
self.stats.memo_hits += 1;
}
stack.pop();
continue;
}
self.meter.visit()?;
let node = cx.node(n);
if node.op.as_ext().is_some()
&& cx
.registry
.as_deref()
.and_then(|r| r.op_at(node.aux))
.is_some_and(|o| o.traits().opaque)
{
stack.pop();
self.set(cx, phase, n, n, Fin::FINAL);
continue;
}
let mut waiting = false;
for c in node.children() {
if self.lookup(cx, phase, c).is_none() {
stack.push(Frame::Visit(c));
waiting = true;
}
}
if waiting {
continue;
}
stack.pop();
let mut kids = [0u32; 3];
let mut own = Fin::FINAL;
let mut changed = false;
for (k, c) in node.children().enumerate() {
let (r, fin) = self.lookup(cx, phase, c).unwrap_or((c, Fin::PROVISIONAL));
kids[k] = r;
own = own.and(fin);
changed |= r != c;
}
if changed {
let n1 = self.build(cx, |cx| cx.rebuild(n, kids))?;
if n1 != n {
stack.push(Frame::Finish(n, n1, own));
stack.push(Frame::Visit(n1));
continue;
}
}
let step = match &self.inner.phases[phase] {
PhaseImpl::Local(_) => self.rewrite(cx, phase, n)?,
PhaseImpl::Pass(k) => pass::step(self, cx, *k, n)?,
#[cfg(feature = "mba")]
PhaseImpl::Mba(cfg) => pass::mba_step(self, cx, cfg, n)?,
};
match step {
Step::To(r, fin) => {
stack.push(Frame::Finish(n, r, own.and(fin)));
stack.push(Frame::Visit(r));
}
Step::Normal(fin) => self.set(cx, phase, n, n, own.and(fin.unchanged())),
}
}
}
}
Ok(self
.lookup(cx, phase, root)
.unwrap_or((root, Fin::PROVISIONAL)))
}
fn rewrite(&mut self, cx: &mut Context, phase: usize, n: u32) -> Result<Step, Stop> {
let inner = self.inner;
let PhaseImpl::Local(net) = &inner.phases[phase] else {
return Ok(Step::Normal(Fin::FINAL));
};
let mut cands = core::mem::take(&mut self.cands);
cands.clear();
cands.extend(net.candidates(cx, n));
let r = self.try_candidates(cx, n, &cands);
self.cands = cands;
r
}
fn try_candidates(&mut self, cx: &mut Context, n: u32, cands: &[u32]) -> Result<Step, Stop> {
let inner = self.inner;
let mut fin = Fin::FINAL;
for &ri in cands {
let linked = &inner.rules[ri as usize];
let rule = &linked.rule;
if self.quarantined[ri as usize] {
fin = fin.and(Fin::PROVISIONAL);
continue;
}
self.meter.charge(Counter::Candidates, 1)?;
self.emit(Event::Candidate {
rule,
node: cx.handle(n),
});
let left = self.meter.left();
let steps = u32::try_from(left.match_steps)
.unwrap_or(u32::MAX)
.min(MATCH_STEPS);
let fact_cap = u32::try_from(left.fact_work).unwrap_or(u32::MAX);
let mut env = ApplyEnv {
assumptions: self.assumptions,
fact_cap: Some(fact_cap),
steps,
..ApplyEnv::default()
};
let (work0, capped0) = (cx.facts.work, cx.facts.capped);
let r = self.build(cx, |cx| Ok(try_apply_with(&mut env, cx, rule, n)))?;
self.meter.spent.match_steps += u64::from(steps - env.steps);
self.meter.spent.fact_work += cx.facts.work - work0;
let fact_capped = cx.facts.capped != capped0 || env.out_of_facts;
if fact_capped && fact_cap < cx.config().fact_work {
return Err(Stop::Exhausted(Exhausted::FactWork));
}
let out_of_steps = env.degraded && env.steps == 0 && !fact_capped;
if out_of_steps && steps < MATCH_STEPS {
return Err(Stop::Exhausted(Exhausted::MatchSteps));
}
self.meter.check()?;
let Some(r) = r else {
if env.instantiating && cx.len() as u64 >= u64::from(cx.config().max_nodes) {
return Err(Stop::Exhausted(Exhausted::ArenaCapacity));
}
if env.degraded {
let why = if fact_capped {
Exhausted::FactWork
} else {
Exhausted::MatchSteps
};
fin = fin.and(Fin::capped(why));
self.stats.degraded += 1;
self.emit(Event::Degraded { rule });
} else if env.matched {
self.stats.guard_false += 1;
self.emit(Event::GuardFalse { rule });
} else {
self.stats.no_match += 1;
self.emit(Event::NoMatch { rule });
}
continue;
};
if r == n {
self.stats.no_change += 1;
self.emit(Event::NoChange { rule });
continue;
}
match self.accept(cx, By::Rule(rule), linked.proven, n, r, env.rel)? {
Accept::Yes => return Ok(Step::To(r, fin.and(Fin::relying(env.rel)))),
Accept::Vetoed => continue,
Accept::Rejected(reason) => {
if matches!(reason, Reject::Width | Reject::Verify | Reject::Tripwire) {
self.quarantined[ri as usize] = true;
self.stats.quarantined += 1;
}
continue;
}
}
}
Ok(Step::Normal(fin))
}
fn accept(
&mut self,
cx: &mut Context,
by: By<'_>,
proven: bool,
n: u32,
r: u32,
rel: Reliance,
) -> Result<Accept, Stop> {
if let Some(reason) = self.postcondition(cx, by, proven, n, r, rel)? {
self.stats.rejected += 1;
self.emit(Event::Rejected { by, reason });
return Ok(Accept::Rejected(reason));
}
let (before, after) = (cx.handle(n), cx.handle(r));
if let Some(h) = self.hooks
&& !h.admit(cx, before, after, by)
{
self.stats.hook_vetoes += 1;
self.emit(Event::Vetoed { by });
return Ok(Accept::Vetoed);
}
self.meter.charge(Counter::Rewrites, 1)?;
self.emit(Event::Applied { by, before, after });
Ok(Accept::Yes)
}
fn postcondition(
&mut self,
cx: &mut Context,
by: By<'_>,
proven: bool,
n: u32,
r: u32,
rel: Reliance,
) -> Result<Option<Reject>, Stop> {
let v = self.inner.verify;
if cx.wid(r) != cx.wid(n) {
return Ok(Some(Reject::Width));
}
if v.termination
&& matches!(by, By::Rule(_))
&& crate::rules::order::ground_greater(cx, n, r) == Some(false)
{
return Ok(Some(Reject::Termination));
}
let points = if proven {
v.sampled_points
} else {
v.sampled_points.max(v.unproven_points)
};
if points > 0 {
self.sample(cx, n)?;
self.sample(cx, r)?;
let k = points as usize;
let held = self.constraints_hold(cx, rel, k)?;
let (a, b) = (&self.samples[&n], &self.samples[&r]);
if (0..k).any(|j| held[j] && a[j] != b[j]) {
return Ok(Some(Reject::Verify));
}
}
if v.tripwire {
let cap = u32::try_from(self.meter.left().fact_work).unwrap_or(u32::MAX);
let work0 = cx.facts.work;
let (a, b) = (cx.handle(n), cx.handle(r));
let fa = cx.try_facts_cap(a, cap).map_err(Stop::Error)?;
let used = u32::try_from(cx.facts.work - work0).unwrap_or(u32::MAX);
let fb = cx
.try_facts_cap(b, cap.saturating_sub(used))
.map_err(Stop::Error)?;
self.meter.spent.fact_work += cx.facts.work - work0;
match (fa, fb) {
(Some(fa), Some(fb)) => {
if fa.meet(&fb).is_none() {
return Ok(Some(Reject::Tripwire));
}
}
_ if cap < cx.config().fact_work => {
return Err(Stop::Exhausted(Exhausted::FactWork));
}
_ => {}
}
self.meter.check()?;
}
Ok(None)
}
fn constraints_hold(
&mut self,
cx: &Context,
rel: Reliance,
k: usize,
) -> Result<Vec<bool>, Stop> {
let mut held = vec![true; k];
let Some(a) = self.assumptions else {
return Ok(held);
};
if rel.is_none() {
return Ok(held);
}
for (id, e, f) in a.constraints() {
if !rel.may_use(id) {
continue;
}
let i = cx.id(e).map_err(Stop::Error)?;
self.sample(cx, i)?;
let vals = &self.samples[&i];
for (j, h) in held.iter_mut().enumerate() {
*h &= f.contains(&vals[j]);
}
}
Ok(held)
}
fn sample(&mut self, cx: &Context, root: u32) -> Result<(), Stop> {
let v = self.inner.verify;
let points = v.sampled_points.max(v.unproven_points).max(16) as usize;
if self.samples.contains_key(&root) {
return Ok(());
}
let mut samples = core::mem::take(&mut self.samples);
let mut stack: Vec<(u32, bool)> = vec![(root, false)];
let mut out = Ok(());
while let Some((i, expanded)) = stack.pop() {
if samples.contains_key(&i) {
continue;
}
if !expanded {
stack.push((i, true));
for c in cx.node(i).children() {
if !samples.contains_key(&c) {
stack.push((c, false));
}
}
continue;
}
if let Err(e) = self.meter.visit() {
out = Err(Stop::Exhausted(e));
break;
}
let seed = cx.meta[i as usize].shash;
let vals: Result<Vec<BitVec>, Error> = (0..points)
.map(|k| cx.eval_node(i, |j| samples[&j][k], |_, w| Some(point_value(seed, w, k))))
.collect();
match vals {
Ok(v) => {
samples.insert(i, v.into_boxed_slice());
}
Err(e) => {
out = Err(Stop::Error(e));
break;
}
}
}
self.samples = samples;
out
}
}
fn point_value(seed: u64, w: Width, k: usize) -> BitVec {
match k {
0 => BitVec::zero(w),
1 => BitVec::ones(w),
_ => {
let mut x = seed ^ (k as u64).wrapping_mul(0x9e37_79b9_7f4a_7c15);
let limbs: Vec<u64> = (0..8)
.map(|_| {
x = x.wrapping_add(0x9e37_79b9_7f4a_7c15);
let mut z = x;
z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
z ^ (z >> 31)
})
.collect();
BitVec::wrapping_from_limbs(w, &limbs)
}
}
}
impl Engine {
pub fn standard() -> Engine {
static STANDARD: OnceLock<Engine> = OnceLock::new();
STANDARD
.get_or_init(|| {
Engine::builder()
.builtin()
.strategy(Strategy::standard())
.build()
.unwrap_or_else(|e| panic!("the built-in engine does not link: {e}"))
})
.clone()
}
pub fn builder() -> EngineBuilder {
EngineBuilder::default()
}
pub fn strategy(&self) -> &Strategy {
&self.inner.strategy
}
pub fn simplify(&self, cx: &mut Context, e: Expr) -> Result<RootOutcome, Error> {
Ok(self.run(cx, &[e], Run::default())?.roots[0])
}
pub fn run(&self, cx: &mut Context, roots: &[Expr], run: Run<'_>) -> Result<Outcome, Error> {
let ids = cx.ids(roots)?;
let inner = &*self.inner;
let Run {
allowance,
per_call,
admission,
observer,
hooks,
assumptions,
deadline,
} = run;
if let Some(a) = assumptions {
a.check_context(cx)?;
}
let assumptions = assumptions.filter(|a| !a.is_infeasible());
let epoch =
inner.id ^ u128::from(hooks.map_or(0, |h| combine(0x686f_6f6b_7321, h.revision())));
cx.memo.prepare(epoch, assumptions, inner.phases.len());
let limit = match &allowance {
Some(a) => per_call.min(&a.remaining()),
None => per_call,
};
let mut runner = Runner {
inner,
meter: Meter::new(limit, deadline),
stats: Stats::default(),
observer,
hooks,
assumptions,
quarantined: vec![false; inner.rules.len()],
partial: (0..inner.phases.len()).map(|_| IdMap::default()).collect(),
cands: Vec::new(),
samples: IdMap::default(),
linear: Default::default(),
bitwise: IdMap::default(),
xor: Default::default(),
compares: IdMap::default(),
shuffle: IdMap::default(),
quarantined_passes: Vec::new(),
uses: None,
uses_upto: 0,
phase_start: 0,
dead: IdMap::default(),
live_roots: {
let mut seen: IdMap<u32, ()> = IdMap::default();
ids.iter()
.copied()
.filter(|&i| seen.insert(i, ()).is_none())
.collect()
},
active: 0,
counted: IdMap::default(),
};
let mut done: IdMap<u32, (u32, End, Reliance)> = IdMap::default();
let mut failure: Option<Error> = None;
for &root in &ids {
if done.contains_key(&root) {
continue;
}
let out = match admit(cx, root, &admission) {
Some(cap) => {
runner.stats.declined += 1;
(
root,
End::Declined(Decline::AdmissionCap(cap)),
Reliance::NONE,
)
}
None => {
let (r, end, rel) = match runner.strategy(cx, root) {
Ok((r, fin)) => match fin.why {
Some(why) if !fin.done => (r, End::BudgetTerminated(why), fin.rel),
_ => (r, End::Completed, fin.rel),
},
Err((r, fin, Stop::Exhausted(e))) => (r, End::BudgetTerminated(e), fin.rel),
Err((_, _, Stop::Error(e))) => {
failure = Some(e);
break;
}
};
let slot = match end {
End::Completed => &mut runner.stats.completed,
_ => &mut runner.stats.budget_terminated,
};
slot[usize::from(r != root)] += 1;
(r, end, rel)
}
};
if let Some(slot) = runner.live_roots.get_mut(runner.active) {
*slot = out.0;
}
runner.active += 1;
done.insert(root, out);
}
let spent = runner.meter.spent;
if let Some(a) = allowance {
a.spend(&spent);
}
if let Some(e) = failure {
return Err(e);
}
let mut stats = runner.stats;
stats.node_visits = spent.node_visits;
stats.candidates = spent.candidates;
stats.match_steps = spent.match_steps;
stats.rewrites = spent.rewrites;
stats.new_nodes = spent.new_nodes;
stats.fact_work = spent.fact_work;
stats.pass_work = spent.pass_work;
stats.mba.calls = spent.mba_calls;
Ok(Outcome {
roots: ids
.iter()
.map(|&i| {
let (r, end, rel) = done[&i];
RootOutcome {
expr: cx.handle(r),
changed: r != i,
end,
relies_on: if r == i { Reliance::NONE } else { rel },
}
})
.collect(),
stats,
})
}
}
fn admit(cx: &mut Context, root: u32, a: &Admission) -> Option<Cap> {
let meta = cx.meta[root as usize];
if meta.height > a.max_root_height {
return Some(Cap::Height);
}
if meta.tree > a.max_root_tree_size {
return Some(Cap::TreeSize);
}
if let Some(cap) = a.max_root_dag_size {
let e = cx.handle(root);
match cx.dag_size(&[e], cap) {
Ok(crate::Bounded::Exact(_)) => {}
_ => return Some(Cap::DagSize),
}
}
None
}
impl Runner<'_, '_> {
fn strategy(&mut self, cx: &mut Context, root: u32) -> Result<(u32, Fin), (u32, Fin, Stop)> {
let inner = self.inner;
let mut cur = root;
let mut fin = Fin::FINAL;
for round in 0..inner.strategy.max_rounds.max(1) {
self.stats.rounds = self.stats.rounds.max(u32::from(round) + 1);
let start = cur;
for (phase, p) in inner.strategy.phases.iter().enumerate() {
let _ = p;
match self.local(cx, phase, cur) {
Ok((r, f)) => {
cur = r;
fin = fin.and(f);
}
Err(stop) => return Err((cur, fin, stop)),
}
}
if cur == start {
break;
}
}
Ok((cur, fin))
}
}