ReviewOS

zig-utils/zig-js

JIT: specialize inherited properties with concurrent watchpoints

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

Parent: #456 Unblocked: #429 Roadmap: #146, #134

Goal

Implement optimizer-visible prototype-chain property specialization and mutation watchpoints so the exact PR-249 GC-wait-vs-Class-A witnesses execute a real inherited-property assumption, jettison it through the shared JIT/GC conductor, and resume correctly.

Acceptance

  • Profile the receiver and exact prototype holder for inherited named data-property reads without source recognition.
  • Publish immutable receiver/chain/holder shape guards and the holder slot in optimizer-owned metadata.
  • Execute the guarded inherited read natively; accessors, proxies, dictionary/exotic objects, chain changes, and misses take the canonical path without replay.
  • Register shape/prototype dependencies and fire Class-A invalidation only for mutations that can invalidate a published assumption.
  • Preserve exact roots, steps, deoptimization, moving-GC relocation, generation checks, and concurrent mutation semantics.
  • Add native-on/off differentials for monomorphic and polymorphic inherited reads, shadowing, replacement, deletion, prototype replacement, accessors, and GC.
  • Promote cve/mc-safe-gcwait-vs-classa-stop.js and cve/mc-safe-gcwait-vs-classa-stop-noropevariant.js only when normal and TSan runs prove both cooperative collection and Class-A invalidation occurred.
  • Pass focused normal/TSan gates and the batched JIT/no-GIL/full-unit release gates required by the parents.

Completion

Implemented by 16b2a1a2, a757cd1a, and 2bdb47be; promoted by 3f2fe88b. The exact fixtures each published 18 artifacts, fired 13 invalidations, and completed one cooperative collection in normal and TSan runs. Final parent evidence is recorded in docs/.data/jit-concurrency-2026-07-22.md by ead94ad8.

9 days ago

Complete through 16b2a1a2 → a757cd1a → 2bdb47be → 3f2fe88b.

Inherited one-hop specialization is real: the interpreter profiles the receiver and the exact prototype holder (InheritedPropertyObservation), the optimizer publishes an immutable receiver-shape/holder-shape/holder-slot triple in artifact-owned NativePropertyCache metadata, and AArch64 executes the guarded read inline. Shadowing, prototype replacement, a missing holder property, a null prototype, attribute-mapped holders, and exotic array holders all yield to the canonical path without replay; an in-place rewrite of the holder's own slot is still tracked.

Class-A firing is scoped rather than unconditional: the owner summarizes the shape tokens of its published property caches into a bit filter and a mutation whose receiver shape is absent skips the stop. The filter only over-approximates — collisions, unknown shapes, shapeless receivers, and any site with no object in hand keep the unconditional behavior — so no guarded shape can be missed.

One real gap surfaced and was fixed rather than worked around: a get_prop node types as .other, so o.y + 1 refused Number specialization and left the whole function uncompiled — nothing published, nothing to invalidate. 2bdb47be routes exactly .get_prop-operand binaries through the runtime-operation ABI, with graph-side and lowering-side decisions sharing one binaryNeedsRuntimeOperands helper called on unresolved operand ids so they cannot drift. Keying on may_have_effect was rejected as it would have discarded specialization for all argument arithmetic.

Promotion evidence for the two pinned witnesses, identical in normal and TSan runs with no race reported:

cve/mc-safe-gcwait-vs-classa-stop.js                publications=18 invalidations=13 collections=1
cve/mc-safe-gcwait-vs-classa-stop-noropevariant.js  publications=18 invalidations=13 collections=1

13 invalidations against the witness's 12 mutation rounds plus a cooperative collection is the "both cooperative collection and Class-A invalidation occurred" gate this issue set, not a green assertion. The counter is a lifetime optimizing-tier publication count that survives clear, so a compile-then-jettison still counts. Both cases also pass serialized (~121 s / ~126 s versus ~1 s no-GIL) but join the no-GIL allowlist matching their own --useThreadGILOffUnsafe=1 header.

Gates: 172/172 focused JIT, 142/142 optimizer, 7/7 inherited, 5/5 Class-A, inventory drift check and 7/7 audit self-tests. Inventory moves 243 → 245 promoted, 10 → 8 blocked of the same 259 executables; the retired shared-concurrent-gc dependency is dropped and the JSON is regenerated from --print-inventory rather than hand-edited.

Sign in to comment on this issue.