Download backend/wasm/Wasm/Isa.lean from Snapkitty/ai-free: direct link, hf CLI and curl.
- Browser
- Download file 10.5 kB
-
https://huggingface.co/Snapkitty/ai-free/resolve/main/backend/wasm/Wasm/Isa.lean
- Command line
-
hf download hf://Snapkitty/ai-free/backend/wasm/Wasm/Isa.lean
-
curl -L -o Isa.lean https://huggingface.co/Snapkitty/ai-free/resolve/main/backend/wasm/Wasm/Isa.lean
10.5 kB
| /-! | |
| # Wasm.Isa | |
| A model of the WebAssembly subset the backend emits, following the WebAssembly core | |
| specification: | |
| * typed operand stack (`i32` / `i64`), mutable globals, linear memory, a function table; | |
| * `i64.load`/`i64.store` on **bytes** (little-endian), taking an `i32` address plus a static offset | |
| computed in unbounded arithmetic; an access with any byte outside the memory traps. There is no | |
| aliasing assumption: overlapping accesses are modelled exactly; | |
| * `i64.div_s` traps on division by zero and on `INT64_MIN / -1`; `i64.div_u` traps on division by | |
| zero; shifts and rotates take the count modulo 64 (the spec's definition); | |
| * `if` (without `else`) and `loop`/`br` with the spec's label discipline, including unwinding the | |
| operand stack to the label height on a branch; `call_indirect` through the function table (a | |
| callee runs on a fresh operand stack and returns the single value it leaves). | |
| Two host calls are terminal pseudo-instructions: `exitTrap c` (`i32.const c; call $proc_exit`) and | |
| `exitHalt` (`global.get sp; call $halt`, which dumps the machine state and exits 0). | |
| Semantics is a big-step relation, so loops need no fuel. That the model matches a real | |
| WebAssembly engine is an assumption, tested by differential execution (`wasmw`). | |
| -/ | |
| namespace WordDialect | |
| namespace Wasm | |
| abbrev W := BitVec 64 | |
| abbrev W32 := BitVec 32 | |
| inductive Val where | |
| | i32 (v : W32) | |
| | i64 (v : W) | |
| deriving DecidableEq | |
| /-- Little-endian read of `n` bytes starting at `a` (each byte taken modulo 256). -/ | |
| def rdBytes (b : Nat β Nat) (a : Nat) : Nat β Nat | |
| | 0 => 0 | |
| | n + 1 => rdBytes b a n + (b (a + n) % 256) * 256 ^ n | |
| structure WMem where | |
| bytes : Nat β Nat | |
| size : Nat | |
| def WMem.read64 (m : WMem) (a : Nat) : Option W := | |
| if a + 8 β€ m.size then some (BitVec.ofNat 64 (rdBytes m.bytes a 8)) else none | |
| def WMem.write64 (m : WMem) (a : Nat) (v : W) : Option WMem := | |
| if a + 8 β€ m.size then | |
| some { m with bytes := fun p => if a β€ p β§ p < a + 8 then (v.toNat / 256 ^ (p - a)) % 256 else m.bytes p } | |
| else none | |
| structure WState where | |
| stack : List Val | |
| globals : Nat β Val | |
| mem : WMem | |
| inductive WI where | |
| | i32const (v : W32) | |
| | i64const (v : W) | |
| | globalGet (i : Nat) | |
| | globalSet (i : Nat) | |
| | i64load (off : Nat) | |
| | i64store (off : Nat) | |
| | i64add | i64sub | i64mul | i64and | i64or | i64xor | |
| | i64shl | i64shru | i64rotl | i64rotr | i64divu | i64divs | |
| | i64eq | i64ne | i64ltu | i64leu | i64gtu | i64geu | i64lts | i64les | i64gts | i64ges | |
| | i64eqz | |
| | i32add | i32sub | i32shl | i32gtu | i32eq | |
| | i32wrap | |
| | i64extu | |
| | select | |
| | loop (body : List WI) | |
| | ite (thn : List WI) | |
| | br (l : Nat) | |
| | callIndirect | |
| | exitTrap (code : Nat) | |
| | exitHalt | |
| /-- Control constructs, calls and terminal pseudo-instructions (handled by `RunI`, not `stepI`). -/ | |
| def WI.isCtl : WI β Bool | |
| | .loop _ | .ite _ | .br _ | .callIndirect | .exitTrap _ | .exitHalt => true | |
| | _ => false | |
| def bool32 (b : Bool) : W32 := if b then 1#32 else 0#32 | |
| def bin64 (f : W β W β W) (s : WState) : Option WState := | |
| match s.stack with | |
| | .i64 b :: .i64 a :: r => some { s with stack := .i64 (f a b) :: r } | |
| | _ => none | |
| def cmp64 (f : W β W β Bool) (s : WState) : Option WState := | |
| match s.stack with | |
| | .i64 b :: .i64 a :: r => some { s with stack := .i32 (bool32 (f a b)) :: r } | |
| | _ => none | |
| def bin32 (f : W32 β W32 β W32) (s : WState) : Option WState := | |
| match s.stack with | |
| | .i32 b :: .i32 a :: r => some { s with stack := .i32 (f a b) :: r } | |
| | _ => none | |
| /-- One non-control instruction; `none` is a trap. -/ | |
| def stepI : WI β WState β Option WState | |
| | .i32const v, s => some { s with stack := .i32 v :: s.stack } | |
| | .i64const v, s => some { s with stack := .i64 v :: s.stack } | |
| | .globalGet i, s => some { s with stack := s.globals i :: s.stack } | |
| | .globalSet i, s => | |
| match s.stack with | |
| | v :: r => some { s with stack := r, globals := fun j => if j = i then v else s.globals j } | |
| | _ => none | |
| | .i64load off, s => | |
| match s.stack with | |
| | .i32 a :: r => | |
| match s.mem.read64 (a.toNat + off) with | |
| | some v => some { s with stack := .i64 v :: r } | |
| | none => none | |
| | _ => none | |
| | .i64store off, s => | |
| match s.stack with | |
| | .i64 v :: .i32 a :: r => | |
| match s.mem.write64 (a.toNat + off) v with | |
| | some m => some { s with stack := r, mem := m } | |
| | none => none | |
| | _ => none | |
| | .i64add, s => bin64 (Β· + Β·) s | |
| | .i64sub, s => bin64 (Β· - Β·) s | |
| | .i64mul, s => bin64 (Β· * Β·) s | |
| | .i64and, s => bin64 (Β· &&& Β·) s | |
| | .i64or, s => bin64 (Β· ||| Β·) s | |
| | .i64xor, s => bin64 (Β· ^^^ Β·) s | |
| | .i64shl, s => bin64 (fun a b => a <<< (b.toNat % 64)) s | |
| | .i64shru, s => bin64 (fun a b => a >>> (b.toNat % 64)) s | |
| | .i64rotl, s => bin64 (fun a b => a.rotateLeft (b.toNat % 64)) s | |
| | .i64rotr, s => bin64 (fun a b => a.rotateRight (b.toNat % 64)) s | |
| | .i64divu, s => | |
| match s.stack with | |
| | .i64 b :: .i64 a :: r => | |
| if b = 0#64 then none | |
| else some { s with stack := .i64 (BitVec.ofNat 64 (a.toNat / b.toNat)) :: r } | |
| | _ => none | |
| | .i64divs, s => | |
| match s.stack with | |
| | .i64 b :: .i64 a :: r => | |
| if b = 0#64 β¨ (a = BitVec.intMin 64 β§ b = BitVec.allOnes 64) then none | |
| else some { s with stack := .i64 (BitVec.ofInt 64 (Int.tdiv a.toInt b.toInt)) :: r } | |
| | _ => none | |
| | .i64eq, s => cmp64 (fun a b => a == b) s | |
| | .i64ne, s => cmp64 (fun a b => a != b) s | |
| | .i64ltu, s => cmp64 (fun a b => a.ult b) s | |
| | .i64leu, s => cmp64 (fun a b => a.ule b) s | |
| | .i64gtu, s => cmp64 (fun a b => b.ult a) s | |
| | .i64geu, s => cmp64 (fun a b => b.ule a) s | |
| | .i64lts, s => cmp64 (fun a b => a.slt b) s | |
| | .i64les, s => cmp64 (fun a b => a.sle b) s | |
| | .i64gts, s => cmp64 (fun a b => b.slt a) s | |
| | .i64ges, s => cmp64 (fun a b => b.sle a) s | |
| | .i64eqz, s => | |
| match s.stack with | |
| | .i64 a :: r => some { s with stack := .i32 (bool32 (a == 0#64)) :: r } | |
| | _ => none | |
| | .i32add, s => bin32 (Β· + Β·) s | |
| | .i32sub, s => bin32 (Β· - Β·) s | |
| | .i32shl, s => bin32 (fun a b => a <<< (b.toNat % 32)) s | |
| | .i32gtu, s => | |
| match s.stack with | |
| | .i32 b :: .i32 a :: r => some { s with stack := .i32 (bool32 (b.ult a)) :: r } | |
| | _ => none | |
| | .i32eq, s => | |
| match s.stack with | |
| | .i32 b :: .i32 a :: r => some { s with stack := .i32 (bool32 (a == b)) :: r } | |
| | _ => none | |
| | .i32wrap, s => | |
| match s.stack with | |
| | .i64 a :: r => some { s with stack := .i32 (BitVec.ofNat 32 a.toNat) :: r } | |
| | _ => none | |
| | .i64extu, s => | |
| match s.stack with | |
| | .i32 a :: r => some { s with stack := .i64 (BitVec.ofNat 64 a.toNat) :: r } | |
| | _ => none | |
| | .select, s => | |
| match s.stack with | |
| | .i32 c :: v2 :: v1 :: r => some { s with stack := (if c β 0#32 then v1 else v2) :: r } | |
| | _ => none | |
| | _, _ => none | |
| /-- Outcomes of running a list of instructions. -/ | |
| inductive Res where | |
| | normal (s : WState) | |
| | br (n : Nat) (s : WState) | |
| | exit (code : Nat) | |
| | halted (s : WState) | |
| | fault | |
| /-- Leaving a labelled construct entered with operand stack `entry`: a branch to label 0 is | |
| consumed (for `if`; `loop` re-enters) and the operand stack is unwound to its height at entry; | |
| other branches propagate outward. -/ | |
| inductive BlockOut (entry : List Val) : Res β Res β Prop where | |
| | norm {s} : BlockOut entry (.normal s) (.normal s) | |
| | br0 {s} : BlockOut entry (.br 0 s) (.normal { s with stack := entry }) | |
| | brS {n s} : BlockOut entry (.br (n + 1) s) (.br n s) | |
| | exit {c} : BlockOut entry (.exit c) (.exit c) | |
| | halted {s} : BlockOut entry (.halted s) (.halted s) | |
| | fault : BlockOut entry .fault .fault | |
| /-- Results that abandon the rest of an instruction sequence. -/ | |
| def Res.abrupt : Res β Prop | |
| | .normal _ => False | |
| | _ => True | |
| /-- A function table: entry `i` is the body of function `i`. -/ | |
| abbrev Funcs := List (List WI) | |
| mutual | |
| inductive RunI (fs : Funcs) : WI β WState β Res β Prop where | |
| | simple {i s s'} : i.isCtl = false β stepI i s = some s' β RunI fs i s (.normal s') | |
| | simpleFault {i s} : i.isCtl = false β stepI i s = none β RunI fs i s .fault | |
| | loopNormal {body s s'} : Run fs body s (.normal s') β RunI fs (.loop body) s (.normal s') | |
| | loopBr0 {body s s' r} : | |
| Run fs body s (.br 0 s') β RunI fs (.loop body) { s' with stack := s.stack } r β | |
| RunI fs (.loop body) s r | |
| | loopBrS {body s n s'} : Run fs body s (.br (n + 1) s') β RunI fs (.loop body) s (.br n s') | |
| | loopExit {body s c} : Run fs body s (.exit c) β RunI fs (.loop body) s (.exit c) | |
| | loopHalted {body s s'} : Run fs body s (.halted s') β RunI fs (.loop body) s (.halted s') | |
| | loopFault {body s} : Run fs body s .fault β RunI fs (.loop body) s .fault | |
| | iteThen {thn s r c res res'} : | |
| s.stack = .i32 c :: r β c β 0#32 β Run fs thn { s with stack := r } res β | |
| BlockOut r res res' β RunI fs (.ite thn) s res' | |
| | iteElse {thn s r c} : | |
| s.stack = .i32 c :: r β c = 0#32 β RunI fs (.ite thn) s (.normal { s with stack := r }) | |
| | iteFault {thn s} : (β c r, s.stack β .i32 c :: r) β RunI fs (.ite thn) s .fault | |
| | br {l s} : RunI fs (.br l) s (.br l s) | |
| | callNormal {s r i body s' v} : | |
| s.stack = .i32 i :: r β fs[i.toNat]? = some body β | |
| Run fs body { s with stack := [] } (.normal s') β s'.stack = [.i32 v] β | |
| RunI fs .callIndirect s (.normal { s' with stack := .i32 v :: r }) | |
| | callBad {s r i body s'} : | |
| s.stack = .i32 i :: r β fs[i.toNat]? = some body β | |
| Run fs body { s with stack := [] } (.normal s') β (β v, s'.stack β [.i32 v]) β | |
| RunI fs .callIndirect s .fault | |
| | callAbrupt {s r i body res} : | |
| s.stack = .i32 i :: r β fs[i.toNat]? = some body β | |
| Run fs body { s with stack := [] } res β res.abrupt β | |
| RunI fs .callIndirect s (match res with | .br _ _ => .fault | other => other) | |
| | callMissing {s r i} : | |
| s.stack = .i32 i :: r β fs[i.toNat]? = none β RunI fs .callIndirect s .fault | |
| | callBadStack {s} : (β i r, s.stack β .i32 i :: r) β RunI fs .callIndirect s .fault | |
| | exitTrap {c s} : RunI fs (.exitTrap c) s (.exit c) | |
| | exitHalt {s} : RunI fs .exitHalt s (.halted s) | |
| inductive Run (fs : Funcs) : List WI β WState β Res β Prop where | |
| | nil {s} : Run fs [] s (.normal s) | |
| | consNormal {i is s s' r} : RunI fs i s (.normal s') β Run fs is s' r β Run fs (i :: is) s r | |
| | consAbrupt {i is s r} : RunI fs i s r β r.abrupt β Run fs (i :: is) s r | |
| end | |
| end Wasm | |
| end WordDialect | |