ReviewOS

zig-utils/zig-js

Intl: reuse immutable DisplayNames state

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

Parent: #475 Related: #460, #461, #480, #582

Problem

Intl.DisplayNames.prototype.of re-reads the formatter's internal \0opts object (type, fallback, languageDisplay, and style) plus \0locale through ordinary named-property lookup on every steady call. These values are fully normalized during construction and immutable by specification. Valid language/currency canonicalization also allocates multiple arena fragments per call.

The benchmark boundary uses only public Intl.DisplayNames APIs and byte-identical system-JSC outputs across English language, region, script, and currency rows. The distinct locale-data/composition gaps discovered by the differential are tracked in #582 and are excluded explicitly, not hidden by an order-insensitive checksum.

Scope

  • Freeze a steady of() row with construction outside the timer, equal work, exact string hashing, and language/region/script/currency option controls.
  • Publish one formatter-owned immutable native resolved record after all user option getters/coercions complete.
  • Consume that record in of() and resolvedOptions() without repeated generic internal option traversal.
  • Bound canonicalization scratch and retain only bytes whose lifetime requires object ownership.
  • Preserve validation, aliasing, case canonicalization, fallback, missing-name behavior, and exact observable coercion order.
  • Keep #582's generated locale-data work separate.

No-workaround rules

  • No input/code/source recognition, benchmark-only path, system ICU/JSC dependency, unbounded cache, skipped validation, or locale omission presented as speed.
  • Do not modify stacks, queues, jobs, Promise/microtask, or Worker lifecycle code.

Acceptance

  • Committed public benchmark control agrees byte-for-byte with system JSC and exceeds the timing floor.
  • Exact-parent repeated samples preserve the checksum and attribute wall/CPU, allocation, GC, retained memory, property locks, and RSS.
  • OOM publication rollback, bounded heap, moving/concurrent GC, shared-realm read-only use, no-GIL, and TSan pass.
  • DisplayNames + full Intl402 test262 and full units have no regressions.
  • #582 remains visible for excluded semantic/data differences.
9 days ago

Benchmark boundary landed on main in 6f1e25f8 (bench(intl): isolate DisplayNames state (#583)).

The public steady row covers English language, region, script, and currency formatters, keeps construction/fixed valid codes outside the timer, and hashes every returned string in order. At 100 jobs, zig-js and system JSC agree on checksum 139703394; JSC takes about 624.0 ms and the exact parent takes about 3.880 s, both above the timing floor.

Parent attribution across the warmed invocation records 31,977,215 object property-lock acquisitions, 17,223,285 backing allocations, 2,445,001,837 cumulative backing bytes, 512,032,770 current backing bytes, 9,019,255 GC cells, and 310 collections. These give the immutable-state and scratch-ownership change an exact causal boundary.

The broader locale-data/composition divergences remain separately visible in #582. Benchmark-comparison structural validation is green. This issue remains open for the implementation and exact-parent gates.

9 days ago

Completed on main via benchmark boundary 6f1e25f8 and implementation 925075c8.

DisplayNames construction now publishes one formatter-owned immutable native record after all observable option reads/coercions. of() and resolvedOptions() consume closed enums plus an owned locale directly; forged hidden properties no longer satisfy the internal brand. Common three-byte currency and primary-language canonicalization uses bounded stack storage, while compound language tags use one invocation-local reclaimable arena.

Exact-parent 100-job attribution, frozen checksum 139703394:

  • Object property-lock acquisitions: 31,977,215 -> 25,423,573 (-20.49%)
  • Cumulative backing bytes: 2,445,001,837 -> 2,276,065,815 (-6.91%)
  • Current backing bytes: 512,032,770 -> 343,079,996 (-33.00%)
  • Peak backing bytes: 515,317,420 -> 346,365,175 (-32.79%)
  • Retained RSS: 369,770,496 -> 340,197,376 (-8.00%)
  • Backing allocations: 17,223,285 -> 17,223,266; GC cells: 9,019,255 -> 9,019,221; collections: 310 -> 310

Seven order-balanced diagnostic medians improve 4.333 s -> 4.125 s (-4.80%). The samples were noisy, so the deterministic counter/memory result is the causal claim; no broad throughput claim is made. System JSC and zig-js retain exact checksum equality, and the reference row remains above the timing floor.

Validation:

  • DisplayNames test262: 57 -> 57, zero failures/flips
  • Full Intl402: 3,341 -> 3,341, zero failures
  • Focused normal and suppression-free TSan: 4/4, including OOM publication, immutable option observation, forged-brand rejection, four concurrent native readers, and a 32 MiB heap-limit compound-tag stress case
  • Full units: 1,680 passed, 1 skipped, 0 failed, 0 leaked
  • Benchmark-comparison structural self-test: green

The public language-pattern and non-English locale-data differences remain explicitly tracked in #582. No stacks, queues, jobs, Promise/microtask, or Worker-lifecycle code changed.

Sign in to comment on this issue.