diff --git a/.dev/debt.yaml b/.dev/debt.yaml index e83fd6ec2..d8eb5bbe2 100644 --- a/.dev/debt.yaml +++ b/.dev/debt.yaml @@ -3,6 +3,83 @@ # Refresh on every /continue resume — .claude/skills/continue/RESUME.md Step 0.5. entries: + - id: "D-523" + layer: "test" + status: "note" + front: D-wasi03 + description: |- + The committed wasip3 conformance fixtures (`test/component/wasip3/*.wasm`, + 7 rust CLI components) import **wasi 0.2.6 interfaces** (`wasi:cli@0.2.6`, + `wasi:io@0.2.6`, `wasi:clocks/wall-clock@0.2.6`; verified via + `wasm-tools component wit` 2026-07-17). The corpus therefore certifies + the CM-async export ABI + the 0.2 host surface, NOT the official 0.3.0 + WIT (system-clock, via-stream stdio, async sockets). + **Nightly-bump hypothesis REFUTED by measurement (2026-07-17)**: rust + std pins `wasip3 = 0.6.0+wasi-0.3.0-rc-2026-03-15` since ~2026-04-22, + and a regen on nightly-2026-06-24 (which compiles that crate) STILL + emits all-0.2.6 imports + sync `wasi:cli/run@0.2.0` — the imports come + from the BORROWED WASIP2 WASI-LIBC (`-lc` in the link recipe) through + which sync-main stdio/env/clock route, not from the nightly's std + bindings. (Also measured: nightlies 2026-07-08/-16 cannot build-std for + wasip3 at all — upstream `std/sys/env/wasi.rs` `OsStringExt` regression + — so 2026-06-24 is pinned as newest-known-good; recipe re-verified.) + DISCHARGE now requires one of: (a) upstream ships a wasip3-native + wasi-libc/self-contained objects (drop the wasip2 borrow, regen); or + (b) switch the fixture path to wit-bindgen-generated 0.3.0 bindings + (official wasi-testsuite path ①), heavier toolchain. TRIGGER: rust + wasip3 target maturing past Tier-3 / wasi-libc wasip3 release / first + real 0.3.0-built consumer. + first_raised: "2026-07-17" + last_reviewed: "2026-07-17" + refs: |- + proposal_watch 2026-07-17 entry; D-335 (P3 front); D-448 (wasip3 + toolchain recipe). Test-side half of the official-0.3.0 alignment + (host-side half = D-524). + - id: "D-524" + layer: "code" + status: "note" + front: D-wasi03 + description: |- + Official WASI 0.3.0 host-surface residual (host-side half of the + official-0.3.0 alignment; spec = `~/Documents/OSS/WASI/proposals/*/wit/`): + (1) `wasi:clocks/monotonic-clock@0.3.0` `wait-until: async func(when)` / + `wait-for: async func(how-long)` — ASYNC host funcs; need a host-side + timer waitable wired into the P3 scheduler (driveScheduler), not a + sync trampoline; (2) `wasi:filesystem` via-stream data-plane + + `wasi:sockets` async rework + `wasi:http` — already scoped FUTURE-BUCKET + under D-335 REMAINING(2), listed here only for the 0.3.0 mapping; + (3) interface-version gating: the adapter matches names with `@version` + stripped, so 0.2-vs-0.3 signature drift on a SAME-NAMED interface + (none today — system-clock/instant took a new name) would need a + version-aware dispatch row before any such interface is served. + Sync 0.3.0 clocks surface (`system-clock.now/get-resolution` + + `monotonic-clock.get-resolution`) SHIPPED in this sweep — see refs. + first_raised: "2026-07-17" + last_reviewed: "2026-07-17" + refs: |- + proposal_watch 2026-07-17 entry; D-335 REMAINING(2); D-523 (test-side) + - id: "D-525" + layer: "build" + status: "note" + description: |- + The `-Dgc` build option is INERT: `build.zig` threads it to + `build_options.enable_gc`, whose only reader is the + `src/feature/gc/register.zig` re-export that nothing consumes — + GC compiles in (and executes) whenever `-Dwasm=v3_0` (the default); + the actual strip lever is the wasm-level DCE (`check_build_dce.sh` + asserts `gc_struct`/`i31_new` absent from v1_0/v2_0 builds only). + ADR-0115 §3 planned `enable_gc` as the op-handler compile-time gate + ("WAMR-equivalent nuclear strip") but the op handlers never adopted + it. DISCHARGE (pick one): (a) wire the gate — branch the wasm_3_0 + GC per-op registrations/handlers on `enable_gc` + extend + check_build_dce with a `-Dgc=false` leg (binary-size relevant, + cf. ADR-0204); or (b) retire the flag + the register.zig re-export + and amend ADR-0115 §3. Docs corrected 2026-07-17 (migration guide + no longer claims "GC opt-in via -Dgc"). + first_raised: "2026-07-17" + last_reviewed: "2026-07-17" + refs: |- + ADR-0115 §3; scripts/check_build_dce.sh; migration_v1_to_v2.md §3.2 - id: "D-522" layer: "api" status: "note" diff --git a/.dev/handover.md b/.dev/handover.md index b1f5f042c..a5157c794 100644 --- a/.dev/handover.md +++ b/.dev/handover.md @@ -5,14 +5,13 @@ ## Current state — MAINTENANCE MODE (post-v2.0.0) -**v2.2.1 is Latest** (tag `v2.2.1` @62b13a6c9, 2026-07-16 — binary-size -campaign ADR-0204: CLI −21% ReleaseSafe via D-522 stage 1; release.yml -auto-built Release + 5 assets; tag USER-GRANTED in-session per ADR-0156. -v2.2.0 = the 2026-07-09 AOT-full-fidelity line). v1 frozen at `v1.11.1`. Dev model: cut +**v2.3.0 is the release line** (tag cut 2026-07-17, USER-GRANTED in-session +per ADR-0156 — WASI-0.3.0-official sweep + system-clock + Homebrew tap +`brew install clojurewasm/tap/zwasm`; v2.2.1 = 2026-07-16 binary-size line, +v2.2.0 = 2026-07-09 AOT line). v1 frozen at `v1.11.1`. Dev model: cut a `develop/` branch from `main` → PR → CI `ci-required` 3-OS gate must be green to merge. **Release stays user-only (ADR-0156)** — never autonomously tag / -publish / cut over (v2.2.1 tag was user-granted 2026-07-16 in-session). No -active campaign; no cron self-re-arm. +publish / cut over. No active campaign; no cron self-re-arm. ## Binary-size campaign — CLOSED 2026-07-16 (ADR-0204 Implemented, v2.2.1) @@ -35,18 +34,18 @@ DA-critique 20/20) · close #146 (refutation record + v2.2.1). Key outcomes: ## AOT-full-fidelity campaign — CLOSED 2026-07-09 (ADR-0203 Implemented) -Kickoff #136 (phases I–III) · stage 1 #137 (36 helper bakes → `[rt+off]` -slots, D-516) · stage 2 #138 (format v0.5 + `load_compiled.zig` -deserializer, D-519) · stage 3 #139 (run-path swap, mini-runtime DELETED, -§4.5.4 start-func JIT bug fixed, D-517+D-518, D-520 CI hole) · stage 4 -#140 (elision serialization D-515(1)) · stage 5 #141 (`--cache` D-508; -DA-critique failure-path fixes: HIT header-gate + self-heal, refusal = -BYPASS, interp bypass) · stage V #142 (retro: bench parity record, docs, -`.cwasm --engine interp` loud refusal, lesson -failure-path-tests-certified-the-defect). Net: `zig build test-aot-diff` -cross-process differential 63/63 incl. cache lanes. **Residual = -D-515(2)** (spec-assert corpus under elision; harness memory -provisioning) + D-514 (SIMD elision symmetry) — both in debt.yaml. +PRs #136-#142: format v0.5 full-fidelity, run-path swap (mini-runtime +DELETED), elision serialization, `--cache` (D-508). `zig build +test-aot-diff` 63/63 incl. cache lanes. Residual = D-515(2) + D-514 +(debt.yaml). Details: ADR-0203 + CHANGELOG 2.2.0. + +## WASI-0.3.0-official sweep — 2026-07-17 (branch develop/wasm30-wasi03-inventory-sweep) + +**WASI 0.3.0 released 2026-06-11** (spec at `~/Documents/OSS/WASI/`, clones +pulled). Docs truth-sweep (README 0.3 row, --help/canon.zig/3.0-runner lies, +`-Dgc` is INERT → D-525) + `system-clock`/`get-resolution` host support +(instant{s64,u32}, DA check #12). Fixtures import 0.2.6 → D-523; async +wait-until/wait-for → D-524. Full diff = proposal_watch 2026-07-17 entry. ## Active front — G-senior-gap (2026-07-06, /continue entry point) diff --git a/.dev/proposal_watch.md b/.dev/proposal_watch.md index e777fd44e..58959be7c 100644 --- a/.dev/proposal_watch.md +++ b/.dev/proposal_watch.md @@ -5,7 +5,7 @@ > re-evaluated when they advance. Phase 4 non-web proposals are the > v0.2.0 line. -Last reviewed: **2026-07-03**. +Last reviewed: **2026-07-17**. > **WASI 0.3.0 RATIFIED 2026-06-11** (Bytecode Alliance; Wasmtime 43+). It > rebases WASI onto the **Component Model async primitives** (`async` func, @@ -83,10 +83,28 @@ Text Encoding Builtins` (skip). | WASI version | zwasm phase | Notes | |----------------|---------------|-----------------------------------------| -| 0.1 (preview1) | Phases 4 / 11 | de-facto baseline; complete in Phase 11 | -| 0.2 (preview2) | **Phase 17 (ACTIVE)** | Component Model required; full campaign per ADR-0170 (`component_model_plan.md`) | -| 0.3 | post-v0.1.0 (post-v0.2.0) | **released 2026-06-11**; rebases WASI on CM async — streams/futures replace 0.2 poll/`pollable`; breaking vs 0.2; impl gated on CM-async + stack-switching (D-300, DEFER) | - +| 0.1 (preview1) | ✅ COMPLETE | de-facto baseline; complete since Phase 11 | +| 0.2 (preview2) | ✅ COMPLETE (default-ON) | Component Model campaign done 2026-06-13 (ADR-0170); official corpus 158/0/0 | +| 0.3 | 🚧 core SHIPPED (opt-in `-Dwasi=p3`) | **released 2026-06-11**; rebases WASI on CM async (async func / `stream` / `future` — NOT core stack-switching, see callout). zwasm ships the CM-async substrate + cli/clocks/random host; official-0.3.0 interface deltas tracked in the 2026-07-17 entry below | + +- **2026-07-17** — **WASI-0.3.0-official diff inventory** (reference clones + pulled: WASI monorepo → 2026-07-15 HEAD incl. the `v0.3.0` release of + 2026-06-11; wasmtime v48; wasm-tools v1.252; the per-interface + `WebAssembly/wasi-*` repos were ARCHIVED upstream 2025-11-25 — living WIT = + `WASI/proposals/*/wit/`). Deltas between the official 0.3.0 WIT and zwasm's + draft-era P3 surface: (1) `wasi:clocks/wall-clock` was RENAMED + `system-clock`, `datetime{u64,u32}` → `instant{seconds: s64, ns: u32}`, + + `get-resolution`; (2) `wasi:clocks/monotonic-clock` gains `get-resolution` + + `wait-until`/`wait-for` **async funcs**; (3) `wasi:cli` stdio via-stream + shapes match zwasm's ADR-0190 impl (stdout/stderr `write-via-stream(stream) + -> future>`); (4) `wasi:io` is deleted upstream (CM + builtins replace it) — zwasm's P2 wasi:io host stays for 0.2 guests. ALSO + measured: the committed `test/component/wasip3/*.wasm` fixtures import + **wasi 0.2.6 interfaces** — root cause is the borrowed wasip2 wasi-libc in + the link recipe, NOT the nightly pin (nightly-bump hypothesis refuted by a + regen on nightly-2026-06-24, which compiles std's `wasip3 0.6.0 + +wasi-0.3.0-rc` crate yet still emits all-0.2.6; details in D-523). + Host-side `system-clock` support added this sweep. - **2026-07-03** — **post-v2.0.0 maintenance sweep** (reference clones refreshed ff-only: wasmtime / WAMR / wasm-tools / component-model to upstream HEAD; spec + testsuite left at their `wg-3.0` / `spec_pin.yaml` pins). **No proposal phase diff --git a/.dev/reference_clones.md b/.dev/reference_clones.md index 8bcf7195e..4b415a8c2 100644 --- a/.dev/reference_clones.md +++ b/.dev/reference_clones.md @@ -14,8 +14,8 @@ material, not project state. | Path | What it is | |--------------------------------------------------|------------------------------------------------------------------------| -| `~/Documents/MyProducts/zwasm/` | zwasm v1 (current main, ClojureWasm consumer) — **read, never copy** | -| `~/Documents/MyProducts/ClojureWasmFromScratch/` | cw v1 — procedural template that this project mirrors | +| `~/Documents/MyProducts/zwasm/` | THIS repo (v2 on `main`; v1 frozen at tag `v1.11.1` in the same history — **read v1, never copy**) | +| `~/Documents/MyProducts/ClojureWasm/` | cljw — downstream consumer (pins zwasm by git tag-hash) + procedural template this project mirrored | | `~/Documents/OSS/wasmtime/` | wasmtime + cranelift (winch / regalloc2 reference) | | `~/Documents/OSS/zware/` | Zig idiomatic interpreter | | `~/Documents/OSS/wasm3/` | wasm3 (M3 IR + tail-call dispatch interpreter) | @@ -34,36 +34,31 @@ material, not project state. | `~/Documents/OSS/poop/` | Andrew Kelley's perf-bench tool (Zig) | | `~/Documents/OSS/hyperfine/` | Hyperfine source (bench tool used in `bench/`) | | `~/Documents/OSS/extism/` | Extism (multi-language Wasm host SDK reference) | -| `~/Documents/OSS/WebAssembly/spec/` | reference interpreter (OCaml) + spec text — see version pin note below | +| `~/Documents/OSS/WebAssembly/spec/` | reference interpreter (OCaml) + spec text — checked out at `wg-3.0`; see version pin note below | | `~/Documents/OSS/WebAssembly/testsuite/` | spec testsuite — see version pin note below | | `~/Documents/OSS/WebAssembly//` | per-proposal spec + tests (multi-value, simd, gc, eh, ...) | +| `~/Documents/OSS/WASI/` | WASI spec monorepo — `specifications/wasi-0.3.0/` (released 2026-06-11) + live `proposals/*/wit/` WIT sources. NOTE: the per-interface `WebAssembly/wasi-*` repos below were ARCHIVED 2025-11-25; this monorepo is the living source | +| `~/Documents/OSS/WebAssembly/wasi-/` | archived (2025-11-25) per-interface WASI repos (cli/clocks/filesystem/http/io/random/sockets) — historical WIT incl. `wit-0.3.0-draft/`; prefer `WASI/proposals/` for current WIT | +| `~/Documents/OSS/WebAssembly/component-model/` | Component Model spec (canonical ABI, async, WIT) — WASI 0.2/0.3 substrate | +| `~/Documents/OSS/wasi-testsuite/` | WASI testsuite (P1 + p3 guest tests) | +| `~/Documents/OSS/wasmtime-py/` | wasmtime Python embedding (host-API shape reference) | | `~/Documents/OSS/zig/` | Zig 0.16 stdlib source | -## Version pin policy (Wasm 2.0 scope per ADR-0061) +## Version pin policy (Wasm 3.0 scope; historical 2.0 policy = ADR-0061) -Phase 9 keeps strictly to Wasm 2.0 (W3C Rec 2024-12). Upstream -`WebAssembly/spec` and `WebAssembly/testsuite` track `main`, -which has accumulated Wasm 3.0 amendments (typed-refs, -extended-const, multi-memory, tail-call) in historically-2.0 -wast files (`local_tee.wast`, `func.wast`, etc.). Behaviour: +The authoritative pin is [`.dev/spec_pin.yaml`](spec_pin.yaml) — +`WebAssembly/spec` + `WebAssembly/testsuite` SHAs acknowledged as the +vendoring baseline for the committed `test/spec/` conformance corpora. +`scripts/check_spec_bump.sh` alerts when upstream advances beyond the +pin; drift is assessed at the quarterly `proposal_watch.md` review +(benign drift → pin bump, `test/core/` changes → re-vendor + re-distil). -- The regen scripts (`scripts/regen_spec_2_0_assert.sh`, - `scripts/regen_spec_simd_assert.sh`) invoke `wast2json` - **without `--enable-*` flags for 3.0 proposals**. wast files - that have been amended with 3.0 syntax fail parsing and the - regen script logs `skip $n (wast2json rejected)`. -- Existing `test/spec/wasm-2.0-assert/$n/` directories - generated by prior regens (under the pre-d-31 flag policy) - are **preserved on rejection** — they contain only 2.0 - content (the prior flags accepted both 2.0 and 3.0 syntax, - and the python distillation filters to scalar shapes). -- Recommended pin (not auto-enforced): check out the spec - repo at the `wg-2.0` tag for full reproducibility of - generated corpora. `cd ~/Documents/OSS/WebAssembly/spec && - git checkout wg-2.0`. The current loop does not gate on - this; if a regen run produces drift, see ADR-0061 - Alternative C (vendoring) for the escalation path. -- Wasm 3.0 corpora (`memory64/`, `multi-memory/`, `gc/`, - `relaxed-simd/`, `exceptions/`, `function-references/`, - `tail-call/`, etc.) are **never added to NAMES** until - Phase 10+ lands the corresponding runtime. +- The local `WebAssembly/spec` clone sits on the `wg-3.0` branch + (Wasm 3.0, W3C Rec 2025-09) — the scope zwasm v2 ships. +- Wasm 3.0 corpora (`test/spec/wasm-3.0-assert/{memory64,multi-memory, + gc,function-references,tail-call,exception-handling}/`) are committed + and wired into `test-all`; the historical Phase-9 rule "never add 3.0 + corpora until the runtime lands" (ADR-0061) is fulfilled and retired. +- `git pull` on these clones is fine (they are reference material); + regen/re-distil of committed corpora happens only via the + `scripts/regen_spec_*.sh` flow against the pinned SHAs. diff --git a/CHANGELOG.md b/CHANGELOG.md index 74562158a..d6e8fdf54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,55 @@ SemVer compatibility guarantees start at the first stable `v2.0.0` tag. _No changes yet._ +## [2.3.0] - 2026-07-17 + +Inventory sweep against the officially released **WASI 0.3.0** +(2026-06-11): the first slice of the official interface set, plus a +docs truth-sweep and Homebrew packaging. + +### Added + +- **Official WASI 0.3.0 clocks surface** — `wasi:clocks/system-clock` + (0.3.0's renamed `wall-clock`; `instant{seconds: s64, nanoseconds: + u32}`, pre-1970 instants representable with the floored split) and + `get-resolution` on both clocks, served on the component host. The + async `wait-until` / `wait-for` need scheduler-wired timer waitables + and are tracked as debt (D-524). +- **Homebrew**: `brew install clojurewasm/tap/zwasm` (macOS arm64, + Linux x86_64 / aarch64), packaging the release binaries. + +### Changed + +- **wasip3 fixture toolchain repinned** nightly-2026-06-14 → + nightly-2026-06-24 (the newest nightly that can still `-Z build-std` + wasm32-wasip3 — 2026-07-08/-16 hit an upstream std regression) and + the conformance fixtures regenerated. Measured: the emitted imports + are unchanged (wasi 0.2.6) because they come from the borrowed + wasip2 wasi-libc, not the nightly's std bindings — the official-WIT + fixture gap remains open as D-523. + +### Fixed + +- **`zwasm --help` engine wording**: the usage text claimed + `interp (default)`; the real default (since 2.0.0) is `auto` — prefers + the JIT with transparent interpreter fallback. The help now matches + `docs/reference/cli.md` and the README, and also lists the `--cache` / + `--cache-clear` flags shipped in 2.2.0. + +### Documentation + +- **WASI 0.3 documented**: README gains a WASI 0.3 row (Component-Model + native-async core, opt-in `-Dwasi=p3`; official WASI 0.3.0 released + 2026-06-11 — `wasi:filesystem`/`wasi:sockets` data-plane and `wasi:http` + pending), and the migration guide's "until it settles" framing is + replaced accordingly. +- **GC build default corrected in the migration guide**: WasmGC is + compiled in and executes by default (part of `-Dwasm=v3_0`); the guide + previously described it as opt-in via `-Dgc`, which is inert (tracked + as debt). +- **README gains an Install section** (Homebrew + prebuilt release + binaries). + ## [2.2.1] - 2026-07-16 Binary-size campaign (ADR-0204, PRs #144-#146), triggered by downstream @@ -139,7 +188,7 @@ Windows x86_64). Earlier pre-releases were tagged `v2.0.0-alpha.*`. - **WebAssembly 3.0** — all 9 proposals: GC, exception handling, tail calls, memory64, multi-memory, typed function references, - extended-const, relaxed-SIMD. Plus full Wasm 1.0 + 2.0 (multi-value, + extended-const, relaxed-SIMD, custom annotations. Plus full Wasm 1.0 + 2.0 (multi-value, SIMD-128, bulk-memory, reference-types, non-trapping FP→int conversion, sign-extension, mutable globals). Spec testsuite green, `skip-impl == 0`. - **Execution backends** — interpreter (full WASI), JIT for ARM64 diff --git a/README.md b/README.md index ee6a43ec9..536b8e070 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ now (demand-driven). |-----------------------------------------------------------------------------------------------------------------|---------|----------------------------------------------------------| | Wasm 1.0 | ✅ 100% | spec testsuite green on the 3-host gate | | Wasm 2.0 (multi-value, SIMD-128, bulk-memory, reference-types, non-trapping FP→int, sign-ext, mutable globals) | ✅ 100% | `skip-impl == 0`; bit-identical across hosts | -| Wasm 3.0 (GC, EH, tail-call, memory64, multi-memory, typed func refs, extended-const, relaxed-simd) | ✅ 100% | all 9 proposals; spec testsuite green on the 3-host gate | +| Wasm 3.0 (GC, EH, tail-call, memory64, multi-memory, typed func refs, extended-const, relaxed-simd, custom annotations) | ✅ 100% | all 9 proposals; spec testsuite green on the 3-host gate | ### WASI @@ -52,6 +52,7 @@ now (demand-driven). |--------------------------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | WASI 0.1 (preview1) | ✅ functional | interpreter: args / env / preopened dirs / clock / random / fd I/O | | WASI 0.2 (preview2, Component Model) | ✅ functional, default-ON | wasmtime-equivalent campaign complete (2026-06-13): real `wasm32-wasip2` Rust/TinyGo components run e2e (fs, sockets incl. TCP listeners, guest-defined resources); typed embedder API (introspection + `invokeTyped`); validation rules 1-12, official corpus 158/0/0; gated by `-Dwasi>=p2` (default), `-Dwasi=p1` = lean opt-out | +| WASI 0.3 (preview3, native async) | 🚧 core, opt-in (`-Dwasi=p3`) | Component-Model async substrate (async-lifted exports via the stackless callback loop, `stream`/`future`, waitable-sets) + `wasi:cli` / `wasi:clocks` / `wasi:random` host; real `wasm32-wasip3` Rust components run e2e. The official WASI 0.3.0 interface set (released 2026-06-11) is not yet complete: `wasi:filesystem` / `wasi:sockets` data-plane and `wasi:http` are pending | All three execution paths do full WASI I/O — the interpreter, the JIT (`--engine jit`), and AOT (`.cwasm`). The JIT additionally @@ -161,6 +162,27 @@ FFI-capable language, not just C. -Dstrip=true|false # default false ``` +## Install + +**Homebrew** (macOS arm64 / Linux): + +```sh +brew install clojurewasm/tap/zwasm +``` + +The `zwasm` binary is not code-signed. Homebrew installs it without a +Gatekeeper prompt on most setups; if macOS still blocks it as coming from an +unidentified developer, clear the quarantine flag once: + +```sh +xattr -d com.apple.quarantine "$(which zwasm)" +``` + +Or grab a prebuilt binary straight from the +[Releases](https://github.com/clojurewasm/zwasm/releases) page (macOS arm64, +Linux x86_64/aarch64, Windows x86_64). Building from source is in +[Quick start](#quick-start) below. + ## Quick start ```sh diff --git a/build.zig.zon b/build.zig.zon index e2ae61a09..d2620572f 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .zwasm, - .version = "2.2.1", + .version = "2.3.0", .fingerprint = 0xfdac0191bf453d15, .minimum_zig_version = "0.16.0", .dependencies = .{ diff --git a/docs/migration_v1_to_v2.md b/docs/migration_v1_to_v2.md index ed77c0659..57cab25bb 100644 --- a/docs/migration_v1_to_v2.md +++ b/docs/migration_v1_to_v2.md @@ -2,7 +2,8 @@ zwasm **v2** is a ground-up rewrite (releases `v1.0.0`–`v1.11.1` are v1, now frozen; v2 ships as `v2.x.x` tags, first stable `v2.0.0`). -It keeps full Wasm spec coverage (Wasm 1.0/2.0/3.0, WASI 0.1/0.2) but **breaks +It keeps full Wasm spec coverage (Wasm 1.0/2.0/3.0, WASI 0.1/0.2, plus an +opt-in WASI 0.3 async core) but **breaks the surfaces on purpose** — the C API, the Zig API, and the CLI all changed. This guide tells you what to do to port, then documents what changed and why. @@ -245,11 +246,11 @@ all three surfaces — Zig API, C `zwasm.h` setters, and the CLI flags. | **Engine model** | one `WasmModule` (load == instantiate) | `Engine` → `Module` → `Instance` (compile once, instantiate many) | | **Default engine** | JIT by default | **`.auto` by default** (prefers JIT, transparent interp fallback); `--engine interp` / `jit` force one | | **SIMD** | interpreter-only (codegen was stubbed) | **JIT-only**; the interpreter does **not** execute SIMD (by design — in an interpreter the dispatch cost dominates the vector work, so it carries no benefit) | -| **GC** | mark-and-sweep, on by default | mark-sweep with conservative native-stack root scan; **opt-in** (`-Dgc`, default off) | +| **GC** | mark-and-sweep, on by default | mark-sweep with conservative native-stack root scan; **on by default** (part of `-Dwasm=v3_0`; stripped from `v1_0`/`v2_0` builds) | | **Atomics (threads opcodes)** | on by default | implemented (validated + lowered + interpreted); broader shared-memory/spawn is a reserved stub | | **Compile to disk** | `--cache` → predecoded-IR `.zwcache` | `compile` → AOT `.cwasm`; `run` auto-detects it | | **Component Model** | decoder, on by default | decoder + canonical ABI + structural validation + WIT; gated by `-Dwasi>=p2` (default `p2`, so **on**); a real `wasm32-wasip2` component runs e2e | -| **Build defaults** | `wat`/`jit`/`simd`/`gc`/`threads`/`component` all on | `-Dwasm=v3_0`, `-Dwasi=p2`, `-Dengine=both`; component **on** (via `-Dwasi>=p2`), `gc` default **off** | +| **Build defaults** | `wat`/`jit`/`simd`/`gc`/`threads`/`component` all on | `-Dwasm=v3_0`, `-Dwasi=p2`, `-Dengine=both`; component **on** (via `-Dwasi>=p2`); GC on (via `-Dwasm=v3_0`) | ### 3.3 WASI @@ -260,8 +261,10 @@ Both ship **WASI 0.1 (preview1)**. In v2: - **WASI 0.2 / preview2** (Component Model) is **default-ON** via the WASI tier `-Dwasi>=p2` (default `p2`; `-Dwasi=p1` for a lean opt-out — the former `-Dcomponent` flag is folded into the version axis) — real `wasm32-wasip2` Rust/Go - components run e2e (corpus 158/0/0). **WASI 0.3 / preview3** (async) compiles at - `-Dwasi=p3` (opt-in; the default `p2` keeps it out until it settles). + components run e2e (corpus 158/0/0). **WASI 0.3 / preview3** (native async; + officially released as WASI 0.3.0 on 2026-06-11) compiles at `-Dwasi=p3` + (opt-in; the default stays `p2` while the 0.3 host interface set — + filesystem/sockets data-plane, http — is completed). - **C-API preopen** is **shipped** (`zwasm_wasi_config_preopen_dir` + `inherit_env`); the CLI `--dir` and Zig facade also cover it. - **Preopen confinement:** guest paths are escape-guarded (absolute and `..` are diff --git a/flake.lock b/flake.lock index e092152fc..b2c496fe2 100644 --- a/flake.lock +++ b/flake.lock @@ -65,11 +65,11 @@ ] }, "locked": { - "lastModified": 1781407245, - "narHash": "sha256-VzJq4MmD0uyNDAceudSe1hHqcQMe9Tau0U4S+5iRGh0=", + "lastModified": 1784265529, + "narHash": "sha256-ohQQiPOngux8ofsrSlloHCMDhRMvocXqJiG8uH45Q5k=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "d5f483210eb016d66102eef22baa128b3b3233fc", + "rev": "068175006cfb69d5b541a140ed93e361488c9e53", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 85b74ca10..92d36829a 100644 --- a/flake.nix +++ b/flake.nix @@ -51,7 +51,10 @@ # rust there). Front-① WASI 0.3 conformance path ② (2026-06-16). # Pinned nightly (not `.latest`) for reproducibility — this exact nightly # is verified to build wasip3 via the recipe in `devShells.gen-wasip3`. - rustNightlyWasip3 = genPkgs.rust-bin.nightly."2026-06-14".minimal.override { + # KNOWN-BROKEN above this pin: nightlies 2026-07-08 / 2026-07-16 fail + # `-Z build-std` for wasip3 (upstream `std/sys/env/wasi.rs` OsStringExt + # regression, verified 2026-07-17; see D-523 before bumping). + rustNightlyWasip3 = genPkgs.rust-bin.nightly."2026-06-24".minimal.override { extensions = [ "rust-src" ]; }; in { diff --git a/src/api/component_tests.zig b/src/api/component_tests.zig index 483176231..7cad3dab2 100644 --- a/src/api/component_tests.zig +++ b/src/api/component_tests.zig @@ -817,6 +817,26 @@ test "D3: a WASI-P2 component reads wall-clock.now() — realtime past 2017 → try testing.expectEqual(@as(u32, 0), host.exit_code.?); } +test "official 0.3.0: a component reads system-clock.now() + both get-resolution — sane → exit 0" { + var threaded: std.Io.Threaded = .init(testing.allocator, .{}); + defer threaded.deinit(); + const io = threaded.io(); + const bytes = try std.Io.Dir.cwd().readFileAlloc(io, "test/component/wasi_p3_systemclock.wasm", testing.allocator, .limited(1 << 20)); + defer testing.allocator.free(bytes); + + var eng = try Engine.init(testing.allocator, .{}); + defer eng.deinit(); + var host = try wasi_host.Host.init(testing.allocator); + defer host.deinit(); + host.io = io; + + // run() reads system-clock.now() (official WASI 0.3.0: the renamed + // wall-clock, instant{seconds: s64, nanoseconds: u32} at retptr) and both + // clocks' get-resolution(); exit(0) iff seconds>1.5e9 and 0 < res <= 1s. + try runWasiP2Main(&eng, testing.allocator, bytes, &host, .{}); + try testing.expectEqual(@as(u32, 0), host.exit_code.?); +} + test "D3: a WASI-P2 component calls random.get-random-bytes(16) — list realloc → exit 0" { var threaded: std.Io.Threaded = .init(testing.allocator, .{}); defer threaded.deinit(); diff --git a/src/api/component_wasi_p2.zig b/src/api/component_wasi_p2.zig index ae58fa4ba..be1fe2529 100644 --- a/src/api/component_wasi_p2.zig +++ b/src/api/component_wasi_p2.zig @@ -303,6 +303,66 @@ fn p2WallNow(caller: *Caller, retptr: u32) WasiP2Error!void { try mem.write(retptr + 8, @as(u32, @intCast(ns % std.time.ns_per_s))); } +/// `wasi:clocks/system-clock` `now()` → instant{seconds: s64, nanoseconds: u32} +/// (official WASI 0.3.0 — the renamed 0.2 `wall-clock`, reading the same host +/// realtime clock; the record's seconds field became SIGNED). Writes the +/// 12-byte record to the return area at `retptr` (seconds @ 0, ns @ 8). +fn p2SystemNow(caller: *Caller, retptr: u32) WasiP2Error!void { + const ctx = caller.data(WasiP2Ctx); + const mem = try ctxMemory(caller); + const ns = wasi_clocks.clockTimeNsSigned(ctx.host, 0) catch + return WasiP2Error.NoHostIo; // precondition: the component-run path plants host.io + const inst = instantFromNs(ns); + try mem.write(retptr, inst.seconds); + try mem.write(retptr + 8, inst.nanoseconds); +} + +/// Split signed epoch-nanoseconds into the WIT `instant` encoding: FLOORED +/// seconds + a non-negative sub-second remainder (the spec's example — 1 ns +/// before the epoch — is `{-1 seconds, 999_999_999 nanoseconds}`). Seconds +/// beyond the i64 domain clamp (mirrors `clockTimeNs`'s u64 clamp). +fn instantFromNs(ns: i96) struct { seconds: i64, nanoseconds: u32 } { + const secs = @divFloor(ns, std.time.ns_per_s); + const min_s: i96 = std.math.minInt(i64); + const max_s: i96 = std.math.maxInt(i64); + return .{ + .seconds = @intCast(std.math.clamp(secs, min_s, max_s)), + .nanoseconds = @intCast(@mod(ns, std.time.ns_per_s)), + }; +} + +test "instantFromNs: floored split incl. the WIT pre-epoch example" { + // The system-clock.wit example: 1 ns before the epoch. + try std.testing.expectEqual(@as(i64, -1), instantFromNs(-1).seconds); + try std.testing.expectEqual(@as(u32, 999_999_999), instantFromNs(-1).nanoseconds); + // Positive path + exact-second boundaries. + try std.testing.expectEqual(@as(i64, 1), instantFromNs(1_500_000_000).seconds); + try std.testing.expectEqual(@as(u32, 500_000_000), instantFromNs(1_500_000_000).nanoseconds); + try std.testing.expectEqual(@as(i64, -2), instantFromNs(-2_000_000_000).seconds); + try std.testing.expectEqual(@as(u32, 0), instantFromNs(-2_000_000_000).nanoseconds); +} + +/// `wasi:clocks/{system,monotonic}-clock` `get-resolution()` → duration(u64) +/// (official WASI 0.3.0): the host clock granularity in ns, returned directly +/// as the lowered `i64`. WIT declares it infallible, so a host that cannot +/// report a resolution surfaces a loud error (never a fabricated value). +fn clockGetResolution(caller: *Caller, clock_id: u32) WasiP2Error!i64 { + const ctx = caller.data(WasiP2Ctx); + const ns = wasi_clocks.clockResNs(ctx.host, clock_id) catch |err| switch (err) { + error.Inval => unreachable, // clock id is hardcoded 0/1 at the call sites + error.NoSys, error.NotSup, error.Io => return WasiP2Error.NoHostIo, + }; + return @bitCast(ns); +} + +fn p2SystemGetResolution(caller: *Caller) WasiP2Error!i64 { + return clockGetResolution(caller, 0); +} + +fn p2MonotonicGetResolution(caller: *Caller) WasiP2Error!i64 { + return clockGetResolution(caller, 1); +} + /// `wasi:cli/stdin` `get-stdin` → mint an input-stream handle bound to fd 0. fn p2GetStdin(caller: *Caller) WasiP2Error!u32 { const ctx = caller.data(WasiP2Ctx); @@ -1448,6 +1508,9 @@ fn defineClassifiedFunc(lk: *Linker, module: []const u8, name: []const u8, op: a .cli_exit => try lk.defineFuncCtx(module, name, ctx, fn (*Caller, u32) WasiP2Error!void, p2Exit), .clocks_monotonic_now => try lk.defineFuncCtx(module, name, ctx, fn (*Caller) WasiP2Error!i64, p2MonotonicNow), .clocks_wall_now => try lk.defineFuncCtx(module, name, ctx, fn (*Caller, u32) WasiP2Error!void, p2WallNow), + .clocks_system_now => try lk.defineFuncCtx(module, name, ctx, fn (*Caller, u32) WasiP2Error!void, p2SystemNow), + .clocks_system_get_resolution => try lk.defineFuncCtx(module, name, ctx, fn (*Caller) WasiP2Error!i64, p2SystemGetResolution), + .clocks_monotonic_get_resolution => try lk.defineFuncCtx(module, name, ctx, fn (*Caller) WasiP2Error!i64, p2MonotonicGetResolution), // insecure shares the secure handler: identical signature, and the host's // secure fill over-satisfies the insecure contract (no separate RNG state). .random_get_bytes, .random_insecure_get_bytes => try lk.defineFuncCtx(module, name, ctx, fn (*Caller, u64, u32) WasiP2Error!void, p2RandomGetBytes), diff --git a/src/cli/dispatch.zig b/src/cli/dispatch.zig index 3c4f1ed8b..3eda90b32 100644 --- a/src/cli/dispatch.zig +++ b/src/cli/dispatch.zig @@ -47,7 +47,7 @@ pub const usage = \\Usage: \\ zwasm run [args...] Run a module (WASI _start / main) \\ [--invoke [=a,b,...]] Invoke a named export (optional call args) - \\ [--engine ] Engine: interp (default) or jit (both full WASI; jit adds SIMD) + \\ [--engine ] Engine: default auto (prefers JIT, interp fallback); interp|jit force one (both full WASI; jit adds SIMD) \\ [--dir [:]] Preopen a host directory for WASI \\ [--env ] Set an environment variable for the guest \\ [--fuel ] Trap after a fuel budget (units are engine-specific: @@ -55,6 +55,8 @@ pub const usage = \\ [--timeout ] Interrupt the guest after a wall-clock deadline \\ [--max-memory ] Refuse memory.grow past this many bytes (64 KiB page granularity) \\ [--max-table-elements ] Refuse table growth/alloc past this many elements + \\ [--cache[=DIR]] Transparent compilation cache (content-keyed .cwasm reuse) + \\ [--cache-clear] Delete this build's cache subdirectory (combine with --cache to repopulate) \\ zwasm compile -o Compile to a .cwasm AOT artifact \\ zwasm --version | -V Print the version \\ zwasm --help | -h | help Print this help diff --git a/src/cli/main.zig b/src/cli/main.zig index fd53e37b2..feb70f925 100644 --- a/src/cli/main.zig +++ b/src/cli/main.zig @@ -16,15 +16,16 @@ //! export; exit with the guest's //! `proc_exit` code. //! run --invoke Invoke the named func export (zero-args) -//! run --engine Engine: interp (default) or jit — BOTH do full +//! run --engine Engine: default (omitted) = auto, prefers JIT +//! with interp fallback; interp|jit force one — BOTH do full //! WASI (D-244); jit additionally executes SIMD //! (the interp does not). `--engine=jit` accepted. //! instead of the default `_start` / `main` //! selection. Typed `--invoke NAME=a,b,...` //! arg marshalling + result printing are //! handled in `cli/invoke_args.zig` (D-273). -//! compile Produce a `.cwasm` v0.1 artifact (per -//! -o ADR-0039). Generator side; `run +//! compile Produce a `.cwasm` v0.5 full-fidelity artifact +//! -o (per ADR-0203). Generator side; `run //! ` loads + executes it. //! //! The surface is `run` + `compile` only (ADR-0159, §16.4): the diff --git a/src/feature/component/canon.zig b/src/feature/component/canon.zig index 9f89896a8..05028969d 100644 --- a/src/feature/component/canon.zig +++ b/src/feature/component/canon.zig @@ -11,7 +11,7 @@ //! over memory · recursive `store`/`load` for list/record/variant · the //! multi-value flat lowering for fn-call params (`flattenType`/`lowerFlat`/ //! `liftFlat`) · the decoded-`TypeInfo`→`CanonType` bridge · resource handle -//! own/borrow (D-322). utf16/latin1 string encodings pending. +//! own/borrow (D-322) · utf16 / latin1+utf16 string encodings (D-502). //! //! The realloc callback is INJECTED (vtable pattern, `zone_deps`): canon.zig //! never imports the core runtime's instance/invoke; the orchestration layer @@ -219,8 +219,8 @@ pub const ReallocError = error{ AllocFailed, OutOfBounds }; /// (ADR-0171). An error result signals OOM / trap. pub const ReallocFn = *const fn (ctx: *anyopaque, old_ptr: u32, old_size: u32, alignment: u32, new_size: u32) ReallocError!u32; -/// Guest string encoding (`canonopt` `string-encoding`). utf8 is implemented; -/// utf16 / latin1+utf16 are still pending (rejected by lift/lower for now). +/// Guest string encoding (`canonopt` `string-encoding`). All three are +/// implemented at the canon lift/lower layer (D-502). pub const StringEncoding = enum { utf8, utf16, latin1_utf16 }; /// Per-call canonical-ABI context: the guest linear memory (lift/lower target), diff --git a/src/feature/gc/register.zig b/src/feature/gc/register.zig index f2f4be683..37f1f76d1 100644 --- a/src/feature/gc/register.zig +++ b/src/feature/gc/register.zig @@ -7,9 +7,10 @@ //! function installs the parser / validator / interp / arm64 / //! x86_64 emit slots for the feature's opcodes. //! -//! Currently a placeholder per ADR-0023 §3 P-H. Implementation -//! lands per ROADMAP §11 (proposal phasing) when the feature's -//! Phase row opens. +//! The GC feature itself SHIPPED (Phase 10): its ops register via the +//! per-op `src/instruction/wasm_3_0/` dispatch files, so `register()` +//! stays a no-op, and the `enable_gc` re-export below is currently +//! unwired — GC compiles in with `-Dwasm=v3_0`; see debt D-525. //! //! Zone 1 (`src/feature/gc/`). diff --git a/src/wasi/adapter.zig b/src/wasi/adapter.zig index 35640188c..5e2bda39d 100644 --- a/src/wasi/adapter.zig +++ b/src/wasi/adapter.zig @@ -118,6 +118,13 @@ pub const P2Op = enum { // wasi:clocks. clocks_wall_now, clocks_monotonic_now, + // wasi:clocks@0.3.0 (official WASI 0.3.0): `wall-clock` was RENAMED + // `system-clock` with `instant{seconds: s64, nanoseconds: u32}`, and both + // clocks gained `get-resolution`. The 0.3.0 async `wait-until`/`wait-for` + // need a scheduler-wired timer waitable, not a sync trampoline (D-524). + clocks_system_now, + clocks_system_get_resolution, + clocks_monotonic_get_resolution, // wasi:random. random_get_bytes, // wasi:filesystem/types — `descriptor` resource methods. A descriptor is a @@ -243,6 +250,8 @@ pub const P1Target = union(enum) { proc_exit, /// P1 `clock_time_get` for the given clock id (0=realtime, 1=monotonic). clock_time_get: u32, + /// P1 `clock_res_get` for the given clock id (0=realtime, 1=monotonic). + clock_res_get: u32, /// P1 `random_get`. random_get, /// `descriptor` (P1 fd) ops — the fd is resolved from the handle rep at @@ -289,6 +298,9 @@ pub fn p1Target(op: P2Op) P1Target { .cli_exit => .proc_exit, .clocks_wall_now => .{ .clock_time_get = 0 }, .clocks_monotonic_now => .{ .clock_time_get = 1 }, + .clocks_system_now => .{ .clock_time_get = 0 }, + .clocks_system_get_resolution => .{ .clock_res_get = 0 }, + .clocks_monotonic_get_resolution => .{ .clock_res_get = 1 }, .random_get_bytes => .random_get, .fs_descriptor_read => .fd_pread, .fs_descriptor_write => .fd_pwrite, @@ -394,6 +406,10 @@ const table = [_]Entry{ .{ .iface = "wasi:io/streams", .func = "[resource-drop]input-stream", .op = .in_stream_drop }, .{ .iface = "wasi:clocks/wall-clock", .func = "now", .op = .clocks_wall_now }, .{ .iface = "wasi:clocks/monotonic-clock", .func = "now", .op = .clocks_monotonic_now }, + // Official WASI 0.3.0 clock surface (renamed/extended vs the 0.2 WIT). + .{ .iface = "wasi:clocks/system-clock", .func = "now", .op = .clocks_system_now }, + .{ .iface = "wasi:clocks/system-clock", .func = "get-resolution", .op = .clocks_system_get_resolution }, + .{ .iface = "wasi:clocks/monotonic-clock", .func = "get-resolution", .op = .clocks_monotonic_get_resolution }, .{ .iface = "wasi:random/random", .func = "get-random-bytes", .op = .random_get_bytes }, .{ .iface = "wasi:filesystem/types", .func = "[method]descriptor.read", .op = .fs_descriptor_read }, .{ .iface = "wasi:filesystem/types", .func = "[method]descriptor.write", .op = .fs_descriptor_write }, @@ -535,6 +551,16 @@ test "p1Target: clocks + random" { try testing.expectEqual(@as(u32, 0), p1Target(.clocks_wall_now).clock_time_get); try testing.expectEqual(@as(u32, 1), p1Target(.clocks_monotonic_now).clock_time_get); try testing.expectEqual(P1Target.random_get, p1Target(.random_get_bytes)); + // Official WASI 0.3.0 clock surface (system-clock = realtime id 0). + try testing.expectEqual(@as(u32, 0), p1Target(.clocks_system_now).clock_time_get); + try testing.expectEqual(@as(u32, 0), p1Target(.clocks_system_get_resolution).clock_res_get); + try testing.expectEqual(@as(u32, 1), p1Target(.clocks_monotonic_get_resolution).clock_res_get); +} + +test "classify: official 0.3.0 system-clock + get-resolution (version-stripped iface names)" { + try testing.expectEqual(P2Op.clocks_system_now, classifyImport("wasi:clocks/system-clock", "now").?); + try testing.expectEqual(P2Op.clocks_system_get_resolution, classifyImport("wasi:clocks/system-clock", "get-resolution").?); + try testing.expectEqual(P2Op.clocks_monotonic_get_resolution, classifyImport("wasi:clocks/monotonic-clock", "get-resolution").?); } test "classify: wasi:io/poll + subscribe methods" { diff --git a/src/wasi/clocks.zig b/src/wasi/clocks.zig index 9e770f6bb..f86d72217 100644 --- a/src/wasi/clocks.zig +++ b/src/wasi/clocks.zig @@ -77,6 +77,16 @@ pub fn clockTimeGet( /// can return the value directly (its lowered `()->i64`) instead of through /// guest memory. Same clock-id mapping as `clock_time_get`; requires `host.io`. pub fn clockTimeNs(host: *Host, clock_id: u32) error{ NoSys, Inval }!u64 { + const ns_i = try clockTimeNsSigned(host, clock_id); + if (ns_i < 0) return error.Inval; + return @intCast(@min(ns_i, std.math.maxInt(u64))); +} + +/// Read a clock as SIGNED nanoseconds since its epoch. The official WASI +/// 0.3.0 `system-clock` `instant` carries signed seconds (pre-1970 instants +/// are representable), so its trampoline needs the un-clamped value; the +/// P1/0.2 paths keep the unsigned `clockTimeNs` view. +pub fn clockTimeNsSigned(host: *Host, clock_id: u32) error{ NoSys, Inval }!i96 { const io = host.io orelse return error.NoSys; const clock: std.Io.Clock = switch (clock_id) { 0 => .real, @@ -85,10 +95,7 @@ pub fn clockTimeNs(host: *Host, clock_id: u32) error{ NoSys, Inval }!u64 { 3 => .cpu_thread, else => return error.Inval, }; - const ts = std.Io.Timestamp.now(io, clock); - const ns_i = ts.toNanoseconds(); - if (ns_i < 0) return error.Inval; - return @intCast(@min(ns_i, std.math.maxInt(u64))); + return std.Io.Timestamp.now(io, clock).toNanoseconds(); } // ============================================================ @@ -106,22 +113,36 @@ pub fn clockResGet( clock_id: u32, resolution_ptr: u32, ) p1.Errno { - const io = host.io orelse return .nosys; + const ns_u = clockResNs(host, clock_id) catch |err| return switch (err) { + error.NoSys => .nosys, + error.Inval => .inval, + error.NotSup => .notsup, + error.Io => .io, + }; + return writeU64LE(mem, resolution_ptr, ns_u); +} + +/// Read a clock's resolution as a raw nanosecond `u64` — the value +/// `clock_res_get` writes to guest memory. Factored out so the WASI-P2/P3 +/// `get-resolution` trampolines (`wasi:clocks@0.3.0`) can return it directly +/// (their lowered `()->i64`) instead of through guest memory. Same clock-id +/// mapping as `clock_time_get`; requires `host.io`. +pub fn clockResNs(host: *Host, clock_id: u32) error{ NoSys, Inval, NotSup, Io }!u64 { + const io = host.io orelse return error.NoSys; const clock: std.Io.Clock = switch (clock_id) { 0 => .real, 1 => .awake, 2 => .cpu_process, 3 => .cpu_thread, - else => return .inval, + else => return error.Inval, }; const dur = clock.resolution(io) catch |err| switch (err) { - error.ClockUnavailable => return .notsup, - error.Unexpected => return .io, + error.ClockUnavailable => return error.NotSup, + error.Unexpected => return error.Io, }; const ns_i = dur.toNanoseconds(); - if (ns_i < 0) return .inval; - const ns_u: u64 = @intCast(@min(ns_i, std.math.maxInt(u64))); - return writeU64LE(mem, resolution_ptr, ns_u); + if (ns_i < 0) return error.Inval; + return @intCast(@min(ns_i, std.math.maxInt(u64))); } // ============================================================ diff --git a/test/component/wasi_p3_systemclock.wasm b/test/component/wasi_p3_systemclock.wasm new file mode 100644 index 000000000..9f457b870 Binary files /dev/null and b/test/component/wasi_p3_systemclock.wasm differ diff --git a/test/component/wasi_p3_systemclock.wat b/test/component/wasi_p3_systemclock.wat new file mode 100644 index 000000000..5bd6c3b5d --- /dev/null +++ b/test/component/wasi_p3_systemclock.wat @@ -0,0 +1,65 @@ +;; Official WASI 0.3.0 clocks surface: system-clock (the renamed 0.2 wall-clock, +;; instant{seconds: s64, nanoseconds: u32}) + get-resolution on both clocks. +;; exit(0) iff now().seconds is past 2017 (> 1_500_000_000) AND both clock +;; resolutions are sane (0 < res <= 1s), else exit(1). +;; Exercises the clocks_system_now / clocks_system_get_resolution / +;; clocks_monotonic_get_resolution trampolines: now() -> instant lowers to a +;; core (i32 retptr)->() with the 12-byte record written at retptr; +;; get-resolution() -> duration(u64) lowers to ()->i64. +(component + (import "wasi:clocks/system-clock@0.3.0" (instance $sys + (type $instant-def (record (field "seconds" s64) (field "nanoseconds" u32))) + (export "instant" (type $instant (eq $instant-def))) + (export "now" (func (result $instant))) + (export "get-resolution" (func (result u64))))) + (import "wasi:clocks/monotonic-clock@0.3.0" (instance $mono + (export "get-resolution" (func (result u64))))) + (import "wasi:cli/exit@0.3.0" (instance $cli-exit + (export "exit" (func (param "status" (result)))))) + + (core module $libc (memory (export "memory") 1)) + (core instance $libc (instantiate $libc)) + (core func $sys-now (canon lower (func $sys "now") (memory $libc "memory"))) + (core func $sys-res (canon lower (func $sys "get-resolution"))) + (core func $mono-res (canon lower (func $mono "get-resolution"))) + (core func $exit (canon lower (func $cli-exit "exit"))) + + (core module $M + (import "io" "sys-now" (func $sys_now (param i32))) ;; retptr to a 12-byte instant area + (import "io" "sys-res" (func $sys_res (result i64))) + (import "io" "mono-res" (func $mono_res (result i64))) + (import "io" "exit" (func $exit (param i32))) + (import "libc" "memory" (memory 1)) + ;; 0 < res <= 1s (1_000_000_000 ns) — sane host clock granularity. + (func $res_sane (param $r i64) (result i32) + (i32.and + (i64.gt_u (local.get $r) (i64.const 0)) + (i64.le_u (local.get $r) (i64.const 1000000000)))) + (func (export "run") (result i32) + (call $sys_now (i32.const 16)) ;; write instant at offset 16 + (if (i32.and + (i64.gt_s (i64.load (i32.const 16)) (i64.const 1500000000)) ;; seconds @ 16 (s64) + (i32.and + (call $res_sane (call $sys_res)) + (call $res_sane (call $mono_res)))) + (then (call $exit (i32.const 0))) + (else (call $exit (i32.const 1)))) + (i32.const 0))) + + (core instance $deps-io + (export "sys-now" (func $sys-now)) + (export "sys-res" (func $sys-res)) + (export "mono-res" (func $mono-res)) + (export "exit" (func $exit))) + (core instance $m (instantiate $M + (with "io" (instance $deps-io)) + (with "libc" (instance $libc)))) + + (type $run-result (result)) + (func $run (result $run-result) (canon lift (core func $m "run"))) + (component $RunShim + (import "import-func-run" (func $rf (result (result)))) + (export "run" (func $rf))) + (instance $run-inst (instantiate $RunShim (with "import-func-run" (func $run)))) + (export "wasi:cli/run@0.3.0" (instance $run-inst)) +) diff --git a/test/component/wasip3/cli-args.wasm b/test/component/wasip3/cli-args.wasm index 72d65401d..2e5b34d83 100644 Binary files a/test/component/wasip3/cli-args.wasm and b/test/component/wasip3/cli-args.wasm differ diff --git a/test/component/wasip3/cli-clocks.wasm b/test/component/wasip3/cli-clocks.wasm index 3565f88d6..c7c2e8791 100644 Binary files a/test/component/wasip3/cli-clocks.wasm and b/test/component/wasip3/cli-clocks.wasm differ diff --git a/test/component/wasip3/cli-env.wasm b/test/component/wasip3/cli-env.wasm index 87a2ca156..f83a6b03c 100644 Binary files a/test/component/wasip3/cli-env.wasm and b/test/component/wasip3/cli-env.wasm differ diff --git a/test/component/wasip3/cli-exit.wasm b/test/component/wasip3/cli-exit.wasm index c1604afc9..79ab7d97b 100644 Binary files a/test/component/wasip3/cli-exit.wasm and b/test/component/wasip3/cli-exit.wasm differ diff --git a/test/component/wasip3/cli-stderr.wasm b/test/component/wasip3/cli-stderr.wasm index 1849b9740..71e36eab2 100644 Binary files a/test/component/wasip3/cli-stderr.wasm and b/test/component/wasip3/cli-stderr.wasm differ diff --git a/test/component/wasip3/cli-stdin.wasm b/test/component/wasip3/cli-stdin.wasm index 8c91c5583..1d05f0d9a 100644 Binary files a/test/component/wasip3/cli-stdin.wasm and b/test/component/wasip3/cli-stdin.wasm differ diff --git a/test/component/wasip3/cli-stdout.wasm b/test/component/wasip3/cli-stdout.wasm index 092748c41..87bba86a6 100644 Binary files a/test/component/wasip3/cli-stdout.wasm and b/test/component/wasip3/cli-stdout.wasm differ diff --git a/test/spec/spec_assert_runner_wasm_3_0.zig b/test/spec/spec_assert_runner_wasm_3_0.zig index ff4f543df..20de6bbb9 100644 --- a/test/spec/spec_assert_runner_wasm_3_0.zig +++ b/test/spec/spec_assert_runner_wasm_3_0.zig @@ -1,18 +1,16 @@ -//! Wasm 3.0 spec assertion runner skeleton (10.T-2b). +//! Wasm 3.0 spec assertion runner (10.T-2b, JIT-executing since Phase 10). //! -//! Sub-corpus selector for the 5 Wasm 3.0 proposals (memory64 / -//! tail-call / exception-handling / gc / function-references). -//! Currently a SKELETON — enumerates the baked manifests under -//! `///manifest.txt` and reports -//! per-proposal directive counts. JIT-execute + actual assertion -//! matching comes online cycle-by-cycle as impl rows 10.M / 10.R / -//! 10.TC / 10.E / 10.G land (each impl row will adopt the -//! `spec_assert_runner_base` callbacks pattern once its proposal's -//! ZIR / runtime / codegen surface exists). +//! Sub-corpus selector for the Wasm 3.0 proposals (memory64 / multi-memory / +//! tail-call / exception-handling / gc / function-references): enumerates the +//! baked manifests under `///manifest.txt` and +//! executes the assertions — assert_return / assert_trap / assert_invalid / +//! assert_unlinkable / assert_malformed / assert_exception — with real +//! pass/fail accounting, plus an opt-in JIT-return lane +//! (`ZWASM_SPEC_ENGINE=jit`) whose enumerated skips are the D-217 +//! harness-shape gaps, not silent omissions. //! -//! Until then this runner serves as the **observable wiring** — -//! `zig build test-spec-wasm-3.0-assert` builds + runs it, -//! exiting clean against the smoke-baked corpus (10.T-2a). A +//! `zig build test-spec-wasm-3.0-assert` builds + runs it against the +//! committed corpus. A //! missing corpus ROOT `exit(1)`s — the corpus is committed, so a //! missing root is a real error, not a fresh-checkout state //! (ADR-0174 no-silent-skip), matching the other assert runners.