Enable arm64_32-apple-watchos (ILP32) static-lib build — cleanup of #97#98
Merged
Conversation
The previous override declared a custom `panic` namespace and `std_options` unconditionally for every C-API consumer (Mac / iOS / Linux / Windows static and shared libs), losing pretty panic messages and stdlib logging to work around a compile error that only surfaces on ILP32 (arm64_32-apple-watchos). Zig 0.16 already ships `std.debug.no_panic` — the canonical trap-on-every-panic namespace. Use it directly on ILP32 and fall back to `std.debug.FullPanic(std.debug.defaultPanic)` (the stdlib default) on every other target. Same gating for `std_options`. Net: ~80 fewer hand-rolled lines, no behaviour change for 64-bit C-API consumers, ILP32 build still compiles.
`.single_threaded = true` was being forced on every static-lib build (macOS / iOS / Linux / Windows), silently degrading wasm- threads `atomic.wait/notify` on 64-bit C-API consumers — the wait queue paths in `src/memory.zig` rely on `std.Thread.Mutex` / `Condition`, which become no-ops under single_threaded. The ILP32 motivation (std.Io.Threaded not compiling under arm64_32-apple-watchos) only applies when usize is 32-bit, so gate on `target.result.ptrBitWidth() < 64` and leave the default (multi-threaded) on every other target.
…is requested The previous ILP32 code path left `vm.io` as `undefined` whenever config.io was null, on the assumption that consumers without WASI would never dereference it. That is unsafe: `setDeadlineTimeoutMs` unconditionally calls `std.Io.Timestamp.now(self.io, ...)` for any non-null `config.timeout_ms`, and `applyWasiOptions` reaches into `io.vtable.now` / `io.openDir` via `addPreopenPath` when WASI is enabled. Both would deref an undefined vtable. Refuse loadCore early with `error.IlpRequiresExplicitIo` if either of these features is requested on ILP32 without a caller-supplied io. Pure-interpreter workloads (the watchOS wasm-benchmark use case: no WASI, no timeout, no atomics) are unaffected. Wasm modules executing `memory.atomic.wait/notify` also reach io but can't be detected statically — embedders who run such modules on ILP32 must pass config.io themselves.
Add a D## entry capturing why ILP32 needs separate handling (std.Io.Threaded fails u64 → usize narrowing under that ABI), which features stay supported, which are explicitly refused (WASI / timeout without config.io), and the comptime gating strategy that keeps 64-bit consumers byte-identical. Track open follow-ups under W55-watchos-ilp32 in checklist.md: upstream Zig .a packing bug, C-header documentation of the new error code, and the atomic.wait/notify correctness gap. Tables touched by `md-table-align` (per project convention) collapsed adjacent rows in D128 / D137 that were already mis-aligned — pure whitespace, no semantic change.
GitHub Actions runners do not ship the watchOS SDK so the resulting archive cannot be linked or executed there; without a build-only gate the ILP32 comptime branches in src/c_api.zig, src/guard.zig, src/memory.zig, src/types.zig, src/vm.zig and the build.zig single_threaded selector will rot the next time anyone touches those files. Add a dedicated job that runs `zig build static-lib -Dtarget=aarch64-watchos-ilp32 -Djit=false -Dcomponent=false -Dwat=false` on macos-latest and verifies the resulting cached `libzwasm_zcu.o` is the expected `arm64_32 / armv8` Mach-O object — Zig 0.16's broken .a packing for this triple (D139 known issue) means the `.a` itself is only the SYMDEF, not a useful linkable artefact, so we point the check at the `.o` directly.
The error returned when loadCore refuses to auto-init io on ILP32 is part of WasmModule.load's inferred error set across every target, not just watchOS. Naming it after the ABI (`IlpRequiresExplicitIo`) leaks that detail into the public cross-target error surface and asks 64-bit callers to handle an arm-name they can never observe. `error.MissingIo` describes the cause (config.io was null when something needed it) without advertising the platform. Same loud-failure semantics, neutral name.
CI run on macos-latest failed because the runner's `file` version prints `Mach-O object arm64_32_v8` while local Darwin 25 prints `Mach-O 64_32-bit armv8 object`. The original grep matched only the local form. Accept either by switching to `grep -E 'arm64_32|64_32-bit armv8'` — the substring `arm64_32` is the stable cpusubtype name common to both.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replacement for #97 with project-conventional cleanup stacked on top of @matthargett's original commit. Authored-by metadata on commit
3d563237is preserved.Closes #97.
Summary
Enables
zig build static-lib -Dtarget=aarch64-watchos-ilp32 -Djit=falsefor Apple Watch SE / SE2 / S4-S8 (ILP32 ABI). Matt's original commit (3d563237) ships untouched; the 6 stacked commits address review feedback so the change is safe to land on main.Why a replacement PR
The
maintainerCanModify=trueflag on #97 unfortunately did not allow us to push directly to the fork branch fromclojurewasm/zwasm's admin account (GitHub rejected with 403 — likely an org-side restriction onrebeckerspecialties). Rather than block on that, the cleanup commits land here with full credit preserved.Commits
Enable arm64_32-apple-watchos (aarch64-watchos-ilp32) build(matthargett — original)fix(c_api): use std.debug.no_panic on ILP32, scope override to that ABIfix(build): scope static-lib single_threaded to ILP32 targetsfix(types): require explicit config.io on ILP32 when WASI or timeout is requesteddocs(D139,W55): document arm64_32-apple-watchos ILP32 support strategyci: build-only smoke check for aarch64-watchos-ilp32fix(types): rename ILP32 io guard to error.MissingIoWhat the cleanup changed vs #97
single_threadedis now ILP32-only (target.result.ptrBitWidth() < 64). Enable arm64_32-apple-watchos (Apple Watch SE2 / Series 4-8) build #97 forced it on every static-lib target, silently degradingmemory.atomic.wait/notifyfor 64-bit Linux / macOS / Windows C-API consumers.panicnamespace shrunk to one line —pub const panic = if (ilp32) std.debug.no_panic else std.debug.FullPanic(std.debug.defaultPanic);. Zig 0.16 already shipsstd.debug.no_panicfor exactly this purpose; the original ~80-line hand-rolled@trap()namespace also affected every C-API consumer's panic messages.error.MissingIoon ILP32 when WASI ortimeout_msis requested withoutconfig.io— the previous code leftvm.ioasundefinedand relied on the embedder not exercising deadline / WASI host preopen paths. Now we fail loud..dev/decisions.mddocuments the support matrix, alternatives considered, and the rationale per Enable arm64_32-apple-watchos (Apple Watch SE2 / Series 4-8) build #97's safety strategy. W55 in.dev/checklist.mdtracks the Zig 0.16.apacking bug and other follow-ups.aarch64-watchos-ilp32onmacos-latest— GitHub runners have no watchOS SDK so the archive isn't exercised, but the comptime gates are checked.Verified
zig build static-lib -Dtarget=aarch64-watchos-ilp32 -Djit=false -Dcomponent=false -Dwat=falseon Mac: produces 2.7 MBarm64_32 / armv8Mach-O object (Zig 0.16 archive-packing bug for this triple still requiresar rcsworkaround on the consumer side, as noted in Enable arm64_32-apple-watchos (Apple Watch SE2 / Series 4-8) build #97 and tracked in W55)Test plan
bash scripts/gate-commit.shon Mac aarch64bash scripts/gate-commit.shon Ubuntu x86_64bash scripts/record-merge-bench.shon Mac (perD-gpolicy)