-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SimpleIter
example ITree proof and reorganize files
- Loading branch information
Showing
16 changed files
with
688 additions
and
380 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
-- import LeanBoogie.BoogieDsl | ||
import LeanBoogie.ITree | ||
import LeanBoogie.Boog | ||
import LeanBoogie.Mem | ||
import Auto | ||
import Aesop | ||
|
||
open Boogie ITree | ||
|
||
set_option auto.smt true | ||
set_option trace.auto.smt.printCommands true | ||
set_option trace.auto.smt.result true | ||
set_option trace.auto.printLemmas true | ||
set_option auto.smt.trust true | ||
set_option auto.smt.solver.name "z3" | ||
set_option pp.fieldNotation.generalized false | ||
|
||
-- Very simple example which can be proven using unrolling or a congruence on the while loop | ||
|
||
def p1 : ITree MemEv Unit := do | ||
Mem.write "i" 0 | ||
while_ (return (<- Mem.read "i") < 3) do | ||
Mem.update "i" (. + 1) | ||
Mem.update "x" (. + 2) | ||
Mem.write "i" 0 -- need to set `i` to 0 afterwards, otherwise the programs compute the same `x` but not `i`. | ||
|
||
def p2 : ITree MemEv Unit := do | ||
Mem.write "i" 0 | ||
while_ (return (<- Mem.read "i") < 6) do | ||
Mem.update "x" (. + 1) | ||
Mem.update "i" (. + 1) | ||
Mem.write "i" 0 | ||
|
||
example : EuttB (interp p1) (interp p2) := by | ||
rw [p1, p2] | ||
-- 1. unroll loops | ||
conv => lhs; rw [Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1] | ||
conv => rhs; rw [Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1, Eutt.eq while_unroll1] | ||
|
||
-- 2. Push `interp` inwards as far as possible, | ||
-- this will change `ITree.{pure, bind, iter, ite, read, write}` | ||
-- into `Boog.{pure, bind, iter, ite, read, write}` | ||
simp [EuttB.eq interp_bind, EuttB.eq interp_write, EuttB.eq interp_ite, EuttB.eq interp_pure, | ||
Mem.update, EuttB.eq interp_write, EuttB.eq interp_read, skip] | ||
-- Our goal is now of form `b1 b2 : (S -> ITree ∅ (A × S)) ⊢ ∀σ:S, Eutt (b1 σ) (b2 σ)`, with the predominant `bind` being `Boog.bind`. | ||
|
||
-- 3. Push state `σ` inwards as far as possible. This allows us to apply `pure_bind` and obtain | ||
-- a pure state transition function, because we no longer have any relevant coinduction. | ||
-- Nonetheless, this causes the predominant `bind` to become `ITree.bind` yet again (`Boog.read v : Boog ..`, but `Boog.read v σ : ITree ..`). | ||
-- However, we know that `ITree.bind (Boog.read v σ) k` is actually `ITree.bind (ITree.pure (σ v, σ)) k`, which simplifies to `k (σ v) σ` via `pure_bind`. Similar for `.write`. | ||
intro σ | ||
simp only [Eutt.eq Boog.bind_push_state, Eutt.eq Boog.ite_push_state] | ||
simp only [Boog.read, Boog.write, BoogieState.update.eq_unfold] | ||
simp only [pure_bind, Nat.ofNat_pos, dite_eq_ite, ↓reduceDIte, ↓reduceIte, String.reduceEq, zero_add, Nat.one_lt_ofNat, Int.reduceAdd, Int.reduceLT, lt_self_iff_false] | ||
simp_all only [↓reduceIte] | ||
|
||
dsimp [Pure.pure, ITree.pure] | ||
-- Our goal is now of form `σ : S ⊢ .ret (a, f σ) = .ret (b, g σ)` | ||
apply Eutt.ret_congr | ||
congr 1 | ||
unfold BoogieState at σ | ||
unfold BoogieState | ||
-- 4. Solve by auto :) | ||
auto |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
import LeanBoogie.ITree | ||
import LeanBoogie.Boog | ||
|
||
namespace Boogie | ||
|
||
open ITree KTree | ||
|
||
/- | ||
# Basic, Labels, `goto` | ||
-/ | ||
|
||
abbrev Label := Nat | ||
abbrev Labels := { xs : List Label // xs.length >= 0 } | ||
|
||
structure Block : Type where | ||
assumes : ITree MemEv Bool := ITree.ret true | ||
/-- A block is code which returns at least one label to jump to. -/ | ||
code : ITree MemEv Labels | ||
|
||
/-- Boogie procedure. Just a bunch of blocks. -/ | ||
structure Procedure : Type where | ||
blocks : List Block | ||
|
||
/-- Get a block, or spin forever if index is invalid. -/ | ||
def getBlock (proc : Procedure) (l : Label) : ITree E Block := do | ||
if h : l < proc.blocks.length then return proc.blocks[l] | ||
else ITree.spin | ||
|
||
/-- Return the label of the first block whose `assume`s decide to true. If none are, spin. -/ | ||
def choose (proc : Procedure) : KTree MemEv Labels Label | ||
:= fun ⟨ls, _⟩ => impl proc ls | ||
where impl (proc : Procedure) : KTree MemEv (List Label) Label | ||
| [] => ITree.spin | ||
| l :: ls => do | ||
let b <- getBlock proc l | ||
if <- b.assumes | ||
then return l | ||
else impl proc ls | ||
|
||
#check case | ||
|
||
/-- Run a bunch of blocks until no jump label is returned anymore. -/ | ||
-- def Procedure.run (proc : Procedure) : Label -> ITree MemEv Unit := | ||
def Procedure.run (proc : Procedure) : KTree MemEv Label Unit := | ||
ITree.iter (A := Label) (B := _) fun (l : Label) => do | ||
let block <- getBlock proc l | ||
let ls : Labels <- block.code -- run block | ||
-- TODO: potentially non-deterministic branching. | ||
let l <- choose proc ls | ||
if l = 0 then return .inr () -- hard-code label 0 as the exit label for now. | ||
else return .inl l | ||
/- Here, `run` must somehow know which of the labels to jump to, because we are essentially | ||
building an interpreter, and Lean is deterministic. So how can we know this? | ||
1. Sometimes, the `assume`s at the beginning of each block are disjoint, so the jump is | ||
actually deterministic. The problems with this are: | ||
- We'd need to *look inside* those blocks. We have the list of blocks in `proc`, but they | ||
are of type `ITree _ _`, which we can't pattern match on. So we have to store this | ||
extra information somewhere along with the list of blocks. Now we have information | ||
doubling, which is not very pretty. | ||
This could be avoided if the blocks were a syntactic construct, so that you could | ||
pattern match on them and read out the `assume`s. | ||
- We'd have to decide the propositions. Often this will be easy, since those propositions | ||
often stem from `if` and the like. However, Boogie allows arbitrary propositions in | ||
`assume`, which may even include forall-quantifiers. | ||
This means we have to read some variables with `Mem.get`, which we know doesn't change | ||
the state after interpretation, but theoretically we can't know this at this point; | ||
it also breaks eutt. | ||
2. We can use an "event oracle", i.e. add an effect to our ITrees so that we can ask | ||
the world which branch to take. This is (oversimplifying) how | ||
(Choice Trees)[https://arxiv.org/pdf/2211.06863] paper does it, but there are subtleties | ||
to consider, such as: | ||
- For `ITree MemEv A`, we have some nice laws such as associativity after interpretation. | ||
But you don't want to interpret `ITree (MemEv ⊕ NonDet) A`, because... you can't. | ||
So what do you do? You need to recover this structure somehow, and that's what the | ||
CTrees paper is for. See section 2.2 of the ctrees paper. | ||
- A different notion of program equivalence than eutt, which can deal with non-det. | ||
3. So instead, we take a very practical, somewhat hacky, approach for now: For each | ||
destination block, jump to the first block whose `assume φ` decides to true. | ||
This will coincidentally give us correct semantics if the assumes are disjoint, and will | ||
act as a tie-breaker for non-determinism. | ||
- The problem with having to *look inside* the blocks and read out the `assume`s remain. | ||
-/ | ||
|
||
|
||
|
||
|
||
-- ## Example: | ||
|
||
def bb1 : Block := { | ||
code := do | ||
Mem.write "i1" 1 | ||
return ⟨[2], by rw [List.length]; omega⟩ | ||
} | ||
|
||
def bb2 : Block := { | ||
assumes := do return (<- Mem.read "i") <= 5 | ||
code := do | ||
Mem.write "x" ((<- Mem.read "x") + 2) | ||
Mem.write "i" ((<- Mem.read "i") + 1) | ||
return ⟨[2, 3], by rw [List.length]; omega⟩ | ||
} | ||
|
||
def bb3 : Block := { | ||
assumes := do return !((<- Mem.read "i") <= 5) | ||
code := do return ⟨[0], by rw [List.length]; omega⟩ | ||
} | ||
|
||
def myProc : Procedure := { | ||
blocks := [bb1, bb2, bb3] | ||
} | ||
|
||
-- theorem run_step : Procedure.run proc l = proc.blocks[l] >>> proc.run l := sorry | ||
|
||
-- example : myProc.run ~~~ myProc.run := by | ||
|
||
-- done | ||
|
||
-- #check ITree.iter | ||
-- /-- -/ | ||
-- example : KTree MemEv Nat Nat := | ||
-- KTree.iter |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.