Skip to main content

WASI in Exec Mode

Wasmrun's exec mode provides WASI Preview 1 support, enabling WASM modules to interact with the host system through a controlled syscall interface.

Supported Syscalls

SyscallDescriptionStatus
fd_writeWrite to file descriptors (stdout, stderr) — reads iovecs from memory
fd_readRead from file descriptors (stdin returns EOF)
fd_closeClose a file descriptor
fd_seekSeek within a file descriptor
fd_fdstat_getFile descriptor status (filetype, flags, rights)
fd_prestat_getPreopened directory info (returns EBADF — no preopens yet)✅ Stub
fd_prestat_dir_namePreopened directory name✅ Stub
args_getRetrieve command-line arguments from memory
args_sizes_getGet argument count and buffer sizes
environ_getRetrieve environment variables from memory
environ_sizes_getGet environment variable count and sizes
clock_time_getGet current time (realtime, monotonic) in nanoseconds
random_getFill buffer with random bytes
proc_exitExit with a status code (terminates execution cleanly)
poll_oneoffPoll for events (stub — returns ENOSYS)✅ Stub
sched_yieldYield execution (stub — returns success)✅ Stub
path_openOpen a file by path🔧 Planned
path_filestat_getStat a path🔧 Planned
path_create_directoryCreate directory🔧 Planned
fd_readdirRead directory entries🔧 Planned

How It Works

WASI syscalls are registered as host functions in the linker under the wasi_snapshot_preview1 module namespace. When the WASM module calls an imported function, the executor dispatches to the corresponding Rust implementation with access to linear memory.

WASM module calls fd_write(fd=1, iovs, iovs_len, nwritten)
→ executor detects imported function (func_idx < import_count)
→ dispatches to host function via linker
→ host reads iovec structs {buf_ptr, buf_len} from linear memory
→ reads string bytes from memory at buf_ptr
→ appends bytes to WasiEnv stdout buffer
→ writes total bytes_written to nwritten pointer in memory
→ returns errno (0 = success)

Environment Setup

The WasiEnv struct configures the WASI environment:

WasiEnv::new()
.with_args(vec!["program".into(), "arg1".into()])
.with_env("KEY".into(), "value".into())
  • Arguments — written to linear memory via args_get / args_sizes_get
  • Environment variables — written as KEY=VALUE\0 strings via environ_get / environ_sizes_get
  • Output capture — stdout/stderr buffered in WasiEnv for programmatic access via get_stdout() / get_stderr()

Linker Integration

The executor uses a Linker to resolve imported functions. The linker maps (module, name) pairs to host function implementations:

let wasi_env = Arc::new(Mutex::new(WasiEnv::new()));
let linker = create_wasi_linker(wasi_env.clone());
let mut executor = Executor::new_with_linker(module, linker)?;

Host functions receive &mut LinearMemory so they can read pointers and write results directly into the module's address space.

Clock Support

Clock IDConstantDescription
REALTIME0Wall clock time (nanoseconds since Unix epoch)
MONOTONIC1Monotonically increasing (for measuring intervals)

Process Exit

proc_exit terminates execution by raising a sentinel error that the executor catches. The exit code is extracted and returned to the caller:

match executor.execute_with_args(func_idx, args) {
Ok(_) => 0,
Err(e) => Executor::is_proc_exit(&e).unwrap_or(-1),
}

Filesystem

WASI filesystem integration is planned for v0.17.4. It will bridge the exec mode executor to wasmrun's existing WasiFilesystem, which provides:

  • Mount host directories into the WASM sandbox
  • Path traversal protection
  • Read-only mode support
  • File size limits

See the roadmap for details on upcoming WASI filesystem support.