ReviewOS

zig-utils/zig-js

Intl Collator: reuse normalized compare state

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

Parents: #475, #460 Related: #528, #577, #581

Problem

Each bound Intl.Collator.prototype.compare call correctly coerces its two arguments, but then rebuilds instance configuration by allocating a fresh locale string cell, running CanonicalizeLocaleList again, boxing/re-reading the internal options object through generic property operations, and reconstructing CollatorOptions. The constructor already validated and normalized that immutable per-instance state.

Sorting attacker-controlled arrays multiplies this configuration work by the comparison count, adding allocation, GC, and lock pressure unrelated to collation itself. The representative matrix has no dedicated direct-compare or sort consumer row, so the scored boundary must be committed first.

Scope

  • Add dependency-free steady direct-compare and bound-comparator sort rows across default, search, numeric, case-first, sensitivity, punctuation, collation, locale-extension, ASCII, accented, and Unicode inputs.
  • Keep Collator and fixed input fixture construction outside the steady boundary. Normalize compare results to sign and hash every sorted string so zig-js and system JSC execute identical public work.
  • Attribute exact-parent allocations/bytes, GC cells/collections, property/environment locks, timing, and process memory before changing the runtime.
  • Reuse the constructor-normalized immutable locale/options state for bound compare calls without allocating or rerunning locale-list/option abstract operations.
  • Preserve per-call left-to-right ToString coercion and abrupt completion, bound-function identity, locale/extension resolution, all sensitivity/numeric/case/punctuation/collation behavior, moving roots, and shared-realm no-GIL safety.

Security and no-workaround rules

Strings, array length/order, comparison count, locale/options, accessors, and coercion side effects are attacker-controlled. The implementation must remove repeated instance-state work without caching comparison results or keys globally, recognizing benchmark source/names, skipping input coercion/comparisons, changing sort work, omitting supported locale behavior, weakening OOM/GC/watchdog accounting, or adding unbounded mutable state.

Acceptance

  • Committed direct-compare and sort rows meet the timing protocol and have stable non-zero sign/content checksums identical to system JSC.
  • Exact-parent evidence attributes and removes repeated locale/options allocation and generic instance-state work, without reducing scored comparisons or increasing collections/locks.
  • Default/search/numeric/caseFirst/sensitivity/ignorePunctuation/collation/extension behavior, coercion/throws, and bound compare identity remain exact.
  • Focused OOM, bounded-heap adversarial sort, moving nursery, shared-realm no-GIL, and suppression-free TSan coverage pass.
  • The complete test/intl402/Collator subtree, full Intl402, and full sharded units have no regressions.

Exclusions

No Promise, microtask, Worker, queue, job-lifecycle, stack, or user-owned documentation changes.

9 days ago

Benchmark boundary landed in bench(intl): isolate Collator compare state (#580).

At 100 jobs, zig-js and system JSC agree exactly:

  • direct bound compare: checksum 925849600; JSC 57.6 ms, zig-js 2.160 s; pre-change 6,471,191 backing allocations / 2,795,216,014 cumulative bytes / 1,712,231,497 retained backing bytes / 2,465,554 GC cells / 51 collections / 1,258,078,208 retained RSS
  • positional stable sort: checksum 818968495; JSC 62.0 ms, zig-js 1.520 s; pre-change 9,646,364 allocations / 2,173,543,745 bytes / 763,547,020 retained backing bytes / 4,099,955 cells / 120 collections / 621,166,592 retained RSS

The fixtures cover eight Collator configurations with sign-normalized direct results and order-sensitive full sorted output. Per-configuration differential probes deliberately exclude the separately observed German phonebook Öl/Oel and Thai default-sensitivity disagreements rather than masking them in the performance aggregate. The complete benchmark-comparison gate passes; runtime work starts only after this commit.

9 days ago

Filed #581 from the public differential used to freeze this issue's workloads. It tracks the deliberately excluded German phonebook and Thai default-ignorePunctuation sign divergences with exact zig-js/JSC repros. #580 remains scoped to immutable compare state and invocation-local key ownership; its scored pairs/configurations are sign-equivalent and do not hide #581.

9 days ago

Completed on main:

  • 56bf8586 — bench(intl): isolate Collator compare state (#580)
  • 9f1fbf48 — perf(intl): reuse Collator compare state (#580)
  • 92f9bc4a — test(intl): exercise shared Collator state (#580)

The constructor now publishes normalized Collator configuration in an immutable, GC-finalized native sidecar. Bound compare calls borrow that closed state without allocating a locale cell, rerunning locale-list canonicalization, or reading hidden properties. Unicode normalization/case-folding keys—including recursive numeric segments—live in one invocation-local arena backed by the Context's freeable, budget-accounted scratch allocator.

Exact-parent attribution, 100 jobs:

  • direct compare: backing allocations 6,471,191 → 2,272,681; cumulative bytes 2,795,216,014 → 1,849,129,826; retained backing 1,712,231,497 → 152,569,468; GC cells 2,465,554 → 7,954; collections 51 → 0; property locks 11,292,223 → 6,581,871; environment locks 7,404,020 → 6,584,820.
  • bound sort: backing allocations 9,646,364 → 5,448,763; cumulative bytes 2,173,543,745 → 1,379,807,209; retained backing 763,547,020 → 108,549,860; GC cells 4,099,955 → 1,903,955; collections 120 → 75; property locks 8,860,223 → 4,660,271; environment locks 2,793,620 → 2,061,620.

Seven order-balanced exact-parent timing pairs retained the exact checksums (925849600 direct, 818968495 sort) and reduced median wall time by 28.3% on both rows: direct 2.121 s → 1.520 s, sort 1.566 s → 1.123 s. These are issue-local exact-parent measurements, not public benchmark publication claims.

Validation:

  • Collator test262: 65 → 65, zero failures.
  • full Intl402: 3,341 → 3,341, zero failures.
  • full sharded units after all commits: 1,675 passed, 1 skipped, 0 failed, 0 leaked.
  • focused allocation-failure, 32 MiB bounded-heap adversarial sort, normal concurrency, and suppression-free TSan coverage pass.
  • benchmark comparison/evidence self-tests pass.

The public differential also found two pre-existing locale-weight gaps that were excluded from the equivalent scored pairs rather than hidden: German phonebook Öl/Oel ordering and Thai default-ignorePunctuation case/accent collapse. They are tracked with exact zig-js/JSC repros in #581.

Sign in to comment on this issue.