ReviewOS

zig-utils/zig-js

JSON.stringify: bypass generic Get for ordinary own data fields

#1001
Closed glennmichael123 opened this 9 days ago ·
9 days ago

Problem

A symbolized execution of the frozen representative JSON workload attributes the largest JSON.stringify subtree to ordinary named-property lookup rather than output writing or parsing. The diagnostic sample recorded 1,198 samples below jsonStringify, including 672 in Shape.lookupState, 307 in Interpreter.getPropertyWithReceiverFound, 219 in byte equality, and 160 in Object.namedOwnPropertySnapshot. This profile is a causal locator only; it is not publication timing because the symbolized runner used the arena diagnostic configuration.

SerializeJSONObject already snapshots its property-key list before reading values, but every selected key then takes the complete generic [[Get]] pipeline. On the common direct ordinary-object/data-property case, the current descriptor can be read with Object.namedOwnPropertySnapshot and passed directly into serialization.

Required semantic boundary

  • Preserve ECMA-262 SerializeJSONProperty live value reads.
  • If the current own descriptor is a data property, use that current value.
  • If it became an accessor or disappeared, fall back to full [[Get]] so getters and inherited replacements remain observable.
  • Keep Proxy, module namespace, typed-array, host-class, dense-element, and other exotic paths on the generic implementation.
  • Preserve replacer PropertyList behavior, toJSON/replacer ordering, abrupt completion, stack-depth/OOM behavior, and no-GIL synchronization.
  • Add witnesses where an earlier getter changes a later data value, converts it to an accessor, or deletes it to expose an inherited value.

Evidence and completion

Measure exact-parent alternating samples on both frozen representative_json rows with exact checksums and anti-specialization controls. Qualify focused unit tests, test262 JSON, required VM/tree agreement, deep/cycle/OOM paths, and the shared no-GIL TSan witness. Land only if the change is semantics-preserving and measurably improves the frozen row; otherwise document and reject it.

Parent: #473.

9 days ago

Landed on main in bafe31a8. SerializeJSONObject now reads a current own data descriptor directly only for direct ordinary objects. Accessors and misses return to full [[Get]], and Proxy/module namespace/typed-array/host-class/dense-element paths remain generic.

Seven alternating exact-parent pairs on the frozen 2,200 x 90 rows, real GC and stripped ReleaseFast:

  • representative_json: retired instructions 7,247,303,668 -> 7,028,322,002 (0.9698x); CPU median 488.667 ms -> 474.537 ms (0.9711x).
  • representative_json_variant: retired instructions 7,230,962,402 -> 7,036,168,223 (0.9731x); CPU median 496.262 ms -> 472.495 ms (0.9521x).
  • Both checksums remain exactly 324952086; thermal state was 0 -> 0. Host load was high, so elapsed time is diagnostic-only and is not being published as stable evidence.

Qualification: focused runtime 1/1; direct context regression 2/2; JSON stringify semantic/OOM/deep-cycle/GC batch 12/12; JSON Test262 165 pass / 0 fail; required-bytecode agreement 165 agree / 0 diverge; no-GIL TSan witness 2/2 with no sanitizer report. Guarded builds peaked at 2,080 MB or less.

Sign in to comment on this issue.