Skip to content

Cheaper checks for unproven typed-array and string indexes - #842

Merged
ctate merged 11 commits into
mainfrom
perf/exact-index-checks
Oct 11, 2026
Merged

ctate merged 11 commits into
mainfrom
perf/exact-index-checks

Conversation

@cramforce

Copy link
Copy Markdown
Contributor

A typed-array, inline ASCII charCodeAt, local array, constant-table or number-map read whose index is not a proven integer checked validity with two float compares against the converted length, then an fptoui/uitofp round trip. On x86-64 both unsigned conversions expand to long instruction sequences, and the tsc-ts scanner pays them on every character it reads by position.

  • Signed round trip. exactIndexLines emits fptosi, freeze, sitofp, one float equality and one unsigned integer compare against the length. A frozen out-of-range or NaN conversion is an arbitrary integer that either doesn't convert back to the index or isn't below the length. Below the length the round trip is exact. -0 converts to 0, like ToPropertyKey(-0).
  • ToUint32 hint. A NaN-coded typed-array read keeps its integer hint even when the index may be invalid: the hint is ToUint32 of the value, and ToUint32(NaN) is 0, which the invalid arm already contributed. Worker programs also keep it across the shared-buffer join, whose integer result is converted exactly. hash ^ chars[i] then uses the loaded integer instead of re-deriving ToInt32 from the double.

tsc-ts parse-only CPU: Excalidraw −7.6%, self-check −4.6%, tRPC −2.5%, TypeORM −2.3%. tsc-ts binary −0.23%.

New corpus program exact-index-round-trip.ts: fractional, negative, −0, NaN, ±Infinity, 2^31/2^32/2^53/2^63/2^64 and 1e300 indexes through typed-array reads (u8/u16/i32), inline charCodeAt on ASCII and non-ASCII strings, local arrays with holes, numeric-keyed maps, and ToInt32/ToUint32 of missing reads.

First of four stacked on #830 (parser hot paths).

A typed-array, inline ASCII charCodeAt, local array, constant-table or
number-map read whose index is not a proven integer decided validity with
two float compares against the converted length, then an fptoui/uitofp round
trip. On x86-64 both unsigned conversions expand to long sequences, and the
tsc-ts scanner pays them on every character it reads by position.

exactIndexLines replaces them with fptosi, freeze, sitofp, one float equality
and one unsigned integer compare against the length. A frozen out-of-range or
NaN conversion is an arbitrary integer that never converts back to the index
or is not below the length; below the length the round trip is exact, and -0
converts to 0 like ToPropertyKey(-0).
The hint is ToUint32 of the value, and ToUint32(NaN) is 0, so a read whose
index may be invalid keeps it (the invalid arm already contributed 0); it was
dropped unless the index was proven in bounds. Worker programs also dropped it
at the join with the shared-buffer path, which now converts its integer result
exactly. `hash ^ chars[i]` and similar operators then use the loaded integer
instead of re-deriving ToInt32 from the double.
A reference field that only its class family's constructors write, through
their own `this`, never changes outside them. While its object is held, the
field's value is held too, so `this.scanner.scan()` and
`const units = this.units` need no retain and release (and no collector
buffering on the release).

analyzeFinalFields finds such fields over the whole program. It looks through
the derived constructor's guards on `this`, and disqualifies fields that live
dynamic views can store into (the detection int32 slots already uses, now
shared as dynamicFieldStores), fields of runtime-provided or generic classes,
and every field in library builds. canBorrowCallArgument accepts a final field
of a borrowable object, except in constructors, async functions and
generators, and except for nullable-pointer fields, which have no box to lend.
`for (let i = start; i < end; i++)` kept a double counter when the start had
no proven integer range, as in a hash over `[start, end)` whose bounds come
from fields. Such a loop is now versioned like one with an unproven limit:
when the start is an exact integer of at most 2^53 - 1 in magnitude (compared
bit for bit, so -0 takes the original loop), the integer-counter copy runs.
The usual bounds on duplicated bodies apply.
`const DOT = 0x2e` at module scope was a global that every reader loaded (a
thread-local load in worker programs), hiding the value from integer ranges,
switch dispatch and LLVM. foldConstantGlobals rewrites reads of an immutable
number or boolean global that the program writes exactly once, with a literal,
and that has neither a TDZ sentinel nor an initialization flag (globals that
can be read early keep their checked load). The store stays, so namespace
objects still find the value. Library builds keep their globals.
@vercel

vercel Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
scriptc Ready Ready Preview, v0 Oct 11, 2026 4:36pm UTC

ctate added 2 commits October 10, 2026 21:28
Borrow final reference fields without a retain
Run counted loops from an unproven start as integer loops
Conflicts, all resolved to this branch's side:
- emitter.ts (numeric-table get, map index probe), expr-bytes.ts, local-array-reads.ts, string-reads.ts: the base only respelled the size-to-double conversions in the old range checks (uitofp nneg, now sitofp); exactIndexLines replaces those checks entirely. The base's other hunks in these files (host inline truncation, the remaining sitofp conversions) merge as they are.
- int32-slots.ts: #837 and #843 both extracted dynamicFieldStores with identical bodies; keep this branch's signature, whose optional records/unions maps also serve #837's single-argument callers.
@cramforce
cramforce changed the base branch from perf/alloc-hugepages to main October 11, 2026 16:37
@ctate
ctate merged commit e503d26 into main Oct 11, 2026
75 of 83 checks passed
@ctate
ctate deleted the perf/exact-index-checks branch October 11, 2026 17:06

This branch was successfully deployed

1 active deployment
Preview — 98dc0f13 Deployed Oct 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants