ReviewOS

zig-utils/zig-js

Intl Segmenter: share input ownership across segment records

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

Parents: #475, #460 Depends on: #577

Problem

Intl.Segmenter.prototype.segment stores one coerced input string on the %Segments% object, but every containing() result and iterator result calls Value.strAlloc on the complete input again. For an attacker-controlled input with many short segments, retained/cumulative string-cell work grows with input length multiplied by segment count even though ECMA-402 segment records all expose the same immutable input string. Iterator construction also allocates another closed granularity string cell.

The representative benchmark matrix has no dedicated Segmenter consumer rows, so changing ownership without symmetric controls would be unauditable.

Scope

  • Add dependency-free word-segmentation rows for full iteration and indexed containing() probes, with Segmenter/input fixture construction outside the timed boundary.
  • Consume complete segment, index, input, and isWordLike results through stable checksums using identical source and inputs in zig-js and system JSC.
  • Attribute exact-parent allocations, bytes, GC cells/collections, locks, timing, and retained memory before changing the engine.
  • Preserve the single ToString coercion result as one GC-owned immutable string value across %Segments%, iterators, and every segment-data record instead of rebuilding the full input cell per result.
  • Represent closed Segmenter granularity metadata without per-iterator dynamic string allocation when exact attribution justifies it.
  • Keep each returned segment substring independently owned and preserve ordinary result-object mutation, UTF-16 indices, iterator progress, containing() boundaries, abrupt completion, and observable coercion order.

Security and no-workaround rules

Input length and segment count are attacker-controlled. The implementation must make full-input ownership linear and bounded without an unbounded cache, global interning, input truncation, skipped records, precomputed benchmark output, or recognition of benchmark source/names. It must not weaken precise roots, moving relocation, no-GIL synchronization, OOM atomicity, watchdog accounting, or Unicode boundary semantics.

Acceptance

  • Committed iterate and containing rows have stable non-zero checksums, identical zig-js/JSC work, and meet the harness timing protocol.
  • Exact-parent attribution proves the full input string is allocated once per segment() result rather than once per returned record; cumulative allocation scales linearly in input plus output.
  • Checksums and Segmenter semantics remain exact, with no increased property locks or collections.
  • Focused OOM, bounded-heap adversarial input, moving nursery, shared-realm no-GIL, and suppression-free TSan coverage pass.
  • The complete test/intl402/Segmenter subtree 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 Segmenter input ownership (#578).

At 100 jobs, both engines clear the 50 ms floor and checksums are exact: iteration 360,619,407, containing 720,083,379. Pre-change zig-js invocation attribution:

  • iteration: 9,677,775 backing allocations, 1,319,738,700 cumulative bytes, 5,051,615 GC cells, 164 collections, 14,838,118 property locks;
  • containing: 4,034,026 backing allocations, 556,472,775 cumulative bytes, 2,075,742 GC cells, 67 collections, 6,014,496 property locks.

The full benchmark-comparison validator passes. The next commit will change input/granularity ownership against this exact parent; no engine optimization is included in the boundary.

9 days ago

Landed perf(intl): share Segmenter input values (#578) on main, following the frozen benchmark boundary bench(intl): isolate Segmenter input ownership (#578).

The implementation retains the one coerced immutable input Value across %Segments%, iterators, and segment-data records; keeps each emitted segment substring independently owned; uses closed native granularity metadata; and preserves representation-aware UTF-16 indexing. No cache, interning workaround, truncation, or benchmark recognition was introduced.

Measured at 100 jobs:

  • full iteration checksum remains 360619407; backing allocations 9,677,775 → 9,604,974 (-72,801), cumulative bytes 1,319,738,700 → 1,315,318,248 (-4,420,452), GC cells 5,051,615 → 4,978,814, collections 164 → 162
  • indexed containing() checksum remains 720083379; allocations 4,034,026 → 4,002,025 (-32,001), bytes 556,472,775 → 554,553,523 (-1,919,252), GC cells 2,075,742 → 2,043,741, collections remain 67
  • property-lock counts are unchanged in both rows
  • order-balanced 14-sample timings were mixed within noise (iterate 0.987x, containing 1.013x), so there is intentionally no latency claim

Verification:

  • exact zig-js/JSC checksums match at 100 and 320 jobs
  • focused normal: 5/5, zero failures/leaks
  • focused suppression-free TSan: 5/5, zero races/failures/leaks
  • bounded 4 MiB adversarial heap, moving nursery, OOM failure atomicity, and four-lane shared-realm no-GIL coverage pass
  • test/intl402/Segmenter: 79 → 79
  • full sharded units: 1,667 passed, 1 intentional skip, 0 failed, 0 leaked

Sign in to comment on this issue.