Skip to content

Commit 210c056

Browse files
authored
feat: adding asynchronous execution of solver tactics for generated VCs (#33)
* feat: implement asynchronous tactic execution in Loom This commit introduces several functions for executing tactics asynchronously, allowing for parallel processing of goals. Key additions include `wrapTactic`, `asyncRunTactic`, and `allGoalsAsync`, which facilitate running tactics on multiple goals concurrently. The implementation also includes helper functions for managing task priorities and cancellation tokens, enhancing the overall efficiency of tactic execution in Loom. * feat: enhance Loom tactics with asynchronous capabilities This commit adds new syntax for asynchronous tactics, including `loom_solve_async` and `loom_solve_async!`, allowing for improved parallel processing of goals. It introduces helper functions to manage asynchronous execution and provides warnings for potential mismatches in task numbers. Additionally, the previous `Loom.Tactic` file has been removed in favor of `Loom.TacticAsync`, streamlining the tactic execution process. * feat: add a case study for async * refactor: use grind in SerachSubstring * chore: bump to v4.24.0 * fix one case study * clean up
1 parent 7d3dbab commit 210c056

File tree

13 files changed

+260
-97
lines changed

13 files changed

+260
-97
lines changed

CaseStudies/Tactic.lean

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import Loom.MonadAlgebras.WP.Attr
66
import Loom.MonadAlgebras.WP.Tactic
77
-- import Loom.MonadAlgebras.WP.DoNames'
88
import Loom.MonadAlgebras.WP.Gen
9-
import Loom.Tactic
9+
-- import Loom.Tactic
10+
import Loom.TacticAsync
1011
import Loom.SMT
1112

1213
import CaseStudies.Extension
@@ -58,6 +59,8 @@ def getAssertionStx : TacticM (Option Term) := withMainContext do
5859

5960
declare_syntax_cat loom_solve_tactic
6061
syntax "loom_solve" : loom_solve_tactic
62+
syntax "loom_solve_async" (num)? : loom_solve_tactic
63+
syntax "loom_solve_async!" (num)? : loom_solve_tactic
6164
syntax "loom_solve?" : loom_solve_tactic
6265
syntax "loom_solve!" : loom_solve_tactic
6366
syntax loom_solve_tactic : tactic
@@ -67,6 +70,16 @@ syntax "loom_unfold" : tactic
6770
syntax "loom_auto" : tactic
6871
syntax "loom_solver" : tactic
6972

73+
def loomSolveAsync? : TSyntax `loom_solve_tactic -> Option (Option Nat)
74+
| `(loom_solve_tactic| loom_solve_async $n ?)
75+
| `(loom_solve_tactic| loom_solve_async! $n ?) => some (n.map (·.getNat))
76+
| _ => none
77+
78+
def loomSolveIsReporting : TSyntax `loom_solve_tactic -> Bool
79+
| `(loom_solve_tactic| loom_solve_async! $_ ?)
80+
| `(loom_solve_tactic| loom_solve!) => true
81+
| _ => false
82+
7083
elab_rules : tactic
7184
| `(tactic| loom_goals_intro) => withMainContext do
7285
let vlsIntro <- `(tactic| (
@@ -115,31 +128,31 @@ elab_rules : tactic
115128
else
116129
let vlsIntro ← `(tactic| loom_goals_intro)
117130
let vlsUnfold ← `(tactic| loom_unfold)
118-
let vlsSolve ← `(tactic| loom_solver)
131+
let vlsSolve ← `(tactic| try solve | loom_unfold; loom_solver)
119132
evalTactic vlsIntro
120-
let res <- anyGoalsWithTag fun _mvarId => do
121-
let stx_res <- getAssertionStx
133+
let goals <- getUnsolvedGoals
134+
if let some n := loomSolveAsync? vls then
135+
if goals.length < n.getD 0 then
136+
logWarningAt vls m!"This method has only {goals.length} Verification Conditions, but `loom_solve_async` is called with {n.get!} asynchronous tasks"
137+
allGoalsAsync (numTasks := n.map (·.min goals.length)) do
138+
evalTactic vlsSolve
139+
logWarningAt vls m!
140+
"`loom_solve_async` uses sorry to admit all the solved goals for now, consider running `loom_solve` once proof is complete to get the proof term"
141+
else
142+
allGoals do evalTactic vlsSolve
143+
let mut unsolved := #[]
144+
for mvarId in <- getUnsolvedGoals do
145+
setGoals [mvarId]
146+
let stx <- getAssertionStx
122147
evalTactic vlsUnfold
123-
let mvarId <- getMainGoal
124-
evalTactic vlsSolve
125-
if (<- getUnsolvedGoals).length > 0 then
126-
match stx_res with
127-
| some stx =>
128-
return some (.mkSimple stx.raw.prettyPrint.pretty, (mvarId, some stx))
129-
| none =>
130-
return some (`unnamed, (mvarId, none))
131-
else return none
132-
let tryVlsSolve ← `(tactic| all_goals try loom_solver)
133-
let tryVlsUnfold ← `(tactic| all_goals try loom_unfold)
134-
evalTactic tryVlsSolve
135-
evalTactic tryVlsUnfold
136-
match vls with
137-
| `(loom_solve_tactic| loom_solve!) =>
138-
for (mvarId, stx_res) in res do
139-
match stx_res with
140-
| some stx => logErrorAt stx $ m!"Failed to prove assertion\n{mvarId}"
141-
| none => logError m!"Failed to prove nameless assertion\n{mvarId}"
142-
| _ => pure ()
148+
if let some stx := stx then
149+
(<- getMainGoal).setTag <| .mkSimple stx.raw.prettyPrint.pretty
150+
if loomSolveIsReporting vls then
151+
logErrorAt stx $ m!"Failed to prove assertion\n{mvarId}"
152+
else if loomSolveIsReporting vls then
153+
logErrorAt vls m!"Failed to prove nameless assertion\n{mvarId}"
154+
unsolved := unsolved.append (← getUnsolvedGoals).toArray
155+
setGoals unsolved.toList
143156

144157
elab "loom_solve?" : tactic => withMainContext do
145158
let ctx := (<- solverHints.get)
@@ -175,7 +188,7 @@ elab_rules : tactic
175188
| "grind" =>
176189
/- In case of `grind` solver, we need to fetch the number of splits from the options first. -/
177190
let splits := Lean.Syntax.mkNatLit <| (opts.getNat (defVal := 20) `loom.solver.grind.splits)
178-
evalTactic $ <- `(tactic| try grind ($(mkIdent `splits):ident := $splits))
191+
evalTactic $ <- `(tactic| grind ($(mkIdent `splits):ident := $splits))
179192
| "custom" =>
180193
evalTactic $ <- `(tactic| fail "Custom solver is not specified")
181194
| _ => evalTactic $ <- `(tactic| loom_auto)

CaseStudies/Theory.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Mathlib.Algebra.Ring.Int.Defs
77
import Loom.MonadAlgebras.NonDetT.Extract
88
import Loom.MonadAlgebras.Instances.Basic
99
import Loom.MonadAlgebras.WP.Tactic
10-
import Loom.Tactic
10+
import Loom.Meta
1111

1212
import CaseStudies.Extension
1313
variable {m τ l} [Monad m] [LawfulMonad m] [Nonempty τ] [CompleteBooleanAlgebra l]

CaseStudies/Velvet/VelvetExamples/Recursion.lean

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,3 @@ method pow2 (n: Nat) return (res: Nat)
201201

202202
prove_correct pow2 by
203203
loom_solve
204-
rw [if_pos]; rfl

CaseStudies/Velvet/VelvetExamples/SubstringSearch.lean

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import CaseStudies.TestingUtil
44
set_option loom.semantics.termination "total"
55
set_option loom.semantics.choice "demonic"
66
set_option loom.solver "cvc5"
7-
set_option loom.solver.smt.timeout 5
7+
set_option loom.solver.smt.timeout 4
88

99
--this will ne our answer type
1010
structure SubstringResult where
@@ -14,7 +14,7 @@ structure SubstringResult where
1414
deriving Repr, Inhabited
1515

1616
--predicate for substring all characters of which satisfy the predicate
17-
@[loomAbstractionSimp]
17+
@[grind, loomAbstractionSimp]
1818
def CorrectSubstring (s : Array Char) (p : Char -> Bool) (l r : Nat) : Prop :=
1919
l ≤ r ∧ r < s.size ∧
2020
(∀ i, l ≤ i ∧ i ≤ r → p s[i]!)
@@ -76,8 +76,9 @@ do
7676
pnt := pnt + 1
7777
return ⟨l_ans, r_ans, ans > 0
7878

79+
#time
7980
prove_correct SubstringSearch by
80-
loom_solve
81+
loom_solve_async
8182

8283
--prove theorem not about the monadic computation but the actual
8384
--extract result
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import Auto
2+
import Lean
3+
4+
import CaseStudies.Velvet.Std
5+
import CaseStudies.TestingUtil
6+
7+
set_option loom.semantics.termination "total"
8+
set_option loom.semantics.choice "demonic"
9+
10+
--this will ne our answer type
11+
structure SubstringResult where
12+
l : Nat
13+
r : Nat
14+
flag: Bool
15+
deriving Repr, Inhabited
16+
17+
--predicate for substring all characters of which satisfy the predicate
18+
@[grind]
19+
def CorrectSubstring (s : Array Char) (p : Char -> Bool) (l r : Nat) : Prop :=
20+
l ≤ r ∧ r < s.size ∧
21+
(∀ i, l ≤ i ∧ i ≤ r → p s[i]!)
22+
23+
--actual method
24+
-- if there are no substrings,
25+
-- flag is false and all characters do not satisfy the predicate
26+
method SubstringSearch (s: Array Char) (p: Char -> Bool) return (res: SubstringResult)
27+
--postconditions, don't need any preconditions.
28+
ensures (res.l ≤ res.r)
29+
ensures 0 < s.size → res.r < s.size
30+
ensures res.flag → CorrectSubstring s p res.l res.r
31+
ensures res.flag →
32+
(∀ l₁ r₁, CorrectSubstring s p l₁ r₁ →
33+
r₁ - l₁ + 1 = res.r - res.l + 1 ∧ res.r ≤ r₁ ∨
34+
r₁ - l₁ + 1 < res.r - res.l + 1)
35+
ensures ¬res.flag → ∀ i < s.size, ¬p s[i]!
36+
do
37+
if s.size = 0 then
38+
--basic case with an empty string
39+
return0, 0, false
40+
let mut cnt := 0
41+
let mut pnt := 0
42+
let mut ans := 0
43+
let mut l_ans := 0
44+
let mut r_ans := 0
45+
while pnt < s.size
46+
--loop invariants
47+
invariant 0 ≤ cnt
48+
invariant cnt ≤ pnt
49+
invariant pnt ≤ s.size
50+
invariant l_ans ≤ r_ans
51+
invariant r_ans < s.size
52+
invariant cnt ≤ ans
53+
invariant r_ans ≤ pnt
54+
invariant ∀ j, pnt - cnt ≤ j ∧ j < pnt → p s[j]!
55+
invariant ans > 0
56+
ans = r_ans - l_ans + 1
57+
CorrectSubstring s p l_ans r_ans
58+
invariant ans = 0 → (∀ i, i < pnt → ¬p s[i]!)
59+
invariant cnt < pnt → ¬p s[pnt - cnt - 1]!
60+
invariant ∀ l₁ r₁,
61+
CorrectSubstring s p l₁ r₁ ∧ r₁ < pnt →
62+
r₁ - l₁ + 1 = ans ∧ r_ans ≤ r₁ ∨ r₁ - l₁ + 1 < ans
63+
--value decreases by 1 with each iteration,
64+
--therefore time complexity is O(size s), as other parts
65+
--take constant time
66+
decreasing s.size - pnt
67+
do
68+
--loop body
69+
if p s[pnt]! then
70+
cnt := cnt + 1
71+
if ans < cnt then
72+
l_ans := pnt + 1 - cnt
73+
r_ans := pnt
74+
ans := cnt
75+
else
76+
cnt := 0
77+
pnt := pnt + 1
78+
return ⟨l_ans, r_ans, ans > 0
79+
80+
/--
81+
warning: `loom_solve_async` uses sorry to admit all the solved goals for now, consider running `loom_solve` once proof is complete to get the proof term
82+
---
83+
warning: declaration uses 'sorry'
84+
-/
85+
#guard_msgs in
86+
prove_correct SubstringSearch by
87+
loom_solve_async
88+
89+
--prove theorem not about the monadic computation but the actual
90+
--extract result
91+
theorem finalCorrectnessTheorem (s : Array Char) (p : Char → Bool) :
92+
let res := SubstringSearch s p |>.extract
93+
(res.flag = false → ∀ i < s.size, p s[i]! = false) ∧
94+
(res.flag = true
95+
(∀ l₁ r₁, CorrectSubstring s p l₁ r₁ →
96+
r₁ - l₁ + 1 = res.r - res.l + 1 ∧ res.r ≤ r₁ ∨
97+
r₁ - l₁ + 1 < res.r - res.l + 1)) ∧
98+
(res.flag = true → CorrectSubstring s p res.l res.r) ∧
99+
(0 < s.size → res.r < s.size) ∧
100+
(res.l ≤ res.r) := by
101+
grind [VelvetM.extract_spec, SubstringSearch_correct]

CaseStudies/Velvet/VelvetTheory.lean

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ lemma wp_mono_part (x : NonDetT DivM α) (post₁ post₂ : α -> Prop) :
5555
lemma VelvetM.total_decompose {α : Type} (x : VelvetM α) (post₁ post₂ : α -> Prop):
5656
[totl| wp x post₁] ⊓ [part| wp x post₂] = [totl| wp x (post₁ ⊓ post₂)] := by
5757
unhygienic induction x <;> try simp [loomLogicSimp]
58-
{ simp [DivM.total_decompose]
58+
{ --simp [DivM.total_decompose]
5959
simp [[totl|DivM.wp_eq]]
6060
split
6161
{ simp }

Loom/MonadAlgebras/Instances/Gen.lean

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ open Plausible
88

99
universe u
1010

11+
-- TODO: Fix this instance
1112
/- Ordered Monad Algebra instance for Gen -/
12-
instance MAlgGenInst : MAlgOrdered Gen (ULift Nat -> ULift StdGen -> Prop) :=
13-
inferInstanceAs
14-
(MAlgOrdered
15-
(ReaderT (ULift Nat)
16-
(StateT (ULift StdGen) Id))
17-
(ULift Nat ->
18-
ULift StdGen -> Prop))
13+
-- instance MAlgGenInst : MAlgOrdered Gen (ULift Nat -> ULift StdGen -> Prop) :=
14+
-- inferInstanceAs
15+
-- (MAlgOrdered
16+
-- (ReaderT (ULift Nat)
17+
-- (StateT (ULift StdGen) Id))
18+
-- (ULift Nat ->
19+
-- ULift StdGen -> Prop))

Loom/MonadAlgebras/WP/Basic.lean

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -500,11 +500,12 @@ section Gen
500500

501501
open Plausible
502502

503+
-- TODO: Fix this instance
503504
/- WP for rand in Gen -/
504-
lemma Gen.wp_rand {α : Type} (c : Gen α) :
505-
triple ⊤ c (fun _ => ⊤) := by
506-
simp [triple, MAlgGenInst, ReaderT.wp_eq, StateT.wp_eq]
507-
simp [wp, liftM, monadLift, MAlg.lift, MAlgOrdered.μ]; rfl
505+
-- lemma Gen.wp_rand {α : Type} (c : Gen α) :
506+
-- triple ⊤ c (fun _ => ⊤) := by
507+
-- simp [triple, MAlgGenInst, ReaderT.wp_eq, StateT.wp_eq]
508+
-- simp [wp, liftM, monadLift, MAlg.lift, MAlgOrdered.μ]; rfl
508509

509510
end Gen
510511

Loom/Tactic.lean

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)