Skip to main content

sv_sim_sim/sva/
engine.rs

1//! The live driver of one concurrent assertion (16.14, plan T29): turns the
2//! simulator's per-time-step observations into the trace the reference
3//! [`Evaluator`] runs on, and reports every evaluation attempt the moment it is
4//! decided.
5//!
6//! The engine is scheduler-agnostic. Its owner calls
7//!
8//! * [`Engine::fire`] once per time step, in the Observed region (the engine
9//!   copes with being called more than once per step), and
10//! * [`Engine::end_of_step`] at the end of every time step with a snapshot of
11//!   the signals the assertion reads, which is the Preponed (sampled) value of
12//!   the next step (16.5.1),
13//!
14//! and everything that touches simulator state is behind [`Host`].
15//!
16//! ## The trace
17//!
18//! A *position* is a time step in which a clock of the assertion ticked (with a
19//! single clock that is exactly its ticks, so the evaluator's `Timeline` is not
20//! needed; with several, the positions are the union of the clocks' steps and a
21//! `Timeline` says which clock ticked where). A position holds the sampled
22//! signal row of its step, the current value of the `disable iff` condition
23//! observed in it (16.12: not sampled), and `btw`: whether a current-valued
24//! condition (the disable condition, an asynchronous `accept_on`/`reject_on`
25//! condition) was true at some time step strictly after it and before the next
26//! position -- the evaluator's `PropEnv::between`.
27//!
28//! Evaluation is stateless: every pending attempt is re-run over the whole window
29//! from its start whenever a position is appended, which is exactly how the
30//! reference evaluator is specified (a decided verdict is stable under
31//! extension). The window is cut at the earliest pending attempt's start, so a
32//! property that decides quickly keeps a window of a few positions.
33//!
34//! ## Limitations (documented in the plan's report)
35//!
36//! * A clock edge is the net change of the clock signal over a time step: a
37//!   pulse that begins and ends inside one step is invisible, and a clock
38//!   changed in the Reactive region of a step is seen in the next.
39//! * A `disable iff` that becomes true after an attempt was decided in the same
40//!   time step does not retroactively undo the report.
41//! * A local variable of at most 64 bits is a two-state `i64` (an `x`/`z` bit
42//!   assigned to it reads as 0/1); a wider one is four-state, in 64-bit limbs
43//!   ([`super::wide`]), as is a wide value an instance captures.
44
45use super::incremental::{Accel, automaton_span};
46use super::ir::{AbortMode, CallId, ClockId, LeafId, LocalVarId};
47use super::property::{Disposition, Evaluator, Prop, PropEnv, PropertyIr};
48use super::trace::{Timeline, TraceError};
49use crate::sched::assert_control::Q_ALL;
50use crate::sched::with_sva_leaf_time;
51
52/// What a concurrent assertion statement is (16.14.1-16.14.5).
53#[derive(Clone, Copy, Debug, PartialEq, Eq)]
54pub enum AssertKind {
55    /// `assert property`: failures and successes run the action block.
56    Assert,
57    /// `assume property`: checked as `assert` in simulation (16.14.2).
58    Assume,
59    /// `cover property`: counts and runs the pass statement per successful
60    /// attempt (16.14.3).
61    CoverProperty,
62    /// `cover sequence`: counts and runs the pass statement per match.
63    CoverSequence,
64    /// `restrict property`: never checked in simulation (16.14.5).
65    Restrict,
66    /// The `expect` statement (16.17): a procedural blocking statement that
67    /// starts one evaluation thread on the clocking event strictly after its
68    /// own step and reports its single outcome. Judged as an `assert` is.
69    Expect,
70}
71
72/// What a decided attempt did.
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74pub enum EventKind {
75    /// The attempt succeeded (a vacuous success included).
76    Pass,
77    /// The attempt failed.
78    Fail,
79    /// One match of a `cover sequence` (16.14.3: counted with multiplicity).
80    Match,
81    /// A subroutine call attached to a sequence (16.11) reached an end point:
82    /// [`Event::call`] says which, with the arguments sampled there. Reported
83    /// the moment the end point is seen, which is before the attempt's own
84    /// outcome when that is decided in the same pass.
85    Call,
86}
87
88/// One executed subroutine call (16.11), the payload of an [`EventKind::Call`].
89#[derive(Clone, Debug, PartialEq, Eq)]
90pub struct CallFire {
91    pub call: CallId,
92    /// Per actual argument, the value sampled at the end point: `None` for a
93    /// `ref` argument (bound at call time) and for a constant the kernel that
94    /// runs the call lowers itself ([`CallArg`](super::ir::CallArg)).
95    pub args: Vec<Option<i64>>,
96}
97
98/// One reported attempt outcome.
99#[derive(Clone, Debug, PartialEq, Eq)]
100pub struct Event {
101    pub kind: EventKind,
102    /// The attempt's ordinal (0-based) among the attempts this engine started.
103    pub attempt: u64,
104    /// Whether the success was vacuous (16.14.8: the attempt was not nonvacuous).
105    /// Always `false` for a failure and for a `cover sequence` match.
106    pub vacuous: bool,
107    /// The values the attempt's instance captured when it was queued (16.14.6.1;
108    /// empty for a static assertion), which its action block reads.
109    pub values: Vec<i64>,
110    /// The tag the attempt's instance was activated with
111    /// ([`Engine::activate_tagged`]; 0 for [`Engine::activate`] and for a static
112    /// assertion): which blocked process an `expect` outcome unblocks.
113    pub tag: u64,
114    /// The call of an [`EventKind::Call`]; `None` for every other kind.
115    pub call: Option<CallFire>,
116    /// The `Q_*` action flags ([`crate::sched::assert_control`]) in force when the
117    /// attempt started: an assertion already executing is not affected by a later
118    /// `$assertpassoff`/`$assertfailoff`/... (20.11), so the report filter reads
119    /// these, not the current ones.
120    pub flags: u32,
121    /// With [`EngineConfig::future`], for a verdict: the time step of the tick
122    /// the verdict was reached at, the attempt's last assertion clock tick
123    /// (16.9.4: the verdict is reported at the global clock tick after it, but
124    /// the attempt's interval ends at that tick). `None` otherwise.
125    pub time: Option<u64>,
126}
127
128/// How a stepped attempt's step ended (39.4.2).
129#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130pub enum StepOutcome {
131    /// The attempt progressed and is still pending.
132    Pending,
133    /// The attempt reached its accepting state.
134    Accept,
135    /// The attempt failed to progress.
136    Fail,
137}
138
139/// One step of an attempt whose stepping VPI enabled (39.4.2, 39.5.2): the
140/// progress the newest clock tick made.
141#[derive(Clone, Debug, PartialEq, Eq)]
142pub struct StepRecord {
143    /// The attempt's ordinal.
144    pub attempt: u64,
145    /// How many of the attempt's ticks came before this one (0: its first).
146    pub index: u64,
147    /// The time step of the tick.
148    pub time: u64,
149    /// The leaves read at this tick that held, in read order, each once; for
150    /// a [`StepOutcome::Fail`], the leaf the attempt failed on last.
151    pub matched: Vec<LeafId>,
152    pub outcome: StepOutcome,
153}
154
155/// Running counters (16.14.3 for covers; the attempt counter includes disabled
156/// attempts, the others do not).
157#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
158pub struct Stats {
159    pub attempted: u64,
160    /// Successes, vacuous ones included (at most one per attempt).
161    pub passed: u64,
162    /// Of `passed`, the ones that were vacuous successes (16.14.8: "succeeded
163    /// because of vacuity").
164    pub vacuous: u64,
165    pub failed: u64,
166    pub disabled: u64,
167    pub matched: u64,
168    /// Attempts discarded by a control action (39.5.2 `vpiAssertionKill`,
169    /// `$assertkill`).
170    pub killed: u64,
171}
172
173/// The simulator, as the engine sees it. Every method is infallible: a host
174/// failure is a bug in the codegen, not a runtime condition.
175pub trait Host {
176    /// Whether `clock` ticked this time step, given the previous step's final
177    /// signal row `prev` (an edge is the change from `prev` to now).
178    fn clock_ticked(&self, clock: ClockId, prev: &[u128]) -> bool;
179    /// The value of a Boolean or value leaf over the sampled `row`, with the
180    /// local-variable `frame` of the thread evaluating it (every local variable
181    /// the leaf reads is assigned in it).
182    fn leaf(&self, leaf: LeafId, row: &[u128], frame: &[i64]) -> i64;
183    /// Whether case `label` selects `selector` (12.5 case equality).
184    fn case_pair(&self, selector: LeafId, label: LeafId, row: &[u128], frame: &[i64]) -> bool;
185    /// The CURRENT value of a live leaf (see [`live_leaves`]) as a truth value.
186    /// `sampled` is the position-shaped row of this time step with
187    /// [`EngineConfig::live_history`] (the step's sampled row, then each clock's
188    /// history, as of before this step's ticks), else empty.
189    fn live(&self, leaf: LeafId, sampled: &[u128]) -> bool;
190    /// [`Self::live`] of every leaf of `leaves`, appended to `out` in order. A
191    /// host that pays a fixed cost per query (a signal snapshot) answers all of
192    /// them with one.
193    /// [`Self::live`] of a leaf that also reads captured values, `frame` being
194    /// the local-variable frame holding them (16.14.6.1). A host without such
195    /// leaves never gets asked.
196    fn live_with(&self, leaf: LeafId, sampled: &[u128], frame: &[i64]) -> bool {
197        let _ = frame;
198        self.live(leaf, sampled)
199    }
200    fn live_all(&self, leaves: &[LeafId], sampled: &[u128], out: &mut Vec<bool>) {
201        out.extend(leaves.iter().map(|&l| self.live(l, sampled)));
202    }
203}
204
205/// Everything the engine needs to know about one assertion.
206#[derive(Clone)]
207pub struct EngineConfig {
208    pub ir: PropertyIr,
209    pub kind: AssertKind,
210    /// The number of distinct clocks of the assertion.
211    pub n_clocks: usize,
212    /// Per leaf, the local variables its expression reads (each must be assigned
213    /// when the leaf is evaluated, 16.10).
214    pub leaf_reads: Vec<Vec<LocalVarId>>,
215    /// Per leaf, the local variable a PLAIN assignment match item (`v = e`)
216    /// writes: the kernel is handed its frame slot but the variable need not be
217    /// assigned yet (the item is what assigns it).
218    pub leaf_writes: Vec<Option<LocalVarId>>,
219    /// A procedural concurrent assertion (16.14.6) instead of a static one: its
220    /// attempts start only for instances queued with [`Engine::activate`], not
221    /// at every tick.
222    pub activated: bool,
223    /// The automaton local-variable slot of each value an instance captures
224    /// (`Engine::activate`'s `values`, same order). Assigned when the attempt
225    /// starts, so the leaves that read the captured items find them.
226    pub captured: Vec<LocalVarId>,
227    /// The `disable iff` condition reads captured values AND signals (16.12,
228    /// 16.14.6.1). The condition is not a constant of the attempt, nor a pure
229    /// signal: the engine evaluates it against the live signals once per
230    /// distinct tuple of captured values ([`Host::live_with`]) and each attempt
231    /// reads the record of its own tuple.
232    pub disable_mixed: bool,
233    /// How many previous clock ticks' rows a position carries behind its own
234    /// (0: just the row). The sampled value functions of an assertion read
235    /// them (16.9.3): `$rose`/`$fell`/`$stable`/`$changed` the previous tick's,
236    /// `$past(x, n)` the n-th previous. A position's row handed to
237    /// [`Host::leaf`] is then `(1 + history_depth) * row_len` entries long, the
238    /// tick's own sampled row first, then the row of the previous tick, then the
239    /// one before it, and so on. Before the first tick the previous rows are the
240    /// initial row (16.5.1: the default sampled value is the initial one). Only
241    /// a single-clock assertion has such a history: with several clocks the
242    /// positions are the union of the clocks' ticks, not any one clock's.
243    pub history_depth: usize,
244    /// How many clocks past the assertion's own [`EngineConfig::n_clocks`] the
245    /// host also tells the ticks of (`Host::clock_ticked` is asked for clocks
246    /// `n_clocks..n_clocks + hist_clocks` too). They have a history block in a
247    /// position's row like any clock but start no attempt and make no position:
248    /// they are the clocking events (`$past(x, 1, , @(posedge fclk))`), gated
249    /// events (`$past(x, 2, en)`) and global clock (`$rose_gclk(x)`) of the sampled
250    /// value functions that are not the clock of their own leaf (16.9.3, 16.9.4).
251    pub hist_clocks: usize,
252    /// The property calls a global clocking future sampled value function
253    /// (16.9.4): a position's row carries, after its history, the sampled row of
254    /// the NEXT tick of [`EngineConfig::future_clock`] (the global clock) strictly
255    /// after the position's time step. A position is therefore held back until
256    /// that tick arrives, so every verdict, and the action it runs, comes at the
257    /// global clock tick that follows the attempt's end, as the LRM requires; the
258    /// attempt's interval is unchanged.
259    pub future: bool,
260    /// With [`EngineConfig::future`]: the global clock, one of the assertion's
261    /// clocks or one of its history-only ones.
262    pub future_clock: u32,
263    /// A live leaf (the `disable iff` condition, an asynchronous abort condition)
264    /// reads sampled values: [`Host::live`] is handed the position-shaped row of
265    /// the current time step, the step's sampled row followed by every clock's
266    /// history (16.12, 16.9.3: `$sampled(x)` and `$rose(x, @ev)` in a disable
267    /// condition are meaningful since the condition itself is not sampled).
268    pub live_history: bool,
269    /// A clocking event's `iff` condition calls a sampled value function
270    /// (16.9.3: legal when explicitly clocked): [`Host::clock_ticked`] is handed
271    /// the position-shaped row of the step ([`Engine::tick_row`]), the step's
272    /// sampled row followed by every clock's history as of before this step's
273    /// ticks, instead of the sampled row alone.
274    pub tick_history: bool,
275}
276
277/// The leaves whose CURRENT value (not the sampled one) the engine records at
278/// every time step: the `disable iff` condition and every asynchronous
279/// `accept_on`/`reject_on` condition, in a deterministic order.
280pub fn live_leaves(ir: &PropertyIr) -> Vec<LeafId> {
281    let mut out: Vec<LeafId> = Vec::new();
282    let mut add = |l: LeafId| {
283        if !out.contains(&l) {
284            out.push(l);
285        }
286    };
287    if let Some(d) = ir.disable {
288        add(d);
289    }
290    for p in &ir.props {
291        if let Prop::Abort { cond, mode, .. } = p
292            && matches!(mode, AbortMode::Accept | AbortMode::Reject)
293        {
294            add(*cond);
295        }
296    }
297    out
298}
299
300/// Why an engine could not be built.
301#[derive(Clone, Debug, PartialEq, Eq)]
302pub struct EngineError(pub String);
303
304impl std::fmt::Display for EngineError {
305    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306        f.write_str(&self.0)
307    }
308}
309
310impl std::error::Error for EngineError {}
311
312#[derive(Clone)]
313struct Pos {
314    /// The time step of the position.
315    time: u64,
316    /// The sampled signal row of this step (the previous step's final values).
317    row: Row,
318    /// Which clock ticked at this step.
319    ticked: smallvec::SmallVec<[bool; 4]>,
320    /// The disable condition's current value, OR-ed over this step's firings.
321    disable_cur: bool,
322    /// Per live leaf: true at some step strictly after this position.
323    btw: Vec<bool>,
324    /// With [`EngineConfig::disable_mixed`], per distinct captured tuple (index
325    /// into `Engine::mixed_tuples`; absent = false): the condition's current
326    /// value at this position, and whether it was true at some step after it.
327    mixed_cur: Vec<bool>,
328    mixed_btw: Vec<bool>,
329    /// The value of each frame-independent leaf over `row`, once asked for (empty
330    /// until the first one; then one slot per leaf).
331    vals: std::cell::RefCell<smallvec::SmallVec<[Option<i64>; 4]>>,
332}
333
334/// A position's sampled row: inline for the usual few words, so a position
335/// (made per tick per assertion) costs no heap allocation.
336type Row = smallvec::SmallVec<[u128; 8]>;
337
338#[derive(Clone)]
339struct Attempt {
340    id: u64,
341    /// Absolute index of the start position.
342    start: u64,
343    /// The first absolute position whose cover-sequence matches have not been
344    /// counted for this attempt yet.
345    from: u64,
346    /// The captured values of an activated instance.
347    values: Vec<i64>,
348    /// The activation's tag (see [`Event::tag`]).
349    tag: u64,
350    /// The first absolute position whose subroutine calls (16.11) have not been
351    /// reported yet. A call is a function of the samples up to its own tick, so
352    /// a longer trace only adds calls at the new positions.
353    called_from: u64,
354    /// The action flags at the start (see [`Event::flags`]).
355    flags: u32,
356}
357
358/// An activated instance waiting for the next leading clock event.
359#[derive(Clone)]
360struct Matured {
361    /// The time step it matured in (an `expect` starts strictly after it).
362    at: u64,
363    tag: u64,
364    values: Vec<i64>,
365}
366
367/// One assertion's runtime state. A clone evaluates a step without touching
368/// the original (a trial run, e.g. a checker's solution attempt, 17.7.2).
369#[derive(Clone)]
370pub struct Engine {
371    cfg: EngineConfig,
372    live: Vec<LeafId>,
373    disable_live: Option<usize>,
374    /// With [`EngineConfig::disable_mixed`]: the distinct tuples of captured
375    /// values the instances so far carried, and the condition's value for each
376    /// at the current step.
377    mixed_tuples: Vec<Vec<i64>>,
378    mixed_now: Vec<bool>,
379    /// The previous step's final signal row.
380    last_end: Vec<u128>,
381    /// The ordinals of the attempts the last [`Engine::fire`] found disabled
382    /// (`disable iff`), which report no [`Event`], each with the time step of
383    /// the tick it was found disabled at (global clocking future sampled value
384    /// functions only, 39.4.2.1).
385    disabled_now: Vec<(u64, Option<u64>)>,
386    /// With [`EngineConfig::future`]: the positions (oldest first) with their
387    /// time step and whether the entry clock ticked there, waiting for the next
388    /// tick of the future clock for their next row.
389    held: std::collections::VecDeque<(Pos, u64, bool)>,
390    /// With [`EngineConfig::live_history`]: the position-shaped row live leaves
391    /// were handed in the current step ([`Host::live`]), kept so a second
392    /// `fire` of the step sees the history as of before its ticks too.
393    live_row: Vec<u128>,
394    /// Per clock (history-only ones last), the rows of its last
395    /// `cfg.history_depth` ticks, the latest first (see
396    /// [`EngineConfig::history_depth`]).
397    history: Vec<std::collections::VecDeque<Vec<u128>>>,
398    positions: Vec<Pos>,
399    /// Absolute index of `positions[0]`.
400    base: u64,
401    attempts: Vec<Attempt>,
402    next_attempt: u64,
403    step_time: Option<u64>,
404    /// With [`EngineConfig::future`]: `(attempt, time)` of the attempts started
405    /// since the last [`Engine::take_started`] (the clock tick the attempt
406    /// started at, not the global clock tick it was released at).
407    started: Vec<(u64, u64)>,
408    /// Whether the step `step_time` pushed a position.
409    step_is_tick: bool,
410    /// Whether the entry clock ticked in the step `step_time`.
411    step_starts: bool,
412    /// Instances that matured (16.14.6) and wait for the next leading clock
413    /// event.
414    matured: Vec<Matured>,
415    /// Something changed since the last evaluation (a late activation).
416    dirty: bool,
417    stats: Stats,
418    /// Cover sequence: the longest a match can end past its start.
419    span: Option<u32>,
420    /// Per sequence instance the end-point methods test: its clock and how many
421    /// of its ticks to keep before the earliest pending attempt, so that the
422    /// instance's attempts that end there are still in the window (`None`: every
423    /// position, [`super::ir::Automaton::end_point_lookback`]).
424    lookback: Vec<(ClockId, Option<u32>)>,
425    /// Whether the automaton attaches subroutine calls (16.11).
426    has_calls: bool,
427    /// [`super::incremental::automaton_span`] of the property, which every
428    /// evaluator needs.
429    auto_span: u32,
430    /// Buffers [`Engine::fire`] reuses from one call to the next.
431    scratch_live: Vec<bool>,
432    scratch_ticked: Vec<bool>,
433    /// An emptied attempts buffer [`Self::judge_all`] reuses for its output.
434    spare: std::cell::RefCell<Vec<Attempt>>,
435    /// Whether new attempts start of the engine's own accord (`$asserton`, as
436    /// against `$assertoff`/`$assertkill`, 20.11).
437    checking: bool,
438    /// The action flags new attempts start with (see [`Event::flags`]).
439    flags: u32,
440    /// The incremental sequence evaluator the judging asks first (see
441    /// [`super::incremental`]); `None` for a shape it does not model.
442    accel: Option<std::cell::RefCell<Accel>>,
443    /// VPI tracing (39.4.2); empty unless a VPI library asks for it.
444    tracing: Tracing,
445}
446
447/// What VPI asked the engine to trace, and what it traced (39.4.2).
448#[derive(Clone, Default)]
449struct Tracing {
450    /// The attempts whose steps are reported, with the newest absolute
451    /// position each one's steps were reported up to.
452    stepped: std::collections::HashMap<u64, Option<u64>>,
453    /// Whether a failure reports the leaf it failed on (`failExpr`).
454    fail_leaf: bool,
455    /// Whether every attempt started from now on is stepped
456    /// ([`Engine::set_step_new`]).
457    step_new: bool,
458    steps: Vec<StepRecord>,
459    fail_leaves: Vec<(u64, Option<LeafId>)>,
460}
461
462/// Whether every evaluation also runs the reference evaluator, without the
463/// incremental one, and asserts that it reports the same events, counters and
464/// pending attempts (`SV_SIM_SVA_DIFF=1`/`0`; by default on in debug builds,
465/// which is every test).
466fn differential() -> bool {
467    static MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
468    *MODE.get_or_init(|| match std::env::var("SV_SIM_SVA_DIFF").as_deref() {
469        Ok("0") => false,
470        Ok(_) => true,
471        Err(_) => cfg!(debug_assertions),
472    })
473}
474
475/// Whether the incremental evaluator is used at all (`SV_SIM_SVA_ACCEL=0`
476/// turns it off, leaving the reference evaluator alone).
477fn accel_enabled() -> bool {
478    static MODE: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
479    *MODE.get_or_init(|| std::env::var("SV_SIM_SVA_ACCEL").as_deref() != Ok("0"))
480}
481
482/// What one judging of every attempt yields: the events, the counters, the
483/// attempts still pending, and the attempts a `disable iff` cut short.
484type Judged = Result<(Vec<Event>, Stats, Vec<Attempt>, Vec<(u64, Option<u64>)>), TraceError>;
485
486/// Panics unless the two judgings agree on everything observable.
487fn assert_same_outcome(fast: &Judged, oracle: &Judged) {
488    match (fast, oracle) {
489        (Ok((ev, st, keep, dis)), Ok((ev0, st0, keep0, dis0))) => {
490            let key = |k: &[Attempt]| -> Vec<(u64, u64, u64, u64)> {
491                k.iter()
492                    .map(|a| (a.id, a.start, a.from, a.called_from))
493                    .collect()
494            };
495            assert_eq!(ev, ev0, "incremental SVA evaluation diverged: events");
496            assert_eq!(st, st0, "incremental SVA evaluation diverged: counters");
497            assert_eq!(
498                key(keep),
499                key(keep0),
500                "incremental SVA evaluation diverged: pending attempts"
501            );
502            assert_eq!(
503                dis, dis0,
504                "incremental SVA evaluation diverged: disabled attempts"
505            );
506        }
507        (Err(e), Err(e0)) => assert_eq!(e, e0, "incremental SVA evaluation diverged: error"),
508        (Ok(_), Err(e0)) => {
509            panic!("incremental SVA evaluation succeeded where the reference failed: {e0:?}")
510        }
511        (Err(e), Ok(_)) => {
512            panic!("incremental SVA evaluation failed where the reference succeeded: {e:?}")
513        }
514    }
515}
516
517impl Engine {
518    /// The row this step's tick samples: the previous step's final values.
519    pub fn sampled_row(&self) -> &[u128] {
520        &self.last_end
521    }
522
523    /// The row [`Host::clock_ticked`] reads (see [`EngineConfig::tick_history`]):
524    /// the sampled row, or with a history-reading clocking event the position
525    /// row.
526    pub fn tick_row(&self) -> std::borrow::Cow<'_, [u128]> {
527        if self.cfg.tick_history {
528            std::borrow::Cow::Owned(self.position_row().into_vec())
529        } else {
530            std::borrow::Cow::Borrowed(&self.last_end)
531        }
532    }
533
534    /// Overrides words of the sampled row with their current values: an active
535    /// free checker variable's sampled value is its current one (17.7.2).
536    pub fn set_sampled_words(&mut self, words: &[(usize, u128)]) {
537        for &(i, w) in words {
538            self.last_end[i] = w;
539        }
540    }
541
542    pub fn new(cfg: EngineConfig, initial_row: Vec<u128>) -> Result<Self, EngineError> {
543        let history = (0..(cfg.n_clocks + cfg.hist_clocks).max(1))
544            .map(|_| std::iter::repeat_n(initial_row.clone(), cfg.history_depth).collect())
545            .collect();
546        let has_calls = cfg.ir.automaton.has_calls();
547        if cfg.ir.automaton.end_points_have_calls() {
548            return Err(EngineError(
549                "a subroutine call (16.11) inside a sequence instance tested with `.triggered` or \
550                 `.matched` is not executable: the instance's calls would have to run at its \
551                 own end points, which the end-point status handoff does not carry"
552                    .into(),
553            ));
554        }
555        if has_calls && cfg.kind == AssertKind::Expect {
556            return Err(EngineError(
557                "a subroutine call (16.11) in the property of an `expect` statement is not \
558                 executable: the statement blocks on its single outcome and the call would run \
559                 in the Reactive region of a process that is still waiting for it"
560                    .into(),
561            ));
562        }
563        if has_calls && cfg.ir.props.iter().any(|p| matches!(p, Prop::Abort { .. })) {
564            return Err(EngineError(
565                "a subroutine call (16.11) in a property under `accept_on`/`reject_on` is not \
566                 executable: the abort re-judges the attempt on a word cut at the abort, so \
567                 which end points the calls ran at is not defined by the attempt alone"
568                    .into(),
569            ));
570        }
571        let mut live = live_leaves(&cfg.ir);
572        // A `disable iff` condition over captured values (16.14.6.1) is a
573        // constant of each attempt, judged per attempt, not recorded live.
574        if let Some(d) = cfg.ir.disable
575            && !cfg.leaf_reads[d.0 as usize].is_empty()
576            && !cfg
577                .ir
578                .props
579                .iter()
580                .any(|p| matches!(p, Prop::Abort { cond, .. } if *cond == d))
581        {
582            live.retain(|&l| l != d);
583        }
584        let disable_live = cfg
585            .ir
586            .disable
587            .and_then(|d| live.iter().position(|&l| l == d));
588        let span = cfg.ir.automaton.max_span();
589        let lookback = cfg.ir.automaton.end_point_lookback();
590        let auto_span = automaton_span(&cfg.ir.automaton);
591        let locals_read = cfg.leaf_reads.iter().any(|r| !r.is_empty());
592        let accel = if accel_enabled() && !cfg.disable_mixed {
593            Accel::new(&cfg.ir, cfg.n_clocks, has_calls, locals_read).map(std::cell::RefCell::new)
594        } else {
595            None
596        };
597        Ok(Engine {
598            accel,
599            tracing: Tracing::default(),
600            lookback,
601            cfg,
602            live,
603            disable_live,
604            mixed_tuples: Vec::new(),
605            mixed_now: Vec::new(),
606            last_end: initial_row,
607            disabled_now: Vec::new(),
608            held: Default::default(),
609            live_row: Vec::new(),
610            history,
611            positions: Vec::new(),
612            base: 0,
613            attempts: Vec::new(),
614            next_attempt: 0,
615            step_time: None,
616            started: Vec::new(),
617            step_is_tick: false,
618            step_starts: false,
619            matured: Vec::new(),
620            dirty: false,
621            stats: Stats::default(),
622            span,
623            has_calls,
624            auto_span,
625            scratch_live: Vec::new(),
626            scratch_ticked: Vec::new(),
627            spare: std::cell::RefCell::new(Vec::new()),
628            checking: true,
629            flags: Q_ALL,
630        })
631    }
632
633    /// `$asserton`/`$assertoff` (20.11): whether a static assertion starts new
634    /// attempts. A procedural one starts attempts only for instances that matured
635    /// ([`Engine::activate`]); whether an instance is queued at all is decided
636    /// where it is queued, so the flag does not hold back an instance already
637    /// waiting for its clock.
638    pub fn set_checking(&mut self, on: bool) {
639        self.checking = on;
640    }
641
642    /// The action flags attempts started from now on carry (see
643    /// [`Event::flags`]).
644    pub fn set_action_flags(&mut self, flags: u32) {
645        self.flags = flags;
646    }
647
648    /// `$assertkill` (20.11): aborts every executing attempt -- they report
649    /// nothing and run no action -- and drops the matured instances still waiting
650    /// to start. The statistics keep what they counted.
651    pub fn kill(&mut self) {
652        self.stats.killed += self.attempts.len() as u64;
653        self.attempts.clear();
654        self.matured.clear();
655        self.trim();
656    }
657
658    pub fn kind(&self) -> AssertKind {
659        self.cfg.kind
660    }
661
662    pub fn stats(&self) -> Stats {
663        self.stats
664    }
665
666    /// The live leaves the host must be able to answer [`Host::live`] for.
667    pub fn live_set(&self) -> &[LeafId] {
668        &self.live
669    }
670
671    /// The signal row of the end of the time step that just finished: the
672    /// sampled values of the next one.
673    pub fn end_of_step(&mut self, row: Vec<u128>) {
674        self.last_end = row;
675    }
676
677    /// [`Self::end_of_step`] that has `fill` write the row in place (over zeros),
678    /// which saves the allocation of a row per step.
679    pub fn end_of_step_with(&mut self, fill: impl FnOnce(&mut [u128])) {
680        self.last_end.fill(0);
681        fill(&mut self.last_end);
682    }
683
684    /// A procedural concurrent assertion instance matured in the Observed
685    /// region of time step `now` (16.14.6), with the values it captured when the
686    /// procedure reached it. When the leading clock ticked in this very step its
687    /// attempt begins at once; otherwise the instance waits for the next tick.
688    /// Call it before [`Engine::fire`] of the same step.
689    pub fn activate(&mut self, now: u64, values: Vec<i64>) {
690        self.activate_tagged(now, values, 0);
691    }
692
693    /// [`Engine::activate`] with a `tag` the attempt's [`Event`]s carry back.
694    ///
695    /// An [`AssertKind::Expect`] instance never starts in the step it was
696    /// activated in, whether or not the leading clock ticked there: its first
697    /// evaluation is on the *subsequent* clocking event (16.17).
698    pub fn activate_tagged(&mut self, now: u64, values: Vec<i64>, tag: u64) {
699        let strict = self.cfg.kind == AssertKind::Expect;
700        self.register_tuple(&values);
701        // A held position starts its instances when it is released.
702        if !strict && !self.cfg.future && self.step_time == Some(now) && self.step_starts {
703            self.start_attempt(values, tag);
704            self.dirty = true;
705        } else {
706            self.matured.push(Matured {
707                at: now,
708                tag,
709                values,
710            });
711        }
712    }
713
714    /// With [`EngineConfig::disable_mixed`]: makes `values` a tuple whose
715    /// condition value the engine records from the next [`Engine::fire`] on.
716    fn register_tuple(&mut self, values: &[i64]) {
717        if self.cfg.disable_mixed && !self.mixed_tuples.iter().any(|t| t == values) {
718            self.mixed_tuples.push(values.to_vec());
719            self.dirty = true;
720        }
721    }
722
723    /// The number of captured-value tuples whose `disable iff` value the engine
724    /// records (see [`EngineConfig::disable_mixed`]); bounded by the live
725    /// instances, which a long run's test observes.
726    pub fn live_tuples(&self) -> usize {
727        self.mixed_tuples.len()
728    }
729
730    /// Drops the tuples no instance can still ask about: the ones neither a
731    /// pending attempt nor an instance waiting to start carries (16.14.6.1). A
732    /// later instance with the same tuple registers it afresh, and it starts at
733    /// the newest position, whose record it picks up on the next firing.
734    fn prune_tuples(&mut self) {
735        if self.mixed_tuples.is_empty() {
736            return;
737        }
738        let keep: Vec<bool> = self
739            .mixed_tuples
740            .iter()
741            .map(|t| {
742                self.attempts.iter().any(|a| a.values == *t)
743                    || self.matured.iter().any(|m| m.values == *t)
744            })
745            .collect();
746        if keep.iter().all(|&k| k) {
747            return;
748        }
749        let prune = |v: &mut Vec<bool>| {
750            let mut i = 0;
751            v.retain(|_| {
752                let k = keep.get(i).copied().unwrap_or(true);
753                i += 1;
754                k
755            });
756        };
757        let mut i = 0;
758        self.mixed_tuples.retain(|_| {
759            let k = keep[i];
760            i += 1;
761            k
762        });
763        prune(&mut self.mixed_now);
764        for p in &mut self.positions {
765            prune(&mut p.mixed_cur);
766            prune(&mut p.mixed_btw);
767        }
768        for (p, _, _) in &mut self.held {
769            prune(&mut p.mixed_cur);
770            prune(&mut p.mixed_btw);
771        }
772    }
773
774    /// Starts an attempt at the newest position.
775    fn start_attempt(&mut self, values: Vec<i64>, tag: u64) {
776        self.register_tuple(&values);
777        let start = self.base + self.positions.len() as u64 - 1;
778        self.attempts.push(Attempt {
779            id: self.next_attempt,
780            start,
781            from: start,
782            called_from: start,
783            values,
784            tag,
785            flags: self.flags,
786        });
787        if self.cfg.future {
788            let at = self.positions.last().map_or(0, |p| p.time);
789            self.started.push((self.next_attempt, at));
790        }
791        if self.tracing.step_new {
792            self.tracing.stepped.insert(self.next_attempt, None);
793        }
794        self.next_attempt += 1;
795        self.stats.attempted += 1;
796    }
797
798    /// The Observed-region step at simulation time `now`. Returns the outcomes
799    /// decided by it, in attempt-start order.
800    /// Discards the attempt with ordinal `id` (VPI `vpiAssertionKill`, 39.5.2):
801    /// it reports nothing; the assertion stays enabled. Whether it was pending.
802    /// Turns step reporting for attempt `id` on or off (39.5.2
803    /// `vpiAssertionEnableStep`/`vpiAssertionDisableStep`). Reported steps are
804    /// collected by [`Engine::take_steps`].
805    pub fn set_step(&mut self, id: u64, on: bool) {
806        if on {
807            self.tracing.stepped.entry(id).or_insert(None);
808        } else {
809            self.tracing.stepped.remove(&id);
810        }
811    }
812
813    /// Whether every attempt that starts from now on has its steps reported
814    /// (39.5.2: stepping requested for an attempt start time not reached
815    /// yet). The caller turns it off again after the time step.
816    pub fn set_step_new(&mut self, on: bool) {
817        self.tracing.step_new = on;
818    }
819
820    /// Whether a failure records the leaf it failed on
821    /// ([`Engine::take_fail_leaves`]).
822    pub fn set_fail_leaf(&mut self, on: bool) {
823        self.tracing.fail_leaf = on;
824    }
825
826    /// With [`EngineConfig::future`]: the start times of the attempts started
827    /// since the last call, by attempt ordinal (empty otherwise).
828    pub fn take_started(&mut self) -> Vec<(u64, u64)> {
829        std::mem::take(&mut self.started)
830    }
831
832    /// The steps reported since the last call.
833    pub fn take_steps(&mut self) -> Vec<StepRecord> {
834        std::mem::take(&mut self.tracing.steps)
835    }
836
837    /// The failures reported since the last call with the leaf each failed on
838    /// (`None` when it read none), by attempt ordinal.
839    pub fn take_fail_leaves(&mut self) -> Vec<(u64, Option<LeafId>)> {
840        std::mem::take(&mut self.tracing.fail_leaves)
841    }
842
843    pub fn kill_attempt(&mut self, id: u64) -> bool {
844        let before = self.attempts.len();
845        self.attempts.retain(|a| a.id != id);
846        let killed = self.attempts.len() != before;
847        if killed {
848            self.stats.killed += (before - self.attempts.len()) as u64;
849            self.trim();
850        }
851        killed
852    }
853
854    /// The ordinals of the attempts the last [`Engine::fire`] found disabled.
855    pub fn disabled_attempts(&self) -> &[(u64, Option<u64>)] {
856        &self.disabled_now
857    }
858
859    pub fn fire(&mut self, host: &dyn Host, now: u64) -> Result<Vec<Event>, TraceError> {
860        self.disabled_now.clear();
861        if self.cfg.kind == AssertKind::Restrict {
862            return Ok(Vec::new());
863        }
864        let first = self.step_time != Some(now);
865        self.step_time = Some(now);
866        if first && self.cfg.live_history {
867            // The step's sampled row and the histories as they are BEFORE this
868            // step's ticks: a sampled value function reads strictly prior ticks.
869            self.live_row = self.position_row().into_vec();
870        }
871        let mut live_now = std::mem::take(&mut self.scratch_live);
872        live_now.clear();
873        // The live values are read lazily: only a tick's position (its
874        // `disable_cur`) or an attempt that can still start at or after the
875        // newest position (its `btw` bits) ever consumes them, so a step in
876        // which nothing is pending skips the signal snapshot they cost.
877        let mut live_ready = false;
878        let live_pending =
879            !self.attempts.is_empty() || !self.held.is_empty() || !self.matured.is_empty();
880        let mut pushed = false;
881        let mut live_hit = false;
882        self.mixed_now.clear();
883        if self.cfg.disable_mixed
884            && let Some(d) = self.cfg.ir.disable
885        {
886            // 16.12, 16.14.6.1: the condition over the live signals and each
887            // tuple of captured values.
888            let mut frame = vec![0i64; self.cfg.ir.automaton.local_vars.len()];
889            for t in &self.mixed_tuples {
890                for (slot, v) in self.cfg.captured.iter().zip(t) {
891                    frame[slot.0 as usize] = *v;
892                }
893                self.mixed_now
894                    .push(host.live_with(d, &self.live_row, &frame));
895            }
896        }
897        let mut ticked = std::mem::take(&mut self.scratch_ticked);
898        if first {
899            self.step_is_tick = false;
900            self.step_starts = false;
901            ticked.clear();
902            let tick_row = self.tick_row();
903            ticked.extend(
904                (0..self.cfg.n_clocks + self.cfg.hist_clocks)
905                    .map(|c| host.clock_ticked(ClockId(c as u32), &tick_row)),
906            );
907            drop(tick_row);
908            if ticked.iter().any(|&t| t) {
909                // A position is a tick of one of the assertion's own clocks; the
910                // tick of a history-only clock just records its row.
911                let own = &ticked[..self.cfg.n_clocks];
912                let entry = self.cfg.ir.automaton.entry_clock.0 as usize;
913                let starts = own.get(entry).copied().unwrap_or(false);
914                if !live_ready && !self.live.is_empty() {
915                    host.live_all(&self.live, &self.live_row, &mut live_now);
916                    live_ready = true;
917                }
918                let disable_cur = self.disable_live.is_some_and(|i| live_now[i]);
919                // The position's row, then each clock's previous ticks' rows: a
920                // sampled value function reads the history of its own clock
921                // (16.9.3), which in a multiclock assertion is not the previous
922                // position.
923                let pos = own.iter().any(|&t| t).then(|| Pos {
924                    time: now,
925                    row: self.position_row(),
926                    ticked: own.iter().copied().collect(),
927                    disable_cur,
928                    btw: vec![false; self.live.len()],
929                    mixed_cur: self.mixed_now.clone(),
930                    mixed_btw: Vec::new(),
931                    vals: Default::default(),
932                });
933                if self.cfg.history_depth > 0 {
934                    for (c, clock) in self.history.iter_mut().enumerate() {
935                        if ticked.get(c).copied().unwrap_or(false) {
936                            clock.push_front(self.last_end.clone());
937                            clock.truncate(self.cfg.history_depth);
938                        }
939                    }
940                }
941                if pos.is_none() {
942                    // No position here: the step is one between two positions.
943                    if live_pending && !live_ready && !self.live.is_empty() {
944                        host.live_all(&self.live, &self.live_row, &mut live_now);
945                    }
946                    live_hit |= self.record_between(&live_now);
947                }
948                if self.cfg.future
949                    && ticked
950                        .get(self.cfg.future_clock as usize)
951                        .copied()
952                        .unwrap_or(false)
953                {
954                    // This step's sampled row is the next row of every position
955                    // held since an earlier step.
956                    while let Some((mut prev, at, prev_starts)) = self.held.pop_front() {
957                        prev.row.extend_from_slice(&self.last_end);
958                        self.release(prev, at, prev_starts);
959                        pushed = true;
960                    }
961                }
962                if let Some(pos) = pos {
963                    self.step_is_tick = true;
964                    self.step_starts = starts;
965                    if self.cfg.future {
966                        self.held.push_back((pos, now, starts));
967                    } else {
968                        self.release(pos, now, starts);
969                        pushed = true;
970                    }
971                }
972            } else {
973                if live_pending && !live_ready && !self.live.is_empty() {
974                    host.live_all(&self.live, &self.live_row, &mut live_now);
975                }
976                live_hit |= self.record_between(&live_now);
977            }
978        } else if self.step_is_tick {
979            if live_pending && !live_ready && !self.live.is_empty() {
980                host.live_all(&self.live, &self.live_row, &mut live_now);
981            }
982            if let Some(i) = self.disable_live
983                && live_now.get(i).copied().unwrap_or(false)
984                && let Some(last) = self.newest_mut()
985                && !last.disable_cur
986            {
987                last.disable_cur = true;
988                live_hit = true;
989            }
990            let mixed = std::mem::take(&mut self.mixed_now);
991            if let Some(last) = self.newest_mut() {
992                for (i, &v) in mixed.iter().enumerate() {
993                    if v && !last.mixed_cur.get(i).copied().unwrap_or(false) {
994                        if last.mixed_cur.len() <= i {
995                            last.mixed_cur.resize(i + 1, false);
996                        }
997                        last.mixed_cur[i] = true;
998                        live_hit = true;
999                    }
1000                }
1001            }
1002            self.mixed_now = mixed;
1003        } else {
1004            if live_pending && !live_ready && !self.live.is_empty() {
1005                host.live_all(&self.live, &self.live_row, &mut live_now);
1006            }
1007            live_hit |= self.record_between(&live_now);
1008        }
1009        self.scratch_live = live_now;
1010        self.scratch_ticked = ticked;
1011        if !pushed && !live_hit && !self.dirty {
1012            return Ok(Vec::new());
1013        }
1014        self.dirty = false;
1015        let events = self.evaluate(host)?;
1016        self.trim();
1017        Ok(events)
1018    }
1019
1020    /// Appends the position of time step `at` to the window and starts the
1021    /// attempts that begin there.
1022    fn release(&mut self, pos: Pos, at: u64, starts: bool) {
1023        self.positions.push(pos);
1024        if !starts {
1025            return;
1026        }
1027        if self.cfg.activated {
1028            // An `expect` starts on a tick strictly after its activation; any
1029            // other instance on the first tick at or after it.
1030            let strict = self.cfg.kind == AssertKind::Expect;
1031            let (ready, later): (Vec<Matured>, Vec<Matured>) = std::mem::take(&mut self.matured)
1032                .into_iter()
1033                .partition(|m| if strict { m.at < at } else { m.at <= at });
1034            self.matured = later;
1035            for m in ready {
1036                self.start_attempt(m.values, m.tag);
1037            }
1038        } else if self.checking {
1039            self.start_attempt(Vec::new(), 0);
1040        }
1041    }
1042
1043    /// The newest position: the last held one, or else the window's last.
1044    fn newest_mut(&mut self) -> Option<&mut Pos> {
1045        match self.held.back_mut() {
1046            Some((pos, _, _)) => Some(pos),
1047            None => self.positions.last_mut(),
1048        }
1049    }
1050
1051    /// The row of a position made now: the step's sampled row, then every
1052    /// clock's history (see [`EngineConfig::history_depth`]).
1053    fn position_row(&self) -> Row {
1054        let mut row = Row::from_slice(&self.last_end);
1055        for clock in &self.history {
1056            for earlier in clock {
1057                row.extend_from_slice(earlier);
1058            }
1059        }
1060        row
1061    }
1062
1063    fn record_between(&mut self, live_now: &[bool]) -> bool {
1064        let mixed = std::mem::take(&mut self.mixed_now);
1065        let Some(last) = self.newest_mut() else {
1066            self.mixed_now = mixed;
1067            return false;
1068        };
1069        let mut hit = false;
1070        for (i, &v) in live_now.iter().enumerate() {
1071            if v && !last.btw[i] {
1072                last.btw[i] = true;
1073                hit = true;
1074            }
1075        }
1076        for (i, &v) in mixed.iter().enumerate() {
1077            if v && !last.mixed_btw.get(i).copied().unwrap_or(false) {
1078                if last.mixed_btw.len() <= i {
1079                    last.mixed_btw.resize(i + 1, false);
1080                }
1081                last.mixed_btw[i] = true;
1082                hit = true;
1083            }
1084        }
1085        self.mixed_now = mixed;
1086        hit
1087    }
1088
1089    /// Runs every pending attempt over the window; removes and reports the ones
1090    /// that are decided.
1091    fn evaluate(&mut self, host: &dyn Host) -> Result<Vec<Event>, TraceError> {
1092        if self.attempts.is_empty() {
1093            return Ok(Vec::new());
1094        }
1095        let len = self.positions.len() as u32;
1096        let timeline = if self.cfg.n_clocks > 1 {
1097            let ticks = (0..self.cfg.n_clocks)
1098                .map(|c| {
1099                    self.positions
1100                        .iter()
1101                        .enumerate()
1102                        .filter(|(_, p)| p.ticked[c])
1103                        .map(|(i, _)| i as u32)
1104                        .collect()
1105                })
1106                .collect();
1107            Some(Timeline::new(len, ticks)?)
1108        } else {
1109            None
1110        };
1111        let attempts = std::mem::take(&mut self.attempts);
1112        let oracle_input = (self.accel.is_some() && differential()).then(|| attempts.clone());
1113        if let Some(accel) = &self.accel {
1114            accel.borrow_mut().set_base(self.base);
1115        }
1116        let mut traced = Traced::default();
1117        let ran = self.judge_all(
1118            host,
1119            timeline.as_ref(),
1120            len,
1121            attempts,
1122            self.accel.as_ref(),
1123            Some(&mut traced),
1124        );
1125        if let Some(input) = oracle_input {
1126            let oracle = self.judge_all(host, timeline.as_ref(), len, input, None, None);
1127            assert_same_outcome(&ran, &oracle);
1128        }
1129        let (events, stats, keep, disabled) = ran?;
1130        self.record_traced(traced, len, &events);
1131        self.stats = stats;
1132        self.attempts = keep;
1133        self.disabled_now.extend(disabled);
1134        Ok(events)
1135    }
1136
1137    /// Judges every attempt of `attempts` over the window of `len` positions: the
1138    /// events, the counters and the attempts still pending. `accel` is the
1139    /// incremental sequence evaluator the judging may use (`None`: the reference
1140    /// evaluator alone).
1141    fn judge_all(
1142        &self,
1143        host: &dyn Host,
1144        timeline: Option<&Timeline>,
1145        len: u32,
1146        attempts: Vec<Attempt>,
1147        accel: Option<&std::cell::RefCell<Accel>>,
1148        mut traced: Option<&mut Traced>,
1149    ) -> Judged {
1150        let strong = self.cfg.kind == AssertKind::CoverProperty;
1151        let mut events = Vec::new();
1152        let mut stats = self.stats;
1153        let mut keep = std::mem::take(&mut *self.spare.borrow_mut());
1154        keep.clear();
1155        let mut disabled = Vec::new();
1156        let env_for = |values: &[i64]| WindowEnv {
1157            host,
1158            positions: &self.positions,
1159            disable: self.cfg.ir.disable,
1160            live: &self.live,
1161            leaf_reads: &self.cfg.leaf_reads,
1162            leaf_writes: &self.cfg.leaf_writes,
1163            timeline,
1164            captured: &self.cfg.captured,
1165            values: values.to_vec(),
1166            mixed: self.cfg.disable_mixed.then_some(&self.mixed_tuples[..]),
1167            log: None,
1168        };
1169        let judge = Judge {
1170            kind: self.cfg.kind,
1171            span: self.span,
1172            multiclock: timeline.is_some(),
1173            has_calls: self.has_calls,
1174            base: self.base,
1175            len,
1176            times: if self.cfg.future {
1177                self.positions.iter().map(|p| p.time).collect()
1178            } else {
1179                Vec::new()
1180            },
1181        };
1182        // 39.4.2: an attempt VPI traces is judged on its own logging
1183        // environment by the reference evaluator, whose reads are its steps.
1184        let wants_trace =
1185            |att: &Attempt| self.tracing.fail_leaf || self.tracing.stepped.contains_key(&att.id);
1186        let (mut attempts, traced_atts): (Vec<Attempt>, Vec<Attempt>) = match traced.as_ref() {
1187            Some(_) if self.tracing.fail_leaf || !self.tracing.stepped.is_empty() => {
1188                attempts.into_iter().partition(|a| !wants_trace(a))
1189            }
1190            _ => (attempts, Vec::new()),
1191        };
1192        for att in traced_atts {
1193            let mut env = env_for(&att.values);
1194            env.log = Some(std::cell::RefCell::new(Vec::new()));
1195            let (id, start) = (att.id, att.start);
1196            {
1197                let ev = Evaluator::new_with_span(&self.cfg.ir, &env, strong, self.auto_span)
1198                    .open_future();
1199                judge.run(&ev, att, &mut events, &mut stats, &mut keep, &mut disabled)?;
1200            }
1201            if let (Some(t), Some(log)) = (traced.as_deref_mut(), env.log.take()) {
1202                t.logs.push((id, start, log.into_inner()));
1203            }
1204        }
1205        if self.cfg.captured.is_empty() {
1206            // Every attempt reads the same environment.
1207            let env = env_for(&[]);
1208            let ev = Evaluator::new_with_span(&self.cfg.ir, &env, strong, self.auto_span)
1209                .open_future()
1210                .with_accel(accel);
1211            for att in attempts.drain(..) {
1212                judge.run(&ev, att, &mut events, &mut stats, &mut keep, &mut disabled)?;
1213            }
1214        } else {
1215            // Each attempt starts with its own captured values.
1216            for att in attempts.drain(..) {
1217                let env = env_for(&att.values);
1218                let ev = Evaluator::new_with_span(&self.cfg.ir, &env, strong, self.auto_span)
1219                    .open_future()
1220                    .with_accel(accel);
1221                judge.run(&ev, att, &mut events, &mut stats, &mut keep, &mut disabled)?;
1222            }
1223        }
1224        // The emptied input buffer is the next judging's output buffer.
1225        *self.spare.borrow_mut() = attempts;
1226        Ok((events, stats, keep, disabled))
1227    }
1228
1229    /// Drops the positions no pending attempt needs. An activated engine keeps
1230    /// the position of a tick step it is in: an instance can be activated after
1231    /// this step's [`Engine::fire`] -- a checker procedure runs in the Reactive
1232    /// region, after the Observed region this step's first `fire` ran in
1233    /// (17.7.3) -- and then starts its attempt at that position (16.14.6).
1234    fn trim(&mut self) {
1235        self.prune_tuples();
1236        let newest = if self.cfg.activated && self.step_starts && !self.positions.is_empty() {
1237            self.base + self.positions.len() as u64 - 1
1238        } else {
1239            self.base + self.positions.len() as u64
1240        };
1241        let keep_from = self
1242            .attempts
1243            .first()
1244            .map_or(newest, |a| a.start.min(newest));
1245        let mut keep_from = keep_from;
1246        for &(clock, span) in &self.lookback {
1247            let Some(n) = span else {
1248                // An unbounded instance: its every past start matters.
1249                keep_from = self.base;
1250                break;
1251            };
1252            let mut need = n + 1;
1253            let mut p = keep_from;
1254            while p > self.base && need > 0 {
1255                p -= 1;
1256                if self.positions[(p - self.base) as usize]
1257                    .ticked
1258                    .get(clock.0 as usize)
1259                    .copied()
1260                    .unwrap_or(true)
1261                {
1262                    need -= 1;
1263                }
1264            }
1265            keep_from = keep_from.min(p);
1266        }
1267        let drop_n = (keep_from - self.base) as usize;
1268        if drop_n == 0 {
1269            return;
1270        }
1271        self.positions.drain(..drop_n);
1272        self.base = keep_from;
1273        let base = self.base;
1274        if let Some(accel) = &self.accel {
1275            accel.borrow_mut().evict_before(base);
1276        }
1277    }
1278}
1279
1280/// The leaf reads of the attempts judged with tracing in one pass: per
1281/// attempt, `(tick, leaf, value)` in read order.
1282#[derive(Default)]
1283struct Traced {
1284    /// `(attempt, start position, reads)`.
1285    logs: Vec<(u64, u64, LeafLog)>,
1286}
1287
1288/// A traced attempt's leaf reads: `(tick, leaf, value)` in read order.
1289type LeafLog = Vec<(u32, LeafId, i64)>;
1290
1291impl Engine {
1292    /// Turns one pass's leaf reads into step records and failure leaves.
1293    fn record_traced(&mut self, traced: Traced, len: u32, events: &[Event]) {
1294        if len == 0 {
1295            return;
1296        }
1297        let newest = len - 1;
1298        let abs_newest = self.base + u64::from(newest);
1299        for (id, start, log) in traced.logs {
1300            let failed = events
1301                .iter()
1302                .any(|e| e.attempt == id && e.kind == EventKind::Fail);
1303            let passed = events
1304                .iter()
1305                .any(|e| e.attempt == id && e.kind == EventKind::Pass);
1306            // The leaf the failure was decided on: the last one read false at
1307            // the newest tick, else the last one read at all.
1308            let fail_leaf = log
1309                .iter()
1310                .rev()
1311                .find(|(t, _, v)| *t == newest && *v == 0)
1312                .or_else(|| log.last())
1313                .map(|&(_, l, _)| l);
1314            if failed && self.tracing.fail_leaf {
1315                self.tracing.fail_leaves.push((id, fail_leaf));
1316            }
1317            let Some(reported) = self.tracing.stepped.get_mut(&id) else {
1318                continue;
1319            };
1320            // One step per tick, however often the step is judged; an attempt
1321            // whose stepping was enabled after it started (in its first time
1322            // step) catches up from its start.
1323            let first = reported.map_or(start, |r| r + 1);
1324            if first > abs_newest {
1325                continue;
1326            }
1327            *reported = Some(abs_newest);
1328            let outcome = if failed {
1329                StepOutcome::Fail
1330            } else if passed {
1331                StepOutcome::Accept
1332            } else {
1333                StepOutcome::Pending
1334            };
1335            for abs in first.max(self.base)..=abs_newest {
1336                let tick = (abs - self.base) as u32;
1337                let last = abs == abs_newest;
1338                let mut matched: Vec<LeafId> = Vec::new();
1339                for &(t, l, v) in &log {
1340                    if t == tick && v != 0 && !matched.contains(&l) {
1341                        matched.push(l);
1342                    }
1343                }
1344                let outcome = if last { outcome } else { StepOutcome::Pending };
1345                if outcome == StepOutcome::Fail
1346                    && let Some(l) = fail_leaf
1347                {
1348                    matched.retain(|&m| m != l);
1349                    matched.push(l);
1350                }
1351                self.tracing.steps.push(StepRecord {
1352                    attempt: id,
1353                    index: abs - start,
1354                    time: self.positions[tick as usize].time,
1355                    matched,
1356                    outcome,
1357                });
1358            }
1359            if outcome != StepOutcome::Pending {
1360                self.tracing.stepped.remove(&id);
1361            }
1362        }
1363    }
1364}
1365
1366/// What one evaluation pass needs to judge an attempt.
1367struct Judge {
1368    kind: AssertKind,
1369    span: Option<u32>,
1370    /// The assertion has several clocks (16.13): the window is a [`Timeline`].
1371    multiclock: bool,
1372    has_calls: bool,
1373    base: u64,
1374    len: u32,
1375    /// With [`EngineConfig::future`]: the time step of each position of the
1376    /// window, to date a verdict (see [`Event::time`]); empty otherwise.
1377    times: Vec<u64>,
1378}
1379
1380impl Judge {
1381    /// With global clocking future sampled value functions: the time step of
1382    /// the first tick of the window the attempt starting at `rel` was decided
1383    /// at (16.9.4), found as the shortest prefix of the window that already
1384    /// decides it. `None` without them, or when only the whole window does.
1385    fn verdict_time(&self, ev: &Evaluator<'_>, rel: u32) -> Result<Option<u64>, TraceError> {
1386        if self.times.is_empty() {
1387            return Ok(None);
1388        }
1389        for l in rel + 1..self.len {
1390            if !matches!(ev.attempt(l, rel)?, Disposition::Pending) {
1391                return Ok(self.times.get(l as usize - 1).copied());
1392            }
1393        }
1394        Ok(self.times.get(self.len as usize - 1).copied())
1395    }
1396
1397    /// With global clocking future sampled value functions: the time step of
1398    /// window tick `t` (the last tick when `t` is past the window), else `None`.
1399    fn tick_time(&self, t: u32) -> Option<u64> {
1400        self.times
1401            .get(t as usize)
1402            .or_else(|| self.times.last())
1403            .copied()
1404    }
1405
1406    /// Judges `att` on the window: reports it, drops it, or keeps it pending.
1407    fn run(
1408        &self,
1409        ev: &Evaluator<'_>,
1410        mut att: Attempt,
1411        events: &mut Vec<Event>,
1412        stats: &mut Stats,
1413        keep: &mut Vec<Attempt>,
1414        disabled: &mut Vec<(u64, Option<u64>)>,
1415    ) -> Result<(), TraceError> {
1416        let (len, kind) = (self.len, self.kind);
1417        let rel = (att.start - self.base) as u32;
1418        let event = |att: &Attempt, kind: EventKind| Event {
1419            kind,
1420            attempt: att.id,
1421            vacuous: false,
1422            values: att.values.clone(),
1423            tag: att.tag,
1424            call: None,
1425            flags: att.flags,
1426            time: None,
1427        };
1428        // 16.11: the calls attached to the sequences the attempt has run are
1429        // executed at their end points, ahead of the attempt's own report.
1430        if self.has_calls {
1431            let calls = match kind {
1432                AssertKind::CoverSequence => ev.sequence_calls(len, rel)?,
1433                AssertKind::Assert
1434                | AssertKind::Assume
1435                | AssertKind::CoverProperty
1436                | AssertKind::Expect => ev.scheduled_calls(len, rel)?,
1437                AssertKind::Restrict => unreachable!("a restrict property is never fired"),
1438            };
1439            for c in calls {
1440                if self.base + u64::from(c.tick) >= att.called_from {
1441                    events.push(Event {
1442                        call: Some(CallFire {
1443                            call: c.call,
1444                            args: c.args,
1445                        }),
1446                        ..event(&att, EventKind::Call)
1447                    });
1448                }
1449            }
1450            att.called_from = self.base + u64::from(len);
1451        }
1452        match kind {
1453            AssertKind::Assert
1454            | AssertKind::Assume
1455            | AssertKind::CoverProperty
1456            | AssertKind::Expect => {
1457                let report = kind != AssertKind::CoverProperty;
1458                match ev.attempt(len, rel)? {
1459                    Disposition::Pending => keep.push(att),
1460                    Disposition::Passes => {
1461                        // 16.14.8: a success that is not nonvacuous is a vacuous one.
1462                        let vacuous = !ev.nonvacuous(len, rel)?;
1463                        stats.passed += 1;
1464                        stats.vacuous += u64::from(vacuous);
1465                        events.push(Event {
1466                            vacuous,
1467                            time: self.verdict_time(ev, rel)?,
1468                            ..event(&att, EventKind::Pass)
1469                        });
1470                    }
1471                    Disposition::Fails => {
1472                        stats.failed += 1;
1473                        if report {
1474                            events.push(Event {
1475                                time: self.verdict_time(ev, rel)?,
1476                                ..event(&att, EventKind::Fail)
1477                            });
1478                        }
1479                    }
1480                    Disposition::Disabled => {
1481                        stats.disabled += 1;
1482                        let at = ev.disabling_prefix(len, rel)?;
1483                        disabled.push((att.id, at.and_then(|p| self.tick_time(p))));
1484                    }
1485                }
1486            }
1487            AssertKind::CoverSequence => {
1488                let disabled_at = ev.disabling_prefix(len, rel)?;
1489                let from = (att.from - self.base) as u32;
1490                for (end, _) in ev.root_sequence_matches(len, rel)? {
1491                    if end >= from && disabled_at.is_none_or(|p| end < p) {
1492                        stats.matched += 1;
1493                        events.push(Event {
1494                            time: self.tick_time(end),
1495                            ..event(&att, EventKind::Match)
1496                        });
1497                    }
1498                }
1499                att.from = self.base + u64::from(len);
1500                if let Some(p) = disabled_at {
1501                    stats.disabled += 1;
1502                    disabled.push((att.id, self.tick_time(p)));
1503                } else if self.multiclock {
1504                    // 16.13.1: positions are the union of the clocks' ticks, so the
1505                    // tick-count span says nothing; ask the timeline.
1506                    if !ev.sequence_exhausted(len, rel)? {
1507                        keep.push(att);
1508                    }
1509                } else if self.span.is_none_or(|s| len - 1 < rel.saturating_add(s)) {
1510                    keep.push(att);
1511                }
1512            }
1513            AssertKind::Restrict => unreachable!("a restrict property is never fired"),
1514        }
1515        Ok(())
1516    }
1517}
1518
1519/// The window as a [`PropEnv`].
1520struct WindowEnv<'a> {
1521    host: &'a dyn Host,
1522    positions: &'a [Pos],
1523    disable: Option<LeafId>,
1524    live: &'a [LeafId],
1525    leaf_reads: &'a [Vec<LocalVarId>],
1526    leaf_writes: &'a [Option<LocalVarId>],
1527    timeline: Option<&'a Timeline>,
1528    /// The local-variable slots an instance's captured values are assigned to,
1529    /// and the values of the attempt being judged.
1530    captured: &'a [LocalVarId],
1531    values: Vec<i64>,
1532    /// With [`EngineConfig::disable_mixed`]: the tuples the condition is
1533    /// recorded for; the attempt reads the record of its own `values`.
1534    mixed: Option<&'a [Vec<i64>]>,
1535    /// When the attempt is traced (39.4.2): every leaf read, `(tick, leaf,
1536    /// value)`, in order.
1537    log: Option<std::cell::RefCell<Vec<(u32, LeafId, i64)>>>,
1538}
1539
1540impl WindowEnv<'_> {
1541    fn note(&self, tick: u32, leaf: LeafId, v: i64) {
1542        if let Some(log) = &self.log {
1543            log.borrow_mut().push((tick, leaf, v));
1544        }
1545    }
1546
1547    /// `frame` as the host's two-state frame, after checking that every local
1548    /// variable the leaves read is assigned.
1549    fn host_frame(&self, leaves: &[LeafId], frame: &[Option<i64>]) -> Result<Vec<i64>, TraceError> {
1550        for l in leaves {
1551            for v in &self.leaf_reads[l.0 as usize] {
1552                if frame.get(v.0 as usize).copied().flatten().is_none() {
1553                    return Err(TraceError::UnassignedRead(*v));
1554                }
1555            }
1556        }
1557        Ok(frame.iter().map(|v| v.unwrap_or(0)).collect())
1558    }
1559
1560    /// The record index of this attempt's captured tuple, with a mixed
1561    /// `disable iff` condition (see [`EngineConfig::disable_mixed`]).
1562    fn mixed_index(&self) -> Option<usize> {
1563        self.mixed?.iter().position(|t| *t == self.values)
1564    }
1565
1566    fn reads_locals(&self, leaf: LeafId) -> bool {
1567        !self.leaf_reads[leaf.0 as usize].is_empty() || self.leaf_writes[leaf.0 as usize].is_some()
1568    }
1569}
1570
1571impl WindowEnv<'_> {
1572    fn value_of(&self, leaf: LeafId, tick: u32, frame: &[Option<i64>]) -> Result<i64, TraceError> {
1573        let pos = &self.positions[tick as usize];
1574        if Some(leaf) == self.disable
1575            && let Some(i) = self.mixed_index()
1576        {
1577            return Ok(i64::from(pos.mixed_cur.get(i).copied().unwrap_or(false)));
1578        }
1579        if Some(leaf) == self.disable && !self.reads_locals(leaf) {
1580            return Ok(i64::from(pos.disable_cur));
1581        }
1582        if !self.reads_locals(leaf) {
1583            let slot = leaf.0 as usize;
1584            if let Some(Some(v)) = pos.vals.borrow().get(slot) {
1585                return Ok(*v);
1586            }
1587            let v = with_sva_leaf_time(pos.time, || self.host.leaf(leaf, &pos.row, &[]));
1588            let mut vals = pos.vals.borrow_mut();
1589            if vals.is_empty() {
1590                vals.resize(self.leaf_reads.len(), None);
1591            }
1592            vals[slot] = Some(v);
1593            return Ok(v);
1594        }
1595        let f = self.host_frame(&[leaf], frame)?;
1596        Ok(with_sva_leaf_time(pos.time, || {
1597            self.host.leaf(leaf, &pos.row, &f)
1598        }))
1599    }
1600}
1601
1602impl PropEnv for WindowEnv<'_> {
1603    fn value(&self, leaf: LeafId, tick: u32, frame: &[Option<i64>]) -> Result<i64, TraceError> {
1604        let v = self.value_of(leaf, tick, frame)?;
1605        if Some(leaf) != self.disable {
1606            self.note(tick, leaf, v);
1607        }
1608        Ok(v)
1609    }
1610
1611    fn case_match(
1612        &self,
1613        selector: LeafId,
1614        label: LeafId,
1615        tick: u32,
1616        frame: &[Option<i64>],
1617    ) -> Result<bool, TraceError> {
1618        let f = self.host_frame(&[selector, label], frame)?;
1619        let m = self
1620            .host
1621            .case_pair(selector, label, &self.positions[tick as usize].row, &f);
1622        self.note(tick, selector, 1);
1623        self.note(tick, label, i64::from(m));
1624        Ok(m)
1625    }
1626
1627    fn between(&self, leaf: LeafId, after: u32) -> Result<bool, TraceError> {
1628        if Some(leaf) == self.disable
1629            && let Some(i) = self.mixed_index()
1630        {
1631            return Ok(self.positions[after as usize]
1632                .mixed_btw
1633                .get(i)
1634                .copied()
1635                .unwrap_or(false));
1636        }
1637        let Some(i) = self.live.iter().position(|&l| l == leaf) else {
1638            return Ok(false);
1639        };
1640        Ok(self.positions[after as usize].btw[i])
1641    }
1642
1643    fn timeline(&self) -> Option<&Timeline> {
1644        self.timeline
1645    }
1646
1647    fn initial_frame(&self) -> Vec<(LocalVarId, i64)> {
1648        self.captured
1649            .iter()
1650            .copied()
1651            .zip(self.values.iter().copied())
1652            .collect()
1653    }
1654}
1655
1656#[cfg(test)]
1657mod tests;