use core::fmt;
use std::sync::Arc;
use super::backward::{backward, holding};
use super::{FactMap, Facts};
use crate::error::Error;
use crate::expr::{Context, Expr};
use crate::hash::IdMap;
use crate::ops::CmpOp;
use crate::{BitVec, Width};
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct ConstraintId(u32);
impl ConstraintId {
pub fn index(self) -> u32 {
self.0
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Default)]
pub struct Reliance(u64);
const REST: u64 = 1 << 63;
impl Reliance {
pub const NONE: Reliance = Reliance(0);
pub(crate) fn of(id: ConstraintId) -> Reliance {
Reliance(if id.0 >= 63 { REST } else { 1 << id.0 })
}
pub fn is_none(self) -> bool {
self.0 == 0
}
pub fn may_use(self, id: ConstraintId) -> bool {
self.0 & Reliance::of(id).0 != 0
}
pub fn all_before(self, id: ConstraintId) -> bool {
if id.0 >= 63 {
self.0 & REST == 0
} else {
self.0 >> id.0 == 0
}
}
#[must_use]
pub fn union(self, o: Reliance) -> Reliance {
Reliance(self.0 | o.0)
}
pub fn indices(self) -> (impl Iterator<Item = u32>, bool) {
let bits = self.0 & !REST;
(
(0..63u32).filter(move |i| bits >> i & 1 == 1),
self.0 & REST != 0,
)
}
}
impl core::ops::BitOr for Reliance {
type Output = Reliance;
fn bitor(self, o: Reliance) -> Reliance {
self.union(o)
}
}
impl core::ops::BitOrAssign for Reliance {
fn bitor_assign(&mut self, o: Reliance) {
*self = self.union(o);
}
}
impl fmt::Debug for Reliance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (low, rest) = self.indices();
let mut l = f.debug_set();
l.entries(low);
if rest {
l.entry(&format_args!("63.."));
}
l.finish()
}
}
pub(crate) mod worlds {
use crate::ops::CmpOp;
const LT_LT: u8 = 1;
const LT_GT: u8 = 2;
const GT_LT: u8 = 4;
const GT_GT: u8 = 8;
const EQ: u8 = 16;
pub(crate) const ALL: u8 = 31;
pub(crate) const fn holding(op: CmpOp) -> u8 {
match op {
CmpOp::Eq => EQ,
CmpOp::Ne => LT_LT | LT_GT | GT_LT | GT_GT,
CmpOp::Ult => LT_LT | LT_GT,
CmpOp::Ule => LT_LT | LT_GT | EQ,
CmpOp::Slt => LT_LT | GT_LT,
CmpOp::Sle => LT_LT | GT_LT | EQ,
}
}
pub(crate) const fn swap(m: u8) -> u8 {
(m & EQ) | ((m & LT_LT) << 3) | ((m & GT_GT) >> 3) | ((m & LT_GT) << 1) | ((m & GT_LT) >> 1)
}
pub(crate) const fn decide(m: u8, op: CmpOp) -> Option<bool> {
let h = holding(op);
if m & !h == 0 {
Some(true)
} else if m & h == 0 {
Some(false)
} else {
None
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub(crate) struct Relation {
pub(crate) a: Expr,
pub(crate) b: Expr,
pub(crate) worlds: u8,
pub(crate) rel: Reliance,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Store {
pub(crate) declaration_revision: u64,
pub(crate) seeds: Vec<(Expr, Facts)>,
pub(crate) entries: Vec<(Expr, Facts, Reliance)>,
pub(crate) relations: Vec<Relation>,
pub(crate) classes: Vec<(Expr, Expr, Reliance)>,
pub(crate) infeasible: Option<Reliance>,
pub(crate) truncated: bool,
pub(crate) env: Env,
}
impl Store {
fn publish(&mut self, cx: &Context) {
let mut relations: Vec<Relation> = self
.env
.relations
.iter()
.map(|(&(a, b), &(worlds, rel))| Relation {
a: cx.handle(a),
b: cx.handle(b),
worlds,
rel,
})
.collect();
relations.sort_unstable_by_key(|r| (r.a.index(), r.b.index()));
self.relations = relations;
let mut classes: Vec<(Expr, Expr, Reliance)> = self
.env
.root
.iter()
.map(|(&m, &(r, rel))| (cx.handle(m), cx.handle(r), rel))
.collect();
classes.sort_unstable_by_key(|c| c.0.index());
self.classes = classes;
}
}
impl PartialEq for Store {
fn eq(&self, o: &Store) -> bool {
self.seeds == o.seeds
&& self.entries == o.entries
&& self.relations == o.relations
&& self.classes == o.classes
&& self.infeasible == o.infeasible
&& self.truncated == o.truncated
}
}
#[derive(Clone, Default)]
pub struct Assumptions {
pub(crate) store: Arc<Store>,
}
impl PartialEq for Assumptions {
fn eq(&self, o: &Assumptions) -> bool {
Arc::ptr_eq(&self.store, &o.store) || *self.store == *o.store
}
}
impl fmt::Debug for Assumptions {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Assumptions")
.field("constraints", &self.store.seeds)
.field("infeasible", &self.store.infeasible)
.finish_non_exhaustive()
}
}
const STEPS: u32 = 4096;
const ROUNDS: u32 = 3;
impl Assumptions {
pub fn new() -> Self {
Self::default()
}
pub fn assume(
&mut self,
cx: &mut Context,
e: Expr,
facts: Facts,
) -> Result<ConstraintId, Error> {
let w = cx.width(e)?;
if facts.width() != w {
return Err(crate::WidthError::Mismatch {
left: w.bits(),
right: facts.width().bits(),
}
.into());
}
self.add(cx, e, facts)
}
pub fn assume_true(&mut self, cx: &mut Context, p: Expr) -> Result<ConstraintId, Error> {
self.predicate(cx, p, true)
}
pub fn assume_false(&mut self, cx: &mut Context, p: Expr) -> Result<ConstraintId, Error> {
self.predicate(cx, p, false)
}
fn predicate(&mut self, cx: &mut Context, p: Expr, v: bool) -> Result<ConstraintId, Error> {
let w = cx.width(p)?;
if w != Width::W1 {
return Err(crate::WidthError::Mismatch {
left: w.bits(),
right: 1,
}
.into());
}
self.add(cx, p, Facts::constant(&BitVec::from_bool(v)))
}
fn add(&mut self, cx: &mut Context, e: Expr, facts: Facts) -> Result<ConstraintId, Error> {
let n = cx.id(e)?;
self.check_context(cx)?;
self.refresh(cx)?;
let id = self.next_id();
cx.facts.forget(self);
let store = Arc::make_mut(&mut self.store);
store.declaration_revision = cx.declaration_revision;
store.seeds.push((e, facts));
if store.infeasible.is_none() {
let mut p = Propagation {
overlay: FactMap::default(),
changed: Vec::new(),
steps: STEPS,
store,
work: Vec::new(),
};
p.work.push((n, facts, Reliance::of(id)));
if p.drain(cx)? {
for _ in 0..ROUNDS {
if !p.sweep(cx)? {
break;
}
}
}
store.publish(cx);
}
Ok(id)
}
pub(crate) fn check_context(&self, cx: &Context) -> Result<(), Error> {
if let Some((e, _)) = self.store.seeds.first() {
cx.id(*e)?;
}
Ok(())
}
pub fn refresh(&mut self, cx: &mut Context) -> Result<(), Error> {
if let std::borrow::Cow::Owned(current) = self.refreshed(cx)? {
*self = current;
}
Ok(())
}
pub(crate) fn refreshed<'a>(
&'a self,
cx: &mut Context,
) -> Result<std::borrow::Cow<'a, Self>, Error> {
use std::borrow::Cow;
self.check_context(cx)?;
if self.is_empty() || self.store.declaration_revision == cx.declaration_revision {
return Ok(Cow::Borrowed(self));
}
if let Some(cached) = &cx.facts.overlay_key
&& cached.store.declaration_revision == cx.declaration_revision
&& cached.store.seeds == self.store.seeds
{
return Ok(Cow::Owned(cached.clone()));
}
let mut current = Assumptions::new();
for &(expression, facts) in &self.store.seeds {
current.assume(cx, expression, facts)?;
}
cx.facts.overlay.clear();
cx.facts.overlay_key = Some(current.clone());
Ok(Cow::Owned(current))
}
pub fn is_infeasible(&self) -> bool {
self.store.infeasible.is_some()
}
pub fn conflict(&self) -> Option<Reliance> {
self.store.infeasible
}
pub fn is_empty(&self) -> bool {
self.store.seeds.is_empty()
}
pub fn len(&self) -> usize {
self.store.seeds.len()
}
pub fn next_id(&self) -> ConstraintId {
ConstraintId(u32::try_from(self.store.seeds.len()).unwrap_or(u32::MAX))
}
pub fn constraint(&self, id: ConstraintId) -> Option<(Expr, Facts)> {
self.store.seeds.get(id.0 as usize).copied()
}
pub fn constraints(&self) -> impl Iterator<Item = (ConstraintId, Expr, Facts)> + '_ {
self.store
.seeds
.iter()
.enumerate()
.map(|(i, (e, f))| (ConstraintId(i as u32), *e, *f))
}
pub fn predicates(&self, cx: &mut Context) -> Result<Vec<Expr>, Error> {
self.check_context(cx)?;
self.constraints()
.map(|(_, expression, facts)| {
let id = cx.id(expression)?;
let predicate = facts_predicate(cx, id, facts)?;
Ok(cx.handle(predicate))
})
.collect()
}
pub fn is_truncated(&self) -> bool {
self.store.truncated
}
}
pub(crate) fn facts_predicate(cx: &mut Context, value: u32, facts: Facts) -> Result<u32, Error> {
use crate::{BinOp, BitVec, CmpOp};
let width = facts.width();
if let Some(constant) = facts.as_constant() {
let constant = cx.mk_const(&constant)?;
return cx.c_cmp(CmpOp::Eq, value, constant);
}
let mut clauses = Vec::new();
let known = facts.known();
if !known.known().is_zero() {
let mask = cx.mk_const(&known.known())?;
let ones = cx.mk_const(&known.known_one())?;
let masked = cx.c_bin(BinOp::And, value, mask)?;
clauses.push(cx.c_cmp(CmpOp::Eq, masked, ones)?);
}
let unsigned = facts.urange();
let lo = unsigned.lo();
let hi = unsigned.hi();
if !lo.is_zero() {
let bound = cx.mk_const(&lo)?;
clauses.push(cx.c_cmp(CmpOp::Ule, bound, value)?);
}
if !hi.is_ones() {
let bound = cx.mk_const(&hi)?;
clauses.push(cx.c_cmp(CmpOp::Ule, value, bound)?);
}
let stride = unsigned.stride();
if stride > 1 {
let bound = cx.mk_const(&lo)?;
if width.bits() < 64 && stride >> width.bits() != 0 {
clauses.push(cx.c_cmp(CmpOp::Eq, value, bound)?);
} else if stride.is_power_of_two() {
let mask = cx.mk_const(&BitVec::wrapping_from_u64(width, stride - 1))?;
let masked = cx.c_bin(BinOp::And, value, mask)?;
let residue = cx.c_bin(BinOp::And, bound, mask)?;
clauses.push(cx.c_cmp(CmpOp::Eq, masked, residue)?);
} else {
let delta = cx.c_bin(BinOp::Sub, value, bound)?;
let modulus = cx.mk_const(&BitVec::wrapping_from_u64(width, stride))?;
let remainder = cx.c_bin(BinOp::URem, delta, modulus)?;
let zero = cx.mk_const(&BitVec::zero(width))?;
clauses.push(cx.c_cmp(CmpOp::Eq, remainder, zero)?);
}
}
let signed = facts.srange();
if signed.lo() != BitVec::smin(width) {
let bound = cx.mk_const(&signed.lo())?;
clauses.push(cx.c_cmp(CmpOp::Sle, bound, value)?);
}
if signed.hi() != BitVec::smax(width) {
let bound = cx.mk_const(&signed.hi())?;
clauses.push(cx.c_cmp(CmpOp::Sle, value, bound)?);
}
let mut predicate = cx.mk_const(&BitVec::from_bool(true))?;
for clause in clauses {
predicate = cx.c_bin(BinOp::And, predicate, clause)?;
}
Ok(predicate)
}
#[derive(Clone, Debug)]
pub(crate) struct Env {
pub(crate) assumed: IdMap<u32, (Facts, Reliance)>,
pub(crate) relations: IdMap<(u32, u32), (u8, Reliance)>,
pub(crate) min: u32,
pub(crate) infeasible: Option<Reliance>,
entry_slot: IdMap<u32, usize>,
root: IdMap<u32, (u32, Reliance)>,
members: IdMap<u32, Vec<u32>>,
}
impl Default for Env {
fn default() -> Self {
Env {
assumed: IdMap::default(),
relations: IdMap::default(),
entry_slot: IdMap::default(),
root: IdMap::default(),
members: IdMap::default(),
min: u32::MAX,
infeasible: None,
}
}
}
impl Env {
pub(crate) fn is_empty(&self) -> bool {
self.assumed.is_empty() && self.relations.is_empty()
}
pub(crate) fn find(&self, a: u32) -> (u32, Reliance) {
self.root.get(&a).copied().unwrap_or((a, Reliance::NONE))
}
pub(crate) fn relation(&self, a: u32, b: u32) -> Option<(u8, Reliance)> {
let ((ra, xa), (rb, xb)) = (self.find(a), self.find(b));
if ra == rb {
return (a != b).then_some((worlds::holding(CmpOp::Eq), xa | xb));
}
let (m, r) = if ra < rb {
*self.relations.get(&(ra, rb))?
} else {
let (m, r) = *self.relations.get(&(rb, ra))?;
(worlds::swap(m), r)
};
Some((m, r | xa | xb))
}
}
struct Propagation<'p> {
overlay: FactMap,
changed: Vec<u32>,
steps: u32,
store: &'p mut Store,
work: Vec<(u32, Facts, Reliance)>,
}
impl Propagation<'_> {
fn current(&mut self, cx: &mut Context, n: u32) -> Option<(Facts, Reliance)> {
if let Some(v) = cx.const_val(n) {
return Some((Facts::constant(&v), Reliance::NONE));
}
let cap = cx.config().fact_work;
match cx.overlay_facts(&self.store.env, &mut self.overlay, n, cap) {
Ok(f) => Some(f),
Err(rel) => {
self.infeasible(rel);
None
}
}
}
fn infeasible(&mut self, rel: Reliance) {
let prev = self.store.infeasible.unwrap_or(Reliance::NONE);
self.store.infeasible = Some(prev | rel);
self.store.env.infeasible = self.store.infeasible;
self.work.clear();
}
fn drain(&mut self, cx: &mut Context) -> Result<bool, Error> {
let mut changed = false;
while let Some((n, req, rel)) = self.work.pop() {
if self.steps == 0 {
self.store.truncated = true;
self.work.clear();
break;
}
self.steps -= 1;
let Some((cur, cur_rel)) = self.current(cx, n) else {
self.infeasible(rel);
return Ok(changed);
};
let Some(new) = cur.meet(&req) else {
self.infeasible(rel | cur_rel);
return Ok(true);
};
if new == cur {
continue;
}
let rel = rel | cur_rel;
self.record(cx, n, new, rel);
changed = true;
if !self.backward(cx, n, new, rel)? {
return Ok(true);
}
}
Ok(changed)
}
fn record(&mut self, cx: &Context, n: u32, f: Facts, rel: Reliance) {
let e = cx.handle(n);
match self.store.env.entry_slot.get(&n) {
Some(&k) => self.store.entries[k] = (e, f, rel),
None => {
self.store
.env
.entry_slot
.insert(n, self.store.entries.len());
self.store.entries.push((e, f, rel));
}
}
self.store.env.assumed.insert(n, (f, rel));
self.store.env.min = self.store.env.min.min(n);
self.invalidate_from(n);
self.changed.push(n);
}
fn invalidate_from(&mut self, n: u32) {
self.overlay.retain_below(n);
}
fn backward(
&mut self,
cx: &mut Context,
n: u32,
r: Facts,
rel: Reliance,
) -> Result<bool, Error> {
let node = cx.node(n);
let arity = node.op.arity();
if arity == 0 {
return Ok(true);
}
let kids: Vec<u32> = node.children().collect();
let mut facts = [Facts::top(Width::W1); 3];
let mut all = rel;
for (k, &c) in kids.iter().enumerate() {
let Some((f, r)) = self.current(cx, c) else {
self.infeasible(all);
return Ok(false);
};
facts[k] = f;
all |= r;
}
let op = cx.top_of(n);
let refs: [&Facts; 3] = [&facts[0], &facts[1], &facts[2]];
let Some(out) = backward(&op, &r, &refs[..arity]) else {
self.infeasible(all);
return Ok(false);
};
if let (Some(cmp), Some(v)) = (node.op.as_cmp(), r.as_constant())
&& !self.relate(cmp, kids[0], kids[1], !v.is_zero(), all)
{
return Ok(false);
}
for (k, f) in out.iter().enumerate().take(arity) {
if let Some(f) = f {
self.work.push((kids[k], *f, all));
}
}
Ok(true)
}
fn relate(&mut self, op: CmpOp, a: u32, b: u32, v: bool, rel: Reliance) -> bool {
let (held, swap) = holding(op, v);
let (a, b) = if swap { (b, a) } else { (a, b) };
let env = &self.store.env;
let ((ra, xa), (rb, xb)) = (env.find(a), env.find(b));
let rel = rel | xa | xb;
let mut m = worlds::holding(held);
if ra == rb {
if m & worlds::holding(CmpOp::Eq) == 0 {
self.infeasible(rel);
return false;
}
return true;
}
let (lo, hi) = if ra < rb {
(ra, rb)
} else {
m = worlds::swap(m);
(rb, ra)
};
self.store.env.min = self.store.env.min.min(a.min(b));
self.narrow(lo, hi, m, rel)
}
fn narrow(&mut self, lo: u32, hi: u32, m: u8, rel: Reliance) -> bool {
let (old, old_rel) = self
.store
.env
.relations
.get(&(lo, hi))
.copied()
.unwrap_or((worlds::ALL, Reliance::NONE));
let new = old & m;
if new == 0 {
self.infeasible(rel | old_rel);
return false;
}
if new == old {
return true;
}
let rel = rel | old_rel;
self.invalidate_from(lo.min(hi));
self.mark_classes(lo, hi);
if new == worlds::holding(CmpOp::Eq) {
self.store.env.relations.remove(&(lo, hi));
return self.merge(lo, hi, rel);
}
self.store.env.relations.insert((lo, hi), (new, rel));
true
}
fn mark_classes(&mut self, a: u32, b: u32) {
let env = &self.store.env;
for r in [a, b] {
let (rep, _) = env.find(r);
self.changed.push(rep);
if let Some(ms) = env.members.get(&rep) {
self.changed.extend(ms.iter().copied());
}
}
}
fn merge(&mut self, lo: u32, hi: u32, rel: Reliance) -> bool {
let env = &mut self.store.env;
let mut moved = env.members.remove(&hi).unwrap_or_default();
moved.push(hi);
for &x in &moved {
let (_, xr) = env.find(x);
env.root.insert(x, (lo, xr | rel));
}
env.members.entry(lo).or_default().extend(moved);
let mut rekey: Vec<((u32, u32), (u8, Reliance))> = env
.relations
.iter()
.filter(|((a, b), _)| *a == hi || *b == hi)
.map(|(&k, &v)| (k, v))
.collect();
rekey.sort_unstable_by_key(|&(k, _)| k);
for &(k, _) in &rekey {
env.relations.remove(&k);
}
for ((a, b), (m, r)) in rekey {
let (other, m) = if a == hi {
(b, m)
} else {
(a, worlds::swap(m))
};
let env = &self.store.env;
let ((x, xr), (y, yr)) = (env.find(hi), env.find(other));
let rel = r | rel | xr | yr;
let ok = if x == y {
m & worlds::holding(CmpOp::Eq) != 0 || {
self.infeasible(rel);
false
}
} else if x < y {
self.narrow(x, y, m, rel)
} else {
self.narrow(y, x, worlds::swap(m), rel)
};
if !ok {
return false;
}
}
true
}
fn sweep(&mut self, cx: &mut Context) -> Result<bool, Error> {
let changed = core::mem::take(&mut self.changed);
let Some(&lo) = changed.iter().min() else {
return Ok(false);
};
let hit: IdMap<u32, ()> = changed.iter().map(|&c| (c, ())).collect();
let mut reach: IdMap<u32, bool> = IdMap::default();
let mut reaches = |cx: &Context, n: u32| -> bool {
let mut stack = vec![(n, false)];
while let Some((i, expanded)) = stack.pop() {
if reach.contains_key(&i) {
continue;
}
if i < lo || hit.contains_key(&i) {
reach.insert(i, i >= lo);
continue;
}
let node = cx.node(i);
if !expanded {
stack.push((i, true));
stack.extend(node.children().map(|c| (c, false)));
continue;
}
let r = node.children().any(|c| reach.get(&c) == Some(&true));
reach.insert(i, r);
}
reach[&n]
};
let mut learned = false;
let entries: Vec<Expr> = self.store.entries.iter().map(|(e, _, _)| *e).collect();
for e in entries {
if self.store.infeasible.is_some() {
break;
}
if self.steps == 0 {
self.store.truncated = true;
break;
}
let n = cx.id(e)?;
if n < lo || !cx.node(n).children().any(|c| reaches(cx, c)) {
continue;
}
self.steps -= 1;
let Some((f, rel)) = self.current(cx, n) else {
return Ok(true);
};
if !self.backward(cx, n, f, rel)? {
return Ok(true);
}
learned |= self.drain(cx)?;
}
Ok(learned)
}
}