ReviewOS

zig-utils/zig-js

Autonomous backlog: parser early errors and adversarial complexity

#933
Open glennmichael123 opened this 8 days ago ·
8 days ago

Ordered work queue for long unattended runs. This issue is the source of truth for what to do next; CLAUDE.md points here so the queue survives context compaction.

Work top to bottom. Tick an item only when its definition of done is met and the evidence is posted on its own issue. Anything that needs a human decision goes to Blocked on a human instead of being guessed.

Queue

  • 0f. #1104 — pending asyncHold lifecycle registration. Fixed in 4025527: the lock is held before worker creation; all four lifecycle oracles pass in both modes, including unsuppressed TSan. Full ReleaseSafe gate passes 253/253. The full Debug no-GIL gate exposes api/thread-lifecycle.js expecting zero side effects under a main hold. The identical assertion fails 10/10 times on pinned 80e4595. Prove startup/registration ordering before correcting the fixture; retain all four async-lifecycle oracles and run both modes plus unsuppressed TSan.

  • 0g. #1105 — Debug no-GIL corpus timeouts. Fixed in 729837d (futex registration) and 969c0f5 (exact uncaptured block-layout reuse). Full Darwin Debug and ReleaseSafe no-GIL gates pass 253/253 under the unchanged 600-second per-case watchdog. Unchanged validation-storm workload passes both modes in Debug/ReleaseSafe/unsuppressed TSan; 7,621 affected test262 cases and exact VM failure sets are unchanged. Default/verify/mid-GC/TSan fuzz all pass. Evidence posted on #1105.

  • 0e. #1103 — CI no-GIL Condition wake-count failure. Fixed in fea2e17: main holds the lock before worker construction, proving ticket registration before notification. All exact waiter and empty-queue counts pass Debug/ReleaseSafe/unsuppressed TSan in both modes. With #1104 and #1105 resolved, the full unchanged Debug and ReleaseSafe no-GIL gates now pass 253/253. Evidence posted on #1103; new push CI integration remains pending.

  • 0. CI: re-pin bench/frontend_parse.zig in representative matrix v35. Fixed in 6cfaa3f: the representative-matrix self-test passes in CI (run 35217156597). The #926/#928 workload commits changed a runner that matrix v34 pins by SHA-256, so benchmark-comparison-harness fails with "V34 measured-boundary native runner changed without a matrix version bump" (first seen at 2d04e81). Caused by this queue, so it comes first. Done: v35 hash-inherits v34 and re-pins only that runner, following the v30 re-pin (18cd453); tools/representative-matrix.ts and its self-test accept it; the job's check passes locally.

  • 0b. Fixed in 7c2e660: unit, TSan unit and focused-engine-tests jobs pass. CI: parser.test.template token storage growth and nesting propagate scratch allocation failures fails with NondeterministicMemoryUsage in the full unit binary (unit shard, TSan shard, focused-engine-tests) since at least 650efda, but passes alone and in the parser-only test root. Something earlier in the process changes how many scratch allocations the parse makes. Done: cause identified, test deterministic in the combined binary.

  • 0c. Fixed in cea5f24: dependency-audit passes in CI. CI: dependency-audit reports tools/exact-parent-regression.ts has an unclassified reference to docs/.data/representative-benchmark-matrix-v34.json since 650efda. Check whether item 0 changes it before fixing separately.

  • 0d. #1044 — fixed in 9d435e3: the algorithmic-growth self-test now completes within the unchanged default zig-js evaluator budget; both zig-js and JSC Home runs pass, and the aggregate benchmark harness passes all 19 validators. Redundant successful-artifact rebuilds and validation-only formatting traversals were removed while public rendering remains fail-closed. Matrix V48 re-pins only the validator; evidence schemas, validation rules, scored workloads, and publication acceptance are unchanged.

  • 1. #932 — fixed in 2688dc9: 63x at depth 4000, 13,814 VM-forced tests identical, 7,317 ReleaseSafe tests with the invariant asserted. — nested destructuring catch parameters are quadratic inside function bodies (x3.9/doubling, 1.87 s at depth 4000). Root cause unknown. Done: hot frames located by sample on a depth-4000 run, fix landed, before/after growth curve posted, 0 test262 regressions on the affected subtrees.

  • 1b. #935 — fixed in d91ae16: 10 walkers iterate chains; 57/96 crashing runs -> 0/96; 13,773 VM-witness tests identical. — flat expression chains segfault where node runs them: a 2,000-substitution template literal crashes tree mode. Valid, realistic code, so first. Done: left spines walked iteratively in the tree-walker and compiler, preserving evaluation and coercion order; binary, template, logical and comma chains measured and pinned against node; no growth regression on the #926/#928 rows.

  • 1c. #936 — fixed in 480516b: 18 shapes x 3 modes SIGSEGV -> RangeError; five paths that relabelled the error fixed; 24,403 tests identical; frontend benchmarks 0.990-1.022. — parser and post-parse analysis overflow on deep nesting. Done: RangeError via stack_scan.nearLimit at the mapped chokepoints, a subprocess test in test-runtime, and a moderate valid depth still parsing.

  • 1d. #937 — fixed in b895723: 32/384 compile-only cells SIGSEGV -> RangeError; module import surfacing fixed; 22,316 witness and 24,067 diag tests identical; compile benchmarks 0.984-1.022. — bytecode compiler and its pre-scans overflow on deep nesting. Done: guarded dispatchers and pre-scans, surfaced as RangeError rather than a tree-walker fallback, and the #926/#928/#932 measurements re-run.

  • 1e. #938 — fixed in 81b49b1: 39 of 232 eval-matrix cells SIGSEGV -> RangeError; deep if-chain 139 -> 2047 levels in both modes; test/language 23,648, test/annexB 1,086, test/built-ins 23,126 and 12 vm-witness subtrees identical (0 flips); committed layout measures 0.995x default and 1.019x tree against a 1.020x no-op control. — tree-walker recursion inside one call escapes stackGuard. Done: hoisting, Annex B scan, destructuring and AST copying charged to stackGuard, including strict and sloppy block nesting, plus the host print path.

  • 1h. #940 — fixed in f2b655d: 8 crashing shapes now answer or raise RangeError, matching node; deep bound call returns 7 as node does; 8 subtrees (19,554 tests) and 3 vm-witness subtrees identical, 0 flips; quadratic bound-name memory split out as #942. — deep runtime chains crash with SIGSEGV where node throws RangeError or runs them: flat(Infinity) on deep or self-referential arrays, trap-less proxy chains (getPrototypeOf, isExtensible, defineProperty, Object.prototype.toString), and bound-function chains for calls and instanceof. Done: each shape throws the catchable RangeError, or runs where node does (bound calls). Iterate chains that have no user-visible step between links; check the stack where user code can run.

  • 1i. #941 — fixed in 8f0947d: the 10-value probe now matches the JSC oracle exactly, including JSC's check-before-separator-coercion ordering (1 coercion, V8 does 2) and recovery after a throwing toString; 6 subtrees (17,264 tests) identical, 0 flips. — Array.prototype.join throws RangeError on cyclic arrays where JavaScriptCore and V8 return "1,". Done: re-entered arrays join as empty in join, toString and toLocaleString, with the in-progress set popped on every exit path including a throw, and pinned against the JSC oracle.

  • 1j. Module top-level await whose operand throws — fixed in 2dce041: reproduced with two dynamic imports through the corpus runner's module host — the first rejected with SyntaxError: Cannot parse module and the second never settled; both now reject with the thrown Error: operand, matching node. 4 module subtrees (1,670 tests) identical, 0 flips, pinned by a new module test. — suspendModuleAwait (src/context.zig) evaluates the operand with try, outside finishModuleError, so a throw there may leave the module "evaluating" for later importers. Found by the #938 review, which traced the same gap for hoisting (that one is fixed with #938). Reproduce with two dynamic imports of such a module before fixing.

  • 1f. zig-utils/zig-regex#23 — fixed in zig-regex 3e8654e: 14 walkers over pattern spines made iterative; flat patterns go from crashing at ~26,000 elements to compiling at 1,000,000; 3,352 zig-js RegExp/String/literal tests identical, 0 flips; nesting depth split out as zig-regex#25 and the remaining recursive walkers as zig-regex#26. — long regex patterns crash compile and backtracking. Sibling repository; flat patterns must work, since node runs them.

  • 1g. #939 — fixed in b1acc36: head form decided by an exact token scan, so each head parses once; member 411 MB -> 11 MB at depth 18 and depth 200 now parses in 0.01 s; 14,609 tests across 6 subtrees identical, 0 flips; depth 24 pinned. — valid nested classic for heads with function bodies parse in exponential time and memory (431 MB at depth 18; node: 0.2 ms at depth 200). Done: one parse per head, pinned at a depth that exhausts memory without the fix.

  • 2. Function-body variants of the frozen growth workloads. — done in ffc4e58: ten rows frozen (…_in_function_… for all eight #926/#928 shapes, plus a catch_destructure family for #932's shape at both scopes). All 18 rows grow x2 per doubling, so no new quadratic was filed; the one x6.24 reading was 3-sample noise (x2.02 at nine samples). The #932 shape costs a flat ~4x more inside a function body without changing slope. The #926 and #928 rows parse top-level scripts only, so they are blind to effects confined to function bodies — #932 is one, and plain nested blocks inside a function also steepen (x2.44 then x4.05). Confirmed separate from #932: the #932 fix leaves that shape unchanged (155.5 ms -> 164.2 ms at depth 4000), so it has its own cause. Done: each #926/#928 shape and the #932 shape wrapped in a function, frozen in bench/frontend_parse.zig, measured, and any new quadratic filed with evidence rather than assumed.

  • 3. #930 family 4 — fixed in 3e792ee: strict block and switch duplicate functions now reject under the owning FunctionNode strictness; 8,775 affected test262 cases identical, 0 flips.

  • 4. #930 family 3 — fixed in 0a2c1cb: labelled Annex B functions now contribute var or lexical names according to their containing statement list; 574 affected test262 cases identical, 0 flips.

  • 5. #930 family 2 — fixed in ada64ff: both declaration walkers now traverse with bodies, including #931 catch conflicts; 1,871 affected test262 cases identical, 0 flips.

  • 6. #930 family 1 — fixed in bb48fb2: every function body is checked once as it is parsed, and the scope walk no longer descends into functions; 15 expression-nested positions now reject like node (plus the #931 catch, with and strict-duplicate shapes, pinned in f2bafeb); all 18 #926/#928 rows incl. the item-2 function-body rows stay x2 per doubling (median of 18, interleaved); 22,984 tests across 9 subtrees identical, 0 flips. #930 closed. — function bodies nested in expressions are never walked. The largest item and perf-sensitive: widening the walk is exactly how #928's quadratic could return. Done: fixed, and the #928 frozen rows plus the item-2 function-body rows re-measured with no growth regression.

  • 7. #927 — measured (no fix, per this item): siblings quadratic (1,186 MB at N=4096 vs a 61 MB control); deep nesting cubic, worse than predicted (411 MB at N=256, >3 GB at 512). A throwaway build proved the cubic memory is the site-2 clone retained by deinit(self.arena) (426 -> 43 MB when freed) but time stays cubic from repeated cloning, so the fix is the flattening design question, not an allocator swap. Evidence on #927. — runtime private-environment flattening. Measure first; no fix until the curve is confirmed. The #926 undo-log trick may apply, since the question there is stack-disciplined even though the map carries a payload.

  • 8. checkForHeadVarConflict — fixed in f4100cc: frozen representative_frontend_for_head_* rows confirmed x4.04/x4.29 per doubling; heads now check by declaration order against one per-var-scope record (34.2 ms -> 0.61 ms at 1,024 nested heads, var control unchanged). Kept at parse time because the scope walk still misses expression-nested functions (item 6). Matrix v37; 3,716 tests identical, 0 flips. — re-collects a for body's var names per lexical for head, the same shape as #928. Freeze a workload and measure before filing or fixing.

  • 8b. Deep parentheses, object literals and for await heads take 5-13 s to fail at 200,000 levels while arrays take 0.0 s — fixed in 320e211: confirmed as the arrow lookahead scanning each ( to its match (9.7 s to refuse 200,000 parens in ReleaseFast). Past 64 levels the parser now indexes every paren once; at 64,000 levels parens 0.96 s -> 0.09 s and for await 0.57 s -> 0.07 s (Debug), matching the bracket control. Object literals were already flat in expression position. 2,223 tests across 6 subtrees identical, 0 flips. (#936 gate). Probably a lookahead rescanning each (. Measure growth at depths that parse before filing.

  • 8c. representative_frontend_compile_binding_inventory_destructure_4096 fails with error: InvalidProgram on main. — fixed in ffc4e58: lowering traced to a2d301a (#730); the row now pins the lowering's local budget (7 iterator temporaries per declaration) instead of an unsupported_lowering rejection, with matrix v36 re-pinning the runner. Neighbour rows byte-identical; matrix self-test 75/75. Its validator (bench/frontend_parse.zig, validateBindingInventoryCompileProgram) still expects the compiler to reject lexical destructuring declarations with unsupported_lowering, but they now compile. Find the commit that added the lowering, then update the frozen expectation, which needs another matrix version because v35 pins the runner.

  • 9. home-tool private-accessor oracle defect — scoped in #943: wider than accessors — every reference to a private method or accessor (instance or static, 14 syntactic positions) is a JSC SyntaxError under home-tool, while private fields work. Cause: home-tool's ts_emit writes those member names as string keys at esnext, so the private name is never declared (and unreferenced ones become public "#m" properties). The fix belongs in home-lang/home. — home-tool rejects valid get #x()/set #x() references. Scope how wide the blind spot is, since it is the JSC oracle for error-parity work.

Blocked on a human — do not loop on these

  • HOME_RUN_MAX_MB: a cold src/ relink peaks near 3.1 GB, so zig build test-parallel cannot complete under the 3072 MB cap. Use the per-module test root (~350 MB) in the meantime; raising the cap is the user's call.
  • Maximum string length (from #970). zig-js caps a constructed string at 2^26 bytes (64 MiB, max_string_bytes). That is a policy limit from 7bedeab, not a representation limit: string cells hold longer strings. JavaScriptCore allows 2^31-1, V8 about 2^29. repeat, +, concat, template literals and, since 7bda007, padStart/padEnd/join separators enforce it. join of large inputs, String.raw and JSON.stringify do not, so output above 64 MiB works through some builtins and not others. Raising the cap toward JSC's would suit Home; enforcing it everywhere would reject outputs that work today. The choice changes behaviour embedders see, so it needs a decision.

Definition of done, for every item

  1. Reproduce or measure before changing code. A growth claim needs the owned runner and a control that separates the plausible causes.
  2. For any behaviour change: pin cases confirmed against node first, then run test262 on the affected subtrees with a before binary built from the pre-change parser and the after binary — 0 fail on both, not just matching counts.
  3. Commit and push with measured numbers in the body, then post the evidence on the item's issue and tick it here.
8 days ago

Queue items 0, 0b and 0c: CI evidence

These items have no issues of their own, so the evidence is here. CI run 35217156597 on 7c2e660 (the first push carrying all three fixes plus 20b15b5): 52 jobs passed, 1 failed.

itemcommitbeforeafter
0: matrix v35 re-pins bench/frontend_parse.zig6cfaa3fthe harness threw at import time: "V34 measured-boundary native runner changed without a matrix version bump"the representative-matrix self-test passes
0b: template-token allocation replay7c2e660NondeterministicMemoryUsage in the unit shard, TSan unit shard and focused-engine-testsall three jobs pass
0c: dependency audit classifies v34 as a callercea5f24"tool 'tools/exact-parent-regression.ts' has unclassified caller/reference 'docs/.data/representative-benchmark-matrix-v34.json'"the audit passes
Home private-value fixture compile20b15b5home_private_value_shims.zig:1409:28: error: missing struct field: tagged_ptrprivate-abi-boundary passes

Cause of 0b. checkAllAllocationFailures needs the same allocation count on every run. Token storage grows by remapping, and whether that succeeds in place depends on what the allocator has free next to it. Across 24 consecutive parses the counts went 3 4 3 3 3 3 4 …; with in-place growth refused, every parse made 6.

Remaining failure. benchmark-comparison-harness now gets past the matrix check and fails later, in algorithmic-growth, with home-tool (zig-js): RangeError: evaluation step budget exceeded. The job already failed with that error on Sep 13 and 14, before the frontend runner changed. It is now item 0d.

8 days ago

Taking 8 and 8b (both measure-first) so we don't overlap — I'm working the queue from this end while items 3–5 were landed top-down in 3e792ee, 0a2c1cb and ada64ff. For the record: I had fixed 3 and 4 locally before those arrived; that duplicate was discarded unpushed, and its tests were a subset of the ones that landed.

8 days ago

8b, 8 and 9 are done (320e211, f4100cc, #943). Taking 7 (#927, measure first) next, and leaving 6 (#930 family 1) alone since it continues the #930 work already in progress.

8 days ago

Taking 6 (#930 family 1) as well, now that 7 is measured (#927). If you're already partway into family 1, say so here and I'll stop.

8 days ago

6 is done (bb48fb2, pins in f2bafeb, evidence on #930, which is now closed). With that, every item in the queue is ticked; only the "Blocked on a human" list remains.

Correction to the timing statistic in items 2 and 8. The helper I used to take medians split multi-line runner output with zsh's ${(z)…}. That inserts a ; token between samples, sort -n orders those as 0, and so the "median" it returned was really the minimum sample. That affects:

  • ffc4e58 (item 2): "median of 3 samples per width", and the "nine samples" recheck of private_nested_single_inner, were minimum of 3 and minimum of 9.
  • f4100cc (item 8): "runner timing, median of 5" was minimum of 5.

I re-measured both with a true median: 18 samples a side, the two binaries interleaved A B B A. The conclusions hold.

row (item 8, 128 / 256 / 512 / 1024, µs)beforexafterx
for_head_lexical_of415 / 1675 / 7968 / 342134.2964 / 128 / 241 / 4852.01
for_head_lexical_classic421 / 1732 / 8052 / 343324.2674 / 152 / 310 / 5961.92
for_head_var (control)48 / 99 / 195 / 3962.0350 / 101 / 195 / 3932.02

For item 2, bb48fb2's before column holds the same 18 rows at a true median on f4100cc, and all of them come out at x1.97–x2.14 per doubling. So "no new quadratic" stands.

The x6.24 reading on private_nested_single_inner in item 2, and a 636 µs reading on the same row during item 6, both look like efficiency-core scheduling rather than noise in the parser. Under taskpolicy -b that row runs 600–770 µs (median ~646), against 270–387 µs normally. Interleaving the two binaries keeps such a spike from landing on only one side unnoticed; a single-binary series can't show that.

8 days ago

2026-09-23 continuation: landed work and qualification

Pre-change CI comparison, checked 2026-09-24: run 35833052911, exact baseline bcc32b717ee2765d2d1143ff0f697390eda642e7, failed unit shard 0 on the same parallel_js: concurrent Promise attachment and resolving capability publication are exact test (Throw), with the same 565 passed / 59 skipped / 1 failed / 0 leaked totals. That specific failure predates this series. This comparison does not establish the causes of all other failed CI jobs.

The requested two-hour minimum has been met. No runtime-coordination code or human-blocked policy item in this queue was changed.

  • #976 / #977 verified, not reimplemented: preserved Date/collation probes in three time zones and both engine modes, plus 12 controlled New York DST probes. Date receipt, collation receipt. Four legacy Date parsing forms and non-UTC display-name differences remain documented limitations.
  • #987 fixed in b66eb53b: legitimate compiler-pressure/CLDR tools classified; dependency audit passes with 16 edges, 0 migration targets, 65 tools. Documentation gates pass.
  • One zig-utils/zig-regex#30 gap fixed in regex 601edb49, integrated by #988 / 9da999ae. Preserved differential: 308 -> 141 mismatches, 167 corrected IDs, zero new mismatch IDs, 800 unchanged refusals; exact same result in Debug and explicit ReleaseSafe. The remaining 141 mismatches keep the dependency issue open.
  • Verification: 992 targeted test262 cases identical and passing before/after; Debug regex core 369/369; direct ReleaseSafe root 189/189; existing ReleaseSafe regression fixture 82/82 before/after; full focused runtime 28/28 before and 29/29 after (new case added). Qualification receipt.
  • Measured cost evidence frozen in c47efccf, published with raw samples and a scoped README table in 0d0bb75c: report. Four library-only controls; no engine/JSC speedup or general overhead claim.

All five commits are pushed directly to their repositories' origin/main, authored and committed as the configured glennmichael123 <gtorregosa@gmail.com>, without trailers. Heavy work remains serialized through the shared lock with an explicit 3072 MB ceiling; no private-lock bypass, unrelated cleanup, or history rewrite.

Extended qualification concluded with a resource limitation, September 24: all 1,879 RegExp cases were attempted on each frozen binary. Exactly 1,878 complete and agree (1,686 positive / 192 expected-negative); one remains incomplete under the unchanged 3072 MB guard on both baseline and candidate. Isolated character-class-escape-non-whitespace.js reproduces the cap breach, tracked in #989, not counted as a pass or skip. Together with 339 String and 238 regex-literal cases, the disjoint total is 2,455 unique passing cases plus 1 resource-incomplete case, out of 2,456 attempted per build. No full-RegExp/full-corpus pass is claimed. Both raw-result auditors succeeded; the summary explicitly says allCasesAttempted: true, complete: false. All interrupted logs remain preserved; earlier unexplained SIGTERMs and supervisor loss are distinct from the later proven memory-guard exits. The final driver exited and no qualification workers survived. No weakened tests, cap increase, or private-lock bypass was used. Fixing the separately reproduced resource issue requires follow-up; this goal's landed capture-correctness change does not resolve it.

Hosted CI now terminal: run 35866558170, exact head 0d0bb75c, completed with failure. Dependency-audit, docs, and test262-parallel passed. The benchmark harness reports RangeError: evaluation step budget exceeded, matching this queue's human-blocked budget decision. Unit shard 0 reports 565 passed / 59 skipped / 1 failed / 0 leaked; its failure is context.test.parallel_js: concurrent Promise attachment and resolving capability publication are exact (Throw). Other concurrency checks also failed; no complete causality audit of those failures has been performed. This is not a green-CI claim or a claim that every failure is unrelated to the series.

8 days ago

Queue item 0d is complete in 9d435e3 / #1044.

The algorithmic-growth self-test now fits the unchanged default zig-js evaluator budget by removing redundant traversals inside the test itself. Public artifact validation and rendering remain fail-closed; no embedding budget or execution-time policy changed. The same contract passes under JSC, and the aggregate benchmark harness passes 19/19 validators with the current Home/zig-js host.

Matrix V48 versions only the validator hash. Evidence schemas, validation rules, scored workloads, timing boundaries, and publication acceptance remain unchanged.

Sign in to comment on this issue.