Skip to content

WasiHost

Defined in: packages/runtime/src/wasm/wasi-host.ts:261

Host-side implementation of wasi_snapshot_preview1 for fishbowl.

Architecture. WasiHost is a stateful syscall dispatcher. It owns the fd table (stdio + preopens + dynamically opened files/dirs), the readdir cookie cache, and per-fd path tracking. It routes file I/O directly to a bound ProcFS — there is no MEMFS shadow.

Two-phase lifecycle.

  1. Construct with args, env, and preopens. The memory reference is a placeholder at this point — only the syscall shape is known.
  2. Post-instantiation, call WasiHost.bindMemory with the real linear memory, WasiHost.bindStdio for streams, WasiHost.bindProcFs for file ops, and WasiHost.bindAsyncify to enable async syscalls.

ABI shim. WasiHost.getImports wraps every syscall with a memory refresh because WASM code may call memory.grow() at any time (notably sbrk in our custom malloc), detaching the ArrayBuffer. It also bridges the i64-arg mismatch between native wasm32-wasip1 (bigint) and Emscripten (split lo/hi i32 pairs) for fd_seek, path_open, fd_readdir, fd_filestat_set_size, fd_filestat_set_times, and path_filestat_set_times.

Async syscalls. Every async syscall is implemented as a standalone async doXxx method wrapped by an Asyncify handleSleep bridge — this structure is designed to swap to JSPI later with minimal churn.

const mem = new WasiMemory(wasmMemory)
const host = new WasiHost({ memory: mem, args, env, preopens })
const { instance } = await WebAssembly.instantiate(binary, host.getImports())
const wasmMem = instance.exports.memory as WasmLinearMemory
host.bindMemory(new WasiMemory(wasmMem), wasmMem)
host.bindStdio(stdinQueue, proc.stdout, proc.stderr)
host.bindProcFs(proc.fs)
;(instance.exports._start as () => void)()

new WasiHost(opts): WasiHost

Defined in: packages/runtime/src/wasm/wasi-host.ts:308

WasiHostOpts

WasiHost

args_get(argv_ptr_ptr, argv_buf_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:519

Writes the argument strings into argv_buf as NUL-terminated UTF-8, and fills the argv pointer array with addresses to each string.

number

number

number


args_sizes_get(argc_ptr, argv_buf_size_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:500

Returns the number of command-line arguments and the total byte size required to store them all as NUL-terminated strings.

number

number

number


bindAsyncify(asyncify): void

Defined in: packages/runtime/src/wasm/wasi-host.ts:370

Store asyncify binding for suspend/resume in fd_read/fd_write and other async syscalls.

Accepts both EmscriptenAsyncify and RawAsyncify — only the handleSleep + state surface is used.

AsyncifyLike

void


bindMemory(mem, rawMemory): void

Defined in: packages/runtime/src/wasm/wasi-host.ts:356

Rebind memory after WebAssembly instantiation.

Call this once the instance is created and the memory export is available. The stored raw memory reference is used by WasiHost to refresh views after memory.grow().

WasiMemory

WasmLinearMemory

void


bindProcFs(procFs): void

Defined in: packages/runtime/src/wasm/wasi-host.ts:412

Bind a ProcFS for file and directory operations (Tier 2 and 3).

Must be called before path_open, fd_seek, fd_readdir, etc. can function. Without it, file-system syscalls return ENOSYS.

ProcFS

void


bindStdio(stdinQueue, stdout, stderr): void

Defined in: packages/runtime/src/wasm/wasi-host.ts:386

Wire stdio streams to the fd table entries.

Call after construction to connect ProcContext streams. Any argument passed as undefined leaves that fd slot as-is (initial placeholder).

WasmTTYInput | undefined

Input source for fd 0 reads (waitForByte).

{ write: Promise<number>; } | undefined

Writable for fd 1 writes.

{ write: Promise<number>; } | undefined

Writable for fd 2 writes.

void


clock_time_get(clockid, _precision, timestamp_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:742

WASI clock_time_get — read a clock as nanoseconds since its epoch.

Divergence from spec: WASI defines separate PROCESS_CPUTIME_ID and THREAD_CPUTIME_ID clocks, but single-threaded JS has no meaningful distinction from monotonic wall-clock — both fall back to performance.now(). _precision is ignored; callers treat the returned value as best-effort.

number

bigint

number

number


doFdClose(fd): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:713

Async implementation of fd_close for file fds. Public for direct unit testing.

number

Promise<number>


doFdFilestatGet(fd): Promise<{ errno: number; stat?: StatResult; }>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1188

Async implementation of fd_filestat_get. Public for direct unit testing.

number

Promise<{ errno: number; stat?: StatResult; }>


doFdFilestatSetSize(fd, size): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1440

Async implementation for fd_filestat_set_size. The sync caller validates fd/kind/writable/path before dispatching here, but this method is also public for direct unit testing, so it retains the writable guard to stay safe when called without the sync wrapper. Public for direct unit testing.

number

bigint

Promise<number>


doFdRead(fd, iovecs): Promise<{ data: Uint8Array; errno: number; nread: number; }>

Defined in: packages/runtime/src/wasm/wasi-host.ts:933

Async implementation of fd_read, decoupled from Asyncify for future JSPI swap. Public for direct unit testing.

number

readonly object[]

Promise<{ data: Uint8Array; errno: number; nread: number; }>


doFdReaddir(fd, bufLen, cookie): Promise<{ data: Uint8Array; errno: number; }>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1604

Async implementation of fd_readdir. Public for direct unit testing.

Each dirent entry: d_next: u64 (cookie for next entry) d_ino: u64 (inode from the DirEntry, 0 when the fileserver omits it) d_namlen: u32 (byte length of name) d_type: u8 (WASI filetype) name: bytes

Total header: 8 + 8 + 4 + 1 = 21 bytes (but WASI pads d_type field) Actually per WASI spec: d_next(8) + d_ino(8) + d_namlen(4) + d_type(4, padded to u32) = 24 bytes header

number

number

number

Promise<{ data: Uint8Array; errno: number; }>


doFdWrite(fd, data): Promise<{ errno: number; written: number; }>

Defined in: packages/runtime/src/wasm/wasi-host.ts:857

Async implementation of fd_write, decoupled from Asyncify for future JSPI swap. Public for direct unit testing.

number

Uint8Array

Promise<{ errno: number; written: number; }>


doPathCreateDirectory(absPath): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1701

Async implementation of path_create_directory. Public for direct unit testing.

string

Promise<number>


doPathFilestatGet(absPath, followSymlinks?): Promise<{ errno: number; stat?: StatResult; }>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1246

Async implementation of path_filestat_get. Public for direct unit testing.

string

boolean = true

When true, follow symlinks (stat). When false, don’t follow (lstat).

Promise<{ errno: number; stat?: StatResult; }>


doPathOpen(absPath, oflags, rightsBase, followSymlinks?): Promise<{ errno: number; wasiFd: number; }>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1086

Async implementation of path_open. Public for direct unit testing.

string

number

bigint

boolean = true

When true, follow symlinks on final component (SYMLINK_FOLLOW). When false (nofollow), return ELOOP if final component is a symlink.

Promise<{ errno: number; wasiFd: number; }>


doPathReadlink(absPath): Promise<{ data: Uint8Array<ArrayBuffer>; errno: number; }>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1398

Async implementation of path_readlink. Public for direct unit testing.

string

Promise<{ data: Uint8Array<ArrayBuffer>; errno: number; }>


doPathRemove(absPath): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1752

Shared async implementation for path_remove_directory and path_unlink_file. Both delegate to procFs.remove() — fishbowl does not distinguish file vs dir removal. Public for direct unit testing.

string

Promise<number>


doPathRemoveDirectory(absPath): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1765

string

Promise<number>


doPathRename(oldPath, newPath): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1808

Async implementation of path_rename. Public for direct unit testing.

string

string

Promise<number>


doPathSymlink(target, absPath): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1343

Async implementation of path_symlink. Public for direct unit testing.

string

string

Promise<number>


doPathUnlinkFile(absPath): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1772

string

Promise<number>


doPollOneoff(subs): Promise<WasiEvent[]>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1892

Async implementation of poll_oneoff — races clock timeouts against fd readiness, then returns events for all ready subscriptions. Public for direct unit testing.

WasiSubscription[]

Promise<WasiEvent[]>


doSetTimes(absPath, mtimNs, fstFlags): Promise<number>

Defined in: packages/runtime/src/wasm/wasi-host.ts:1527

Shared async implementation for fd_filestat_set_times and path_filestat_set_times. Public for direct unit testing.

atime bits (FSTFLAGS_ATIM, FSTFLAGS_ATIM_NOW) are intentionally ignored — fishbowl follows Plan 9 in not tracking access time.

string

bigint

number

Promise<number>


environ_get(environ_ptr_ptr, environ_buf_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:563

WASI environ_get — write KEY=VALUE\0 strings + pointer array.

Format matches POSIX environ[]: pointers into a packed byte buffer, one NUL-terminated entry per variable.

number

number

number


environ_sizes_get(environ_count_ptr, environ_buf_size_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:542

WASI environ_sizes_get — count and byte size of the env block.

Mirrors WasiHost.args_sizes_get for KEY=VALUE strings; each serialized pair includes its NUL terminator.

number

number

number


fd_close(fd): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:672

Closes an open file descriptor. For stdio fds: no-op (returns SUCCESS). For file fds: delegates to procFs.close() (async via Asyncify). For preopens/dirs: removes from table.

number

number


fd_fdstat_get(fd, fdstat_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:591

Returns the status of an open file descriptor.

fdstat layout (24 bytes): offset 0: u8 filetype offset 1: u8 padding offset 2: u16 fdflags offset 4: u32 padding offset 8: u64 rights_base offset 16: u64 rights_inheriting

number

number

number


fd_filestat_get(fd, buf_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1169

WASI fd_filestat_get — stat an open fd, write 64-byte filestat struct.

Resolves the fd’s path via WasiHost.fdPaths, then delegates to the shared stat helper. Returns ERRNO_BADF for unknown fds.

number

number

number


fd_filestat_set_size(fd, size_lo, size_hi?): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1419

WASI fd_filestat_set_size — truncate/extend a file via open fd.

Guards: fd must be a writable file fd with a tracked path. Delegates to procFs.wstat({ size }). Non-writable or non-file fds return ERRNO_BADF/ERRNO_INVAL per POSIX ftruncate semantics.

number

number | bigint

number

number


fd_filestat_set_times(fd, _atim_lo, _atim_hi, mtim_lo, mtim_hi, fst_flags): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1469

WASI fd_filestat_set_times — set timestamps via open fd.

Divergence: atime (FSTFLAGS_ATIM / FSTFLAGS_ATIM_NOW) is intentionally not tracked — fishbowl follows Plan 9 in ignoring access time. An atime-only call returns ERRNO_SUCCESS as a harmless no-op; see WasiHost.doSetTimes.

number

number | bigint

number | bigint

number | bigint

number | bigint

number

number


fd_prestat_dir_name(fd, path_ptr, path_len): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:652

WASI fd_prestat_dir_name — write the preopen’s absolute path to memory.

Truncates to path_len bytes if the caller’s buffer is smaller than the size reported by WasiHost.fd_prestat_get. Returns ERRNO_BADF for non-preopen fds.

number

number

number

number


fd_prestat_get(fd, prestat_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:631

WASI fd_prestat_get — report that fd is a preopen and give name length.

POSIX mapping: returns ERRNO_BADF for any fd that isn’t a preopen. WASI programs iterate fds starting at 3 calling this until it returns EBADF (ERRNO_BADF) to discover the preopen list — that’s the contract, not a bug.

number

number

number


fd_read(fd, iovs_ptr, iovs_len, nread_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:900

WASI fd_read — scatter-read into iovecs from stdin or a file fd.

Stdin divergence: reads exactly ONE byte per call regardless of iovec capacity. This matches the tty-bridge pattern — returning more would require repeatedly calling waitForByte() inside a single Asyncify cycle, which double-unwinds the stack. File fds read up to total iovec capacity in one shot.

POSIX errno mapping: ERRNO_BADF for unknown fds or reads from stdout/stderr; ERRNO_NOSYS when no ProcFS is bound. EOF is signaled by nread=0, errno=SUCCESS.

number

number

number

number

number


fd_readdir(fd, buf_ptr, buf_len, cookie_lo, cookie_hi, bufused_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1561

WASI fd_readdir — page directory entries into a caller buffer.

Cookie-based pagination: the guest starts at cookie=0 and re-calls with the last entry’s d_next value until the returned buffer is smaller than requested. The cached listing (see WasiHost.readdirCache) is keyed by fd — refreshed on cookie=0, reused for continuation cookies so concurrent directory modifications don’t fragment the paginated response.

number

number

number

number | bigint

number

number

number


fd_seek(fd, offset_lo, offset_hi, whence, newoffset_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:993

WASI fd_seek: reposition read/write offset of a file descriptor.

number

WASI file descriptor

number | bigint

lower 32 bits of i64 offset

number

upper 32 bits of i64 offset

number

0=SET, 1=CUR, 2=END

number

pointer to u64 output (new offset)

number


fd_tell(fd, offset_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1027

WASI fd_tell: return current read/write offset of a file descriptor.

number

WASI file descriptor

number

pointer to u64 output

number


fd_write(fd, iovs_ptr, iovs_len, nwritten_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:834

WASI fd_write — scatter-write from iovecs to stdio or a file fd.

Gathers iovec contents synchronously (before Asyncify can detach the buffer), then delegates the actual write to WasiHost.doFdWrite wrapped in an Asyncify cycle. nwritten_ptr receives the byte count only on success.

POSIX errno mapping: ERRNO_BADF for unknown fds or writes to stdin; ERRNO_NOSYS when no ProcFS is bound.

number

number

number

number

number


gatherIovecs(iovs_ptr, iovs_len): Uint8Array

Defined in: packages/runtime/src/wasm/wasi-host.ts:806

Read an array of WASI iovecs from memory and concatenate the referenced byte regions into a single contiguous Uint8Array. Public so it can be unit-tested directly.

number

number

Uint8Array


getImports(): WasiImportObject

Defined in: packages/runtime/src/wasm/wasi-host.ts:2118

Return the WebAssembly import object for this host.

All implemented syscalls are bound to this and wrapped in a memory refresh that re-syncs WasiMemory views before each call (to survive WASM-side memory.grow()). Unimplemented syscalls return ERRNO_NOSYS as a harmless stub.

WasiImportObject

{ wasi_snapshot_preview1: { <syscall>: fn, ... } } — ready to pass as the imports argument to WebAssembly.instantiate.

const { instance } = await WebAssembly.instantiate(binary, host.getImports())

path_create_directory(dirfd, path_ptr, path_len): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1690

WASI path_create_directory — mkdir relative to a preopen/dir fd.

POSIX errno mapping: ERRNO_EXIST when the target already exists, ERRNO_NOTDIR / ERRNO_NOENT if parent is missing or not a dir.

number

number

number

number


path_filestat_get(dirfd, flags, path_ptr, path_len, buf_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1217

WASI path_filestat_get — stat a path, write 64-byte filestat struct.

flags & LOOKUPFLAGS_SYMLINK_FOLLOW selects stat vs lstat semantics (follow the final symlink or return the link node itself). Path is resolved relative to a preopen or dir fd with sandbox enforcement.

number

number

number

number

number

number


path_filestat_set_times(dirfd, _flags, path_ptr, path_len, _atim_lo, _atim_hi, mtim_lo, mtim_hi, fst_flags): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1500

WASI path_filestat_set_times — set timestamps via path.

Path-variant counterpart to WasiHost.fd_filestat_set_times; same atime-is-ignored divergence. Path resolved through the dirfd sandbox.

number

number

number

number

number | bigint

number | bigint

number | bigint

number | bigint

number

number


path_open(dirFd, opts): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1060

WASI path_open: open a file or directory relative to a preopen fd.

The ABI-level path_open takes 9 scalar arguments (native wasm32-wasip1) or 11 scalar arguments (Emscripten, which splits each i64 rights mask into lo/hi i32 pairs). Both calling conventions are normalized at the import dispatch site in WasiHost.getImports; by the time we reach this method, the two rights masks are canonical bigints.

number

the preopen fd the path is resolved against.

PathOpenOpts

normalized WASI path_open arguments. Every field maps directly to a WASI spec field; see PathOpenOpts.

number


path_readlink(dirfd, path_ptr, path_len, buf_ptr, buf_len, bufused_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1366

WASI path_readlink — read a symlink’s target into a caller-provided buffer.

Truncates silently when the target is longer than buf_len (WASI spec does not define an overflow error). bufused_ptr receives the actual byte count written, so callers can detect truncation by comparing against the buffer size they supplied.

number

number

number

number

number

number

number


path_remove_directory(dirfd, path_ptr, path_len): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1723

WASI path_remove_directory — rmdir relative to a preopen/dir fd.

Divergence: fishbowl’s procFs.remove() does not distinguish file vs directory — POSIX rmdir would return ERRNO_NOTDIR when applied to a regular file, but here the underlying fs makes that call. Mostly benign since WASI guests check filestat before calling rmdir.

number

number

number

number


path_rename(old_dirfd, old_path_ptr, old_path_len, new_dirfd, new_path_ptr, new_path_len): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1788

WASI path_rename — rename/move a file or directory.

Both source and destination are resolved through their respective dirfds with sandbox enforcement. Paths escaping the preopen subtree surface as ERRNO_ACCES (from resolvePath); underlying fs errors surface via mapFsError.

number

number

number

number

number

number

number


path_symlink(old_path_ptr, old_path_len, dirfd, new_path_ptr, new_path_len): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1324

WASI path_symlink — create a symbolic link.

WASI argument order is (target, dirfd, linkpath)old_path is the symlink target (an opaque string, NOT resolved against dirfd), and new_path is the link location (resolved relative to dirfd with sandbox enforcement). Empty targets map to ERRNO_INVAL.

number

number

number

number

number

number


path_unlink_file(dirfd, path_ptr, path_len): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1740

WASI path_unlink_file — remove a regular file relative to a preopen/dir fd.

Delegates to the shared WasiHost.doPathRemove — same file-vs-dir divergence as path_remove_directory above.

number

number

number

number


poll_oneoff(in_ptr, out_ptr, nsubscriptions, nevents_ptr): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:1831

WASI poll_oneoff — multiplexing primitive.

Uses a CUSTOM Asyncify path (not asyncOp) because zero-timeout polls must return synchronously without triggering handleSleep.

Phase 1: clock, fd_read (stdin async + file always-ready), fd_write (always-ready). Pipe fd readiness deferred to Phase 2.

number

number

number

number

number


proc_exit(exit_code): never

Defined in: packages/runtime/src/wasm/wasi-host.ts:793

WASI proc_exit — terminate the guest with an exit code.

Throws WasiExitSentinel (NOT an Error) so the host can unwind the JS stack cleanly and recover the numeric exit code in wasiExec.

number

never


random_get(buf_ptr, buf_len): number

Defined in: packages/runtime/src/wasm/wasi-host.ts:767

WASI random_get — fill memory with cryptographic random bytes.

Backed by globalThis.crypto.getRandomValues; chunks requests larger than 64 KiB because the Web Crypto API rejects oversized buffers. Returns ERRNO_SUCCESS for zero-length requests without touching memory.

number

number

number


resolvePath(dirfd, pathPtr, pathLen): { absPath: string; } | { errno: number; }

Defined in: packages/runtime/src/wasm/wasi-host.ts:473

Resolve a WASI path relative to a preopen directory fd. Returns the absolute path or a WASI errno on failure.

Enforces sandbox: resolved path must stay within the preopen subtree. Public for unit testing.

number

number

number

{ absPath: string; } | { errno: number; }