|
| 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 * 65536 ≤ 4294967296 := 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 |
0 commit comments