Skip to content

feat(wasm-host): W1 integer interpreter for scoot-wasm - #147

Merged
jamiesun merged 1 commit into
mainfrom
feat/wasm-host-w1
Jun 26, 2026
Merged

feat(wasm-host): W1 integer interpreter for scoot-wasm#147
jamiesun merged 1 commit into
mainfrom
feat/wasm-host-w1

Conversation

@jamiesun

Copy link
Copy Markdown
Collaborator

Continues #100 (W1, independent PR per maintainer guidance; W0 landed in #145).

What this adds

A standalone, dependency-free pure-Zig Wasm executor for the scoot-wasm host. The zero-dependency core never embeds a Wasm runtime — the engine is compiled only into the gated -Dwasm-host=true binary and a standalone test artifact.

New src/wasm_engine.zig: an integer stack machine that dares to run untrusted modules without panicking. Every fault becomes a structured trap, and execution is bounded by fuel, call depth, value/control stack and memory page limits.

Capabilities (W1)

  • Stack machine with locals/operand layout; calls via native recursion bounded by a depth cap.
  • Structured control flow: block/loop/if/else/br/br_if/br_table/return/call/call_indirect, unreachable/nop/drop/select.
  • i32/i64 const, arithmetic, bitwise, shifts/rotates, comparisons, wrap/extend conversions.
  • Linear memory (64KiB pages): bounds-checked load/store variants, memory.size/grow, bulk copy/fill.
  • Globals, active data segments, a funcref table with active element segments and call_indirect type checking.
  • Traps: unreachable, div/rem by zero, integer overflow, OOB memory/table, undefined element, indirect type mismatch, call-stack depth, out of fuel.

Host

scoot-wasm run <module.wasm> <export> [int args...] — prints the integer result(s) or TRAP ....

Safety hardening

Untrusted input must never panic. Fixed/guarded:

  • local.get/set/tee bounds-checked against the frame local count.
  • start function index validated at load and guarded in callFunction.
  • block/if param arity checked before computing the operand base.
  • export-referenced function index guarded in invokeExport.
  • total locals per function capped at load time.

Tests

18 hand-built .wasm fixtures (no external toolchain), including regression tests for each malformed-input panic path above. A separate wasm_engine_tests artifact is wired into the test step and kept out of the core binary.

Verification

  • zig fmt --check build.zig src examples
  • zig build, zig build test
  • zig build -Doptimize=ReleaseSafe
  • zig build -Dwasm-host=true -Doptimize=ReleaseSafe
  • End-to-end: scoot-wasm run add.wasm add 2 40 -> OK -> 42; bad export -> TRAP.

Out of scope (later PRs)

WASI (W2), full validator / spec conformance (W3), floats + bulk-memory spec test suite (W4).

Implement W1 of the standalone pure-Zig Wasm executor: an integer stack
machine that runs untrusted modules without panicking. Every fault becomes
a structured trap and execution is bounded by fuel, call depth, value/control
stack and memory page limits.

New `src/wasm_engine.zig` (compiled only into the gated `scoot-wasm` host and
a standalone test artifact — never into the zero-dependency core):
- Stack machine with locals/operand layout and native-recursion calls.
- Structured control flow: block/loop/if/else/br/br_if/br_table/return/
  call/call_indirect, unreachable/nop/drop/select.
- i32/i64 const, arithmetic, bitwise, shifts/rotates, comparisons,
  wrap/extend conversions.
- Linear memory (64KiB pages) with bounds-checked load/store variants,
  memory.size/grow, and bulk copy/fill.
- Globals, active data segments, a funcref table with active element
  segments and call_indirect type checking.
- Traps: unreachable, div/rem by zero, integer overflow, OOB memory/table,
  undefined element, indirect type mismatch, call-stack depth, out of fuel.

Wire `scoot-wasm run <module.wasm> <export> [int args...]` into the host.

Safety hardening (untrusted input must never panic):
- local.get/set/tee bounds-checked against the frame's local count.
- start function index validated at load and guarded in callFunction.
- block/if param arity checked before computing the operand base.
- export-referenced function index guarded in invokeExport.
- total locals per function capped at load time.

Tests: 18 hand-built `.wasm` fixtures (no external toolchain), including
regression tests for the malformed-input panic paths above. Wired a separate
`wasm_engine_tests` artifact into the `test` step, kept out of the core binary.

Docs/CHANGELOG updated (en + zh). Out of scope (later PRs): WASI (W2),
full validator/spec conformance (W3), floats + spec test suite (W4).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jamiesun
jamiesun merged commit dfaebc0 into main Jun 26, 2026
7 checks passed
@jamiesun
jamiesun deleted the feat/wasm-host-w1 branch June 26, 2026 16:19
jamiesun added a commit that referenced this pull request Jun 27, 2026
- wasm host W0-W4: integer, WASI preview1, type validation, float execution (#145, #147, #148, #149, #162)
- feat: wasm_tool action for compute-only packages (#154)
- wasm: narrow plugin sandbox to stdin/stdout/stderr/argv as a new hard rule (#164)
- feat: committed playground test environment with full action coverage (#161)
- release: single ReleaseSafe flavor, ship scoot-wasm artifacts, Homebrew tap (#166)
- fix(release): reference SCOOT_DOCKERHUB_* secrets so Docker Hub login is attempted
- docs: README rewrite + infographic, English comment translation (#160, #165, #146, #153)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant