vj_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 (`VJ_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("VJ_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 (`VJ_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("VJ_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;