|
| 1 | +# Zig 0.15.2 → 0.16.0 Migration — Working Document |
| 2 | + |
| 3 | +**Status**: In progress (branch `develop/zig-016-migration`) |
| 4 | +**Baseline**: commit `8bfbf5b` (`pre-zig-016` in `bench/history.yaml`) |
| 5 | +**Target zwasm**: v1.11.0 (released) |
| 6 | + |
| 7 | +This is a **temporary** working doc. Delete after Phase 7 completion (or |
| 8 | +move learnings into `.dev/decisions.md` D## entry and `.dev/zig-tips.md`). |
| 9 | + |
| 10 | +## Phase -1: zwasm Dependency Audit |
| 11 | + |
| 12 | +### Existing -Dwasm Infrastructure (already in place) |
| 13 | + |
| 14 | +`build.zig` already supports `-Dwasm=true|false` (default true) with full |
| 15 | +conditional gating. Zig source files using zwasm are already wrapped in |
| 16 | +`if (enable_wasm) ...` patterns. **No new build-side gating needed**. |
| 17 | + |
| 18 | +- `build.zig:10` — `-Dwasm` flag definition |
| 19 | +- `build.zig:17` — propagated to `build_options.enable_wasm` |
| 20 | +- `build.zig:22-37` — `zwasm_mod` / `zwasm_native_mod` conditional dep |
| 21 | +- `build.zig:44,59,84` — conditional `addImport("zwasm", ...)` |
| 22 | +- `build.zig:115` — `wasm32-wasi` target does NOT depend on zwasm (correct) |
| 23 | + |
| 24 | +Source-side gates already present: |
| 25 | + |
| 26 | +- `src/runtime/wasm_types.zig:20` — `const zwasm = if (enable_wasm) @import("zwasm") else struct {};` |
| 27 | +- `src/lang/lib/cljw_wasm.zig:16` — `.enabled = wasm_types.enable_wasm` |
| 28 | + (NamespaceDef level — `cljw.wasm` namespace is unregistered when disabled) |
| 29 | + |
| 30 | +### Verified working under `-Dwasm=false` on Zig 0.15.2 |
| 31 | + |
| 32 | +- `zig build -Dwasm=false` → exit 0 ✓ |
| 33 | +- `zig build test -Dwasm=false` → exit 0 ✓ (Zig unit tests auto-skip) |
| 34 | +- `zig build -Doptimize=ReleaseSafe -Dwasm=false` → exit 0 ✓ |
| 35 | + |
| 36 | +### What FAILS under `-Dwasm=false` (needs Phase 0 work) |
| 37 | + |
| 38 | +#### 1. E2E Wasm tests (test/e2e/wasm/, 6 files) |
| 39 | + |
| 40 | +``` |
| 41 | +test/e2e/wasm/01_basic_test.clj |
| 42 | +test/e2e/wasm/02_tinygo_test.clj |
| 43 | +test/e2e/wasm/03_host_functions_test.clj |
| 44 | +test/e2e/wasm/04_module_objects_test.clj |
| 45 | +test/e2e/wasm/05_wit_test.clj |
| 46 | +test/e2e/wasm/06_multi_module_test.clj |
| 47 | +``` |
| 48 | + |
| 49 | +All start with `(require '[cljw.wasm :as wasm])` → fail with "Could not |
| 50 | +locate cljw.wasm on load path" because the namespace is not registered. |
| 51 | + |
| 52 | +#### 2. Test runners that unconditionally invoke wasm tests/benchmarks |
| 53 | + |
| 54 | +| Runner | What breaks | |
| 55 | +|---|---| |
| 56 | +| `test/run_all.sh` | step "e2e tests (wasm)" calls `bash test/e2e/run_e2e.sh` (no dir filter) → all e2e dirs incl. wasm | |
| 57 | +| `test/e2e/run_e2e.sh` | no `--no-wasm` flag; finds all `*_test.clj` recursively | |
| 58 | +| `bench/wasm_bench.sh` | runs wasm benchmarks via TinyGo .wasm modules — needs cljw.wasm | |
| 59 | +| `bench/run_bench.sh` | runs benchmarks 21-25, 28-31 (9 wasm benchmarks) under `bench/benchmarks/` | |
| 60 | + |
| 61 | +#### 3. Wasm benchmarks (bench/benchmarks/) |
| 62 | + |
| 63 | +``` |
| 64 | +21_wasm_load 22_wasm_call 23_wasm_memory 24_wasm_fib 25_wasm_sieve |
| 65 | +28_wasm_tgo_fib 29_wasm_tgo_tak 30_wasm_tgo_arith 31_wasm_tgo_sieve |
| 66 | +``` |
| 67 | + |
| 68 | +### Source files referencing WasmModule type (for migration awareness) |
| 69 | + |
| 70 | +Already-gated, but require io threading in Phase 2: |
| 71 | + |
| 72 | +- `src/runtime/wasm_types.zig` — main bridge |
| 73 | +- `src/runtime/wasm_wit_parser.zig` — WIT parser, uses @embedFile (no io) |
| 74 | +- `src/runtime/value.zig` — `.wasm_module` variant |
| 75 | +- `src/runtime/dispatch.zig` — invokeWasmFn dispatch |
| 76 | +- `src/runtime/gc.zig` — WasmModule finalizer registry |
| 77 | +- `src/lang/lib/cljw_wasm.zig` — NamespaceDef |
| 78 | +- `src/lang/lib/cljw_wasm_builtins.zig` — wasm/load, wasm/fn impl |
| 79 | +- `src/engine/vm/vm.zig`, `src/engine/evaluator/tree_walk.zig` — call sites |
| 80 | +- `src/app/repl/nrepl.zig:1427` — `#<WasmModule>` formatter |
| 81 | +- `src/app/deps.zig` — `cljw/wasm-deps` config parsing (test data only) |
| 82 | + |
| 83 | +## Phase 0: Plan |
| 84 | + |
| 85 | +Reduced scope thanks to existing infrastructure: |
| 86 | + |
| 87 | +1. **Add `--no-wasm` flag to test runners**: |
| 88 | + - `test/run_all.sh` — skip "e2e tests (wasm)" step when `--no-wasm` |
| 89 | + - `test/e2e/run_e2e.sh` — skip `wasm/` directory when `--no-wasm` (or `WASM_DISABLED=1` env) |
| 90 | + - `bench/wasm_bench.sh` — early exit with friendly message when `--no-wasm` |
| 91 | + - `bench/run_bench.sh` — filter out wasm_* benchmarks when `--no-wasm` |
| 92 | + |
| 93 | +2. **Update build.zig.zon**: `minimum_zig_version = "0.16.0"` (will be done as |
| 94 | + part of Phase 0 commit, even though we still build with 0.15.2 during the |
| 95 | + actual code migration phases — `.zon` is just metadata until we actually |
| 96 | + bump zig). |
| 97 | + |
| 98 | + Actually: defer this to first 0.16-only commit so we can keep building |
| 99 | + with 0.15.2 during preparatory commits. |
| 100 | + |
| 101 | +3. **Update zwasm dep tag**: defer to Phase 6 (currently v1.9.1, target v1.11.0). |
| 102 | + Until Phase 6, build with `-Dwasm=false` so the v1.9.1 zwasm dep is never resolved. |
| 103 | + |
| 104 | +4. **Update `.dev/baselines.md`**: relax binary size cap (≤5.0MB → provisional |
| 105 | + ≤5.5MB during migration, finalize in Phase 7). |
| 106 | + |
| 107 | +5. **Doc/CI sweep**: grep "0.15.2", "Zig 0.15", update to "Zig 0.16.0": |
| 108 | + - `.claude/CLAUDE.md` |
| 109 | + - `.dev/baselines.md`, `.dev/decisions.md`, `.dev/references/*.md` |
| 110 | + - `README.md` |
| 111 | + - `flake.nix`, `flake.lock` (if present) |
| 112 | + - `.github/workflows/*.yml` (if present) |
| 113 | + - `scripts/*.sh` |
| 114 | + |
| 115 | +## Decision: Gating mechanism for test runners |
| 116 | + |
| 117 | +Use **`--no-wasm` flag** on each runner (matches existing `--quick`, |
| 118 | +`--tree-walk` patterns). Avoid env vars to keep behavior explicit. |
| 119 | + |
| 120 | +`test/run_all.sh` will pass `--no-wasm` down to `run_e2e.sh` when invoked |
| 121 | +with `--no-wasm`, and skip `wasm_bench.sh` entirely. |
| 122 | + |
| 123 | +## Open questions for Phase 6 (deferred) |
| 124 | + |
| 125 | +- Does zwasm v1.11.0 export the same module interface as v1.9.1? |
| 126 | + (`zwasm.WasmModule`, `zwasm.Capabilities`, `zwasm.ImportEntry`, etc.) |
| 127 | +- Are there breaking API changes in zwasm v1.10.0 → v1.11.0 we'd need |
| 128 | + to absorb at the `wasm_types.zig` bridge? |
| 129 | +- Action: read `~/Documents/MyProducts/zwasm/CHANGELOG.md` v1.10.0 + v1.11.0 |
| 130 | + notes when entering Phase 6. |
| 131 | + |
| 132 | +## Phase 7: Atomic Toolchain Flip (deferred) |
| 133 | + |
| 134 | +Once code migration is complete and tests are green on Zig 0.16.0, |
| 135 | +flip all toolchain pins and version-mention strings in a single commit. |
| 136 | +Doing this earlier creates a window where neither 0.15.2 nor 0.16.0 builds |
| 137 | +cleanly. |
| 138 | + |
| 139 | +Files to update: |
| 140 | + |
| 141 | +| File | Lines | Change | |
| 142 | +|---|---|---| |
| 143 | +| `build.zig.zon` | 11 | `.minimum_zig_version = "0.16.0"` | |
| 144 | +| `flake.nix` | 9, 20, 23, 27, 31, 35, 46, 58 | URLs and comments → 0.16.0 | |
| 145 | +| `flake.lock` | 71 | regenerate via `nix flake update zig-overlay` | |
| 146 | +| `.github/workflows/ci.yml` | 16, 74, 117 | `version: 0.16.0` | |
| 147 | +| `.github/workflows/nightly.yml` | 15, 59 | `version: 0.16.0` | |
| 148 | +| `.github/workflows/release.yml` | 32 | `version: 0.16.0` | |
| 149 | +| `README.md` | 5, 34 | badge + install link | |
| 150 | +| `.claude/CLAUDE.md` | 3, 290, 293 | intro + "Pitfalls" section header + path hint | |
| 151 | +| `.claude/references/zig-tips.md` | 1, 34 | title + body content | |
| 152 | +| `.dev/baselines.md` | 4 | "Zig 0.15.2" → "Zig 0.16.0" platform line | |
| 153 | +| `.dev/CONTRIBUTING.md` | 33 | install requirement | |
| 154 | +| `.dev/references/setup-orbstack.md` | 19, 30 | install + version check | |
| 155 | +| `.dev/references/ubuntu-testing-guide.md` | 56 | describe 0.16-specific behavior if changed | |
| 156 | +| `docs/differences.md` | 10 | runtime row | |
| 157 | +| `.dev/future.md` | 365 | check if still relevant | |
| 158 | + |
| 159 | +DO NOT touch: |
| 160 | +- `.dev/archive/**` — historical phase notes |
| 161 | +- `.dev/decisions.md` D## entries that reference 0.15.2 — these are immutable history |
| 162 | + (D## about ArenaAllocator.free, @call always_tail, etc. — those decisions remain valid context) |
| 163 | + |
| 164 | +After flip: |
| 165 | +- Re-run `bash test/run_all.sh` (no --no-wasm) on Zig 0.16.0 |
| 166 | +- OrbStack Ubuntu validation: `--seed 0` still required? Re-test |
| 167 | +- Update binary size baseline to actual measured value |
| 168 | +- Add D## entry in `.dev/decisions.md` for the migration |
| 169 | +- Add F## in `.dev/checklist.md` for the libc strip follow-up (zwasm W46 equivalent) |
| 170 | +- Delete this file (`.dev/zig-016-migration.md`) |
0 commit comments