uce/spikes/wasm-phase4
2026-06-12 19:55:35 +00:00
..
2026-06-12 19:55:35 +00:00
2026-06-12 19:55:35 +00:00
2026-06-12 19:55:35 +00:00

spikes/wasm-phase4 — production mechanics kill-test spike

Phase 4 validates the operational mechanics needed before a WASM worker can be trusted in production. This spike is intentionally smaller than the future UCE worker integration: it uses tiny WAT modules instead of generated UCE units, but it exercises the Wasmtime controls and workspace cleanup paths the worker will use. It is also deliberately the first spike on the Wasmtime-specific C++ API (wasmtime.hh) — fuel, epochs, store limiters, and snapshots do not exist in the portable wasm.h surface, so runtime-agnosticism ends here by design.

Run on k-uce:

bash spikes/wasm-phase4/build_runner.sh
/tmp/uce/wasm-phase4/runner

Expected final line:

PHASE4 EXIT CRITERION: PASS

What this proves:

  • Reusable compiled artifact / snapshot proxy: modules are compiled once and then instantiated in fresh stores. Not OS CoW yet, but it validates the shape of "shared core artifact + per-request workspace birth".
  • Unharmed worker: after all six kill cases, the same engine serves a healthy request that completes with the expected value. This — not merely "didn't crash" — is the worker half of the Phase 4 exit criterion.
  • CPU limits, both mechanisms: fuel (deterministic, per-instruction cost) and epoch interruption (near-zero overhead, ticker thread, traps as interrupt). Production default is epoch per the Phase 0 findings; fuel is validated as the deterministic fallback. Note: the engine epoch only advances, so every store must set its own deadline.
  • Memory limit, actually load-bearing: the OOM fixture grows by 10 pages — within its declared max (100) — so only the store limiter (2 pages) can deny the growth. The denial is observed by the guest (memory.grow → -1) and converted to a trap.
  • Trap-to-error-page data path: every kill case's gate asserts the captured message contains a wasm backtrace and the expected cause. Fixture frames show <unknown> — readable production traces require units to keep their name section (or a symbolication side-file in the artifact cache).
  • Trace summarizer (src/lib/wasm_trace.h): trap messages are rendered through the production collapse facility (repeated frames → ×N lines, mangled symbols demangled, cause/detail split out). The stack-exhaustion case gates it on live trap output here; site/tests/core.uce gates the parsing on canned messages in the native suite.
  • Handle cleanup with falsifiable checks: closers must run exactly once per handle and while the store is still alive (production closers may flush guest-resident state); destructor-only cleanup fails the ordering check.

Kill fixtures (all genuinely trap):

  • unreachable: __builtin_trap analog.
  • oob-access: wild pointer; load at 128 KiB from a 64 KiB memory.
  • stack-exhaustion: runaway recursion → call stack exhausted.
  • infinite-loop-fuel / infinite-loop-epoch: same loop, both CPU limits.
  • oom-limiter: limiter-denied memory.grow → guest converts -1 to a trap.

A literal C++ null-pointer dereference is deliberately absent: address 0 is valid wasm linear memory, so *(int*)nullptr does not trap — it silently writes inside the workspace, which is then dropped at request end (still strictly better than a native SIGSEGV). See WASM-PROPOSAL §10 for the recorded risk/policy.

Still deferred to production Phase 4:

  • real core snapshot + OS-level CoW birth;
  • wiring wasm_trace.h summaries into the UCE error-page UI (the summarizer itself is done and gated); name-section policy for unit artifacts;
  • wiring these traps into linux_fastcgi.cpp / the WASM worker backend;
  • real runtime handle-table cleanup for sqlite/mysql/sockets/tasks;
  • artifact ABI versioning end-to-end;
  • memory-limit policy for guest allocators that return failure instead of trapping (bump allocator vs dlmalloc behavior under the limiter).