#![allow(clippy::should_implement_trait)]
use alloc::boxed::Box;
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Iv {
pub lo: u64,
pub hi: u64,
}
impl Iv {
pub const fn point(v: u64) -> Iv {
Iv { lo: v, hi: v }
}
pub const fn new(lo: u64, hi: u64) -> Iv {
if lo <= hi {
Iv { lo, hi }
} else {
Iv { lo: hi, hi: lo }
}
}
pub const fn full() -> Iv {
Iv {
lo: 0,
hi: u64::MAX,
}
}
pub const fn contains(&self, v: u64) -> bool {
self.lo <= v && v <= self.hi
}
fn add(self, o: Iv) -> Iv {
match (self.lo.checked_add(o.lo), self.hi.checked_add(o.hi)) {
(Some(lo), Some(hi)) => Iv { lo, hi },
_ => Iv::full(), }
}
fn sub(self, o: Iv) -> Iv {
match (self.lo.checked_sub(o.hi), self.hi.checked_sub(o.lo)) {
(Some(lo), Some(hi)) => Iv { lo, hi },
_ => Iv::full(),
}
}
fn mul(self, o: Iv) -> Iv {
match (self.lo.checked_mul(o.lo), self.hi.checked_mul(o.hi)) {
(Some(lo), Some(hi)) => Iv { lo, hi },
_ => Iv::full(),
}
}
fn shl(self, k: u32) -> Iv {
if k >= 64 {
return if self.lo == 0 && self.hi == 0 {
Iv::point(0)
} else {
Iv::full()
};
}
if self.hi <= (u64::MAX >> k) {
Iv {
lo: self.lo << k,
hi: self.hi << k,
}
} else {
Iv::full()
}
}
fn shr(self, k: u32) -> Iv {
if k >= 64 {
return Iv::point(0);
}
Iv {
lo: self.lo >> k,
hi: self.hi >> k,
} }
fn bitand(self, mask: u64) -> Iv {
let hi = if mask < self.hi { mask } else { self.hi };
Iv { lo: 0, hi }
}
fn bitor(self, mask: u64) -> Iv {
let lo = if mask > self.lo { mask } else { self.lo };
Iv { lo, hi: u64::MAX }
}
fn rem(self, m: u64) -> Iv {
if m == 0 {
return Iv::full();
}
if self.hi < m {
self
} else {
Iv { lo: 0, hi: m - 1 }
}
}
}
#[derive(Clone, Debug)]
pub enum Expr {
Var(u32),
Const(u64),
Add(Box<Expr>, Box<Expr>),
Sub(Box<Expr>, Box<Expr>),
Mul(Box<Expr>, Box<Expr>),
Shl(Box<Expr>, u32),
Shr(Box<Expr>, u32),
And(Box<Expr>, u64),
Or(Box<Expr>, u64),
Rem(Box<Expr>, u64),
}
impl Expr {
pub fn var() -> Expr {
Expr::Var(0)
}
pub fn var_at(i: u32) -> Expr {
Expr::Var(i)
}
pub fn c(v: u64) -> Expr {
Expr::Const(v)
}
pub fn add(self, o: Expr) -> Expr {
Expr::Add(Box::new(self), Box::new(o))
}
pub fn sub(self, o: Expr) -> Expr {
Expr::Sub(Box::new(self), Box::new(o))
}
pub fn mul(self, o: Expr) -> Expr {
Expr::Mul(Box::new(self), Box::new(o))
}
pub fn shl(self, k: u32) -> Expr {
Expr::Shl(Box::new(self), k)
}
pub fn shr(self, k: u32) -> Expr {
Expr::Shr(Box::new(self), k)
}
pub fn and(self, m: u64) -> Expr {
Expr::And(Box::new(self), m)
}
pub fn or(self, m: u64) -> Expr {
Expr::Or(Box::new(self), m)
}
pub fn rem(self, m: u64) -> Expr {
Expr::Rem(Box::new(self), m)
}
pub fn subst(&self, next: &[Expr]) -> Expr {
match self {
Expr::Var(i) => next.get(*i as usize).cloned().unwrap_or(Expr::Var(*i)),
Expr::Const(v) => Expr::Const(*v),
Expr::Add(a, b) => Expr::Add(Box::new(a.subst(next)), Box::new(b.subst(next))),
Expr::Sub(a, b) => Expr::Sub(Box::new(a.subst(next)), Box::new(b.subst(next))),
Expr::Mul(a, b) => Expr::Mul(Box::new(a.subst(next)), Box::new(b.subst(next))),
Expr::Shl(a, k) => Expr::Shl(Box::new(a.subst(next)), *k),
Expr::Shr(a, k) => Expr::Shr(Box::new(a.subst(next)), *k),
Expr::And(a, m) => Expr::And(Box::new(a.subst(next)), *m),
Expr::Or(a, m) => Expr::Or(Box::new(a.subst(next)), *m),
Expr::Rem(a, m) => Expr::Rem(Box::new(a.subst(next)), *m),
}
}
fn eval_iv(&self, doms: &[Iv]) -> Iv {
match self {
Expr::Var(i) => doms.get(*i as usize).copied().unwrap_or_else(Iv::full),
Expr::Const(v) => Iv::point(*v),
Expr::Add(a, b) => a.eval_iv(doms).add(b.eval_iv(doms)),
Expr::Sub(a, b) => a.eval_iv(doms).sub(b.eval_iv(doms)),
Expr::Mul(a, b) => a.eval_iv(doms).mul(b.eval_iv(doms)),
Expr::Shl(a, k) => a.eval_iv(doms).shl(*k),
Expr::Shr(a, k) => a.eval_iv(doms).shr(*k),
Expr::And(a, m) => a.eval_iv(doms).bitand(*m),
Expr::Or(a, m) => a.eval_iv(doms).bitor(*m),
Expr::Rem(a, m) => a.eval_iv(doms).rem(*m),
}
}
fn eval_at(&self, xs: &[u64]) -> u64 {
match self {
Expr::Var(i) => xs.get(*i as usize).copied().unwrap_or(0),
Expr::Const(v) => *v,
Expr::Add(a, b) => a.eval_at(xs).wrapping_add(b.eval_at(xs)),
Expr::Sub(a, b) => a.eval_at(xs).wrapping_sub(b.eval_at(xs)),
Expr::Mul(a, b) => a.eval_at(xs).wrapping_mul(b.eval_at(xs)),
Expr::Shl(a, k) => a.eval_at(xs).wrapping_shl(*k),
Expr::Shr(a, k) => a.eval_at(xs).wrapping_shr(*k),
Expr::And(a, m) => a.eval_at(xs) & *m,
Expr::Or(a, m) => a.eval_at(xs) | *m,
Expr::Rem(a, m) => {
if *m == 0 {
0
} else {
a.eval_at(xs) % *m
}
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Tri {
True,
False,
Unknown,
}
impl Tri {
fn negate(self) -> Tri {
match self {
Tri::True => Tri::False,
Tri::False => Tri::True,
Tri::Unknown => Tri::Unknown,
}
}
fn and(self, o: Tri) -> Tri {
match (self, o) {
(Tri::False, _) | (_, Tri::False) => Tri::False,
(Tri::True, Tri::True) => Tri::True,
_ => Tri::Unknown,
}
}
fn or(self, o: Tri) -> Tri {
match (self, o) {
(Tri::True, _) | (_, Tri::True) => Tri::True,
(Tri::False, Tri::False) => Tri::False,
_ => Tri::Unknown,
}
}
}
#[derive(Clone, Debug)]
pub enum Prop {
Le(Expr, Expr),
Lt(Expr, Expr),
Ge(Expr, Expr),
Gt(Expr, Expr),
Eq(Expr, Expr),
Ne(Expr, Expr),
And(Box<Prop>, Box<Prop>),
Or(Box<Prop>, Box<Prop>),
Not(Box<Prop>),
Implies(Box<Prop>, Box<Prop>),
}
impl Prop {
pub fn and(self, o: Prop) -> Prop {
Prop::And(Box::new(self), Box::new(o))
}
pub fn or(self, o: Prop) -> Prop {
Prop::Or(Box::new(self), Box::new(o))
}
pub fn not(self) -> Prop {
Prop::Not(Box::new(self))
}
pub fn implies(self, o: Prop) -> Prop {
Prop::Implies(Box::new(self), Box::new(o))
}
pub fn subst(&self, next: &[Expr]) -> Prop {
match self {
Prop::Le(a, b) => Prop::Le(a.subst(next), b.subst(next)),
Prop::Lt(a, b) => Prop::Lt(a.subst(next), b.subst(next)),
Prop::Ge(a, b) => Prop::Ge(a.subst(next), b.subst(next)),
Prop::Gt(a, b) => Prop::Gt(a.subst(next), b.subst(next)),
Prop::Eq(a, b) => Prop::Eq(a.subst(next), b.subst(next)),
Prop::Ne(a, b) => Prop::Ne(a.subst(next), b.subst(next)),
Prop::And(p, q) => Prop::And(Box::new(p.subst(next)), Box::new(q.subst(next))),
Prop::Or(p, q) => Prop::Or(Box::new(p.subst(next)), Box::new(q.subst(next))),
Prop::Not(p) => Prop::Not(Box::new(p.subst(next))),
Prop::Implies(p, q) => Prop::Implies(Box::new(p.subst(next)), Box::new(q.subst(next))),
}
}
fn eval_iv(&self, doms: &[Iv]) -> Tri {
match self {
Prop::Le(a, b) => cmp_le_full(a, b, doms),
Prop::Lt(a, b) => cmp_lt_full(a, b, doms),
Prop::Ge(a, b) => cmp_le_full(b, a, doms),
Prop::Gt(a, b) => cmp_lt_full(b, a, doms),
Prop::Eq(a, b) => cmp_eq_full(a, b, doms),
Prop::Ne(a, b) => cmp_eq_full(a, b, doms).negate(),
Prop::And(p, q) => p.eval_iv(doms).and(q.eval_iv(doms)),
Prop::Or(p, q) => p.eval_iv(doms).or(q.eval_iv(doms)),
Prop::Not(p) => p.eval_iv(doms).negate(),
Prop::Implies(p, q) => p.eval_iv(doms).negate().or(q.eval_iv(doms)),
}
}
fn eval_at(&self, xs: &[u64]) -> bool {
match self {
Prop::Le(a, b) => a.eval_at(xs) <= b.eval_at(xs),
Prop::Lt(a, b) => a.eval_at(xs) < b.eval_at(xs),
Prop::Ge(a, b) => a.eval_at(xs) >= b.eval_at(xs),
Prop::Gt(a, b) => a.eval_at(xs) > b.eval_at(xs),
Prop::Eq(a, b) => a.eval_at(xs) == b.eval_at(xs),
Prop::Ne(a, b) => a.eval_at(xs) != b.eval_at(xs),
Prop::And(p, q) => p.eval_at(xs) && q.eval_at(xs),
Prop::Or(p, q) => p.eval_at(xs) || q.eval_at(xs),
Prop::Not(p) => !p.eval_at(xs),
Prop::Implies(p, q) => !p.eval_at(xs) || q.eval_at(xs),
}
}
}
struct Affine {
c: i128,
coeffs: Vec<(u32, i128)>,
}
impl Affine {
fn add(mut self, o: Affine) -> Affine {
self.c += o.c;
for (v, k) in o.coeffs {
match self.coeffs.iter_mut().find(|(vv, _)| *vv == v) {
Some(e) => e.1 += k,
None => self.coeffs.push((v, k)),
}
}
self
}
fn scale(mut self, s: i128) -> Affine {
self.c *= s;
for e in self.coeffs.iter_mut() {
e.1 *= s;
}
self
}
fn neg(self) -> Affine {
self.scale(-1)
}
fn bounds(&self, doms: &[Iv]) -> (i128, i128) {
let mut lo = self.c;
let mut hi = self.c;
for &(v, k) in &self.coeffs {
let d = doms.get(v as usize).copied().unwrap_or_else(Iv::full);
let (dl, dh) = (d.lo as i128, d.hi as i128);
if k >= 0 {
lo += k * dl;
hi += k * dh;
} else {
lo += k * dh;
hi += k * dl;
}
}
(lo, hi)
}
}
fn to_affine(e: &Expr) -> Option<Affine> {
match e {
Expr::Var(i) => Some(Affine {
c: 0,
coeffs: alloc::vec![(*i, 1)],
}),
Expr::Const(v) => Some(Affine {
c: *v as i128,
coeffs: Vec::new(),
}),
Expr::Add(a, b) => Some(to_affine(a)?.add(to_affine(b)?)),
Expr::Mul(a, b) => {
let (af, bf) = (to_affine(a)?, to_affine(b)?);
if af.coeffs.is_empty() {
Some(bf.scale(af.c))
} else if bf.coeffs.is_empty() {
Some(af.scale(bf.c))
} else {
None }
}
Expr::Shl(a, k) if *k < 63 => Some(to_affine(a)?.scale(1i128 << k)),
_ => None,
}
}
fn affine_diff(a: &Expr, b: &Expr, doms: &[Iv]) -> Option<(i128, i128)> {
let (af, bf) = (to_affine(a)?, to_affine(b)?);
const MAX: i128 = u64::MAX as i128;
if af.bounds(doms).1 > MAX || bf.bounds(doms).1 > MAX {
return None;
}
Some(af.add(bf.neg()).bounds(doms)) }
fn cmp_le_full(a: &Expr, b: &Expr, doms: &[Iv]) -> Tri {
if let Some((dlo, dhi)) = affine_diff(a, b, doms) {
if dhi <= 0 {
return Tri::True; }
if dlo > 0 {
return Tri::False;
}
}
cmp_le(a.eval_iv(doms), b.eval_iv(doms))
}
fn cmp_lt_full(a: &Expr, b: &Expr, doms: &[Iv]) -> Tri {
if let Some((dlo, dhi)) = affine_diff(a, b, doms) {
if dhi < 0 {
return Tri::True;
}
if dlo >= 0 {
return Tri::False;
}
}
cmp_lt(a.eval_iv(doms), b.eval_iv(doms))
}
fn cmp_eq_full(a: &Expr, b: &Expr, doms: &[Iv]) -> Tri {
if let Some((dlo, dhi)) = affine_diff(a, b, doms) {
if dlo == 0 && dhi == 0 {
return Tri::True; }
if dlo > 0 || dhi < 0 {
return Tri::False; }
}
cmp_eq(a.eval_iv(doms), b.eval_iv(doms))
}
fn cmp_le(a: Iv, b: Iv) -> Tri {
if a.hi <= b.lo {
Tri::True
} else if a.lo > b.hi {
Tri::False
} else {
Tri::Unknown
}
}
fn cmp_lt(a: Iv, b: Iv) -> Tri {
if a.hi < b.lo {
Tri::True
} else if a.lo >= b.hi {
Tri::False
} else {
Tri::Unknown
}
}
fn cmp_eq(a: Iv, b: Iv) -> Tri {
if a.lo == a.hi && b.lo == b.hi && a.lo == b.lo {
Tri::True
} else if a.hi < b.lo || b.hi < a.lo {
Tri::False
} else {
Tri::Unknown
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SymVerdict {
Proven,
Refuted { witness: Vec<u64> },
Unknown,
}
pub fn prove_forall(domain: Iv, prop: &Prop) -> SymVerdict {
prove_forall_n(&[domain], prop)
}
pub fn prove_forall_n(doms: &[Iv], prop: &Prop) -> SymVerdict {
match prop.eval_iv(doms) {
Tri::True => SymVerdict::Proven,
Tri::False => {
let xs: Vec<u64> = doms.iter().map(|d| d.lo).collect();
if !prop.eval_at(&xs) {
SymVerdict::Refuted { witness: xs }
} else {
probe(doms, prop)
}
}
Tri::Unknown => probe(doms, prop),
}
}
pub fn prove_contract(doms: &[Iv], precond: &Prop, postcond: &Prop) -> SymVerdict {
let implication = precond.clone().implies(postcond.clone());
prove_forall_n(doms, &implication)
}
const fn as_var(e: &Expr) -> Option<u32> {
if let Expr::Var(i) = e {
Some(*i)
} else {
None
}
}
const fn as_const(e: &Expr) -> Option<u64> {
if let Expr::Const(v) = e {
Some(*v)
} else {
None
}
}
fn assume_narrow(p: &Prop, doms: &[Iv]) -> Option<Vec<Iv>> {
let mut d = doms.to_vec();
fn le(a: &Expr, b: &Expr, d: &mut [Iv]) -> Option<()> {
if let (Some(vi), Some(c)) = (as_var(a), as_const(b)) {
let iv = d.get_mut(vi as usize)?;
iv.hi = iv.hi.min(c);
if iv.lo > iv.hi {
return None;
}
} else if let (Some(c), Some(vi)) = (as_const(a), as_var(b)) {
let iv = d.get_mut(vi as usize)?;
iv.lo = iv.lo.max(c);
if iv.lo > iv.hi {
return None;
}
}
Some(())
}
fn lt(a: &Expr, b: &Expr, d: &mut [Iv]) -> Option<()> {
if let (Some(vi), Some(c)) = (as_var(a), as_const(b)) {
if c == 0 {
return None; }
let iv = d.get_mut(vi as usize)?;
iv.hi = iv.hi.min(c - 1);
if iv.lo > iv.hi {
return None;
}
} else if let (Some(c), Some(vi)) = (as_const(a), as_var(b)) {
let iv = d.get_mut(vi as usize)?;
iv.lo = iv.lo.max(c.saturating_add(1));
if iv.lo > iv.hi {
return None;
}
}
Some(())
}
fn go(p: &Prop, d: &mut [Iv]) -> Option<()> {
match p {
Prop::And(a, b) => {
go(a, d)?;
go(b, d)
}
Prop::Le(a, b) => le(a, b, d),
Prop::Lt(a, b) => lt(a, b, d),
Prop::Ge(a, b) => le(b, a, d),
Prop::Gt(a, b) => lt(b, a, d),
Prop::Eq(a, b) => {
le(a, b, d)?;
le(b, a, d)
}
_ => Some(()), }
}
go(p, &mut d)?;
Some(d)
}
pub fn prove_inductive(
init_doms: &[Iv],
guard: &Prop,
transition: &[Expr],
invariant: &Prop,
state_doms: &[Iv],
max_splits: u32,
) -> SymVerdict {
let initiation = prove_forall_refine(init_doms, invariant, max_splits);
if initiation != SymVerdict::Proven {
return initiation;
}
let inv_next = invariant.subst(transition);
let assumption = invariant.clone().and(guard.clone());
match assume_narrow(&assumption, state_doms) {
None => SymVerdict::Proven, Some(narrowed) => {
if prove_forall_refine(&narrowed, &inv_next, max_splits) == SymVerdict::Proven {
return SymVerdict::Proven;
}
let vc = assumption.implies(inv_next);
prove_forall_refine(state_doms, &vc, max_splits)
}
}
}
fn probe(doms: &[Iv], prop: &Prop) -> SymVerdict {
let n = doms.len();
if n <= 12 {
for mask in 0u32..(1u32 << n) {
let xs: Vec<u64> = (0..n)
.map(|i| {
if mask & (1 << i) != 0 {
doms[i].hi
} else {
doms[i].lo
}
})
.collect();
if !prop.eval_at(&xs) {
return SymVerdict::Refuted { witness: xs };
}
}
}
for i in 0..n {
let d = doms[i];
let mid = d.lo.wrapping_add(d.hi.wrapping_sub(d.lo) / 2);
for &sv in &[d.lo, d.hi, mid, 0, u64::MAX, 1u64 << 63, 1] {
if !d.contains(sv) {
continue;
}
let mut xs: Vec<u64> = doms.iter().map(|dd| dd.lo).collect();
xs[i] = sv;
if !prop.eval_at(&xs) {
return SymVerdict::Refuted { witness: xs };
}
}
}
SymVerdict::Unknown
}
pub fn prove_forall_refine(doms: &[Iv], prop: &Prop, max_splits: u32) -> SymVerdict {
let mut budget = max_splits;
refine(doms, prop, &mut budget)
}
fn refine(doms: &[Iv], prop: &Prop, budget: &mut u32) -> SymVerdict {
match prop.eval_iv(doms) {
Tri::True => SymVerdict::Proven,
Tri::False => {
let xs: Vec<u64> = doms.iter().map(|d| d.lo).collect();
if !prop.eval_at(&xs) {
SymVerdict::Refuted { witness: xs }
} else {
probe(doms, prop)
}
}
Tri::Unknown => {
let mut wi: Option<usize> = None;
let mut wwidth = 0u64;
for (i, d) in doms.iter().enumerate() {
let w = d.hi - d.lo;
if w > 0 && w >= wwidth {
wwidth = w;
wi = Some(i);
}
}
let i = match wi {
Some(i) => i,
None => return probe(doms, prop),
};
if *budget == 0 {
return probe(doms, prop); }
*budget -= 1;
let d = doms[i];
let mid = d.lo + (d.hi - d.lo) / 2;
let mut left = doms.to_vec();
left[i] = Iv::new(d.lo, mid);
let mut right = doms.to_vec();
right[i] = Iv::new(mid + 1, d.hi);
match refine(&left, prop, budget) {
SymVerdict::Refuted { witness } => SymVerdict::Refuted { witness },
SymVerdict::Unknown => match refine(&right, prop, budget) {
SymVerdict::Refuted { witness } => SymVerdict::Refuted { witness },
_ => SymVerdict::Unknown,
},
SymVerdict::Proven => refine(&right, prop, budget), }
}
}
}