ReviewOS

zig-utils/zig-js

Compiler: lower for-in through an owned bytecode enumerator

#725
Closed chris opened this 9 days ago ·
9 days ago

Parents: #465, #498 Related: #491, #616, #618, #706, #721, #724

Problem

Program, plain-function, and async bytecode admission reject for-in statements even though generators have a partial lowering through enum_keys. That generator path materializes a JavaScript Array and drives it through the ordinary iterator protocol. A patched Array iterator can therefore introduce observable calls and synthetic IteratorClose behavior that for-in must not perform, and keys deleted before visitation are not rechecked. Extending that path to more tiers would broaden a semantic divergence rather than remove one.

Scope

  • Add an owned bytecode enumeration path shared by program, plain, generator, and async bodies.
  • Evaluate the RHS once, box primitives once, snapshot the ordered candidate string-key set without exposing it, advance one candidate per VM dispatch without invoking user-modifiable iterator methods, and recheck the candidate against the original target before binding.
  • Keep enumerator state activation-local and GC-scanned across ordinary execution, suspension, exceptions, debugger/watchdog checkpoints, native-tier fallback, and labeled abrupt control flow.
  • Preserve null/undefined no-op behavior, prototype shadowing, enumerable ordering, Symbol exclusion, Proxy ownKeys/descriptor/has effects, deletion before visitation, and the absence of synthetic IteratorClose.
  • Preserve var/let/const, assignment/member/destructuring targets, per-iteration closure cells, defaults, and computed keys. Program patterns and member references must use #706 activation scratch; function, generator, and async bodies retain frame/environment activation temps.
  • Keep unsupported for (using ... of ...) disposal on its causal path.

No-workaround rules

  • No source rewriting, user-visible helper iterator, Array iterator dependence, duplicated RHS/key evaluation, tree-walker callback for the owned enumeration path, hidden program globals, result cache, benchmark recognizer, skipped Proxy/descriptor checks, or stack/queue/job runtime changes.

Acceptance

  • Program/plain/generator/async admission uses one owned for-in operation family with exact operand-stack and root accounting.
  • Forced tree-walker and required-bytecode witnesses match key order, effects, exceptions, labels, deletion, primitives, Proxies, native patterns, captures, and suspension.
  • Program identifier, member, and destructuring bindings prove per-iteration and nested-evaluation isolation through #706 scratch, without synthesized globals.
  • Focused compiler/frontend, full units, applicable TSan/GC relocation, and exact nonzero Test262 for-in accounting pass with zero regressions.
  • Parent issues and the landing commit are cross-linked with quantified evidence.
9 days ago

Implementation checkpoint after rebasing onto #706: the owned enumerator now keeps {target, private key snapshot, cursor} on the GC-scanned operand stack, advances one candidate per VM dispatch, and uses a completion-aware cleanup opcode for return/throw/outer labels. Program member and native destructuring bindings use #706 scratch; no hidden globals or Array iterator path remain. Focused tree-walker/required-bytecode, moving-GC, frontend, optimizer side-exit, and TSan witnesses are green. Full units and exact Test262 accounting remain before landing.

9 days ago

Landed on main in 44371d94. Final evidence: focused semantic/compiler 3/3, frontend 1/1, optimizer side-exit 1/1, focused TSan 3/3, moving GC 1/1, and full units 1,850 passed / 1 skipped / 0 failed / 0 leaked. Exact Test262 test/language/statements/for-in accounting was 115/115 on parent and candidate: 0 flips, 0 regressions.

Sign in to comment on this issue.