feature: WASI preview1 support, per-context isolation & glue API

Implement full WASI snapshot_preview1 with 46 registered host functions,
per-context hook/registry isolation, and a single-unit public API.

WASI Preview1 (15 new handler units):
- Tier 1: fd_write, fd_read, fd_close, fd_seek, fd_prestat_get,
  fd_prestat_dir_name, fd_fdstat_get, proc_exit, environ_*, args_*
- Tier 2: clock_time_get, clock_res_get, random_get
- Tier 3: 14 fd_* stubs, 10 path_* stubs (ENOSYS)
- Tier 4: poll_oneoff, proc_raise, sched_yield, 4 sock_* stubs

Per-context isolation:
- Move WASIHooks (12 hook table) and HostFuncRegistry onto
  TWASMProcessContext — each context is fully independent
- Dynamic registry: starts at 16 entries, doubles on demand, no cap
- All 15 preview1 handlers read hooks from context, not globals

Glue API (wasm.pas):
- Single entry point: wasm_init, wasm_load, wasm_set_*,
  wasm_register_wasi_preview1, wasm_register_host_func,
  wasm_resolve_imports, wasm_start, wasm_prepare_start, wasm_tick,
  wasm_dump_state
- WASURO.lpr simplified to use only the wasm unit

Refactoring:
- Rename leb128.pas -> wasm.types.leb128.pas (updates 40+ opcode units)
- New wasm.types.wasi.pas with WASI errno/filetype constants
- New wasm.types.context.pas fields for hooks + registry types
- New wasm.vm.setup unit (find_start, count_func_imports)
- New wasi.emu.hooks for emulator-side hook implementations
- Add read_uint16, write_uint16 to wasm.types.heap

Testing:
- 10 new test units: hooks, registry, glue, stubs, clock, random,
  extensibility, call, types, vm.setup (1087 tests / 232 units)
- E2E WAT test runner (wat.ps1) with 9 integration tests:
  hello, exit42, multiwrite, prestat, fdstat, environ, clock,
  clockres, random

121 files changed, 5391 insertions(+), 267 deletions(-)
This commit is contained in:
t3hn3rd committed 2026-02-25 21:44:00 +00:00
1 parent 378f999143
commit 302dcb66f0
121 files changed
+5394 -270

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
;; clock.wat — WASI clock_time_get test
;; Calls clock_time_get for realtime clock, verifies ESUCCESS (exit 0)
;; @expect-exit 0
(module
(import "wasi_snapshot_preview1" "clock_time_get"
(func $clock_time_get (param i32 i64 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func (export "_start")
(local $errno i32)
;; clock_time_get(clock_id=0 [realtime], precision=1000, time_ptr=0)
(local.set $errno
(call $clock_time_get
(i32.const 0) ;; CLOCK_REALTIME
(i64.const 1000) ;; precision
(i32.const 0))) ;; time output pointer
;; Exit with errno (should be 0 = ESUCCESS)
(call $proc_exit (local.get $errno))
)
)
+23
View File
@@ -0,0 +1,23 @@
;; clockres.wat — WASI clock_res_get test
;; Calls clock_res_get for realtime clock, verifies ESUCCESS (exit 0)
;; @expect-exit 0
(module
(import "wasi_snapshot_preview1" "clock_res_get"
(func $clock_res_get (param i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func (export "_start")
(local $errno i32)
;; clock_res_get(clock_id=0 [realtime], resolution_ptr=0)
(local.set $errno
(call $clock_res_get
(i32.const 0) ;; CLOCK_REALTIME
(i32.const 0))) ;; resolution output pointer
;; Exit with errno (should be 0 = ESUCCESS)
(call $proc_exit (local.get $errno))
)
)
+36
View File
@@ -0,0 +1,36 @@
;; environ.wat — WASI environ test
;; Calls environ_sizes_get, expects count=0 and buf_size=0.
;;
;; @expect-exit 0
(module
(import "wasi_snapshot_preview1" "environ_sizes_get"
(func $environ_sizes_get (param i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func $main (export "_start")
(local $errno i32)
;; environ_sizes_get(count_ptr=0, buf_size_ptr=4)
(local.set $errno
(call $environ_sizes_get (i32.const 0) (i32.const 4)))
;; Should return ESUCCESS=0
(if (i32.ne (local.get $errno) (i32.const 0))
(then (call $proc_exit (i32.const 1))))
;; count at offset 0 should be 0
(if (i32.ne (i32.load (i32.const 0)) (i32.const 0))
(then (call $proc_exit (i32.const 2))))
;; buf_size at offset 4 should be 0
(if (i32.ne (i32.load (i32.const 4)) (i32.const 0))
(then (call $proc_exit (i32.const 3))))
;; All good
(call $proc_exit (i32.const 0))
)
)
+15
View File
@@ -0,0 +1,15 @@
;; exit42.wat — WASI proc_exit test
;; Calls proc_exit(42) immediately. Verify ExitCode=42.
;;
;; @expect-exit 42
(module
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func $main (export "_start")
(call $proc_exit (i32.const 42))
)
)
+33
View File
@@ -0,0 +1,33 @@
;; fdstat.wat — WASI fd_fdstat_get test
;; Calls fd_fdstat_get for stdout (fd=1), expects ESUCCESS.
;; Checks that filetype byte = 2 (CHARACTER_DEVICE).
;;
;; @expect-exit 0
(module
(import "wasi_snapshot_preview1" "fd_fdstat_get"
(func $fd_fdstat_get (param i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func $main (export "_start")
(local $errno i32)
;; fd_fdstat_get(fd=1, buf=0) — write fdstat struct at offset 0
(local.set $errno
(call $fd_fdstat_get (i32.const 1) (i32.const 0)))
;; Should return ESUCCESS=0
(if (i32.ne (local.get $errno) (i32.const 0))
(then (call $proc_exit (i32.const 1))))
;; First byte (filetype) should be 2 (CHARACTER_DEVICE)
(if (i32.ne (i32.load8_u (i32.const 0)) (i32.const 2))
(then (call $proc_exit (i32.const 2))))
;; All good
(call $proc_exit (i32.const 0))
)
)
+39
View File
@@ -0,0 +1,39 @@
;; hello.wat — WASI "Hello, World!" test program
;; Writes "Hello, World!\n" to stdout via fd_write, then exits with code 0.
;;
;; @expect-exit 0
;; @expect-stdout Hello, World!
(module
;; Import fd_write from WASI
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
;; Import proc_exit from WASI
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
;; 1 page of linear memory (64KB), exported as "memory"
(memory (export "memory") 1)
;; "Hello, World!\n" at offset 8
(data (i32.const 8) "Hello, World!\n")
;; iovec at offset 0: { buf_ptr=8, buf_len=14 }
(data (i32.const 0) "\08\00\00\00" ;; buf ptr = 8
"\0e\00\00\00") ;; buf len = 14
(func $main (export "_start")
;; fd_write(stdout=1, iovs=0, iovs_len=1, nwritten=100)
(call $fd_write
(i32.const 1) ;; fd = stdout
(i32.const 0) ;; iovs pointer
(i32.const 1) ;; iovs count
(i32.const 100) ;; nwritten pointer
)
drop ;; discard errno
;; proc_exit(0)
(call $proc_exit (i32.const 0))
)
)
+39
View File
@@ -0,0 +1,39 @@
;; multiwrite.wat — WASI fd_write with multiple iovecs
;; Writes "AB" then "CD" as two separate iovecs in one fd_write call.
;;
;; @expect-exit 0
;; @expect-stdout ABCD
(module
(import "wasi_snapshot_preview1" "fd_write"
(func $fd_write (param i32 i32 i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
;; String data
(data (i32.const 100) "AB") ;; 2 bytes at offset 100
(data (i32.const 110) "CD") ;; 2 bytes at offset 110
;; iovec array at offset 0 (2 entries, 8 bytes each)
;; iovec[0] = { ptr=100, len=2 }
(data (i32.const 0) "\64\00\00\00" ;; buf ptr = 100
"\02\00\00\00") ;; buf len = 2
;; iovec[1] = { ptr=110, len=2 }
(data (i32.const 8) "\6e\00\00\00" ;; buf ptr = 110
"\02\00\00\00") ;; buf len = 2
(func $main (export "_start")
;; fd_write(stdout=1, iovs=0, iovs_len=2, nwritten=200)
(call $fd_write
(i32.const 1) ;; fd = stdout
(i32.const 0) ;; iovs pointer
(i32.const 2) ;; iovs count = 2
(i32.const 200) ;; nwritten pointer
)
drop
(call $proc_exit (i32.const 0))
)
)
+35
View File
@@ -0,0 +1,35 @@
;; prestat.wat — WASI prestat enumeration test
;; Calls fd_prestat_get for fds 3..7, expects EBADF (8) for all.
;; Proves libc's pre-open enumeration loop terminates correctly.
;;
;; @expect-exit 0
(module
(import "wasi_snapshot_preview1" "fd_prestat_get"
(func $fd_prestat_get (param i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func $main (export "_start")
(local $errno i32)
;; fd_prestat_get(3, buf=200) should return EBADF=8
(local.set $errno
(call $fd_prestat_get (i32.const 3) (i32.const 200)))
;; If errno != 8, exit with 1 (failure)
(if (i32.ne (local.get $errno) (i32.const 8))
(then (call $proc_exit (i32.const 1))))
;; fd_prestat_get(4, buf=200) should also return EBADF
(local.set $errno
(call $fd_prestat_get (i32.const 4) (i32.const 200)))
(if (i32.ne (local.get $errno) (i32.const 8))
(then (call $proc_exit (i32.const 2))))
;; All good — exit 0
(call $proc_exit (i32.const 0))
)
)
+23
View File
@@ -0,0 +1,23 @@
;; random.wat — WASI random_get test
;; Calls random_get to fill 8 bytes, verifies ESUCCESS (exit 0)
;; @expect-exit 0
(module
(import "wasi_snapshot_preview1" "random_get"
(func $random_get (param i32 i32) (result i32)))
(import "wasi_snapshot_preview1" "proc_exit"
(func $proc_exit (param i32)))
(memory (export "memory") 1)
(func (export "_start")
(local $errno i32)
;; random_get(buf=0, buf_len=8)
(local.set $errno
(call $random_get
(i32.const 0) ;; buf pointer
(i32.const 8))) ;; 8 bytes
;; Exit with errno (should be 0 = ESUCCESS)
(call $proc_exit (local.get $errno))
)
)