The workload's release binary links dynamic-tier symbols through its
worker messages: node:worker_threads values (Worker, parentPort,
workerData) are dyn objects, and worker.on and postMessage lower to
dynamic method calls. ThreadSlots and InternTable link none on their
own. intern-table and typed-array-scan-workers are listed for the same
reason.
The gate read an empty symbol list when nm could not start, was killed by
a signal, or exited with a non-zero status, so the dynamic-tier symbol
check and the executable comparison passed without any symbols. nmOutput
now throws with the binary, the reason and nm's stderr.
--cachegrind now also runs differing corpus executables (without
arguments, accepting intentional non-zero exits), so emitter changes that
only touch corpus programs get per-function instruction deltas. AGENTS.md
mentions the dynamic-tier symbol rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/zero-cost-gate.mjs compiles a fixed good-code set (the runtime
benchmark workloads, the self-hosted compiler, and the TypeScript corpus)
with a baseline and a candidate checkout. It requires byte-identical IR,
LLVM and release executables, and names the first differing IR function,
LLVM function or executable symbol when something changes. Programs that
match the change's pattern signature (--allow) may differ. It also checks
compile time (paired builds, bootstrap interval), binary size, optional
per-function cachegrind instruction counts, and the two-tier I3 symbol
rule: no executable gains dynamic-tier runtime symbols, and fully typed
benchmark workloads define none.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>