Skip to content

Commit b9cd620

Browse files
committed
Merge upstream/main (post cajal-technologies#133 iris-lean SwapElementsSpec)
2 parents 4791800 + 910fbfa commit b9cd620

22 files changed

Lines changed: 2372 additions & 126 deletions

codelib/CodeLib.lean

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import CodeLib.RustStd.Frame
88
import CodeLib.RustStd.Region
99
import CodeLib.RustStd.MemArray
1010
import CodeLib.RustStd.MemFillLoop
11+
import CodeLib.RustStd.MemCopyLoop
1112
import CodeLib.RustStd.UInt
1213
import CodeLib.RustStd.U64.Basic
1314
import CodeLib.RustStd.U64.AbsDiff
@@ -30,6 +31,10 @@ import CodeLib.Near.State
3031
import CodeLib.Near.Env
3132
import CodeLib.Near.Proof
3233
import CodeLib.IEEE32.Exec
34+
import CodeLib.SepLogic.WasmHeap
35+
import CodeLib.SepLogic.WasmRules
36+
import CodeLib.SepLogic.WasmWP
37+
import CodeLib.SepLogic.Adequacy
3338

3439
/-!
3540
# CodeLib — umbrella import for downstream code

codelib/CodeLib/RustStd/MemArray.lean

Lines changed: 85 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,94 @@ theorem Mem.words64_succ (m : Mem) (base : UInt32) (n : Nat) :
136136
m.words64 base (n + 1) = m.words64 base n ++ [m.read64 (base + 8 * UInt32.ofNat n)] := by
137137
simp [Mem.words64, List.range_succ, List.map_append]
138138

139+
/-- Writing the `n`-th slot with `w` appends `w` to the length-`n` view (the
140+
earlier words are framed away). The store-into-a-fresh-slot step shared by
141+
copy/fill loops over a `u64` array; the 64-bit twin of
142+
`Mem.words32_write32_snoc`. -/
143+
theorem Mem.words64_write64_snoc (m : Mem) (base : UInt32) (n : Nat) (w : UInt64)
144+
(hbnd : base.toNat + 8 * (n + 1) ≤ 4294967296) :
145+
(m.write64 (base + 8 * UInt32.ofNat n) w).words64 base (n + 1)
146+
= m.words64 base n ++ [w] := by
147+
have haddr := Mem.words64_slotAddr_toNat base n (by omega)
148+
rw [Mem.words64_succ,
149+
Mem.words64_write64_outside m base n _ w (by omega) (Or.inr (by rw [haddr])),
150+
Mem.read64_write64_same]
151+
139152
/-- The fill step, as a view equation: if the first `n` words are already `v`
140153
and slot `n` is written with `v`, the first `n+1` words are `v`. This is the
141-
loop invariant's inductive step, discharged once here. -/
154+
loop invariant's inductive step; the `replicate`-specialised corollary of
155+
`Mem.words64_write64_snoc`. -/
142156
theorem Mem.words64_write64_extend (m : Mem) (base : UInt32) (n : Nat) (v : UInt64)
143157
(hbnd : base.toNat + 8 * (n + 1) ≤ 4294967296)
144158
(hfill : m.words64 base n = List.replicate n v) :
145159
(m.write64 (base + 8 * UInt32.ofNat n) v).words64 base (n + 1) = List.replicate (n + 1) v := by
146-
have haddr := Mem.words64_slotAddr_toNat base n (by omega)
147-
rw [Mem.words64_succ,
148-
Mem.words64_write64_outside m base n _ v (by omega) (Or.inr (by rw [haddr])),
149-
hfill, Mem.read64_write64_same, List.replicate_succ']
160+
rw [Mem.words64_write64_snoc m base n v hbnd, hfill, List.replicate_succ']
161+
162+
/-! ## 32-bit twin
163+
164+
`Mem.words32` is the `u32` array view, matching the element stride of
165+
`MemRegion.slot32` (and of the `wordsAt` view carried by the merge_sort work
166+
in PR #106, so that file can become an import — `wordsAt` is not in-tree yet). -/
167+
168+
/-- The `List UInt32` view of the `u32` array `[base, base + 4*n)`. -/
169+
def Mem.words32 (m : Mem) (base : UInt32) (n : Nat) : List UInt32 :=
170+
(List.range n).map fun k => m.read32 (base + 4 * (UInt32.ofNat k))
171+
172+
@[simp] theorem Mem.length_words32 (m : Mem) (base : UInt32) (n : Nat) :
173+
(m.words32 base n).length = n := by
174+
simp [Mem.words32]
175+
176+
theorem Mem.getElem_words32 (m : Mem) (base : UInt32) (n k : Nat) (h : k < n) :
177+
(m.words32 base n)[k]'(by simpa using h) = m.read32 (base + 4 * UInt32.ofNat k) := by
178+
simp [Mem.words32]
179+
180+
/-- Two `u32` array views agree iff their words agree pointwise. -/
181+
theorem Mem.words32_ext {m m' : Mem} {base : UInt32} {n : Nat}
182+
(h : ∀ k < n, m.read32 (base + 4 * UInt32.ofNat k) = m'.read32 (base + 4 * UInt32.ofNat k)) :
183+
m.words32 base n = m'.words32 base n := by
184+
apply List.ext_getElem (by simp)
185+
intro k hk _
186+
simp only [length_words32] at hk
187+
rw [getElem_words32 m base n k hk, getElem_words32 m' base n k hk, h k hk]
188+
189+
/-- The wasm address of the `k`-th `u32` slot, `base + 4 * k`, is the integer
190+
`base.toNat + 4 * k` as long as it does not wrap. Shared address bridge for the
191+
framing lemmas below (and their loop consumers); the 32-bit twin of
192+
`Mem.words64_slotAddr_toNat`. -/
193+
theorem Mem.words32_slotAddr_toNat (base : UInt32) (k : Nat)
194+
(h : base.toNat + 4 * k < 4294967296) :
195+
(base + 4 * UInt32.ofNat k).toNat = base.toNat + 4 * k := by
196+
have hsize : (UInt32.size : Nat) = 4294967296 := rfl
197+
have hkn : (UInt32.ofNat k).toNat = k :=
198+
UInt32.toNat_ofNat_of_lt' (by omega : k < UInt32.size)
199+
have := MemRegion.slot32_base_toNat base (UInt32.ofNat k) (by rw [hkn]; omega)
200+
rw [hkn] at this; exact this
201+
202+
/-- A `write32` disjoint from the whole `[base, base+4n)` region leaves the
203+
view unchanged. -/
204+
theorem Mem.words32_write32_outside (m : Mem) (base : UInt32) (n : Nat) (a v : UInt32)
205+
(hbnd : base.toNat + 4 * n ≤ 4294967296)
206+
(hout : a.toNat + 4 ≤ base.toNat ∨ base.toNat + 4 * n ≤ a.toNat) :
207+
(m.write32 a v).words32 base n = m.words32 base n := by
208+
apply words32_ext
209+
intro k hk
210+
have haddr := Mem.words32_slotAddr_toNat base k (by omega)
211+
exact Mem.read32_write32_disjoint m a _ v (by rw [haddr]; omega)
212+
213+
/-- One more word: `words32 base (n+1)` is `words32 base n` with the `n`-th
214+
word appended. -/
215+
theorem Mem.words32_succ (m : Mem) (base : UInt32) (n : Nat) :
216+
m.words32 base (n + 1) = m.words32 base n ++ [m.read32 (base + 4 * UInt32.ofNat n)] := by
217+
simp [Mem.words32, List.range_succ, List.map_append]
218+
219+
/-- Writing the `n`-th slot with `w` appends `w` to the length-`n` view (the
220+
earlier words are framed away). The store-into-a-fresh-slot step shared by
221+
copy/fill loops over a `u32` array. -/
222+
theorem Mem.words32_write32_snoc (m : Mem) (base : UInt32) (n : Nat) (w : UInt32)
223+
(hbnd : base.toNat + 4 * (n + 1) ≤ 4294967296) :
224+
(m.write32 (base + 4 * UInt32.ofNat n) w).words32 base (n + 1)
225+
= m.words32 base n ++ [w] := by
226+
have haddr := Mem.words32_slotAddr_toNat base n (by omega)
227+
rw [Mem.words32_succ,
228+
Mem.words32_write32_outside m base n _ w (by omega) (Or.inr (by rw [haddr])),
229+
Mem.read32_write32_same]
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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, while every byte outside
18+
`[dst, dst+4n)` is left untouched (the frame condition, so the theorem composes
19+
with facts about neighbouring memory). This is the shape LLVM emits
20+
for a small `copy_from_slice` at `opt-level = 0` (a `load`/`store` loop, not a
21+
`memory.copy`), and the direct analogue of `merge_sort`'s copy helpers. It
22+
exercises the invariant/variant loop rule together with the `words32` view and
23+
`MemRegion` disjointness. -/
24+
25+
namespace Wasm
26+
27+
/-- Copy loop. Params `dst : i32`, `src : i32`, `n : i32`; local `i : i32`.
28+
Copies `mem[src + 4*i]` to `mem[dst + 4*i]` for `i = 0 … n-1`. -/
29+
def CopyWords : Program := [
30+
.const 0, .localSet 3,
31+
.loop 0 0 [
32+
.block 0 0 [
33+
.block 0 0 [
34+
.localGet 3, .localGet 2, .ltU, .br_if 0,
35+
.br 1
36+
],
37+
.localGet 0, .localGet 3, .const 2, .shl, .add,
38+
.localGet 1, .localGet 3, .const 2, .shl, .add,
39+
.load32 0, .store32 0,
40+
.localGet 3, .const 1, .add, .localSet 3,
41+
.br 1 ] ]
42+
]
43+
44+
set_option maxHeartbeats 1000000 in
45+
/-- Running `CopyWords` on a store where the two `u32` arrays are addressable,
46+
within the wasm32 page cap (no wraparound), and **disjoint** terminates with the
47+
destination holding a copy of the source, the source unchanged. -/
48+
theorem copyWords_spec (m : Module) (st : Store Unit) (dst src n : UInt32)
49+
(hsrc : src.toNat + 4 * n.toNat ≤ st.mem.pages * 65536)
50+
(hdst : dst.toNat + 4 * n.toNat ≤ st.mem.pages * 65536)
51+
(hpages : st.mem.pages ≤ 65536)
52+
(hdisj : MemRegion.Disjoint ⟨dst, 4 * n.toNat⟩ ⟨src, 4 * n.toNat⟩) :
53+
wp m CopyWords
54+
(fun c => ∃ st' s', c = .Fallthrough st' s'
55+
∧ st'.mem.words32 dst n.toNat = st.mem.words32 src n.toNat
56+
∧ st'.mem.words32 src n.toNat = st.mem.words32 src n.toNat
57+
∧ st'.mem.pages = st.mem.pages
58+
∧ ∀ a : Nat, (a < dst.toNat ∨ dst.toNat + 4 * n.toNat ≤ a) →
59+
st'.mem.bytes a = st.mem.bytes a)
60+
st { params := [.i32 dst, .i32 src, .i32 n], locals := [.i32 0], values := [] } := by
61+
have hcap : st.mem.pages * 655364294967296 := by
62+
have := Nat.mul_le_mul_right 65536 hpages; omega
63+
have hdisj : dst.toNat + 4 * n.toNat ≤ src.toNat ∨ src.toNat + 4 * n.toNat ≤ dst.toNat := hdisj
64+
unfold CopyWords
65+
wp_run
66+
simp
67+
apply wp_loop_cons
68+
(Inv := fun st' s' => ∃ i : UInt32,
69+
s' = ⟨[.i32 dst, .i32 src, .i32 n], [.i32 i], []⟩
70+
∧ i.toNat ≤ n.toNat
71+
∧ st'.mem.words32 dst i.toNat = st'.mem.words32 src i.toNat
72+
∧ st'.mem.words32 src n.toNat = st.mem.words32 src n.toNat
73+
∧ st'.mem.pages = st.mem.pages
74+
∧ ∀ a : Nat, (a < dst.toNat ∨ dst.toNat + 4 * n.toNat ≤ a) →
75+
st'.mem.bytes a = st.mem.bytes a)
76+
(μ := fun _ s' => match s'.locals.headD (.i32 0) with | .i32 i => n.toNat - i.toNat | _ => 0)
77+
· exact ⟨0, rfl, by simp, by simp [Mem.words32], rfl, rfl, fun a _ => rfl⟩
78+
· rintro st' s' ⟨i, rfl, hile, hcopy, hsrceq, hpg, hframe⟩
79+
apply wp_block_cons
80+
apply wp_block_cons
81+
wp_run
82+
simp
83+
by_cases hlt : i < n
84+
· -- body: copy word i
85+
have hilt : i.toNat < n.toNat := hlt
86+
have hoi : UInt32.ofNat i.toNat = i := by simp [UInt32.ofNat_toNat]
87+
have hshlU : i <<< 2 = 4 * i := MemRegion.shl2_eq_mul4 i
88+
have hmod1 : (1 + i.toNat) % 4294967296 = i.toNat + 1 := by
89+
rw [Nat.mod_eq_of_lt (by have := n.toNat_lt; omega)]; omega
90+
have hshlN : i.toNat <<< 2 = i.toNat * 4 := by rw [Nat.shiftLeft_eq]
91+
have haddr_d : 4 * i + dst = dst + 4 * UInt32.ofNat i.toNat := by
92+
rw [hoi]; exact UInt32.add_comm _ _
93+
have haddr_s : 4 * i + src = src + 4 * UInt32.ofNat i.toNat := by
94+
rw [hoi]; exact UInt32.add_comm _ _
95+
have hda : (dst + 4 * UInt32.ofNat i.toNat).toNat = dst.toNat + 4 * i.toNat :=
96+
Mem.words32_slotAddr_toNat dst i.toNat (by omega)
97+
simp only [hlt, ↓reduceIte, hshlU, hshlN, hmod1, haddr_d, haddr_s]
98+
set w : UInt32 := st'.mem.read32 (src + 4 * UInt32.ofNat i.toNat) with hw
99+
refine ⟨?_, ?_, ⟨?_, ?_, ?_, ?_, ?_⟩, ?_⟩
100+
· rw [Nat.mod_eq_of_lt (by omega)]; omega -- load in bounds
101+
· rw [Nat.mod_eq_of_lt (by omega)]; omega -- store in bounds
102+
· omega -- i+1 ≤ n
103+
· -- words32 dst (i+1) = words32 src (i+1) at the new store
104+
rw [Mem.words32_write32_snoc st'.mem dst i.toNat w (by omega),
105+
Mem.words32_write32_outside st'.mem src (i.toNat + 1)
106+
(dst + 4 * UInt32.ofNat i.toNat) w (by omega) (by rw [hda]; omega),
107+
Mem.words32_succ, hcopy]
108+
· -- words32 src n unchanged
109+
rw [Mem.words32_write32_outside st'.mem src n.toNat
110+
(dst + 4 * UInt32.ofNat i.toNat) w (by omega) (by rw [hda]; omega), hsrceq]
111+
· exact hpg -- pages
112+
· -- frame: the write lands in `[dst, dst+4n)`, so bytes outside are kept
113+
intro a ha
114+
rw [Mem.write32_bytes_of_disjoint st'.mem (dst + 4 * UInt32.ofNat i.toNat) w a
115+
(by rw [hda]; omega)]
116+
exact hframe a ha
117+
· omega -- variant
118+
· -- exit: i = n, dst view already equals src view
119+
have hin : i.toNat = n.toNat := by
120+
have : ¬ i.toNat < n.toNat := hlt
121+
omega
122+
simp only [hlt, ↓reduceIte]
123+
rw [hin] at hcopy
124+
exact ⟨hcopy.trans hsrceq, hsrceq, hpg, hframe⟩
125+
126+
end Wasm

codelib/CodeLib/RustStd/Region.lean

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,45 @@ theorem slot64_disjoint (base k l : UInt32)
162162
simp only [slot64]
163163
omega
164164

165+
/-- The `k`-th 4-byte slot of a `u32` array based at `base` (wasm address
166+
`base + 4 * k`). The 32-bit twin of `slot64`, matching the `Mem.words32`
167+
element stride. -/
168+
def slot32 (base k : UInt32) : MemRegion := ⟨base + 4 * k, 4
169+
170+
/-- `x <<< 2 = 4 * x` on `UInt32`: the `(const 2) shl` address computation LLVM
171+
emits for a `u32` array index. -/
172+
theorem shl2_eq_mul4 (x : UInt32) : x <<< (2 % 32 : UInt32) = 4 * x := by bv_decide
173+
174+
/-- The codegen's `(k <<< 2) + base` lands on the slot base address. The
175+
32-bit twin of `slot64_of_shl` (whose consumer is `SwapElementsSpec`); the
176+
`u32` element-slot proofs of the merge_sort work (PR #106) are the intended
177+
consumer here. -/
178+
theorem slot32_of_shl (base k : UInt32) :
179+
k <<< (2 % 32 : UInt32) + base = (slot32 base k).base := by
180+
simp only [slot32]; bv_decide
181+
182+
/-- No wraparound: if the slot's true byte offset stays below `2^32`, the wasm
183+
address of `slot32 base k` is the integer `base.toNat + 4 * k.toNat`. -/
184+
theorem slot32_base_toNat (base k : UInt32)
185+
(h : base.toNat + 4 * k.toNat < 4294967296) :
186+
(slot32 base k).base.toNat = base.toNat + 4 * k.toNat := by
187+
simp only [slot32, UInt32.toNat_add, UInt32.toNat_mul, UInt32.reduceToNat]
188+
omega
189+
190+
/-- Distinct in-bounds element slots of a no-wrap `u32` array are disjoint.
191+
The 32-bit twin of `slot64_disjoint`, for the same per-element aliasing
192+
arguments (`SwapElementsSpec`-style) over `u32` arrays (PR #106). -/
193+
theorem slot32_disjoint (base k l : UInt32)
194+
(hk : base.toNat + 4 * k.toNat < 4294967296)
195+
(hl : base.toNat + 4 * l.toNat < 4294967296)
196+
(hkl : k ≠ l) :
197+
(slot32 base k).Disjoint (slot32 base l) := by
198+
unfold Disjoint
199+
rw [slot32_base_toNat base k hk, slot32_base_toNat base l hl]
200+
have : k.toNat ≠ l.toNat := fun he => hkl (UInt32.toNat.inj he)
201+
simp only [slot32]
202+
omega
203+
165204
end MemRegion
166205

167206
end Wasm

0 commit comments

Comments
 (0)