Skip to content

Slice 1 — WASIX skeleton + hello-world smoke test#367

Merged
taybenlor merged 13 commits into
taybenlor:mainfrom
liverpoolie:wasix/slice-1-skeleton
Apr 21, 2026
Merged

Slice 1 — WASIX skeleton + hello-world smoke test#367
taybenlor merged 13 commits into
taybenlor:mainfrom
liverpoolie:wasix/slice-1-skeleton

Conversation

@liverpoolie

Copy link
Copy Markdown
Contributor

Summary

Stands up the WASIX class skeleton per WASIX-PLAN.md:

  • WASIX (sibling of WASI, not subclass) — composes an internal WASI and delegates wasi_snapshot_preview1 / wasi_unstable to it; owns memory and shares it with the internal WASI; start() returns WASIXExecutionResult.
  • WASIXContext — mirrors WASIContext, with optional synchronous provider slots (stored, not wired this slice).
  • wasix-32v1 ABI module — Result errno enum (parallels preview1's), ClockId, Signal. Every wasix_32v1 import is stubbed to ENOSYS. No speculative ABI.
  • Raw sync providers (providers.ts) — ClockProvider, RandomProvider, TTYProvider, ThreadsProvider, FutexProvider, SignalsProvider, SocketsProvider, ProcProvider. No Promise return types.
  • AsyncCapable<T> generic + derived AsyncClockProvider, … (providers/async.ts) — worker-only, consumed by WASIXWorkerHost in a later slice.
  • Public surfacelib/main.ts re-exports WASIX, WASIXContext, all provider types; src/main.ts puts WASIX + WASIXContext on window for Playwright.
  • Smoke test — reuses the existing hello-world.wasi.wasm (built from packages/wasi/programs/src/bin/hello-world.rs); Playwright asserts exitCode === 0 and stdout === "hello, world\n" across chromium / firefox / webkit.
  • Vite breadcrumbvite.config.js carries a comment noting COOP/COEP will be needed in Slice 4; headers stay off for now.

Deviation note — preview1 smoke binary, not cargo wasix

The plan originally mentioned a Rust program compiled via cargo wasix. A cargo-wasix println!("hello, world") routes stdout through the wasix_32v1 namespace, which this slice stubs to ENOSYS — the binary would never print. Since WASIX delegates wasi_snapshot_preview1 back to its internal WASI, running the existing preview1 hello-world.wasi.wasm under WASIX is exactly the smoke test we want for Slice 1. A cargo wasix variant will land in a later slice once wasix_32v1 providers are wired up.

Test plan

  • chromium green (`npx playwright test --project=chromium` → 65/65)
  • firefox green (65/65)
  • webkit green (65/65)
  • `tsc --noEmit` clean
  • no regressions in existing WASI tests (`core`, `libc`, `libstd`, `args`, `stdio`, `reactor` — 195/195 combined across three browsers)
  • new `wasix-smoke.spec.ts` passes across three browsers

Follow-ups (separate PRs, stacked on this one)

  • Slice 2 — Clock + Random providers
  • Slice 3 — filesystem provider + wasmer test harness
  • Slice 4 — WASIXWorkerHost + Atomics bridge (will require COOP/COEP)

liverpoolie and others added 13 commits April 21, 2026 21:11
Adds lib/wasix/wasix-32v1.ts: the ABI module for the wasix_32v1 import
namespace, mirroring the structure of lib/wasi/snapshot-preview1.ts.
Every syscall in wasix_32v1 returns ENOSYS this slice; later slices wire
providers as they implement each group.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WASIXContext is the execution context passed to WASIX.start(). It has the
same fs/args/env/stdin/stdout/stderr/isTTY/debug surface as WASIContext and
adds optional provider slots (clock, random, tty, threads, futex, signals,
sockets, proc). All provider methods are synchronous; async-capable variants
live in providers/async.ts for WASIXWorkerHost (future slice).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…reads, Futex, Signals, Sockets, Proc

Raw synchronous provider interfaces consumed by the WASIX class. No Promise
return types — every method is called synchronously from inside the WebAssembly
guest. AsyncCapable<T> lifts any provider to optionally return Promises; those
variants are accepted by WASIXWorkerHost (Slice 4) via the syscall bridge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iew1/unstable to internal WASI

WASIX is a sibling of WASI (not a subclass). It composes a WASI instance
internally to service wasi_snapshot_preview1 and wasi_unstable imports.
Memory is owned by WASIX and shared with the internal WASI instance via
direct field assignment after instantiation.

getImportObject() returns all three namespaces: wasix_32v1 (all stubs),
wasi_snapshot_preview1, and wasi_unstable (both delegated). proc_exit in
preview1/unstable is intercepted to throw WASIXExit so WASIX.start() can
catch it without depending on WASI's private WASIExit class.

Also adds WASIXExecutionResult to lib/types.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ses WASIX + WASIXContext on window

Adds WASIX, WASIXContext, WASIX32v1 namespace, all provider interfaces, and
AsyncCapable types to the package root export. Attaches WASIX and WASIXContext
to window alongside the existing WASI surface so Playwright injected tests can
reach them via page.evaluate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… wat2wasm in build:tests

Slice 1 proves preview1 delegation only; wasix_32v1 imports are ENOSYS-stubbed.
A cargo-wasix "hello, world" would exercise wasix_32v1 for stdout and hit
ENOSYS, so the smoke binary is hand-rolled in WAT (fd_write + proc_exit from
wasi_snapshot_preview1). The Rust/cargo-wasix version will land in a later
slice once wasix_32v1 providers are wired up.
…hain is available

The Rust source is the intended build target for cargo wasix; the committed
wasix-hello.wasm is built from the equivalent wasix-hello.wat until the
cargo-wasix toolchain is set up (test:cargo-wasix in test:prepare).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ttern so fresh checkouts get a clear hint instead of silent failure
Ben asked us to skip the hand-rolled WAT smoke binary and reuse an
existing WASI preview1 test (hello-world.wasi.wasm, already built
from packages/wasi/programs/src/bin/hello-world.rs by build:tests).
This removes the wasix-hello/ WAT source, the committed wasix-hello.wasm
artefact, the .gitignore exception, and the wat2wasm/test:wabt wiring
in package.json.

The smoke test is rewired in the next commit.
Use the existing WASI preview1 Rust hello-world (built from
packages/wasi/programs/src/bin/hello-world.rs) instead of the
now-removed wasix-hello.wasm. WASIX delegates preview1 fd_write /
proc_exit to its internal WASI, so running a plain preview1 binary
under WASIX is exactly the smoke test we want for slice 1.

Renamed the spec file from wasix-hello.spec.ts → wasix-smoke.spec.ts
since "wasix-hello" no longer has a referent.

@taybenlor taybenlor left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@taybenlor taybenlor enabled auto-merge (squash) April 21, 2026 11:47
@taybenlor taybenlor merged commit 5aec906 into taybenlor:main Apr 21, 2026
2 checks passed
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.

2 participants