ReviewOS

zig-utils/zig-js

Strings: make known-ASCII UTF-16 indexing constant time

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

Parents: #473, #493 Related: #460, #765

Problem

ECMAScript string indices address UTF-16 code units, while zig-js stores canonical strings as WTF-8. String.prototype.charAt, charCodeAt, at, and codePointAt currently call stringCodeUnitAt, which creates a new WTF-8 iterator at byte zero for every indexed read. Primitive string .length similarly walks the complete WTF-8 payload on every access. A conventional for (i < text.length) sequential scan of an ASCII string therefore performs O(n²) decoding even though the string cell already caches that every byte is one ASCII UTF-16 code unit.

This is observable on real tooling, not only a synthetic microbenchmark. In main CI run 32905159766, #765's pinned Home benchmark validator spent 47m44s in algorithmic-growth before the 60-minute job cancellation. An exact pinned local reproduction remained inside the first JSON artifact hash after 368.86 seconds. The first sample showed repeated charCodeAt rescans. After routing indexed reads through the cached ASCII path, a second 3-second sample attributed 1,729/1,746 stacks to getPrimitiveMember -> utf16LenOfValue -> utf16LenOfString while evaluating the encoder's loop condition, proving the remaining repeated .length scan.

Scope

  • Make indexed code-unit reads and UTF-16 length queries O(1) whenever the receiver's cached StringCell.isAscii() classification proves byte offset equals UTF-16 index.
  • Route charAt, charCodeAt, at, codePointAt, primitive string property indexing, and primitive .length/index-bound queries through the representation-aware path where the source cell identity is available.
  • Preserve the canonical WTF-8 iterator and exact surrogate/code-point semantics for non-ASCII strings and generic coerced byte slices whose representation is not known.
  • Add focused semantic and large sequential-scan regression coverage, then verify the exact pinned Home workload and String Test262 subtree.

Security/performance invariant

A valid ASCII input must not amplify n indexed reads or n loop-bound length queries into quadratic engine work. The optimization must rely only on the immutable cached string-cell classification, require no input-specific precomputation, and add no mutable cache or synchronization surface.

No-workaround rules

  • No benchmark timeout increase, reduced fixture, skipped validator, alternate engine, native hashing escape hatch, source-text matching, or swallowed failure.
  • No incorrect byte indexing for Latin-1/non-ASCII/WTF-8, no changed lone-surrogate behavior, and no unbounded index cache.

Acceptance

  • All affected indexed String operations and length queries use constant-time byte access for known-ASCII cells.
  • Existing astral and lone-surrogate UTF-16 semantics remain covered and green.
  • A large sequential ASCII charCodeAt/property-index scan with a .length loop bound completes with the expected checksum.
  • The exact pinned zig-js-backed algorithmic-growth --self-test completes successfully with measured before/after evidence.
  • Focused unit/TSan and Test262 String coverage show no regressions.
9 days ago

Root-cause implementation evidence before conformance closure:

  • The indexed-read patch routes charAt, charCodeAt, at, codePointAt, and primitive string property access through an O(1) byte/code-unit path only when the immutable cached ASCII classification proves the mapping.
  • A first exact pinned Home rerun advanced past the 1,024-width hash, then a 3-second sample attributed 1,729/1,746 stacks to repeated primitive .length scans in utf16LenOfValue. The same cached-ASCII invariant now makes those UTF-16 length queries O(1) too.
  • The 16,384-code-unit regression preserves method/property checksums of 1,097,728 each and improved from 11.079 s after the index-only patch to 268 ms after the length correction (41.3x on this exact focused test).
  • Existing String semantics passed 4/4 normally and 4/4 under TSan; the new sequential regression passed 1/1 normally and 1/1 under TSan.
  • The complete unchanged pinned zig-js-backed algorithmic-growth --self-test passed every phase in 333.72 s total, including all three artifact hashes, accepted report validation, zero-allocation replay, and all fail-closed mutations. Before the fix, a bounded 368.86 s run had not completed the first hash; main CI previously spent 47m44s in this validator before cancellation.

Test262 String before/after evidence is still pending, so this issue remains open and no conformance claim is being made yet.

9 days ago

Landed as 00bfb3e3. The immutable ASCII classification now makes UTF-16 length, charAt, charCodeAt, at, codePointAt, and primitive string index reads constant-time without changing the non-ASCII WTF-8 path. Focused normal and TSan coverage pass 5/5 each; pinned Test262 String remains 1,223/1,223 with 0 flips; and the exact pinned zig-js-backed algorithmic-growth validator completes every unchanged phase in 333.72 s instead of failing to finish its first hash in a 368.86 s bounded pre-fix run.

Sign in to comment on this issue.