#[cfg(test)]
mod tests;
use crate::error::Error;
use crate::expr::{Context, Expr, OpCode, count_mod};
use crate::ext::Invertible;
use crate::facts::{Assumptions, Facts, KnownBits, Proof, Reliance, Truth};
use crate::hash::IdMap;
use crate::ops::{BinOp, UnOp};
use crate::{BitVec, Width};
pub(crate) const MAX_REGION: usize = 256;
const MAX_CANDIDATES: usize = 8;
const MAX_MATCH: u32 = 1024;
const MAX_MATCH_DEPTH: u32 = 256;
const MAX_HOLE_BITS: u16 = 128;
pub(crate) const MAX_SPLIT: usize = 16;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub(crate) enum Kind {
Injective,
Bijective,
}
pub(crate) trait Oracle {
type Err;
fn facts(&mut self, cx: &mut Context, i: u32) -> Result<Option<Facts>, Self::Err>;
fn charge(&mut self, units: u64) -> Result<(), Self::Err>;
}
#[derive(Clone, Debug)]
pub(crate) enum Map {
Un(UnOp),
Add(u32),
Sub(u32),
SubFrom(u32),
Xor(u32),
Mul(u32),
RotL(u32),
RotR(u32),
Zext,
Sext,
ConcatHi(u32),
ConcatLo(u32),
Ext {
output: u8,
arg: u8,
},
Region(Box<Region>),
}
#[derive(Clone, Debug)]
pub(crate) struct Layer {
pub(crate) inner: u32,
pub(crate) kind: Kind,
pub(crate) map: Map,
}
type Plan = Vec<Vec<(u8, u16)>>;
#[derive(Clone, Debug)]
pub(crate) struct Region {
nodes: Vec<u32>,
levels: Plan,
}
fn primitive(cx: &Context, n: u32, pos: usize) -> Option<(Map, Kind, Option<u32>)> {
use Kind::{Bijective, Injective};
let node = cx.node(n);
let other = if pos == 0 { node.b } else { node.a };
Some(match (node.op, pos) {
(OpCode::Not, 0) => (Map::Un(UnOp::Not), Bijective, None),
(OpCode::Neg, 0) => (Map::Un(UnOp::Neg), Bijective, None),
(OpCode::Bswap, 0) => (Map::Un(UnOp::Bswap), Bijective, None),
(OpCode::BitRev, 0) => (Map::Un(UnOp::BitRev), Bijective, None),
(OpCode::Add, 0 | 1) => (Map::Add(other), Bijective, None),
(OpCode::Xor, 0 | 1) => (Map::Xor(other), Bijective, None),
(OpCode::Mul, 0 | 1) => (Map::Mul(other), Bijective, Some(other)),
(OpCode::Sub, 0) => (Map::Sub(node.b), Bijective, None),
(OpCode::Sub, 1) => (Map::SubFrom(node.a), Bijective, None),
(OpCode::RotL, 0) => (Map::RotL(node.b), Bijective, None),
(OpCode::RotR, 0) => (Map::RotR(node.b), Bijective, None),
(OpCode::Zext, 0) => (Map::Zext, Injective, None),
(OpCode::Sext, 0) => (Map::Sext, Injective, None),
(OpCode::Concat, 0) => (Map::ConcatHi(node.b), Injective, None),
(OpCode::Concat, 1) => (Map::ConcatLo(node.a), Injective, None),
_ => return None,
})
}
fn odd<O: Oracle>(cx: &mut Context, o: &mut O, k: u32) -> Result<bool, O::Err> {
if let Some(v) = cx.const_val(k) {
return Ok(v.bit(0) == Some(true));
}
Ok(o.facts(cx, k)?.and_then(|f| f.known().bit(0)) == Some(true))
}
fn ext_kind<O: Oracle>(
cx: &mut Context,
o: &mut O,
n: u32,
pos: usize,
) -> Result<Option<Kind>, O::Err> {
let node = cx.node(n);
let Some((_, output)) = node.op.as_ext() else {
return Ok(None);
};
let args: Vec<u32> = node.children().collect();
let mut known = Vec::with_capacity(args.len());
for (k, &c) in args.iter().enumerate() {
let w = cx.width_of(c);
known.push(if k == pos {
KnownBits::unknown(w)
} else if let Some(v) = cx.const_val(c) {
KnownBits::constant(&v)
} else {
o.facts(cx, c)?.map_or(KnownBits::unknown(w), |f| f.known())
});
}
let Some(op) = cx.registry.as_deref().and_then(|r| r.op_at(node.aux)) else {
return Ok(None);
};
Ok(match op.invertible(output as u8, pos as u8, &known) {
Invertible::Bijective if cx.width_of(n) == cx.width_of(args[pos]) => Some(Kind::Bijective),
Invertible::Injective => Some(Kind::Injective),
_ => None,
})
}
pub(crate) fn mul_inverse(k: &BitVec) -> Option<BitVec> {
if k.bit(0) != Some(true) {
return None;
}
let w = k.width();
let one = BitVec::one(w);
let two = BitVec::wrapping_from_u64(w, 2);
let mul = |a: &BitVec, b: &BitVec| BitVec::bin_unchecked(BinOp::Mul, a, b);
let mut i = one;
for _ in 0..10 {
i = mul(&i, &BitVec::bin_unchecked(BinOp::Sub, &two, &mul(k, &i)));
}
(mul(k, &i) == one).then_some(i)
}
#[derive(Clone, Debug)]
struct Bits {
dep: Vec<u128>,
piv: Vec<u128>,
}
impl Bits {
fn opaque(dep: Vec<u128>) -> Bits {
let piv = vec![0; dep.len()];
Bits { dep, piv }
}
fn all(&self) -> u128 {
all(&self.dep)
}
}
fn all(d: &[u128]) -> u128 {
d.iter().fold(0, |a, x| a | x)
}
fn count_span(op: OpCode, f: &Facts, w: usize) -> (u64, u64) {
let (lo, hi) = (f.urange().lo(), f.urange().hi());
let wu = w as u64;
if matches!(op, OpCode::RotL | OpCode::RotR) {
let span = BitVec::bin_unchecked(BinOp::Sub, &hi, &lo).to_u64();
let n = span.map_or(wu, |s| s.saturating_add(1).min(wu));
(count_mod(&lo, w as u16), n)
} else {
let cap = |v: BitVec| v.to_u64().map_or(wu, |v| v.min(wu));
let (first, last) = (cap(lo), cap(hi));
(first, last - first + 1)
}
}
fn shift_deps(op: OpCode, a: &[u128], cf: &Facts, cd: u128) -> Vec<u128> {
let w = a.len();
let wu = w as u64;
let (first, n) = count_span(op, cf, w);
let mut out = vec![cd; w];
match op {
OpCode::RotL | OpCode::RotR => {
if n == wu {
let x = all(a) | cd;
out.iter_mut().for_each(|o| *o = x);
return out;
}
for t in first..first + n {
let t = (t % wu) as usize;
for (i, o) in out.iter_mut().enumerate() {
let j = if op == OpCode::RotL {
(i + w - t) % w
} else {
(i + t) % w
};
*o |= a[j];
}
}
}
_ => {
for t in first..first + n {
let t = t as usize;
for (i, o) in out.iter_mut().enumerate() {
*o |= match op {
OpCode::Shl if t <= i && t < w => a[i - t],
OpCode::LShr if i + t < w => a[i + t],
OpCode::AShr => a[(i + t).min(w - 1)],
_ => 0,
};
}
}
}
}
out
}
fn carry_chain(a: &Bits, c: Option<&Bits>) -> Bits {
let w = a.dep.len();
let mut out = Bits::opaque(vec![0; w]);
let mut low = 0u128;
for k in 0..w {
let (da, pa) = (a.dep[k], a.piv[k]);
let (dc, pc) = c.map_or((0, 0), |c| (c.dep[k], c.piv[k]));
out.dep[k] = low | da | dc;
out.piv[k] = (pa & !(low | dc)) | (pc & !(low | da));
low |= da | dc;
}
out
}
fn node_bits(cx: &Context, i: u32, b: &IdMap<u32, Bits>, f: &IdMap<u32, Facts>) -> Bits {
let n = cx.node(i);
let w = usize::from(n.width);
let pick = |a: u32, src: &dyn Fn(usize) -> Option<usize>| {
let ba = &b[&a];
let mut out = Bits::opaque(vec![0; w]);
for k in 0..w {
if let Some(s) = src(k) {
out.dep[k] = ba.dep[s];
out.piv[k] = ba.piv[s];
}
}
out
};
let known = |c: u32, k: usize| f[&c].known().bit(k as u16);
match n.op {
OpCode::Const | OpCode::Sym => Bits::opaque(vec![0; w]),
OpCode::Not => b[&n.a].clone(),
OpCode::Neg => carry_chain(&b[&n.a], None),
OpCode::Add | OpCode::Sub => carry_chain(&b[&n.a], Some(&b[&n.b])),
OpCode::Mul => {
let fixed = |m: u32| b[&m].all() == 0;
let (y, m) = if fixed(n.b) { (n.a, n.b) } else { (n.b, n.a) };
if !fixed(m) {
let mut out = carry_chain(&b[&n.a], Some(&b[&n.b]));
out.piv.iter_mut().for_each(|p| *p = 0);
return out;
}
let t = f[&m].known().trailing_known_zeros() as usize;
let base = carry_chain(&b[&y], None);
let mut out = Bits::opaque(vec![0; w]);
for k in t..w {
out.dep[k] = base.dep[k - t];
out.piv[k] = base.piv[k - t];
}
if t >= w || known(m, t) != Some(true) {
out.piv.iter_mut().for_each(|p| *p = 0);
}
out
}
OpCode::And | OpCode::Or | OpCode::Xor => {
let (ba, bc) = (&b[&n.a], &b[&n.b]);
let mut out = Bits::opaque(vec![0; w]);
for k in 0..w {
out.dep[k] = ba.dep[k] | bc.dep[k];
let through = |x: u32| match n.op {
OpCode::And => known(x, k) == Some(true),
OpCode::Or => known(x, k) == Some(false),
_ => false,
};
let (pa, pc) = (ba.piv[k], bc.piv[k]);
out.piv[k] = if n.op == OpCode::Xor {
(pa & !bc.dep[k]) | (pc & !ba.dep[k])
} else if through(n.b) {
pa
} else if through(n.a) {
pc
} else {
0
};
}
out
}
OpCode::Shl | OpCode::LShr | OpCode::AShr | OpCode::RotL | OpCode::RotR => {
let Some(v) = cx.const_val(n.b) else {
let d = shift_deps(n.op, &b[&n.a].dep, &f[&n.b], b[&n.b].all());
return Bits::opaque(d);
};
let c = count_span(n.op, &Facts::constant(&v), w).0 as usize;
match n.op {
OpCode::Shl => pick(n.a, &|k| (c <= k).then(|| k - c)),
OpCode::LShr => pick(n.a, &|k| (k + c < w).then(|| k + c)),
OpCode::AShr => pick(n.a, &|k| Some((k + c).min(w - 1))),
OpCode::RotL => pick(n.a, &|k| Some((k + w - c) % w)),
_ => pick(n.a, &|k| Some((k + c) % w)),
}
}
OpCode::Bswap => pick(n.a, &|k| Some((w / 8 - 1 - k / 8) * 8 + k % 8)),
OpCode::BitRev => pick(n.a, &|k| Some(w - 1 - k)),
OpCode::Zext => {
let wa = usize::from(cx.wid(n.a));
pick(n.a, &|k| (k < wa).then_some(k))
}
OpCode::Sext => {
let wa = usize::from(cx.wid(n.a));
pick(n.a, &|k| Some(k.min(wa - 1)))
}
OpCode::Extract => {
let lo = n.b as usize;
pick(n.a, &|k| Some(lo + k))
}
OpCode::Concat => {
let (h, l) = (&b[&n.a], &b[&n.b]);
let wl = l.dep.len();
let mut out = Bits::opaque(vec![0; w]);
for k in 0..w {
let (x, s) = if k < wl { (l, k) } else { (h, k - wl) };
out.dep[k] = x.dep[s];
out.piv[k] = x.piv[s];
}
out
}
OpCode::Select => {
let (c, t, e) = (&b[&n.a], &b[&n.b], &b[&n.c]);
let cond = c.all();
let mut out = Bits::opaque(vec![0; w]);
for k in 0..w {
out.dep[k] = cond | t.dep[k] | e.dep[k];
if cond == 0 {
out.piv[k] = t.piv[k] & e.piv[k];
}
}
out
}
_ => Bits::opaque(vec![n.children().fold(0, |acc, c| acc | b[&c].all()); w]),
}
}
fn recover<O: Oracle>(
cx: &mut Context,
o: &mut O,
top: u32,
hole: u32,
nodes: &[u32],
) -> Result<Option<Plan>, O::Err> {
let wv = cx.wid(hole);
if wv > MAX_HOLE_BITS || nodes.last() != Some(&top) {
return Ok(None);
}
let wv = usize::from(wv);
let mut bits: IdMap<u32, Bits> = IdMap::default();
let mut facts: IdMap<u32, Facts> = IdMap::default();
bits.insert(
hole,
Bits {
dep: (0..wv).map(|k| 1u128 << k).collect(),
piv: (0..wv).map(|k| 1u128 << k).collect(),
},
);
facts.insert(hole, Facts::top(cx.width_of(hole)));
for &i in nodes {
let n = cx.node(i);
for c in n.children() {
if bits.contains_key(&c) {
continue;
}
let f = match cx.const_val(c) {
Some(k) => Facts::constant(&k),
None => o
.facts(cx, c)?
.unwrap_or_else(|| Facts::top(cx.width_of(c))),
};
bits.insert(c, Bits::opaque(vec![0; usize::from(cx.wid(c))]));
facts.insert(c, f);
}
let counts = match n.op {
OpCode::Shl | OpCode::LShr | OpCode::AShr | OpCode::RotL | OpCode::RotR
if cx.const_val(n.b).is_none() =>
{
count_span(n.op, &facts[&n.b], usize::from(n.width)).1
}
_ => 1,
};
o.charge(1 + u64::from(n.width) * counts)?;
let kids: Vec<Facts> = n.children().map(|c| facts[&c]).collect();
let refs: Vec<&Facts> = kids.iter().collect();
let f = cx.transfer_local(i, &refs);
let mut nb = node_bits(cx, i, &bits, &facts);
let known = f.known().known();
for k in 0..nb.dep.len() {
if known.bit(k as u16) == Some(true) {
nb.dep[k] = 0;
nb.piv[k] = 0;
}
}
bits.insert(i, nb);
facts.insert(i, f);
}
let t = &bits[&top];
o.charge(t.dep.len() as u64 * wv as u64 / 16 + 1)?;
Ok(levels(t, wv))
}
fn levels(top: &Bits, wv: usize) -> Option<Plan> {
let full = if wv >= 128 {
u128::MAX
} else {
(1u128 << wv) - 1
};
let mut done = 0u128;
let mut out = Vec::new();
while done != full {
let mut level = Vec::new();
let mut got = 0u128;
for (k, (&d, &p)) in top.dep.iter().zip(&top.piv).enumerate() {
let open = d & !done;
if open.count_ones() != 1 || p & open == 0 || got & open != 0 {
continue;
}
level.push((open.trailing_zeros() as u8, k as u16));
got |= open;
}
if level.is_empty() {
return None;
}
done |= got;
out.push(level);
}
Some(out)
}
fn dominators(cx: &Context, top: u32, varies: &dyn Fn(u32) -> bool) -> (Vec<u32>, u64) {
let mut heap = std::collections::BinaryHeap::new();
let mut seen: IdMap<u32, ()> = IdMap::default();
for c in cx.node(top).children() {
if varies(c) && seen.insert(c, ()).is_none() {
heap.push(c);
}
}
let mut out = Vec::new();
let mut looked = 0u64;
while let Some(i) = heap.pop() {
looked += 1;
if heap.is_empty() {
out.push(i);
if out.len() >= MAX_CANDIDATES {
break;
}
}
if looked as usize > MAX_REGION {
break;
}
for c in cx.node(i).children() {
if varies(c) && seen.insert(c, ()).is_none() {
heap.push(c);
}
}
}
(out, looked)
}
fn between(cx: &Context, top: u32, hole: u32, varies: &dyn Fn(u32) -> bool) -> Option<Vec<u32>> {
let mut seen: IdMap<u32, ()> = IdMap::default();
let mut out = Vec::new();
let mut stack = vec![top];
while let Some(i) = stack.pop() {
if i == hole || seen.insert(i, ()).is_some() || (i != top && !varies(i)) {
continue;
}
let n = cx.node(i);
if n.op.arity() == 0 || out.len() >= MAX_REGION {
return None;
}
out.push(i);
stack.extend(n.children());
}
out.sort_unstable();
Some(out)
}
fn candidates(cx: &Context, top: u32, varies: &dyn Fn(u32) -> bool) -> (Vec<(u32, Vec<u32>)>, u64) {
let (doms, looked) = dominators(cx, top, varies);
let out: Vec<(u32, Vec<u32>)> = doms
.into_iter()
.filter(|&d| cx.wid(d) <= MAX_HOLE_BITS)
.filter_map(|d| between(cx, top, d, varies).map(|r| (d, r)))
.collect();
let work = looked + out.iter().map(|(_, r)| r.len() as u64).sum::<u64>();
(out, work)
}
enum Undo {
Bind(u32),
Hole,
}
struct AntiUnify<'c> {
cx: &'c Context,
hole: Option<(u32, u32)>,
map: IdMap<u32, u32>,
trail: Vec<Undo>,
steps: u32,
depth: u32,
}
impl AntiUnify<'_> {
fn bind(&mut self, x1: u32, x2: u32) {
self.map.insert(x1, x2);
self.trail.push(Undo::Bind(x1));
}
fn undo(&mut self, to: usize) {
while self.trail.len() > to {
match self.trail.pop() {
Some(Undo::Bind(x)) => {
self.map.remove(&x);
}
Some(Undo::Hole) => self.hole = None,
None => {}
}
}
}
fn go(&mut self, x1: u32, x2: u32) -> bool {
self.steps += 1;
if self.steps > MAX_MATCH || self.depth >= MAX_MATCH_DEPTH {
return false;
}
if let Some(&p) = self.map.get(&x1) {
return p == x2;
}
if x1 == x2 {
self.bind(x1, x2);
return true;
}
if let Some((_, h2)) = self.hole {
return x2 != h2 && self.descend(x1, x2);
}
let mark = self.trail.len();
if self.descend(x1, x2) {
return true;
}
self.undo(mark);
if self.cx.wid(x1) != self.cx.wid(x2) {
return false;
}
self.hole = Some((x1, x2));
self.trail.push(Undo::Hole);
self.bind(x1, x2);
true
}
fn descend(&mut self, x1: u32, x2: u32) -> bool {
let (n1, n2) = (self.cx.node(x1), self.cx.node(x2));
if n1.op != n2.op
|| n1.width != n2.width
|| n1.aux != n2.aux
|| matches!(n1.op, OpCode::Const | OpCode::Sym)
|| (n1.op == OpCode::Extract && n1.b != n2.b)
{
return false;
}
let mark = self.trail.len();
self.bind(x1, x2);
self.depth += 1;
let (k1, k2): (Vec<u32>, Vec<u32>) = (n1.children().collect(), n2.children().collect());
let commutative = n1.op.as_bin().is_some_and(BinOp::is_commutative)
|| matches!(n1.op, OpCode::Eq | OpCode::Ne);
let ok = if commutative {
(self.go(k1[0], k2[0]) && self.go(k1[1], k2[1])) || {
self.undo(mark + 1);
self.go(k1[0], k2[1]) && self.go(k1[1], k2[0])
}
} else {
k1.iter().zip(&k2).all(|(&a, &b)| self.go(a, b))
};
self.depth -= 1;
if !ok {
self.undo(mark);
}
ok
}
}
struct Unified {
v1: u32,
map: IdMap<u32, u32>,
}
fn anti_unify(cx: &Context, n1: u32, n2: u32) -> (Option<Unified>, u32) {
let mut m = AntiUnify {
cx,
hole: None,
map: IdMap::default(),
trail: Vec::new(),
steps: 0,
depth: 0,
};
let ok = m.go(n1, n2);
let steps = m.steps;
match (ok, m.hole) {
(true, Some((v1, _))) => (Some(Unified { v1, map: m.map }), steps),
_ => (None, steps),
}
}
fn eval_region(cx: &Context, nodes: &[u32], hole: u32, x: &BitVec) -> Option<BitVec> {
let mut vals: IdMap<u32, BitVec> = IdMap::default();
vals.insert(hole, *x);
for &i in nodes {
let r = cx
.eval_node(
i,
|j| {
vals.get(&j)
.copied()
.or_else(|| cx.const_val(j))
.unwrap_or(BitVec::zero(Width::W1))
},
|_, _| None,
)
.ok()?;
vals.insert(i, r);
}
nodes.last().and_then(|t| vals.get(t)).copied()
}
fn region_solve<O: Oracle>(
cx: &Context,
o: &mut O,
r: &Region,
hole: u32,
c: &BitVec,
) -> Result<Option<Option<BitVec>>, O::Err> {
let w = cx.width_of(hole);
let cost = r.nodes.len() as u64;
let mut x = BitVec::zero(w);
for level in &r.levels {
o.charge(cost)?;
let Some(y) = eval_region(cx, &r.nodes, hole, &x) else {
return Ok(None);
};
for &(j, k) in level {
if y.bit(k) != c.bit(k) {
let m = BitVec::bin_unchecked(
BinOp::Shl,
&BitVec::one(w),
&BitVec::wrapping_from_u64(w, u64::from(j)),
);
x = BitVec::bin_unchecked(BinOp::Or, &x, &m);
}
}
}
o.charge(cost)?;
let Some(y) = eval_region(cx, &r.nodes, hole, &x) else {
return Ok(None);
};
Ok(Some((y == *c).then_some(x)))
}
pub(crate) fn solve_layer<O: Oracle>(
cx: &mut Context,
o: &mut O,
n: u32,
) -> Result<Option<Layer>, O::Err> {
let node = cx.node(n);
let kids: Vec<u32> = node.children().collect();
let open: Vec<usize> = (0..kids.len())
.filter(|&p| cx.const_val(kids[p]).is_none())
.collect();
if let [pos] = open[..] {
if let Some((map, kind, cond)) = primitive(cx, n, pos) {
if cond.is_some_and(|k| cx.const_val(k).and_then(|v| v.bit(0)) != Some(true)) {
return Ok(None);
}
return Ok(Some(Layer {
inner: kids[pos],
kind,
map,
}));
}
if let Some((_, output)) = node.op.as_ext()
&& let Some(kind) = ext_kind(cx, o, n, pos)?
{
return Ok(Some(Layer {
inner: kids[pos],
kind,
map: Map::Ext {
output: output as u8,
arg: pos as u8,
},
}));
}
}
if open.is_empty() {
return Ok(None);
}
let (cands, work) = candidates(cx, n, &|c| cx.const_val(c).is_none());
o.charge(work)?;
for (d, nodes) in cands {
if let Some(levels) = recover(cx, o, n, d, &nodes)? {
let kind = if cx.wid(n) == cx.wid(d) {
Kind::Bijective
} else {
Kind::Injective
};
return Ok(Some(Layer {
inner: d,
kind,
map: Map::Region(Box::new(Region { nodes, levels })),
}));
}
}
Ok(None)
}
pub(crate) fn preimage<O: Oracle>(
cx: &Context,
o: &mut O,
n: u32,
l: &Layer,
c: &BitVec,
) -> Result<Option<Option<BitVec>>, O::Err> {
let k = |p: u32| cx.const_val(p);
let bin = |op, x: &BitVec, y: &BitVec| BitVec::bin_unchecked(op, x, y);
let wi = cx.width_of(l.inner);
let exact = |v: BitVec| Some(Some(v));
Ok(match &l.map {
Map::Un(op) => exact(BitVec::un_unchecked(*op, c)),
Map::Add(p) => k(*p).and_then(|p| exact(bin(BinOp::Sub, c, &p))),
Map::Sub(p) => k(*p).and_then(|p| exact(bin(BinOp::Add, c, &p))),
Map::SubFrom(p) => k(*p).and_then(|p| exact(bin(BinOp::Sub, &p, c))),
Map::Xor(p) => k(*p).and_then(|p| exact(bin(BinOp::Xor, c, &p))),
Map::Mul(p) => k(*p)
.and_then(|p| mul_inverse(&p))
.and_then(|i| exact(bin(BinOp::Mul, c, &i))),
Map::RotL(p) => k(*p).and_then(|p| exact(bin(BinOp::RotR, c, &p))),
Map::RotR(p) => k(*p).and_then(|p| exact(bin(BinOp::RotL, c, &p))),
Map::Zext | Map::Sext => (|| {
let t = c.trunc(wi).ok()?;
let back = if matches!(l.map, Map::Zext) {
t.zext(c.width()).ok()?
} else {
t.sext(c.width()).ok()?
};
Some((back == *c).then_some(t))
})(),
Map::ConcatHi(p) => k(*p).and_then(|p| {
let wl = p.width();
let (hi, lo) = (c.extract(wl.bits(), wi).ok()?, c.trunc(wl).ok()?);
Some((lo == p).then_some(hi))
}),
Map::ConcatLo(p) => k(*p).and_then(|p| {
let (hi, lo) = (c.extract(wi.bits(), p.width()).ok()?, c.trunc(wi).ok()?);
Some((hi == p).then_some(lo))
}),
Map::Ext { output, arg } => {
let node = cx.node(n);
let Some(op) = cx.registry.as_deref().and_then(|r| r.op_at(node.aux)) else {
return Ok(None);
};
let mut args: Vec<BitVec> = Vec::new();
for (p, a) in node.children().enumerate() {
args.push(if p == usize::from(*arg) {
BitVec::zero(cx.width_of(a))
} else {
match k(a) {
Some(v) => v,
None => return Ok(None),
}
});
}
match op.invert(*output, *arg, &args, c) {
Some(v) if v.width() == wi => {
args[usize::from(*arg)] = v;
let out = crate::ext::run_eval(op, &args).ok();
let hit = out.and_then(|o| o.get(usize::from(*output)).copied()) == Some(*c);
hit.then_some(Some(v))
}
Some(_) => None,
None if l.kind == Kind::Injective => Some(None),
None => None,
}
}
Map::Region(r) => region_solve(cx, o, r, l.inner, c)?,
})
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum Solved {
Eq(u32, BitVec),
Const(bool),
}
pub(crate) fn solve_eq<O: Oracle>(
cx: &mut Context,
o: &mut O,
e: u32,
c: &BitVec,
) -> Result<Option<Solved>, O::Err> {
let (mut cur, mut val) = (e, *c);
let mut peeled = false;
loop {
o.charge(1)?;
let Some(l) = solve_layer(cx, o, cur)? else {
break;
};
match preimage(cx, o, cur, &l, &val)? {
None => break,
Some(None) => return Ok(Some(Solved::Const(false))),
Some(Some(v)) => {
(cur, val) = (l.inner, v);
peeled = true;
}
}
}
Ok(peeled.then_some(Solved::Eq(cur, val)))
}
pub(crate) fn cancel_layer<O: Oracle>(
cx: &mut Context,
o: &mut O,
n1: u32,
n2: u32,
) -> Result<Option<(u32, u32)>, O::Err> {
let (a, b) = (cx.node(n1), cx.node(n2));
if a.op != b.op || a.width != b.width || a.aux != b.aux || n1 == n2 {
return Ok(None);
}
let (ka, kb): (Vec<u32>, Vec<u32>) = (a.children().collect(), b.children().collect());
if ka.len() != kb.len() || ka.is_empty() {
return Ok(None);
}
let swappable = a.op.as_bin().is_some_and(BinOp::is_commutative)
|| (a.op.as_ext().is_some()
&& ka.len() >= 2
&& cx
.registry
.as_deref()
.and_then(|r| r.op_at(a.aux))
.is_some_and(|op| op.traits().commutative));
let mut pairings = vec![(0..ka.len()).collect::<Vec<usize>>()];
if swappable {
let mut s: Vec<usize> = (0..ka.len()).collect();
s.swap(0, 1);
pairings.push(s);
}
for pairing in &pairings {
let diff: Vec<usize> = (0..ka.len()).filter(|&p| ka[p] != kb[pairing[p]]).collect();
let [p] = diff[..] else {
continue;
};
let (x, y) = (ka[p], kb[pairing[p]]);
if cx.wid(x) != cx.wid(y) {
continue;
}
if let Some((_, _, cond)) = primitive(cx, n1, p) {
if let Some(k) = cond
&& !odd(cx, o, k)?
{
continue;
}
return Ok(Some((x, y)));
}
if a.op.as_ext().is_some() && pairing[p] == p && ext_kind(cx, o, n1, p)?.is_some() {
return Ok(Some((x, y)));
}
}
let (found, steps) = anti_unify(cx, n1, n2);
o.charge(u64::from(steps))?;
let Some(Unified { v1, map }) = found else {
return Ok(None);
};
let (cands, work) = candidates(cx, n1, &|c| c == v1 || map.get(&c).is_some_and(|&p| p != c));
o.charge(work)?;
for (d1, nodes) in cands {
let Some(&d2) = map.get(&d1) else {
continue;
};
if cx.wid(d1) == cx.wid(d2) && recover(cx, o, n1, d1, &nodes)?.is_some() {
return Ok(Some((d1, d2)));
}
}
Ok(None)
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Chain {
Independent,
Proved(Kind),
Unknown,
}
const MAX_CHAIN_NODES: usize = 1 << 16;
pub(crate) fn chain<O: Oracle>(
cx: &mut Context,
o: &mut O,
e: u32,
x: u32,
) -> Result<Chain, O::Err> {
if e == x {
return Ok(Chain::Proved(Kind::Bijective));
}
let mut below: Vec<u32> = Vec::new();
let mut seen: IdMap<u32, ()> = IdMap::default();
let mut stack = vec![e];
while let Some(i) = stack.pop() {
if seen.insert(i, ()).is_some() {
continue;
}
below.push(i);
if below.len() > MAX_CHAIN_NODES {
return Ok(Chain::Unknown);
}
if i != x {
stack.extend(cx.node(i).children());
}
}
o.charge(below.len() as u64)?;
below.sort_unstable();
let mut reach: IdMap<u32, ()> = IdMap::default();
for &i in &below {
if i == x || cx.node(i).children().any(|c| reach.contains_key(&c)) {
reach.insert(i, ());
}
}
if !reach.contains_key(&e) {
return Ok(Chain::Independent);
}
let mut kind = Kind::Bijective;
let mut cur = e;
while cur != x {
o.charge(1)?;
let Some((next, k)) = chain_layer(cx, o, cur, &reach)? else {
return Ok(Chain::Unknown);
};
kind = kind.min(k);
cur = next;
}
Ok(Chain::Proved(kind))
}
fn chain_layer<O: Oracle>(
cx: &mut Context,
o: &mut O,
n: u32,
reach: &IdMap<u32, ()>,
) -> Result<Option<(u32, Kind)>, O::Err> {
let node = cx.node(n);
let kids: Vec<u32> = node.children().collect();
let open: Vec<usize> = (0..kids.len())
.filter(|&p| reach.contains_key(&kids[p]))
.collect();
if let [pos] = open[..] {
if let Some((_, kind, cond)) = primitive(cx, n, pos) {
if let Some(k) = cond
&& !odd(cx, o, k)?
{
return Ok(None);
}
return Ok(Some((kids[pos], kind)));
}
if node.op.as_ext().is_some()
&& let Some(kind) = ext_kind(cx, o, n, pos)?
{
return Ok(Some((kids[pos], kind)));
}
}
let (cands, work) = candidates(cx, n, &|c| reach.contains_key(&c));
o.charge(work)?;
for (d, nodes) in cands {
if recover(cx, o, n, d, &nodes)?.is_some() {
let kind = if cx.wid(n) == cx.wid(d) {
Kind::Bijective
} else {
Kind::Injective
};
return Ok(Some((d, kind)));
}
}
Ok(None)
}
struct Queries<'a> {
a: Option<&'a Assumptions>,
rel: Reliance,
}
impl Oracle for Queries<'_> {
type Err = Error;
fn facts(&mut self, cx: &mut Context, i: u32) -> Result<Option<Facts>, Error> {
let e = cx.handle(i);
let Some(a) = self.a else {
return Ok(Some(cx.facts(e)?));
};
let cap = cx.config().fact_work;
Ok(match cx.facts_under_cap(e, a, cap)? {
Ok((f, r)) => {
self.rel |= r;
Some(f)
}
Err(_) => None,
})
}
fn charge(&mut self, _units: u64) -> Result<(), Error> {
Ok(())
}
}
pub(crate) fn prove(
cx: &mut Context,
e: Expr,
of: Expr,
onto: bool,
a: Option<&Assumptions>,
) -> Result<Proof, Error> {
let answer = |truth, relies_on| Proof { truth, relies_on };
let (ei, xi) = (cx.id(e)?, cx.id(of)?);
if let Some(a) = a {
a.check_context(cx)?;
let cap = cx.config().fact_work;
if let Err(r) = cx.facts_under_cap(e, a, cap)? {
return Ok(answer(Truth::True, r));
}
}
let (we, wx) = (cx.wid(ei), cx.wid(xi));
if we < wx || (onto && we != wx) {
return Ok(answer(Truth::False, Reliance::NONE));
}
let mut o = Queries {
a,
rel: Reliance::NONE,
};
Ok(match chain(cx, &mut o, ei, xi)? {
Chain::Independent => answer(Truth::False, Reliance::NONE),
Chain::Proved(_) => answer(Truth::True, o.rel),
Chain::Unknown => answer(Truth::Unknown, Reliance::NONE),
})
}