Skip to content

Commit 1d416a3

Browse files
committed
fix(gate): cite zwasm ledger ids as zwasm/D-NNN; two pipefail traps
Smell-audited: 1: the checker learns one qualified spelling instead of an allowlist of foreign ids, so the next foreign citation needs no script edit; the two pipe fixes apply the herestring shape the repo already uses. Three full-gate failures, all non-code, all pre-dating this session's source change on staging. The first two would have failed PR #16's CI. debt_id_refs: the per-call wasm write-up cites zwasm's own debt rows D-584 and D-585. A bare `D-NNN` is a cljw row to check_debt_id_refs.sh, so D-584 read as undefined, and D-585 passed only because cljw happens to own a D-585 of its own (the JIT zero-result gap, ADR-0192). The two ledgers share the id shape and already collide. zwasm's ids are now always written `zwasm/D-NNN`; the checker strips that qualified form before extracting ids, so a bare id stays a cljw citation and a qualified one is never mistaken for one. Retrofit: every zwasm citation in .dev/wasm_percall_findings.md, .dev/bench/ffi_boundary/README.md, .dev/handover.md, .dev/zwasm_capabilities.md and build.zig.zon. cljw's D-585 (engine.zig, ADR-0192, mem_kernel.wat) is untouched. epipe_head: bench/compare_langs.sh:504 fed `sed … /proc/cpuinfo | head -1` under pipefail (one line per core, so head closes the pipe early). Replaced by a single awk that prints the first match and exits; no pipe. e2e_phase16_wasm_memory: failed once in the full gate with the expected line visibly present in the output it printed, and passed on two direct re-runs. Its `want` helper was `echo "$out" | grep -qF` under `set -o pipefail`: grep -q exits on the first match and the producer's EPIPE fails the pipeline. Same class check_debt_id_refs.sh documents; same fix, `grep -qF "$1" <<<"$out"`, applied to all three pipes in the script.
1 parent f25c8df commit 1d416a3

5 files changed

Lines changed: 22 additions & 17 deletions

File tree

‎.dev/bench/ffi_boundary/README.md‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ separates "the crossing is expensive" from "the loop is expensive".
2222
wasm/call :engine interp 392 ns (2.1x a Clojure call)
2323
wasm/call :engine auto (JIT) 18,875 ns (48x the interp path)
2424

25-
The 18.9 us is not the cost of crossing. It is zwasm D-584: `computeStackLimit`
25+
The 18.9 us is not the cost of crossing. It is zwasm/D-584: `computeStackLimit`
2626
runs on every JIT invocation, and on Linux/glibc on the INITIAL thread glibc
2727
answers by opening and parsing `/proc/self/maps`. Confirmed with zwasm's own
2828
runner on this box (`zig build bench-latency`): `stack_limit_query_ns` 26,262
@@ -31,7 +31,7 @@ interpreter caches the same value, which is why it wins here.
3131

3232
Do not re-derive this from the source. An earlier pass read the call graph, found
3333
`findExportFunc` re-parsing the module three times per call, and named that as
34-
the cause. It is real (zwasm D-585) but sits in the ~1.2 us remainder. See hive
34+
the cause. It is real (zwasm/D-585) but sits in the ~1.2 us remainder. See hive
3535
memory 20260905010714-4c94602c.
3636

3737
## The engine x thread matrix, measured 2026-09-05
@@ -48,14 +48,14 @@ process. Run it directly: `./zig-out/bin/cljw .dev/bench/ffi_boundary/engine_thr
4848
Three readings, and each names a different fix:
4949

5050
1. **The 14x JIT penalty is entirely the initial thread.** Nothing else differs
51-
between rows 1 and 2. This is D-584 and it is dodgeable without touching
51+
between rows 1 and 2. This is zwasm/D-584 and it is dodgeable without touching
5252
zwasm: run the invocation on any thread that is not the process's first one.
5353
`@(future ...)` already does it today, measured at 12.5x on a separate run.
5454
2. **The interpreter is thread-indifferent** (399 vs 413), which confirms it
5555
caches the stack limit rather than re-querying per call.
5656
3. **Even on a worker thread the JIT is 2.9x the interpreter** on a trivial
57-
callee. That residual is D-585, the per-call module re-parse, now visible
58-
because D-584 has stopped swamping it.
57+
callee. That residual is zwasm/D-585, the per-call module re-parse, now visible
58+
because zwasm/D-584 has stopped swamping it.
5959

6060
So the engine choice and the thread choice are independent levers, and the right
6161
engine still depends on call shape: the JIT wins 15.5x on compute-in-wasm

‎.dev/wasm_percall_findings.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ zwasm ADR-0209 (`.dev/decisions/0209_percall_latency_bench.md` in the zwasm
3333
tree; clone at `~/PP/referential-projects/zwasm`) names both. Do not re-report
3434
them upstream.
3535

36-
- **D-584** dominates on Linux. `computeStackLimit` runs on every JIT
36+
- **zwasm/D-584** dominates on Linux. `computeStackLimit` runs on every JIT
3737
invocation, and glibc's `pthread_getattr_np` answers for the process's FIRST
3838
thread by opening and parsing `/proc/self/maps`. Any other thread answers from
3939
the thread descriptor. zwasm measures 26.8 us initial thread, 561-578 ns
4040
worker thread, 3.1-3.4 ns on aarch64-macos. The interpreter caches the value,
4141
which is why it is thread-indifferent above.
42-
- **D-585** is the residual. The embedding API resolves the export by name on
42+
- **zwasm/D-585** is the residual. The embedding API resolves the export by name on
4343
every call and `findExportFunc` parses the whole module to do it. Linear in
4444
module size (687-859 ns/KiB upstream). Public issue zwasm#208.
4545

@@ -49,8 +49,8 @@ still called at `entry.zig` 248 and 272 and `entry_buffer_write.zig` 86.
4949
A warning worth repeating, because this session walked into it. ADR-0209 says:
5050

5151
> "A reviewer checking this on Linux alone will find the two paths agree within
52-
> 2%, which reads as 'the re-parse does not matter'. That agreement is D-584's
53-
> constant, well over 100x larger, swamping D-585."
52+
> 2%, which reads as 'the re-parse does not matter'. That agreement is zwasm/D-584's
53+
> constant, well over 100x larger, swamping zwasm/D-585."
5454
5555
The mirror-image error is just as easy: attributing the whole Linux cost to the
5656
re-parse because the re-parse is the part you read. Two costs on one path cannot
@@ -61,7 +61,7 @@ be separated by reading either one. Get a number that isolates the candidate.
6161
Spawn one thread in `main.zig` with an explicit stack size, run the existing
6262
main body on it, join. The runtime stays SINGLE-THREADED; it just stops being
6363
thread 1. One spawn at startup, tens of microseconds, once. It buys ~14x on
64-
every JIT wasm invocation on Linux and costs nothing on macOS, where D-584 does
64+
every JIT wasm invocation on Linux and costs nothing on macOS, where zwasm/D-584 does
6565
not exist.
6666

6767
The win is already reachable from Clojure with no cljw change: wrapping
@@ -97,7 +97,7 @@ a full gate.
9797
### What it does not fix
9898

9999
Even on a worker thread the JIT is 2.9x the interpreter on a trivial callee.
100-
That is D-585, untouched by this change. Engine choice stays shape-dependent:
100+
That is zwasm/D-585, untouched by this change. Engine choice stays shape-dependent:
101101
the JIT wins 15.5x on compute-in-wasm (`bench/wasm_jit_vs_interp.sh`) and loses
102102
on crossing-heavy work at any thread. A single default cannot serve both, which
103103
is the open question in `[CLJW-WASM-ENGINE-DEFAULT]`.

‎bench/compare_langs.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -501,7 +501,7 @@ if [[ -n "$YAML_FILE" ]]; then
501501
env_os="$(sw_vers -productName 2>/dev/null) $(sw_vers -productVersion 2>/dev/null) ($(sw_vers -buildVersion 2>/dev/null))"
502502
else
503503
env_machine="$(cat /sys/devices/virtual/dmi/id/product_name 2>/dev/null || uname -m)"
504-
env_cpu="$(sed -n 's/^model name[[:space:]]*: //p' /proc/cpuinfo 2>/dev/null | head -1), $(nproc 2>/dev/null)-core"
504+
env_cpu="$(awk -F': *' '/^model name/ {print $2; exit}' /proc/cpuinfo 2>/dev/null), $(nproc 2>/dev/null)-core"
505505
env_ram="$(awk '/^MemTotal:/ {printf "%d", $2/1048576}' /proc/meminfo 2>/dev/null) GB"
506506
env_os="$(. /etc/os-release 2>/dev/null && echo "$PRETTY_NAME")"
507507
fi

‎scripts/check_debt_id_refs.sh‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,14 @@ phantom=$(rg -n --no-heading ${wt_globs[@]+"${wt_globs[@]}"} 'D-NEW[A-Z0-9-]*' "
6262

6363
# 2. Real-looking D-NNN refs with no entry in debt.yaml.
6464
# `\b` so "PID-1" / "JDK-19"-style substrings don't masquerade as a debt ref.
65+
# zwasm's own ledger ids are cited as `zwasm/D-NNN` (namespace-qualified) and
66+
# are not cljw citations: the two ledgers share the D-NNN shape and already
67+
# collide on D-585, so a bare id is ALWAYS a cljw row and the qualified form is
68+
# stripped before extraction. The line-level `rg -v` cannot do that (a line may
69+
# cite both), hence the sed on the matched text.
6570
defined=$(rg -o '\bD-[0-9]+' "$DEBT" 2>/dev/null | sort -u || true)
66-
referenced=$(rg -o --no-heading ${wt_globs[@]+"${wt_globs[@]}"} '\bD-[0-9]+' "${search_paths[@]}" 2>/dev/null \
67-
| rg -v "$exclude" | grep -o 'D-[0-9]\+' | sort -u || true)
71+
referenced=$(rg -o --no-heading ${wt_globs[@]+"${wt_globs[@]}"} '(zwasm/)?\bD-[0-9]+' "${search_paths[@]}" 2>/dev/null \
72+
| rg -v "$exclude" | sed -E 's#zwasm/D-[0-9]+##' | grep -o 'D-[0-9]\+' | sort -u || true)
6873
missing=""
6974
for id in $referenced; do
7075
# Herestring, NOT `printf | grep -q`: grep -q exits on first match, and

‎test/e2e/phase16_wasm_memory.sh‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ out="$("$BIN" "$FIX" 2>&1)" || fail "cljw exited non-zero running $FIX:
2424
$out"
2525

2626
want() {
27-
echo "$out" | grep -qF "$1" || fail "expected '$1' in the probe output, got:
27+
grep -qF "$1" <<<"$out" || fail "expected '$1' in the probe output, got:
2828
$out"
2929
}
3030

@@ -59,7 +59,7 @@ echo "PASS wasm-memory-dtypes -> signedness + unaligned access"
5959
# (6) Every failure is CATCHABLE and names the fn the user wrote. A NOT-CAUGHT
6060
# means the error escaped (catch …); an uncatchable one would have exited
6161
# 70 above and never reached here.
62-
echo "$out" | grep -q "NOT-CAUGHT" && fail "a wasm/mem-* error was not raised or not caught:
62+
grep -q "NOT-CAUGHT" <<<"$out" && fail "a wasm/mem-* error was not raised or not caught:
6363
$out"
6464
want "no-memory wasm/mem-size: this module exports no linear memory"
6565
want "bad-handle wasm/mem-size: the first argument must be a loaded wasm module"
@@ -74,7 +74,7 @@ want "element-range wasm/mem-write!: element 1 is outside this element type's ra
7474
want "element-fraction wasm/mem-write!: element 0 is not an integer"
7575
echo "PASS wasm-memory-catchable -> negative offset / overflow / bad element all raise, none panic"
7676

77-
echo "$out" | grep -q "^DONE$" || fail "the probe did not run to completion:
77+
grep -q "^DONE$" <<<"$out" || fail "the probe did not run to completion:
7878
$out"
7979

8080
echo

0 commit comments

Comments
 (0)