This PR adds an API for building symbolic simulation engines and verification condition generators that leverage `grind`. The API wraps `Sym` operations to work with `grind`'s `Goal` type, enabling lightweight symbolic execution while carrying `grind` state for discharge steps. New operations on `Goal`: - `mkGoal`: create a `Goal` from an `MVarId` - `introN`, `intros`: introduce binders - `apply`: apply backward rules - `simp`, `simpIgnoringNoProgress`: simplify using `Sym.Simp` - `internalize`, `internalizeAll`: add hypotheses to the E-graph - `grind`: attempt to close the goal using `grind` - `assumption`: close by matching a hypothesis A new test demonstrates the API on a stateful program with conditionals, using `grind` to discharge arithmetic side conditions.
80 lines
2.7 KiB
Text
80 lines
2.7 KiB
Text
import Lean.Meta.Tactic
|
||
import Lean.Meta.Sym
|
||
|
||
open Lean Meta Sym
|
||
def profileM {α : Type} (k : MetaM α) (msg : String := "experiment") : MetaM α :=
|
||
profileitM Exception msg (Options.empty.set `profiler true |>.set `profiler.threshold 0) k
|
||
|
||
def genTerm (n : Nat) : Expr := Id.run do
|
||
let mut e := mkConst ``True
|
||
let nat := mkConst ``Nat
|
||
for _ in 0...n do
|
||
let eq := mkApp3 (mkConst ``Eq [1]) nat (mkBVar 0) (mkNatAdd (mkBVar 2) (mkBVar 1))
|
||
e := mkApp2 (mkConst ``And) eq e
|
||
e := mkApp2 (mkConst ``Exists [1]) nat (mkLambda `y .default nat e)
|
||
e := mkForall `x .default nat e
|
||
e := mkLet `z nat (mkNatAdd (mkBVar 1) (mkBVar 0)) e
|
||
let eq := mkApp3 (mkConst ``Eq [1]) nat (mkBVar 0) (mkNatAdd (mkBVar 2) (mkBVar 1))
|
||
e := mkApp2 (mkConst ``And) eq e
|
||
e := mkApp2 (mkConst ``Exists [1]) nat (mkLambda `y .default nat e)
|
||
e := mkForall `x .default nat e
|
||
e := mkLet `z nat (mkNatLit 0) e
|
||
return e
|
||
|
||
set_option maxRecDepth 10000000
|
||
|
||
def tryIntros? (goals : List MVarId) : SymM (Option (List MVarId)) := do
|
||
try
|
||
let goal :: goals := goals | return none
|
||
let .goal _ goal' ← intros goal | failure
|
||
return some (goal' :: goals)
|
||
catch _ =>
|
||
return none
|
||
|
||
def tryApply? (rule : BackwardRule) (goals : List MVarId) : SymM (Option (List MVarId)) := do
|
||
let goal :: goals := goals | return none
|
||
let .goals goals' ← rule.apply goal | return none
|
||
return some (goals' ++ goals)
|
||
|
||
def tryApplyAny? (rules : List BackwardRule) (goals : List MVarId) : SymM (Option (List MVarId)) := do
|
||
match rules with
|
||
| [] => return none
|
||
| rule :: rules =>
|
||
if let some goals' ← tryApply? rule goals then
|
||
return some goals'
|
||
else
|
||
tryApplyAny? rules goals
|
||
|
||
def solve (n : Nat) (type : Expr) : MetaM Unit := profileM (msg := s!"size {n}") <| SymM.run do
|
||
let mvarId := (← mkFreshExprMVar type).mvarId!
|
||
let rules ← [``Exists.intro, ``And.intro, ``Eq.refl, ``True.intro].mapM fun declName => mkBackwardRuleFromDecl declName
|
||
let goal ← preprocessMVar mvarId
|
||
discard <| go 10000000 rules [goal]
|
||
return ()
|
||
where
|
||
go (fuel : Nat) (rules : List BackwardRule) (goals : List MVarId) : SymM Bool := do
|
||
let fuel + 1 := fuel | throwError "out of fuel"
|
||
let goal :: goals' := goals | return true
|
||
if (← goal.isAssigned) then
|
||
go fuel rules goals'
|
||
else
|
||
if let some goals' ← tryIntros? goals then
|
||
go fuel rules goals'
|
||
else if let some goals' ← tryApplyAny? rules goals then
|
||
go fuel rules goals'
|
||
else
|
||
throwError "Stuck at {goal}"
|
||
|
||
def test (n : Nat) : MetaM Unit := do
|
||
let e := genTerm n
|
||
solve n e
|
||
|
||
-- We are solving problems of the following form
|
||
#eval logInfo (genTerm 2)
|
||
|
||
#eval test 1000
|
||
#eval test 2000
|
||
#eval test 3000
|
||
#eval test 4000
|
||
#eval test 5000
|
||
#eval test 6000
|