WASIX Slice 3.5: Module-instantiation surface (env imports + v2 stubs + COOP/COEP)#370
Merged
taybenlor merged 12 commits intoMay 10, 2026
Conversation
Slice 3.5 carves the env.memory auto-detect path out of Slice 6 because every wasix-libc binary imports a shared env.memory regardless of whether it uses threads, so FS-only tests can't instantiate without it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WASIX.start now compiles the module first, walks WebAssembly.Module.imports for env.memory / env.__indirect_function_table, and constructs a matching WebAssembly.Memory / WebAssembly.Table from the import descriptor (or validates the host-supplied override against it). The resolved values land in imports.env so wasix-libc binaries — which import a shared env.memory — can instantiate. Adds memory and indirectFunctionTable overrides to WASIXContextOptions for hosts that need to share the import surface across sibling instances (threaded configurations, Slice 6). Emits a one-shot console.warn when crossOriginIsolated is false: the shared-memory import otherwise fails with a generic engine error that doesn't point at COOP/COEP. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wasix-libc binaries built with the upstream sysroot link against the flag-extended v2 surface alongside v1. Stub the v2 entries that surface on the wasmer integration suite so the binaries can instantiate: - proc_exit2 → throws WASIXExit (same exit path as proc_exit). - path_open2, fd_dup2, fd_fdflags_get / _set → ENOSYS, deferred to the fd-table extraction in Slice 9. - proc_exec3, proc_spawn2, proc_fork_env → ENOSYS, deferred to the proc provider in Slice 7. - proc_signals_get / _sizes_get → ENOSYS, deferred to the signals provider in Slice 8. Stubs live in the import-table assembly, not in wasix-32v1.ts (the ABI table stays for spec-level constants). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wasix-libc binaries import a shared env.memory. Browsers reject the underlying SharedArrayBuffer unless the host page is cross-origin-isolated, so the dev server (used by Playwright via test:server) now sends COOP same-origin + COEP require-corp. Replaces the Slice-4 NOTE comment that flagged this as needed once the worker host landed; the import surface in Slice 3.5 needs the same headers without the worker host. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WASIX guests import a SharedArrayBuffer-backed env.memory. TextDecoder rejects views over a SharedArrayBuffer with "The provided ArrayBufferView value must not be shared", so the preview1 fd_write stdio path and every path_* helper that decodes a guest string blew up before reaching the file system. Slice into a fresh non-shared Uint8Array before decoding. Cheap for single-iov stdio writes and short paths; preview1 binaries (non-shared memory) keep the same shape with one extra .slice() per decode. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier env-auto-detect commit assumed
WebAssembly.Module.imports() exposed each entry's descriptor (memory
limits, shared flag, table element type), but the standard surface
returns only { module, name, kind } — engines do not expose limits.
Confirmed against V8 (Chrome / Node 22): the import section parse
has to happen on the raw bytes.
Replace the static-API approach with a small import-section parser
(tolerant of leading custom sections, varuint32 / memory64 limits,
funcref / externref tables). WASIX.start now buffers the response
bytes, walks the import section for env.memory / env.__indirect_function_table,
constructs matching memory + table from the descriptors, then compiles.
Other plumbing this slice picks up so wasix-libc binaries reach main:
- Share the inner WASI's drive with WASIDriveFileSystemProvider.drive
when the host hands one over. wasix-libc reaches for both
wasi_snapshot_preview1 and wasix_32v1 fs imports — the inner WASI
must see the same files. Other (opaque) FileSystemProvider
implementations keep an empty inner drive (those hosts shouldn't
have wasix-libc binaries in the first place).
- Wire path_open2 to the existing WASIDriveFileSystemProvider.pathOpen,
ignoring the v2 fdflags2 arg until the fd-table extraction (Slice 9).
wasix-libc routes every open()/opendir() through path_open2 so the
ENOSYS stub blocked the entire suite at the first stat.
- proc_signals_get / _sizes_get return SUCCESS with size=0 instead of
ENOSYS. wasix-libc's _start treats ENOSYS as a fatal init error and
exits 71 before reaching main; reporting "no signals" lets early
init finish. Real signal delivery lands in Slice 8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the ENV_IMPORTS_BUILT_TESTS block — every binary in that list now instantiates under the new env-import surface. Re-classify each former entry per current failure mode: - closing-pre-opened-dirs, create-and-remove-dirs, create-dir-at-cwd*, cross-fs-rename, cwd-to-home, distinct-inodes-same-basename, fstatat-with-chdir, open-under-file → requires-future-feature with REQUIRES_CWD_PLUMBING note. wasix-libc's default cwd is /home and the wasmer runner mounts the test dir there; the binaries instantiate and reach main but stat / mkdir / opendir fail at the first cwd-relative resolution because the WASIX runtime doesn't yet implement getcwd / chdir or the /home preopen. - pwrite-and-size → requires-future-feature, distinct cause: the test opens absolute /data/my_file.txt, which needs the wasmer --volume=.:/data mount. WASIDrive's single preopen can't model it. - fd-close → requires-provider-sockets (test opens a TCP socket). - popen, posix_spawn → requires-provider-proc. - vfork → requires-provider-proc (note distinguishing wasix's vfork semantics from POSIX). - udp → requires-provider-sockets. - fs-mount, mount-tmp-locally → requires-future-feature (mount syscall). - msync-* / munmap-* / read-after-munmap → requires-future-feature (mmap / file-backed mappings not modelled). - symlink-open-read-write → requires-future-feature (symlinks). No proposed-pass test currently flips green: the env-import surface lets every binary instantiate but the cwd / chdir plumbing the wasmer runner provides is out of scope for this slice. Surface + classification ship here so downstream slices can wire individual provider tokens (grep `requires-provider-proc` etc.) atomically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wasix-libc's compiled-in default cwd is /home and the wasmer test runner
mounts each test dir there (`--volume .` is shorthand for `.=/home`).
The flat-path WASIDrive only modelled the implicit fd 3 = "/" preopen,
so wasix-libc binaries that called open("foo") had nowhere to resolve
the cwd-relative path against.
Extend the drive to accept additional preopens at construction (each
binds a guest-visible name like "/home" to a drive-internal prefix),
and surface them through WASIDriveFileSystemProvider so the WASIX
runtime can answer `fd_prestat_get(4)` with the new mount.
Also fix a latent bug in WASIDrive's `open` / `pathStat` where opening
a sub-directory under any non-root preopen used `prefix = /<path>/`
instead of `<parent.prefix><path>/` — fine for the implicit fd 3 = "/"
case (the strings collapse), but it would have routed every sub-dir
lookup under fd 4 = "/home/" to the wrong subtree.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
wasix-libc reads `getcwd` from `__wasilibc_resolve_path` to turn relative paths absolute before walking the preopen table; without a real implementation every cwd-relative open fell through with ENOTCAPABLE before reaching the FS provider. Wire `getcwd` and `chdir` to a runtime-side cwd string (default `/home`, matching wasix-libc's compiled-in default and the wasmer runner's mount point), and override preview1's `fd_prestat_get` / `fd_prestat_dir_name` so wasix-libc's preopen discovery sees the FS provider's full preopen map (fd 4 = /home alongside fd 3 = ".") rather than wasi.ts's hardcoded fd-3-only view. `chdir` validates the resolved target lives under a known preopen and resolves to a directory via the FS provider; cwd-relative path resolution further down the syscall stack is unnecessary because wasix-libc handles that itself before calling `path_*`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mount each per-test input set under /home/<rel-path> instead of /<rel-path>, configure the FS provider with a /home preopen at fd 4, and prime PWD=/home so wasix-libc's startup resolver sees the cwd before falling back to getcwd. This mirrors what `wasmer run --volume .` does (the runner's default mount point is /home because that's wasix-libc's default cwd). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cwd / chdir / /home preopen unblock four of the previous
REQUIRES_CWD_PLUMBING tests (cross-fs-rename, cwd-to-home,
distinct-inodes-same-basename, fstatat-with-chdir). The remaining
five surface drive-level limitations rather than cwd gaps; replace
the cwd carve-out with notes pointing at the real root cause:
- create-dir-at-cwd, create-dir-at-cwd-with-chdir: WASIDrive does not
normalise `./` segments so mkdirat(cwd_fd, "./testN") writes a
path subsequent stat cannot find.
- create-and-remove-dirs: drive does not enforce parent-dir-exists
on mkdir.
- closing-pre-opened-dirs: closing a preopen drops the fd entry,
breaking wasmer-style libc preopen retention across close.
- open-under-file: drive does not validate parent type, so
open("file/child") never returns ENOTDIR.
All five lift with the WASIDrive extraction in a later slice.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aps (refs #22) Brings the remaining FS-category wasix-suite tests to green by fixing the drive-level bugs that were carved out in the previous skip-map pass, plus the harness-side gaps that the carve-outs were masking: * `WASIDrive`: normalize `./` and empty path segments via a `normalizeRelative` helper so `mkdirat(cwd, "./foo")` and `stat("foo")` resolve to the same flat-path key. `..` is rejected (the flat-path map can't model traversal). * `WASIDrive.pathCreateDir` / `open`: reject paths whose parent component is missing (ENOENT) or a regular file (ENOTDIR), via a shared `validateParent` guard. POSIX-correct and lets `create-and-remove-dirs` / `open-under-file` enforce their negative assertions. * `WASIDrive.close`: keep preopen fds in the openMap on user-initiated close. wasix-libc's preopen cache continues to resolve cwd-relative paths via the "closed" fds — matches wasmer's runtime behaviour and unblocks `closing-pre-opened-dirs`. * `WASIDriveFileSystemProvider.fdReaddir`: synthesize POSIX `.` / `..` entries and filter the `.runno` directory sentinel. * `WASIX` preview1 overrides: route `path_filestat_get`, `path_open`, `path_create_directory`, `path_remove_directory`, `path_unlink_file`, `path_rename`, and `fd_readdir` through the WASIX provider. wasix-libc imports several of these from preview1 (notably `rmdir` → `path_remove_directory`, `stat` → `path_filestat_get`), where the preview1 stubs in wasi.ts either return ENOSYS or skip the provider's POSIX translation layer. * Provider error mapping: rewrite drive-side `ENOTCAPABLE` to `ENOENT` for WASIX callers so wasix-libc's `errno == ENOENT` checks fire on missing paths. Preview1 callers go through wasi.ts directly and still see the original code (the WASI test suite asserts on it explicitly). * Harness: parse `--volume host:guest` mounts from each test's `run.sh` and pre-seed the guest mount points (plus the implicit `/tmp` MemFS) as empty directories in the in-memory FS. Also seed `main.c` / `run.sh` and synthesize empty `main.wasm` / `output` placeholders so cwd-listing tests see the same layout wasmer's runner provides. * `WASIX_SUITE_SKIPS`: drop the FS-category entries that now pass (`create-dir-at-cwd`, `create-dir-at-cwd-with-chdir`, `create-and-remove-dirs`, `closing-pre-opened-dirs`, `open-under-file`, `pwrite-and-size`). 204 tests pass across chromium / firefox / webkit; 45 skipped (all non-FS provider gaps — sockets, threads, mmap, proc, fd-table).
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements issue #22.
Closes #22
Summary
Make every buildable wasmer-suite binary instantiate under the WASIX runtime so the FS-only tests can run end-to-end against the Slice 3 filesystem provider. Carve-out from Slice 6 (memory auto-detect) and Slice 4 (COOP/COEP) — neither the threads provider nor the worker host ships here.
What landed
env.memorydeclared shared regardless of whether it uses threads).env.memory/env.__indirect_function_tableauto-detect.WebAssembly.Module.imports()only exposes{ module, name, kind }— descriptors (limits, shared flag, table element type) are not standard, confirmed against V8 (Chrome / Node 22). Implementation walks the import section on the raw bytes (tolerates leading custom sections, varuint32 / memory64 limits, funcref / externref tables), thenWebAssembly.compile+instantiate. Memory + table can be overridden via newWASIXContextOptions.memory/.indirectFunctionTable.wasix_32v1v2 import stubs.fd_dup2,path_open2(wired via existingpathOpenignoring the v2 fdflags2 arg),fd_fdflags_get/_set,proc_exit2(throws WASIXExit, same exit semantics as proc_exit),proc_exec3,proc_spawn2,proc_fork_env,proc_signals_get/_sizes_get— names taken fromwasm-objdumpof the wasmer-suite binaries.proc_signals_get/_sizes_getreturn SUCCESS with size=0 instead of ENOSYS so wasix-libc's_startreaches main (ENOSYS is a fatal init error there — exits 71).test:server).console.warnon WASIX construction whencrossOriginIsolated === false. Without it, the shared-memory import failure looks like a generic engine error.wasi_snapshot_preview1andwasix_32v1filesystem imports — preopen discovery (fd_prestat_get) is preview1. The internal WASI now reuses theWASIDriveFileSystemProvider's drive when one is supplied; opaque providers keep an empty inner drive.TextDecoderrejects views over aSharedArrayBuffer; copying via.slice()before decode unblocks every preview1 stdout/path decode for WASIX guests.WASIDriveacceptsWASIDrivePreopen[]; harness wires/homeat fd 4 to matchwasmer run --volume ..getcwd/chdirbound on the wasix_32v1 surface (withresolveAbsolute+ longest-preopen match + ENOTDIR/ENOENT shape). Preview1fd_prestat_get/_dir_nameoverridden so wasix-libc's preopen walk sees fd 4../normalisation, parent-component validation onopen/pathCreateDir, preopen retention on close,./..synthesis +.runnofiltering infdReaddir, preview1 path syscalls routed through the WASIX provider, ENOTCAPABLE → ENOENT mapping for WASIX callers, and harness--volume host:guestparsing so absolute mount points are pre-seeded.ENV_IMPORTS_BUILT_TESTSblock; classify each former entry per current failure mode (full list in the commit messages). Tokens stay grep-stable for downstream slice work.Test status
requires-provider-sockets).Surfaced env imports (vs the proposed list)
Inspected via
wasm-objdump -x public/bin/wasix-tests/*.wasm:env.memory(shared, initial=129, max=65536) — ✓ matched the hypothesis.env.__indirect_function_table— not present in any of the 25 wasmer-suite binaries at the pinned SHA. The runtime still handles it (constructed from the descriptor when surfaced) so the surface is forward-compatible.fd_dup2 / path_open2 / proc_exit2: alsoproc_signals_get,proc_signals_sizes_get,fd_fdflags_get,fd_fdflags_set,proc_exec3,proc_spawn2,proc_fork_env. All stubbed, all mapped to TODO(slice-N) pointers.Test plan
npm run test:prepare:wasix-suite— every binary builds (25/25).npx tsc --noEmitclean.npx playwright test tests/wasix-suite.spec.ts --project=chromium --project=firefox --project=webkit— 204 pass / 45 skip.npx playwright test tests/core.spec.ts tests/libc.spec.ts tests/libstd.spec.ts tests/reactor.spec.ts tests/wasix-clock-random.spec.ts tests/wasix-fs-provider.spec.ts --project=chromium— 57 passed.args.spec.ts/stdio.spec.ts/wasix-smoke.spec.tsneed cargo-built*.wasi.wasmbinaries; not exercised in this slice.🤖 Generated with Claude Code