Skip to content

Commit 9c55e52

Browse files
El3ssarmfornetclaude
authored
decoder: remove dead stub-immediate path, fix stale docstrings (#127)
* decoder: remove dead stub-immediate path, fix stale docstrings `stubImmediateCount` unconditionally returns `none` (all GC ops are decoded for real now), which makes the `some n => consumeStubAtoms …` branch that calls it statically unreachable and `consumeStubAtoms` dead. This removes both and folds the branch to its only reachable arm (`.ok rest`), keeping the live `consumeBrOnCastImmediates` case untouched — behaviour is unchanged. It also refreshes several comments that no longer match the decoder: the top-of-file and `parseModule` docstrings (type/table/memory/global/elem/data/tag content is fully parsed now, not discarded), the `parsePlainOp` catch-all (floats and SIMD are modelled; the fallback only stubs still-unmodelled proposals like atomics), and the `isMemOp` comments (memory ops emit real load/stores). * decoder+array: correct import docstrings, generalize slice callee bridge Address code-review findings on the decoder cleanup and the new slice corpus: - Wat.lean: the module-header and `parseModule` docstrings claimed non-func imports are "dropped"; they are in fact parsed into `globals`/`tables`/`memory` and recorded in `importedGlobals`/`importedTables`/`importedMemories`. Reword to match. Also remove the now-unreachable `br_on_cast` arm in the `parsePlainOp` fallback (handled by an explicit arm) and its orphaned `consumeBrOnCastImmediates`. - Array trunk: add chunk-generic `unSliceBodyTerminates`; `isEmptyBodyTerminates` and the new symmetric `lenBodyTerminates` are one-line instances, so the `len` callee bridge no longer hand-rolls the `of_returns_wp` glue. Drop the now-unused op-specific `lenBodyWp`/`isEmptyBodyWp` (subsumed by the generic `unBodyReturnsWp`). - RustArray/Spec.lean: bridges first, internal specs reuse them at `initialStore` (matching RustArrayTests) instead of duplicating the callee term. - Add `open_slice_export` macro factoring the uniform export-proof head; apply to all six slice export proofs. Verified: codelib and programs both `lake build` clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Marcelo Fornet <mfornet94@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ec5641f commit 9c55e52

7 files changed

Lines changed: 167 additions & 161 deletions

File tree

codelib/CodeLib/RustStd/Array/Basic.lean

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ exact shape the integer trunk `CodeLib/RustStd/UInt.lean` already abstracts as
1515
`UnChunk` (over any `UIntWasm` type; here `UInt32`, whose `toV` is `.i32`).
1616
So a length-only op needs *no new chunk shape*: its chunk is a `UnChunk` instance
1717
(`Len.len_chunk`, `IsEmpty.isEmpty_chunk`) and its called monomorphized body is
18-
discharged by the trunk's `unBodyReturnsWp` (`Len.lenBodyWp`,
19-
`IsEmpty.isEmptyBodyWp`) — the same `unBodyReturnsWp` reads the length from an
20-
**arbitrary** local `i` (slices carry the length in the second fat-pointer field,
21-
e.g. param local `1`), so nothing slice-specific is needed there.
18+
discharged by the shared `unSliceBodyTerminates` below (instantiated per op as
19+
`Len.lenBodyTerminates`, `IsEmpty.isEmptyBodyTerminates`), which feeds the chunk
20+
through the integer trunk's `unBodyReturnsWp`. That reads the length from the
21+
slice's second fat-pointer field (param local `1`), so nothing beyond the chunk
22+
is needed per op.
2223
2324
The one genuinely new unit here is `fatPtrLoadWp`: unlike a scalar, a slice
2425
crosses the C ABI **spilled to linear memory**, so the export wrapper must read
@@ -106,4 +107,56 @@ macro_rules
106107
List.reverseAux, List.map, List.length_cons, List.length_nil]
107108
rw [fatPtrLoadWp 0 $p $dataPtr $len [] (by simp) $hfat]))
108109

110+
/-- Open a memory-resident slice *export* proof: apply the entry lemma, unfold the
111+
wrapper `def`s, and marshal the fat pointer back from memory with `load_fat_ptr` —
112+
the uniform three-line head every slice export shares before it `call`s its body.
113+
`fdef`/`fbody` are the wrapper's `Function`/`Program` defs; the remaining operands
114+
match `load_fat_ptr`. Leaves the goal just past the fat-pointer read, with the
115+
per-export tail (`wp_call_tw`, result rewriting) still explicit since it varies. -/
116+
syntax "open_slice_export " ident ", " ident " at "
117+
term ", " term ", " term " using " term : tactic
118+
119+
macro_rules
120+
| `(tactic| open_slice_export $fdef, $fbody at $p, $dataPtr, $len using $hfat) =>
121+
`(tactic|
122+
(apply TerminatesWith.of_wp_entry_for (f := $fdef) rfl
123+
unfold $fdef $fbody
124+
load_fat_ptr $p, $dataPtr, $len using $hfat))
125+
126+
/-! ## Generic length-only slice callee
127+
128+
A length-only slice primitive compiles at opt-0 to a two-param leaf body
129+
`[.localGet 1] ++ frag ++ [.ret]`: the fat pointer is passed as `(dataPtr, len)`
130+
(local `0`, local `1`), the body reads the length from local `1`, and `frag` is a
131+
`UnChunk` transforming it. `len` (`frag = []`, `op = id`) and `is_empty`
132+
(`frag = [.const 0, .eq, .const 1, .and]`, `op = isEmptyValue`) are both this
133+
shape, so one chunk-generic `TerminatesWith` bridge serves them — and any future
134+
unary slice primitive — instead of a per-op copy of the `of_returns_wp` glue. -/
135+
136+
/-- Callee bridge for a length-only slice body: any module function `id` whose
137+
body is `[.localGet 1] ++ frag ++ [.ret]` for a `UnChunk frag op` terminates,
138+
when called with stack `(len, dataPtr, …rest)`, returning `op len` on top of
139+
`rest`. Instantiate at a concrete chunk (`len_chunk`, `isEmpty_chunk`) to obtain
140+
that primitive's leaf-call fact; the `of_returns_wp`/`unBodyReturnsWp` glue lives
141+
here once. -/
142+
theorem unSliceBodyTerminates {α} {env : HostEnv α} {m : Module} {id : Nat}
143+
{f : Function} {frag : Program} {op : UInt32 → UInt32} (chunk : UnChunk frag op)
144+
(st : Store α) (dataPtr len : UInt32) (rest : List Value)
145+
(hf : m.funcs[id - m.imports.length]? = some f)
146+
(hbody : f.body = [.localGet 1] ++ frag ++ [.ret])
147+
(hnp : f.numParams = 2)
148+
(hres : f.results.length = 1)
149+
(hImp : m.imports[id]? = none := by rfl) :
150+
TerminatesWith env m id st (.i32 len :: .i32 dataPtr :: rest)
151+
(fun st' vs => vs = .i32 (op len) :: rest ∧ framePost st st') := by
152+
refine (TerminatesWith.of_returns_wp (f := f) (rs := [.i32 (op len)])
153+
(P := framePost st) hf hres.symm ?_ hImp).mono ?_
154+
· rw [hbody]
155+
simp only [Function.toLocals, hnp]
156+
exact unBodyReturnsWp chunk st 1 len [] rfl
157+
· intro st' vs h
158+
refine ⟨?_, h.2
159+
rw [h.1, hnp]
160+
simp
161+
109162
end Wasm.RustStd.Array

codelib/CodeLib/RustStd/Array/IsEmpty.lean

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import CodeLib.RustStd.Array.Basic
33
/-! `&[T]::is_empty` — a zero-length test masked to a Rust bool, computed from the
44
slice length component. The reusable unit is the unary chunk (a `UnChunk` over
55
`UInt32`) for the fragment `[.const 0, .eq, .const 1, .and]`; the called body
6-
(`isEmptyBodyWp`) derives from it through the integer trunk's `unBodyReturnsWp`,
7-
and an inlined occurrence reuses the chunk directly. -/
6+
(`isEmptyBodyTerminates`) derives from it through the trunk's
7+
`unSliceBodyTerminates`, and an inlined occurrence reuses the chunk directly. -/
88

99
namespace Wasm.RustStd.Array
1010

@@ -28,32 +28,21 @@ theorem isEmptyValue_and_one (len : UInt32) :
2828
/-- The reusable chunk: with the slice length on the stack, the fragment
2929
`[.const 0, .eq, .const 1, .and]` computes `isEmptyValue len`. The single
3030
stack-form unit for `is_empty` (a `UnChunk` over `UInt32`): it feeds the called
31-
body via the trunk's `unBodyReturnsWp`, and is `rw`-able directly at an inlined
32-
`is_empty` once the length is on the stack. -/
31+
body via the trunk's `unSliceBodyTerminates`, and is `rw`-able directly at an
32+
inlined `is_empty` once the length is on the stack. -/
3333
theorem isEmpty_chunk : UnChunk (T := UInt32) [.const 0, .eq, .const 1, .and] isEmptyValue := by
3434
intro α m env Q st P L rest len vs
3535
simp only [toV_u32, List.cons_append, List.nil_append, wp_const_cons, wp_eq_cons, wp_and_cons]
3636
unfold isEmptyValue
3737
by_cases h : len = 0 <;> simp [h]
3838

39-
/-- Function-body theorem for the generated `&[T]::is_empty` primitive body
40-
`[localGet i, .const 0, .eq, .const 1, .and, .ret]`, reusing the integer trunk's
41-
`unBodyReturnsWp` with `isEmpty_chunk`. This is the *called* shape; an inlined
42-
`is_empty` reuses `isEmpty_chunk` directly (opt-0 only ever emits the call). -/
43-
theorem isEmptyBodyWp {α} {m : Module} {env : HostEnv α} (st : Store α)
44-
{P L : List Value} (i : Nat) (len : UInt32) (vs : List Value)
45-
(hlen : (⟨P, L, vs⟩ : Locals).get i = some (.i32 len)) :
46-
wp m [.localGet i, .const 0, .eq, .const 1, .and, .ret]
47-
(Returns (.i32 (isEmptyValue len) :: vs) (framePost st)) st ⟨P, L, vs⟩ env :=
48-
unBodyReturnsWp isEmpty_chunk st i len vs hlen
49-
5039
/-- Reusable *callee* fact for a generated leaf `is_empty` body. Any module
5140
function `id` whose body is the canonical `[localGet 1, const 0, eq, const 1, and,
5241
ret]` (the slice length sits in param local `1`, the second fat-pointer field)
5342
terminates, when called with stack `(len, dataPtr, …rest)`, returning
54-
`isEmptyValue len` on top of `rest`. Each corpus' leaf `is_empty` call bridge is
55-
this lemma at its concrete `func…Def`, so the `of_returns_wp`/`isEmptyBodyWp` glue
56-
lives here once instead of being restated per corpus. -/
43+
`isEmptyValue len` on top of `rest`. This is the trunk's `unSliceBodyTerminates`
44+
at `isEmpty_chunk`; each corpus' leaf `is_empty` call bridge is this lemma at its
45+
concrete `func…Def`. -/
5746
theorem isEmptyBodyTerminates {α} {env : HostEnv α} {m : Module} {id : Nat}
5847
{f : Function} (st : Store α) (dataPtr len : UInt32) (rest : List Value)
5948
(hf : m.funcs[id - m.imports.length]? = some f)
@@ -62,15 +51,7 @@ theorem isEmptyBodyTerminates {α} {env : HostEnv α} {m : Module} {id : Nat}
6251
(hres : f.results.length = 1)
6352
(hImp : m.imports[id]? = none := by rfl) :
6453
TerminatesWith env m id st (.i32 len :: .i32 dataPtr :: rest)
65-
(fun st' vs => vs = .i32 (isEmptyValue len) :: rest ∧ framePost st st') := by
66-
refine (TerminatesWith.of_returns_wp (f := f) (rs := [.i32 (isEmptyValue len)])
67-
(P := framePost st) hf hres.symm ?_ hImp).mono ?_
68-
· rw [hbody]
69-
simp only [Function.toLocals, hnp]
70-
exact isEmptyBodyWp st 1 len [] rfl
71-
· intro st' vs h
72-
refine ⟨?_, h.2
73-
rw [h.1, hnp]
74-
simp
54+
(fun st' vs => vs = .i32 (isEmptyValue len) :: rest ∧ framePost st st') :=
55+
unSliceBodyTerminates isEmpty_chunk st dataPtr len rest hf hbody hnp hres hImp
7556

7657
end Wasm.RustStd.Array

codelib/CodeLib/RustStd/Array/Len.lean

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import CodeLib.RustStd.Array.Basic
33
/-! `&[T]::len` — the slice length is the `i32` length component of the fat
44
pointer; the monomorphized primitive body just returns it. The reusable unit is
55
the degenerate unary chunk (`frag = []`, `op = id`) over the length, which feeds
6-
the called body through the integer trunk's `unBodyReturnsWp`. The *inlined*
7-
read is just `wp_localGet_cons`, so it needs no slice-specific lemma. -/
6+
the called body through the trunk's `unSliceBodyTerminates`. The *inlined* read
7+
is just `wp_localGet_cons`, so it needs no slice-specific lemma. -/
88

99
namespace Wasm.RustStd.Array
1010

@@ -13,20 +13,26 @@ open Wasm Wasm.RustStd
1313
/-- The reusable chunk: with the length on the stack, the empty fragment leaves
1414
it unchanged — `len` is the identity length-only op. The `frag = []`, `op = id`
1515
case of the trunk's `UnChunk` (at `UInt32`, whose `toV` is `.i32`); feeds
16-
`lenBodyWp` via `unBodyReturnsWp`. -/
16+
`lenBodyTerminates` via `unSliceBodyTerminates`. -/
1717
theorem len_chunk : UnChunk (T := UInt32) [] (id : UInt32 → UInt32) := by
1818
intro α m env Q st P L rest len vs
1919
simp
2020

21-
/-- Function-body theorem for the generated `&[T]::len` primitive body
22-
`[localGet i, .ret]`, reusing the integer trunk's `unBodyReturnsWp` with
23-
`len_chunk`. Serves the *called* shape; the *inlined* shape is just
24-
`wp_localGet_cons`. -/
25-
theorem lenBodyWp {α} {m : Module} {env : HostEnv α} (st : Store α)
26-
{P L : List Value} (i : Nat) (len : UInt32) (vs : List Value)
27-
(hlen : (⟨P, L, vs⟩ : Locals).get i = some (.i32 len)) :
28-
wp m [.localGet i, .ret]
29-
(Returns (.i32 len :: vs) (framePost st)) st ⟨P, L, vs⟩ env :=
30-
unBodyReturnsWp len_chunk st i len vs hlen
21+
/-- Reusable *callee* fact for a generated leaf `len` body. Any module function
22+
`id` whose body is the canonical `[localGet 1, ret]` (the slice length sits in
23+
param local `1`, the second fat-pointer field) terminates, when called with stack
24+
`(len, dataPtr, …rest)`, returning `len` on top of `rest`. This is the trunk's
25+
`unSliceBodyTerminates` at `len_chunk` (`op = id`); each corpus' leaf `len` call
26+
bridge is this lemma at its concrete `func…Def`. -/
27+
theorem lenBodyTerminates {α} {env : HostEnv α} {m : Module} {id : Nat}
28+
{f : Function} (st : Store α) (dataPtr len : UInt32) (rest : List Value)
29+
(hf : m.funcs[id - m.imports.length]? = some f)
30+
(hbody : f.body = [.localGet 1, .ret])
31+
(hnp : f.numParams = 2)
32+
(hres : f.results.length = 1)
33+
(hImp : m.imports[id]? = none := by rfl) :
34+
TerminatesWith env m id st (.i32 len :: .i32 dataPtr :: rest)
35+
(fun st' vs => vs = .i32 len :: rest ∧ framePost st st') :=
36+
unSliceBodyTerminates len_chunk st dataPtr len rest hf hbody hnp hres hImp
3137

3238
end Wasm.RustStd.Array

interpreter/Interpreter/Wasm/Decoder/Wat.lean

Lines changed: 39 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ A small parser for the WebAssembly text format, targeting Wasm's AST.
99
Supported:
1010
* `(module ...)` with any number of `(func ...)` definitions.
1111
* `(type ...)`, `(export ...)`, `(import ...)`, `(table ...)`, `(memory ...)`,
12-
`(global ...)`, `(elem ...)`, `(data ...)`, `(start ...)` — recognized at the
13-
module level. Only `func` and `export` contribute to the resulting
14-
`Wasm.Module`; the rest are accepted to allow round-tripping the spec
15-
testsuite, but their content is discarded.
12+
`(global ...)`, `(elem ...)`, `(data ...)`, `(tag ...)`, `(start ...)` —
13+
recognized at the module level and fully parsed into the resulting
14+
`Wasm.Module`. Non-func imports (`(import … (memory|global|table …))`)
15+
are parsed too: each becomes a placeholder decl at the low end of its
16+
index space and is recorded in `importedGlobals`/`importedTables`/
17+
`importedMemories` for later host substitution.
1618
* Func headers may include `(type N)`, `(param ...)*`, `(result ...)*`,
1719
`(local ...)*` in any order, with grouped or singleton declarations.
1820
* Linear instruction stream and folded operand expressions
@@ -26,8 +28,10 @@ Supported:
2628
* Numeric indices and symbolic identifiers (`$L`).
2729
* `(;0;)` block comments and `;;` line comments are stripped during tokenization.
2830
29-
Features Wasm does not model (memory loads/stores, `memory.*`, globals,
30-
`call_indirect`, tables) are accepted lexically but lowered to
31+
Memory loads/stores, `memory.*`, globals, `call_indirect`, tables,
32+
floats, and SIMD are all modelled and decode to real instructions.
33+
Instructions from proposals the interpreter still doesn't model (e.g.
34+
atomics/threads) are accepted lexically but lowered to
3135
`Wasm.Instruction.unreachable` so the surrounding function still
3236
type-checks. `local.tee i` is desugared to `[local.set i; local.get i]`. -/
3337

@@ -672,13 +676,14 @@ private def parsePlainOp : String → Except Err Wasm.Instruction
672676
| "i31.get_u" => .ok (.gc .i31GetU)
673677
| "ref.eq" => .ok (.gc .refEq)
674678
| op =>
675-
-- Accept instructions from proposals the interpreter doesn't model
676-
-- (floats, SIMD, reference types, tables, GC, exceptions, tail calls)
677-
-- by lowering them to `unreachable`. This lets modules whose
678-
-- *signatures* or unrelated functions touch these features still
679-
-- decode; any function that actually executes such an instruction
680-
-- traps with "unreachable" instead of failing to decode at all,
681-
-- which would cascade to every assert in the file.
679+
-- Fallback for mnemonics not matched above (and not caught by
680+
-- `simdOp?`): still-unmodelled proposals such as atomics/threads and
681+
-- relaxed SIMD, plus stray leftovers from partly-modelled proposals
682+
-- (reference types, tables, GC, exceptions, tail calls). Lower them to
683+
-- `unreachable` so modules whose *signatures* or unrelated functions
684+
-- touch these features still decode; a function that actually executes
685+
-- such an instruction traps with "unreachable" instead of failing to
686+
-- decode at all, which would cascade to every assert in the file.
682687
if op.startsWith "f32." || op.startsWith "f64." || op.startsWith "v128."
683688
|| op.startsWith "i8x16." || op.startsWith "i16x8." || op.startsWith "i32x4."
684689
|| op.startsWith "i64x2." || op.startsWith "f32x4." || op.startsWith "f64x2."
@@ -695,9 +700,9 @@ private def parsePlainOp : String → Except Err Wasm.Instruction
695700
else
696701
.error s!"unsupported instruction: {op}"
697702

698-
/-- Memory ops that take an offset immediate. We accept them lexically
699-
(`offset=`/`align=` attributes parsed and discarded) but emit
700-
`unreachable` for the instruction itself. -/
703+
/-- Memory ops that take an offset immediate, mapped to their natural
704+
alignment (byte width). `offset=`/`align=` attributes are parsed off the
705+
token stream; `memOpToInstruction` then emits the real load/store. -/
701706
private def isMemOp (op : String) : Option Nat :=
702707
match op with
703708
| "i32.load" => some 4
@@ -714,10 +719,8 @@ private def isMemOp (op : String) : Option Nat :=
714719
| "i64.store8" => some 1
715720
| "i64.store16" => some 2
716721
| "i64.store32" => some 4
717-
-- Float and SIMD memory ops are lexically accepted (their offset=/
718-
-- align= attributes parsed and discarded), but `memOpToInstruction`
719-
-- lowers them to `unreachable` since the interpreter doesn't model
720-
-- those value types.
722+
-- Float and SIMD memory ops (their offset=/align= attributes parsed the
723+
-- same way); `memOpToInstruction` emits the matching real load/store.
721724
| "f32.load" | "f32.store" => some 4
722725
| "f64.load" | "f64.store" => some 8
723726
| "v128.load" | "v128.store" => some 16
@@ -766,24 +769,6 @@ private def consumeMemAttrs (natAlign : Nat) (toks : List Sexpr)
766769
| xs => .ok (offset, xs)
767770
loop 0 toks
768771

769-
/-- Number of atom immediates a *lowered* (treated-as-`unreachable`) op
770-
consumes. Used to keep the linear/folded parsers in sync with the token
771-
stream when we accept-and-stub instructions from proposals the
772-
interpreter doesn't model. Returns `none` for ops we don't pretend to
773-
support. -/
774-
private def stubImmediateCount (_op : String) : Option Nat :=
775-
-- `br_on_cast`/`br_on_cast_fail` take label + from_type + to_type and are
776-
-- handled separately by `consumeBrOnCastImmediates`; all other GC ops are
777-
-- now decoded to real instructions.
778-
none
779-
780-
/-- Drop the first `n` atom tokens from `toks`. Errors if a non-atom is
781-
encountered or the stream is too short. -/
782-
private partial def consumeStubAtoms (op : String) : Nat → List Sexpr → Except Err (List Sexpr)
783-
| 0, ts => .ok ts
784-
| k+1, .atom _ :: ts => consumeStubAtoms op k ts
785-
| _+1, _ => .error s!"{op}: expected immediate atom"
786-
787772
/-! ## SIMD mnemonic table
788773
789774
Shape-prefixed mnemonics (`i8x16.add`, `f64x2.pmin`, …) decode through
@@ -1022,17 +1007,6 @@ private def looksLikeLabel (s : String) : Bool :=
10221007
else
10231008
s.toList.all (fun c => c.isDigit || c = '_')
10241009

1025-
/-- Consume the label immediate and the two type-immediates of a
1026-
`br_on_cast` / `br_on_cast_fail`. The label is a single atom; each type
1027-
is either an atom (e.g. `anyref`) or a `(ref …)` list. -/
1028-
private def consumeBrOnCastImmediates (op : String)
1029-
: List Sexpr → Except Err (List Sexpr)
1030-
| .atom _ :: t1 :: t2 :: rest =>
1031-
match t1, t2 with
1032-
| .atom _, .atom _ | .atom _, .list _
1033-
| .list _, .atom _ | .list _, .list _ => .ok rest
1034-
| _ => .error s!"{op}: expected label + 2 type immediates"
1035-
10361010
/-- Parse the *optional* table-index immediate carried by `table.get` /
10371011
`table.size` in flat (post-`wasm-tools print`) form. The index is `$name`
10381012
or a numeric literal when present and defaults to table `0` when omitted;
@@ -1374,15 +1348,12 @@ private partial def parseInstr (ctx : Ctx) (toks : List Sexpr)
13741348
| none =>
13751349
match parsePlainOp op with
13761350
| .error e => .error e
1377-
| .ok i => do
1378-
-- For ops we lowered to `unreachable`, consume any textual
1379-
-- immediates so the token stream stays aligned.
1380-
let rest' ← if op == "br_on_cast" || op == "br_on_cast_fail" then
1381-
consumeBrOnCastImmediates op rest
1382-
else match stubImmediateCount op with
1383-
| some n => consumeStubAtoms op n rest
1384-
| none => .ok rest
1385-
.ok ([i], rest')
1351+
-- Ops reaching this fallback are lowered to `unreachable` and carry
1352+
-- no immediates we track, so the token stream stays aligned with
1353+
-- `rest` untouched. (Ops that *do* carry immediates — including
1354+
-- `br_on_cast`/`br_on_cast_fail` — are handled by explicit arms above
1355+
-- and never fall through here.)
1356+
| .ok i => .ok ([i], rest)
13861357

13871358
private partial def parseFolded (ctx : Ctx) (xs : List Sexpr)
13881359
: Except Err (List Wasm.Instruction) :=
@@ -2849,14 +2820,16 @@ private def collectImports (types : Array TypeEntry) (fields : List Sexpr)
28492820
| _ => pure ()
28502821
return (imports, idOf)
28512822

2852-
/-- Walk a `(module ...)` form. `(func …)`, `(export …)`, `(global …)`,
2853-
`(memory …)`, `(data …)`, `(start …)`, and `(import "mod" "name"
2854-
(func …))` all contribute to the resulting `Wasm.Module`. Function
2855-
imports occupy the low end of the unified function index space (indices
2856-
`0 … N-1`); in-module function indices are shifted up by
2857-
`imports.length`. Other recognised fields (`type`, `table`, `elem`, non-
2858-
func imports) are accepted lexically so the spec testsuite still loads,
2859-
but their content is discarded. -/
2823+
/-- Walk a `(module ...)` form. `(type …)`, `(func …)`, `(export …)`,
2824+
`(global …)`, `(table …)`, `(memory …)`, `(elem …)`, `(data …)`,
2825+
`(tag …)`, `(start …)`, and `(import "mod" "name" (…))` — for func and
2826+
non-func imports alike — all contribute to the resulting `Wasm.Module`.
2827+
Function imports occupy the low end of the unified function index space
2828+
(indices `0 … N-1`); in-module function indices are shifted up by
2829+
`imports.length`. Non-func imports (`(import … (memory|global|table …))`)
2830+
are collected by `collectEntityImports` into placeholder decls at the low
2831+
end of the global/table/memory index spaces and recorded in
2832+
`importedGlobals`/`importedTables`/`importedMemories`. -/
28602833
def parseModule (xs : List Sexpr) : Except Err Wasm.Module := do
28612834
let mut rest := xs
28622835
match rest with

0 commit comments

Comments
 (0)