ReviewOS

zig-utils/zig-js

Intl PluralRules: intern closed category results

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

Parents: #475, #460 Related: #577

Problem

Intl.PluralRules.prototype.select evaluates generated CLDR rules correctly, but then allocates a new GC-owned string cell for every result through Value.strAlloc. The result vocabulary is closed by ECMA-402/CLDR to zero, one, two, few, many, and other. resolvedOptions().pluralCategories repeats dynamic category-cell allocation when constructing each result array.

High-frequency numeric classification therefore turns immutable structural metadata into avoidable allocation and collection pressure. The representative matrix has no dedicated PluralRules consumer/control rows, so the boundary must be committed before the runtime change.

Scope

  • Add dependency-free steady select() and resolvedOptions().pluralCategories rows covering cardinal/ordinal rules, multiple locale category sets, fractional operands, notation/exponent behavior, and stable full-result checksums.
  • Keep formatter construction and fixed inputs outside the timed steady boundary; each scored call must execute the public operation and consume its observable result.
  • Record exact zig-js/JSC checksums plus exact-parent allocations, bytes, GC cells/collections, property locks, timing, and process memory.
  • Represent the six closed plural categories natively and expose process-lifetime immutable string values from both select() and pluralCategories.
  • Preserve dynamic CLDR rule evaluation, ToNumber/abrupt completion order, rounding/fraction/exponent operands, fresh ordinary resolved-options objects/arrays, category ordering, moving roots, and no-GIL behavior.

Security and no-workaround rules

Numeric inputs, call count, locales, and options are attacker-controlled. The implementation must remove category-cell churn without caching numeric results, bypassing rule evaluation, precomputing benchmark output, recognizing workload source/names, weakening OOM/GC/watchdog accounting, or adding unbounded/global mutable state.

Acceptance

  • Committed steady select and resolved-category rows meet the timing protocol and have stable non-zero checksums identical to system JSC.
  • Exact-parent attribution proves category string cells no longer scale with select/result-category count, without increased property locks or collections.
  • Cardinal/ordinal, locale category ordering, fractional/exponent, coercion/throw, result freshness/mutation, and exact checksums remain correct.
  • Focused OOM, bounded-heap, moving nursery, shared-realm no-GIL, and suppression-free TSan coverage pass.
  • The complete test/intl402/PluralRules subtree, full Intl402, and full sharded units have no regressions.

Exclusions

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

Landed

9 days ago

Benchmark boundary landed in bench(intl): isolate PluralRules category cells (#579).

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

  • steady select(): checksum 691593768; JSC 61.0 ms, zig-js 678.7 ms; pre-change 2,853,727 backing allocations / 446,387,544 cumulative bytes / 1,524,016 GC cells / 56 collections
  • fresh resolvedOptions().pluralCategories: checksum 872788430; JSC 133.0 ms, zig-js 1.793 s; pre-change 9,811,493 allocations / 1,245,099,533 bytes / 5,050,897 cells / 182 collections

Both rows cover eight preconstructed cardinal/ordinal formatters across category-rich locales and fixed fractional, negative, non-finite, and scientific inputs. The complete benchmark-comparison self-test passes. Runtime implementation starts only after this commit.

9 days ago

Landed perf(intl): intern PluralRules categories (#579) on main, after the frozen benchmark boundary bench(intl): isolate PluralRules category cells (#579).

The six ECMA-402 categories are now a closed native enum exposing process-lifetime immutable string cells. CLDR rule evaluation, coercion/throws, fresh ordinary resolved-options objects and arrays, mutation isolation, category order, moving roots, and no-GIL behavior remain unchanged.

At 100 jobs:

  • select() checksum remains 691593768; backing allocations fall 2,853,727 → 2,655,325 (-198,402), cumulative bytes 446,387,544 → 445,437,496, GC cells 1,524,016 → 1,325,616, GC bytes 234,483,776 → 221,786,176, collections 56 → 53
  • resolved-category checksum remains 872788430; allocations fall 9,811,493 → 9,414,691 (-396,802), bytes 1,245,099,533 → 1,243,543,053, GC cells 5,050,897 → 4,654,097, GC bytes 768,254,080 → 742,858,880, collections 182 → 175
  • property-lock counts are unchanged in both rows
  • balanced 14-sample medians are neutral within dispersion (0.994x select, 1.001x resolved), so there is intentionally no latency claim

Verification: exact zig-js/JSC checksums; focused normal and suppression-free TSan 6/6 with zero leaks/races; allocation-failure-safe static emission; bounded 4 MiB heap; moving nursery; four-lane shared-realm no-GIL; PluralRules test262 53 → 53; full Intl402 3,341 → 3,341; full units 1,672 passed, 1 intentional skip, 0 failed/leaked.

Sign in to comment on this issue.