Bulk string scanning and a simple-number fast path for JSON.parse#2725
Merged
Conversation
ScanStringLiteral now copies runs of ordinary characters in bulk instead
of one char at a time. On net8+ a SearchValues<char> over the closing
quote, the escape backslash, every control character (< 0x20) and the two
Unicode line separators locates the next stop exactly where the per-char
loop would have halted; older TFMs use IndexOfAny('"','\') plus a short
control/line-separator validation. Escape handling, interned-key routing
and every error message/position are preserved byte-for-byte (including
the U+2028/U+2029 break to UnexpectedEOS).
ScanNumericLiteral gains a fast path for the dominant shape -- optional
'-', integer digits, optional '.fraction', no exponent, <= 15 total
digits -- accumulating one exact long numerator and dividing once by an
exact power of ten. The result is bit-identical to double.Parse for every
accepted input because both operands are exactly representable. Gated to
net8+ where double.Parse is IEEE correctly-rounded; legacy runtimes keep
deferring to double.Parse (which can differ by an ULP and parses "-0" as
+0.0) so behavior stays byte-for-byte identical everywhere. Negative zero
also defers to double.Parse.
Tests: bulk-scan escapes at start/middle/end, internal-buffer-boundary
crossings, control-char rejection at exact positions and U+2028/U+2029
termination; a 200k-case number bit-identity property test plus edge
cases and negative/positive-zero handling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U3NyGAFzB3uRWQnPUNL8i2
lahma
enabled auto-merge (squash)
July 21, 2026 16:54
lahma
added a commit
that referenced
this pull request
Jul 21, 2026
Single-session re-measure of all lanes on main with #2725/#2726 merged. Extends the campaign notes with the round-2 wins (JSON scan fast paths, closure-read chain memoization) and records the measured-and-dropped direct-dispatch experiment: the builtin-call path is already at its cost floor, so the residual string-row gap is interpreter dispatch itself, not call ceremony. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
lahma
added a commit
that referenced
this pull request
Jul 22, 2026
…nd keys (#2738) Four long-standing spec deviations surfaced while reviewing the 4.14 JSON parser changes (#2718/#2725/#2732), all verified against V8: - '-09' / '-00' parsed as -9 / -0: the leading-zero rejection was guarded on builder length 1, which a minus sign defeated. The rule now applies to the first digit regardless of sign (int = zero / digit1-9 *DIGIT). - '1.' / '1.e3' parsed as 1 / 1000: a decimal point now requires at least one fraction digit (frac = decimal-point 1*DIGIT). - Raw U+2028/U+2029 inside strings threw SyntaxError: the JSON grammar permits any code point except quote, backslash and control characters below 0x20 raw inside a string. The line separators are removed from the bulk-scan stop set (a smaller set, marginally faster) and the scanner's terminator lane is gone. - Escaped control characters in member keys were rejected while the same content was accepted as a value; any string is a valid key, so the key-only validation is removed. Raw control characters keep being rejected everywhere by the string scanner. Full suite plus Test262 green; Test262 does not cover these forms either way, so no exclusion changes. Claude-Session: https://claude.ai/code/session_0115tQFNyyQqc1HQGPLUgZND Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Campaign-2 item (follow-up to #2716–#2723). Post-interning profiles put
JsonParser.ScanStringLiteralat 8.5% self (now value strings) and numeric parsing at ~5%.Changes
Bulk string scanning: the char-by-char scan becomes a bulk span copy — net8+ uses a
SearchValues<char>over the full stop set (control chars,",\, U+2028/U+2029) soIndexOfAnylands exactly where the per-char loop stopped; older TFMs useIndexOfAny('"','\')plus a short validation pass. Escapes keep per-char handling; key interning untouched; error messages and positions byte-for-byte preserved (locked by tests, including the U+2028 →UnexpectedEOSquirk).Simple-number fast path: optional
-, digits, optional fraction, no exponent, ≤15 total digits — one exactlongnumerator divided by an exact power of ten. Gated to net8+: testing revealed legacy net472double.Parseis not correctly rounded even for ≤15 digits (1-ULP differences), so older TFMs keep deferring to the platform parser and bit-identity holds per platform by construction.-0always defers. A 200,000-case property test proves bit-identity againstdouble.Parsefor covered shapes.Numbers (default BDN job, same-base A/B vs main)
Gates
Build clean on all 5 TFMs; JsonTests 131/131 on net10 + net472; full Jint.Tests 3733 passed / 0 failed; Test262 99,431 / 0 failed (JSON subset 502/502).
🤖 Generated with Claude Code