Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions codelib/CodeLib.lean
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ import CodeLib.Near.State
import CodeLib.Near.Env
import CodeLib.Near.Proof
import CodeLib.IEEE32.Exec
import CodeLib.SepLogic.WasmHeap
import CodeLib.SepLogic.WasmRules
import CodeLib.SepLogic.WasmWP
import CodeLib.SepLogic.Adequacy

/-!
# CodeLib — umbrella import for downstream code
Expand Down
691 changes: 691 additions & 0 deletions codelib/CodeLib/SepLogic/Adequacy.lean

Large diffs are not rendered by default.

124 changes: 124 additions & 0 deletions codelib/CodeLib/SepLogic/WasmHeap.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import Iris
import Iris.BI.Lib.GenHeap
import Interpreter.Wasm
/-! # Wasm Memory as an Iris GenHeap
Instantiates iris-lean's GenHeap for Wasm byte-level memory.
Location = UInt32 (byte address), Value = Option UInt8 (byte).
-/
namespace Wasm.SepLogic
open Iris Std
abbrev WasmHeapMap := fun V => ExtTreeMap UInt32 V compare
abbrev WasmHeapGF : BundledGFunctors
| 0 => ⟨InvMapF, by infer_instance⟩
| 1 => ⟨constOF (DisjointLeibnizSet CoPset), by infer_instance⟩
| 2 => ⟨constOF (DisjointLeibnizSet PosSet), by infer_instance⟩
| 3 => ⟨Auth.AuthURF (constOF Credit), by infer_instance⟩
| 4 => ⟨constOF (HeapView UInt32 (Agree (DiscreteO (Option UInt8))) WasmHeapMap), by infer_instance⟩
| 5 => ⟨constOF (HeapView UInt32 (Agree (DiscreteO GName)) WasmHeapMap), by infer_instance⟩
| 6 => ⟨constOF MetaUR, by infer_instance⟩
| _ => ⟨constOF Unit, by infer_instance⟩
-- Wire genHeapPreS (following HeapLang's instHeapLangGS_HeapLangS)
instance instWasmHeapPreS : genHeapPreS UInt32 (Option UInt8) WasmHeapGF WasmHeapMap where
heap := by constructor; exists 4
metaInfo := by constructor; exists 5
metaData := by exists 6
-- The full genHeap instance with ghost names
class WasmHeapGS extends genHeapGS UInt32 (Option UInt8) WasmHeapGF WasmHeapMap
/-! ## Points-to assertions

Byte-level `↦w` plus multi-byte and array derived forms.

**Address arithmetic caveat:** the multi-byte assertions below compute
their footprint with `UInt32` addition (`addr + 1`, …), which wraps
mod 2^32, whereas the interpreter's `Mem.read64`/`write64` index bytes at
`addr.toNat + k : Nat` with no wraparound. The two footprints agree only
when the access does not overflow the 32-bit address space (e.g.
`addr.toNat + 8 ≤ 2^32` for `pointsTo_u64`, and
`ptr.toNat + 4 * xs.length ≤ 2^32` for `arrayAt`). Any future rule
bridging these assertions to `Mem.read*/write*` must carry such a
no-overflow side condition — without it the ghost footprint at high
addresses wraps to low addresses and the bridge would be unprovable (or
unsound if forced). -/
section PointsTo
variable [inst : WasmHeapGS]
-- Notation for Wasm points-to (scoped: available inside this namespace
-- and via `open Wasm.SepLogic`, without leaking through the CodeLib umbrella)
scoped notation:50 addr:50 " ↦w " v:50 => pointsTo (L := UInt32) (V := Option UInt8)
(GF := WasmHeapGF) (H := WasmHeapMap) addr (DFrac.own 1) (some v)
-- Multi-byte: u64 as 8 consecutive owned bytes (little-endian)
def pointsTo_u64 (addr : UInt32) (v : UInt64) : IProp WasmHeapGF :=
let byte (n : Nat) : UInt8 := ⟨(v.toNat / (256 ^ n)) % 256, by omega⟩
iprop%
(addr ↦w byte 0) ∗ ((addr + 1) ↦w byte 1) ∗
((addr + 2) ↦w byte 2) ∗ ((addr + 3) ↦w byte 3) ∗
((addr + 4) ↦w byte 4) ∗ ((addr + 5) ↦w byte 5) ∗
((addr + 6) ↦w byte 6) ∗ ((addr + 7) ↦w byte 7)
-- Multi-byte: u32 as 4 consecutive owned bytes (little-endian)
def pointsTo_u32 (addr : UInt32) (v : UInt32) : IProp WasmHeapGF :=
let byte (n : Nat) : UInt8 := ⟨(v.toNat / (256 ^ n)) % 256, by omega⟩
iprop%
(addr ↦w byte 0) ∗ ((addr + 1) ↦w byte 1) ∗
((addr + 2) ↦w byte 2) ∗ ((addr + 3) ↦w byte 3)
-- Array ownership: n consecutive u32 elements at ptr
-- arrayAt ptr [x₀, x₁, ..., xₙ₋₁] = pointsTo_u32 ptr x₀ ∗ pointsTo_u32 (ptr+4) x₁ ∗ ...
def arrayAt (ptr : UInt32) (xs : List UInt32) : IProp WasmHeapGF :=
match xs with
| [] => iprop% emp
| x :: rest => iprop% (pointsTo_u32 ptr x) ∗ (arrayAt (ptr + 4) rest)
-- element-offset arithmetic shared by the arrayAt lemmas: stepping past
-- the head element shifts the base by one 4-byte stride
omit inst in
private theorem elem_offset_succ (ptr : UInt32) (k : Nat) :
ptr + 4 * UInt32.ofNat (k + 1) = (ptr + 4) + 4 * UInt32.ofNat k := by
symm
rw [UInt32.ofNat_add, show UInt32.ofNat 1 = 1 from rfl, UInt32.mul_add, UInt32.mul_one]
rw [UInt32.add_assoc ptr 4, UInt32.add_comm 4, ← UInt32.add_assoc]

-- arrayAt splits across ++ : ownership of a concatenation is
-- ownership of both halves (merge_sort_into splits data at mid)
theorem arrayAt_append (ptr : UInt32) (xs ys : List UInt32) :
arrayAt ptr (xs ++ ys) ⊣⊢
arrayAt ptr xs ∗ arrayAt (ptr + 4 * UInt32.ofNat xs.length) ys := by
induction xs generalizing ptr with
| nil => simp [arrayAt]; exact BI.emp_sep.symm
| cons x rest ih =>
simp only [List.cons_append, List.length_cons, arrayAt]
rw [elem_offset_succ]
exact (BI.sep_congr_right (ih (ptr + 4))).trans BI.sep_assoc.symm

-- update element k: give back a cell with a NEW value,
-- own the updated array (merge writes out[k] = v)
theorem arrayAt_set (ptr : UInt32) (xs : List UInt32) (k : Nat)
(v : UInt32) (hk : k < xs.length) :
arrayAt ptr xs ⊢
pointsTo_u32 (ptr + 4 * UInt32.ofNat k) xs[k] ∗
(pointsTo_u32 (ptr + 4 * UInt32.ofNat k) v -∗ arrayAt ptr (xs.set k v)) := by
induction xs generalizing ptr k with
| nil => simp at hk
| cons x rest ih =>
cases k with
| zero =>
simp only [List.getElem_cons_zero, List.set_cons_zero, arrayAt]
rw [show ptr + 4 * UInt32.ofNat 0 = ptr from by simp [UInt32.ofNat]]
exact BI.sep_mono .rfl (BI.wand_intro BI.sep_symm)
| succ k' =>
simp only [List.length_cons] at hk
have hk' : k' < rest.length := by omega
simp only [List.getElem_cons_succ, List.set_cons_succ, arrayAt]
rw [elem_offset_succ]
exact (BI.sep_mono_right (ih (ptr + 4) k' hk')).trans
(BI.sep_left_comm.mp.trans (BI.sep_mono_right
(BI.wand_intro (BI.sep_assoc.mp.trans (BI.sep_mono_right BI.wand_elim_left)))))

-- extract element k: whole-array ownership gives the single
-- cell plus everything else (merge reads left[i], right[j]).
-- The special case of arrayAt_set that writes back the value just read.
theorem arrayAt_get (ptr : UInt32) (xs : List UInt32) (k : Nat)
(hk : k < xs.length) :
arrayAt ptr xs ⊢
pointsTo_u32 (ptr + 4 * UInt32.ofNat k) xs[k] ∗
(pointsTo_u32 (ptr + 4 * UInt32.ofNat k) xs[k] -∗ arrayAt ptr xs) := by
have h := arrayAt_set ptr xs k xs[k] hk
rwa [List.set_getElem_self] at h
end PointsTo
end Wasm.SepLogic
59 changes: 59 additions & 0 deletions codelib/CodeLib/SepLogic/WasmRules.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import CodeLib.SepLogic.WasmHeap
import Interpreter.Wasm

/-! # Bridge: Talos Mem ↔ iris-lean GenHeap

Defines `heapAgreesWithMem` — the agreement predicate between an abstract
GenHeap state σ and physical memory — together with per-byte load/store
soundness lemmas for it, stated against the interpreter's `Mem.read8` /
`Mem.write8` API so they apply directly to interpreter-produced states.

**Status: not yet wired into the WP.** Nothing in `wp_wasm_F`,
`wasm_adequacy`, or `wasm_heap_adequacy` currently asserts this agreement:
the ghost heap threaded through `wp_wasm` is a free-floating resource, so a
`pointsTo` fact does not (yet) imply anything about `st.mem`. These
definitions are the intended ingredient for a future state interpretation
that maintains `heapAgreesWithMem σ st.mem` across steps; until that lands,
memory facts in program proofs must come from pure hypotheses about
`st.mem` (as the load/store rules in `Adequacy.lean` require).
-/

namespace Wasm.SepLogic

open Iris Wasm Std

/-! Agreement: wherever GenHeap has an entry, Mem agrees. -/

def heapAgreesWithMem (σ : WasmHeapMap (Option UInt8)) (mem : Mem) : Prop :=
∀ (addr : UInt32) (v : UInt8),
get? σ addr = some (some v) → mem.read8 addr = v

/-! Soundness of load:
If GenHeap says addr ↦ v and σ agrees with Mem,
then Mem.read8 addr = v. -/

theorem load_sound (σ : WasmHeapMap (Option UInt8)) (mem : Mem)
(addr : UInt32) (v : UInt8)
(h_agree : heapAgreesWithMem σ mem)
(h_own : get? σ addr = some (some v)) :
mem.read8 addr = v :=
h_agree addr v h_own

/-! Soundness of store:
After Mem.write8, the updated σ still agrees with the new Mem. -/

theorem store_sound (σ : WasmHeapMap (Option UInt8)) (mem : Mem)
(addr : UInt32) (new_v : UInt8)
(h_agree : heapAgreesWithMem σ mem) :
heapAgreesWithMem (insert σ addr (some new_v)) (mem.write8 addr new_v) := by
intro addr' v' h_get
by_cases h : addr' = addr
· subst h
simp [get?_insert_eq rfl] at h_get
simp [Mem.write8, Mem.read8, h_get]
· simp [get?_insert_ne (Ne.symm h)] at h_get
have hne : addr'.toNat ≠ addr.toNat :=
fun h' => h (UInt32.ext h')
simpa [Mem.write8, Mem.read8, hne] using h_agree addr' v' h_get

end Wasm.SepLogic
26 changes: 26 additions & 0 deletions codelib/CodeLib/SepLogic/WasmWP.lean
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import CodeLib.SepLogic.WasmHeap
import CodeLib.SepLogic.WasmRules
import Interpreter.Wasm

/-! # Prop-level Weakest Precondition for Wasm

`wp_wasm_prop` — the fuel-abstracted Prop-level weakest precondition:
some fuel exists under which the program runs to completion (fallthrough
or return) in a state satisfying `Q`. This is the layer program proofs
compose in: see the `wp_wasm_prop_*` rules in `Adequacy.lean`, which also
defines the iProp-level `wp_wasm` fixpoint and the adequacy bridge from
it down to this predicate. -/

namespace Wasm.SepLogic

open Iris Wasm

def wp_wasm_prop (m : Module) (st : Store Unit) (locals : Locals)
(prog : Program) (env : HostEnv Unit)
(Q : Store Unit → List Value → Prop) : Prop :=
∃ fuel, match exec fuel m st locals prog env with
| .Fallthrough st' _ => Q st' []
| .Return st' vals => Q st' vals
| _ => False

end Wasm.SepLogic
52 changes: 31 additions & 21 deletions codelib/lake-manifest.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,43 @@
{"version": "1.2.0",
"packagesDir": "../.lake/packages",
"packages":
[{"type": "path",
[{"url": "https://github.com/leanprover-community/iris-lean.git",
"type": "git",
"subDir": "Iris",
"scope": "",
"rev": "c2485783af2626575d171398c86cea8b3a6e79ad",
"name": "iris",
"manifestFile": "lake-manifest.json",
"inputRev": null,
"inherited": false,
"configFile": "lakefile.toml"},
{"type": "path",
"scope": "",
"name": "WasmInterpreterLean",
"manifestFile": "lake-manifest.json",
"inherited": false,
"dir": "../interpreter",
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/batteries",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "023ce7d62a0531e22a5331e20b587817a80d49ff",
"name": "batteries",
"manifestFile": "lake-manifest.json",
"inputRev": "v4.32.0",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/quote4",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "38d591e778f100aec9762bb582f9c7f55f50e9dc",
"name": "Qq",
"manifestFile": "lake-manifest.json",
"inputRev": "v4.32.0",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/mathlib4",
"type": "git",
"subDir": null,
Expand Down Expand Up @@ -68,26 +98,6 @@
"inputRev": "master",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/quote4",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "38d591e778f100aec9762bb582f9c7f55f50e9dc",
"name": "Qq",
"manifestFile": "lake-manifest.json",
"inputRev": "master",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover-community/batteries",
"type": "git",
"subDir": null,
"scope": "leanprover-community",
"rev": "023ce7d62a0531e22a5331e20b587817a80d49ff",
"name": "batteries",
"manifestFile": "lake-manifest.json",
"inputRev": "main",
"inherited": true,
"configFile": "lakefile.toml"},
{"url": "https://github.com/leanprover/lean4-cli",
"type": "git",
"subDir": null,
Expand Down
7 changes: 7 additions & 0 deletions codelib/lakefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@ packagesDir = "../.lake/packages"
name = "WasmInterpreterLean"
path = "../interpreter"

[[require]]
name = "iris"
git.url = "https://github.com/leanprover-community/iris-lean.git"
git.subDir = "Iris"
# post-v4.32 bump (iris-lean#514); no v4.32.0 tag cut yet, so pin the commit
git.rev = "c2485783af2626575d171398c86cea8b3a6e79ad"

[[lean_lib]]
name = "CodeLib"
1 change: 1 addition & 0 deletions programs/lean/Project.lean
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Project.RustArrayTests.Spec
import Project.RustU64.Spec
import Project.RustU64Tests.Spec
import Project.SwapElements.Spec
import Project.SwapElements.SwapSepLogic
import Project.TotalVariation.Spec
import Project.FloatTrunc.Spec
import Project.FloatRound.Spec
Expand Down
Loading
Loading