pub(crate) mod bitwise;
pub(super) mod casts;
pub(super) mod compares;
pub(super) mod demanded;
pub(super) mod forms;
pub(super) mod invert;
pub(super) mod linear;
pub(super) mod linear_mba;
#[cfg(feature = "mba")]
pub(super) mod mba;
pub(super) mod shuffle;
#[cfg(test)]
mod tests;
pub(super) mod xor;
use super::{Accept, By, Fin, Reject, Runner, Step, Stop};
use crate::engine::Exhausted;
use crate::engine::budget::Counter;
use crate::expr::{Context, OpCode};
use crate::facts::{Facts, Reliance};
pub(crate) struct Marks {
stamp: Vec<u32>,
epoch: u32,
}
impl Default for Marks {
fn default() -> Self {
Marks {
stamp: Vec::new(),
epoch: 1,
}
}
}
impl Marks {
pub(crate) fn begin(&mut self, len: usize) {
if self.stamp.len() < len {
self.stamp.resize(len, 0);
}
self.clear();
}
pub(crate) fn clear(&mut self) {
self.epoch = self.epoch.wrapping_add(1);
if self.epoch == 0 {
self.stamp.fill(0);
self.epoch = 1;
}
}
#[inline]
pub(crate) fn insert(&mut self, i: u32) -> bool {
let i = i as usize;
if i >= self.stamp.len() {
self.stamp.resize(i + 1, 0);
}
let fresh = self.stamp[i] != self.epoch;
self.stamp[i] = self.epoch;
fresh
}
#[inline]
pub(crate) fn remove(&mut self, i: u32) -> bool {
match self.stamp.get_mut(i as usize) {
Some(s) if *s == self.epoch => {
*s = 0;
true
}
_ => false,
}
}
#[inline]
pub(crate) fn contains(&self, i: u32) -> bool {
self.stamp.get(i as usize) == Some(&self.epoch)
}
}
pub(crate) struct Counts {
stamp: Vec<u32>,
val: Vec<u32>,
epoch: u32,
}
impl Default for Counts {
fn default() -> Self {
Counts {
stamp: Vec::new(),
val: Vec::new(),
epoch: 1,
}
}
}
impl Counts {
pub(crate) fn begin(&mut self, len: usize) {
if self.stamp.len() < len {
self.stamp.resize(len, 0);
self.val.resize(len, 0);
}
self.epoch = self.epoch.wrapping_add(1);
if self.epoch == 0 {
self.stamp.fill(0);
self.epoch = 1;
}
}
#[inline]
pub(crate) fn get(&self, i: u32) -> Option<u32> {
let i = i as usize;
(self.stamp.get(i) == Some(&self.epoch)).then(|| self.val[i])
}
#[inline]
pub(crate) fn add(&mut self, i: u32) {
let i = i as usize;
if i >= self.stamp.len() {
self.stamp.resize(i + 1, 0);
self.val.resize(i + 1, 0);
}
if self.stamp[i] != self.epoch {
self.stamp[i] = self.epoch;
self.val[i] = 0;
}
self.val[i] += 1;
}
#[inline]
pub(crate) fn dec(&mut self, i: u32) -> Option<u32> {
let i = i as usize;
if self.stamp.get(i) != Some(&self.epoch) {
return None;
}
self.val[i] = self.val[i].saturating_sub(1);
Some(self.val[i])
}
}
#[derive(Default)]
pub(crate) struct Scratch {
region: Marks,
seen: Marks,
kept: Marks,
dying: Counts,
local: Counts,
queue: std::collections::VecDeque<u32>,
stack: Vec<u32>,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) enum PassKind {
FactFold,
Linear,
Bitwise,
Xor,
Compares,
Casts,
Demanded,
LinearMba,
Shuffle,
Invert,
#[cfg_attr(not(feature = "mba"), allow(dead_code))]
Mba,
}
impl PassKind {
pub(crate) fn name(self) -> &'static str {
match self {
PassKind::FactFold => "fact_fold",
PassKind::Linear => "linear",
PassKind::Bitwise => "bitwise",
PassKind::Xor => "xor",
PassKind::Compares => "compares",
PassKind::Casts => "casts",
PassKind::Demanded => "demanded",
PassKind::LinearMba => "linear_mba",
PassKind::Shuffle => "shuffle",
PassKind::Invert => "invert",
PassKind::Mba => "mba",
}
}
}
pub(super) fn step(
r: &mut Runner<'_, '_>,
cx: &mut Context,
kind: PassKind,
n: u32,
) -> Result<Step, Stop> {
r.stats.passes.entry(kind.name()).or_default().calls += 1;
r.meter.charge(Counter::PassWork, 1)?;
match kind {
PassKind::FactFold => fold(r, cx, n),
PassKind::Linear => linear::step(r, cx, n),
PassKind::Bitwise => bitwise::step(r, cx, n),
PassKind::Xor => xor::step(r, cx, n),
PassKind::Compares => compares::step(r, cx, n),
PassKind::Casts => casts::step(r, cx, n),
PassKind::Demanded => demanded::step(r, cx, n),
PassKind::LinearMba => linear_mba::step(r, cx, n),
PassKind::Shuffle => shuffle::step(r, cx, n),
PassKind::Invert => invert::step(r, cx, n),
PassKind::Mba => Ok(Step::Normal(Fin::FINAL)),
}
}
#[cfg(feature = "mba")]
pub(super) fn mba_step(
r: &mut Runner<'_, '_>,
cx: &mut Context,
cfg: &crate::mba::MbaConfig,
n: u32,
) -> Result<Step, Stop> {
if r.quarantined_passes.contains(&PassKind::Mba) {
return Ok(Step::Normal(Fin::PROVISIONAL));
}
if let Some(Some(fin)) = r.first.get(&n) {
return Ok(Step::Normal(*fin));
}
let additive = |op: OpCode| matches!(op, OpCode::Add | OpCode::Sub | OpCode::Neg | OpCode::Shl);
if let Some(&p) = r.chain_parent.get(&n) {
let (op, up) = (cx.node(n).op, cx.node(p).op);
if ((matches!(op, OpCode::Add | OpCode::Sub) && additive(up))
|| (op == OpCode::Mul && up == OpCode::Mul))
&& mba::asks(r, cx, cfg, p)?
{
return Ok(Step::Normal(Fin::PROVISIONAL));
}
}
r.stats.passes.entry("mba").or_default().calls += 1;
r.meter.charge(Counter::PassWork, 1)?;
mba::step(r, cx, cfg, n)
}
#[cfg(feature = "mba")]
pub(super) fn mba_first(
r: &mut Runner<'_, '_>,
cx: &mut Context,
cfg: &crate::mba::MbaConfig,
n: u32,
) -> Result<Option<(u32, Fin)>, Stop> {
let top = match r.chain_parent.get(&n) {
None => true,
Some(&p) => !mba::asks(r, cx, cfg, p)?,
};
if !top || !mba::asks(r, cx, cfg, n)? {
r.first.insert(n, None);
return Ok(None);
}
match mba_step(r, cx, cfg, n)? {
Step::To(x, fin) => {
r.first.insert(n, None);
Ok(Some((x, fin)))
}
Step::Normal(fin) => {
r.first.insert(n, Some(fin));
Ok(None)
}
}
}
pub(super) fn facts(
r: &mut Runner<'_, '_>,
cx: &mut Context,
n: u32,
) -> Result<(Option<Facts>, Fin), Stop> {
if let Some(v) = cx.const_val(n) {
return Ok((Some(Facts::constant(&v)), Fin::FINAL));
}
let left = r.meter.left().fact_work;
let cap = u32::try_from(left).unwrap_or(u32::MAX);
if cap == 0 {
return Err(Stop::Exhausted(Exhausted::FactWork));
}
let (work0, capped0) = (cx.facts.work, cx.facts.capped);
let e = cx.handle(n);
let (f, rel) = match r.assumptions {
Some(a) => match cx.facts_under_cap(e, a, cap).map_err(Stop::Error)? {
Ok((f, rel)) => (Some(f), rel),
Err(_) => (None, Reliance::NONE),
},
None => (
cx.try_facts_cap(e, cap).map_err(Stop::Error)?,
Reliance::NONE,
),
};
r.meter.spent.fact_work += cx.facts.work - work0;
if cx.facts.capped != capped0 {
if cap < cx.config().fact_work {
return Err(Stop::Exhausted(Exhausted::FactWork));
}
r.stats.degraded += 1;
return Ok((None, Fin::capped(Exhausted::FactWork)));
}
r.meter.check()?;
Ok((f, Fin::relying(rel)))
}
fn fold(r: &mut Runner<'_, '_>, cx: &mut Context, n: u32) -> Result<Step, Stop> {
if cx.const_val(n).is_some() {
return Ok(Step::Normal(Fin::FINAL));
}
let (f, fin) = facts(r, cx, n)?;
let Some(v) = f.and_then(|f| f.as_constant()) else {
r.stats.passes.entry("fact_fold").or_default().noop += 1;
return Ok(Step::Normal(fin));
};
if let Some(h) = r.hooks
&& !h.fold_known(cx, cx.handle(n))
{
r.stats.hook_vetoes += 1;
return Ok(Step::Normal(fin));
}
let before = cx.len() as u32;
let c = r.build(cx, |cx| cx.mk_const(&v))?;
finish(r, cx, PassKind::FactFold, n, c, before, &[], fin)
}
fn refresh_uses(r: &mut Runner<'_, '_>, cx: &Context) -> Result<(), Stop> {
if !r.uses_on {
r.uses.begin(cx.len());
r.scratch.seen.begin(cx.len());
let mut stack: Vec<u32> = r.live_roots.clone();
while let Some(i) = stack.pop() {
if !r.scratch.seen.insert(i) || r.dead.contains(i) {
continue;
}
if let Err(e) = r.meter.charge(Counter::PassWork, 1) {
r.counted.clear();
return Err(e.into());
}
for c in cx.node(i).children() {
r.uses.add(c);
stack.push(c);
}
r.counted.insert(i);
}
r.uses_on = true;
r.uses_upto = r.phase_start;
}
let len = cx.len() as u32;
let from = r.uses_upto.min(len);
r.meter.charge(Counter::PassWork, u64::from(len - from))?;
let mut stack: Vec<u32> = (from..len)
.rev()
.filter(|&i| !r.dead.contains(i) && !r.counted.contains(i))
.collect();
while let Some(i) = stack.pop() {
if !r.counted.insert(i) {
continue;
}
r.dead.remove(i);
r.meter.charge(Counter::PassWork, 1)?;
for c in cx.node(i).children() {
r.uses.add(c);
if !r.counted.contains(c) {
stack.push(c);
}
}
}
r.uses_upto = len;
Ok(())
}
fn region(r: &mut Runner<'_, '_>, cx: &Context, n: u32, atoms: &[u32]) -> Result<Vec<u32>, Stop> {
let sc = &mut r.scratch;
sc.region.begin(cx.len());
sc.queue.clear();
sc.queue.push_back(n);
let mut out: Vec<u32> = Vec::new();
while let Some(i) = sc.queue.pop_front() {
if !sc.region.insert(i) {
continue;
}
r.meter.charge(Counter::PassWork, 1)?;
out.push(i);
if out.len() >= REGION_CAP as usize {
break;
}
for c in cx.node(i).children() {
if atoms.binary_search(&c).is_err() {
sc.queue.push_back(c);
}
}
}
out.sort_unstable_by(|a, b| b.cmp(a));
Ok(out)
}
fn dying_in(
r: &mut Runner<'_, '_>,
cx: &Context,
n: u32,
order: &[u32],
kept: bool,
limit: u32,
shared: bool,
) -> Result<u32, Stop> {
if shared {
refresh_uses(r, cx)?;
}
let sc = &mut r.scratch;
if !shared {
sc.local.begin(cx.len());
for &i in order {
for c in cx.node(i).children() {
if sc.region.contains(c) {
sc.local.add(c);
}
}
}
}
let uses = |sc: &Scratch, i: u32| {
if shared {
r.uses.get(i)
} else {
sc.local.get(i)
}
};
sc.dying.begin(cx.len());
let mut count = 0u32;
for &i in order {
if kept && sc.kept.contains(i) {
continue;
}
let dies = i == n || sc.dying.get(i).unwrap_or(0) >= uses(sc, i).unwrap_or(u32::MAX);
if !dies {
continue;
}
count += 1;
if count >= limit {
return Ok(count);
}
for c in cx.node(i).children() {
if sc.region.contains(c) {
sc.dying.add(c);
}
}
}
Ok(count)
}
fn needed(
r: &mut Runner<'_, '_>,
cx: &Context,
e: u32,
atoms: &[u32],
) -> Result<Option<u32>, Stop> {
let sc = &mut r.scratch;
sc.seen.begin(cx.len());
sc.kept.begin(cx.len());
sc.stack.clear();
sc.stack.push(e);
let mut new = 0u32;
while let Some(i) = sc.stack.pop() {
if !sc.seen.insert(i) || atoms.binary_search(&i).is_ok() {
continue;
}
r.meter.charge(Counter::PassWork, 1)?;
if sc.region.contains(i) {
sc.kept.insert(i);
continue;
}
new += 1;
if new > REGION_CAP {
r.meter.check()?;
return Ok(None);
}
sc.stack.extend(cx.node(i).children());
}
r.meter.check()?;
Ok(Some(new))
}
pub(super) const REGION_CAP: u32 = 1024;
pub(super) fn worth_building(
r: &mut Runner<'_, '_>,
cx: &Context,
n: u32,
atoms: &[u32],
estimate: u32,
) -> Result<Option<Fin>, Stop> {
let mut sorted = atoms.to_vec();
sorted.sort_unstable();
sorted.dedup();
let order = region(r, cx, n, &sorted)?;
r.meter.check()?;
if dying_in(r, cx, n, &order, false, estimate.saturating_add(1), true)? > estimate {
return Ok(None);
}
let alone = dying_in(r, cx, n, &order, false, estimate.saturating_add(1), false)?;
Ok(Some(if alone > estimate {
Fin::PROVISIONAL
} else {
Fin::FINAL
}))
}
pub(super) fn shrinks(
r: &mut Runner<'_, '_>,
cx: &Context,
n: u32,
e: u32,
atoms: &[u32],
) -> Result<Result<(), Fin>, Stop> {
if cx.node(e).op == OpCode::Const && cx.node(n).op != OpCode::Const {
return Ok(Ok(()));
}
let mut sorted = atoms.to_vec();
sorted.sort_unstable();
sorted.dedup();
let order = region(r, cx, n, &sorted)?;
r.meter.check()?;
let Some(new) = needed(r, cx, e, &sorted)? else {
return Ok(Err(Fin::FINAL));
};
let freed = dying_in(r, cx, n, &order, true, new.saturating_add(1), true)?;
if new >= freed {
let alone = dying_in(r, cx, n, &order, true, new.saturating_add(1), false)?;
return Ok(Err(if new < alone {
Fin::PROVISIONAL
} else {
Fin::FINAL
}));
}
Ok(Ok(()))
}
#[allow(clippy::too_many_arguments)]
pub(super) fn finish(
r: &mut Runner<'_, '_>,
cx: &mut Context,
kind: PassKind,
n: u32,
e: u32,
before: u32,
atoms: &[u32],
fin: Fin,
) -> Result<Step, Stop> {
let name = kind.name();
if e == n {
r.stats.passes.entry(name).or_default().noop += 1;
return Ok(Step::Normal(fin));
}
if let Err(more) = shrinks(r, cx, n, e, atoms)? {
r.stats.passes.entry(name).or_default().rejected_cost += 1;
discard(r, cx, before);
return Ok(Step::Normal(fin.and(more)));
}
match r.accept(cx, By::Pass(name), true, n, e, fin.rel)? {
Accept::Yes => {
r.stats.passes.entry(name).or_default().changed += 1;
Ok(Step::To(e, fin))
}
Accept::Vetoed => {
r.stats.passes.entry(name).or_default().rejected += 1;
discard(r, cx, before);
Ok(Step::Normal(fin))
}
Accept::Rejected(reason) => {
r.stats.passes.entry(name).or_default().rejected += 1;
discard(r, cx, before);
if matches!(reason, Reject::Width | Reject::Verify | Reject::Tripwire) {
r.quarantined_passes.push(kind);
r.stats.quarantined += 1;
}
Ok(Step::Normal(fin))
}
}
}
pub(super) fn discard(r: &mut Runner<'_, '_>, cx: &Context, before: u32) {
for i in before..cx.len() as u32 {
r.retire(cx, i);
}
}