use std::cell::{Cell, RefCell};
use std::collections::{BTreeSet, HashMap};
use std::rc::Rc;
use super::incremental::{Accel, automaton_span};
use super::ir::{AbortMode, Automaton, ClockId, LeafId, LocalVarId, ThreadStateId, Transition};
use super::trace::{CallEvent, Frame, Matches, TailWord, Timeline, TraceError, attempt_word_calls};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PropId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeclId(pub u32);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SeqRef {
pub entry: ThreadStateId,
pub admits_empty: bool,
pub clock: ClockId,
pub end_clock: ClockId,
}
impl SeqRef {
pub fn single(entry: ThreadStateId, admits_empty: bool) -> Self {
SeqRef {
entry,
admits_empty,
clock: ClockId(0),
end_clock: ClockId(0),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Strength {
Strong,
Weak,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CaseArm {
pub labels: Vec<LeafId>,
pub body: PropId,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Prop {
Seq {
seq: SeqRef,
strength: Option<Strength>,
},
Not(PropId),
And(PropId, PropId),
Or(PropId, PropId),
If {
cond: LeafId,
then: PropId,
els: Option<PropId>,
},
Case {
selector: LeafId,
arms: Vec<CaseArm>,
default: Option<PropId>,
},
Implication {
ante: SeqRef,
cons: PropId,
overlapped: bool,
},
Nexttime { p: PropId, n: u32, strong: bool },
Always {
p: PropId,
lo: u32,
hi: Option<u32>,
strong: bool,
},
Eventually {
p: PropId,
lo: u32,
hi: Option<u32>,
strong: bool,
},
Until { p: PropId, q: PropId, strong: bool },
Abort {
cond: LeafId,
p: PropId,
mode: AbortMode,
},
Let {
writes: Vec<(LocalVarId, LeafId)>,
body: PropId,
},
Clocked { clock: ClockId, p: PropId },
Call {
decl: DeclId,
args: Vec<(LocalVarId, LeafId)>,
inits: Vec<(LocalVarId, LeafId)>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PropDecl {
pub name: String,
pub body: PropId,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PropertyIr {
pub automaton: Automaton,
pub props: Vec<Prop>,
pub decls: Vec<PropDecl>,
pub root: PropId,
pub disable: Option<LeafId>,
pub nv_rules: Vec<(PropId, NvRule)>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NvRule {
Implies,
UntilWith,
}
impl PropertyIr {
pub fn nv_rule(&self, id: PropId) -> Option<NvRule> {
self.nv_rules
.iter()
.find(|(p, _)| *p == id)
.map(|(_, r)| *r)
}
pub fn prop(&self, id: PropId) -> &Prop {
&self.props[id.0 as usize]
}
pub fn is_recursive(&self) -> bool {
self.props.iter().any(|p| matches!(p, Prop::Call { .. }))
}
}
pub trait PropEnv {
fn value(&self, leaf: LeafId, tick: u32, frame: &[Option<i64>]) -> Result<i64, TraceError>;
fn case_match(
&self,
selector: LeafId,
label: LeafId,
tick: u32,
frame: &[Option<i64>],
) -> Result<bool, TraceError>;
fn between(&self, _leaf: LeafId, _after: u32) -> Result<bool, TraceError> {
Ok(false)
}
fn timeline(&self) -> Option<&Timeline> {
None
}
fn initial_frame(&self) -> Vec<(LocalVarId, i64)> {
Vec::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Verdict {
Fails,
Holds,
HoldsStrongly,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Disposition {
Fails,
Pending,
Passes,
Disabled,
}
impl From<Verdict> for Disposition {
fn from(v: Verdict) -> Self {
match v {
Verdict::Fails => Disposition::Fails,
Verdict::Holds => Disposition::Pending,
Verdict::HoldsStrongly => Disposition::Passes,
}
}
}
pub struct Evaluator<'a> {
ir: &'a PropertyIr,
env: &'a dyn PropEnv,
implicit_strong: bool,
open_future: bool,
span: u32,
calls: RefCell<HashMap<CallKey, bool>>,
timelines: RefCell<HashMap<u32, Rc<Timeline>>>,
exhaustive: Cell<bool>,
pending_until_match: Cell<bool>,
call_sink: RefCell<Option<BTreeSet<CallEvent>>>,
accel: Option<&'a RefCell<Accel>>,
}
enum Settled {
Verdict(bool),
Resume(u32),
}
type CallKey = (DeclId, u32, TailWord, Frame, u32, ClockId);
impl<'a> Evaluator<'a> {
pub fn new(ir: &'a PropertyIr, env: &'a dyn PropEnv, implicit_strong: bool) -> Self {
Self::new_with_span(ir, env, implicit_strong, automaton_span(&ir.automaton))
}
pub fn new_with_span(
ir: &'a PropertyIr,
env: &'a dyn PropEnv,
implicit_strong: bool,
span: u32,
) -> Self {
Evaluator {
ir,
env,
implicit_strong,
open_future: false,
span,
calls: RefCell::new(HashMap::new()),
timelines: RefCell::new(HashMap::new()),
exhaustive: Cell::new(false),
pending_until_match: Cell::new(false),
call_sink: RefCell::new(None),
accel: None,
}
}
pub fn with_accel(mut self, accel: Option<&'a RefCell<Accel>>) -> Self {
self.accel = accel;
self
}
pub fn open_future(mut self) -> Self {
self.open_future = true;
self
}
fn root_clock(&self) -> ClockId {
self.ir.automaton.entry_clock
}
fn timeline_for(&self, word: &TailWord) -> Result<Option<Rc<Timeline>>, TraceError> {
let Some(full) = self.env.timeline() else {
return Ok(None);
};
if let Some(t) = self.timelines.borrow().get(&word.real) {
return Ok(Some(Rc::clone(t)));
}
let cut = Rc::new(full.truncated(word.real));
self.timelines
.borrow_mut()
.insert(word.real, Rc::clone(&cut));
Ok(Some(cut))
}
fn after(&self, word: &TailWord, dom: ClockId, from: u32, k: u32) -> Result<u32, TraceError> {
Ok(match self.timeline_for(word)? {
Some(t) => t.kth_after(dom, from, k),
None => from + k,
})
}
fn lead_clock(&self, id: PropId, fallback: ClockId) -> ClockId {
match self.ir.prop(id) {
Prop::Seq { seq, .. } => seq.clock,
Prop::Clocked { clock, .. } => *clock,
Prop::Not(p) => self.lead_clock(*p, fallback),
Prop::Let { body, .. } => self.lead_clock(*body, fallback),
Prop::And(a, b) | Prop::Or(a, b) => {
let (x, y) = (self.lead_clock(*a, fallback), self.lead_clock(*b, fallback));
if x == y { x } else { fallback }
}
_ => fallback,
}
}
fn align(&self, word: &TailWord, dom: ClockId, pos: u32) -> Result<u32, TraceError> {
let at = match self.timeline_for(word)? {
Some(t) => t.next_tick(dom, pos),
None => pos,
};
Ok(at.min(word.extent()))
}
pub fn verdict(&self, len: u32, start: u32) -> Result<Verdict, TraceError> {
let weak = self.holds(&TailWord::weak(len), start)?;
if !weak {
return Ok(Verdict::Fails);
}
self.pending_until_match.set(true);
let strong = self.holds(&TailWord::strong(len), start);
self.pending_until_match.set(false);
Ok(if strong? {
Verdict::HoldsStrongly
} else {
Verdict::Holds
})
}
pub fn attempt(&self, len: u32, start: u32) -> Result<Disposition, TraceError> {
let Some(prefix) = self.disabling_prefix(len, start)? else {
return Ok(self.verdict(len, start)?.into());
};
if prefix == start {
return Ok(Disposition::Disabled);
}
Ok(match self.verdict(prefix, start)? {
Verdict::Holds => Disposition::Disabled,
decided => decided.into(),
})
}
pub fn disabling_prefix(&self, len: u32, start: u32) -> Result<Option<u32>, TraceError> {
let Some(cond) = self.ir.disable else {
return Ok(None);
};
let frame = self.initial_frame();
for t in start..len {
if self.env.value(cond, t, &frame)? != 0 {
return Ok(Some(t));
}
if self.env.between(cond, t)? {
return Ok(Some(t + 1));
}
}
Ok(None)
}
fn initial_frame(&self) -> Frame {
let mut frame = vec![None; self.ir.automaton.local_vars.len()];
for (var, value) in self.env.initial_frame() {
frame[var.0 as usize] = Some(value);
}
frame
}
pub fn holds(&self, word: &TailWord, start: u32) -> Result<bool, TraceError> {
self.calls.borrow_mut().clear();
let frame = self.initial_frame();
self.sat(
self.ir.root,
word,
start,
&frame,
0,
self.root_clock(),
false,
)
}
pub fn root_sequence_matches(&self, len: u32, start: u32) -> Result<Matches, TraceError> {
let Prop::Seq { seq, .. } = self.ir.prop(self.ir.root) else {
return Err(TraceError::NotASequence);
};
self.calls.borrow_mut().clear();
let frame = self.initial_frame();
self.matches(*seq, &TailWord::strong(len), start, &frame)
}
pub fn recursion_limit(&self, word: &TailWord) -> u32 {
2 * (self.ir.decls.len() as u32).max(1) * (word.extent() + 2) + 4
}
fn horizon(&self, word: &TailWord, pos: u32) -> u32 {
word.extent().max(pos) + self.span + 1
}
fn matches(
&self,
seq: SeqRef,
word: &TailWord,
pos: u32,
frame: &Frame,
) -> Result<Matches, TraceError> {
if let Some(cell) = self.accel
&& word.mid.is_empty()
&& let Ok(mut accel) = cell.try_borrow_mut()
&& let Some(found) = accel.matches(
&self.ir.automaton,
self.env,
seq.entry,
word.real,
word.tail_top,
pos,
frame,
)
{
return Ok(found);
}
let eval = |leaf: LeafId, tick: u32, f: &[Option<i64>]| self.env.value(leaf, tick, f);
let timeline = self.timeline_for(word)?;
let (found, calls) = attempt_word_calls(
&self.ir.automaton,
seq.entry,
frame,
pos,
word,
self.horizon(word, pos),
&eval,
timeline.as_deref(),
seq.clock,
)?;
if let Some(sink) = self.call_sink.borrow_mut().as_mut() {
sink.extend(calls);
}
Ok(found.into_iter().collect())
}
pub fn scheduled_calls(&self, len: u32, start: u32) -> Result<Vec<CallEvent>, TraceError> {
let eff = self.disabling_prefix(len, start)?.unwrap_or(len);
*self.call_sink.borrow_mut() = Some(BTreeSet::new());
self.exhaustive.set(true);
let ran = self
.holds(&TailWord::weak(eff), start)
.and_then(|_| self.holds(&TailWord::strong(eff), start));
self.exhaustive.set(false);
let sink = self.call_sink.borrow_mut().take().unwrap_or_default();
ran?;
Ok(sink.into_iter().collect())
}
pub fn sequence_exhausted(&self, len: u32, start: u32) -> Result<bool, TraceError> {
let Prop::Seq { seq, .. } = self.ir.prop(self.ir.root) else {
return Err(TraceError::NotASequence);
};
let Some(tl) = self.env.timeline() else {
return Ok(false);
};
let au = &self.ir.automaton;
let first = tl.next_tick(seq.clock, start);
let mut best: HashMap<(u32, ClockId), u32> = HashMap::new();
let mut work = vec![((seq.entry.0, seq.clock), first)];
while let Some(((state, dom), pos)) = work.pop() {
if pos >= len {
return Ok(false);
}
if best.get(&(state, dom)).is_some_and(|&b| b >= pos) {
continue;
}
best.insert((state, dom), pos);
for edge in &au.state(ThreadStateId(state)).edges {
let target = edge.target.0;
match &edge.label {
Transition::Delay { min, max } => {
let Some(max) = max else {
return Ok(false);
};
work.push(((target, dom), tl.kth_after(dom, pos, (*max).max(*min))));
}
Transition::ClockSync { to, strict } => {
let at = if *strict {
tl.kth_after(*to, pos, 1)
} else {
tl.next_tick(*to, pos)
};
work.push(((target, *to), at));
}
Transition::Match { .. } => {}
Transition::Epsilon
| Transition::Abort { .. }
| Transition::LocalVarWrite { .. }
| Transition::LocalVarCopy { .. }
| Transition::FirstMatchBegin { .. }
| Transition::FirstMatchEnd { .. }
| Transition::LocalVarStep { .. }
| Transition::SubroutineCall { .. }
| Transition::EndPoint { .. } => work.push(((target, dom), pos)),
}
}
}
Ok(true)
}
pub fn sequence_calls(&self, len: u32, start: u32) -> Result<Vec<CallEvent>, TraceError> {
let eff = self.disabling_prefix(len, start)?.unwrap_or(len);
*self.call_sink.borrow_mut() = Some(BTreeSet::new());
let ran = self.root_sequence_matches(eff, start);
let sink = self.call_sink.borrow_mut().take().unwrap_or_default();
ran?;
Ok(sink.into_iter().collect())
}
fn boolean(
&self,
leaf: LeafId,
word: &TailWord,
tick: u32,
frame: &Frame,
) -> Result<bool, TraceError> {
match word.letter(tick) {
Some(top) => Ok(top),
None => Ok(self.env.value(leaf, tick, frame)? != 0),
}
}
fn strong_seq(
&self,
seq: SeqRef,
word: &TailWord,
pos: u32,
frame: &Frame,
) -> Result<bool, TraceError> {
if let Some(cell) = self.accel
&& word.mid.is_empty()
&& let Ok(mut accel) = cell.try_borrow_mut()
&& let Some(found) = accel.strong(
&self.ir.automaton,
self.env,
seq.entry,
word.real,
word.tail_top,
pos,
frame,
)
{
return Ok(found);
}
Ok(!self.matches(seq, word, pos, frame)?.is_empty())
}
fn weak_seq(
&self,
seq: SeqRef,
word: &TailWord,
pos: u32,
frame: &Frame,
) -> Result<bool, TraceError> {
if self.pending_until_match.get() && !word.tail_top && word.mid.is_empty() {
return self.strong_seq(seq, word, pos, frame);
}
if let Some(cell) = self.accel
&& word.mid.is_empty()
&& let Ok(mut accel) = cell.try_borrow_mut()
&& let Some(found) = accel.weak(
&self.ir.automaton,
self.env,
seq.entry,
word.real,
word.tail_top,
pos,
frame,
)
{
return Ok(found);
}
let ext = word.extent();
let last = if word.tail_top {
ext.max(pos + 1)
} else {
ext.max(pos) + self.span + 1
};
for n in pos + 1..=last {
let cut = word.cut_then_top(n);
if !self.strong_seq(seq, &cut, pos, frame)? {
return Ok(false);
}
}
Ok(true)
}
fn stand(
&self,
prop: &Prop,
word: &TailWord,
pos: u32,
dom: ClockId,
strict: bool,
) -> Result<(u32, bool), TraceError> {
let through = pass_through(prop);
let pos = if through {
pos.min(word.extent())
} else {
let lead = match prop {
Prop::Seq { seq, .. } => seq.clock,
Prop::Implication { ante, .. } => ante.clock,
Prop::Let { body, .. } => self.lead_clock(*body, dom),
_ => dom,
};
if strict {
self.after(word, lead, pos, 1)?.min(word.extent())
} else {
self.align(word, lead, pos)?
}
};
Ok((pos, strict && through))
}
fn call_frame(
&self,
args: &[(LocalVarId, LeafId)],
inits: &[(LocalVarId, LeafId)],
word: &TailWord,
pos: u32,
frame: &Frame,
) -> Result<Frame, TraceError> {
let mut f: Frame = frame.clone();
for (var, leaf) in args {
let raw = if word.letter(pos).is_some() {
0
} else {
self.env.value(*leaf, pos, frame)?
};
f[var.0 as usize] = Some(self.ir.automaton.local_var(*var).cast(raw));
}
let mut seen: Frame = frame.clone();
for (var, leaf) in inits {
let raw = if word.letter(pos).is_some() {
0
} else {
self.env.value(*leaf, pos, &seen)?
};
let v = Some(self.ir.automaton.local_var(*var).cast(raw));
f[var.0 as usize] = v;
seen[var.0 as usize] = v;
}
Ok(f)
}
#[allow(clippy::too_many_arguments)]
fn sat(
&self,
id: PropId,
word: &TailWord,
pos: u32,
frame: &Frame,
depth: u32,
dom: ClockId,
strict: bool,
) -> Result<bool, TraceError> {
let prop = self.ir.prop(id);
let (pos, strict) = self.stand(prop, word, pos, dom, strict)?;
match prop {
Prop::Seq { seq, strength } => {
let strong = match strength {
Some(Strength::Strong) => true,
Some(Strength::Weak) => false,
None => self.implicit_strong,
};
if strong {
self.strong_seq(*seq, word, pos, frame)
} else {
self.weak_seq(*seq, word, pos, frame)
}
}
Prop::Not(p) => Ok(!self.sat(*p, &word.flip(), pos, frame, depth, dom, strict)?),
Prop::And(a, b) => {
let left = self.sat(*a, word, pos, frame, depth, dom, strict)?;
if !left && !self.exhaustive.get() {
return Ok(false);
}
let right = self.sat(*b, word, pos, frame, depth, dom, strict)?;
Ok(left && right)
}
Prop::Or(a, b) => {
let left = self.sat(*a, word, pos, frame, depth, dom, strict)?;
if left && !self.exhaustive.get() {
return Ok(true);
}
let right = self.sat(*b, word, pos, frame, depth, dom, strict)?;
Ok(left || right)
}
Prop::If { cond, then, els } => {
if self.boolean(*cond, word, pos, frame)? {
self.sat(*then, word, pos, frame, depth, dom, false)
} else if let Some(e) = els {
self.sat(*e, word, pos, frame, depth, dom, false)
} else {
Ok(true)
}
}
Prop::Case {
selector,
arms,
default,
} => {
for arm in arms {
if self.arm_selected(*selector, arm, word, pos, frame)? {
return self.sat(arm.body, word, pos, frame, depth, dom, false);
}
}
match default {
Some(d) => self.sat(*d, word, pos, frame, depth, dom, false),
None => Ok(true),
}
}
Prop::Implication {
ante,
cons,
overlapped,
} => {
let on = ante.end_clock;
let dual;
let aw = if self.open_future {
dual = word.flip();
&dual
} else {
word
};
let mut holds = true;
for (end, f) in self.matches(*ante, aw, pos, frame)? {
if !*overlapped {
let next = self.after(aw, self.lead_clock(*cons, on), end, 1)?;
if aw.letter(next) == Some(false) {
continue;
}
}
if !self.sat(*cons, word, end, &f, depth, dom, !*overlapped)? {
if !self.exhaustive.get() {
return Ok(false);
}
holds = false;
}
}
if !overlapped
&& ante.admits_empty
&& aw.letter(pos) != Some(false)
&& !self.sat(*cons, word, pos, frame, depth, dom, false)?
{
holds = false;
}
Ok(holds)
}
Prop::Nexttime { p, n, .. } => {
let to = self.after(word, dom, pos, *n)?;
self.sat(*p, word, to, frame, depth, dom, false)
}
Prop::Always { p, lo, hi, .. } => {
if let Some(found) =
self.scan_range(*p, word, pos, *lo, *hi, frame, depth, dom, false)
{
return found.map(|failed| !failed);
}
for t in self.positions(word, dom, pos, *lo, *hi)? {
if !self.sat(*p, word, t, frame, depth, dom, false)? {
return Ok(false);
}
}
Ok(true)
}
Prop::Eventually { p, lo, hi, .. } => {
if let Some(found) =
self.scan_range(*p, word, pos, *lo, *hi, frame, depth, dom, true)
{
return found;
}
for t in self.positions(word, dom, pos, *lo, *hi)? {
if self.sat(*p, word, t, frame, depth, dom, false)? {
return Ok(true);
}
}
Ok(false)
}
Prop::Until { p, q, strong } => {
let mut t = pos;
if let Some(decided) = self.skip_settled_until(*p, *q, word, pos, frame, depth, dom)
{
match decided {
Settled::Verdict(v) => return Ok(v),
Settled::Resume(from) => t = from,
}
}
loop {
if self.sat(*q, word, t, frame, depth, dom, false)? {
return Ok(true);
}
if !self.sat(*p, word, t, frame, depth, dom, false)? {
return Ok(false);
}
if t >= word.extent() {
break;
}
t = self.align(word, dom, self.after(word, dom, t, 1)?)?;
}
Ok(!strong)
}
Prop::Clocked { clock, p } => self.sat(*p, word, pos, frame, depth, *clock, strict),
Prop::Abort { cond, p, mode } => {
self.abort(*cond, *p, *mode, word, pos, frame, depth, dom)
}
Prop::Let { writes, body } => {
let mut f = frame.clone();
for (var, leaf) in writes {
let raw = if word.letter(pos).is_some() {
0
} else {
self.env.value(*leaf, pos, &f)?
};
f[var.0 as usize] = Some(self.ir.automaton.local_var(*var).cast(raw));
}
self.sat(*body, word, pos, &f, depth, dom, false)
}
Prop::Call { decl, args, inits } => {
if depth >= self.recursion_limit(word) {
return Ok(word.letter(pos) != Some(false));
}
let f = self.call_frame(args, inits, word, pos, frame)?;
let key = (*decl, pos, word.clone(), f, depth, dom);
if let Some(&known) = self.calls.borrow().get(&key) {
return Ok(known);
}
let body = self.ir.decls[decl.0 as usize].body;
let holds = self.sat(body, word, pos, &key.3, depth + 1, dom, false)?;
self.calls.borrow_mut().insert(key, holds);
Ok(holds)
}
}
}
pub fn nonvacuous(&self, len: u32, start: u32) -> Result<bool, TraceError> {
let end = self.disabling_prefix(len, start)?.unwrap_or(len);
self.calls.borrow_mut().clear();
let frame = self.initial_frame();
self.nv(
self.ir.root,
&TailWord::strong(end),
start,
&frame,
0,
self.root_clock(),
false,
)
}
#[allow(clippy::too_many_arguments)]
fn nv(
&self,
id: PropId,
word: &TailWord,
pos: u32,
frame: &Frame,
depth: u32,
dom: ClockId,
strict: bool,
) -> Result<bool, TraceError> {
let prop = self.ir.prop(id);
let (pos, strict) = self.stand(prop, word, pos, dom, strict)?;
let real = |t: u32| word.letter(t).is_none();
match prop {
Prop::Seq { .. } => Ok(true),
Prop::Not(p) => self.nv(*p, &word.flip(), pos, frame, depth, dom, strict),
Prop::And(a, b) => Ok(self.nv(*a, word, pos, frame, depth, dom, strict)?
|| self.nv(*b, word, pos, frame, depth, dom, strict)?),
Prop::Or(a, b) => {
if self.ir.nv_rule(id) == Some(NvRule::Implies) {
let Prop::Not(p) = self.ir.prop(*a) else {
unreachable!("an `implies` node's left operand is the negation of `p`")
};
return Ok(self.sat(*p, word, pos, frame, depth, dom, strict)?
&& self.nv(*p, word, pos, frame, depth, dom, strict)?
&& self.nv(*b, word, pos, frame, depth, dom, strict)?);
}
Ok(self.nv(*a, word, pos, frame, depth, dom, strict)?
|| self.nv(*b, word, pos, frame, depth, dom, strict)?)
}
Prop::If { cond, then, els } => {
if self.boolean(*cond, word, pos, frame)? {
self.nv(*then, word, pos, frame, depth, dom, false)
} else if let Some(e) = els {
self.nv(*e, word, pos, frame, depth, dom, false)
} else {
Ok(false)
}
}
Prop::Case {
selector,
arms,
default,
} => {
for arm in arms {
if self.arm_selected(*selector, arm, word, pos, frame)? {
return self.nv(arm.body, word, pos, frame, depth, dom, false);
}
}
match default {
Some(d) => self.nv(*d, word, pos, frame, depth, dom, false),
None => Ok(false),
}
}
Prop::Implication {
ante,
cons,
overlapped,
} => {
let on = ante.end_clock;
let dual;
let aw = if self.open_future {
dual = word.flip();
&dual
} else {
word
};
for (end, f) in self.matches(*ante, aw, pos, frame)? {
if !*overlapped {
let next = self.after(aw, self.lead_clock(*cons, on), end, 1)?;
if aw.letter(next) == Some(false) {
continue;
}
}
if self.nv(*cons, word, end, &f, depth, dom, !*overlapped)? {
return Ok(true);
}
}
Ok(!overlapped
&& ante.admits_empty
&& aw.letter(pos) != Some(false)
&& self.nv(*cons, word, pos, frame, depth, dom, false)?)
}
Prop::Nexttime { p, n, .. } => {
let to = self.after(word, dom, pos, *n)?;
Ok(real(to) && self.nv(*p, word, to, frame, depth, dom, false)?)
}
Prop::Always { p, lo, hi, .. } => {
for t in self.positions(word, dom, pos, *lo, *hi)? {
if !real(t) {
break;
}
if self.nv(*p, word, t, frame, depth, dom, false)? {
return Ok(true);
}
if !self.sat(*p, word, t, frame, depth, dom, false)? {
return Ok(false);
}
}
Ok(false)
}
Prop::Eventually { p, lo, hi, .. } => {
for t in self.positions(word, dom, pos, *lo, *hi)? {
if !real(t) {
break;
}
if self.nv(*p, word, t, frame, depth, dom, false)? {
return Ok(true);
}
if self.sat(*p, word, t, frame, depth, dom, false)? {
return Ok(false);
}
}
Ok(false)
}
Prop::Until { p, q, .. } => {
let with = self.ir.nv_rule(id) == Some(NvRule::UntilWith);
let mut t = pos;
while real(t) {
if self.nv(*p, word, t, frame, depth, dom, false)?
|| (!with && self.nv(*q, word, t, frame, depth, dom, false)?)
{
return Ok(true);
}
if self.sat(*q, word, t, frame, depth, dom, false)?
|| !self.sat(*p, word, t, frame, depth, dom, false)?
{
return Ok(false);
}
t = self.align(word, dom, self.after(word, dom, t, 1)?)?;
}
Ok(false)
}
Prop::Clocked { clock, p } => self.nv(*p, word, pos, frame, depth, *clock, strict),
Prop::Abort { cond, p, mode } => {
if !self.nv(*p, word, pos, frame, depth, dom, false)? {
return Ok(false);
}
let sync = mode.is_sync();
let timeline = self.timeline_for(word)?;
for t in pos..word.real {
let looks = !sync || timeline.as_ref().is_none_or(|tl| tl.is_tick(dom, t));
if looks && self.boolean(*cond, word, t, frame)? {
return Ok(false);
}
if !sync && timeline.is_none() && self.env.between(*cond, t)? {
return Ok(false);
}
}
Ok(true)
}
Prop::Let { writes, body } => {
let mut f = frame.clone();
for (var, leaf) in writes {
let raw = if word.letter(pos).is_some() {
0
} else {
self.env.value(*leaf, pos, &f)?
};
f[var.0 as usize] = Some(self.ir.automaton.local_var(*var).cast(raw));
}
self.nv(*body, word, pos, &f, depth, dom, false)
}
Prop::Call { decl, args, inits } => {
if depth >= self.recursion_limit(word) {
return Ok(false);
}
let f = self.call_frame(args, inits, word, pos, frame)?;
let body = self.ir.decls[decl.0 as usize].body;
self.nv(body, word, pos, &f, depth + 1, dom, false)
}
}
}
fn arm_selected(
&self,
selector: LeafId,
arm: &CaseArm,
word: &TailWord,
pos: u32,
frame: &Frame,
) -> Result<bool, TraceError> {
if let Some(top) = word.letter(pos) {
return Ok(top);
}
for &label in &arm.labels {
if self.env.case_match(selector, label, pos, frame)? {
return Ok(true);
}
}
Ok(false)
}
fn positions(
&self,
word: &TailWord,
dom: ClockId,
pos: u32,
lo: u32,
hi: Option<u32>,
) -> Result<Vec<u32>, TraceError> {
let ext = word.extent();
let mut out = Vec::new();
let mut k = lo;
loop {
let at = self.after(word, dom, pos, k)?;
if at >= ext {
out.push(ext);
break;
}
out.push(at);
if hi.is_some_and(|h| k >= h) {
break;
}
k += 1;
}
Ok(out)
}
fn settled(
&self,
id: PropId,
word: &TailWord,
frame: &Frame,
depth: u32,
dom: ClockId,
) -> Option<u32> {
let cell = self.accel?;
if !word.mid.is_empty() || self.env.timeline().is_some() {
return None;
}
let (reach, base) = {
let accel = cell.try_borrow().ok()?;
(accel.final_reach(id)?, accel.base())
};
let limit = word.real.saturating_sub(reach);
loop {
let (next, blocked) = cell.try_borrow_mut().ok()?.final_next(id);
let next_rel = u32::try_from(next - base).ok()?;
if blocked || next_rel >= limit {
return Some(next_rel.min(limit));
}
let verdict = self.sat(id, word, next_rel, frame, depth, dom, false);
let mut accel = cell.try_borrow_mut().ok()?;
accel.final_reach(id)?;
accel.final_record(id, next, verdict.ok());
}
}
#[allow(clippy::too_many_arguments)]
fn scan_range(
&self,
p: PropId,
word: &TailWord,
pos: u32,
lo: u32,
hi: Option<u32>,
frame: &Frame,
depth: u32,
dom: ClockId,
stop_on: bool,
) -> Option<Result<bool, TraceError>> {
let end = self.settled(p, word, frame, depth, dom)?;
let cell = self.accel?;
let first = pos.saturating_add(lo);
let upper = hi.map_or(end, |h| end.min(pos.saturating_add(h).saturating_add(1)));
let mut k = lo;
if first < upper {
let accel = cell.try_borrow().ok()?;
let base = accel.base();
let hit = accel.final_first(p, stop_on, base + u64::from(first));
if hit.is_some_and(|t| t < base + u64::from(upper)) {
return Some(Ok(true));
}
k = upper - pos;
}
let ext = word.extent();
loop {
if hi.is_some_and(|h| k > h) {
return Some(Ok(false));
}
let at = pos + k;
match self.sat(p, word, at.min(ext), frame, depth, dom, false) {
Err(e) => return Some(Err(e)),
Ok(v) if v == stop_on => return Some(Ok(true)),
Ok(_) => {}
}
if at >= ext || hi.is_some_and(|h| k >= h) {
return Some(Ok(false));
}
k += 1;
}
}
#[allow(clippy::too_many_arguments)]
fn skip_settled_until(
&self,
p: PropId,
q: PropId,
word: &TailWord,
pos: u32,
frame: &Frame,
depth: u32,
dom: ClockId,
) -> Option<Settled> {
let ep = self.settled(p, word, frame, depth, dom)?;
let eq = self.settled(q, word, frame, depth, dom)?;
let end = ep.min(eq);
if pos >= end {
return None;
}
let accel = self.accel?.try_borrow().ok()?;
let (from, to) = (accel.base() + u64::from(pos), accel.base() + u64::from(end));
let q_true = accel.final_first(q, true, from).filter(|&t| t < to);
let p_false = accel.final_first(p, false, from).filter(|&t| t < to);
Some(match (q_true, p_false) {
(Some(a), Some(b)) => Settled::Verdict(a <= b),
(Some(_), None) => Settled::Verdict(true),
(None, Some(_)) => Settled::Verdict(false),
(None, None) => Settled::Resume(end),
})
}
#[allow(clippy::too_many_arguments)]
fn abort(
&self,
cond: LeafId,
p: PropId,
mode: AbortMode,
word: &TailWord,
pos: u32,
frame: &Frame,
depth: u32,
dom: ClockId,
) -> Result<bool, TraceError> {
if mode.accepts() {
self.accept_on(cond, mode.is_sync(), p, false, word, pos, frame, depth, dom)
} else {
Ok(!self.accept_on(
cond,
mode.is_sync(),
p,
true,
&word.flip(),
pos,
frame,
depth,
dom,
)?)
}
}
#[allow(clippy::too_many_arguments)]
fn accept_on(
&self,
cond: LeafId,
sync: bool,
p: PropId,
negated: bool,
word: &TailWord,
pos: u32,
frame: &Frame,
depth: u32,
dom: ClockId,
) -> Result<bool, TraceError> {
let inner = |w: &TailWord| -> Result<bool, TraceError> {
if negated {
Ok(!self.sat(p, &w.flip(), pos, frame, depth, dom, false)?)
} else {
self.sat(p, w, pos, frame, depth, dom, false)
}
};
let timeline = self.timeline_for(word)?;
let mut aborted = false;
for t in pos..=word.extent() {
let looks = !sync || timeline.as_ref().is_none_or(|tl| tl.is_tick(dom, t));
if looks && self.boolean(cond, word, t, frame)? {
aborted = true;
if inner(&word.cut_then_top(t))? {
return Ok(true);
}
}
if !sync && timeline.is_none() && t < word.real && self.env.between(cond, t)? {
aborted = true;
if inner(&word.cut_then_top(t + 1))? {
return Ok(true);
}
}
}
if aborted { Ok(false) } else { inner(word) }
}
}
fn pass_through(p: &Prop) -> bool {
match p {
Prop::Not(_) | Prop::And(..) | Prop::Or(..) | Prop::Clocked { .. } => true,
Prop::Seq { .. }
| Prop::If { .. }
| Prop::Case { .. }
| Prop::Implication { .. }
| Prop::Nexttime { .. }
| Prop::Always { .. }
| Prop::Eventually { .. }
| Prop::Until { .. }
| Prop::Abort { .. }
| Prop::Let { .. }
| Prop::Call { .. } => false,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SequenceClass {
Never,
EmptyOnly,
NonEmpty { also_empty: bool },
}
pub fn classify_sequence(
au: &Automaton,
seq: SeqRef,
guard_never: &dyn Fn(LeafId) -> bool,
) -> SequenceClass {
let mut seen = vec![false; au.states.len()];
let mut stack = vec![seq.entry];
seen[seq.entry.0 as usize] = true;
let mut accepting = false;
while let Some(s) = stack.pop() {
for e in &au.state(s).edges {
if e.guard.is_some_and(guard_never) {
continue;
}
if matches!(e.label, Transition::Match { .. }) {
accepting = true;
continue;
}
if !seen[e.target.0 as usize] {
seen[e.target.0 as usize] = true;
stack.push(e.target);
}
}
}
match (accepting, seq.admits_empty) {
(true, also_empty) => SequenceClass::NonEmpty { also_empty },
(false, true) => SequenceClass::EmptyOnly,
(false, false) => SequenceClass::Never,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sva::ir::LeafId;
struct B {
au: Automaton,
props: Vec<Prop>,
decls: Vec<PropDecl>,
}
impl B {
fn new() -> Self {
B {
au: Automaton::empty(),
props: Vec::new(),
decls: Vec::new(),
}
}
fn prop(&mut self, p: Prop) -> PropId {
self.props.push(p);
PropId(self.props.len() as u32 - 1)
}
fn seq(&mut self, leaves: &[u32], delays: &[u32]) -> SeqRef {
let ranges: Vec<(u32, Option<u32>)> = delays.iter().map(|&d| (d, Some(d))).collect();
self.seq_r(leaves, &ranges)
}
fn seq_r(&mut self, leaves: &[u32], delays: &[(u32, Option<u32>)]) -> SeqRef {
assert_eq!(leaves.len(), delays.len() + 1);
let entry = self.au.new_state();
let mut at = entry;
for (i, l) in leaves.iter().enumerate() {
let next = self.au.new_state();
self.au
.add_edge(at, Some(LeafId(*l)), Transition::Epsilon, next);
at = next;
if let Some(&(min, max)) = delays.get(i) {
let next = self.au.new_state();
self.au
.add_edge(at, None, Transition::Delay { min, max }, next);
at = next;
}
}
self.au.add_edge(
at,
None,
Transition::Match {
actions: Vec::new(),
},
at,
);
SeqRef::single(entry, false)
}
fn leaf(&mut self, l: u32) -> SeqRef {
self.seq(&[l], &[])
}
fn b(&mut self, l: u32, strength: Option<Strength>) -> PropId {
let seq = self.leaf(l);
self.prop(Prop::Seq { seq, strength })
}
fn not(&mut self, p: PropId) -> PropId {
self.prop(Prop::Not(p))
}
fn finish(self, root: PropId) -> PropertyIr {
PropertyIr {
automaton: self.au,
props: self.props,
decls: self.decls,
root,
disable: None,
nv_rules: Vec::new(),
}
}
}
struct Rows(Vec<Vec<bool>>);
impl PropEnv for Rows {
fn value(&self, leaf: LeafId, tick: u32, _: &[Option<i64>]) -> Result<i64, TraceError> {
Ok(i64::from(self.0[leaf.0 as usize][tick as usize]))
}
fn case_match(
&self,
selector: LeafId,
label: LeafId,
tick: u32,
f: &[Option<i64>],
) -> Result<bool, TraceError> {
Ok(self.value(selector, tick, f)? == self.value(label, tick, f)?)
}
}
fn rows(r: &[&str]) -> Rows {
Rows(
r.iter()
.map(|s| s.chars().map(|c| c == '1').collect())
.collect(),
)
}
fn verdict(ir: &PropertyIr, env: &Rows, len: u32, start: u32, strong: bool) -> Verdict {
Evaluator::new(ir, env, strong).verdict(len, start).unwrap()
}
#[test]
fn lrm_sequence_property_is_weak_in_assert_and_strong_in_cover() {
let mut b = B::new();
let a = b.leaf(0);
let p3 = b.seq(&[1, 2], &[1]);
let p3p = b.prop(Prop::Seq {
seq: p3,
strength: None,
});
let a1 = b.prop(Prop::Implication {
ante: a,
cons: p3p,
overlapped: true,
});
let ir = b.finish(a1);
let end_after_b = rows(&["1", "1", "0"]);
assert_eq!(
verdict(&ir, &end_after_b, 1, 0, false),
Verdict::Holds,
"weak: `b` holds and there is no next tick"
);
let full = rows(&["11", "10", "01"]);
assert_eq!(verdict(&ir, &full, 2, 0, false), Verdict::HoldsStrongly);
let bad = rows(&["11", "10", "00"]);
assert_eq!(verdict(&ir, &bad, 2, 0, false), Verdict::Fails);
let vac = rows(&["0", "0", "0"]);
assert_eq!(verdict(&ir, &vac, 1, 0, false), Verdict::HoldsStrongly);
let nob = rows(&["1", "0", "1"]);
assert_eq!(verdict(&ir, &nob, 1, 0, false), Verdict::Fails);
}
fn all_traces(leaves: usize, len: usize) -> Vec<Rows> {
let bits = leaves * len;
(0..1u32 << bits)
.map(|m| {
Rows(
(0..leaves)
.map(|l| (0..len).map(|t| m >> (l * len + t) & 1 == 1).collect())
.collect(),
)
})
.collect()
}
fn verdicts(ir: &PropertyIr, leaves: usize, max_len: usize, strong: bool) -> Vec<Verdict> {
let mut out = Vec::new();
for len in 1..=max_len {
for env in all_traces(leaves, len) {
for start in 0..len as u32 {
out.push(verdict(ir, &env, len as u32, start, strong));
}
}
}
out
}
#[test]
fn lrm_not_of_a_weak_sequence_is_the_pitfall_and_strong_is_the_fix() {
let mut b = B::new();
let r = b.seq_r(&[0, 1], &[(1, None)]);
let weak = b.prop(Prop::Seq {
seq: r,
strength: None,
});
let a1 = b.not(weak);
let strong = b.prop(Prop::Seq {
seq: r,
strength: Some(Strength::Strong),
});
let a2 = b.not(strong);
let mut ir1 = b.finish(a1);
let env = rows(&["1", "0"]);
assert_eq!(verdict(&ir1, &env, 1, 0, false), Verdict::Fails);
ir1.root = a2;
assert_eq!(verdict(&ir1, &env, 1, 0, false), Verdict::Holds);
let env = rows(&["10", "01"]);
assert_eq!(verdict(&ir1, &env, 2, 0, false), Verdict::Fails);
}
#[test]
fn weak_unbounded_sequence_is_pending_until_it_matches() {
let mut b = B::new();
let r = b.seq_r(&[0, 1], &[(1, None)]);
let weak = b.prop(Prop::Seq {
seq: r,
strength: None,
});
let ir = b.finish(weak);
for (a, bb) in [("1", "0"), ("10", "00"), ("100", "000")] {
let env = rows(&[a, bb]);
assert_eq!(verdict(&ir, &env, a.len() as u32, 0, false), Verdict::Holds);
}
let env = rows(&["100", "001"]);
assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::HoldsStrongly);
}
#[test]
fn strong_sequence_implies_weak_sequence() {
let mut b = B::new();
let r = b.seq_r(&[0, 1], &[(1, Some(2))]);
let s = b.prop(Prop::Seq {
seq: r,
strength: Some(Strength::Strong),
});
let w = b.prop(Prop::Seq {
seq: r,
strength: Some(Strength::Weak),
});
let mut ir = b.finish(s);
let strong = verdicts(&ir, 2, 4, false);
ir.root = w;
let weak = verdicts(&ir, 2, 4, false);
assert_eq!(strong.len(), weak.len());
for (s, w) in strong.iter().zip(&weak) {
assert!(
*s != Verdict::HoldsStrongly || *w == Verdict::HoldsStrongly,
"strong match on a trace the weak reading does not hold strongly"
);
assert!(*s == Verdict::Fails || *w != Verdict::Fails);
}
}
#[test]
fn classify_sequence_lrm_examples() {
let never = |_: LeafId| false;
let mut au = Automaton::empty();
let e = au.new_state();
let dead = au.new_state();
au.add_edge(e, Some(LeafId(0)), Transition::Epsilon, dead);
let r = SeqRef::single(e, false);
assert_eq!(classify_sequence(&au, r, &never), SequenceClass::Never);
let r = SeqRef::single(e, true);
assert_eq!(classify_sequence(&au, r, &never), SequenceClass::EmptyOnly);
let mut b = B::new();
let mut seq = b.leaf(0);
seq.admits_empty = true;
assert_eq!(
classify_sequence(&b.au, seq, &never),
SequenceClass::NonEmpty { also_empty: true }
);
let mut seq = b.leaf(1);
assert_eq!(
classify_sequence(&b.au, seq, &|l| l == LeafId(1)),
SequenceClass::Never
);
seq.admits_empty = false;
assert_eq!(
classify_sequence(&b.au, seq, &never),
SequenceClass::NonEmpty { also_empty: false }
);
}
#[test]
fn nexttime_duality_and_alignment() {
for n in 0..=2u32 {
for strength in [Some(Strength::Weak), Some(Strength::Strong)] {
let mut b = B::new();
let p = b.b(0, strength);
let snt = b.prop(Prop::Nexttime { p, n, strong: true });
let np = b.not(p);
let nn = b.prop(Prop::Nexttime {
p: np,
n,
strong: false,
});
let dual = b.not(nn);
let mut ir = b.finish(snt);
let a = verdicts(&ir, 1, 4, false);
ir.root = dual;
assert_eq!(a, verdicts(&ir, 1, 4, false), "n = {n}");
}
}
let mut b = B::new();
let p = b.b(0, Some(Strength::Strong));
let nt = b.prop(Prop::Nexttime {
p,
n: 1,
strong: false,
});
let ir = b.finish(nt);
assert_eq!(verdict(&ir, &rows(&["0"]), 1, 0, false), Verdict::Holds);
assert_eq!(
verdict(&ir, &rows(&["01"]), 2, 0, false),
Verdict::HoldsStrongly
);
assert_eq!(verdict(&ir, &rows(&["10"]), 2, 0, false), Verdict::Fails);
}
#[test]
fn always_eventually_until_dualities() {
let mut b = B::new();
let p = b.b(0, Some(Strength::Weak));
let seventually = b.prop(Prop::Eventually {
p,
lo: 0,
hi: None,
strong: true,
});
let np = b.not(p);
let al = b.prop(Prop::Always {
p: np,
lo: 0,
hi: None,
strong: false,
});
let dual = b.not(al);
let always = b.prop(Prop::Always {
p,
lo: 0,
hi: None,
strong: false,
});
let zero = b.b(1, Some(Strength::Weak));
let until = b.prop(Prop::Until {
p,
q: zero,
strong: false,
});
let mut ir = b.finish(seventually);
let a = verdicts(&ir, 2, 3, false);
ir.root = dual;
assert_eq!(a, verdicts(&ir, 2, 3, false));
ir.root = always;
let al = verdicts_with(&ir, 3);
ir.root = until;
let un = verdicts_with(&ir, 3);
assert_eq!(al, un);
}
fn verdicts_with(ir: &PropertyIr, max_len: usize) -> Vec<Verdict> {
let mut out = Vec::new();
for len in 1..=max_len {
for env in all_traces(1, len) {
let env = Rows(vec![env.0[0].clone(), vec![false; len]]);
for start in 0..len as u32 {
out.push(verdict(ir, &env, len as u32, start, false));
}
}
}
out
}
#[test]
fn until_family_truth_table() {
let mut b = B::new();
let p = b.b(0, Some(Strength::Weak));
let q = b.b(1, Some(Strength::Weak));
let u = b.prop(Prop::Until {
p,
q,
strong: false,
});
let su = b.prop(Prop::Until { p, q, strong: true });
let mut ir = b.finish(u);
let env = rows(&["111", "000"]);
assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::Holds);
ir.root = su;
assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::Holds);
let env = rows(&["101", "001"]);
assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::Fails);
let env = rows(&["110", "001"]);
assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::HoldsStrongly);
let env = rows(&["100", "010"]);
assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::HoldsStrongly);
}
#[test]
fn accept_on_cuts_the_word_at_the_abort() {
let mut b = B::new();
let r = b.seq(&[0, 1], &[1]);
let p = b.prop(Prop::Seq {
seq: r,
strength: Some(Strength::Strong),
});
let acc = b.prop(Prop::Abort {
cond: LeafId(2),
p,
mode: AbortMode::SyncAccept,
});
let ir = b.finish(acc);
assert_eq!(
verdict(&ir, &rows(&["10", "01", "00"]), 2, 0, false),
Verdict::HoldsStrongly
);
let env = rows(&["10", "00", "01"]);
assert_eq!(verdict(&ir, &env, 2, 0, false), Verdict::HoldsStrongly);
let env = rows(&["100", "000", "001"]);
assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::Fails);
let rej = {
let mut b = B::new();
let r = b.seq(&[0, 1], &[1]);
let p = b.prop(Prop::Seq {
seq: r,
strength: Some(Strength::Strong),
});
let rej = b.prop(Prop::Abort {
cond: LeafId(2),
p,
mode: AbortMode::SyncReject,
});
b.finish(rej)
};
let env = rows(&["10", "01", "01"]);
assert_eq!(verdict(&rej, &env, 2, 0, false), Verdict::Fails);
let env = rows(&["10", "01", "00"]);
assert_eq!(verdict(&rej, &env, 2, 0, false), Verdict::HoldsStrongly);
}
#[test]
fn abort_wins_a_tie_and_the_outermost_nested_abort_wins() {
let mut b = B::new();
let x = b.b(0, Some(Strength::Strong));
let acc = b.prop(Prop::Abort {
cond: LeafId(1),
p: x,
mode: AbortMode::SyncAccept,
});
let rej = b.prop(Prop::Abort {
cond: LeafId(1),
p: x,
mode: AbortMode::SyncReject,
});
let env = rows(&["0", "1"]);
let mut ir = b.finish(acc);
assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::HoldsStrongly);
ir.root = rej;
assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::Fails);
let env = rows(&["1", "1"]);
assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::Fails);
let mut b = B::new();
let r = b.seq(&[0, 0], &[5]);
let p1 = b.prop(Prop::Seq {
seq: r,
strength: Some(Strength::Strong),
});
let inner = b.prop(Prop::Abort {
cond: LeafId(2),
p: p1,
mode: AbortMode::SyncReject,
});
let outer = b.prop(Prop::Abort {
cond: LeafId(1),
p: inner,
mode: AbortMode::SyncAccept,
});
let ir = b.finish(outer);
let same = rows(&["111", "001", "001"]);
assert_eq!(verdict(&ir, &same, 3, 0, false), Verdict::HoldsStrongly);
let b_first = rows(&["111", "001", "010"]);
assert_eq!(verdict(&ir, &b_first, 3, 0, false), Verdict::Fails);
let a_first = rows(&["111", "010", "001"]);
assert_eq!(verdict(&ir, &a_first, 3, 0, false), Verdict::HoldsStrongly);
}
#[test]
fn async_abort_sees_between_ticks_and_sync_does_not() {
struct Between(Rows, Vec<u32>);
impl PropEnv for Between {
fn value(&self, l: LeafId, t: u32, f: &[Option<i64>]) -> Result<i64, TraceError> {
self.0.value(l, t, f)
}
fn case_match(
&self,
s: LeafId,
l: LeafId,
t: u32,
f: &[Option<i64>],
) -> Result<bool, TraceError> {
self.0.case_match(s, l, t, f)
}
fn between(&self, leaf: LeafId, after: u32) -> Result<bool, TraceError> {
Ok(leaf == LeafId(1) && self.1.contains(&after))
}
}
let build = |mode| {
let mut b = B::new();
let r = b.seq(&[0, 2], &[1]);
let p = b.prop(Prop::Seq {
seq: r,
strength: Some(Strength::Strong),
});
let r = b.prop(Prop::Abort {
cond: LeafId(1),
p,
mode,
});
b.finish(r)
};
let fails = Between(rows(&["10", "00", "00"]), vec![0]);
let ev =
|ir: &PropertyIr, env: &Between| Evaluator::new(ir, env, false).verdict(2, 0).unwrap();
assert_eq!(
ev(&build(AbortMode::Accept), &fails),
Verdict::HoldsStrongly
);
assert_eq!(ev(&build(AbortMode::SyncAccept), &fails), Verdict::Fails);
let holds = Between(rows(&["10", "00", "01"]), vec![0]);
assert_eq!(ev(&build(AbortMode::Reject), &holds), Verdict::Fails);
assert_eq!(
ev(&build(AbortMode::SyncReject), &holds),
Verdict::HoldsStrongly
);
}
#[test]
fn if_and_case_follow_the_condition() {
let mut b = B::new();
let t = b.b(1, Some(Strength::Strong));
let e = b.b(2, Some(Strength::Strong));
let i = b.prop(Prop::If {
cond: LeafId(0),
then: t,
els: Some(e),
});
let ir = b.finish(i);
assert_eq!(
verdict(&ir, &rows(&["1", "1", "0"]), 1, 0, false),
Verdict::HoldsStrongly
);
assert_eq!(
verdict(&ir, &rows(&["0", "1", "0"]), 1, 0, false),
Verdict::Fails
);
let mut b = B::new();
let p1 = b.b(3, Some(Strength::Strong));
let p2 = b.b(4, Some(Strength::Strong));
let p3 = b.b(5, Some(Strength::Strong));
let c = b.prop(Prop::Case {
selector: LeafId(0),
arms: vec![
CaseArm {
labels: vec![LeafId(1)],
body: p1,
},
CaseArm {
labels: vec![LeafId(2)],
body: p2,
},
],
default: Some(p3),
});
let ir = b.finish(c);
let env = rows(&["1", "0", "1", "0", "1", "0"]);
assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::HoldsStrongly);
let env = rows(&["1", "0", "0", "1", "1", "0"]);
assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::Fails);
}
struct Multi {
rows: Rows,
timeline: Timeline,
}
impl PropEnv for Multi {
fn value(&self, leaf: LeafId, tick: u32, f: &[Option<i64>]) -> Result<i64, TraceError> {
self.rows.value(leaf, tick, f)
}
fn case_match(
&self,
selector: LeafId,
label: LeafId,
tick: u32,
f: &[Option<i64>],
) -> Result<bool, TraceError> {
self.rows.case_match(selector, label, tick, f)
}
fn timeline(&self) -> Option<&Timeline> {
Some(&self.timeline)
}
}
fn multi(leaves: &[&str], clocks: &[&str]) -> Multi {
let bits: Vec<Vec<bool>> = clocks
.iter()
.map(|s| s.chars().map(|c| c == '1').collect())
.collect();
let refs: Vec<&[bool]> = bits.iter().map(Vec::as_slice).collect();
Multi {
rows: rows(leaves),
timeline: Timeline::from_rows(&refs),
}
}
fn multi_verdict(ir: &PropertyIr, env: &Multi) -> Verdict {
Evaluator::new(ir, env, false)
.verdict(env.timeline.real(), 0)
.unwrap()
}
impl B {
fn leaf_on(&mut self, l: u32, clock: u32) -> SeqRef {
let mut s = self.leaf(l);
s.clock = ClockId(clock);
s.end_clock = ClockId(clock);
s
}
fn seq_sync(&mut self, l0: u32, l1: u32, to: u32, strict: bool) -> SeqRef {
let entry = self.au.new_state();
let s1 = self.au.new_state();
let s2 = self.au.new_state();
let s3 = self.au.new_state();
self.au
.add_edge(entry, Some(LeafId(l0)), Transition::Epsilon, s1);
self.au.add_edge(
s1,
None,
Transition::ClockSync {
to: ClockId(to),
strict,
},
s2,
);
self.au
.add_edge(s2, Some(LeafId(l1)), Transition::Epsilon, s3);
self.au.add_edge(
s3,
None,
Transition::Match {
actions: Vec::new(),
},
s3,
);
SeqRef {
entry,
admits_empty: false,
clock: ClockId(0),
end_clock: ClockId(to),
}
}
}
#[test]
fn overlapped_implication_awaits_the_nearest_tick_of_the_consequent_clock() {
let mut b = B::new();
let a = b.leaf_on(0, 0);
let cons = b.leaf_on(1, 1);
let c = b.prop(Prop::Seq {
seq: cons,
strength: Some(Strength::Strong),
});
let c = b.prop(Prop::Clocked {
clock: ClockId(1),
p: c,
});
let root = b.prop(Prop::Implication {
ante: a,
cons: c,
overlapped: true,
});
let ir = b.finish(root);
let clocks = ["1010", "0101"];
let env = multi(&["1000", "0100"], &clocks);
assert_eq!(multi_verdict(&ir, &env), Verdict::HoldsStrongly);
let env = multi(&["1000", "0001"], &clocks);
assert_eq!(multi_verdict(&ir, &env), Verdict::Fails);
let env = multi(&["1000", "1010"], &clocks);
assert_eq!(multi_verdict(&ir, &env), Verdict::Fails);
}
#[test]
fn non_overlapped_implication_moves_to_the_strictly_later_consequent_tick() {
let mut b = B::new();
let a = b.leaf_on(0, 0);
let cons = b.leaf_on(1, 1);
let c = b.prop(Prop::Seq {
seq: cons,
strength: Some(Strength::Strong),
});
let c = b.prop(Prop::Clocked {
clock: ClockId(1),
p: c,
});
let root = b.prop(Prop::Implication {
ante: a,
cons: c,
overlapped: false,
});
let ir = b.finish(root);
let clocks = ["1010", "1111"];
let env = multi(&["1000", "0100"], &clocks);
assert_eq!(multi_verdict(&ir, &env), Verdict::HoldsStrongly);
let env = multi(&["1000", "0010"], &clocks);
assert_eq!(
multi_verdict(&ir, &env),
Verdict::Fails,
"the consequent is checked at step 1, the next clk1 tick after the antecedent"
);
let env = multi(&["1000", "1000"], &clocks);
assert_eq!(multi_verdict(&ir, &env), Verdict::Fails);
}
#[test]
fn cross_clock_delay_synchronises_to_the_other_clock() {
let clocks = ["10100", "01011"];
let mut b = B::new();
let s = b.seq_sync(0, 1, 1, true);
let root = b.prop(Prop::Seq {
seq: s,
strength: Some(Strength::Strong),
});
let ir = b.finish(root);
let env = multi(&["10000", "01000"], &clocks);
assert_eq!(multi_verdict(&ir, &env), Verdict::HoldsStrongly);
let env = multi(&["10000", "00010"], &clocks);
assert_eq!(multi_verdict(&ir, &env), Verdict::Fails);
let clocks = ["1010", "1101"];
let mut b = B::new();
let s = b.seq_sync(0, 1, 1, false);
let root = b.prop(Prop::Seq {
seq: s,
strength: Some(Strength::Strong),
});
let ir = b.finish(root);
let env = multi(&["1000", "1000"], &clocks);
assert_eq!(multi_verdict(&ir, &env), Verdict::HoldsStrongly);
}
mod calls;
mod incremental;
mod nonvacuity;
}