Skip to content

Commit 316fa96

Browse files
theebayuserclaude
andcommitted
codelib: words64 array view + first ∀-quantified loop-over-memory proof
Phase 2b of #68, on top of the MemRegion algebra. MemArray.lean — Mem.words64 base n : List UInt64, the list view of the u64 array [base, base+8n). Defined via List.range/map so length and indexing are simp-lemmas; plus words64_ext, words64_write64_outside (framing through MemRegion), words64_succ (snoc) and words64_write64_extend (the fill step as a view equation). This is the codelib home for the memory-as-list view merge_sort (#106) carries privately as wordsAt. MemFillLoop.lean — fillWords_spec: the canonical fill loop writes v to each of the n u64 slots of [base, base+8n), proved for ALL n via the invariant/variant loop rule (wp_loop_cons), with the whole-region result stated as st'.mem.words64 base n.toNat = List.replicate n.toNat v. Every memory example under interpreter/Examples is a concrete native_decide check because symbolic framing lives downstream here in CodeLib; this is the first universally-quantified loop-over-memory theorem in the repo, and the shape merge_sort's proof needs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent d507945 commit 316fa96

3 files changed

Lines changed: 175 additions & 0 deletions

File tree

codelib/CodeLib.lean

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import CodeLib.UInt32
55
import CodeLib.UInt64
66
import CodeLib.RustStd.Frame
77
import CodeLib.RustStd.Region
8+
import CodeLib.RustStd.MemArray
9+
import CodeLib.RustStd.MemFillLoop
810
import CodeLib.RustStd.UInt
911
import CodeLib.RustStd.U64.Basic
1012
import CodeLib.RustStd.U64.AbsDiff
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import CodeLib.RustStd.Region
2+
3+
/-!
4+
# `CodeLib.RustStd.MemArray`
5+
6+
A `List UInt64` *view* of a `u64` array in linear memory (issue #68, spec
7+
readability). `Mem.words64 base n` is the length-`n` list of words at
8+
`base, base+8, …, base+8(n−1)`, so a spec can say `m.words64 base n = vs`
9+
instead of `∀ k < n, m.read64 (base + 8*k) = vs[k]`.
10+
11+
The view is defined via `List.range`/`map` so `length` and indexing are
12+
`simp`-lemmas, and its interaction with `write64` factors through the
13+
`MemRegion` framing algebra: a write disjoint from the array leaves the view
14+
unchanged (`words64_write64_outside`), and a write to slot `j` sets index `j`
15+
(`words64_write64_set`).
16+
-/
17+
18+
namespace Wasm
19+
20+
/-- The `List UInt64` view of the `u64` array `[base, base + 8*n)`. -/
21+
def Mem.words64 (m : Mem) (base : UInt32) (n : Nat) : List UInt64 :=
22+
(List.range n).map fun k => m.read64 (base + 8 * (UInt32.ofNat k))
23+
24+
@[simp] theorem Mem.length_words64 (m : Mem) (base : UInt32) (n : Nat) :
25+
(m.words64 base n).length = n := by
26+
simp [Mem.words64]
27+
28+
theorem Mem.getElem_words64 (m : Mem) (base : UInt32) (n k : Nat) (h : k < n) :
29+
(m.words64 base n)[k]'(by simpa using h) = m.read64 (base + 8 * UInt32.ofNat k) := by
30+
simp [Mem.words64]
31+
32+
/-- Two array views agree iff their words agree pointwise. -/
33+
theorem Mem.words64_ext {m m' : Mem} {base : UInt32} {n : Nat}
34+
(h : ∀ k < n, m.read64 (base + 8 * UInt32.ofNat k) = m'.read64 (base + 8 * UInt32.ofNat k)) :
35+
m.words64 base n = m'.words64 base n := by
36+
apply List.ext_getElem (by simp)
37+
intro k hk _
38+
simp only [length_words64] at hk
39+
rw [getElem_words64 m base n k hk, getElem_words64 m' base n k hk, h k hk]
40+
41+
/-- Under no address wraparound, a `write64` whose target slot `j` is `≥ n`
42+
(i.e. outside the array `[base, base+8n)`) leaves the view unchanged. -/
43+
theorem Mem.words64_write64_outside (m : Mem) (base : UInt32) (n : Nat) (a : UInt32) (v : UInt64)
44+
(hbnd : base.toNat + 8 * n ≤ 4294967296)
45+
(hout : a.toNat + 8 ≤ base.toNat ∨ base.toNat + 8 * n ≤ a.toNat) :
46+
(m.write64 a v).words64 base n = m.words64 base n := by
47+
apply words64_ext
48+
intro k hk
49+
have hsize : (UInt32.size : Nat) = 4294967296 := rfl
50+
have hkn : (UInt32.ofNat k).toNat = k :=
51+
UInt32.toNat_ofNat_of_lt' (by omega : k < UInt32.size)
52+
have haddr : (base + 8 * UInt32.ofNat k).toNat = base.toNat + 8 * k := by
53+
have := MemRegion.slot64_base_toNat base (UInt32.ofNat k) (by rw [hkn]; omega)
54+
rw [hkn] at this
55+
exact this
56+
exact Mem.read64_write64_disjoint m a _ v (by rw [haddr]; omega)
57+
58+
/-- One more word: `words64 base (n+1)` is `words64 base n` with the `n`-th
59+
word appended. -/
60+
theorem Mem.words64_succ (m : Mem) (base : UInt32) (n : Nat) :
61+
m.words64 base (n + 1) = m.words64 base n ++ [m.read64 (base + 8 * UInt32.ofNat n)] := by
62+
simp [Mem.words64, List.range_succ, List.map_append]
63+
64+
/-- The fill step, as a view equation: if the first `n` words are already `v`
65+
and slot `n` is written with `v`, the first `n+1` words are `v`. This is the
66+
loop invariant's inductive step, discharged once here. -/
67+
theorem Mem.words64_write64_extend (m : Mem) (base : UInt32) (n : Nat) (v : UInt64)
68+
(hbnd : base.toNat + 8 * (n + 1) ≤ 4294967296)
69+
(hfill : m.words64 base n = List.replicate n v) :
70+
(m.write64 (base + 8 * UInt32.ofNat n) v).words64 base (n + 1) = List.replicate (n + 1) v := by
71+
have hsize : (UInt32.size : Nat) = 4294967296 := rfl
72+
have hkn : (UInt32.ofNat n).toNat = n :=
73+
UInt32.toNat_ofNat_of_lt' (by omega : n < UInt32.size)
74+
have haddr : (base + 8 * UInt32.ofNat n).toNat = base.toNat + 8 * n := by
75+
have := MemRegion.slot64_base_toNat base (UInt32.ofNat n) (by rw [hkn]; omega)
76+
rw [hkn] at this; exact this
77+
rw [Mem.words64_succ,
78+
Mem.words64_write64_outside m base n _ v (by omega) (Or.inr (by rw [haddr])),
79+
hfill, Mem.read64_write64_same, List.replicate_succ']
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import CodeLib.RustStd.MemArray
2+
import Interpreter.Wasm.Wp.Tactic
3+
import Interpreter.Wasm.Wp.Block
4+
import Interpreter.Wasm.Wp.Loop
5+
6+
/-!
7+
# A universally-quantified loop-over-memory proof
8+
9+
Every memory example in `interpreter/.../Examples/` is a concrete
10+
`native_decide` check, because symbolic memory framing lives here in `CodeLib`,
11+
downstream of the interpreter. This file closes that gap with the first proof
12+
that a **loop** establishes a property of a **whole memory region for all `n`**:
13+
the canonical fill loop writes `v` to each of the `n` `u64` slots of
14+
`[base, base + 8n)`, and afterwards `mem.words64 base n = replicate n v`.
15+
16+
It exercises the invariant/variant loop rule (`wp_loop_cons`), the `MemRegion`
17+
framing algebra, and the `words64` view together — the exact shape a
18+
memory-mutating corpus proof (e.g. `merge_sort`) needs. -/
19+
20+
namespace Wasm
21+
22+
/-- Fill loop. Params `base : i32`, `n : i32`, `v : i64`; local `i : i32`.
23+
Writes `v` to `mem[base + 8*i]` for `i = 0 … n-1`. Structure mirrors the
24+
`SimpleLoop` example's while-loop idiom. -/
25+
def FillWords : Program := [
26+
.const 0, .localSet 3,
27+
.loop 0 0 [
28+
.block 0 0 [
29+
.block 0 0 [
30+
.localGet 3, .localGet 1, .ltU, .br_if 0,
31+
.br 1
32+
],
33+
.localGet 0, .localGet 3, .const 3, .shl, .add,
34+
.localGet 2, .store64 0,
35+
.localGet 3, .const 1, .add, .localSet 3,
36+
.br 1 ] ]
37+
]
38+
39+
set_option maxHeartbeats 1000000 in
40+
/-- Running `FillWords` on a store whose memory is large enough to hold the
41+
array (and within the wasm32 page cap, so element addresses do not wrap)
42+
terminates with `[base, base + 8n)` filled with `v` — stated over the whole
43+
region via `Mem.words64`. -/
44+
theorem fillWords_spec (m : Module) (st : Store Unit) (base n : UInt32) (v : UInt64)
45+
(hbnd : base.toNat + 8 * n.toNat ≤ st.mem.pages * 65536)
46+
(hpages : st.mem.pages ≤ 65536) :
47+
wp m FillWords
48+
(fun c => ∃ st' s', c = .Fallthrough st' s'
49+
∧ st'.mem.words64 base n.toNat = List.replicate n.toNat v
50+
∧ st'.mem.pages = st.mem.pages)
51+
st { params := [.i32 base, .i32 n, .i64 v], locals := [.i32 0], values := [] } := by
52+
have hcap : st.mem.pages * 655364294967296 := by
53+
have := Nat.mul_le_mul_right 65536 hpages; omega
54+
unfold FillWords
55+
wp_run
56+
simp
57+
apply wp_loop_cons
58+
(Inv := fun st' s' => ∃ i : UInt32,
59+
s' = ⟨[.i32 base, .i32 n, .i64 v], [.i32 i], []⟩
60+
∧ i.toNat ≤ n.toNat
61+
∧ st'.mem.words64 base i.toNat = List.replicate i.toNat v
62+
∧ st'.mem.pages = st.mem.pages)
63+
(μ := fun _ s' => match s'.locals.headD (.i32 0) with | .i32 i => n.toNat - i.toNat | _ => 0)
64+
· -- initial: i = 0, region empty
65+
exact ⟨0, rfl, by simp, by simp [Mem.words64], rfl⟩
66+
· -- step
67+
rintro st' s' ⟨i, rfl, hile, hfill, hpg⟩
68+
apply wp_block_cons
69+
apply wp_block_cons
70+
wp_run
71+
simp
72+
by_cases hlt : i < n
73+
· -- body: write slot i, increment
74+
have hilt : i.toNat < n.toNat := hlt
75+
have hoi : UInt32.ofNat i.toNat = i := by simp [UInt32.ofNat_toNat]
76+
have hmod1 : (1 + i.toNat) % 4294967296 = i.toNat + 1 := by
77+
rw [Nat.mod_eq_of_lt (by have := n.toNat_lt; omega)]; omega
78+
have hshlN : i.toNat <<< 3 = i.toNat * 8 := by rw [Nat.shiftLeft_eq]
79+
have hshlU : i <<< 3 = 8 * i := by bv_decide
80+
simp only [hlt, ↓reduceIte, hshlN, hmod1]
81+
refine ⟨?_, ⟨?_, ?_, hpg⟩, ?_⟩
82+
· rw [Nat.mod_eq_of_lt (by omega)]; omega
83+
· omega
84+
· rw [hshlU, show 8 * i + base = base + 8 * UInt32.ofNat i.toNat by rw [hoi]; bv_decide]
85+
exact Mem.words64_write64_extend st'.mem base i.toNat v (by omega) hfill
86+
· omega
87+
· -- exit: i ≥ n, so i = n; region already fully filled
88+
have hin : i.toNat = n.toNat := by
89+
have : ¬ i.toNat < n.toNat := hlt
90+
omega
91+
simp only [hlt, ↓reduceIte]
92+
rw [← hin]; exact ⟨hfill, hpg⟩
93+
94+
end Wasm

0 commit comments

Comments
 (0)