Skip to content

Commit 36dc446

Browse files
mfornetclaude
authored andcommitted
codelib: address review — frame condition, slot-addr helper, doc + reuse fixes
Follow-up to review of the words64 array view + fill-loop proof: - fillWords_spec now carries a frame condition: every byte outside `[base, base+8n)` is preserved (postcondition + loop invariant), so the theorem composes with facts about neighbouring memory (the merge_sort shape). - Extract `Mem.words64_slotAddr_toNat`, the shared no-wrap slot-address bridge, and use it in `words64_write64_outside` / `_extend` (was duplicated inline). - FillWords' `(const 3) shl` step reuses `MemRegion.shl3_eq_mul8` instead of a fresh `bv_decide`. - MemArray module docstring: drop the stale `words64_write64_set` reference (the lemma is `words64_write64_extend`) and correct the simp-lemma note (`getElem_words64` is intentionally not `@[simp]`). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7e38771 commit 36dc446

2 files changed

Lines changed: 47 additions & 29 deletions

File tree

codelib/CodeLib/RustStd/MemArray.lean

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@ readability). `Mem.words64 base n` is the length-`n` list of words at
88
`base, base+8, …, base+8(n−1)`, so a spec can say `m.words64 base n = vs`
99
instead of `∀ k < n, m.read64 (base + 8*k) = vs[k]`.
1010
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`).
11+
The view is defined via `List.range`/`map`, so its length is a `simp`-lemma
12+
(`length_words64`) and indexing rewrites through `getElem_words64` (kept off
13+
`simp` because of its bounds side-goal). Its interaction with `write64` factors
14+
through the `MemRegion` framing algebra: a write disjoint from the array leaves
15+
the view unchanged (`words64_write64_outside`), and writing `v` to the next slot
16+
past a `v`-filled prefix extends the fill by one (`words64_write64_extend`).
1617
-/
1718

1819
namespace Wasm
@@ -38,6 +39,18 @@ theorem Mem.words64_ext {m m' : Mem} {base : UInt32} {n : Nat}
3839
simp only [length_words64] at hk
3940
rw [getElem_words64 m base n k hk, getElem_words64 m' base n k hk, h k hk]
4041

42+
/-- The wasm address of the `k`-th `u64` slot, `base + 8 * k`, is the integer
43+
`base.toNat + 8 * k` as long as it does not wrap. Shared address bridge for the
44+
framing lemmas below (and their loop consumers). -/
45+
theorem Mem.words64_slotAddr_toNat (base : UInt32) (k : Nat)
46+
(h : base.toNat + 8 * k < 4294967296) :
47+
(base + 8 * UInt32.ofNat k).toNat = base.toNat + 8 * k := by
48+
have hsize : (UInt32.size : Nat) = 4294967296 := rfl
49+
have hkn : (UInt32.ofNat k).toNat = k :=
50+
UInt32.toNat_ofNat_of_lt' (by omega : k < UInt32.size)
51+
have := MemRegion.slot64_base_toNat base (UInt32.ofNat k) (by rw [hkn]; omega)
52+
rw [hkn] at this; exact this
53+
4154
/-- Under no address wraparound, a `write64` whose target slot `j` is `≥ n`
4255
(i.e. outside the array `[base, base+8n)`) leaves the view unchanged. -/
4356
theorem Mem.words64_write64_outside (m : Mem) (base : UInt32) (n : Nat) (a : UInt32) (v : UInt64)
@@ -46,13 +59,7 @@ theorem Mem.words64_write64_outside (m : Mem) (base : UInt32) (n : Nat) (a : UIn
4659
(m.write64 a v).words64 base n = m.words64 base n := by
4760
apply words64_ext
4861
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
62+
have haddr := Mem.words64_slotAddr_toNat base k (by omega)
5663
exact Mem.read64_write64_disjoint m a _ v (by rw [haddr]; omega)
5764

5865
/-- One more word: `words64 base (n+1)` is `words64 base n` with the `n`-th
@@ -68,12 +75,7 @@ theorem Mem.words64_write64_extend (m : Mem) (base : UInt32) (n : Nat) (v : UInt
6875
(hbnd : base.toNat + 8 * (n + 1) ≤ 4294967296)
6976
(hfill : m.words64 base n = List.replicate n v) :
7077
(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
78+
have haddr := Mem.words64_slotAddr_toNat base n (by omega)
7779
rw [Mem.words64_succ,
7880
Mem.words64_write64_outside m base n _ v (by omega) (Or.inr (by rw [haddr])),
7981
hfill, Mem.read64_write64_same, List.replicate_succ']

codelib/CodeLib/RustStd/MemFillLoop.lean

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ Every memory example in `interpreter/.../Examples/` is a concrete
1111
downstream of the interpreter. This file closes that gap with the first proof
1212
that a **loop** establishes a property of a **whole memory region for all `n`**:
1313
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`.
14+
`[base, base + 8n)`, and afterwards `mem.words64 base n = replicate n v` while
15+
every byte *outside* `[base, base + 8n)` is left untouched (the frame
16+
condition, so the theorem composes with facts about neighbouring memory).
1517
1618
It exercises the invariant/variant loop rule (`wp_loop_cons`), the `MemRegion`
1719
framing algebra, and the `words64` view together — the exact shape a
@@ -40,14 +42,16 @@ set_option maxHeartbeats 1000000 in
4042
/-- Running `FillWords` on a store whose memory is large enough to hold the
4143
array (and within the wasm32 page cap, so element addresses do not wrap)
4244
terminates with `[base, base + 8n)` filled with `v` — stated over the whole
43-
region via `Mem.words64`. -/
45+
region via `Mem.words64` — and every byte outside the region left unchanged. -/
4446
theorem fillWords_spec (m : Module) (st : Store Unit) (base n : UInt32) (v : UInt64)
4547
(hbnd : base.toNat + 8 * n.toNat ≤ st.mem.pages * 65536)
4648
(hpages : st.mem.pages ≤ 65536) :
4749
wp m FillWords
4850
(fun c => ∃ st' s', c = .Fallthrough st' s'
4951
∧ st'.mem.words64 base n.toNat = List.replicate n.toNat v
50-
∧ st'.mem.pages = st.mem.pages)
52+
∧ st'.mem.pages = st.mem.pages
53+
∧ ∀ a : Nat, (a < base.toNat ∨ base.toNat + 8 * n.toNat ≤ a) →
54+
st'.mem.bytes a = st.mem.bytes a)
5155
st { params := [.i32 base, .i32 n, .i64 v], locals := [.i32 0], values := [] } := by
5256
have hcap : st.mem.pages * 655364294967296 := by
5357
have := Nat.mul_le_mul_right 65536 hpages; omega
@@ -59,12 +63,14 @@ theorem fillWords_spec (m : Module) (st : Store Unit) (base n : UInt32) (v : UIn
5963
s' = ⟨[.i32 base, .i32 n, .i64 v], [.i32 i], []⟩
6064
∧ i.toNat ≤ n.toNat
6165
∧ st'.mem.words64 base i.toNat = List.replicate i.toNat v
62-
∧ st'.mem.pages = st.mem.pages)
66+
∧ st'.mem.pages = st.mem.pages
67+
∧ ∀ a : Nat, (a < base.toNat ∨ base.toNat + 8 * n.toNat ≤ a) →
68+
st'.mem.bytes a = st.mem.bytes a)
6369
(μ := 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⟩
70+
· -- initial: i = 0, region empty, memory untouched
71+
exact ⟨0, rfl, by simp, by simp [Mem.words64], rfl, fun a _ => rfl
6672
· -- step
67-
rintro st' s' ⟨i, rfl, hile, hfill, hpg⟩
73+
rintro st' s' ⟨i, rfl, hile, hfill, hpg, hframe
6874
apply wp_block_cons
6975
apply wp_block_cons
7076
wp_run
@@ -76,19 +82,29 @@ theorem fillWords_spec (m : Module) (st : Store Unit) (base n : UInt32) (v : UIn
7682
have hmod1 : (1 + i.toNat) % 4294967296 = i.toNat + 1 := by
7783
rw [Nat.mod_eq_of_lt (by have := n.toNat_lt; omega)]; omega
7884
have hshlN : i.toNat <<< 3 = i.toNat * 8 := by rw [Nat.shiftLeft_eq]
79-
have hshlU : i <<< 3 = 8 * i := by bv_decide
85+
-- The `(const 3) shl` address computation is the `MemRegion` slot bridge.
86+
have hshlU : i <<< 3 = 8 * i := MemRegion.shl3_eq_mul8 i
87+
have haddrU : i <<< 3 + base = base + 8 * UInt32.ofNat i.toNat := by
88+
rw [hshlU, hoi]; bv_decide
89+
have haddrN : (i <<< 3 + base).toNat = base.toNat + 8 * i.toNat := by
90+
rw [haddrU]; exact Mem.words64_slotAddr_toNat base i.toNat (by omega)
8091
simp only [hlt, ↓reduceIte, hshlN, hmod1]
81-
refine ⟨?_, ⟨?_, ?_, hpg⟩, ?_⟩
92+
refine ⟨?_, ⟨?_, ?_, hpg, ?_⟩, ?_⟩
8293
· rw [Nat.mod_eq_of_lt (by omega)]; omega
8394
· omega
84-
· rw [hshlU, show 8 * i + base = base + 8 * UInt32.ofNat i.toNat by rw [hoi]; bv_decide]
95+
· rw [haddrU]
8596
exact Mem.words64_write64_extend st'.mem base i.toNat v (by omega) hfill
97+
· -- frame: the write lands in `[base, base+8n)`, so bytes outside are kept
98+
intro a ha
99+
rw [Mem.write64_bytes_of_disjoint st'.mem (i <<< 3 + base) v a (by rw [haddrN]; omega)]
100+
exact hframe a ha
86101
· omega
87102
· -- exit: i ≥ n, so i = n; region already fully filled
88103
have hin : i.toNat = n.toNat := by
89104
have : ¬ i.toNat < n.toNat := hlt
90105
omega
91106
simp only [hlt, ↓reduceIte]
92-
rw [← hin]; exact ⟨hfill, hpg⟩
107+
refine ⟨?_, hpg, hframe⟩
108+
rw [← hin]; exact hfill
93109

94110
end Wasm

0 commit comments

Comments
 (0)