@@ -550,6 +550,66 @@ def getModelCheckingMode (modeStx : Syntax) : ModelCheckingMode :=
550550 | `(modelCheckMode| compiled) => .compiled
551551 | _ => .default
552552
553+
554+ /-- Get all action label names for never-enabled action warnings. -/
555+ private def getActionLabelNames (mod : Module) : CommandElabM (List String) := do
556+ let labelTypeName ← resolveGlobalConstNoOverload labelType
557+ return mod.actions.map (fun a => s! "{ labelTypeName} .{ a.name} " ) |>.toList
558+
559+ private def warnAboutTransitions (mod : Module) : CommandElabM Unit := do
560+ let transitions := mod.procedures.filter (·.info.isTransition)
561+ if transitions.isEmpty then return
562+ let names := ", " .intercalate (transitions.map (·.info.name.toString) |>.toList)
563+ logWarning m! "Explicit state model checking of transitions is SLOW!\n\n \
564+ The current implementation enumerates all possible states and filters those satisfying \
565+ the transition relation. Your specification has { transitions.size} \
566+ transition{if transitions.size > 1 then "s" else "" } : { names} \n\n \
567+ Consider encoding transitions as imperative actions where possible."
568+
569+ private def resolveTheoryTerm (cmdName : String) (theoryTermOpt : Option Term)
570+ (mod : Module) (instTerm : Term) : CommandElabM Term := do
571+ match theoryTermOpt with
572+ | some t => pure t
573+ | none =>
574+ unless mod.immutableComponents.isEmpty do
575+ let fieldStrs := mod.immutableComponents.map (fun c => s! "{ c.name} := ..." )
576+ let theoryExample := "{ " ++ ", " .intercalate fieldStrs.toList ++ " }"
577+ throwError "This module has immutable fields, so you must specify the theory instantiation:\n \
578+ {cmdName} {instTerm} {theoryExample}"
579+ `({})
580+
581+ /-- Prepend `name` with `mod.name`. -/
582+ private def mkIdentWithModName' (mod : Module) (name : Name) : Ident :=
583+ Lean.mkIdent (mod.name ++ name)
584+
585+ /-- Build search parameters for model checking / simulation. -/
586+ private def buildSearchParameters (mod : Module) (config : ModelCheckerConfig) : CommandElabM Term := do
587+ let mkProp (sa : StateAssertion) : CommandElabM Term :=
588+ `($(mkIdent ``Veil.ModelChecker.SafetyProperty.mk)
589+ ($(mkIdent `name) := $(quote sa.name))
590+ ($(mkIdent `property) := fun $(mkIdent `th) $(mkIdent `st) => $(mkIdentWithModName' mod sa.name) $(mkIdent `th) $(mkIdent `st)))
591+ let safetyList ← `([$((← mod.invariants.mapM mkProp)),*])
592+ let terminatingProp ← match mod.terminations[0 ]? with
593+ | some t => mkProp t
594+ | none => `($(mkIdent `default))
595+ let earlyTermConds ← do
596+ let base ← `([$(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.foundViolatingState),
597+ $(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.assertionFailed),
598+ $(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.deadlockOccurred)])
599+ if config.maxDepth > 0 then `($base ++ [$(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.reachedDepthBound) $(quote config.maxDepth)])
600+ else pure base
601+ `({ $(mkIdent `invariants):ident := $safetyList, $(mkIdent `terminating):ident := $terminatingProp,
602+ $(mkIdent `earlyTerminationConditions):ident := $earlyTermConds })
603+
604+ /-- Display a TraceDisplayViewer widget with the given result JSON. -/
605+ private def displayResultWidget (stx : Syntax) (resultTerm : Term) : CommandElabM Unit := do
606+ let widgetExpr ← `(open ProofWidgets.Jsx in
607+ <ProofWidgets.TraceDisplayViewer result={$resultTerm} layout={"vertical" } />)
608+ let html ← ← liftTermElabM <| ProofWidgets.HtmlCommand.evalCommandMHtml <| ← ``(ProofWidgets.HtmlEval.eval $widgetExpr)
609+ liftCoreM <| Widget.savePanelWidgetInfo
610+ (hash ProofWidgets.HtmlDisplayPanel.javascript)
611+ (return json% { html: $(← Server.rpcEncode html) }) stx
612+
553613@ [command_elab Veil.modelCheck]
554614def elabModelCheck : CommandElab := fun stx => do
555615 -- Use dynamic trace class name for detailed profiling
@@ -561,19 +621,6 @@ def elabModelCheck : CommandElab := fun stx => do
561621 let cfg := stx[4 ]
562622 elabModelCheckCore stx mode instTerm theoryTermOpt cfg
563623where
564- /-- Get the theory term, defaulting to `{}` if not provided and there are no theory fields.
565- Throws a helpful error if theory fields exist but no term was provided. -/
566- getTheoryTerm (theoryTermOpt : Option Term) (mod : Module) (instTerm : Term) : CommandElabM Term := do
567- match theoryTermOpt with
568- | some t => pure t
569- | none =>
570- unless mod.immutableComponents.isEmpty do
571- let fieldStrs := mod.immutableComponents.map (fun c => s! "{ c.name} := ..." )
572- let theoryExample := "{ " ++ ", " .intercalate fieldStrs.toList ++ " }"
573- throwError "This module has immutable fields, so you must specify the theory instantiation:\n \
574- #model_check {instTerm} {theoryExample}"
575- `({})
576-
577624 /-- Generate the model source for compilation:
578625 1. Insert `set_option veil.__modelCheckCompileMode true` after imports
579626 2. Keep everything up to the point where the spec was finalized
@@ -599,65 +646,22 @@ where
599646 let modelCheckCmd := String.Pos.Raw.extract src modelCheckStart modelCheckEnd
600647 return beforeImports ++ compileModePreamble ++ afterImportsToSpecFinalized ++ "\n " ++ modelCheckCmd ++ "\n "
601648
602- /-- Prepend `name` with `mod.name`. Useful when expressions are printed out for debugging. -/
603- mkIdentWithModName (mod : Module) (name : Name) : Ident :=
604- Lean.mkIdent (mod.name ++ name)
605-
606- /-- Display a TraceDisplayViewer widget with the given result term. -/
607- displayResultWidget (stx : Syntax) (resultTerm : Term) : CommandElabM Unit := do
608- let widgetExpr ← `(open ProofWidgets.Jsx in
609- <ProofWidgets.TraceDisplayViewer result={$resultTerm} layout={"vertical" } />)
610- let html ← ← liftTermElabM <| ProofWidgets.HtmlCommand.evalCommandMHtml <| ← ``(ProofWidgets.HtmlEval.eval $widgetExpr)
611- liftCoreM <| Widget.savePanelWidgetInfo
612- (hash ProofWidgets.HtmlDisplayPanel.javascript)
613- (return json% { html: $(← Server.rpcEncode html) }) stx
614-
615- mkSearchParameters (mod : Module) (config : ModelCheckerConfig) : CommandElabM Term := do
616- -- Build SafetyProperty.mk syntax for a StateAssertion
617- let mkProp (sa : StateAssertion) : CommandElabM Term :=
618- `($(mkIdent ``Veil.ModelChecker.SafetyProperty.mk)
619- ($(mkIdent `name) := $(quote sa.name))
620- ($(mkIdent `property) := fun $(mkIdent `th) $(mkIdent `st) => $(mkIdentWithModName mod sa.name) $(mkIdent `th) $(mkIdent `st)))
621- let safetyList ← `([$((← mod.invariants.mapM mkProp)),*])
622- let terminatingProp ← match mod.terminations[0 ]? with
623- | some t => mkProp t
624- | none => `($(mkIdent `default))
625- let earlyTermConds ← do
626- let base ← `([$(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.foundViolatingState),
627- $(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.assertionFailed),
628- $(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.deadlockOccurred)])
629- if config.maxDepth > 0 then `($base ++ [$(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.reachedDepthBound) $(quote config.maxDepth)])
630- else pure base
631- `({ $(mkIdent `invariants):ident := $safetyList, $(mkIdent `terminating):ident := $terminatingProp,
632- $(mkIdent `earlyTerminationConditions):ident := $earlyTermConds })
633-
634649 /-- Build the core model checker call syntax (without parallel config). -/
635650 mkModelCheckerCall (mod : Module) (config : ModelCheckerConfig)
636651 (instTerm theoryTerm : Term) : CommandElabM Term := do
637652 let inst := mkVeilImplementationDetailIdent `inst
638653 let th := mkVeilImplementationDetailIdent `th
639654 let instSortArgs ← (← mod.sortIdents).mapM fun sortIdent => `($inst.$(sortIdent))
640- let sp ← mkSearchParameters mod config
655+ let sp ← buildSearchParameters mod config
641656 -- Model checker call with type annotation to help inference
642657 -- Note: findReachable takes parallelCfg, progressInstanceId, and cancelToken as the last three args
643658 `((let $inst : $instantiationType := $instTerm
644659 let $th : $theoryIdent $instSortArgs* := $theoryTerm
645660 $(mkIdent ``Veil.ModelChecker.Concrete.findReachable)
646661 ($(mkIdent `inhabσ) := $instInhabitedStateFieldConcreteType $instSortArgs*)
647- ($(mkIdentWithModName mod `enumerableTransitionSystem) $instSortArgs* $th)
662+ ($(mkIdentWithModName' mod `enumerableTransitionSystem) $instSortArgs* $th)
648663 $sp : _ → _ → _ → IO _))
649664
650- /-- Warn if the module contains transitions (which are slow to model check). -/
651- warnAboutTransitions (mod : Module) : CommandElabM Unit := do
652- let transitions := mod.procedures.filter (·.info.isTransition)
653- if transitions.isEmpty then return
654- let names := ", " .intercalate (transitions.map (·.info.name.toString) |>.toList)
655- logWarning m! "Explicit state model checking of transitions is SLOW!\n\n \
656- The current implementation enumerates all possible states and filters those satisfying \
657- the transition relation. Your specification has { transitions.size} \
658- transition{if transitions.size > 1 then "s" else "" } : { names} \n\n \
659- Consider encoding transitions as imperative actions where possible."
660-
661665 /-- Create an error JSON object. -/
662666 errorJson (msg : String) : Json := Json.mkObj [("error" , msg)]
663667
@@ -743,11 +747,6 @@ where
743747 Term.synthesizeSyntheticMVarsNoPostponing
744748 unsafe Meta.evalExpr (IO Lean.Json) (mkApp (mkConst ``IO) (mkConst ``Lean.Json)) (← instantiateMVars expr)
745749
746- /-- Get all action label names for never-enabled action warnings. -/
747- getActionLabelNames (mod : Module) : CommandElabM (List String) := do
748- let labelTypeName ← resolveGlobalConstNoOverload labelType
749- return mod.actions.map (fun a => s! "{ labelTypeName} .{ a.name} " ) |>.toList
750-
751750 /-- Log model checking result. -/
752751 logModelCheckResult (stx : Syntax) (resultJson : Json) : CommandElabM Unit := do
753752 let msg := TraceDisplay.formatModelCheckingResult resultJson
@@ -805,7 +804,7 @@ where
805804 let mod ← getCurrentModule (errMsg := "You cannot #model_check outside of a Veil module!" )
806805 mod.throwIfSpecNotFinalized
807806
808- let theoryTerm ← getTheoryTerm theoryTermOpt mod instTerm
807+ let theoryTerm ← resolveTheoryTerm "#model_check" theoryTermOpt mod instTerm
809808
810809 warnAboutTransitions mod
811810 let config ← elabModelCheckerConfig cfg
@@ -926,7 +925,7 @@ private def mkSimulatorCall (mod : Module) (instTerm theoryTerm : Term)
926925 `((let $inst : $instantiationType := $instTerm
927926 let $th : $theoryIdent $instSortArgs* := $theoryTerm
928927 $(mkIdent ``Veil.ModelChecker.Simulation.simulate)
929- ($(Lean.mkIdent ( mod.name ++ `enumerableTransitionSystem) ) $instSortArgs* $th)
928+ ($(mkIdentWithModName' mod `enumerableTransitionSystem) $instSortArgs* $th)
930929 $sp $th $cfgTerm))
931930
932931@ [command_elab Veil.simulate]
@@ -936,14 +935,15 @@ def elabSimulate : CommandElab := fun stx => do
936935 let theoryTermOpt : Option Term := if stx[2 ].isNone then none else some ⟨stx[2 ][0 ]⟩
937936 let mod ← getCurrentModule (errMsg := "You cannot #simulate outside of a Veil module!" )
938937 mod.throwIfSpecNotFinalized
939- let theoryTerm ← getTheoryTerm theoryTermOpt mod instTerm
938+ let theoryTerm ← resolveTheoryTerm "#simulate" theoryTermOpt mod instTerm
939+ warnAboutTransitions mod
940940 let cfg0 ← elabSimulateConfig stx[3 ]
941941 let opts ← getOptions
942942 let maxTraces := if cfg0.maxTraces == 10000 then veil.simulate.maxTraces.get opts else cfg0.maxTraces
943943 let maxSteps := if cfg0.maxSteps == 100 then veil.simulate.maxSteps.get opts else cfg0.maxSteps
944944 let cfg : ModelChecker.Simulation.SimulateConfig := { cfg0 with maxTraces, maxSteps }
945945 let mcCfg : ModelCheckerConfig := { maxDepth := 0 , sequential := false , parallelCfg := none }
946- let sp ← mkSearchParameters mod mcCfg
946+ let sp ← buildSearchParameters mod mcCfg
947947 let callExpr ← mkSimulatorCall mod instTerm theoryTerm sp cfg
948948 let wrappedCallExpr ← `(Functor.map (fun r => Lean.Json.mkObj [
949949 ("result" , Lean.toJson (Veil.ModelChecker.Simulation.SimulateResult.result r)),
@@ -972,49 +972,16 @@ def elabSimulate : CommandElab := fun stx => do
972972 let stepsPerSec := if elapsedMs > 0 then totalSteps * 1000 / elapsedMs else 0
973973 let isViolation := resultJson.getObjValD "result" == Json.str "found_violation" ||
974974 resultJson.getObjValD "error" != .null
975+ -- Log simulation-specific summary
975976 let summary := if isViolation then
976977 s! "simulation: found violation at depth { depth} (trace #{ tracesRun} , { elapsedMs} ms, seed := { seed} ). A shorter violation may exist at depth < { depth} ."
977978 else
978979 s! "simulation: no violation in { tracesRun} traces ({ totalSteps} steps, { elapsedMs} ms, { stepsPerSec} steps/s, seed := { seed} ). Not exhaustive -- use #model_check for full coverage."
979- let details := TraceDisplay.formatModelCheckingResult resultJson
980- let msg := summary ++ "\n " ++ details
981- let violationIsError := veil.violationIsError.get opts
982- if isViolation && violationIsError then logErrorAt stx msg else logInfoAt stx msg
983- where
984- /-- Get the theory term, defaulting to `{}` if not provided and there are no theory fields.
985- Throws a helpful error if theory fields exist but no term was provided. -/
986- getTheoryTerm (theoryTermOpt : Option Term) (mod : Module) (instTerm : Term) : CommandElabM Term := do
987- match theoryTermOpt with
988- | some t => pure t
989- | none =>
990- unless mod.immutableComponents.isEmpty do
991- let fieldStrs := mod.immutableComponents.map (fun c => s! "{ c.name} := ..." )
992- let theoryExample := "{ " ++ ", " .intercalate fieldStrs.toList ++ " }"
993- throwError "This module has immutable fields, so you must specify the theory instantiation:\n \
994- #simulate {instTerm} {theoryExample}"
995- `({})
996-
997- /-- Prepend `name` with `mod.name`. Useful when expressions are printed out for debugging. -/
998- mkIdentWithModName (mod : Module) (name : Name) : Ident :=
999- Lean.mkIdent (mod.name ++ name)
1000-
1001- /-- Build search parameters reused by simulator execution. -/
1002- mkSearchParameters (mod : Module) (config : ModelCheckerConfig) : CommandElabM Term := do
1003- let mkProp (sa : StateAssertion) : CommandElabM Term :=
1004- `($(mkIdent ``Veil.ModelChecker.SafetyProperty.mk)
1005- ($(mkIdent `name) := $(quote sa.name))
1006- ($(mkIdent `property) := fun $(mkIdent `th) $(mkIdent `st) => $(mkIdentWithModName mod sa.name) $(mkIdent `th) $(mkIdent `st)))
1007- let safetyList ← `([$((← mod.invariants.mapM mkProp)),*])
1008- let terminatingProp ← match mod.terminations[0 ]? with
1009- | some t => mkProp t
1010- | none => `($(mkIdent `default))
1011- let earlyTermConds ← do
1012- let base ← `([$(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.foundViolatingState),
1013- $(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.assertionFailed),
1014- $(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.deadlockOccurred)])
1015- if config.maxDepth > 0 then `($base ++ [$(mkIdent ``Veil.ModelChecker.EarlyTerminationCondition.reachedDepthBound) $(quote config.maxDepth)])
1016- else pure base
1017- `({ $(mkIdent `invariants):ident := $safetyList, $(mkIdent `terminating):ident := $terminatingProp,
1018- $(mkIdent `earlyTerminationConditions):ident := $earlyTermConds })
1019-
980+ logInfoAt stx summary
981+ -- Log the same trace display as #model_check
982+ elabModelCheck.logModelCheckResult stx resultJson
983+ -- Display the same TraceDisplayViewer widget as #model_check
984+ let (instanceId, _) ← ModelChecker.Concrete.allocProgressInstance (← getActionLabelNames mod)
985+ ModelChecker.Concrete.finishProgress instanceId resultJson
986+ ModelChecker.displayStreamingProgress stx instanceId
1020987end Veil
0 commit comments