Files
Asuro/doc/planning/wasm.md
t3hn3rd 26e2fd947e feature: add mkdocs-material documentation site with CI deployment
Generate comprehensive documentation for all 173 source units and
integrate mkdocs-material into the build toolchain. Documentation
covers kernel core, x86 architecture, drivers, applications, and
all subsystems with a tabbed navigation structure.

- Add mkdocs.yml with material theme, mermaid support, and tabbed nav
- Add doc/index.md landing page
- Generate doc/src/ markdown for every Pascal unit in src/
- Reorganize planning docs into doc/planning/
- Add compat, lvglh, and toolchain doc sections
- Update Dockerfile to install python3 and mkdocs-material (pinned <2)
- Replace pasdoc-based compile_docs.sh with mkdocs build --strict
- Add compile_docs.sh to the main build pipeline in compile.sh
- Add deploy-docs step in .drone.yml with host volume mount to nginx
- Add site/.gitkeep for build output directory
2026-03-08 21:06:14 +00:00

570 lines
26 KiB
Markdown

# WASM Integration — Architecture Plan
## Overview
Integrate the WASURO WebAssembly VM as a **first-class citizen** in Asuro's preemptive process model. WASM binaries are loaded from the filesystem, identified by magic header, and executed as preemptive processes. All I/O is routed through WASI hooks bridged to the process's StdIO buffers. VM faults are surfaced as structured errors with process-level exit code propagation.
### Key Design Decisions
| Decision | Choice | Rationale |
|---|---|---|
| Execution model | **Budgeted ticks + voluntary yield** (1024 opcodes per iteration) | Finer control over CPU time; sub-quantum fairness between WASM processes; explicit `proc_yield` after each batch |
| Program loading | **Extensible file type dispatcher** (magic-first, extension-fallback) | Typing any filename in the terminal routes to the correct handler; WASM is one handler among future others (images, flat binaries, etc.) |
| WASI ↔ StdIO bridge | **Shim layer with explicit context** | Wrapper record holds both `PProcessContext` + `PWASMProcessContext`; hooks look up IO buffers from the shim, no reliance on `CurrentProcess` global |
| VM fault handling | **Structured fault enum + diagnostic info** + exit code bubbling | `TWASMFaultKind` enum + `LastFaultIP` on the Asuro side; fault propagates to `PProcessContext.ExitCode` |
| Tick budget | **1024 opcodes** per yield | ~1ms of VM work per iteration at ~1 MIPS; responsive scheduling |
| File dispatch strategy | **Magic-first**, extension-fallback | Robust — file with wrong extension still works; first 8 bytes read, checked against registered magic headers |
| Handler API | **Handler receives file path + IO buffers** | Handler reads file itself via VFS; flexible for streaming/partial-read patterns |
| WASM cleanup | **Asuro-side cleanup function** (cannot modify `wasuro/`) | Walks `PWASMProcessContext` fields and kfrees all allocations; registered as resource binding for kill-safety |
| Code location | **`src/prog/wasm/`** subdirectory | Dedicated directory for WASM process integration: runner, IO bridge, cleanup |
| Dispatch registry | **Central `src/filedispatch.pas`** unit | Registration API for magic → handler mappings; vterminal calls `dispatch()` when command lookup fails |
## Architecture Diagram
```
Terminal input: "hello.wasm --arg1 foo"
│
▼
┌──────────────────┐ ┌────────────────────┐
│ vterminal │──command lookup──▶ │ stdio.findCommand │
│ processCommand │ │ → not found │
│ │◀────────────────────│ │
│ │ └────────────────────┘
│ │
│ │──file dispatch──▶ ┌──────────────────────┐
│ │ │ filedispatch.dispatch│
│ │ │ 1. resolve path │
│ │ │ 2. read first 8 bytes│
│ │ │ 3. check magic table │
│ │ │ $6D736100 → WASM │
│ │ │ 4. call handler │
│ │ └──────────┬───────────┘
│ │ │
│ │ ▼
│ │ ┌──────────────────────┐
│ │ │ wasmrunner.handler │
│ │ │ 1. read full file │
│ │ │ 2. wasm_load() │
│ │ │ 3. validate │
│ │ │ 4. create shim │
│ │ │ 5. serialize params │
│ │ │ 6. wire WASI hooks │
│ │ │ 7. create process │
│ │◀────────────────────│ 8. return PID │
│ ForegroundPID │ └──────────────────────┘
└──────────────────┘
Process executes via scheduler:
┌──────────────────────────────────┐
│ wasm_entry(ctx : PProcessContext)│
│ │
│ shim := PProcessWASMShim(Local) │
│ wctx := shim^.WASMCtx │
│ │
│ wasm_prepare_start(wctx) │
│ while wctx^.ExecutionState │
│ .Running do begin │
│ for i := 1 to 1024 do │
│ if not wasm_tick(wctx) then │
│ break; │
│ check PendingMsg │
│ proc_yield; │
│ end; │
│ │
│ propagate exit code │
│ { return → trampoline → │
│ psFinished → reap } │
└──────────────────────────────────┘
```
## Data Structures
### File Dispatch Registry (`src/filedispatch.pas`)
```pascal
const
MAX_MAGIC_LEN = 8; { Maximum magic header length }
MAX_HANDLERS = 16; { Maximum registered file handlers }
type
{ File handler callback — receives resolved path, params, and IO buffers.
Returns true if the handler accepted the file and launched a process. }
TFileHandler = function(path : pchar;
params : PParamList;
stdin_buf, stdout_buf, stderr_buf : POutBuf) : boolean;
{ A registered file type handler }
TFileHandlerEntry = record
Magic : array[0..MAX_MAGIC_LEN-1] of uint8; { Magic bytes to match }
MagicLen : uint8; { Length of magic sequence }
Name : array[0..15] of char; { Human-readable name, e.g. 'WASM' }
Handler : TFileHandler; { Handler function }
end;
PFileHandlerEntry = ^TFileHandlerEntry;
```
**API:**
```pascal
procedure init;
procedure registerHandler(magic : puint8; magicLen : uint8; name : pchar; handler : TFileHandler);
function dispatch(path : pchar; params : PParamList;
stdin_buf, stdout_buf, stderr_buf : POutBuf) : boolean;
```
**Flow:**
1. `dispatch()` resolves the path against the terminal's CWD.
2. Opens the file via VFS, reads first `MAX_MAGIC_LEN` bytes.
3. Iterates registered handlers, comparing magic bytes.
4. If a magic match is found, calls `handler(path, params, ...)`.
5. If no magic match, falls back to file extension lookup (future).
6. If no match at all, returns false — vterminal reports "unknown command or file".
### WASM Process Shim (`src/prog/wasm/wasmshim.pas`)
```pascal
type
{ Bridges the Asuro process model to the WASURO VM context }
PProcessWASMShim = ^TProcessWASMShim;
TProcessWASMShim = record
ProcessCtx : PProcessContext; { The owning Asuro process }
WASMCtx : PWASMProcessContext; { The WASURO VM context }
FileBuffer : puint8; { kalloc'd buffer holding the .wasm file }
FileSize : uint32; { Size of the loaded file }
ArgCount : uint32; { Number of command-line arguments }
ArgBuf : pchar; { Flat buffer of null-terminated arg strings }
ArgBufSize : uint32; { Total byte size of ArgBuf }
end;
```
The shim is allocated by the WASM handler and stored in `PProcessContext.Local`. All WASI hook implementations read from this shim to find the appropriate IO buffers and command-line arguments.
### WASM Fault Info (Asuro-side, in `wasmshim.pas`)
```pascal
type
TWASMFaultKind = (
wfNone, { No fault }
wfInvalidBinary, { Parse/validation failure }
wfNoStartExport, { _start not found in exports }
wfStackOverflow, { Operand or control stack overflow }
wfStackUnderflow, { Operand or control stack underflow }
wfOutOfBoundsMemory, { Linear memory access out of bounds }
wfUnreachable, { unreachable opcode executed }
wfDivisionByZero, { Integer division by zero }
wfInvalidFunctionIndex, { Call to non-existent function }
wfUnimplementedOpcode, { Opcode not yet implemented }
wfImportNotResolved, { Unresolved import at call time }
wfUnknown { Catch-all for unexpected failures }
);
```
**Note:** These are detected on the Asuro side by inspecting `PWASMProcessContext` state after tick returns false (checking `Running`, `ExitCode`, stack states, etc.). The wasuro VM itself is not modified.
## WASI ↔ StdIO Bridge (`src/prog/wasm/wasmio.pas`)
The bridge provides WASI hook implementations that route to Asuro's `POutBuf` system.
### Design
A **module-level variable** `ActiveShim : PProcessWASMShim` is set before each tick batch and cleared after. The WASI hooks read from this variable. This is safe because:
- WASM execution is single-threaded per process
- Hooks only fire synchronously during `wasm_tick()`
- Preemption saves/restores the full register state but does not re-enter the hook mid-execution
```pascal
var
ActiveShim : PProcessWASMShim;
{ Set before tick batch, cleared after }
procedure setActiveShim(shim : PProcessWASMShim);
procedure clearActiveShim;
{ WASI hook implementations — registered via wasm_set_fd_write etc. }
function asuro_fd_write(fd: uint32; buf: puint8; len: uint32): uint32;
function asuro_fd_read(fd: uint32; buf: puint8; len: uint32): uint32;
function asuro_proc_exit(code: uint32);
function asuro_clock_time_get(clock_id: uint32; precision: uint64; var time: uint64): uint32;
function asuro_random_get(buf: puint8; len: uint32): uint32;
function asuro_args_sizes_get(var count: uint32; var buf_size: uint32): uint32;
function asuro_args_get(argv: puint32; argv_buf: puint8): uint32;
{ Direct TWASMHostFunc override for args_get — registered as a custom
host function because the wasuro preview1 glue for args_get is a
stub that does not call the hook. Registered BEFORE
wasm_register_wasi_preview1 to take priority in first-match lookup. }
procedure asuro_wasi_args_get(Context : PWASMProcessContext);
```
### args passthrough
Command-line parameters received by the file handler as a `PParamList` (from `stdio`) are
serialized into a flat buffer of packed null-terminated strings stored in the shim:
```
serializeParams(shim, params)
→ shim^.ArgCount = number of params
→ shim^.ArgBuf = "param1\0param2\0param3\0"
→ shim^.ArgBufSize = total bytes including null terminators
```
- `asuro_args_sizes_get` (WASI hook): returns `ArgCount` / `ArgBufSize` from the active shim.
- `asuro_wasi_args_get` (direct host function): pops `argv_ptr` / `argv_buf_ptr` from the
WASM operand stack, walks the flat `ArgBuf`, writes strings into WASM linear memory at
`argv_buf_ptr`, and records each string's WASM offset into the `argv` array.
This approach is necessary because the wasuro `_WASI_args_get` glue procedure is a stub that
does not invoke the `OnArgsGet` hook. Our custom host function is registered in the host
function registry **before** the bulk WASI preview1 registration, so it is found first by
`find_in_registry`'s linear scan.
### fd_write Implementation
```
1. Check fd: 1 = stdout, 2 = stderr, else return WASI_EBADF
2. Get POutBuf from ActiveShim^.ProcessCtx^.StdOut (or StdErr)
3. Copy buf[0..len-1] into the POutBuf via bufWriteRaw or equivalent
4. Return len (bytes written)
```
### fd_read Implementation
```
1. Check fd: 0 = stdin, else return WASI_EBADF
2. Get POutBuf from ActiveShim^.ProcessCtx^.StdIn
3. If no data available, return 0 (non-blocking) or proc_await (blocking)
4. Copy available data into buf, return bytes read
```
### proc_exit Implementation
```
1. Store code in WASMCtx^.ExitCode
2. Set WASMCtx^.ExecutionState.Running := false
(Actually, cannot modify wasuro/ — so we handle this via the tick loop:
the proc_exit WASI hook in wasuro already handles this internally)
3. Set ProcessCtx^.ExitCode := code
```
## WASM Process Entry Point (`src/prog/wasm/wasmrunner.pas`)
```pascal
procedure wasm_entry(ctx : PProcessContext);
var
shim : PProcessWASMShim;
wctx : PWASMProcessContext;
i : uint32;
begin
shim := PProcessWASMShim(ctx^.Local);
wctx := shim^.WASMCtx;
{ Prepare for execution }
if not wasm_prepare_start(wctx) then begin
bufWriteStrLn(ctx^.StdErr, 'WASM: _start export not found');
ctx^.ExitCode := 1;
exit;
end;
{ Budgeted execution loop }
while wctx^.ExecutionState.Running do begin
{ Check for termination request }
if ctx^.PendingMsg = smTerminate then begin
bufWriteStrLn(ctx^.StdErr, 'WASM: terminated by signal');
ctx^.ExitCode := 130; { Convention: 128 + signal }
exit;
end;
{ Execute 1024 opcodes }
wasmio.setActiveShim(shim);
for i := 1 to 1024 do begin
if not wasm_tick(wctx) then
break;
end;
wasmio.clearActiveShim;
{ Yield to scheduler }
proc_yield;
end;
{ Propagate exit code }
ctx^.ExitCode := wctx^.ExitCode;
{ Detect and report faults }
if (not wctx^.ExecutionState.Running) and (wctx^.ExitCode = 0) then begin
{ Normal completion — check for abnormal state }
if wctx^.ExecutionState.IP >= wctx^.ExecutionState.Limit then begin
{ Ran off the end of code — could be normal or fault }
end;
end;
{ Cleanup: shim + WASM context freed via resource binding on reap }
end;
```
### File Handler (called by filedispatch)
```pascal
function wasm_file_handler(path : pchar;
params : PParamList;
stdin_buf, stdout_buf, stderr_buf : POutBuf) : boolean;
var
shim : PProcessWASMShim;
wctx : PWASMProcessContext;
buf : puint8;
bufsize : uint32;
proc : PProcessContext;
begin
wasm_file_handler := false;
{ 1. Read the file from VFS }
bufsize := vfs.GetFileSize(path);
if bufsize = 0 then begin
bufWriteStrLn(stderr_buf, 'WASM: cannot read file or file empty');
exit;
end;
buf := puint8(kalloc(bufsize));
if vfs.ReadFileByPath(path, buf, bufsize) <> bufsize then begin
kfree(void(buf));
bufWriteStrLn(stderr_buf, 'WASM: file read failed');
exit;
end;
{ 2. Parse the WASM binary }
wctx := wasm_load(buf, puint8(uint32(buf) + bufsize));
if (wctx = nil) or (not wctx^.ValidBinary) then begin
kfree(void(buf));
bufWriteStrLn(stderr_buf, 'WASM: invalid binary');
exit;
end;
{ 3. Create the shim }
shim := PProcessWASMShim(kalloc(SizeOf(TProcessWASMShim)));
shim^.WASMCtx := wctx;
shim^.FileBuffer := buf;
shim^.FileSize := bufsize;
{ 4. Wire up WASI hooks }
wasm_set_fd_write(wctx, @wasmio.asuro_fd_write);
wasm_set_fd_read(wctx, @wasmio.asuro_fd_read);
wasm_set_proc_exit(wctx, @wasmio.asuro_proc_exit);
wasm_set_clock_time_get(wctx, @wasmio.asuro_clock_time_get);
wasm_set_random_get(wctx, @wasmio.asuro_random_get);
wasm_set_args_sizes_get(wctx, @wasmio.asuro_args_sizes_get);
wasm_set_args_get(wctx, @wasmio.asuro_args_get);
wasm_register_wasi_preview1(wctx);
{ 5. Create the process }
proc := processmanager.create('WASM', @wasm_entry, void(shim), 5);
{ 6. Set process StdIO to the terminal's buffers }
proc^.StdIn := stdin_buf;
proc^.StdOut := stdout_buf;
proc^.StdErr := stderr_buf;
{ 7. Complete the shim — process context is now known }
shim^.ProcessCtx := proc;
{ 8. Register cleanup for kill-safety }
processmanager.bindResource(proc, rkCustom, void(shim), @wasm_cleanup);
wasm_file_handler := true;
end;
```
## WASM Context Cleanup (`src/prog/wasm/wasmcleanup.pas`)
Since we cannot modify `wasuro/`, the cleanup function lives on the Asuro side and manually frees all `PWASMProcessContext` allocations by walking the known struct layout.
```pascal
procedure wasm_cleanup(handle : void);
var
shim : PProcessWASMShim;
wctx : PWASMProcessContext;
i : uint32;
begin
shim := PProcessWASMShim(handle);
wctx := shim^.WASMCtx;
if wctx <> nil then begin
{ Free execution state allocations }
if wctx^.ExecutionState.Memory <> nil then
{ Free the WASM heap — wasm.types.heap stores a buffer pointer }
wasm_heap_free(wctx^.ExecutionState.Memory);
if wctx^.ExecutionState.Control_Stack <> nil then
wasm_stack_free(wctx^.ExecutionState.Control_Stack);
if wctx^.ExecutionState.Operand_Stack <> nil then
wasm_stack_free(wctx^.ExecutionState.Operand_Stack);
if wctx^.ExecutionState.Locals <> nil then
kfree(void(wctx^.ExecutionState.Locals));
if wctx^.ExecutionState.Globals <> nil then begin
if wctx^.ExecutionState.Globals^.Globals <> nil then
kfree(void(wctx^.ExecutionState.Globals^.Globals));
kfree(void(wctx^.ExecutionState.Globals));
end;
if wctx^.ExecutionState.Tables <> nil then begin
{ Free individual table entries }
for i := 0 to wctx^.ExecutionState.Tables^.TableCount - 1 do begin
if wctx^.ExecutionState.Tables^.Tables[i].Elements <> nil then
kfree(void(wctx^.ExecutionState.Tables^.Tables[i].Elements));
end;
if wctx^.ExecutionState.Tables^.Tables <> nil then
kfree(void(wctx^.ExecutionState.Tables^.Tables));
kfree(void(wctx^.ExecutionState.Tables));
end;
{ Free sections }
if wctx^.Sections.TypeSection <> nil then
kfree(void(wctx^.Sections.TypeSection));
if wctx^.Sections.ImportSection <> nil then
kfree(void(wctx^.Sections.ImportSection));
if wctx^.Sections.FunctionSection <> nil then
kfree(void(wctx^.Sections.FunctionSection));
if wctx^.Sections.ExportSection <> nil then
kfree(void(wctx^.Sections.ExportSection));
if wctx^.Sections.CodeSection <> nil then
kfree(void(wctx^.Sections.CodeSection));
if wctx^.Sections.MemorySection <> nil then
kfree(void(wctx^.Sections.MemorySection));
{ Free resolved imports }
if wctx^.ResolvedImports.Imports <> nil then
kfree(void(wctx^.ResolvedImports.Imports));
{ Free host function registry }
if wctx^.HostFuncRegistry.Entries <> nil then
kfree(void(wctx^.HostFuncRegistry.Entries));
{ Free the context itself }
kfree(void(wctx));
end;
{ Free the file buffer }
if shim^.FileBuffer <> nil then
kfree(void(shim^.FileBuffer));
{ Free the shim }
kfree(void(shim));
end;
```
**Important:** This cleanup must be kept in sync with `wasuro/`'s allocations. If upstream changes the context layout, this must be updated. We should **suggest upstream add `wasm_destroy()`** as an enhancement to make this unnecessary.
**Upstream suggestion:** Add `procedure wasm_destroy(ctx : PWASMProcessContext);` to `wasuro/wasm.pas` that recursively frees all allocations. This would replace our Asuro-side cleanup and be the "correct" API boundary.
## VTerminal Integration
### Modified Command Dispatch Flow
In `vterminal.processCommand`:
```
1. Parse command line → command name + params
2. Try stdio.findCommand(name)
3. If found → runCommand (existing flow, unchanged)
4. If NOT found → filedispatch.dispatch(name, params, stdin, stdout, stderr)
5. If dispatch returns true → ForegroundPID set by handler, terminal enters busy state
6. If dispatch returns false → "Unknown command or file: <name>"
```
This means typing `hello.wasm` at the terminal prompt will:
1. Fail command lookup (no registered command "hello.wasm")
2. Resolve to a file path (CWD + "hello.wasm")
3. filedispatch reads first 4 bytes → `$00 $61 $73 $6D` → WASM magic
4. Calls `wasmrunner.handler(path, params, ...)`
5. Handler loads, parses, creates process
6. Terminal shows output as the WASM program writes to stdout
## Exit Code Propagation
| Source | Process ExitCode | Description |
|---|---|---|
| WASM `proc_exit(N)` | N | Guest called `proc_exit` via WASI |
| Normal completion | `wctx^.ExitCode` (usually 0) | `_start` returned normally |
| `smTerminate` signal | 130 | Terminated by user (CTRL+C or TERMINATE command) |
| `smKill` signal | N/A (forced reap) | Process killed — no exit code, just reaped |
| Invalid binary | 1 | Parse or validation failure |
| `_start` not found | 1 | No entry point in the WASM module |
| VM fault (future) | 2 | Trapped (unreachable, div/0, OOB, etc.) |
## Implementation Phases
### Phase 6A: File Type Dispatcher
- [x] Create `src/filedispatch.pas` with handler registration + dispatch API
- [x] Static array of `TFileHandlerEntry` (max 16 handlers)
- [x] `dispatch()` reads first 8 bytes via VFS, matches magic, calls handler
- [x] Extension fallback path (stub for now)
- [x] Register `filedispatch.init()` in `progmanager.pas`
- [x] Integrate into `vterminal.processCommand` as fallback after command lookup fails
- [ ] Build + boot-test: no regressions, unrecognized files report "unknown command or file"
### Phase 6B: WASM Process Shim + IO Bridge
- [x] Create `src/prog/wasm/wasmshim.pas` — `TProcessWASMShim`, `TWASMFaultKind`
- [x] Create `src/prog/wasm/wasmio.pas` — WASI hook implementations bridging to POutBuf
- [x] `ActiveShim` pattern: set before tick batch, clear after
- [x] `asuro_fd_write`: fd 1→stdout, fd 2→stderr, else EBADF
- [x] `asuro_fd_read`: fd 0→stdin (non-blocking initially), else EBADF
- [x] `asuro_proc_exit`: set ExitCode, propagate to ProcessCtx
- [x] `asuro_clock_time_get`: use bios_data_area tick or RTC
- [x] `asuro_random_get`: use rand module
- [x] `asuro_args_sizes_get` / `asuro_args_get`: pass command-line params
- [x] Build + verify compilation
### Phase 6C: WASM Process Runner
- [x] Create `src/prog/wasm/wasmrunner.pas` — entry point + file handler
- [x] `wasm_entry()` — budgeted execution loop (1024 ticks), PendingMsg check, proc_yield
- [x] `wasm_file_handler()` — reads file, calls wasm_load, creates shim, wires hooks, creates process
- [x] Register WASM magic (`$00 $61 $73 $6D`) with filedispatch
- [x] Register in `progmanager.pas`
- [x] Build + verify compilation
### Phase 6D: WASM Context Cleanup
- [x] Create `src/prog/wasm/wasmcleanup.pas` — `wasm_cleanup()` procedure
- [x] Walk `PWASMProcessContext` fields, kfree all allocations
- [x] Registered via `processmanager.bindResource(proc, rkCustom, shim, @wasm_cleanup)`
- [ ] Test: create WASM process, kill it, verify no memory leaks via MEMINFO
- [x] Build + boot-test
### Phase 6E: Integration Testing
- [ ] Prepare a minimal WASM binary (hello world via fd_write) on the FAT32 image
- [ ] Boot, type filename at terminal, verify output appears
- [ ] Test CTRL+C → verify smTerminate → clean shutdown
- [ ] Test KILL → verify resource binding cleanup
- [ ] Test invalid WASM binary → verify error message
- [ ] Test non-existent file → verify error message
- [ ] Test multiple WASM processes simultaneously (background with `&`)
- [ ] Verify no memory leaks after process completion
### Phase 6F: Fault Diagnostics (stretch)
- [ ] Inspect `PWASMProcessContext` state after `wasm_tick` returns false to classify fault
- [ ] Report fault kind + IP to stderr
- [ ] Set process ExitCode = 2 for VM faults
- [ ] Consider: should we detect infinite loops (no progress after N ticks)?
## Files Created/Modified
| File | Changes |
|---|---|
| `src/filedispatch.pas` | **New** — file type dispatcher registry + dispatch API (returns PID) |
| `src/prog/wasm/wasmshim.pas` | **New** — `TProcessWASMShim`, `TWASMFaultKind` types |
| `src/prog/wasm/wasmio.pas` | **New** — WASI hook implementations bridging to POutBuf |
| `src/prog/wasm/wasmrunner.pas` | **New** — WASM process entry point + file handler |
| `src/prog/wasm/wasmcleanup.pas` | **New** — WASM context cleanup (Asuro-side teardown) |
| `src/driver/storage/ramdrive.pas` | **New** — RAM-backed VFS drive at `/disk/ram` |
| `src/driver/storage/vfs.pas` | **Modified** — implemented file handle table + OpenFile/ReadFile/WriteFile/CloseFile/FileSize routing to drive callbacks |
| `src/prog/vterminal.pas` | **Modified** — add filedispatch fallback in `processCommand` (fg + bg) |
| `src/progmanager.pas` | **Modified** — add `ramdrive.init`, `filedispatch.init`, `wasmrunner.init` |
## Risk Considerations
| Risk | Mitigation |
|---|---|
| WASM infinite loop | Budgeted ticks + preemptive scheduling means the loop doesn't block the system; user can CTRL+C or KILL |
| Cleanup out of sync with wasuro/ | Document dependency; suggest `wasm_destroy()` upstream |
| VFS file read failures | Defensive checks at every step; errors written to stderr before bailing |
| WASI hook re-entrancy | `ActiveShim` is set/cleared per tick batch; hooks only fire synchronously within `wasm_tick` |
| Large WASM binaries | File buffer is `kalloc`'d — limited by available kernel heap. Future: streaming parser |
| Stack overflow in WASM VM | VM uses its own operand/control stacks (heap-allocated), not the 8KB process stack |
| Multiple WASM processes | Each has its own `PProcessWASMShim` + `PWASMProcessContext`; no shared state |
| FAT32 8.3 filename limits | WASM files will have `.WAS` or similar 3-char extension; magic-based dispatch means extension doesn't matter |