You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
Added
Experimental WASI Preview 2 target — vera compile/run --target wasi-p2 (#237). vera compile --target wasi-p2 emits a binary WebAssembly component whose vera.* IO + Random host imports are implemented on top of WASI 0.2 interfaces, runnable by any stock wasip2 host — wasmtime run works with no flags and no Vera bindings (wasmtime.wat2wasm accepts component text, so no external componentizer and no new dependency). The component wraps the unchanged core module (the default --target wasm emission is untouched, pinned by test): each (import "vera" "op") becomes a same-named call_indirect shim through a funcref dispatch table the main module defines and exports, and a compiler-generated adapter core module implements all 14 IO/Random ops (plus the contract_fail/overflow_trap channels) over canon-lowered WASI imports, planting itself into the table via active elem segments strictly before any lifted export can run — the naive main↔adapter import topology is an instantiation cycle, which the component model forbids. cabi_realloc is a bump allocator over a GC-exempt 64 KiB scratch arena below gc_heap_start (host-written data needs no rooting and a collection can never move a half-written block — the #593/#695 UAF class, host-side); data crossing back into Vera is copied into GC-heap blocks under explicit shadow-stack rooting, and a 500-arg GC-pressure stress pins it. Canonical-ABI variant discriminants are read i32.load8_u (they are u8; retptr-slab reuse leaves garbage in the padding — found live as an EOF misread as last-operation-failed with a stale list length as a "handle"). vera run --target wasi-p2 executes the component under wasmtime-py's built-in add_wasip2() host (new vera/runtime/wasi_host.py) with the core path's ExecuteResult contract: stdout/stderr capture (+ the #543 live tee), os.environ snapshot, cwd preopen for file ops, argv, IO.read_line CRLF handling matching the core host's universal-newlines behavior (a lone \r separator stays content — spec chapter 13), and the #516 trap-kind taxonomy — a contract violation classifies as contract_violation with the full violation text recovered from the WASI stderr channel (structured frames do not cross the component boundary; the JSON envelope's frames is empty — spike check 5's documented degradation). A program using any host family beyond IO/Random gets a diagnostic naming the family — never a silent fallback to the core target. Both entry worlds are exported: wasi:cli/run@0.2.0 (what stock wasmtime run invokes) and a plain lifted main for scalar returns (Int/Nat as s64, Float64, Unit); a String/heap-returning main runs via wasi:cli/run and reports no value. Inherent WASI 0.2 divergences are documented in the new spec chapter 13 and pinned by tests: wasi:cli/exit@0.2.0 carries ok/err only, so IO.exit(n) degrades to exit status 0/1 under every wasip2 host. Validation is live, not parse-only (the design study caught a mis-spelled filesystem error-code case — quota, not disk-quota — only at add_wasip2 instantiation): the suite instantiates and executes every artifact, a dual-target conformance differential runs all 88 run-level conformance programs under both targets and requires byte-identical stdout/stderr (71 execute identically; the rest are family-gated, main-less, or wall-clock-dependent — the sweep also surfaced that handle[Exn] programs need the wasip2 runner to enable the exceptions proposal like the core engine does), and a stock-wasmtime-CLI smoke test runs where the CLI is installed. This closes #237 as written (its listed ops are exactly the IO family) as an experimental WASI Preview 2 target (IO + Random surface) — not a blanket "WASI 0.2 compliant" claim; the remaining vera.* families (Map, Set, Decimal, Json, Html, Md, Regex, Math, Http, Inference, State, Async) stay host-bound on the core target. Also: the doc-builtin-shadowing scanner now skips .claude/ (session-tooling worktrees carry full repo copies that re-flagged every spec chapter).
Fixed
GC use-after-free: pair-typed let bindings (String / Array<T>) were never shadow-rooted (#847, fixed by #846). The plain-let pair branch in translate_block (vera/wasm/context.py) stored the (ptr, len) pair into WASM locals without pushing the pointer onto the GC shadow stack — the last unrooted sibling of the #705 scalar-i32 let fix and the #707 let-destruct pair fix (whose review comment had already named this gap class). Unobservable for Vera-side producers — an array literal or string builtin shadow-pushes its own freshly-allocated block at the alloc site, and that push survives to the function epilogue, masking the missing let root — but a host-import pair (IO.args → Array<String>, IO.read_line → String) is rooted only host-side during construction (_ShadowGuard, popped on return), so the first Vera-side allocation after the let could collect the block while the locals still pointed at it: the free list overwrites the payload's first words and reads through the binding see reclaimed bytes (IO.print of an IO.args element after a nat_to_string call printed 2::++2@ instead of 2:aa+bb; string_join over the swept backing chased overwritten element pointers). Found stress-testing #237 under VERA_EAGER_GC=1; no WASI code involved, and reachable under ordinary collection pressure. Pair lets are now rooted unconditionally — static and null pointers are ignored by the conservative scan's heap range check, so the push is harmless for non-heap values. New targeted eager-GC tests pin the fix (args / read_line reproducers) and confirm the neighbouring host-import ADT paths (IO.read_file → Result<String, String>, IO.get_env → Option<String>) were already rooted by the #705/#707-era fixes.
De-flaked the #841 live-interrupt test; one red combo no longer cancels the CI matrix (#848). test_async_await_keyboard_interrupt_live_request printed its progress marker betweenasync(...) and await, racing the worker thread's request — whose arrival gates the test's interrupt — against the guest reaching the print: a lost race yields the correct exit 130 with empty stdout and failed the stdout assertion (twice on macos-26 runners across PR #846's matrices, each time cancelling the other 11 fail-fast jobs). The print now precedes the async(...), so program order supplies a real happens-before (buffer write → request issuance → arrival → interrupt) and the assertion strengthens from in to ==; prompt issuance at the async point stays pinned by the #841 request-ordering and operand-stack tests, so the reorder loses nothing. The test matrix also sets fail-fast: false, so a single red combo reports alone instead of cancelling eleven healthy jobs. Round 2 (PR #849, closes #848): the reorder alone proved insufficient — server arrival is produced by the executor worker thread and says nothing about the main thread's position, so interrupt_main()'s pending flag could materialize outside execute()'s protected region on slow runners (an xdist worker crash on two macOS jobs, a completed run with exit_code=None on ubuntu — 3/12 jobs at one head). The interrupter now also polls sys._current_frames() until the main thread is verifiably parked in host_async_await → Future.result — the interruptible wait the #595-class machinery intercepts — before firing, with a bounded deadline so the test can never hang.