Skip to content

Commit 8a53e4d

Browse files
theebayuserclaude
andcommitted
codelib: words32/slot32 twins + verified u32 copy loop
Phase 2c of #68 — the 32-bit twins the [u32] memory proofs (merge_sort, #106) need, plus their first consumer. - MemRegion.slot32 (+ shl2_eq_mul4, slot32_base_toNat, slot32_disjoint): the 4-byte element-slot twin of slot64. - Mem.words32: the List UInt32 view of [base, base+4n), in the same (List.range n).map shape as #106's private `wordsAt`, with length/getElem/ext/write32_outside/succ and a general words32_write32_snoc (write the n-th slot ⇒ append its value to the length-n view). - copyWords_spec (MemCopyLoop.lean): the companion to #139's fillWords_spec. The canonical load32/store32 copy loop over two disjoint u32 regions is proved, for all n, to leave `words32 dst n = words32 src n` with the source unchanged — what LLVM emits for a small opt-0 copy_from_slice, and the analogue of merge_sort's copy helpers. Invariant discharged via words32_write32_snoc (dst) + words32_write32_outside (src, MemRegion disjointness); axiom-clean. Refs #68, #106. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 316fa96 commit 8a53e4d

4 files changed

Lines changed: 211 additions & 0 deletions

File tree

codelib/CodeLib.lean

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import CodeLib.RustStd.Frame
77
import CodeLib.RustStd.Region
88
import CodeLib.RustStd.MemArray
99
import CodeLib.RustStd.MemFillLoop
10+
import CodeLib.RustStd.MemCopyLoop
1011
import CodeLib.RustStd.UInt
1112
import CodeLib.RustStd.U64.Basic
1213
import CodeLib.RustStd.U64.AbsDiff

codelib/CodeLib/RustStd/MemArray.lean

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,66 @@ theorem Mem.words64_write64_extend (m : Mem) (base : UInt32) (n : Nat) (v : UInt
7777
rw [Mem.words64_succ,
7878
Mem.words64_write64_outside m base n _ v (by omega) (Or.inr (by rw [haddr])),
7979
hfill, Mem.read64_write64_same, List.replicate_succ']
80+
81+
/-! ## 32-bit twin
82+
83+
`Mem.words32` is the `u32` array view, matching the element stride of
84+
`MemRegion.slot32` and the `wordsAt` view used in memory-based `[u32]` specs. -/
85+
86+
/-- The `List UInt32` view of the `u32` array `[base, base + 4*n)`. -/
87+
def Mem.words32 (m : Mem) (base : UInt32) (n : Nat) : List UInt32 :=
88+
(List.range n).map fun k => m.read32 (base + 4 * (UInt32.ofNat k))
89+
90+
@[simp] theorem Mem.length_words32 (m : Mem) (base : UInt32) (n : Nat) :
91+
(m.words32 base n).length = n := by
92+
simp [Mem.words32]
93+
94+
theorem Mem.getElem_words32 (m : Mem) (base : UInt32) (n k : Nat) (h : k < n) :
95+
(m.words32 base n)[k]'(by simpa using h) = m.read32 (base + 4 * UInt32.ofNat k) := by
96+
simp [Mem.words32]
97+
98+
/-- Two `u32` array views agree iff their words agree pointwise. -/
99+
theorem Mem.words32_ext {m m' : Mem} {base : UInt32} {n : Nat}
100+
(h : ∀ k < n, m.read32 (base + 4 * UInt32.ofNat k) = m'.read32 (base + 4 * UInt32.ofNat k)) :
101+
m.words32 base n = m'.words32 base n := by
102+
apply List.ext_getElem (by simp)
103+
intro k hk _
104+
simp only [length_words32] at hk
105+
rw [getElem_words32 m base n k hk, getElem_words32 m' base n k hk, h k hk]
106+
107+
/-- A `write32` disjoint from the whole `[base, base+4n)` region leaves the
108+
view unchanged. -/
109+
theorem Mem.words32_write32_outside (m : Mem) (base : UInt32) (n : Nat) (a v : UInt32)
110+
(hbnd : base.toNat + 4 * n ≤ 4294967296)
111+
(hout : a.toNat + 4 ≤ base.toNat ∨ base.toNat + 4 * n ≤ a.toNat) :
112+
(m.write32 a v).words32 base n = m.words32 base n := by
113+
apply words32_ext
114+
intro k hk
115+
have hkn : (UInt32.ofNat k).toNat = k :=
116+
UInt32.toNat_ofNat_of_lt' (by have : (UInt32.size : Nat) = 4294967296 := rfl; omega)
117+
have haddr : (base + 4 * UInt32.ofNat k).toNat = base.toNat + 4 * k := by
118+
have := MemRegion.slot32_base_toNat base (UInt32.ofNat k) (by rw [hkn]; omega)
119+
rw [hkn] at this; exact this
120+
exact Mem.read32_write32_disjoint m a _ v (by rw [haddr]; omega)
121+
122+
/-- One more word: `words32 base (n+1)` is `words32 base n` with the `n`-th
123+
word appended. -/
124+
theorem Mem.words32_succ (m : Mem) (base : UInt32) (n : Nat) :
125+
m.words32 base (n + 1) = m.words32 base n ++ [m.read32 (base + 4 * UInt32.ofNat n)] := by
126+
simp [Mem.words32, List.range_succ, List.map_append]
127+
128+
/-- Writing the `n`-th slot with `w` appends `w` to the length-`n` view (the
129+
earlier words are framed away). The store-into-a-fresh-slot step shared by
130+
copy/fill loops over a `u32` array. -/
131+
theorem Mem.words32_write32_snoc (m : Mem) (base : UInt32) (n : Nat) (w : UInt32)
132+
(hbnd : base.toNat + 4 * (n + 1) ≤ 4294967296) :
133+
(m.write32 (base + 4 * UInt32.ofNat n) w).words32 base (n + 1)
134+
= m.words32 base n ++ [w] := by
135+
have hkn : (UInt32.ofNat n).toNat = n :=
136+
UInt32.toNat_ofNat_of_lt' (by have : (UInt32.size : Nat) = 4294967296 := rfl; omega)
137+
have haddr : (base + 4 * UInt32.ofNat n).toNat = base.toNat + 4 * n := by
138+
have := MemRegion.slot32_base_toNat base (UInt32.ofNat n) (by rw [hkn]; omega)
139+
rw [hkn] at this; exact this
140+
rw [Mem.words32_succ,
141+
Mem.words32_write32_outside m base n _ w (by omega) (Or.inr (by rw [haddr])),
142+
Mem.read32_write32_same]
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 verified copy loop over a `u32` array
8+
9+
The companion to `MemFillLoop`: the canonical word-by-word copy loop reads each
10+
of the `n` `u32` words of `[src, src+4n)` and writes it to `[dst, dst+4n)`, and
11+
afterwards the destination view equals the (unchanged) source view —
12+
13+
```
14+
st'.mem.words32 dst n = st.mem.words32 src n
15+
```
16+
17+
for all `n`, given the two regions are disjoint. This is the shape LLVM emits
18+
for a small `copy_from_slice` at `opt-level = 0` (a `load`/`store` loop, not a
19+
`memory.copy`), and the direct analogue of `merge_sort`'s copy helpers. It
20+
exercises the invariant/variant loop rule together with the `words32` view and
21+
`MemRegion` disjointness. -/
22+
23+
namespace Wasm
24+
25+
/-- Copy loop. Params `dst : i32`, `src : i32`, `n : i32`; local `i : i32`.
26+
Copies `mem[src + 4*i]` to `mem[dst + 4*i]` for `i = 0 … n-1`. -/
27+
def CopyWords : Program := [
28+
.const 0, .localSet 3,
29+
.loop 0 0 [
30+
.block 0 0 [
31+
.block 0 0 [
32+
.localGet 3, .localGet 2, .ltU, .br_if 0,
33+
.br 1
34+
],
35+
.localGet 0, .localGet 3, .const 2, .shl, .add,
36+
.localGet 1, .localGet 3, .const 2, .shl, .add,
37+
.load32 0, .store32 0,
38+
.localGet 3, .const 1, .add, .localSet 3,
39+
.br 1 ] ]
40+
]
41+
42+
set_option maxHeartbeats 1000000 in
43+
/-- Running `CopyWords` on a store where the two `u32` arrays are addressable,
44+
within the wasm32 page cap (no wraparound), and **disjoint** terminates with the
45+
destination holding a copy of the source, the source unchanged. -/
46+
theorem copyWords_spec (m : Module) (st : Store Unit) (dst src n : UInt32)
47+
(hsrc : src.toNat + 4 * n.toNat ≤ st.mem.pages * 65536)
48+
(hdst : dst.toNat + 4 * n.toNat ≤ st.mem.pages * 65536)
49+
(hpages : st.mem.pages ≤ 65536)
50+
(hdisj : dst.toNat + 4 * n.toNat ≤ src.toNat ∨ src.toNat + 4 * n.toNat ≤ dst.toNat) :
51+
wp m CopyWords
52+
(fun c => ∃ st' s', c = .Fallthrough st' s'
53+
∧ st'.mem.words32 dst n.toNat = st.mem.words32 src n.toNat
54+
∧ st'.mem.words32 src n.toNat = st.mem.words32 src n.toNat
55+
∧ st'.mem.pages = st.mem.pages)
56+
st { params := [.i32 dst, .i32 src, .i32 n], locals := [.i32 0], values := [] } := by
57+
have hcap : st.mem.pages * 655364294967296 := by
58+
have := Nat.mul_le_mul_right 65536 hpages; omega
59+
unfold CopyWords
60+
wp_run
61+
simp
62+
apply wp_loop_cons
63+
(Inv := fun st' s' => ∃ i : UInt32,
64+
s' = ⟨[.i32 dst, .i32 src, .i32 n], [.i32 i], []⟩
65+
∧ i.toNat ≤ n.toNat
66+
∧ st'.mem.words32 dst i.toNat = st'.mem.words32 src i.toNat
67+
∧ st'.mem.words32 src n.toNat = st.mem.words32 src n.toNat
68+
∧ st'.mem.pages = st.mem.pages)
69+
(μ := fun _ s' => match s'.locals.headD (.i32 0) with | .i32 i => n.toNat - i.toNat | _ => 0)
70+
· exact ⟨0, rfl, by simp, by simp [Mem.words32], rfl, rfl⟩
71+
· rintro st' s' ⟨i, rfl, hile, hcopy, hsrceq, hpg⟩
72+
apply wp_block_cons
73+
apply wp_block_cons
74+
wp_run
75+
simp
76+
by_cases hlt : i < n
77+
· -- body: copy word i
78+
have hilt : i.toNat < n.toNat := hlt
79+
have hoi : UInt32.ofNat i.toNat = i := by simp [UInt32.ofNat_toNat]
80+
have hshlU : i <<< 2 = 4 * i := by bv_decide
81+
have hmod1 : (1 + i.toNat) % 4294967296 = i.toNat + 1 := by
82+
rw [Nat.mod_eq_of_lt (by have := n.toNat_lt; omega)]; omega
83+
have hshlN : i.toNat <<< 2 = i.toNat * 4 := by rw [Nat.shiftLeft_eq]
84+
have haddr_d : 4 * i + dst = dst + 4 * UInt32.ofNat i.toNat := by rw [hoi]; bv_decide
85+
have haddr_s : 4 * i + src = src + 4 * UInt32.ofNat i.toNat := by rw [hoi]; bv_decide
86+
have hda : (dst + 4 * UInt32.ofNat i.toNat).toNat = dst.toNat + 4 * i.toNat := by
87+
rw [hoi]; have := MemRegion.slot32_base_toNat dst i (by omega)
88+
simpa [MemRegion.slot32] using this
89+
simp only [hlt, ↓reduceIte, hshlU, hshlN, hmod1, haddr_d, haddr_s]
90+
set w : UInt32 := st'.mem.read32 (src + 4 * UInt32.ofNat i.toNat) with hw
91+
refine ⟨?_, ?_, ⟨?_, ?_, ?_, ?_⟩, ?_⟩
92+
· rw [Nat.mod_eq_of_lt (by omega)]; omega -- load in bounds
93+
· rw [Nat.mod_eq_of_lt (by omega)]; omega -- store in bounds
94+
· omega -- i+1 ≤ n
95+
· -- words32 dst (i+1) = words32 src (i+1) at the new store
96+
rw [Mem.words32_write32_snoc st'.mem dst i.toNat w (by omega),
97+
Mem.words32_write32_outside st'.mem src (i.toNat + 1)
98+
(dst + 4 * UInt32.ofNat i.toNat) w (by omega) (by rw [hda]; omega),
99+
Mem.words32_succ, hcopy]
100+
· -- words32 src n unchanged
101+
rw [Mem.words32_write32_outside st'.mem src n.toNat
102+
(dst + 4 * UInt32.ofNat i.toNat) w (by omega) (by rw [hda]; omega), hsrceq]
103+
· exact hpg -- pages
104+
· omega -- variant
105+
· -- exit: i = n, dst view already equals src view
106+
have hin : i.toNat = n.toNat := by
107+
have : ¬ i.toNat < n.toNat := hlt
108+
omega
109+
simp only [hlt, ↓reduceIte]
110+
rw [hin] at hcopy
111+
exact ⟨hcopy.trans hsrceq, hsrceq, hpg⟩
112+
113+
end Wasm

codelib/CodeLib/RustStd/Region.lean

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,40 @@ theorem slot64_disjoint (base k l : UInt32)
188188
simp only [slot64]
189189
omega
190190

191+
/-- The `k`-th 4-byte slot of a `u32` array based at `base` (wasm address
192+
`base + 4 * k`). The 32-bit twin of `slot64`, matching the `wordsAt`/`words32`
193+
element stride. -/
194+
def slot32 (base k : UInt32) : MemRegion := ⟨base + 4 * k, 4
195+
196+
/-- `x <<< 2 = 4 * x` on `UInt32`: the `(const 2) shl` address computation LLVM
197+
emits for a `u32` array index. -/
198+
theorem shl2_eq_mul4 (x : UInt32) : x <<< (2 % 32 : UInt32) = 4 * x := by bv_decide
199+
200+
/-- The codegen's `(k <<< 2) + base` lands on the slot base address. -/
201+
theorem slot32_of_shl (base k : UInt32) :
202+
k <<< (2 % 32 : UInt32) + base = (slot32 base k).base := by
203+
simp only [slot32]; bv_decide
204+
205+
/-- No wraparound: if the slot's true byte offset stays below `2^32`, the wasm
206+
address of `slot32 base k` is the integer `base.toNat + 4 * k.toNat`. -/
207+
theorem slot32_base_toNat (base k : UInt32)
208+
(h : base.toNat + 4 * k.toNat < 4294967296) :
209+
(slot32 base k).base.toNat = base.toNat + 4 * k.toNat := by
210+
simp only [slot32, UInt32.toNat_add, UInt32.toNat_mul, UInt32.reduceToNat]
211+
omega
212+
213+
/-- Distinct in-bounds element slots of a no-wrap `u32` array are disjoint. -/
214+
theorem slot32_disjoint (base k l : UInt32)
215+
(hk : base.toNat + 4 * k.toNat < 4294967296)
216+
(hl : base.toNat + 4 * l.toNat < 4294967296)
217+
(hkl : k ≠ l) :
218+
(slot32 base k).Disjoint (slot32 base l) := by
219+
unfold Disjoint
220+
rw [slot32_base_toNat base k hk, slot32_base_toNat base l hl]
221+
have : k.toNat ≠ l.toNat := fun he => hkl (UInt32.toNat.inj he)
222+
simp only [slot32]
223+
omega
224+
191225
end MemRegion
192226

193227
end Wasm

0 commit comments

Comments
 (0)