From 154fc811746768341bfa7905fda6ac0071acb2fb Mon Sep 17 00:00:00 2001 From: "Shota Kudo (chaploud)" Date: Thu, 9 Jul 2026 23:20:12 +0900 Subject: [PATCH 1/2] =?UTF-8?q?chore(campaign):=20ADR-0203=20stage=20V=20r?= =?UTF-8?q?etrospective=20=E2=80=94=20docs,=20bench=20record,=20lesson,=20?= =?UTF-8?q?debt=20reconcile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Campaign close-out (stages 1-5 = PRs #137-#141): - ADR-0203 Status -> Implemented (aot-diff 63/63 incl. cache lanes; D-508/515(1)/516/517/518/519 closed). - docs/reference/cli.md: --cache/--cache-clear rows; stale claims fixed (.cwasm now sandboxed + full-runtime, artifact no longer "v0.1", cache/engine interaction); README CLI block gains --cache lines. - Explicit `.cwasm` + `--engine interp` is now a loud exit-2 refusal (contradictory request: the artifact IS JIT code; D-496 spirit) with an aot-diff E2E probe. - bench/results/history.yaml: per-merge record on the feature branch (campaign parity: worst +1%, median -8% vs pre-campaign entry). - Lesson: failure-path-tests-certified-the-defect (DA critique found the stage-5 unit test asserting the D5 violation it should have caught). - handover.md rewritten to the campaign-closing state (118 lines). Remaining campaign residual: D-515(2) spec-assert corpus under elision (harness memory provisioning), tracked in debt.yaml. Claude-Session: https://claude.ai/code/session_01F2PGseEjGFR1uqid4FaXsm --- .dev/decisions/0203_aot_full_fidelity.md | 3 +- .dev/handover.md | 100 ++++------ ...failure-path-tests-certified-the-defect.md | 48 +++++ .dev/lessons/INDEX.md | 1 + README.md | 2 + bench/results/history.yaml | 171 ++++++++++++++++++ docs/reference/cli.md | 24 ++- src/cli/main.zig | 11 ++ test/aot/aot_process_diff.zig | 11 ++ 9 files changed, 300 insertions(+), 71 deletions(-) create mode 100644 .dev/lessons/2026-07-09-failure-path-tests-certified-the-defect.md diff --git a/.dev/decisions/0203_aot_full_fidelity.md b/.dev/decisions/0203_aot_full_fidelity.md index 5470b274e..170fd6ebb 100644 --- a/.dev/decisions/0203_aot_full_fidelity.md +++ b/.dev/decisions/0203_aot_full_fidelity.md @@ -1,6 +1,7 @@ # ADR-0203: AOT full fidelity — PIC helper indirection, format v0.5, full-runtime load, transparent cache -- **Status**: Accepted (campaign design; user-ratified goal 2026-07-09) +- **Status**: Implemented (stages 1–5 merged: PRs #137 #138 #139 #140 #141; + aot-diff 63/63 incl. cache lanes; D-508/515(1)/516/517/518/519 closed) - **Date**: 2026-07-09 - **Front**: AOT-full-fidelity (D-508 / D-515 / D-516 / D-517 / D-518) - **Findings base**: `.dev/meta_audits/2026-07-09-aot-full-fidelity-investigation.md` diff --git a/.dev/handover.md b/.dev/handover.md index 21ceb5139..bb38e6307 100644 --- a/.dev/handover.md +++ b/.dev/handover.md @@ -12,65 +12,38 @@ COMPLETE; the autonomous `/continue` loop is RETIRED. Dev model: cut a green to merge. **Release stays user-only (ADR-0156)** — never autonomously tag / publish / cut over. No active campaign/bundle; no cron self-re-arm. -## Active rework campaign — AOT-full-fidelity (opened 2026-07-09, USER-RATIFIED) - -> **RELEASE DIRECTIVE (user, 2026-07-09)**: campaign stage V 完了後に -> v2.2.0 タグ + リリース (ADR-0156 の明示指示として記録; version bump + -> tag push → release.yml 自動)。headline = 透過キャッシュ + full-fidelity -> AOT + PIC codegen. - -- **Goal (user directive)**: 本当の cwasm/AOT — deploy artifact stays `.wasm`; - `.cwasm` = explicit `zwasm compile` output AND the transparent-cache value. - A `.cwasm` must load back into the **FULL runtime** (cache-hit == cache-miss) - covering ALL module classes; then **D-508** transparent `run --cache` on top. -- **Phases I–III DONE** (PR #136): findings doc = - `.dev/meta_audits/2026-07-09-aot-full-fidelity-investigation.md` (D-516 - baked-address crash bug found by experiment · D-517 mini-runtime gaps · - D-518 start-func skipped · ROI ~110ms/3MB); Phase II net = - `zig build test-aot-diff` (CROSS-PROCESS `.wasm`-vs-`.cwasm` subprocess - diff — in-process lanes can't see ASLR staleness; expectation table with - RATCHET-FLIP gate); design = **ADR-0203** (D1 de-baking · D2 deserialize- - into-CompiledWasm + normal setup · D3 format v0.5 + two-tier gate · - D4 elision serialization · D5 `--cache` · D6 six-stage plan). -- **Stage 1 MERGED #137** (36 helper bakes → `[rt+off]` slots, D-516 - closed, interposition tests, bench parity). **Stage 2 MERGED #138** - (format v0.5: embedded wasm_bytes + func_extras + EH table; deserializer - `load_compiled.zig` — metadata re-derived, code re-linked via the same - linkWithThunks; `JitInstance.fromCompiled`; exit test green; D-519 - closed). Kickoff #136. -- **Stage 3 CODE-COMPLETE (develop/aot-stage3-runpath-swap)** — the CWAS - branch in runWasiLenientArgs routes `.cwasm` through deserialize + the - normal setup; CLI unified (main.zig CWAS refusals for limits/invoke-args - REMOVED — both now wired); mini-runtime RETIRED (aot/run.zig + - load.zig deleted; 8 runner_test round-trips rewritten to the new path); - expectation table EMPTIED — **aot-diff 62/62 ALL MATCH**. Bonus fix: - the lenient JIT path never ran `(start)` (Wasm §4.5.4) — pre-existing - `--engine jit` spec bug the differential caught; start now runs in - runWasiLenientArgsCore. D-517 + D-518 CLOSED; critique #7 18/20 fixed; - PR #139 in CI (GitHub queue congested; D-520 aggregator hole found live - + fixed: ci-required now FAILS when detect-changes fails). -- **Stage 4 CODE-COMPLETE (develop/aot-stage4-elision, stacked)** — - D-515(1): `flag_bounds_elided` header bit; produce refusal + - compileWasmForAot forced-`.explicit` REMOVED (trap re-registration was - already unconditional in the re-link; allocBacking = no plain-heap - fallback). Unit: elided round-trip guard-faults to a clean trap via - RE-REGISTERED entries. **aot-diff 62/62 with ELIDED artifacts as the - default output** · fuzz-diff green · Rosetta green. D-515 row (1) - struck ((2) spec-corpus remains); ADR-0202 note: AOT elision ENABLED. -- Stage 4 critique #8 (17/20) fixed in-commit (non-D1-host reject added: - ElidedArtifactNeedsGuardedHost; oob_guard_boundary added to aot corpus → - **63/63 MATCH**); PR #140 in CI. **Stage 5 CODE-COMPLETE - (develop/aot-stage5-cache, stacked)** — D-508: `zwasm run --cache[=DIR]` - + `--cache-clear`; src/cli/cache.zig (SHA-256 key, versioned dir - zwasm----, temp+atomic-rename, silent-miss - discipline w/ EXEMPT-FALLBACK per ADR-0203 D5); hit routes the artifact - through the CWAS full-fidelity path. **Measured: tinygo_json 9.2ms → - 4.1ms (2.2x; user CPU 7.2→2.2ms = codegen skipped)**. Unit tests - (subdir/key/miss-hit-corrupt-clear) green; D-508 row closed. -- **NEXT**: merge #140 → rebase stage 5 → critique → PR → merge · stage V - retrospective (debt reconcile · ADR-0203 status · bench record · - docs/README --cache mention · lessons) → **v2.2.0 release - (user-directed: version bump + tag → release.yml)**. +## AOT-full-fidelity campaign — stages 1–5 DONE, closing (ADR-0203) + +> **RELEASE DIRECTIVE (user, 2026-07-09)**: campaign V 完了後に v2.2.0 +> タグ + リリース (ADR-0156 の明示指示として記録; `build.zig.zon` version +> bump + tag push → release.yml 自動)。headline = 透過キャッシュ (--cache, +> 2.2x cold start) + full-fidelity AOT (.cwasm v0.5, aot-diff 63/63) + +> PIC codegen (D-516 fix)。 + +- **Goal (user directive) ACHIEVED**: 本当の cwasm/AOT — deploy artifact + stays `.wasm`; `.cwasm` loads into the FULL runtime (cache-hit == + cache-miss by construction); transparent `run --cache[=DIR]` on top. +- **Merged**: kickoff #136 (phases I–III: findings meta_audit · cross-process + `test-aot-diff` net w/ RATCHET-FLIP table · ADR-0203 D1–D6) · stage 1 #137 + (36 helper bakes → `[rt+off]` slots, D-516) · stage 2 #138 (format v0.5: + embedded wasm_bytes/func_extras/EH; `load_compiled.zig` deserializer; + D-519) · stage 3 #139 (run-path swap; mini-runtime DELETED; CWAS refusals + removed; §4.5.4 start-func JIT bug fixed; D-517+D-518; D-520 CI hole fixed) + · stage 4 #140 (elision serialization D-515(1); ElidedArtifactNeedsGuardedHost). +- **Stage 5 PR #141 (in CI)** — D-508 `--cache`: src/cli/cache.zig, SHA-256 + content key, versioned dir `zwasm----`, measured + 2.2x (tinygo_json 9.2→4.1ms). DA critique (14/20) fixes in-PR: HIT + header-gate + self-heal, compile-refusal = BYPASS (never exit 1), interp + bypass, unique temp, trust-model doc; aot-diff 63/63 W/ CACHE LANES. +- **Stage V retro (develop/aot-campaign-v-retro, stacked on #141)**: bench + history recorded (parity, worst +1%) · ADR-0203 → Implemented · docs + (cli.md --cache + stale .cwasm claims fixed; README) · explicit + `.cwasm --engine interp` loud refusal + E2E · lesson + failure-path-tests-certified-the-defect · debt reconciled (68 rows; + D-515(2) spec-corpus-under-elision is the only campaign residual). +- **NEXT**: merge #141 → rebase + PR retro branch → merge → **v2.2.0 + release (user-directed: version bump + tag → release.yml)** → close + campaign section here. ## Active front — G-senior-gap (2026-07-06, /continue entry point) @@ -79,13 +52,12 @@ Report = `.dev/meta_audits/2026-07-06-senior-runtime-gap-analysis.md`. Retrospective: measured scalar-elision perf ≈ NOISE — "biggest tier-free lever" REFUTED; the 1.75–3.9x gap vs wasmtime = optimising-tier codegen (**D-513**, user-gated). Elision kept (correct, code-size, base for D-509 - threads). AOT elision DISABLED pending D-515. Follow-up **D-514** (SIMD - elision symmetry). + threads). AOT elision ENABLED at ADR-0203 stage 4 (D-515(1)). Follow-up + **D-514** (SIMD elision symmetry). - **G3 = D-510 COMPLETE** (#135) — committed `zig build fuzz-diff` gate: memory-snapshot compare + dual JIT lanes (`.auto`/`.explicit`) + regression corpus. 2008-module campaign 0 mismatch. D-515(2) partially covered. -- **G2 = D-508 folded into the AOT-full-fidelity campaign** (above; scope - corrected — see the D-508 row). +- **G2 = D-508 COMPLETE** via the AOT-full-fidelity campaign (above). - Then: D-314(a) epoch-counter · note-class D-509 (threads campaign, own kickoff + ADR) · D-511/D-512 (demand-driven) · **D-513 (user-gated)**. - Older demand-driven tail unchanged: D-444, D-506, D-502 residual, D-475 @@ -131,7 +103,7 @@ Report = `.dev/meta_audits/2026-07-06-senior-runtime-gap-analysis.md`. dogfooded into cljw (pins zwasm by git tag-hash). Runners ReleaseSafe. - **EH**: cross-instance JIT EH both arches. Interp+JIT EH corpus green. Realworld 56 fixtures interp 56/0; JIT diff-gated. -- **Debt**: 69 entries — **0 `now`-class** (D-505 DONE: 3 arm64-SIMD bitmask sites +- **Debt**: 68 entries — **0 `now`-class** (D-505 DONE: 3 arm64-SIMD bitmask sites spill-aware, bitselect/fma SPILL-EXEMPT, spill_aware promoted to CI+gate_commit; follow-on D-506 = FP spill stage-2, note-class). 完成形 plateau (all dims confirmed, surface audits clean, interp+JIT fuzz 0-crash, v1-JIT parity D-265 closed). diff --git a/.dev/lessons/2026-07-09-failure-path-tests-certified-the-defect.md b/.dev/lessons/2026-07-09-failure-path-tests-certified-the-defect.md new file mode 100644 index 000000000..dd3f83953 --- /dev/null +++ b/.dev/lessons/2026-07-09-failure-path-tests-certified-the-defect.md @@ -0,0 +1,48 @@ +# An invariant's failure path needs its own tests — or the tests certify the defect + +**Date**: 2026-07-09 +**Keywords**: ADR-0203 D5, cache silent-miss discipline, failure-path testing, unit test asserts defective behavior, DA critique reproduced corrupt-entry brick, happy-path bias, EXEMPT-FALLBACK comment contradicted module doc, self-heal, degrade-not-fail +**Citing**: 4a17ceda7 + +## What happened + +Stage-5 shipped the `--cache` happy path complete (atomic store, hit==miss +byte-identity, 2.2x measured) with the ADR-0203 D5 invariant "the cache can +never make `run` fail" stated in the module doc. The independent DA critique +then reproduced FOUR ways a cache defect failed the run (three corrupt-entry +shapes, `ZWASM_DEBUG` produce-refusal) plus an `--engine interp` override — +and found the unit test ASSERTING the defective behavior (corrupt entry +returned as a hit, expected to fail downstream) as if it were the contract. + +## Root cause + +D5's silent-miss discipline was implemented only where an error VISIBLY +crossed the cache layer (read failure → `catch` → miss). The invariant's +other half — an entry that reads fine but is garbage, a compile refusal +inside the cache path — had no failing test, so the implementation drifted +into "content is opaque to the cache layer" and a same-file comment that +directly contradicted the module doc. Tests written by the implementer +mirrored the implementation, not the invariant. + +## Fix (or path forward) + +Enumerate an invariant's failure modes and write one test per mode BEFORE +implementing (the TDD red step applies to failure paths too). Fixed in +4a17ceda7: header-gate + self-heal at lookup, compile-refusal = bypass, +interp bypass — each with a unit/E2E test that would have been red. + +## Why this didn't surface earlier + +`zig build test` green measured only the asserted behavior; the assertion +itself encoded the bug. Only an adversarial reviewer re-reading the ADR +clause-by-clause against E2E probes caught it. + +## Re-derivability + +Not re-derivable from code alone — post-fix code looks like the invariant +was always implemented; the drift mechanism (tests mirroring implementation) +is the observational content. + +## Related + +- ADR-0203 D5; `.devils-advocate/logs/check-9-critique-2026-07-09.md` diff --git a/.dev/lessons/INDEX.md b/.dev/lessons/INDEX.md index bf39b6c94..666c428d2 100644 --- a/.dev/lessons/INDEX.md +++ b/.dev/lessons/INDEX.md @@ -24,6 +24,7 @@ | Date | Slug | Keywords | One-line | | |------------|----------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 2026-07-09 | failure-path-tests-certified-the-defect | ADR-0203 D5 cache silent-miss, failure-path testing, unit test asserted defective behavior, DA critique reproduced corrupt-entry brick + ZWASM_DEBUG fail + interp override, happy-path bias, tests mirror implementation not invariant, enumerate failure modes red-first, self-heal degrade-not-fail | Stage-5 `--cache` shipped the happy path complete while the D5 invariant "cache can never make run fail" was implemented only for read errors; the unit test ASSERTED the defect (corrupt entry returned as hit) and a same-file comment contradicted the module doc. DA critique reproduced 4 failing-run shapes. Lesson: enumerate an invariant's failure modes and write one red test per mode BEFORE implementing — implementer-written tests otherwise mirror the implementation, not the invariant. Fixed 4a17ceda7 (header-gate + self-heal, refusal=bypass, interp bypass). | | | 2026-07-09 | stale-cache-binary-false-differential-finding | stale .zig-cache/o/ exe path, per-edit new hash dir old binary survives, fault-injection artifact mimics real finding, MEM-MISMATCH offset 0x3 +1 signature, re-resolve via zig build step, disambiguate by running candidates on committed known-green corpora, clean-rebuild green before trusting post-injection findings | Running a differential runner by a remembered `.zig-cache/o//` path after source edits executed a STALE (fault-injected) build — its lone campaign "finding" was the injection artifact. Each edit compiles to a NEW hash dir; old ones survive. Rules: re-resolve the exe via `zig build ` after any edit (or verify the candidate against the committed known-green corpora first); end every fault-injection experiment with a clean rebuild + green run; a finding mirroring a recent deliberate perturbation is presumed contamination until a fresh build reproduces it. | | | 2026-07-01 | listen-test-runner-endofstream-guest-stdout | failed command --listen=-, phantom test failure exit 0, Zig 0.16 test runner EndOfStream panic after all pass, WASI guest stdout real fd 1 corrupts protocol, builtin.is_test guard fd.zig signal.zig | `zig build test` shows a phantom `failed command: … --listen=-` even when all tests pass — a WASI test wrote guest stdout to real fd 1 (the --listen protocol channel); guard real std-fd writes with `!builtin.is_test` | | 2026-06-22 | d489-capture-path-investigation | D-489, capture-path theory FALSIFIED re-corrected same day, genuine x86_64 JIT MISCOMPILE not capture artifact, direct CLI jit tinygo_json 130 corrupt on BOTH Rosetta x86_64-macos AND x86_64-linux 90 OK arm64+interp, Rosetta does NOT mask FALSE claim, 3 repro experiments add-syscall + pure-syscall-no-appendSlice both still 130, no memory.grow happens, FAST LOOP Rosetta on Mac no scp zig build -Dtarget=x86_64-macos run --engine jit wc -c, blocks .auto->JIT flip, wrong scalar -> wrong iovec ptr Δ416 + len, spill-pressure class, NEXT dynamic value-trace real run func 136 on Rosetta | The earlier-same-day "capture-path / NOT a miscompile" correction is FALSIFIED. Direct `zwasm run --engine jit tinygo_json.wasm` = 130 corrupt on Rosetta x86_64-macos AND x86_64-linux; 90 OK arm64 native + interp ⇒ a GENUINE x86_64 miscompile that DOES block the `.auto`→JIT flip. Capture theory killed by experiments A/B/C + grow instrumentation (no grow). "Rosetta masks D-489" is FALSE → fast loop is Rosetta on Mac (no scp). Symptom/root-cause narrowing (func 136 run$1, global.get1/br_if-rewind under deep spill) from the 2026-06-21 lessons stands. NEXT = dynamic value-trace of the real run. | | diff --git a/README.md b/README.md index 042ad06af..ee6a43ec9 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ zwasm run [args...] # run a module (WASI _start / main) [--timeout ] # interrupt after a wall-clock deadline [--max-memory ] # refuse memory.grow past this many bytes [--max-table-elements ] # refuse table growth past this many elements + [--cache[=DIR]] # transparent compilation cache (content-keyed .cwasm reuse; a cache defect degrades, never fails the run) + [--cache-clear] # delete this build's cache subdirectory (clear-only; combine with --cache to repopulate) zwasm compile -o # compile to a .cwasm AOT artifact zwasm --version | -V # version + build identity (wasm/wasi/engine) zwasm --help | -h | help diff --git a/bench/results/history.yaml b/bench/results/history.yaml index e2c56db1c..e5972c718 100644 --- a/bench/results/history.yaml +++ b/bench/results/history.yaml @@ -314445,3 +314445,174 @@ stddev_ms: 0.16 min_ms: 2.06 max_ms: 2.42 + +- date: 2026-07-09T14:06:13Z + commit: 4a17ceda77fd82b50bf82e97911229d12c7e46c1 + arch: aarch64-darwin + build: ReleaseFast + reason: "ADR-0203 AOT-full-fidelity campaign (PRs #137-#141): PIC helper slots + format v0.5 + full-runtime .cwasm load + elision serialization + transparent --cache" + runs: 5 + warmup: 3 + benches: + - name: shootout/fib2 + runtime: zwasm + mean_ms: 1204.47 + stddev_ms: 28.65 + min_ms: 1155.29 + max_ms: 1222.15 + - name: shootout/sieve + runtime: zwasm + mean_ms: 789.50 + stddev_ms: 5.57 + min_ms: 782.89 + max_ms: 796.12 + - name: shootout/nestedloop + runtime: zwasm + mean_ms: 3.43 + stddev_ms: 0.11 + min_ms: 3.30 + max_ms: 3.56 + - name: shootout/matrix + runtime: zwasm + mean_ms: 338.23 + stddev_ms: 0.89 + min_ms: 337.09 + max_ms: 339.24 + - name: shootout/heapsort + runtime: zwasm + mean_ms: 1923.38 + stddev_ms: 5.01 + min_ms: 1919.49 + max_ms: 1931.14 + - name: shootout/base64 + runtime: zwasm + mean_ms: 684.50 + stddev_ms: 6.75 + min_ms: 677.17 + max_ms: 693.32 + - name: shootout/gimli + runtime: zwasm + mean_ms: 7.69 + stddev_ms: 0.09 + min_ms: 7.57 + max_ms: 7.83 + - name: shootout/memmove + runtime: zwasm + mean_ms: 37.21 + stddev_ms: 0.80 + min_ms: 36.56 + max_ms: 38.59 + - name: shootout/keccak + runtime: zwasm + mean_ms: 32.46 + stddev_ms: 0.44 + min_ms: 31.87 + max_ms: 33.04 + - name: shootout/ctype + runtime: zwasm + mean_ms: 722.36 + stddev_ms: 2.65 + min_ms: 719.64 + max_ms: 726.50 + - name: shootout/random + runtime: zwasm + mean_ms: 304.43 + stddev_ms: 8.52 + min_ms: 296.16 + max_ms: 316.94 + - name: shootout/ratelimit + runtime: zwasm + mean_ms: 116.24 + stddev_ms: 5.48 + min_ms: 112.14 + max_ms: 125.35 + - name: shootout/minicsv + runtime: zwasm + mean_ms: 965.41 + stddev_ms: 29.53 + min_ms: 931.50 + max_ms: 997.70 + - name: shootout/xblabla20 + runtime: zwasm + mean_ms: 5.74 + stddev_ms: 0.09 + min_ms: 5.64 + max_ms: 5.83 + - name: shootout/xchacha20 + runtime: zwasm + mean_ms: 8.96 + stddev_ms: 0.23 + min_ms: 8.73 + max_ms: 9.22 + - name: tinygo/arith + runtime: zwasm + mean_ms: 2.60 + stddev_ms: 0.32 + min_ms: 2.33 + max_ms: 3.02 + - name: tinygo/fib + runtime: zwasm + mean_ms: 2.38 + stddev_ms: 0.04 + min_ms: 2.32 + max_ms: 2.42 + - name: tinygo/fib_loop + runtime: zwasm + mean_ms: 2.35 + stddev_ms: 0.04 + min_ms: 2.32 + max_ms: 2.40 + - name: tinygo/gcd + runtime: zwasm + mean_ms: 2.48 + stddev_ms: 0.17 + min_ms: 2.34 + max_ms: 2.69 + - name: tinygo/list_build + runtime: zwasm + mean_ms: 2.32 + stddev_ms: 0.06 + min_ms: 2.26 + max_ms: 2.41 + - name: tinygo/mfr + runtime: zwasm + mean_ms: 2.43 + stddev_ms: 0.10 + min_ms: 2.33 + max_ms: 2.59 + - name: tinygo/nqueens + runtime: zwasm + mean_ms: 2.60 + stddev_ms: 0.19 + min_ms: 2.43 + max_ms: 2.87 + - name: tinygo/real_work + runtime: zwasm + mean_ms: 2.49 + stddev_ms: 0.12 + min_ms: 2.35 + max_ms: 2.66 + - name: tinygo/sieve + runtime: zwasm + mean_ms: 2.44 + stddev_ms: 0.06 + min_ms: 2.38 + max_ms: 2.50 + - name: tinygo/string_ops + runtime: zwasm + mean_ms: 2.48 + stddev_ms: 0.05 + min_ms: 2.46 + max_ms: 2.57 + - name: tinygo/tak + runtime: zwasm + mean_ms: 2.42 + stddev_ms: 0.06 + min_ms: 2.36 + max_ms: 2.50 + - name: handwritten/nbody + runtime: zwasm + mean_ms: 1.99 + stddev_ms: 0.20 + min_ms: 1.84 + max_ms: 2.30 diff --git a/docs/reference/cli.md b/docs/reference/cli.md index 7f7a520a3..210c51481 100644 --- a/docs/reference/cli.md +++ b/docs/reference/cli.md @@ -36,24 +36,36 @@ parse/compile). | `--timeout ` | interrupt the guest (`interrupted` trap) after a wall-clock deadline — both engines | | `--max-memory ` | refuse `memory.grow` past this many bytes (64 KiB page granularity); the spec `-1` failure, not a trap | | `--max-table-elements ` | cap a module's **declared initial** table element count at load time (D-332); a module whose initial table exceeds `N` is refused. (Runtime `table.grow` past a table's own declared max already returns the spec `-1`.) | +| `--cache[=DIR]` | transparent compilation cache (ADR-0203): the module is keyed by content hash and the `.cwasm` artifact of a previous run is reused (parse/validate/codegen skipped). Default root = the platform user-cache dir (`~/Library/Caches/zwasm` / `$XDG_CACHE_HOME\|~/.cache/zwasm` / `%LOCALAPPDATA%\zwasm`). ANY cache defect degrades (miss / bypass) — the cache never makes `run` fail. Bypassed under `--engine interp`. Cache-dir write access = native code execution as the user: point `--cache=DIR` only at trusted locations | +| `--cache-clear` | delete this build's versioned cache subdirectory, then run normally (clear-only: does not itself enable caching — combine with `--cache` to clear-then-repopulate) | The sandboxing flags (`--fuel`/`--timeout`/`--max-memory`/`--max-table-elements`) apply to `.wasm` -runs on both engines; a `.cwasm` or component run combined with them is -refused loudly (exit 2) rather than running unsandboxed. +**and `.cwasm`** runs (the artifact loads into the full runtime — ADR-0203); +a component run combined with them is refused loudly (exit 2) rather than +running unsandboxed. ### `compile` -Reads a `.wasm`, runs the JIT pipeline, and writes a `.cwasm` v0.1 AOT -artifact to the `-o` / `--output` path. `zwasm run -` executes it. +Reads a `.wasm`, runs the JIT pipeline, and writes a `.cwasm` AOT artifact +to the `-o` / `--output` path (format-versioned; an artifact with an +incompatible artifact-format version or CPU arch is refused with a +specific error). +`zwasm run ` executes it through the full runtime — identical +WASI / sandbox / `--invoke` behaviour to running the source `.wasm` +(ADR-0203: cache-hit == cache-miss by construction). ## Engine selection -- `.cwasm` input → AOT-loaded directly (full WASI). +- `.cwasm` input → AOT-loaded into the full runtime (full WASI). + `--engine interp` with a `.cwasm` is a contradictory request (the + artifact is precompiled JIT code) and is refused loudly (exit 2). - `.wasm` input → **`auto` by default** (prefers the JIT, transparently falls back to the interpreter). `--engine interp` forces the interpreter; `--engine jit` forces the JIT (full WASI, plus SIMD execution). `auto` is the default only — it is not a spellable `--engine` value. +- `--cache` affects `.wasm` runs only (a `.cwasm` input IS the artifact; + components have no artifact format) and is bypassed under + `--engine interp`. ## Exit codes diff --git a/src/cli/main.zig b/src/cli/main.zig index 440363330..fd53e37b2 100644 --- a/src/cli/main.zig +++ b/src/cli/main.zig @@ -161,6 +161,9 @@ pub fn main(init: std.process.Init) !void { }; if (std.mem.eql(u8, mode, "jit")) { engine_jit = true; + // Last flag wins: undo a preceding `--engine interp`'s + // sticky force (read by the cache + .cwasm gates). + limits.engine = .auto; } else if (std.mem.eql(u8, mode, "interp")) { engine_jit = false; // D-496 `.auto`→JIT flip: the default captured path now prefers @@ -316,6 +319,14 @@ pub fn main(init: std.process.Init) !void { // pre-stage-3 refusals (limits, typed invoke args) are gone — // both are wired through the shared path now. if (run_bytes.len >= 4 and std.mem.eql(u8, run_bytes[0..4], "CWAS")) { + // D-496 spirit: an explicit `--engine interp` cannot be + // honoured for a precompiled JIT artifact — refuse loudly + // rather than silently running the JIT. (The cache path + // never reaches here under interp; it bypasses above.) + if (limits.engine == .interp) { + try printlnErr(io, "zwasm run: --engine interp cannot run a .cwasm artifact (precompiled JIT code); run the original .wasm instead"); + std.process.exit(2); + } const code = cli_run.runWasmJitCaptured(gpa, io, run_bytes, invoke_name, argv_list.items, preopen_list.items, env_keys.items, env_vals.items, limits, null, invoke_args) catch |err| { var buf: [256]u8 = undefined; const msg = std.fmt.bufPrint(&buf, "zwasm run: cannot run '{s}': {s}", .{ path, @errorName(err) }) catch "zwasm run: .cwasm run failed"; diff --git a/test/aot/aot_process_diff.zig b/test/aot/aot_process_diff.zig index 460df3800..14b5b154c 100644 --- a/test/aot/aot_process_diff.zig +++ b/test/aot/aot_process_diff.zig @@ -289,6 +289,17 @@ pub fn main(init: std.process.Init) !void { .{ entry.name, interp_ref.exit, interp_cached.exit, stored }, ); } + // Explicit `.cwasm` + `--engine interp` is a contradictory + // request (the artifact IS JIT code) — loud refusal, exit 2. + var interp_cwasm = try runLane(gpa, io, &.{ cli, "run", "--engine", "interp", entry.name }, tmp_dir); + defer interp_cwasm.deinit(gpa); + if (interp_cwasm.exit != 2) { + unexpected += 1; + try stdout.print( + "INTERP-CWASM-REFUSAL-FAIL {s}: exit={d} (want 2)\n", + .{ entry.name, interp_cwasm.exit }, + ); + } } switch (expectationFor(entry.name)) { From 46a5c4a5b029064dd1ead6322eab5acdaa9dff38 Mon Sep 17 00:00:00 2001 From: "Shota Kudo (chaploud)" Date: Thu, 9 Jul 2026 23:52:43 +0900 Subject: [PATCH 2/2] chore(release): bump version to 2.2.0 + CHANGELOG (USER-DIRECTED release, ADR-0156) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Release directed by the user 2026-07-09 ("campaign Vが終わったら、タグを 切ってリリースしてください"): after this PR merges, the v2.2.0 tag on the merge commit triggers release.yml (auto-build + Release + Latest). Claude-Session: https://claude.ai/code/session_01F2PGseEjGFR1uqid4FaXsm --- CHANGELOG.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ build.zig.zon | 2 +- 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a80b26e..c07093271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,52 @@ SemVer compatibility guarantees start at the first stable `v2.0.0` tag. _No changes yet._ +## [2.2.0] - 2026-07-09 + +AOT-full-fidelity campaign (ADR-0203, PRs #136-#142): `.cwasm` is now a +real deployment-grade artifact, and compilation is transparently cacheable. + +### Added + +- **Transparent compilation cache** (`zwasm run --cache[=DIR]`, D-508): + modules are keyed by content hash and the `.cwasm` artifact of a previous + run is reused — parse/validate/codegen skipped (measured 2.2x cold start + on a 3 MB Go module). Deploy artifact stays `.wasm`; the cache lives in + the platform user-cache dir under a versioned subdirectory. Any cache + defect (corrupt entry, unserializable module, I/O failure) degrades to a + miss or bypass — the cache can never make `run` fail. `--cache-clear` + deletes this build's cache subdirectory. +- **`.cwasm` format v0.5**: embeds the original module bytes plus per-func + frame/EH/oob metadata, so an artifact loads back into the FULL runtime. + +### Changed + +- **`zwasm run x.cwasm` now runs through the full runtime** — identical + WASI, sandbox limits (`--fuel`/`--timeout`/`--max-memory`/ + `--max-table-elements`), `--invoke NAME=ARGS`, and start-function + behaviour to running the source `.wasm` (cache-hit == cache-miss by + construction). The former compute-only AOT mini-runtime is retired, and + the `.cwasm` sandbox-flag refusal is gone. +- **Bounds-check-elided artifacts serialize** (guard-page hosts): `zwasm + compile` output now carries the elision bit and re-registers trap + entries at load; non-guarded hosts refuse the artifact loudly. +- `--engine interp` with a `.cwasm` input is now a loud exit-2 refusal + (the artifact is precompiled JIT code); with `--cache` it bypasses the + cache and runs the interpreter as asked. + +### Fixed + +- **JIT helper addresses are no longer baked into emitted code** (D-516): + a `.cwasm` produced by one process crashed (or worse) in another under + ASLR — all 36 helper call sites now route through position-independent + runtime slots. A cross-process differential gate + (`zig build test-aot-diff`, 63 fixtures) pins the fix. +- **`(start)` function now runs on the lenient JIT path** (Wasm §4.5.4) — + a pre-existing `--engine jit` spec bug the campaign's differential + harness caught. +- CI: the `ci-required` aggregator no longer reports green when its + change-detection job fails. + ## [2.1.0] - 2026-07-06 ### Added diff --git a/build.zig.zon b/build.zig.zon index 3a128cc78..e3d5675b1 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .zwasm, - .version = "2.1.0", + .version = "2.2.0", .fingerprint = 0xfdac0191bf453d15, .minimum_zig_version = "0.16.0", .dependencies = .{