Skip to content

Commit f15aca5

Browse files
committed
lazy trace recording with replay-on-violation
1 parent 434f5e1 commit f15aca5

1 file changed

Lines changed: 90 additions & 23 deletions

File tree

Veil/Core/Tools/ModelChecker/Simulation.lean

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,72 @@ def violatedInvariantNames {ρ σ : Type}
3030
if !p.holdsOn th st then some p.name else none
3131

3232

33-
/-- Inner loop of a single random trace: walk from `currSt` for up to
34-
`stepsLeft` steps, picking a random enabled transition at each step.
35-
Returns `(violation?, updatedRng, stepsTaken)`. -/
33+
/-- Lightweight scan loop: walk without building a trace.
34+
Returns `(violated?, updatedRng, stepsTaken)`. -/
35+
@[inline, specialize]
36+
partial def scanOnceLoop {ρ σ κ : Type} {th₀ : ρ}
37+
(sys : EnumerableTransitionSystem ρ (List ρ) σ (List σ) Int κ (List (κ × ExecutionOutcome Int σ)) th₀)
38+
(params : SearchParameters ρ σ)
39+
(th : ρ)
40+
(stepsLeft : Nat)
41+
(currSt : σ)
42+
(gen : StdGen)
43+
[Inhabited (κ × σ)]
44+
: Bool × StdGen × Nat :=
45+
match stepsLeft with
46+
| 0 => (false, gen, 0)
47+
| stepsLeft + 1 =>
48+
let outcomes := sys.tr th currSt
49+
let assertionFailureFound := outcomes.any fun (_, outcome) =>
50+
match outcome with
51+
| .assertionFailure _ _ => true
52+
| _ => false
53+
if assertionFailureFound then
54+
(true, gen, 1)
55+
else
56+
let nexts := Concrete.extractSuccessfulTransitions outcomes
57+
if nexts.isEmpty then
58+
if !params.terminating.holdsOn th currSt then
59+
(true, gen, 0) -- deadlock
60+
else
61+
(false, gen, 0)
62+
else
63+
let (idx, gen) := randNat gen 0 (nexts.length - 1)
64+
let (_, nextSt) := nexts[idx]!
65+
let violations := violatedInvariantNames params th nextSt
66+
if !violations.isEmpty then
67+
(true, gen, 1)
68+
else
69+
let (violated, gen, innerSteps) := scanOnceLoop sys params th stepsLeft nextSt gen
70+
(violated, gen, innerSteps + 1)
71+
72+
73+
/-- Lightweight scan: pick random init state, walk without trace.
74+
Returns `(violated?, updatedRng, stepsTaken)`. -/
75+
@[inline, specialize]
76+
partial def scanOnce {ρ σ κ : Type} {th₀ : ρ}
77+
(sys : EnumerableTransitionSystem ρ (List ρ) σ (List σ) Int κ (List (κ × ExecutionOutcome Int σ)) th₀)
78+
(params : SearchParameters ρ σ)
79+
(th : ρ)
80+
(gen : StdGen)
81+
(maxSteps : Nat)
82+
[Inhabited σ]
83+
[Inhabited (κ × σ)]
84+
: Bool × StdGen × Nat :=
85+
if sys.initStates.isEmpty then
86+
(false, gen, 0)
87+
else
88+
let (idx, gen) := randNat gen 0 (sys.initStates.length - 1)
89+
let initSt := sys.initStates[idx]!
90+
let initViolations := violatedInvariantNames params th initSt
91+
if !initViolations.isEmpty then
92+
(true, gen, 0)
93+
else
94+
scanOnceLoop sys params th maxSteps initSt gen
95+
96+
97+
/-- Full trace loop: walk and record every step for counterexample.
98+
Returns `(violation?, updatedRng, stepsTaken)`. Used only for replay. -/
3699
@[inline, specialize]
37100
partial def simulateOnceLoop {ρ σ κ : Type} {th₀ : ρ}
38101
(sys : EnumerableTransitionSystem ρ (List ρ) σ (List σ) Int κ (List (κ × ExecutionOutcome Int σ)) th₀)
@@ -48,7 +111,6 @@ partial def simulateOnceLoop {ρ σ κ : Type} {th₀ : ρ}
48111
| 0 => (none, gen, 0)
49112
| stepsLeft + 1 =>
50113
let outcomes := sys.tr th currSt
51-
-- Check assertion failures first (highest priority)
52114
let assertionFailures := outcomes.filterMap fun (_, outcome) =>
53115
match outcome with
54116
| .assertionFailure exId _ => some exId
@@ -67,7 +129,6 @@ partial def simulateOnceLoop {ρ σ κ : Type} {th₀ : ρ}
67129
let nexts := Concrete.extractSuccessfulTransitions outcomes
68130
if nexts.isEmpty then
69131
if !params.terminating.holdsOn th currSt then
70-
-- No enabled transitions and not a terminating state: deadlock
71132
(some (.foundViolation () .deadlock (some trace)), gen, trace.steps.size)
72133
else
73134
(none, gen, trace.steps.size)
@@ -83,8 +144,7 @@ partial def simulateOnceLoop {ρ σ κ : Type} {th₀ : ρ}
83144
simulateOnceLoop sys params th stepsLeft nextSt trace gen
84145

85146

86-
/-- Run a single random trace from a randomly chosen initial state.
87-
Returns `(violation?, updatedRng, stepsTaken)`. -/
147+
/-- Full trace run from random init state. Used only for replay. -/
88148
@[inline, specialize]
89149
partial def simulateOnce {ρ σ κ : Type} {th₀ : ρ}
90150
(sys : EnumerableTransitionSystem ρ (List ρ) σ (List σ) Int κ (List (κ × ExecutionOutcome Int σ)) th₀)
@@ -109,8 +169,8 @@ partial def simulateOnce {ρ σ κ : Type} {th₀ : ρ}
109169

110170

111171
/-- Run `maxTraces` independent random traces, stopping on first violation.
112-
Each trace uses an independent seed derived from `(masterSeed + traceIndex)`
113-
for maximum prefix diversity across traces. -/
172+
Scans without trace recording for speed; replays only the violating trace.
173+
Each trace uses an independent seed derived from `(masterSeed + traceIndex)`. -/
114174
@[inline, specialize]
115175
partial def simulate {ρ σ κ : Type} {th₀ : ρ}
116176
(sys : EnumerableTransitionSystem ρ (List ρ) σ (List σ) Int κ (List (κ × ExecutionOutcome Int σ)) th₀)
@@ -127,21 +187,28 @@ partial def simulate {ρ σ κ : Type} {th₀ : ρ}
127187
let mut i := 0
128188
let mut totalSteps := 0
129189
while i < cfg.maxTraces do
130-
let traceGen := mkStdGen (actualSeed + i)
131-
let (maybeResult, _, stepsUsed) := simulateOnce sys params th traceGen cfg.maxSteps
190+
let traceSeed := actualSeed + i
191+
-- Fast scan: no trace allocation
192+
let (violated, _, stepsUsed) := scanOnce sys params th (mkStdGen traceSeed) cfg.maxSteps
132193
totalSteps := totalSteps + stepsUsed
133-
match maybeResult with
134-
| some result =>
135-
let elapsedMs := (← IO.monoMsNow) - startMs
136-
return {
137-
result := result
138-
tracesRun := i + 1
139-
elapsedMs := elapsedMs
140-
seed := actualSeed
141-
depth := stepsUsed
142-
totalSteps := totalSteps
143-
}
144-
| none =>
194+
if violated then
195+
-- Replay with same seed to build counterexample trace
196+
let (maybeResult, _, _) := simulateOnce sys params th (mkStdGen traceSeed) cfg.maxSteps
197+
match maybeResult with
198+
| some result =>
199+
let elapsedMs := (← IO.monoMsNow) - startMs
200+
return {
201+
result := result
202+
tracesRun := i + 1
203+
elapsedMs := elapsedMs
204+
seed := actualSeed
205+
depth := stepsUsed
206+
totalSteps := totalSteps
207+
}
208+
| none =>
209+
-- Scan flagged violation but replay didn't reproduce (should not happen)
210+
i := i + 1
211+
else
145212
i := i + 1
146213
let elapsedMs := (← IO.monoMsNow) - startMs
147214
return {

0 commit comments

Comments
 (0)