Skip to content

Replace TRegExpr with purpose-built backtracking bytecode VM regex engine#585

Merged
frostney merged 15 commits into
mainfrom
t3code/dbc3a3af
May 9, 2026
Merged

Replace TRegExpr with purpose-built backtracking bytecode VM regex engine#585
frostney merged 15 commits into
mainfrom
t3code/dbc3a3af

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented May 8, 2026

Summary

  • Replace FPC's TRegExpr with a custom backtracking bytecode VM regex engine, fixing the SIGSEGV on large inputs (Engine SIGSEGV: RegExp.prototype.test trailing-input edge case #515) and eliminating three preprocessing workarounds.
  • New Goccia.RegExp.Compiler.pas (recursive-descent parser + bytecode emitter, ~1,400 lines) and Goccia.RegExp.VM.pas (iterative executor with heap backtrack stack + always-on memoization, ~650 lines). Public API (ExecuteRegExp, TGocciaRegExpMatchResult) unchanged; Goccia.RegExp.Runtime.pas and Goccia.Builtins.GlobalRegExp.pas unmodified.
  • Removes FPC regexpr package from the cross-compilation toolchain, simplifying CI.
  • Key design: modifier state ((?ims-ims:...)) encoded per-instruction at compile time for correct scoping; duplicate named backreferences emit strict-mode SPLIT chains; memoization cache (64K entries) prunes exponential backtracking.

Closes #515

Testing

  • Verified no regressions and confirmed the new feature or bugfix in end-to-end JavaScript/TypeScript tests
  • Updated documentation
  • Optional: Verified no regressions and confirmed the new feature or bugfix in native Pascal tests (if AST, scope, evaluator, or value types changed)
  • Optional: Verified no benchmark regressions or confirmed benchmark coverage for the change

…gine (#515)

TRegExpr used native call recursion for backtracking, causing SIGSEGV on
inputs ~42K+ chars when combined with the evaluator's stack depth. Three
preprocessing passes papered over feature gaps (modifier scope leak, no
named groups, ASCII-approximate Unicode properties). This replaces the
entire backend with a custom regex engine while keeping the public API
(ExecuteRegExp, TGocciaRegExpMatchResult) unchanged.

New units:
- Goccia.RegExp.Compiler: recursive-descent parser over ES2026 regex
  grammar, single-pass bytecode emitter with pre-scanned named groups
  for forward \k<name> resolution, inline modifier group scoping, and
  duplicate named group validation via disjunction path tracking.
- Goccia.RegExp.VM: iterative dispatch loop with heap-allocated backtrack
  stack and always-on failure memoization (64K-entry hash table). No
  native call recursion. 10M step limit throws Error instead of crashing.

Key design decisions:
- Modifier state (ignoreCase, multiline, dotAll) is encoded per-instruction
  at compile time, not read from global flags at runtime. This gives correct
  scoping for (?ims-ims:...) modifier groups.
- Duplicate named backreferences emit a SPLIT chain with strict-mode
  backrefs (fail if group uncaptured) + terminal FAIL, so only the
  participating group's captured text is matched.
- Reuses TextSemantics.pas UTF-8 functions (TryReadUTF8CodePoint,
  AdvanceUTF8StringIndex, CodePointToUTF8) rather than reimplementing.
- Removes FPC regexpr package from the cross-compilation toolchain.
- Removes staging/sm/RegExp/test-trailing.js from KNOWN_ENGINE_CRASHES.

Closes #515

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
gocciascript-homepage Ignored Ignored Preview May 9, 2026 10:10am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 2026

📝 Walkthrough

Walkthrough

This PR replaces the TRegExpr-based regex engine with a custom regex compiler and bytecode virtual machine (VM). It introduces a new regex-to-bytecode compiler supporting ES2025 modifier groups, Unicode property escapes, named captures, and backreferences, paired with a VM executor using backtracking, memoization, and step limiting. The legacy regexpr package is removed from the CI/CD toolchain, and existing public regex APIs remain unchanged.

Changes

Regex Engine Replacement

Layer / File(s) Summary
Compiler Foundation
source/units/Goccia.RegExp.Compiler.pas
New 1533-line compiler unit defines bytecode opcodes (TRegExpOpCode: CHAR, CHAR_CLASS, SPLIT, JUMP, SAVE, BACKREF, assertions, lookarounds, MATCH/FAIL). TRegExpCompiler parses regex patterns with support for character classes/ranges, Unicode escapes, property escapes (\p{...}/\P{...}), modifier groups ((?flags:...)), lookahead/lookbehind, capturing/named groups, and quantifiers; emits compiled TRegExpProgram bytecode with character-class table and named-group metadata. Entry points: CompileRegExp and ValidateRegExpPatternNew.
VM Execution Engine
source/units/Goccia.RegExp.VM.pas
New 684-line VM unit implements bytecode interpreter with backtracking stack, memoization (preventing repeated PC/input-position exploration), UTF-8 helpers (forward/reverse code-point lookup), and opcode dispatch for matching, captures, backreferences, assertions, and lookarounds. Step limiting throws ERegExpRuntimeError on catastrophic backtracking. Public entry: ExecuteRegExpVM populates TRegExpVMResult with match status and capture slots.
Engine Integration
source/units/Goccia.RegExp.Engine.pas
ValidateRegExpPattern now delegates to ValidateRegExpPatternNew (compiler-based validation). ValidateRegExpFlags rejects simultaneous u and v flags. ExecuteRegExp compiles pattern via CompileRegExp, executes via ExecuteRegExpVM, maps VM capture slots to Groups, computes NextIndex with UTF-8 advancement, and sources NamedGroups from compiled program.
Runtime Object Handling
source/units/Goccia.RegExp.Runtime.pas, source/units/Goccia.Builtins.GlobalRegExp.pas
IsRegExpValue now checks for own source/flags properties instead of toStringTag. CreateRegExpObject no longer defines flag-derived boolean properties; instead, TGocciaGlobalRegExp adds 10 property accessor methods (source, flags, global, ignoreCase, multiline, dotAll, unicode, sticky, unicodeSets, hasIndices) that return flag state or prototype placeholders. MatchRegExpObject wraps ExecuteRegExp in try/except to convert ERegExpRuntimeError to JavaScript errors.
Removed Legacy Unit
source/units/Goccia.RegExp.Unicode.pas
Deleted (611 lines); Unicode property/escape handling is now built into the compiler.
Toolchain & CI Configuration
.github/workflows/ci.yml, .github/workflows/toolchain.yml
Removed all REGEXPR_SRC variable assignments, directory checks, and -Fu"$REGEXPR_SRC" include paths from FreePascal compile steps for app entrypoints, unit tests, and harness binaries. Toolchain setup no longer copies regexpr package sources to share/fpcsrc/packages; only fcl-base is cached.
Documentation & Metadata
docs/build-system.md, docs/built-ins.md, docs/decision-log.md
Build system docs updated to remove regexpr from cached FPC sources (now only fcl-base). Built-ins docs revised to remove mention of TRegExpr Russian charset extensions being disabled by u flag. New decision-log entry (2026-05-08, engine area) documents switch from TRegExpr to custom bytecode VM, noting compiler/VM architecture, memoization, step-limit errors, UTF-8 reuse, toolchain package removal, and API stability.
Test Suite Updates
scripts/run_test262_suite.ts, tests/built-ins/RegExp/*
Removed staging/sm/RegExp/test-trailing.js from KNOWN_ENGINE_CRASHES skip list (the new engine fixes this crash). Added 36+ RegExp constructor syntax-validation tests, 15-line modifier-scoping tests for backreferences, 96-line exec tests (greedy/backreference/lookahead/lookbehind/catastrophic-backtracking), and 52-line Unicode-mode tests (dot/multiline/syntax restrictions/large-input).

Sequence Diagram

sequenceDiagram
    participant App as Application
    participant Engine as RegExp Engine
    participant Compiler as Regex Compiler
    participant VM as Regex VM
    participant Input as Input String

    App->>Engine: ExecuteRegExp(pattern, flags, input)
    Engine->>Compiler: CompileRegExp(pattern, flags)
    Compiler->>Compiler: Parse pattern tree<br/>(atoms, groups, quantifiers)
    Compiler->>Compiler: Emit bytecode<br/>(opcodes + char-class table)
    Compiler-->>Engine: Return TRegExpProgram
    Engine->>VM: ExecuteRegExpVM(program, input, startIndex)
    VM->>Input: Read UTF-8 at current position
    VM->>VM: Execute opcodes<br/>(match, split, jump, assert)
    VM->>VM: Memoize (PC, inputPos)<br/>to avoid re-exploration
    VM->>VM: Backtrack on failure<br/>(pop stack, retry alternatives)
    alt Match found
        VM-->>Engine: CaptureSlots[], Matched=true
    else Backtracking exceeded step limit
        VM-->>Engine: ERegExpRuntimeError
    else No match
        VM-->>Engine: Matched=false
    end
    Engine->>Engine: Map VM slots to Groups[]<br/>Set NextIndex
    Engine-->>App: TGocciaRegExpMatchResult
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

This PR introduces a complete regex engine replacement with dense, multi-component logic: a 1533-line recursive-descent compiler emitting complex bytecode, a 684-line VM interpreter with memoization and backtracking state management, and coordinated changes across runtime, builtins, and CI/CD. The compiler's opcode design, quantifier/group handling, escape/property parsing, and the VM's backtrack stack and memoization strategy all require careful verification against edge cases and the test suite.

Possibly related issues

Possibly related PRs

  • frostney/GocciaScript#172: Original TRegExpr-based RegExp engine implementation that this PR fully replaces with a custom bytecode compiler/VM.
  • frostney/GocciaScript#295: ES2025 RegExp modifier-group support (e.g., (?i:...)) now integrated natively into the new compiler's parse/compile pipeline.
  • frostney/GocciaScript#207: Named capture groups, named backreferences, and new RegExp flags (unicodeSets, hasIndices) — features now implemented directly in the compiler and exposed via property accessors.

Suggested labels

new feature, refactor, spec compliance, internal, bug

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: replacing TRegExpr with a custom bytecode VM regex engine.
Description check ✅ Passed The description covers the change summary, testing checklist (with appropriate boxes checked), and closes issue #515. It includes implementation constraints and key design decisions.
Linked Issues check ✅ Passed The PR successfully addresses issue #515 by replacing TRegExpr with a backtracking bytecode VM that eliminates native recursion, removing the SIGSEGV crash on large inputs and enabling the test to run normally.
Out of Scope Changes check ✅ Passed All changes are directly related to the regex engine replacement: compiler/VM implementation, documentation updates, toolchain simplification, and test additions for the new engine.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

test262 Conformance

Category Run Passed Δ Pass Failed Pass-rate Δ Rate
built-ins 23,449 14,487 +126 8,961 61.8% +0.5pp
harness 116 71 ±0 45 61.2% ±0pp
intl402 3,324 167 ±0 3,157 5.0% ±0pp
language 23,635 12,570 ±0 11,065 53.2% ±0pp
staging 1,484 483 +13 999 32.5% +0.9pp
total 52,008 27,778 +139 24,227 53.4% +0.3pp

Areas closest to 100%

Area Pass rate Δ vs main Passing
built-ins/WeakSet 98.8% ±0pp 84 / 85
built-ins/WeakMap 98.6% ±0pp 139 / 141
language/asi 97.1% ±0pp 99 / 102
Per-test deltas (+138 / -0)

Newly passing (138):

  • built-ins/Number/prototype/toExponential/undefined-fractiondigits.js
  • built-ins/RegExp/character-class-escape-non-whitespace.js
  • built-ins/RegExp/dotall/without-dotall-unicode.js
  • built-ins/RegExp/from-regexp-like-flag-override.js
  • built-ins/RegExp/from-regexp-like-get-source-err.js
  • built-ins/RegExp/from-regexp-like.js
  • built-ins/RegExp/lookBehind/alternations.js
  • built-ins/RegExp/lookBehind/back-references.js
  • built-ins/RegExp/lookBehind/do-not-backtrack.js
  • built-ins/RegExp/lookBehind/misc.js
  • built-ins/RegExp/lookBehind/negative.js
  • built-ins/RegExp/lookBehind/simple-fixed-length.js
  • built-ins/RegExp/lookBehind/sliced-strings.js
  • built-ins/RegExp/named-groups/non-unicode-references.js
  • built-ins/RegExp/named-groups/unicode-references.js
  • built-ins/RegExp/prototype/dotAll/length.js
  • built-ins/RegExp/prototype/dotAll/name.js
  • built-ins/RegExp/prototype/dotAll/prop-desc.js
  • built-ins/RegExp/prototype/dotAll/this-val-non-obj.js
  • built-ins/RegExp/prototype/dotAll/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/flags/length.js
  • built-ins/RegExp/prototype/flags/name.js
  • built-ins/RegExp/prototype/flags/prop-desc.js
  • built-ins/RegExp/prototype/flags/this-val-non-obj.js
  • built-ins/RegExp/prototype/flags/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/global/15.10.7.2-2.js
  • built-ins/RegExp/prototype/global/length.js
  • built-ins/RegExp/prototype/global/name.js
  • built-ins/RegExp/prototype/global/S15.10.7.2_A8.js
  • built-ins/RegExp/prototype/global/S15.10.7.2_A9.js
  • built-ins/RegExp/prototype/global/this-val-non-obj.js
  • built-ins/RegExp/prototype/global/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/hasIndices/length.js
  • built-ins/RegExp/prototype/hasIndices/name.js
  • built-ins/RegExp/prototype/hasIndices/prop-desc.js
  • built-ins/RegExp/prototype/hasIndices/this-val-non-obj.js
  • built-ins/RegExp/prototype/hasIndices/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/ignoreCase/15.10.7.3-2.js
  • built-ins/RegExp/prototype/ignoreCase/length.js
  • built-ins/RegExp/prototype/ignoreCase/name.js
  • built-ins/RegExp/prototype/ignoreCase/S15.10.7.3_A8.js
  • built-ins/RegExp/prototype/ignoreCase/S15.10.7.3_A9.js
  • built-ins/RegExp/prototype/ignoreCase/this-val-non-obj.js
  • built-ins/RegExp/prototype/ignoreCase/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/multiline/15.10.7.4-2.js
  • built-ins/RegExp/prototype/multiline/length.js
  • built-ins/RegExp/prototype/multiline/name.js
  • built-ins/RegExp/prototype/multiline/S15.10.7.4_A8.js
  • built-ins/RegExp/prototype/multiline/S15.10.7.4_A9.js
  • built-ins/RegExp/prototype/multiline/this-val-non-obj.js
  • built-ins/RegExp/prototype/multiline/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/source/length.js
  • built-ins/RegExp/prototype/source/name.js
  • built-ins/RegExp/prototype/source/prop-desc.js
  • built-ins/RegExp/prototype/source/this-val-non-obj.js
  • built-ins/RegExp/prototype/source/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/sticky/length.js
  • built-ins/RegExp/prototype/sticky/name.js
  • built-ins/RegExp/prototype/sticky/prop-desc.js
  • built-ins/RegExp/prototype/sticky/this-val-non-obj.js
  • built-ins/RegExp/prototype/sticky/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/Symbol.match/get-global-err.js
  • built-ins/RegExp/prototype/Symbol.replace/get-global-err.js
  • built-ins/RegExp/prototype/unicode/length.js
  • built-ins/RegExp/prototype/unicode/name.js
  • built-ins/RegExp/prototype/unicode/prop-desc.js
  • built-ins/RegExp/prototype/unicode/this-val-non-obj.js
  • built-ins/RegExp/prototype/unicode/this-val-regexp-prototype.js
  • built-ins/RegExp/prototype/unicodeSets/length.js
  • built-ins/RegExp/prototype/unicodeSets/name.js
  • built-ins/RegExp/prototype/unicodeSets/prop-desc.js
  • built-ins/RegExp/prototype/unicodeSets/this-val-non-obj.js
  • built-ins/RegExp/prototype/unicodeSets/this-val-regexp-prototype.js
  • built-ins/RegExp/quantifier-integer-limit.js
  • built-ins/RegExp/regexp-modifiers/add-ignoreCase-affects-characterEscapes.js
  • built-ins/RegExp/regexp-modifiers/remove-ignoreCase-affects-characterEscapes.js
  • built-ins/RegExp/S15.10.2.10_A3.1_T1.js
  • built-ins/RegExp/S15.10.2.10_A4.1_T1.js
  • built-ins/RegExp/S15.10.2.10_A4.1_T2.js
  • built-ins/RegExp/S15.10.2.10_A4.1_T3.js
  • built-ins/RegExp/S15.10.2.11_A1_T1.js
  • built-ins/RegExp/S15.10.2.11_A1_T5.js
  • built-ins/RegExp/S15.10.2.11_A1_T7.js
  • built-ins/RegExp/S15.10.2.11_A1_T8.js
  • built-ins/RegExp/S15.10.2.11_A1_T9.js
  • built-ins/RegExp/S15.10.2.13_A1_T1.js
  • built-ins/RegExp/S15.10.2.13_A1_T17.js
  • built-ins/RegExp/S15.10.2.13_A1_T2.js
  • built-ins/RegExp/S15.10.2.13_A1_T3.js
  • built-ins/RegExp/S15.10.2.13_A1_T4.js
  • built-ins/RegExp/S15.10.2.13_A1_T5.js
  • built-ins/RegExp/S15.10.2.13_A2_T1.js
  • built-ins/RegExp/S15.10.2.13_A2_T2.js
  • built-ins/RegExp/S15.10.2.13_A2_T4.js
  • built-ins/RegExp/S15.10.2.13_A2_T8.js
  • built-ins/RegExp/S15.10.2.13_A3_T1.js
  • built-ins/RegExp/S15.10.2.13_A3_T2.js
  • built-ins/RegExp/S15.10.2.13_A3_T3.js
  • built-ins/RegExp/S15.10.2.13_A3_T4.js
  • built-ins/RegExp/S15.10.2.3_A1_T16.js
  • … 38 more

Steady-state failures are non-blocking; regressions vs the cached main baseline (lower total pass count, or any PASS → non-PASS transition) fail the conformance gate. Measured on ubuntu-latest x64, bytecode mode. Areas grouped by the first two test262 path components; minimum 25 attempted tests, areas already at 100% excluded. Δ vs main compares against the most recent cached main baseline.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Suite Timing

Test Runner (interpreted: 9,021 passed; bytecode: 9,021 passed)
Metric Interpreted Bytecode
Total 9021 9021
Passed 9021 ✅ 9021 ✅
Workers 4 4
Test Duration 2.10s 2.11s
Lex (cumulative) 253.0ms 162.2ms
Parse (cumulative) 272.4ms 254.9ms
Compile (cumulative) 577.6ms
Execute (cumulative) 2.42s 2.14s
Engine Total (cumulative) 2.95s 3.13s
Lex (avg/worker) 63.3ms 40.5ms
Parse (avg/worker) 68.1ms 63.7ms
Compile (avg/worker) 144.4ms
Execute (avg/worker) 605.5ms 535.1ms
Engine Total (avg/worker) 736.8ms 783.7ms

Memory

GC rows aggregate the main thread plus all worker thread-local GCs. Test runner worker shutdown frees thread-local heaps in bulk; that shutdown reclamation is not counted as GC collections or collected objects.

Metric Interpreted Bytecode
GC Live 230.04 MiB 224.55 MiB
GC Peak Live 230.05 MiB 224.56 MiB
GC Allocated During Run 233.87 MiB 228.37 MiB
GC Limit 7.81 GiB 7.81 GiB
GC Collections 1 1
GC Collected Objects 74 74
Heap Start Allocated 146.6 KiB 146.6 KiB
Heap End Allocated 1.38 MiB 1.38 MiB
Heap Delta Allocated 1.24 MiB 1.24 MiB
Heap Delta Free 715.3 KiB 715.3 KiB
Benchmarks (interpreted: 407; bytecode: 407)
Metric Interpreted Bytecode
Total 407 407
Workers 4 4
Duration 2.52min 2.37min

Memory

GC rows aggregate the main thread plus all worker thread-local GCs. Benchmark runner performs explicit between-file collections, so collection and collected-object counts can be much higher than the test runner.

Metric Interpreted Bytecode
GC Live 3.44 MiB 3.43 MiB
GC Peak Live 117.30 MiB 90.83 MiB
GC Allocated During Run 15.04 GiB 12.85 GiB
GC Limit 7.81 GiB 7.81 GiB
GC Collections 2,820 2,660
GC Collected Objects 277,858,778 304,629,607
Heap Start Allocated 1.14 MiB 1.14 MiB
Heap End Allocated 1.14 MiB 1.14 MiB
Heap Delta Allocated 128 B 128 B

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 8, 2026

Benchmark Results

407 benchmarks

Interpreted: 🟢 27 improved · 🔴 205 regressed · 175 unchanged · avg -4.5%
Bytecode: 🟢 379 improved · 🔴 25 regressed · 3 unchanged · avg +25.8%

arraybuffer.js — Interp: 🟢 2, 🔴 4, 8 unch. · avg -0.2% · Bytecode: 🟢 14 · avg +25.6%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 174,451 ops/sec [163,417..175,493] → 165,408 ops/sec [138,154..173,837] ~ overlap (-5.2%) 215,650 ops/sec [155,221..219,640] → 264,406 ops/sec [256,604..281,577] 🟢 +22.6%
create ArrayBuffer(64) 166,755 ops/sec [164,703..167,905] → 167,875 ops/sec [162,984..170,073] ~ overlap (+0.7%) 211,275 ops/sec [210,196..211,799] → 261,455 ops/sec [259,340..271,128] 🟢 +23.8%
create ArrayBuffer(1024) 144,055 ops/sec [141,791..147,100] → 149,827 ops/sec [143,288..151,655] ~ overlap (+4.0%) 170,204 ops/sec [169,489..176,056] → 216,719 ops/sec [214,515..220,665] 🟢 +27.3%
create ArrayBuffer(8192) 76,553 ops/sec [75,958..76,761] → 89,999 ops/sec [89,531..90,817] 🟢 +17.6% 81,875 ops/sec [52,887..83,313] → 105,491 ops/sec [102,757..106,539] 🟢 +28.8%
slice full buffer (64 bytes) 199,296 ops/sec [198,172..201,684] → 197,661 ops/sec [196,215..199,940] ~ overlap (-0.8%) 265,422 ops/sec [260,748..266,650] → 324,695 ops/sec [319,789..328,456] 🟢 +22.3%
slice half buffer (512 of 1024 bytes) 176,482 ops/sec [175,876..176,827] → 177,357 ops/sec [174,831..178,882] ~ overlap (+0.5%) 231,329 ops/sec [228,313..232,831] → 286,442 ops/sec [282,570..290,326] 🟢 +23.8%
slice with negative indices 165,682 ops/sec [164,798..167,584] → 165,308 ops/sec [163,879..166,949] ~ overlap (-0.2%) 240,382 ops/sec [236,271..247,151] → 300,970 ops/sec [277,665..305,385] 🟢 +25.2%
slice empty range 187,546 ops/sec [171,617..188,997] → 186,478 ops/sec [124,621..190,060] ~ overlap (-0.6%) 248,662 ops/sec [228,673..256,959] → 314,371 ops/sec [274,474..314,843] 🟢 +26.4%
byteLength access 430,604 ops/sec [411,034..440,325] → 407,904 ops/sec [404,517..420,020] ~ overlap (-5.3%) 517,049 ops/sec [508,923..519,542] → 637,864 ops/sec [618,854..638,626] 🟢 +23.4%
Symbol.toStringTag access 354,566 ops/sec [348,520..355,494] → 328,914 ops/sec [326,511..342,000] 🔴 -7.2% 370,966 ops/sec [362,692..377,356] → 454,563 ops/sec [449,417..456,875] 🟢 +22.5%
ArrayBuffer.isView 264,010 ops/sec [262,414..265,595] → 247,223 ops/sec [246,342..247,332] 🔴 -6.4% 335,245 ops/sec [328,579..352,563] → 436,381 ops/sec [419,105..437,870] 🟢 +30.2%
clone ArrayBuffer(64) 179,495 ops/sec [178,758..182,539] → 177,016 ops/sec [175,237..178,075] 🔴 -1.4% 230,181 ops/sec [228,486..234,492] → 294,193 ops/sec [291,833..296,742] 🟢 +27.8%
clone ArrayBuffer(1024) 151,205 ops/sec [149,960..153,568] → 155,294 ops/sec [153,804..156,966] 🟢 +2.7% 188,194 ops/sec [184,871..193,557] → 241,214 ops/sec [239,230..244,213] 🟢 +28.2%
clone ArrayBuffer inside object 125,342 ops/sec [124,120..127,455] → 123,505 ops/sec [121,073..124,008] 🔴 -1.5% 151,555 ops/sec [148,763..154,999] → 191,416 ops/sec [187,584..192,935] 🟢 +26.3%
arrays.js — Interp: 🔴 15, 4 unch. · avg -3.2% · Bytecode: 🟢 19 · avg +32.8%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 4,334 ops/sec [4,185..4,474] → 3,996 ops/sec [3,764..4,100] 🔴 -7.8% 6,870 ops/sec [6,014..7,415] → 8,908 ops/sec [8,616..9,279] 🟢 +29.7%
Array.from 10 elements 97,769 ops/sec [94,961..98,867] → 93,061 ops/sec [87,283..94,070] 🔴 -4.8% 100,829 ops/sec [99,587..102,752] → 133,174 ops/sec [122,391..134,503] 🟢 +32.1%
Array.of 10 elements 118,459 ops/sec [117,146..118,980] → 110,082 ops/sec [108,059..112,346] 🔴 -7.1% 131,598 ops/sec [127,763..137,289] → 170,310 ops/sec [169,570..171,975] 🟢 +29.4%
spread into new array 146,434 ops/sec [145,692..146,739] → 140,638 ops/sec [139,759..141,383] 🔴 -4.0% 75,682 ops/sec [61,519..75,971] → 102,656 ops/sec [102,022..105,195] 🟢 +35.6%
map over 50 elements 7,365 ops/sec [7,194..7,480] → 7,090 ops/sec [7,041..7,137] 🔴 -3.7% 12,871 ops/sec [12,616..13,219] → 16,464 ops/sec [16,266..16,651] 🟢 +27.9%
filter over 50 elements 6,994 ops/sec [6,880..7,078] → 7,058 ops/sec [5,031..7,124] ~ overlap (+0.9%) 12,552 ops/sec [12,328..12,812] → 15,519 ops/sec [15,395..15,560] 🟢 +23.6%
reduce sum 50 elements 7,544 ops/sec [6,703..7,645] → 7,335 ops/sec [7,241..7,364] ~ overlap (-2.8%) 12,339 ops/sec [12,225..12,505] → 15,724 ops/sec [15,593..15,842] 🟢 +27.4%
forEach over 50 elements 6,637 ops/sec [6,586..6,753] → 6,498 ops/sec [6,432..6,546] 🔴 -2.1% 13,337 ops/sec [13,157..13,614] → 18,504 ops/sec [17,105..18,629] 🟢 +38.7%
find in 50 elements 9,891 ops/sec [9,791..10,186] → 9,607 ops/sec [9,539..9,673] 🔴 -2.9% 19,080 ops/sec [18,617..19,301] → 25,683 ops/sec [25,417..26,035] 🟢 +34.6%
sort 20 elements 3,793 ops/sec [3,664..3,856] → 3,631 ops/sec [3,617..3,648] 🔴 -4.3% 7,283 ops/sec [7,168..7,423] → 10,089 ops/sec [9,955..10,145] 🟢 +38.5%
flat nested array 50,867 ops/sec [50,395..51,570] → 49,203 ops/sec [48,764..49,602] 🔴 -3.3% 51,491 ops/sec [50,936..51,833] → 74,182 ops/sec [73,253..76,001] 🟢 +44.1%
flatMap 28,384 ops/sec [28,108..29,075] → 28,254 ops/sec [27,222..28,552] ~ overlap (-0.5%) 35,606 ops/sec [35,380..35,847] → 49,796 ops/sec [49,044..50,087] 🟢 +39.9%
map inside map (5x5) 7,548 ops/sec [7,511..7,855] → 7,396 ops/sec [7,302..7,491] 🔴 -2.0% 10,387 ops/sec [10,137..10,548] → 14,493 ops/sec [14,313..14,751] 🟢 +39.5%
filter inside map (5x10) 5,411 ops/sec [5,381..5,466] → 5,306 ops/sec [5,267..5,331] 🔴 -1.9% 8,499 ops/sec [8,346..8,669] → 11,276 ops/sec [11,079..11,338] 🟢 +32.7%
reduce inside map (5x10) 6,163 ops/sec [6,028..6,226] → 5,911 ops/sec [5,902..5,939] 🔴 -4.1% 9,549 ops/sec [9,306..9,768] → 12,857 ops/sec [12,800..12,974] 🟢 +34.7%
forEach inside forEach (5x10) 5,361 ops/sec [5,348..5,418] → 5,066 ops/sec [5,008..5,145] 🔴 -5.5% 10,133 ops/sec [10,088..10,161] → 13,110 ops/sec [12,619..13,521] 🟢 +29.4%
find inside some (10x10) 4,527 ops/sec [4,388..4,568] → 4,266 ops/sec [4,232..4,320] 🔴 -5.8% 7,629 ops/sec [7,518..7,731] → 9,762 ops/sec [9,553..10,026] 🟢 +28.0%
map+filter chain nested (5x20) 1,658 ops/sec [1,625..1,686] → 1,613 ops/sec [1,601..1,622] 🔴 -2.7% 2,803 ops/sec [2,673..2,845] → 3,529 ops/sec [3,489..3,581] 🟢 +25.9%
reduce flatten (10x5) 16,699 ops/sec [14,732..17,210] → 17,224 ops/sec [17,024..17,356] ~ overlap (+3.1%) 7,281 ops/sec [7,234..7,309] → 9,596 ops/sec [9,284..9,744] 🟢 +31.8%
async-await.js — Interp: 6 unch. · avg -1.0% · Bytecode: 🟢 6 · avg +24.9%
Benchmark Interpreted Δ Bytecode Δ
single await 152,265 ops/sec [112,858..155,078] → 149,185 ops/sec [140,718..151,240] ~ overlap (-2.0%) 167,593 ops/sec [154,988..169,441] → 204,381 ops/sec [188,615..210,803] 🟢 +22.0%
multiple awaits 72,795 ops/sec [70,472..74,392] → 71,649 ops/sec [67,160..73,053] ~ overlap (-1.6%) 74,535 ops/sec [73,817..75,018] → 91,281 ops/sec [89,843..92,046] 🟢 +22.5%
await non-Promise value 295,817 ops/sec [293,032..299,654] → 290,135 ops/sec [267,971..296,539] ~ overlap (-1.9%) 435,839 ops/sec [424,154..448,581] → 556,784 ops/sec [552,648..564,091] 🟢 +27.7%
await with try/catch 124,702 ops/sec [123,505..125,739] → 125,038 ops/sec [122,897..125,650] ~ overlap (+0.3%) 159,732 ops/sec [130,390..164,924] → 201,204 ops/sec [199,422..203,085] 🟢 +26.0%
await Promise.all 24,505 ops/sec [24,163..24,749] → 24,636 ops/sec [24,213..24,758] ~ overlap (+0.5%) 24,042 ops/sec [23,947..24,077] → 30,453 ops/sec [30,254..30,745] 🟢 +26.7%
nested async function call 80,967 ops/sec [80,231..81,043] → 79,837 ops/sec [78,805..80,231] ~ overlap (-1.4%) 102,991 ops/sec [102,134..103,334] → 128,492 ops/sec [128,090..129,284] 🟢 +24.8%
async-generators.js — Interp: 2 unch. · avg -5.8% · Bytecode: 🟢 2 · avg +28.2%
Benchmark Interpreted Δ Bytecode Δ
for-await-of over async generator 2,515 ops/sec [1,784..2,533] → 2,360 ops/sec [2,204..2,538] ~ overlap (-6.1%) 2,919 ops/sec [2,849..2,923] → 3,746 ops/sec [3,708..3,762] 🟢 +28.3%
async generator with await in body 23,448 ops/sec [22,576..23,787] → 22,162 ops/sec [20,067..22,695] ~ overlap (-5.5%) 24,603 ops/sec [24,000..24,785] → 31,488 ops/sec [31,067..32,004] 🟢 +28.0%
base64.js — Interp: 🔴 8, 2 unch. · avg -48.4% · Bytecode: 🟢 3, 🔴 7 · avg -35.2%
Benchmark Interpreted Δ Bytecode Δ
short ASCII (13 chars) 4,086 ops/sec [3,983..4,175] → 4,009 ops/sec [3,992..4,047] ~ overlap (-1.9%) 4,057 ops/sec [3,877..4,105] → 5,212 ops/sec [5,169..5,302] 🟢 +28.5%
medium ASCII (450 chars) 153 ops/sec [150..157] → 149 ops/sec [147..150] ~ overlap (-2.6%) 150 ops/sec [149..151] → 193 ops/sec [187..196] 🟢 +28.6%
Latin-1 characters 5,971 ops/sec [5,936..6,011] → 5,738 ops/sec [5,645..5,810] 🔴 -3.9% 5,987 ops/sec [5,969..6,048] → 7,531 ops/sec [7,338..7,585] 🟢 +25.8%
short base64 (20 chars) 2,583 ops/sec [2,567..2,595] → 740 ops/sec [736..744] 🔴 -71.3% 2,595 ops/sec [2,539..2,615] → 877 ops/sec [873..882] 🔴 -66.2%
medium base64 (600 chars) 107 ops/sec [100..109] → 27 ops/sec [27..27] 🔴 -74.6% 107 ops/sec [105..108] → 32 ops/sec [31..32] 🔴 -70.2%
Latin-1 output 3,654 ops/sec [3,649..3,678] → 1,162 ops/sec [1,153..1,167] 🔴 -68.2% 3,758 ops/sec [3,708..3,806] → 1,336 ops/sec [1,329..1,348] 🔴 -64.5%
forgiving (no padding) 5,494 ops/sec [5,462..5,561] → 1,820 ops/sec [1,814..1,827] 🔴 -66.9% 5,576 ops/sec [5,549..5,584] → 2,112 ops/sec [2,103..2,123] 🔴 -62.1%
with whitespace 2,339 ops/sec [2,290..2,362] → 703 ops/sec [701..704] 🔴 -70.0% 2,289 ops/sec [2,278..2,292] → 825 ops/sec [785..832] 🔴 -63.9%
atob(btoa(short)) 1,579 ops/sec [1,551..1,595] → 621 ops/sec [597..629] 🔴 -60.6% 1,553 ops/sec [1,538..1,581] → 752 ops/sec [729..757] 🔴 -51.6%
atob(btoa(medium)) 63 ops/sec [62..64] → 23 ops/sec [23..23] 🔴 -63.6% 62 ops/sec [62..63] → 27 ops/sec [27..27] 🔴 -56.0%
classes.js — Interp: 🔴 7, 24 unch. · avg -1.2% · Bytecode: 🟢 31 · avg +27.1%
Benchmark Interpreted Δ Bytecode Δ
simple class new 57,262 ops/sec [57,006..57,647] → 57,754 ops/sec [57,556..57,881] ~ overlap (+0.9%) 75,775 ops/sec [73,542..76,064] → 96,767 ops/sec [95,238..97,266] 🟢 +27.7%
class with defaults 47,119 ops/sec [46,921..47,246] → 46,268 ops/sec [46,056..46,653] 🔴 -1.8% 53,993 ops/sec [53,823..54,233] → 69,894 ops/sec [69,034..70,247] 🟢 +29.4%
50 instances via Array.from 2,141 ops/sec [2,108..2,154] → 2,101 ops/sec [2,092..2,141] ~ overlap (-1.9%) 2,863 ops/sec [2,799..2,890] → 3,663 ops/sec [3,646..3,765] 🟢 +27.9%
instance method call 28,129 ops/sec [28,100..28,262] → 28,271 ops/sec [28,138..28,381] ~ overlap (+0.5%) 37,228 ops/sec [36,842..37,630] → 47,023 ops/sec [46,936..47,227] 🟢 +26.3%
static method call 44,571 ops/sec [44,210..44,934] → 45,321 ops/sec [44,901..45,418] ~ overlap (+1.7%) 75,532 ops/sec [74,479..77,996] → 96,516 ops/sec [96,346..97,496] 🟢 +27.8%
single-level inheritance 23,405 ops/sec [23,165..23,483] → 23,370 ops/sec [23,203..23,553] ~ overlap (-0.2%) 28,575 ops/sec [28,411..28,683] → 35,965 ops/sec [35,522..36,508] 🟢 +25.9%
two-level inheritance 20,297 ops/sec [20,150..20,469] → 20,296 ops/sec [20,084..20,425] ~ overlap (-0.0%) 22,653 ops/sec [22,288..22,932] → 28,722 ops/sec [28,334..29,634] 🟢 +26.8%
private field access 31,522 ops/sec [31,429..31,602] → 31,246 ops/sec [31,017..31,409] 🔴 -0.9% 26,358 ops/sec [26,122..26,693] → 33,549 ops/sec [33,138..33,571] 🟢 +27.3%
private methods 34,528 ops/sec [34,086..34,646] → 33,956 ops/sec [33,672..34,129] ~ overlap (-1.7%) 30,037 ops/sec [29,919..30,212] → 38,149 ops/sec [37,423..38,883] 🟢 +27.0%
getter/setter access 30,939 ops/sec [27,609..31,210] → 30,757 ops/sec [30,563..30,880] ~ overlap (-0.6%) 40,064 ops/sec [39,074..40,132] → 50,581 ops/sec [48,428..52,162] 🟢 +26.3%
class decorator (identity) 42,878 ops/sec [42,652..43,655] → 42,198 ops/sec [41,993..42,619] 🔴 -1.6% 45,606 ops/sec [45,211..46,629] → 57,648 ops/sec [53,698..59,244] 🟢 +26.4%
class decorator (wrapping) 25,245 ops/sec [24,633..25,420] → 24,406 ops/sec [24,204..24,656] ~ overlap (-3.3%) 24,303 ops/sec [24,267..24,321] → 30,482 ops/sec [28,740..31,654] 🟢 +25.4%
identity method decorator 31,688 ops/sec [31,012..31,818] → 30,589 ops/sec [30,140..31,005] 🔴 -3.5% 38,451 ops/sec [37,439..41,082] → 48,977 ops/sec [47,015..51,750] 🟢 +27.4%
wrapping method decorator 25,386 ops/sec [25,198..25,911] → 24,679 ops/sec [24,346..24,770] 🔴 -2.8% 28,825 ops/sec [27,949..30,350] → 36,617 ops/sec [35,159..38,575] 🟢 +27.0%
stacked method decorators (x3) 17,401 ops/sec [17,178..17,738] → 17,494 ops/sec [15,575..17,643] ~ overlap (+0.5%) 20,592 ops/sec [19,265..22,289] → 26,348 ops/sec [25,872..27,351] 🟢 +28.0%
identity field decorator 35,003 ops/sec [34,435..36,108] → 34,855 ops/sec [34,246..35,188] ~ overlap (-0.4%) 34,044 ops/sec [32,448..34,454] → 47,005 ops/sec [43,152..49,911] 🟢 +38.1%
field initializer decorator 29,058 ops/sec [28,835..30,058] → 28,290 ops/sec [27,871..29,214] ~ overlap (-2.6%) 30,910 ops/sec [29,587..31,673] → 38,904 ops/sec [38,402..39,056] 🟢 +25.9%
getter decorator (identity) 31,276 ops/sec [29,777..31,501] → 30,832 ops/sec [30,702..30,949] ~ overlap (-1.4%) 30,418 ops/sec [30,182..30,815] → 38,018 ops/sec [37,562..38,323] 🟢 +25.0%
setter decorator (identity) 26,095 ops/sec [24,061..26,700] → 25,433 ops/sec [25,093..25,641] ~ overlap (-2.5%) 24,354 ops/sec [23,822..24,677] → 30,746 ops/sec [29,798..30,970] 🟢 +26.2%
static method decorator 33,080 ops/sec [32,690..33,637] → 32,162 ops/sec [31,837..32,819] ~ overlap (-2.8%) 39,700 ops/sec [38,934..41,344] → 50,285 ops/sec [48,874..51,491] 🟢 +26.7%
static field decorator 39,037 ops/sec [38,517..39,616] → 38,420 ops/sec [37,486..38,839] ~ overlap (-1.6%) 41,309 ops/sec [39,283..42,131] → 51,528 ops/sec [50,103..53,545] 🟢 +24.7%
private method decorator 26,173 ops/sec [25,267..26,794] → 25,524 ops/sec [25,253..26,036] ~ overlap (-2.5%) 28,556 ops/sec [27,963..29,292] → 37,384 ops/sec [35,613..38,734] 🟢 +30.9%
private field decorator 28,606 ops/sec [25,498..29,361] → 28,289 ops/sec [27,359..28,405] ~ overlap (-1.1%) 25,712 ops/sec [25,343..26,039] → 33,132 ops/sec [32,359..34,103] 🟢 +28.9%
plain auto-accessor (no decorator) 47,792 ops/sec [47,185..49,554] → 47,120 ops/sec [46,654..48,916] ~ overlap (-1.4%) 44,986 ops/sec [43,034..47,626] → 54,680 ops/sec [51,680..56,629] 🟢 +21.6%
auto-accessor with decorator 27,111 ops/sec [26,132..28,939] → 27,646 ops/sec [27,109..28,678] ~ overlap (+2.0%) 27,084 ops/sec [25,533..28,465] → 34,003 ops/sec [32,926..35,906] 🟢 +25.5%
decorator writing metadata 22,072 ops/sec [21,888..22,831] → 21,589 ops/sec [21,427..22,317] ~ overlap (-2.2%) 23,328 ops/sec [23,112..23,741] → 30,500 ops/sec [29,876..32,229] 🟢 +30.7%
static getter read 56,395 ops/sec [55,368..56,947] → 56,218 ops/sec [55,581..56,624] ~ overlap (-0.3%) 69,630 ops/sec [69,086..71,211] → 88,320 ops/sec [86,617..90,169] 🟢 +26.8%
static getter/setter pair 41,870 ops/sec [41,264..41,986] → 40,565 ops/sec [40,366..41,196] 🔴 -3.1% 52,676 ops/sec [51,996..54,154] → 66,125 ops/sec [65,338..66,308] 🟢 +25.5%
inherited static getter 33,632 ops/sec [33,191..34,353] → 34,155 ops/sec [33,504..34,404] ~ overlap (+1.6%) 38,836 ops/sec [38,017..40,099] → 48,360 ops/sec [47,450..49,386] 🟢 +24.5%
inherited static setter 36,583 ops/sec [34,401..36,812] → 35,830 ops/sec [35,556..36,262] ~ overlap (-2.1%) 41,738 ops/sec [40,372..43,491] → 53,146 ops/sec [51,054..54,333] 🟢 +27.3%
inherited static getter with this binding 30,426 ops/sec [30,279..30,924] → 29,441 ops/sec [29,361..29,798] 🔴 -3.2% 34,207 ops/sec [33,459..34,780] → 42,500 ops/sec [41,681..43,308] 🟢 +24.2%
closures.js — Interp: 🔴 6, 5 unch. · avg -3.2% · Bytecode: 🟢 11 · avg +29.5%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 48,160 ops/sec [47,462..48,947] → 46,318 ops/sec [46,140..46,495] 🔴 -3.8% 165,043 ops/sec [163,011..166,097] → 215,870 ops/sec [212,977..219,857] 🟢 +30.8%
closure over multiple variables 49,704 ops/sec [48,673..50,383] → 46,873 ops/sec [46,597..47,076] 🔴 -5.7% 146,855 ops/sec [144,983..147,755] → 189,103 ops/sec [187,479..190,951] 🟢 +28.8%
nested closures 54,098 ops/sec [53,315..55,248] → 52,979 ops/sec [52,374..53,794] ~ overlap (-2.1%) 143,917 ops/sec [142,051..145,080] → 185,113 ops/sec [180,738..190,362] 🟢 +28.6%
function as argument 36,244 ops/sec [35,826..37,486] → 35,324 ops/sec [34,761..35,623] 🔴 -2.5% 154,497 ops/sec [151,546..156,562] → 207,168 ops/sec [205,931..207,514] 🟢 +34.1%
function returning function 46,962 ops/sec [46,400..47,604] → 46,388 ops/sec [45,302..47,190] ~ overlap (-1.2%) 168,842 ops/sec [162,716..171,627] → 218,252 ops/sec [212,660..225,857] 🟢 +29.3%
compose two functions 28,778 ops/sec [28,160..29,333] → 28,503 ops/sec [28,139..29,389] ~ overlap (-1.0%) 97,623 ops/sec [96,672..98,542] → 125,471 ops/sec [121,338..129,570] 🟢 +28.5%
fn.call 65,092 ops/sec [62,052..65,561] → 62,850 ops/sec [62,190..64,196] ~ overlap (-3.4%) 99,623 ops/sec [98,847..100,886] → 124,778 ops/sec [123,914..125,505] 🟢 +25.2%
fn.apply 51,161 ops/sec [50,493..51,647] → 48,619 ops/sec [48,189..49,740] 🔴 -5.0% 95,731 ops/sec [95,700..95,874] → 117,894 ops/sec [115,584..121,816] 🟢 +23.2%
fn.bind 62,129 ops/sec [61,647..62,770] → 59,662 ops/sec [58,797..61,388] 🔴 -4.0% 169,427 ops/sec [166,550..170,115] → 216,235 ops/sec [208,760..222,538] 🟢 +27.6%
recursive sum to 50 4,207 ops/sec [4,017..4,244] → 4,129 ops/sec [4,061..4,144] ~ overlap (-1.8%) 20,296 ops/sec [19,754..21,341] → 28,370 ops/sec [28,141..28,572] 🟢 +39.8%
recursive tree traversal 7,956 ops/sec [7,775..8,005] → 7,553 ops/sec [7,345..7,656] 🔴 -5.1% 20,526 ops/sec [20,428..20,622] → 26,322 ops/sec [25,602..26,665] 🟢 +28.2%
collections.js — Interp: 🔴 6, 6 unch. · avg -2.0% · Bytecode: 🟢 12 · avg +20.6%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 3,409 ops/sec [3,393..3,428] → 3,272 ops/sec [3,235..3,331] 🔴 -4.0% 3,864 ops/sec [3,843..3,889] → 4,754 ops/sec [4,523..4,804] 🟢 +23.0%
has lookup (50 elements) 50,238 ops/sec [49,744..50,747] → 49,805 ops/sec [49,133..50,118] ~ overlap (-0.9%) 56,479 ops/sec [55,691..56,862] → 66,753 ops/sec [66,061..68,209] 🟢 +18.2%
delete elements 28,037 ops/sec [27,839..28,160] → 27,200 ops/sec [27,114..27,284] 🔴 -3.0% 29,387 ops/sec [29,268..29,580] → 35,869 ops/sec [30,997..36,524] 🟢 +22.1%
forEach iteration 5,762 ops/sec [5,672..5,811] → 5,502 ops/sec [5,415..5,554] 🔴 -4.5% 10,166 ops/sec [10,022..10,301] → 12,560 ops/sec [12,437..12,774] 🟢 +23.5%
spread to array 16,882 ops/sec [16,585..17,097] → 17,259 ops/sec [17,037..17,440] ~ overlap (+2.2%) 109,974 ops/sec [108,118..111,563] → 132,825 ops/sec [125,031..147,035] 🟢 +20.8%
deduplicate array 22,973 ops/sec [22,473..23,247] → 22,671 ops/sec [22,219..22,864] ~ overlap (-1.3%) 38,475 ops/sec [38,235..38,991] → 48,498 ops/sec [48,142..48,892] 🟢 +26.1%
set 50 entries 2,480 ops/sec [2,457..2,527] → 2,505 ops/sec [2,467..2,524] ~ overlap (+1.0%) 3,050 ops/sec [3,021..3,078] → 3,562 ops/sec [3,446..3,709] 🟢 +16.8%
get lookup (50 entries) 51,054 ops/sec [50,625..51,703] → 49,475 ops/sec [49,008..50,064] 🔴 -3.1% 53,474 ops/sec [52,951..54,111] → 61,138 ops/sec [60,937..61,224] 🟢 +14.3%
has check 72,374 ops/sec [70,172..74,811] → 70,232 ops/sec [69,622..71,529] ~ overlap (-3.0%) 80,182 ops/sec [79,276..80,813] → 91,010 ops/sec [90,368..93,933] 🟢 +13.5%
delete entries 28,248 ops/sec [27,719..28,357] → 27,150 ops/sec [26,777..27,367] 🔴 -3.9% 28,826 ops/sec [28,383..28,951] → 33,498 ops/sec [32,889..33,692] 🟢 +16.2%
forEach iteration 5,709 ops/sec [5,651..5,783] → 5,515 ops/sec [5,432..5,553] 🔴 -3.4% 10,480 ops/sec [10,265..10,625] → 13,055 ops/sec [12,823..13,164] 🟢 +24.6%
keys/values/entries 4,638 ops/sec [4,551..4,721] → 4,645 ops/sec [4,589..4,668] ~ overlap (+0.1%) 14,889 ops/sec [14,671..14,911] → 19,156 ops/sec [18,896..19,241] 🟢 +28.7%
csv.js — Interp: 🔴 13 · avg -8.7% · Bytecode: 🟢 13 · avg +29.7%
Benchmark Interpreted Δ Bytecode Δ
parse simple 3-column CSV 50,800 ops/sec [49,845..51,422] → 47,698 ops/sec [46,830..48,197] 🔴 -6.1% 53,840 ops/sec [52,403..56,670] → 69,434 ops/sec [67,724..70,091] 🟢 +29.0%
parse 10-row CSV 14,509 ops/sec [14,288..14,652] → 13,196 ops/sec [13,125..13,243] 🔴 -9.1% 15,540 ops/sec [15,267..15,765] → 19,320 ops/sec [18,929..20,406] 🟢 +24.3%
parse 100-row CSV 2,209 ops/sec [2,178..2,260] → 2,052 ops/sec [2,017..2,080] 🔴 -7.1% 2,278 ops/sec [2,221..2,393] → 2,988 ops/sec [2,837..3,021] 🟢 +31.2%
parse CSV with quoted fields 75,815 ops/sec [74,472..77,453] → 69,513 ops/sec [69,101..70,684] 🔴 -8.3% 78,000 ops/sec [77,015..78,201] → 100,424 ops/sec [97,539..103,606] 🟢 +28.7%
parse without headers (array of arrays) 6,137 ops/sec [6,010..6,436] → 5,736 ops/sec [5,629..5,761] 🔴 -6.5% 6,355 ops/sec [6,264..6,464] → 8,134 ops/sec [7,858..8,369] 🟢 +28.0%
parse with semicolon delimiter 10,742 ops/sec [10,623..10,839] → 9,657 ops/sec [9,587..10,116] 🔴 -10.1% 10,512 ops/sec [10,433..10,748] → 14,035 ops/sec [13,817..14,597] 🟢 +33.5%
stringify array of objects 72,484 ops/sec [67,903..76,609] → 64,222 ops/sec [63,976..64,656] 🔴 -11.4% 77,725 ops/sec [76,760..78,105] → 100,751 ops/sec [98,664..104,363] 🟢 +29.6%
stringify array of arrays 26,210 ops/sec [25,669..27,323] → 22,830 ops/sec [22,795..22,866] 🔴 -12.9% 26,762 ops/sec [26,339..26,919] → 35,271 ops/sec [34,967..36,152] 🟢 +31.8%
stringify with values needing escaping 56,696 ops/sec [55,147..58,721] → 48,996 ops/sec [48,794..49,063] 🔴 -13.6% 56,852 ops/sec [56,685..57,129] → 77,301 ops/sec [76,144..77,825] 🟢 +36.0%
reviver converts numbers 1,430 ops/sec [1,425..1,443] → 1,364 ops/sec [1,339..1,379] 🔴 -4.6% 1,640 ops/sec [1,632..1,672] → 2,057 ops/sec [2,045..2,121] 🟢 +25.4%
reviver filters empty to null 11,535 ops/sec [11,199..11,683] → 10,809 ops/sec [10,742..10,819] 🔴 -6.3% 13,892 ops/sec [13,818..14,032] → 18,143 ops/sec [17,650..18,269] 🟢 +30.6%
parse then stringify 9,057 ops/sec [8,963..9,098] → 8,258 ops/sec [8,063..8,266] 🔴 -8.8% 8,906 ops/sec [8,840..9,065] → 11,767 ops/sec [11,698..11,841] 🟢 +32.1%
stringify then parse 8,745 ops/sec [8,531..8,880] → 8,061 ops/sec [7,989..8,149] 🔴 -7.8% 8,633 ops/sec [8,452..8,841] → 10,842 ops/sec [10,437..11,205] 🟢 +25.6%
destructuring.js — Interp: 🔴 5, 17 unch. · avg -1.5% · Bytecode: 🟢 22 · avg +26.7%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 179,879 ops/sec [177,650..185,110] → 172,008 ops/sec [170,328..175,945] 🔴 -4.4% 124,290 ops/sec [122,396..129,822] → 156,724 ops/sec [156,030..158,986] 🟢 +26.1%
with rest element 123,040 ops/sec [121,541..125,777] → 118,056 ops/sec [114,170..120,438] 🔴 -4.1% 96,166 ops/sec [93,565..97,844] → 120,925 ops/sec [120,260..121,927] 🟢 +25.7%
with defaults 179,603 ops/sec [176,152..185,437] → 176,287 ops/sec [176,054..176,496] ~ overlap (-1.8%) 128,180 ops/sec [126,912..132,808] → 166,024 ops/sec [163,992..169,236] 🟢 +29.5%
skip elements 188,929 ops/sec [187,087..191,565] → 186,181 ops/sec [182,347..188,188] ~ overlap (-1.5%) 132,948 ops/sec [130,996..140,444] → 172,757 ops/sec [171,007..174,545] 🟢 +29.9%
nested array destructuring 88,429 ops/sec [87,413..91,845] → 89,836 ops/sec [89,191..90,861] ~ overlap (+1.6%) 45,323 ops/sec [45,013..46,132] → 56,386 ops/sec [55,891..57,843] 🟢 +24.4%
swap variables 212,722 ops/sec [209,498..213,271] → 213,203 ops/sec [210,082..215,431] ~ overlap (+0.2%) 160,128 ops/sec [159,182..173,763] → 203,211 ops/sec [200,162..211,001] 🟢 +26.9%
simple object destructuring 149,917 ops/sec [140,092..155,592] → 142,237 ops/sec [137,666..144,676] ~ overlap (-5.1%) 173,315 ops/sec [169,799..178,212] → 215,310 ops/sec [212,383..227,733] 🟢 +24.2%
with defaults 165,757 ops/sec [163,818..166,894] → 159,002 ops/sec [156,600..161,491] 🔴 -4.1% 221,398 ops/sec [214,199..225,846] → 267,738 ops/sec [264,925..275,560] 🟢 +20.9%
with renaming 153,708 ops/sec [149,787..157,429] → 152,816 ops/sec [151,652..154,985] ~ overlap (-0.6%) 160,590 ops/sec [157,634..172,937] → 213,352 ops/sec [206,856..216,633] 🟢 +32.9%
nested object destructuring 77,314 ops/sec [75,408..79,738] → 77,962 ops/sec [76,461..79,786] ~ overlap (+0.8%) 85,681 ops/sec [82,638..89,358] → 110,037 ops/sec [107,921..115,579] 🟢 +28.4%
rest properties 58,447 ops/sec [57,618..59,859] → 53,962 ops/sec [53,635..55,636] 🔴 -7.7% 82,312 ops/sec [79,538..85,982] → 102,187 ops/sec [100,676..104,418] 🟢 +24.1%
object parameter 45,135 ops/sec [43,801..46,137] → 44,648 ops/sec [43,272..44,834] ~ overlap (-1.1%) 71,269 ops/sec [69,617..74,745] → 92,331 ops/sec [89,141..93,922] 🟢 +29.6%
array parameter 55,654 ops/sec [54,808..56,259] → 55,763 ops/sec [54,454..56,277] ~ overlap (+0.2%) 62,712 ops/sec [60,113..63,786] → 80,845 ops/sec [77,302..84,249] 🟢 +28.9%
mixed destructuring in map 12,598 ops/sec [12,512..12,861] → 12,828 ops/sec [12,511..13,199] ~ overlap (+1.8%) 19,441 ops/sec [19,258..19,659] → 24,345 ops/sec [24,141..24,657] 🟢 +25.2%
forEach with array destructuring 28,184 ops/sec [27,727..28,420] → 27,879 ops/sec [27,812..28,142] ~ overlap (-1.1%) 23,886 ops/sec [23,123..24,842] → 29,758 ops/sec [29,365..30,365] 🟢 +24.6%
map with array destructuring 29,556 ops/sec [29,120..29,893] → 29,360 ops/sec [28,687..29,707] ~ overlap (-0.7%) 22,366 ops/sec [21,532..22,721] → 28,796 ops/sec [27,787..29,496] 🟢 +28.7%
filter with array destructuring 29,956 ops/sec [29,533..30,401] → 29,993 ops/sec [28,332..30,684] ~ overlap (+0.1%) 24,239 ops/sec [23,880..24,425] → 30,794 ops/sec [30,120..30,968] 🟢 +27.0%
reduce with array destructuring 32,554 ops/sec [30,433..33,054] → 32,405 ops/sec [31,166..32,758] ~ overlap (-0.5%) 24,406 ops/sec [23,775..24,773] → 31,072 ops/sec [30,934..31,875] 🟢 +27.3%
map with object destructuring 28,760 ops/sec [27,899..29,038] → 27,745 ops/sec [26,994..28,373] ~ overlap (-3.5%) 42,171 ops/sec [41,594..43,609] → 52,407 ops/sec [51,750..53,611] 🟢 +24.3%
map with nested destructuring 24,412 ops/sec [24,100..24,800] → 23,541 ops/sec [22,762..23,859] 🔴 -3.6% 38,940 ops/sec [38,220..42,079] → 48,887 ops/sec [48,039..50,495] 🟢 +25.5%
map with rest in destructuring 18,104 ops/sec [17,928..18,257] → 18,438 ops/sec [18,044..18,884] ~ overlap (+1.8%) 12,380 ops/sec [12,075..12,800] → 15,831 ops/sec [15,415..15,905] 🟢 +27.9%
map with defaults in destructuring 22,930 ops/sec [22,429..23,110] → 22,769 ops/sec [22,660..22,998] ~ overlap (-0.7%) 31,080 ops/sec [30,152..32,953] → 38,725 ops/sec [37,719..40,336] 🟢 +24.6%
fibonacci.js — Interp: 🔴 6, 2 unch. · avg -4.2% · Bytecode: 🟢 8 · avg +30.3%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 115 ops/sec [113..118] → 111 ops/sec [109..112] 🔴 -3.3% 580 ops/sec [571..583] → 787 ops/sec [781..792] 🟢 +35.7%
recursive fib(20) 10 ops/sec [10..11] → 10 ops/sec [10..10] ~ overlap (-3.8%) 54 ops/sec [53..54] → 71 ops/sec [70..71] 🟢 +32.1%
recursive fib(15) typed 116 ops/sec [113..122] → 114 ops/sec [113..115] ~ overlap (-2.0%) 599 ops/sec [570..602] → 790 ops/sec [781..806] 🟢 +32.0%
recursive fib(20) typed 11 ops/sec [10..11] → 10 ops/sec [10..10] 🔴 -2.7% 55 ops/sec [54..57] → 72 ops/sec [71..73] 🟢 +29.8%
iterative fib(20) via reduce 5,150 ops/sec [5,142..5,186] → 5,051 ops/sec [5,031..5,115] 🔴 -1.9% 10,239 ops/sec [10,011..10,349] → 12,822 ops/sec [12,626..12,929] 🟢 +25.2%
iterator fib(20) 4,067 ops/sec [3,960..4,179] → 3,780 ops/sec [3,679..3,879] 🔴 -7.1% 6,821 ops/sec [6,502..6,990] → 8,726 ops/sec [8,567..8,908] 🟢 +27.9%
iterator fib(20) via Iterator.from + take 5,369 ops/sec [5,156..5,432] → 5,031 ops/sec [4,992..5,092] 🔴 -6.3% 7,686 ops/sec [7,485..7,893] → 9,809 ops/sec [9,656..10,083] 🟢 +27.6%
iterator fib(20) last value via reduce 4,153 ops/sec [4,123..4,176] → 3,885 ops/sec [3,754..4,017] 🔴 -6.5% 5,691 ops/sec [5,596..5,843] → 7,527 ops/sec [7,267..7,619] 🟢 +32.3%
float16array.js — Interp: 🟢 7, 🔴 11, 14 unch. · avg +1.1% · Bytecode: 🟢 32 · avg +41.1%
Benchmark Interpreted Δ Bytecode Δ
new Float16Array(0) 118,798 ops/sec [114,928..120,863] → 120,080 ops/sec [118,895..120,396] ~ overlap (+1.1%) 144,959 ops/sec [143,858..145,890] → 182,447 ops/sec [181,636..184,426] 🟢 +25.9%
new Float16Array(100) 118,855 ops/sec [114,117..121,557] → 113,916 ops/sec [113,011..115,911] ~ overlap (-4.2%) 136,431 ops/sec [135,301..137,222] → 176,690 ops/sec [174,825..177,879] 🟢 +29.5%
new Float16Array(1000) 99,392 ops/sec [98,870..99,585] → 103,266 ops/sec [102,160..104,302] 🟢 +3.9% 109,844 ops/sec [109,142..110,789] → 144,108 ops/sec [143,490..144,405] 🟢 +31.2%
Float16Array.from([...100]) 5,167 ops/sec [5,101..5,212] → 5,075 ops/sec [5,056..5,111] ~ overlap (-1.8%) 5,040 ops/sec [4,955..5,125] → 6,507 ops/sec [6,358..6,582] 🟢 +29.1%
Float16Array.of(1.5, 2.5, 3.5, 4.5, 5.5) 138,673 ops/sec [135,685..140,976] → 131,273 ops/sec [130,438..132,998] 🔴 -5.3% 107,457 ops/sec [106,545..107,738] → 142,609 ops/sec [141,845..142,642] 🟢 +32.7%
new Float16Array(float64Array) 88,606 ops/sec [87,361..89,058] → 80,372 ops/sec [79,824..80,611] 🔴 -9.3% 91,749 ops/sec [90,149..93,477] → 122,095 ops/sec [119,278..124,865] 🟢 +33.1%
sequential write 100 elements 1,452 ops/sec [1,435..1,476] → 1,386 ops/sec [1,380..1,390] 🔴 -4.5% 4,268 ops/sec [4,219..4,309] → 5,741 ops/sec [5,723..5,744] 🟢 +34.5%
sequential read 100 elements 1,666 ops/sec [1,636..1,680] → 1,542 ops/sec [1,532..1,557] 🔴 -7.5% 5,799 ops/sec [5,747..5,954] → 7,870 ops/sec [7,691..8,071] 🟢 +35.7%
write special values (NaN, Inf, -0) 69,343 ops/sec [68,374..69,826] → 65,207 ops/sec [64,230..66,487] 🔴 -6.0% 131,975 ops/sec [129,232..134,281] → 166,690 ops/sec [162,537..173,139] 🟢 +26.3%
Float16Array write 1,465 ops/sec [1,450..1,490] → 1,368 ops/sec [1,354..1,389] 🔴 -6.6% 4,313 ops/sec [4,233..4,361] → 5,773 ops/sec [5,663..6,009] 🟢 +33.9%
Float32Array write 1,447 ops/sec [1,439..1,454] → 1,398 ops/sec [1,385..1,421] 🔴 -3.4% 4,301 ops/sec [4,251..4,386] → 5,811 ops/sec [5,727..5,944] 🟢 +35.1%
Float64Array write 1,438 ops/sec [1,429..1,444] → 1,391 ops/sec [1,377..1,400] 🔴 -3.2% 4,363 ops/sec [4,230..4,496] → 5,795 ops/sec [5,693..6,083] 🟢 +32.8%
Float16Array read 1,537 ops/sec [1,515..1,565] → 1,501 ops/sec [1,481..1,518] ~ overlap (-2.4%) 5,437 ops/sec [5,340..5,520] → 7,611 ops/sec [7,536..7,631] 🟢 +40.0%
Float32Array read 1,550 ops/sec [1,541..1,563] → 1,541 ops/sec [1,520..1,554] ~ overlap (-0.5%) 6,230 ops/sec [6,101..6,527] → 8,103 ops/sec [7,983..8,233] 🟢 +30.1%
Float64Array read 1,569 ops/sec [1,538..1,579] → 1,549 ops/sec [1,547..1,556] ~ overlap (-1.3%) 6,210 ops/sec [6,004..6,471] → 8,028 ops/sec [7,940..8,108] 🟢 +29.3%
fill(1.5) 22,738 ops/sec [22,610..22,967] → 23,256 ops/sec [23,017..23,540] 🟢 +2.3% 23,220 ops/sec [22,948..23,626] → 30,769 ops/sec [30,526..31,427] 🟢 +32.5%
slice() 85,527 ops/sec [84,892..86,097] → 84,840 ops/sec [84,370..85,397] ~ overlap (-0.8%) 93,778 ops/sec [93,122..95,187] → 124,251 ops/sec [122,725..124,752] 🟢 +32.5%
map(x => x * 2) 2,779 ops/sec [2,740..2,798] → 2,835 ops/sec [2,813..2,864] 🟢 +2.0% 3,505 ops/sec [3,479..3,533] → 5,051 ops/sec [4,865..5,138] 🟢 +44.1%
filter(x => x > 25) 2,834 ops/sec [2,793..2,875] → 2,778 ops/sec [2,758..2,810] ~ overlap (-2.0%) 4,163 ops/sec [4,121..4,246] → 5,663 ops/sec [5,618..5,690] 🟢 +36.0%
reduce (sum) 2,821 ops/sec [2,779..2,843] → 2,744 ops/sec [2,716..2,778] 🔴 -2.7% 3,477 ops/sec [3,400..3,548] → 4,641 ops/sec [4,606..4,710] 🟢 +33.5%
sort() 17,617 ops/sec [17,539..17,652] → 23,439 ops/sec [23,352..23,578] 🟢 +33.0% 10,798 ops/sec [10,709..11,042] → 23,223 ops/sec [23,022..23,297] 🟢 +115.1%
indexOf() 99,547 ops/sec [97,000..100,241] → 117,046 ops/sec [116,440..118,042] 🟢 +17.6% 73,561 ops/sec [73,232..73,641] → 137,457 ops/sec [136,153..138,010] 🟢 +86.9%
reverse() 113,040 ops/sec [112,014..113,761] → 112,011 ops/sec [111,061..113,174] ~ overlap (-0.9%) 127,432 ops/sec [126,655..128,304] → 166,727 ops/sec [163,883..170,286] 🟢 +30.8%
toReversed() 49,598 ops/sec [49,084..50,227] → 58,620 ops/sec [58,097..59,256] 🟢 +18.2% 35,807 ops/sec [34,954..36,191] → 65,758 ops/sec [65,238..67,108] 🟢 +83.6%
toSorted() 683 ops/sec [675..685] → 943 ops/sec [937..948] 🟢 +38.1% 418 ops/sec [417..421] → 894 ops/sec [892..898] 🟢 +114.0%
create view over existing buffer 143,006 ops/sec [142,737..143,489] → 135,806 ops/sec [133,830..137,946] 🔴 -5.0% 164,933 ops/sec [161,222..169,231] → 210,825 ops/sec [206,549..217,414] 🟢 +27.8%
subarray() 195,184 ops/sec [194,458..195,649] → 189,542 ops/sec [187,412..194,888] ~ overlap (-2.9%) 227,634 ops/sec [223,174..240,378] → 292,896 ops/sec [286,908..295,847] 🟢 +28.7%
set() from array 217,181 ops/sec [213,858..219,016] → 208,726 ops/sec [207,433..209,407] 🔴 -3.9% 267,402 ops/sec [262,197..276,471] → 344,160 ops/sec [337,358..357,221] 🟢 +28.7%
for-of loop 2,240 ops/sec [2,237..2,252] → 2,243 ops/sec [2,198..2,278] ~ overlap (+0.1%) 8,205 ops/sec [7,924..8,258] → 11,910 ops/sec [11,824..12,255] 🟢 +45.2%
spread into array 9,153 ops/sec [9,126..9,374] → 9,007 ops/sec [8,867..9,140] ~ overlap (-1.6%) 35,557 ops/sec [35,184..36,607] → 47,284 ops/sec [47,121..47,551] 🟢 +33.0%
f16round(1.337) 268,181 ops/sec [266,133..269,603] → 258,828 ops/sec [248,670..266,164] ~ overlap (-3.5%) 248,300 ops/sec [244,176..255,770] → 325,159 ops/sec [322,455..330,061] 🟢 +31.0%
f16round over 100 values 1,563 ops/sec [1,551..1,570] → 1,551 ops/sec [1,533..1,580] ~ overlap (-0.7%) 2,803 ops/sec [2,794..2,808] → 3,760 ops/sec [3,737..3,794] 🟢 +34.2%
for-of.js — Interp: 7 unch. · avg -1.5% · Bytecode: 🟢 7 · avg +29.7%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 20,623 ops/sec [20,285..20,690] → 20,445 ops/sec [20,325..20,806] ~ overlap (-0.9%) 118,073 ops/sec [110,717..118,178] → 153,998 ops/sec [151,085..155,325] 🟢 +30.4%
for...of with 100-element array 2,334 ops/sec [2,262..2,388] → 2,304 ops/sec [2,266..2,339] ~ overlap (-1.3%) 15,603 ops/sec [15,245..15,883] → 20,566 ops/sec [20,485..20,581] 🟢 +31.8%
for...of with string (10 chars) 15,009 ops/sec [14,797..15,153] → 14,906 ops/sec [14,615..15,181] ~ overlap (-0.7%) 34,671 ops/sec [33,994..36,119] → 42,884 ops/sec [41,511..43,732] 🟢 +23.7%
for...of with Set (10 elements) 20,901 ops/sec [20,688..20,972] → 20,589 ops/sec [20,337..20,815] ~ overlap (-1.5%) 115,575 ops/sec [112,856..120,140] → 150,950 ops/sec [147,194..152,471] 🟢 +30.6%
for...of with Map entries (10 entries) 13,943 ops/sec [13,800..14,032] → 13,632 ops/sec [13,515..13,924] ~ overlap (-2.2%) 16,540 ops/sec [16,190..17,169] → 22,056 ops/sec [21,104..22,631] 🟢 +33.3%
for...of with destructuring 17,494 ops/sec [17,390..17,658] → 17,062 ops/sec [16,928..17,422] ~ overlap (-2.5%) 21,552 ops/sec [21,298..21,665] → 27,487 ops/sec [26,926..28,210] 🟢 +27.5%
for-await-of with sync array 19,679 ops/sec [19,498..19,861] → 19,425 ops/sec [19,278..19,677] ~ overlap (-1.3%) 17,235 ops/sec [16,932..18,826] → 22,506 ops/sec [21,771..23,246] 🟢 +30.6%
generators.js — Interp: 🔴 1, 3 unch. · avg -1.2% · Bytecode: 🟢 4 · avg +18.3%
Benchmark Interpreted Δ Bytecode Δ
manual next over object generator 960 ops/sec [946..970] → 929 ops/sec [919..934] 🔴 -3.2% 1,162 ops/sec [1,134..1,188] → 1,367 ops/sec [1,355..1,379] 🟢 +17.6%
for...of over object generator 1,476 ops/sec [1,459..1,528] → 1,465 ops/sec [1,451..1,472] ~ overlap (-0.8%) 2,310 ops/sec [2,289..2,320] → 2,728 ops/sec [2,709..2,764] 🟢 +18.1%
yield delegation 1,481 ops/sec [1,446..1,521] → 1,480 ops/sec [1,454..1,484] ~ overlap (-0.1%) 2,301 ops/sec [2,229..2,411] → 2,711 ops/sec [2,659..2,730] 🟢 +17.8%
class generator method 1,482 ops/sec [1,468..1,550] → 1,474 ops/sec [1,439..1,494] ~ overlap (-0.5%) 2,280 ops/sec [2,229..2,346] → 2,728 ops/sec [2,692..2,870] 🟢 +19.7%
iterators.js — Interp: 🔴 22, 20 unch. · avg -3.2% · Bytecode: 🟢 42 · avg +33.3%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 4,909 ops/sec [4,834..4,944] → 4,624 ops/sec [4,482..4,718] 🔴 -5.8% 6,853 ops/sec [6,467..6,951] → 9,553 ops/sec [9,379..9,803] 🟢 +39.4%
Iterator.from({next}).toArray() — 50 elements 2,061 ops/sec [2,041..2,122] → 2,020 ops/sec [1,947..2,036] 🔴 -2.0% 3,144 ops/sec [3,056..3,219] → 4,125 ops/sec [4,050..4,287] 🟢 +31.2%
spread pre-wrapped iterator — 20 elements 4,913 ops/sec [4,853..5,092] → 4,723 ops/sec [4,641..4,806] 🔴 -3.9% 7,045 ops/sec [6,973..7,114] → 9,445 ops/sec [9,209..9,829] 🟢 +34.1%
Iterator.from({next}).forEach — 50 elements 1,533 ops/sec [1,518..1,550] → 1,461 ops/sec [1,426..1,469] 🔴 -4.7% 2,389 ops/sec [2,358..2,418] → 3,177 ops/sec [3,157..3,204] 🟢 +33.0%
Iterator.from({next}).reduce — 50 elements 1,575 ops/sec [1,569..1,583] → 1,485 ops/sec [1,443..1,506] 🔴 -5.7% 2,295 ops/sec [2,280..2,331] → 3,039 ops/sec [2,987..3,140] 🟢 +32.4%
wrap array iterator 82,375 ops/sec [78,907..82,886] → 77,569 ops/sec [76,589..78,159] 🔴 -5.8% 80,518 ops/sec [79,751..81,632] → 105,462 ops/sec [104,060..106,481] 🟢 +31.0%
wrap plain {next()} object 3,464 ops/sec [3,437..3,471] → 3,250 ops/sec [3,210..3,344] 🔴 -6.2% 5,074 ops/sec [5,010..5,102] → 6,766 ops/sec [6,684..6,970] 🟢 +33.3%
map + toArray (50 elements) 1,614 ops/sec [1,606..1,632] → 1,490 ops/sec [1,465..1,520] 🔴 -7.7% 2,349 ops/sec [2,310..2,385] → 3,032 ops/sec [2,969..3,170] 🟢 +29.1%
filter + toArray (50 elements) 1,581 ops/sec [1,554..1,604] → 1,559 ops/sec [1,542..1,573] ~ overlap (-1.4%) 2,335 ops/sec [2,326..2,343] → 3,088 ops/sec [3,024..3,126] 🟢 +32.2%
take(10) + toArray (50 element source) 9,462 ops/sec [9,356..9,550] → 9,418 ops/sec [9,284..9,531] ~ overlap (-0.5%) 13,502 ops/sec [13,242..13,833] → 19,058 ops/sec [17,447..19,151] 🟢 +41.1%
drop(40) + toArray (50 element source) 2,097 ops/sec [2,082..2,120] → 2,066 ops/sec [2,017..2,123] ~ overlap (-1.5%) 3,047 ops/sec [3,028..3,086] → 4,372 ops/sec [4,261..4,485] 🟢 +43.5%
chained map + filter + take (100 element source) 3,044 ops/sec [2,971..3,247] → 2,849 ops/sec [2,809..2,926] 🔴 -6.4% 4,397 ops/sec [4,348..4,531] → 5,996 ops/sec [5,869..6,058] 🟢 +36.4%
some + every (50 elements) 965 ops/sec [953..970] → 849 ops/sec [833..869] 🔴 -12.0% 1,360 ops/sec [1,308..1,396] → 1,965 ops/sec [1,943..2,009] 🟢 +44.5%
find (50 elements) 2,071 ops/sec [1,986..2,091] → 1,859 ops/sec [1,847..1,893] 🔴 -10.2% 2,934 ops/sec [2,904..2,956] → 3,769 ops/sec [3,710..4,231] 🟢 +28.4%
concat 2 arrays (10 + 10 elements) 75,207 ops/sec [74,668..75,775] → 73,430 ops/sec [66,967..74,171] 🔴 -2.4% 72,452 ops/sec [71,501..73,931] → 95,343 ops/sec [93,003..97,047] 🟢 +31.6%
concat 5 arrays (10 elements each) 45,744 ops/sec [45,606..46,061] → 43,946 ops/sec [43,284..44,080] 🔴 -3.9% 43,754 ops/sec [42,839..44,291] → 56,887 ops/sec [54,713..57,501] 🟢 +30.0%
concat 2 arrays (20 + 20 elements) 67,816 ops/sec [65,335..68,443] → 60,436 ops/sec [59,953..60,863] 🔴 -10.9% 62,029 ops/sec [60,728..62,929] → 80,820 ops/sec [80,007..82,278] 🟢 +30.3%
concat + filter + toArray (20 + 20 elements) 6,508 ops/sec [6,367..6,575] → 6,517 ops/sec [6,452..6,585] ~ overlap (+0.1%) 9,974 ops/sec [9,678..10,113] → 12,497 ops/sec [12,235..12,543] 🟢 +25.3%
concat + map + take (20 + 20 elements, take 10) 20,077 ops/sec [19,926..20,258] → 19,869 ops/sec [19,525..20,086] ~ overlap (-1.0%) 26,385 ops/sec [25,718..26,955] → 34,437 ops/sec [34,378..34,884] 🟢 +30.5%
concat Sets (15 + 15 elements) 69,710 ops/sec [68,393..71,588] → 69,865 ops/sec [69,776..69,976] ~ overlap (+0.2%) 69,745 ops/sec [68,723..70,845] → 89,195 ops/sec [88,021..90,065] 🟢 +27.9%
concat strings (13 + 13 characters) 49,533 ops/sec [48,912..50,162] → 48,057 ops/sec [45,561..48,607] 🔴 -3.0% 46,849 ops/sec [45,086..47,656] → 61,837 ops/sec [61,595..62,151] 🟢 +32.0%
zip 2 arrays (10 + 10 elements) 29,686 ops/sec [29,443..30,006] → 29,213 ops/sec [28,474..29,572] ~ overlap (-1.6%) 28,765 ops/sec [27,960..29,838] → 38,539 ops/sec [38,102..39,169] 🟢 +34.0%
zip 3 arrays (10 elements each) 27,033 ops/sec [26,627..27,202] → 26,906 ops/sec [26,182..27,168] ~ overlap (-0.5%) 26,243 ops/sec [25,626..27,003] → 35,093 ops/sec [34,787..35,382] 🟢 +33.7%
zip 2 arrays (20 + 20 elements) 19,557 ops/sec [18,692..20,070] → 19,364 ops/sec [19,211..19,514] ~ overlap (-1.0%) 18,999 ops/sec [18,667..19,525] → 25,194 ops/sec [25,102..25,686] 🟢 +32.6%
zip 2 arrays (50 + 50 elements) 9,788 ops/sec [9,702..10,069] → 9,952 ops/sec [9,902..10,032] ~ overlap (+1.7%) 9,332 ops/sec [9,204..9,527] → 12,657 ops/sec [12,534..12,762] 🟢 +35.6%
zip shortest mode (20 + 10 elements) 29,002 ops/sec [28,434..29,746] → 29,079 ops/sec [28,365..29,388] ~ overlap (+0.3%) 28,580 ops/sec [28,015..29,662] → 38,696 ops/sec [38,151..39,357] 🟢 +35.4%
zip longest mode (10 + 20 elements) 17,540 ops/sec [15,991..17,938] → 17,412 ops/sec [17,080..17,560] ~ overlap (-0.7%) 16,776 ops/sec [16,483..16,959] → 21,816 ops/sec [21,791..22,071] 🟢 +30.0%
zip strict mode (20 + 20 elements) 18,879 ops/sec [18,581..19,003] → 18,715 ops/sec [18,150..18,824] ~ overlap (-0.9%) 17,913 ops/sec [17,577..18,112] → 24,125 ops/sec [23,611..24,472] 🟢 +34.7%
zip + map + toArray (20 + 20 elements) 7,742 ops/sec [7,581..7,794] → 7,701 ops/sec [7,662..7,740] ~ overlap (-0.5%) 5,603 ops/sec [5,504..5,630] → 7,288 ops/sec [7,216..7,442] 🟢 +30.1%
zip + filter + toArray (20 + 20 elements) 7,841 ops/sec [7,658..7,925] → 7,517 ops/sec [7,482..7,562] 🔴 -4.1% 5,730 ops/sec [5,558..5,778] → 7,464 ops/sec [7,244..7,762] 🟢 +30.3%
zip Sets (15 + 15 elements) 24,681 ops/sec [24,293..25,165] → 24,284 ops/sec [23,387..24,412] ~ overlap (-1.6%) 23,511 ops/sec [23,016..23,716] → 31,351 ops/sec [30,167..31,847] 🟢 +33.3%
zipKeyed 2 keys (10 elements each) 30,260 ops/sec [29,785..30,317] → 29,192 ops/sec [29,020..29,761] 🔴 -3.5% 28,478 ops/sec [28,203..29,165] → 37,784 ops/sec [36,626..39,249] 🟢 +32.7%
zipKeyed 3 keys (20 elements each) 15,485 ops/sec [15,183..15,874] → 15,151 ops/sec [15,075..15,279] ~ overlap (-2.2%) 14,498 ops/sec [14,381..15,006] → 19,397 ops/sec [19,297..19,485] 🟢 +33.8%
zipKeyed longest mode (10 + 20 elements) 17,359 ops/sec [16,547..17,863] → 17,456 ops/sec [17,083..17,560] ~ overlap (+0.6%) 16,256 ops/sec [15,905..16,422] → 21,920 ops/sec [21,258..23,404] 🟢 +34.8%
zipKeyed strict mode (20 + 20 elements) 18,098 ops/sec [17,896..18,836] → 18,264 ops/sec [18,124..18,680] ~ overlap (+0.9%) 17,544 ops/sec [17,303..17,642] → 23,662 ops/sec [22,221..23,939] 🟢 +34.9%
zipKeyed + filter + map (20 elements) 5,628 ops/sec [5,367..5,665] → 5,490 ops/sec [5,468..5,549] ~ overlap (-2.5%) 7,012 ops/sec [6,853..7,083] → 9,563 ops/sec [9,364..9,632] 🟢 +36.4%
array.values().map().filter().toArray() 2,927 ops/sec [2,861..2,945] → 2,753 ops/sec [2,734..2,780] 🔴 -5.9% 4,755 ops/sec [4,699..4,869] → 6,415 ops/sec [6,323..6,474] 🟢 +34.9%
array.values().take(5).toArray() 99,386 ops/sec [98,485..100,205] → 95,905 ops/sec [95,260..97,442] 🔴 -3.5% 103,471 ops/sec [103,080..103,704] → 146,205 ops/sec [143,771..147,910] 🟢 +41.3%
array.values().drop(45).toArray() 82,357 ops/sec [81,238..83,066] → 77,604 ops/sec [76,793..78,872] 🔴 -5.8% 86,890 ops/sec [85,408..88,597] → 121,149 ops/sec [118,763..121,349] 🟢 +39.4%
map.entries() chained helpers 4,287 ops/sec [4,275..4,414] → 4,120 ops/sec [4,026..4,267] 🔴 -3.9% 2,874 ops/sec [2,795..2,921] → 3,824 ops/sec [3,761..3,914] 🟢 +33.0%
set.values() chained helpers 6,733 ops/sec [6,639..6,752] → 6,548 ops/sec [6,495..6,660] ~ overlap (-2.7%) 10,444 ops/sec [10,232..10,529] → 13,110 ops/sec [12,862..13,340] 🟢 +25.5%
string iterator map + toArray 5,642 ops/sec [5,568..5,769] → 5,458 ops/sec [5,400..5,487] 🔴 -3.3% 5,767 ops/sec [5,637..5,854] → 7,298 ops/sec [7,244..7,383] 🟢 +26.6%
json.js — Interp: 🔴 20 · avg -10.4% · Bytecode: 🟢 20 · avg +21.3%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 79,842 ops/sec [78,037..81,568] → 69,243 ops/sec [68,460..72,696] 🔴 -13.3% 82,829 ops/sec [81,531..83,981] → 100,285 ops/sec [99,104..101,812] 🟢 +21.1%
parse nested object 53,773 ops/sec [50,752..54,845] → 45,644 ops/sec [45,296..45,846] 🔴 -15.1% 54,121 ops/sec [53,541..56,900] → 62,527 ops/sec [61,746..62,855] 🟢 +15.5%
parse array of objects 32,115 ops/sec [30,887..32,570] → 27,893 ops/sec [27,558..28,138] 🔴 -13.1% 31,440 ops/sec [30,778..32,651] → 36,796 ops/sec [36,609..37,023] 🟢 +17.0%
parse large flat object 35,315 ops/sec [33,707..36,245] → 29,827 ops/sec [29,594..30,055] 🔴 -15.5% 34,606 ops/sec [34,179..35,825] → 42,515 ops/sec [40,858..44,033] 🟢 +22.9%
parse mixed types 40,110 ops/sec [39,656..40,650] → 35,672 ops/sec [35,243..35,910] 🔴 -11.1% 42,153 ops/sec [41,742..42,502] → 49,111 ops/sec [48,162..49,386] 🟢 +16.5%
stringify simple object 84,135 ops/sec [83,225..85,678] → 76,532 ops/sec [76,437..76,907] 🔴 -9.0% 84,130 ops/sec [83,170..84,836] → 100,293 ops/sec [99,453..100,969] 🟢 +19.2%
stringify nested object 49,867 ops/sec [48,280..52,193] → 44,162 ops/sec [43,976..44,728] 🔴 -11.4% 46,587 ops/sec [46,217..46,934] → 56,744 ops/sec [55,702..57,534] 🟢 +21.8%
stringify array of objects 21,916 ops/sec [21,114..22,570] → 19,383 ops/sec [19,066..19,532] 🔴 -11.6% 21,620 ops/sec [21,503..21,620] → 27,003 ops/sec [26,541..27,685] 🟢 +24.9%
stringify mixed types 33,842 ops/sec [32,524..35,171] → 29,731 ops/sec [29,656..29,987] 🔴 -12.1% 31,911 ops/sec [29,973..34,718] → 38,087 ops/sec [37,795..38,468] 🟢 +19.4%
reviver doubles numbers 15,872 ops/sec [15,521..16,305] → 14,665 ops/sec [14,589..14,814] 🔴 -7.6% 20,669 ops/sec [20,028..21,324] → 23,995 ops/sec [23,715..24,302] 🟢 +16.1%
reviver filters properties 15,005 ops/sec [14,679..15,128] → 13,880 ops/sec [13,744..13,988] 🔴 -7.5% 17,191 ops/sec [17,103..18,166] → 19,892 ops/sec [19,647..20,097] 🟢 +15.7%
reviver on nested object 18,324 ops/sec [17,995..18,804] → 16,878 ops/sec [16,754..17,096] 🔴 -7.9% 22,943 ops/sec [20,839..23,161] → 25,601 ops/sec [24,856..26,028] 🟢 +11.6%
reviver on array 9,751 ops/sec [9,642..9,917] → 9,122 ops/sec [9,000..9,146] 🔴 -6.5% 12,026 ops/sec [11,546..12,120] → 14,563 ops/sec [14,474..14,612] 🟢 +21.1%
replacer function doubles numbers 18,864 ops/sec [18,818..18,880] → 16,724 ops/sec [16,506..16,969] 🔴 -11.3% 22,873 ops/sec [22,581..23,167] → 29,011 ops/sec [28,488..29,253] 🟢 +26.8%
replacer function excludes properties 24,551 ops/sec [24,478..24,637] → 22,748 ops/sec [22,393..22,829] 🔴 -7.3% 27,353 ops/sec [27,156..27,589] → 35,157 ops/sec [34,621..36,210] 🟢 +28.5%
array replacer (allowlist) 53,395 ops/sec [52,769..53,932] → 48,058 ops/sec [47,286..48,563] 🔴 -10.0% 49,884 ops/sec [49,370..50,071] → 61,330 ops/sec [59,755..62,134] 🟢 +22.9%
stringify with 2-space indent 43,048 ops/sec [42,288..43,312] → 39,459 ops/sec [38,774..40,134] 🔴 -8.3% 41,264 ops/sec [40,422..42,059] → 52,116 ops/sec [51,546..53,183] 🟢 +26.3%
stringify with tab indent 43,593 ops/sec [42,416..45,190] → 39,939 ops/sec [38,938..40,988] 🔴 -8.4% 40,612 ops/sec [40,039..40,857] → 51,036 ops/sec [50,413..51,818] 🟢 +25.7%
parse then stringify 26,517 ops/sec [26,097..26,851] → 23,487 ops/sec [23,256..23,854] 🔴 -11.4% 26,028 ops/sec [25,472..26,239] → 32,476 ops/sec [31,898..32,732] 🟢 +24.8%
stringify then parse 15,509 ops/sec [15,494..15,520] → 14,034 ops/sec [13,837..14,408] 🔴 -9.5% 14,943 ops/sec [14,741..15,290] → 19,043 ops/sec [18,550..19,780] 🟢 +27.4%
jsx.jsx — Interp: 🟢 2, 🔴 2, 17 unch. · avg +0.6% · Bytecode: 🟢 21 · avg +26.8%
Benchmark Interpreted Δ Bytecode Δ
simple element 96,893 ops/sec [94,713..99,860] → 97,424 ops/sec [96,201..102,279] ~ overlap (+0.5%) 149,870 ops/sec [146,463..153,032] → 192,130 ops/sec [190,758..192,859] 🟢 +28.2%
self-closing element 100,571 ops/sec [99,377..101,792] → 100,704 ops/sec [97,796..103,042] ~ overlap (+0.1%) 162,615 ops/sec [159,222..165,353] → 207,496 ops/sec [207,216..208,466] 🟢 +27.6%
element with string attribute 84,004 ops/sec [80,454..85,283] → 84,447 ops/sec [82,558..87,266] ~ overlap (+0.5%) 118,382 ops/sec [117,035..119,983] → 152,592 ops/sec [151,340..154,325] 🟢 +28.9%
element with multiple attributes 73,348 ops/sec [72,684..73,746] → 74,389 ops/sec [73,016..75,709] ~ overlap (+1.4%) 93,318 ops/sec [90,684..94,433] → 113,651 ops/sec [112,258..120,145] 🟢 +21.8%
element with expression attribute 77,645 ops/sec [77,397..78,017] → 78,420 ops/sec [77,245..79,606] ~ overlap (+1.0%) 121,862 ops/sec [119,796..124,298] → 157,630 ops/sec [155,193..158,941] 🟢 +29.4%
text child 97,235 ops/sec [95,941..97,519] → 98,148 ops/sec [95,672..103,751] ~ overlap (+0.9%) 150,130 ops/sec [146,075..153,731] → 194,551 ops/sec [190,171..200,169] 🟢 +29.6%
expression child 92,804 ops/sec [91,367..93,864] → 92,629 ops/sec [90,208..95,286] ~ overlap (-0.2%) 143,104 ops/sec [140,408..143,514] → 178,918 ops/sec [172,515..181,811] 🟢 +25.0%
mixed text and expression 89,883 ops/sec [89,640..90,162] → 86,814 ops/sec [83,852..87,302] 🔴 -3.4% 125,247 ops/sec [123,295..126,662] → 153,035 ops/sec [150,045..160,117] 🟢 +22.2%
nested elements (3 levels) 38,038 ops/sec [36,508..38,436] → 37,183 ops/sec [36,747..37,260] ~ overlap (-2.2%) 57,700 ops/sec [56,392..59,743] → 75,042 ops/sec [72,424..76,839] 🟢 +30.1%
sibling children 27,970 ops/sec [27,523..28,723] → 28,150 ops/sec [27,537..28,574] ~ overlap (+0.6%) 41,482 ops/sec [40,800..41,638] → 53,367 ops/sec [52,057..54,376] 🟢 +28.7%
component element 73,016 ops/sec [70,530..73,440] → 73,576 ops/sec [72,670..76,364] ~ overlap (+0.8%) 110,485 ops/sec [106,077..118,469] → 137,916 ops/sec [137,189..142,322] 🟢 +24.8%
component with children 44,676 ops/sec [43,835..45,595] → 45,889 ops/sec [44,038..46,377] ~ overlap (+2.7%) 65,223 ops/sec [64,698..65,459] → 81,584 ops/sec [80,500..85,600] 🟢 +25.1%
dotted component 62,396 ops/sec [61,094..64,034] → 63,649 ops/sec [61,152..64,291] ~ overlap (+2.0%) 83,702 ops/sec [81,788..84,781] → 112,005 ops/sec [108,758..114,176] 🟢 +33.8%
empty fragment 101,596 ops/sec [98,922..104,289] → 103,074 ops/sec [102,309..103,778] ~ overlap (+1.5%) 177,953 ops/sec [173,896..179,203] → 229,897 ops/sec [227,710..231,492] 🟢 +29.2%
fragment with children 28,049 ops/sec [27,269..28,597] → 28,899 ops/sec [28,827..28,958] 🟢 +3.0% 42,122 ops/sec [41,530..42,549] → 53,907 ops/sec [53,382..55,289] 🟢 +28.0%
spread attributes 53,834 ops/sec [53,233..54,163] → 54,063 ops/sec [53,127..55,306] ~ overlap (+0.4%) 62,856 ops/sec [61,553..63,198] → 79,467 ops/sec [78,446..82,308] 🟢 +26.4%
spread with overrides 47,864 ops/sec [47,400..48,522] → 48,015 ops/sec [46,966..48,126] ~ overlap (+0.3%) 55,968 ops/sec [55,849..56,440] → 71,013 ops/sec [70,715..73,459] 🟢 +26.9%
shorthand props 76,818 ops/sec [76,072..77,241] → 74,834 ops/sec [73,218..75,433] 🔴 -2.6% 100,444 ops/sec [97,359..101,692] → 120,856 ops/sec [119,166..129,295] 🟢 +20.3%
nav bar structure 13,394 ops/sec [13,304..13,570] → 13,510 ops/sec [13,370..13,679] ~ overlap (+0.9%) 19,194 ops/sec [18,737..19,462] → 23,730 ops/sec [23,518..24,063] 🟢 +23.6%
card component tree 15,808 ops/sec [15,171..15,852] → 16,277 ops/sec [16,096..16,300] 🟢 +3.0% 21,002 ops/sec [20,666..21,641] → 26,157 ops/sec [25,962..26,476] 🟢 +24.5%
10 list items via Array.from 7,056 ops/sec [7,017..7,118] → 7,165 ops/sec [6,977..7,205] ~ overlap (+1.5%) 9,074 ops/sec [8,908..9,458] → 11,669 ops/sec [11,489..11,767] 🟢 +28.6%
modules.js — Interp: 🔴 8, 1 unch. · avg -6.2% · Bytecode: 🟢 9 · avg +34.5%
Benchmark Interpreted Δ Bytecode Δ
call imported function 168,825 ops/sec [168,619..169,555] → 158,272 ops/sec [153,624..162,309] 🔴 -6.3% 725,854 ops/sec [716,025..734,293] → 985,564 ops/sec [977,727..1,001,175] 🟢 +35.8%
call two imported functions 98,503 ops/sec [95,682..101,454] → 88,900 ops/sec [87,823..90,266] 🔴 -9.7% 484,500 ops/sec [469,183..492,028] → 650,087 ops/sec [637,318..658,964] 🟢 +34.2%
read imported constant 539,732 ops/sec [520,957..566,488] → 509,556 ops/sec [493,326..517,661] 🔴 -5.6% 1,779,169 ops/sec [1,742,482..1,806,108] → 2,370,199 ops/sec [2,350,737..2,416,763] 🟢 +33.2%
read imported string 532,215 ops/sec [522,415..553,612] → 516,175 ops/sec [512,462..517,159] 🔴 -3.0% 1,751,479 ops/sec [1,743,418..1,775,173] → 2,370,590 ops/sec [2,363,863..2,381,911] 🟢 +35.3%
read JSON string property 543,819 ops/sec [520,115..563,638] → 498,493 ops/sec [494,430..511,362] 🔴 -8.3% 1,757,875 ops/sec [1,745,032..1,831,903] → 2,373,103 ops/sec [2,300,599..2,410,413] 🟢 +35.0%
read JSON number property 541,790 ops/sec [528,511..565,304] → 506,645 ops/sec [501,353..513,272] 🔴 -6.5% 1,795,441 ops/sec [1,746,546..1,822,384] → 2,368,429 ops/sec [2,329,751..2,405,497] 🟢 +31.9%
read JSON boolean property 538,839 ops/sec [524,488..555,970] → 510,068 ops/sec [503,553..517,225] 🔴 -5.3% 1,776,743 ops/sec [1,764,683..1,818,422] → 2,388,706 ops/sec [2,331,555..2,424,103] 🟢 +34.4%
read JSON array property 541,782 ops/sec [522,337..560,628] → 515,584 ops/sec [512,623..520,016] 🔴 -4.8% 1,790,135 ops/sec [1,758,558..1,809,808] → 2,376,703 ops/sec [2,312,721..2,458,678] 🟢 +32.8%
read multiple JSON properties 334,470 ops/sec [312,247..351,681] → 312,721 ops/sec [308,490..318,068] ~ overlap (-6.5%) 1,449,121 ops/sec [1,446,576..1,449,493] → 2,001,197 ops/sec [1,940,077..2,046,481] 🟢 +38.1%
numbers.js — Interp: 🔴 8, 3 unch. · avg -4.4% · Bytecode: 🟢 11 · avg +24.4%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 177,413 ops/sec [176,778..178,393] → 164,703 ops/sec [162,656..167,435] 🔴 -7.2% 746,961 ops/sec [740,451..749,358] → 967,237 ops/sec [944,903..1,009,503] 🟢 +29.5%
floating point arithmetic 209,168 ops/sec [203,053..217,784] → 197,236 ops/sec [196,877..198,039] 🔴 -5.7% 315,815 ops/sec [307,278..318,320] → 398,760 ops/sec [396,811..399,774] 🟢 +26.3%
number coercion 78,136 ops/sec [73,734..82,893] → 76,347 ops/sec [75,423..76,765] ~ overlap (-2.3%) 100,582 ops/sec [100,396..101,204] → 128,247 ops/sec [127,643..128,916] 🟢 +27.5%
toFixed 46,814 ops/sec [46,083..47,526] → 45,477 ops/sec [44,551..45,603] 🔴 -2.9% 43,854 ops/sec [43,762..43,933] → 55,946 ops/sec [55,214..57,320] 🟢 +27.6%
toString 67,376 ops/sec [66,999..72,811] → 70,157 ops/sec [69,316..70,379] ~ overlap (+4.1%) 72,452 ops/sec [72,382..72,697] → 93,987 ops/sec [92,829..95,405] 🟢 +29.7%
valueOf 105,348 ops/sec [101,462..107,365] → 106,484 ops/sec [106,203..106,895] ~ overlap (+1.1%) 111,052 ops/sec [110,014..113,338] → 139,635 ops/sec [138,020..142,028] 🟢 +25.7%
toPrecision 41,293 ops/sec [40,696..42,272] → 38,256 ops/sec [37,287..39,185] 🔴 -7.4% 39,373 ops/sec [39,255..39,502] → 48,416 ops/sec [47,862..50,047] 🟢 +23.0%
Number.isNaN 128,137 ops/sec [125,244..132,362] → 119,751 ops/sec [118,658..121,046] 🔴 -6.5% 134,894 ops/sec [134,163..135,227] → 159,735 ops/sec [156,298..164,192] 🟢 +18.4%
Number.isFinite 126,509 ops/sec [125,977..127,576] → 115,617 ops/sec [114,634..116,451] 🔴 -8.6% 115,708 ops/sec [115,125..116,582] → 136,461 ops/sec [133,717..141,324] 🟢 +17.9%
Number.isInteger 128,409 ops/sec [126,786..131,628] → 121,435 ops/sec [118,540..122,124] 🔴 -5.4% 121,658 ops/sec [119,410..123,222] → 146,472 ops/sec [142,221..149,541] 🟢 +20.4%
Number.parseInt and parseFloat 102,372 ops/sec [99,942..104,798] → 94,160 ops/sec [93,928..95,291] 🔴 -8.0% 88,666 ops/sec [86,757..89,221] → 108,099 ops/sec [106,270..110,438] 🟢 +21.9%
objects.js — Interp: 🔴 6, 1 unch. · avg -4.8% · Bytecode: 🟢 7 · avg +25.9%
Benchmark Interpreted Δ Bytecode Δ
create simple object 209,596 ops/sec [205,031..219,487] → 203,098 ops/sec [195,062..206,893] ~ overlap (-3.1%) 256,455 ops/sec [249,631..271,802] → 335,432 ops/sec [325,001..336,115] 🟢 +30.8%
create nested object 112,707 ops/sec [111,870..113,531] → 110,478 ops/sec [110,076..111,749] 🔴 -2.0% 111,498 ops/sec [110,174..111,889] → 143,329 ops/sec [137,214..144,100] 🟢 +28.5%
create 50 objects via Array.from 4,126 ops/sec [4,072..4,158] → 3,970 ops/sec [3,959..3,994] 🔴 -3.8% 4,609 ops/sec [4,586..4,661] → 5,824 ops/sec [5,814..5,866] 🟢 +26.4%
property read 200,822 ops/sec [200,027..202,034] → 184,263 ops/sec [183,643..185,764] 🔴 -8.2% 330,707 ops/sec [321,081..341,219] → 404,438 ops/sec [399,817..409,805] 🟢 +22.3%
Object.keys 125,315 ops/sec [123,649..126,750] → 119,603 ops/sec [117,201..121,063] 🔴 -4.6% 145,381 ops/sec [141,111..151,254] → 181,241 ops/sec [180,005..185,127] 🟢 +24.7%
Object.entries 50,126 ops/sec [49,941..50,589] → 46,924 ops/sec [46,044..47,664] 🔴 -6.4% 54,600 ops/sec [52,959..58,164] → 68,771 ops/sec [67,914..70,752] 🟢 +26.0%
spread operator 89,408 ops/sec [88,413..90,455] → 84,664 ops/sec [84,519..84,715] 🔴 -5.3% 105,973 ops/sec [103,249..106,489] → 130,273 ops/sec [128,335..133,886] 🟢 +22.9%
promises.js — Interp: 🔴 6, 6 unch. · avg -3.3% · Bytecode: 🟢 12 · avg +28.6%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 217,259 ops/sec [213,037..221,330] → 209,052 ops/sec [205,627..212,542] 🔴 -3.8% 228,105 ops/sec [225,042..229,483] → 279,294 ops/sec [274,945..284,663] 🟢 +22.4%
new Promise(resolve => resolve(value)) 81,867 ops/sec [80,846..84,385] → 81,066 ops/sec [80,662..81,751] ~ overlap (-1.0%) 105,605 ops/sec [102,973..106,039] → 134,779 ops/sec [133,896..135,534] 🟢 +27.6%
Promise.reject(reason) 220,123 ops/sec [214,415..227,481] → 213,482 ops/sec [212,070..215,397] ~ overlap (-3.0%) 226,675 ops/sec [224,712..231,683] → 284,858 ops/sec [278,871..290,986] 🟢 +25.7%
resolve + then (1 handler) 78,986 ops/sec [75,756..81,109] → 79,946 ops/sec [78,347..80,719] ~ overlap (+1.2%) 93,502 ops/sec [91,130..94,720] → 117,778 ops/sec [115,620..120,584] 🟢 +26.0%
resolve + then chain (3 deep) 32,956 ops/sec [31,959..33,109] → 32,174 ops/sec [32,038..32,944] ~ overlap (-2.4%) 39,264 ops/sec [37,917..39,870] → 51,186 ops/sec [50,535..51,845] 🟢 +30.4%
resolve + then chain (10 deep) 11,106 ops/sec [10,860..11,234] → 10,374 ops/sec [10,295..10,499] 🔴 -6.6% 12,832 ops/sec [12,727..13,182] → 16,435 ops/sec [16,156..16,630] 🟢 +28.1%
reject + catch + then 47,058 ops/sec [45,962..47,358] → 46,118 ops/sec [45,952..46,336] ~ overlap (-2.0%) 51,944 ops/sec [50,881..52,277] → 68,055 ops/sec [67,614..68,975] 🟢 +31.0%
resolve + finally + then 41,211 ops/sec [40,062..41,617] → 39,635 ops/sec [39,309..40,276] ~ overlap (-3.8%) 42,600 ops/sec [41,302..43,482] → 57,341 ops/sec [57,036..57,740] 🟢 +34.6%
Promise.all (5 resolved) 16,643 ops/sec [16,565..16,767] → 16,263 ops/sec [16,099..16,529] 🔴 -2.3% 15,361 ops/sec [14,689..15,586] → 20,177 ops/sec [19,510..20,463] 🟢 +31.4%
Promise.race (5 resolved) 17,688 ops/sec [17,578..17,800] → 16,809 ops/sec [16,426..17,159] 🔴 -5.0% 16,452 ops/sec [16,217..16,668] → 20,927 ops/sec [20,605..21,334] 🟢 +27.2%
Promise.allSettled (5 mixed) 14,527 ops/sec [14,359..14,753] → 13,795 ops/sec [13,451..14,119] 🔴 -5.0% 12,900 ops/sec [12,714..13,891] → 16,912 ops/sec [16,413..17,090] 🟢 +31.1%
Promise.any (5 mixed) 16,805 ops/sec [16,474..17,304] → 15,803 ops/sec [15,745..16,465] 🔴 -6.0% 15,724 ops/sec [15,516..15,870] → 20,084 ops/sec [19,762..20,639] 🟢 +27.7%
regexp.js — Interp: 🟢 2, 🔴 9 · avg -43.4% · Bytecode: 🟢 4, 🔴 7 · avg -24.9%
Benchmark Interpreted Δ Bytecode Δ
regex literal creation 72,278 ops/sec [70,102..75,041] → 108,587 ops/sec [107,854..109,395] 🟢 +50.2% 68,125 ops/sec [67,195..68,359] → 133,619 ops/sec [131,070..137,079] 🟢 +96.1%
new RegExp(pattern, flags) 63,744 ops/sec [63,024..65,080] → 86,565 ops/sec [85,947..89,781] 🟢 +35.8% 65,772 ops/sec [65,079..66,133] → 126,668 ops/sec [126,306..126,906] 🟢 +92.6%
RegExp(existingRegex) returns the same regex 270,431 ops/sec [263,313..277,814] → 251,036 ops/sec [250,139..254,718] 🔴 -7.2% 434,326 ops/sec [427,480..445,221] → 573,667 ops/sec [547,364..592,518] 🟢 +32.1%
test() on a global regex 70,493 ops/sec [69,098..71,365] → 39,919 ops/sec [39,366..40,348] 🔴 -43.4% 79,130 ops/sec [78,543..79,548] → 60,563 ops/sec [59,664..60,844] 🔴 -23.5%
exec() with capture groups 61,101 ops/sec [60,629..61,541] → 15,775 ops/sec [15,674..15,840] 🔴 -74.2% 67,361 ops/sec [66,304..69,056] → 19,027 ops/sec [18,385..19,591] 🔴 -71.8%
toString() 205,578 ops/sec [202,299..210,623] → 191,639 ops/sec [189,564..193,892] 🔴 -6.8% 260,439 ops/sec [257,246..261,380] → 324,245 ops/sec [317,159..327,680] 🟢 +24.5%
match() with global regex 20,729 ops/sec [20,491..20,991] → 1,739 ops/sec [1,733..1,750] 🔴 -91.6% 20,149 ops/sec [19,796..20,680] → 1,976 ops/sec [1,943..2,000] 🔴 -90.2%
matchAll() with capture groups 10,690 ops/sec [10,604..10,756] → 4,138 ops/sec [4,089..4,176] 🔴 -61.3% 12,766 ops/sec [12,622..12,846] → 5,356 ops/sec [5,326..5,522] 🔴 -58.0%
replace() with global regex 20,242 ops/sec [20,126..20,381] → 1,745 ops/sec [1,704..1,755] 🔴 -91.4% 19,787 ops/sec [19,598..20,394] → 1,971 ops/sec [1,920..2,079] 🔴 -90.0%
search() with regex 41,306 ops/sec [40,841..41,632] → 1,912 ops/sec [1,900..1,920] 🔴 -95.4% 39,467 ops/sec [38,983..40,171] → 2,140 ops/sec [2,113..2,147] 🔴 -94.6%
split() with regex separator 20,618 ops/sec [20,538..20,887] → 1,542 ops/sec [1,526..1,547] 🔴 -92.5% 19,979 ops/sec [19,764..20,208] → 1,730 ops/sec [1,720..1,749] 🔴 -91.3%
strings.js — Interp: 🔴 9, 10 unch. · avg -2.9% · Bytecode: 🟢 19 · avg +24.5%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 158,534 ops/sec [149,570..163,068] → 152,651 ops/sec [151,812..155,367] ~ overlap (-3.7%) 1,015,014 ops/sec [980,653..1,046,551] → 1,261,443 ops/sec [1,226,768..1,272,852] 🟢 +24.3%
template literal 315,145 ops/sec [302,985..319,911] → 296,921 ops/sec [283,425..301,777] 🔴 -5.8% 681,639 ops/sec [660,945..687,256] → 845,572 ops/sec [837,062..850,939] 🟢 +24.0%
string repeat 181,925 ops/sec [179,698..184,889] → 179,033 ops/sec [175,833..181,630] ~ overlap (-1.6%) 217,578 ops/sec [216,493..221,426] → 263,476 ops/sec [261,070..264,995] 🟢 +21.1%
split and join 30,739 ops/sec [30,405..31,933] → 27,103 ops/sec [26,485..27,465] 🔴 -11.8% 31,979 ops/sec [31,432..32,120] → 39,233 ops/sec [38,748..41,138] 🟢 +22.7%
indexOf and includes 55,833 ops/sec [54,967..56,291] → 53,755 ops/sec [53,184..54,072] 🔴 -3.7% 53,742 ops/sec [53,483..54,322] → 66,329 ops/sec [65,571..68,172] 🟢 +23.4%
toUpperCase and toLowerCase 92,662 ops/sec [90,839..92,861] → 93,197 ops/sec [92,461..94,727] ~ overlap (+0.6%) 92,486 ops/sec [91,296..94,033] → 116,639 ops/sec [115,600..118,073] 🟢 +26.1%
slice and substring 56,590 ops/sec [56,487..56,937] → 54,147 ops/sec [53,109..54,600] 🔴 -4.3% 59,990 ops/sec [58,267..61,149] → 73,100 ops/sec [72,484..76,195] 🟢 +21.9%
trim operations 82,718 ops/sec [81,414..83,640] → 84,558 ops/sec [83,632..86,270] ~ overlap (+2.2%) 86,187 ops/sec [84,874..88,851] → 108,023 ops/sec [106,611..108,745] 🟢 +25.3%
replace and replaceAll 86,007 ops/sec [84,287..87,719] → 85,703 ops/sec [84,783..86,229] ~ overlap (-0.4%) 80,873 ops/sec [79,532..82,085] → 98,810 ops/sec [98,103..102,561] 🟢 +22.2%
startsWith and endsWith 53,222 ops/sec [52,348..54,734] → 52,856 ops/sec [51,958..53,984] ~ overlap (-0.7%) 48,946 ops/sec [47,536..51,199] → 61,004 ops/sec [59,606..61,852] 🟢 +24.6%
padStart and padEnd 78,921 ops/sec [77,586..81,925] → 77,544 ops/sec [76,641..79,866] ~ overlap (-1.7%) 80,995 ops/sec [76,986..83,013] → 97,621 ops/sec [96,119..99,219] 🟢 +20.5%
identity tag, no substitutions 176,512 ops/sec [172,963..179,706] → 173,182 ops/sec [167,681..177,769] ~ overlap (-1.9%) 619,328 ops/sec [606,385..650,467] → 818,399 ops/sec [795,748..831,409] 🟢 +32.1%
tag with 1 substitution 38,303 ops/sec [38,078..38,847] → 37,645 ops/sec [37,131..38,020] 🔴 -1.7% 56,212 ops/sec [56,178..56,256] → 68,748 ops/sec [67,626..70,466] 🟢 +22.3%
tag with 3 substitutions 20,459 ops/sec [20,382..21,225] → 20,088 ops/sec [19,920..20,254] 🔴 -1.8% 32,844 ops/sec [31,734..33,728] → 40,260 ops/sec [40,160..40,436] 🟢 +22.6%
tag with 6 substitutions 12,235 ops/sec [12,023..12,653] → 11,429 ops/sec [11,294..11,864] 🔴 -6.6% 18,516 ops/sec [18,179..18,833] → 24,199 ops/sec [23,668..24,522] 🟢 +30.7%
String.raw, no substitutions 236,442 ops/sec [234,188..241,692] → 233,428 ops/sec [229,792..236,937] ~ overlap (-1.3%) 249,747 ops/sec [249,149..249,860] → 305,930 ops/sec [299,102..307,469] 🟢 +22.5%
String.raw, 2 substitutions 173,921 ops/sec [172,002..176,726] → 163,503 ops/sec [160,689..169,419] 🔴 -6.0% 154,731 ops/sec [152,687..156,352] → 206,002 ops/sec [204,651..206,938] 🟢 +33.1%
tag accessing .raw array 72,520 ops/sec [71,801..73,349] → 71,138 ops/sec [68,726..72,267] ~ overlap (-1.9%) 93,960 ops/sec [93,079..94,612] → 117,299 ops/sec [116,053..118,238] 🟢 +24.8%
method as tag (this binding) 27,535 ops/sec [26,705..27,864] → 26,616 ops/sec [26,380..26,669] 🔴 -3.3% 42,577 ops/sec [41,626..43,885] → 51,928 ops/sec [51,141..52,932] 🟢 +22.0%
tsv.js — Interp: 🔴 9 · avg -12.9% · Bytecode: 🟢 9 · avg +28.5%
Benchmark Interpreted Δ Bytecode Δ
parse simple 3-column TSV 51,713 ops/sec [50,557..53,580] → 45,797 ops/sec [44,735..46,598] 🔴 -11.4% 52,430 ops/sec [50,877..53,809] → 66,630 ops/sec [65,966..67,217] 🟢 +27.1%
parse 10-row TSV 14,366 ops/sec [14,212..14,464] → 12,341 ops/sec [12,102..12,801] 🔴 -14.1% 14,167 ops/sec [13,786..14,544] → 18,107 ops/sec [17,880..18,278] 🟢 +27.8%
parse 100-row TSV 2,197 ops/sec [2,182..2,239] → 2,018 ops/sec [2,013..2,020] 🔴 -8.1% 2,152 ops/sec [2,116..2,242] → 2,747 ops/sec [2,701..2,841] 🟢 +27.6%
parse TSV with backslash-escaped fields 10,252 ops/sec [10,216..10,900] → 9,181 ops/sec [9,111..9,277] 🔴 -10.4% 10,096 ops/sec [9,927..10,122] → 12,681 ops/sec [12,516..13,038] 🟢 +25.6%
parse without headers (array of arrays) 6,157 ops/sec [6,104..6,290] → 5,570 ops/sec [5,511..5,590] 🔴 -9.5% 6,027 ops/sec [5,963..6,279] → 7,798 ops/sec [7,732..7,814] 🟢 +29.4%
stringify array of objects 44,693 ops/sec [44,243..45,290] → 37,592 ops/sec [37,194..38,103] 🔴 -15.9% 46,439 ops/sec [45,199..47,324] → 59,634 ops/sec [58,747..60,460] 🟢 +28.4%
stringify array of arrays 13,005 ops/sec [12,802..13,130] → 10,339 ops/sec [10,268..10,396] 🔴 -20.5% 12,987 ops/sec [12,769..13,231] → 16,849 ops/sec [16,656..16,936] 🟢 +29.7%
stringify with values needing escaping 34,297 ops/sec [34,047..34,804] → 30,283 ops/sec [30,231..30,477] 🔴 -11.7% 35,364 ops/sec [34,400..36,067] → 46,699 ops/sec [46,064..47,770] 🟢 +32.1%
parse then stringify 7,838 ops/sec [7,803..7,862] → 6,730 ops/sec [6,643..6,774] 🔴 -14.1% 7,744 ops/sec [7,722..7,766] → 9,949 ops/sec [9,886..9,982] 🟢 +28.5%
typed-arrays.js — Interp: 🟢 1, 🔴 17, 4 unch. · avg -14.0% · Bytecode: 🟢 17, 🔴 3, 2 unch. · avg +20.6%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 128,585 ops/sec [127,249..129,711] → 119,097 ops/sec [117,472..120,507] 🔴 -7.4% 147,547 ops/sec [142,134..153,111] → 186,669 ops/sec [184,338..188,277] 🟢 +26.5%
new Int32Array(100) 121,851 ops/sec [120,791..122,803] → 112,108 ops/sec [109,361..113,843] 🔴 -8.0% 136,239 ops/sec [133,837..137,299] → 172,809 ops/sec [168,200..174,284] 🟢 +26.8%
new Int32Array(1000) 88,132 ops/sec [85,771..89,059] → 88,163 ops/sec [87,725..89,031] ~ overlap (+0.0%) 92,739 ops/sec [91,682..94,720] → 119,141 ops/sec [116,013..120,683] 🟢 +28.5%
new Float64Array(100) 115,121 ops/sec [113,558..115,402] → 108,731 ops/sec [106,473..110,509] 🔴 -5.6% 126,787 ops/sec [126,188..127,075] → 162,008 ops/sec [158,877..164,229] 🟢 +27.8%
Int32Array.from([...]) 5,295 ops/sec [5,210..5,386] → 5,019 ops/sec [4,945..5,077] 🔴 -5.2% 5,150 ops/sec [5,114..5,211] → 6,556 ops/sec [6,485..6,640] 🟢 +27.3%
Int32Array.of(1, 2, 3, 4, 5) 145,295 ops/sec [144,801..145,753] → 130,351 ops/sec [128,667..132,951] 🔴 -10.3% 157,044 ops/sec [155,654..159,589] → 205,119 ops/sec [202,212..205,579] 🟢 +30.6%
sequential write 100 elements 1,634 ops/sec [1,618..1,639] → 1,500 ops/sec [1,475..1,541] 🔴 -8.2% 6,609 ops/sec [6,553..6,848] → 8,954 ops/sec [8,759..9,065] 🟢 +35.5%
sequential read 100 elements 1,714 ops/sec [1,695..1,733] → 1,545 ops/sec [1,540..1,547] 🔴 -9.9% 8,578 ops/sec [6,723..10,074] → 8,809 ops/sec [8,706..9,049] ~ overlap (+2.7%)
Float64Array write 100 elements 1,514 ops/sec [1,478..1,558] → 1,373 ops/sec [1,367..1,392] 🔴 -9.3% 6,650 ops/sec [6,532..6,742] → 5,963 ops/sec [5,846..6,044] 🔴 -10.3%
fill(42) 26,775 ops/sec [26,673..27,036] → 26,711 ops/sec [26,580..27,202] ~ overlap (-0.2%) 37,550 ops/sec [27,374..42,199] → 35,284 ops/sec [35,105..35,346] ~ overlap (-6.0%)
slice() 150,990 ops/sec [115,654..161,899] → 101,785 ops/sec [101,385..103,042] 🔴 -32.6% 133,216 ops/sec [129,289..134,499] → 168,904 ops/sec [168,531..169,785] 🟢 +26.8%
map(x => x * 2) 4,994 ops/sec [4,936..5,045] → 2,759 ops/sec [2,741..2,788] 🔴 -44.8% 4,284 ops/sec [4,252..4,408] → 5,438 ops/sec [5,351..5,501] 🟢 +26.9%
filter(x => x > 50) 3,043 ops/sec [2,985..3,089] → 2,839 ops/sec [2,816..2,865] 🔴 -6.7% 4,760 ops/sec [4,729..4,770] → 6,286 ops/sec [6,177..6,419] 🟢 +32.1%
reduce (sum) 3,013 ops/sec [2,928..3,136] → 2,855 ops/sec [2,792..2,881] 🔴 -5.3% 4,130 ops/sec [4,089..4,196] → 5,644 ops/sec [5,612..5,683] 🟢 +36.7%
sort() 138,579 ops/sec [137,940..139,741] → 89,386 ops/sec [88,902..90,135] 🔴 -35.5% 122,295 ops/sec [121,767..123,865] → 156,414 ops/sec [155,365..156,839] 🟢 +27.9%
indexOf() 313,354 ops/sec [309,818..314,514] → 193,718 ops/sec [191,452..196,218] 🔴 -38.2% 240,476 ops/sec [239,490..241,418] → 300,391 ops/sec [297,545..303,761] 🟢 +24.9%
reverse() 243,177 ops/sec [231,547..245,053] → 147,336 ops/sec [146,559..148,576] 🔴 -39.4% 200,641 ops/sec [199,065..203,006] → 257,914 ops/sec [255,721..258,753] 🟢 +28.5%
create view over existing buffer 235,033 ops/sec [233,395..236,550] → 131,804 ops/sec [130,321..224,180] 🔴 -43.9% 165,922 ops/sec [163,384..168,505] → 220,902 ops/sec [212,874..223,738] 🟢 +33.1%
subarray() 316,070 ops/sec [314,876..319,753] → 305,280 ops/sec [303,422..322,234] ~ overlap (-3.4%) 213,049 ops/sec [210,879..215,791] → 280,929 ops/sec [276,621..284,294] 🟢 +31.9%
set() from array 381,544 ops/sec [376,763..382,299] → 380,905 ops/sec [378,511..382,956] ~ overlap (-0.2%) 445,313 ops/sec [445,020..445,931] → 353,273 ops/sec [350,352..358,504] 🔴 -20.7%
for-of loop 3,859 ops/sec [3,850..3,882] → 3,835 ops/sec [3,827..3,839] 🔴 -0.6% 16,734 ops/sec [16,242..16,886] → 15,051 ops/sec [14,840..15,414] 🔴 -10.1%
spread into array 14,888 ops/sec [14,862..14,907] → 15,739 ops/sec [15,667..15,978] 🟢 +5.7% 56,682 ops/sec [55,788..57,485] → 71,509 ops/sec [70,663..71,661] 🟢 +26.2%
uint8array-encoding.js — Interp: 🟢 3, 🔴 7, 8 unch. · avg -12.6% · Bytecode: 🟢 9, 🔴 8, 1 unch. · avg +8.3%
Benchmark Interpreted Δ Bytecode Δ
short (5 bytes) 286,337 ops/sec [282,532..290,830] → 275,180 ops/sec [266,273..288,930] ~ overlap (-3.9%) 401,135 ops/sec [394,519..403,302] → 504,759 ops/sec [489,447..519,124] 🟢 +25.8%
medium (450 bytes) 161,973 ops/sec [159,522..162,688] → 149,915 ops/sec [147,339..152,685] 🔴 -7.4% 189,904 ops/sec [189,452..190,187] → 245,004 ops/sec [240,137..249,981] 🟢 +29.0%
large (4096 bytes) 35,076 ops/sec [33,804..35,416] → 33,200 ops/sec [32,238..34,097] ~ overlap (-5.3%) 35,018 ops/sec [34,869..35,640] → 45,667 ops/sec [44,310..47,025] 🟢 +30.4%
base64url alphabet 112,521 ops/sec [111,435..114,287] → 109,051 ops/sec [107,235..112,504] ~ overlap (-3.1%) 118,902 ops/sec [117,534..120,008] → 155,587 ops/sec [153,108..158,305] 🟢 +30.9%
omitPadding 166,208 ops/sec [165,305..166,427] → 171,188 ops/sec [168,792..175,037] 🟢 +3.0% 187,625 ops/sec [185,932..190,386] → 244,789 ops/sec [242,030..251,603] 🟢 +30.5%
short (8 chars) 241,096 ops/sec [144,532..243,920] → 155,284 ops/sec [151,962..157,844] ~ overlap (-35.6%) 160,688 ops/sec [158,511..163,021] → 215,326 ops/sec [212,382..216,021] 🟢 +34.0%
medium (600 chars) 124,824 ops/sec [124,050..125,935] → 71,233 ops/sec [68,547..73,267] 🔴 -42.9% 74,837 ops/sec [74,003..75,388] → 101,357 ops/sec [100,707..101,979] 🟢 +35.4%
large (5464 chars) 24,505 ops/sec [23,890..24,868] → 14,382 ops/sec [14,156..22,506] 🔴 -41.3% 13,902 ops/sec [13,751..14,365] → 18,921 ops/sec [18,554..19,669] 🟢 +36.1%
short (5 bytes) 484,351 ops/sec [481,727..488,108] → 287,431 ops/sec [284,922..291,217] 🔴 -40.7% 481,957 ops/sec [478,516..484,867] → 586,455 ops/sec [583,493..598,353] 🟢 +21.7%
medium (450 bytes) 209,007 ops/sec [206,710..211,115] → 136,697 ops/sec [133,158..146,272] 🔴 -34.6% 248,447 ops/sec [246,492..250,143] → 230,150 ops/sec [228,326..234,497] 🔴 -7.4%
large (4096 bytes) 29,133 ops/sec [25,917..36,642] → 28,214 ops/sec [28,033..28,283] ~ overlap (-3.2%) 37,514 ops/sec [37,031..37,961] → 37,299 ops/sec [36,132..38,192] ~ overlap (-0.6%)
short (10 chars) 165,526 ops/sec [164,469..169,585] → 162,209 ops/sec [160,773..162,559] 🔴 -2.0% 276,044 ops/sec [274,435..277,391] → 241,910 ops/sec [237,546..244,261] 🔴 -12.4%
medium (900 chars) 112,278 ops/sec [110,450..112,959] → 111,729 ops/sec [110,383..114,861] ~ overlap (-0.5%) 188,226 ops/sec [184,705..189,481] → 150,485 ops/sec [148,661..153,803] 🔴 -20.1%
large (8192 chars) 29,512 ops/sec [28,010..29,923] → 31,561 ops/sec [31,345..32,097] 🟢 +6.9% 49,026 ops/sec [48,243..49,168] → 38,635 ops/sec [36,856..40,129] 🔴 -21.2%
setFromBase64 (450 bytes) 84,602 ops/sec [70,253..120,602] → 70,231 ops/sec [69,164..70,494] ~ overlap (-17.0%) 133,413 ops/sec [131,922..134,458] → 103,744 ops/sec [103,529..103,976] 🔴 -22.2%
setFromHex (450 bytes) 104,641 ops/sec [104,060..105,736] → 107,823 ops/sec [106,025..108,241] 🟢 +3.0% 180,681 ops/sec [178,412..182,141] → 153,259 ops/sec [152,566..153,528] 🔴 -15.2%
toBase64 → fromBase64 (450 bytes) 57,252 ops/sec [56,471..57,536] → 55,287 ops/sec [54,989..55,522] 🔴 -3.4% 87,271 ops/sec [86,671..87,843] → 74,248 ops/sec [74,049..74,446] 🔴 -14.9%
toHex → fromHex (450 bytes) 72,085 ops/sec [70,107..73,262] → 72,823 ops/sec [72,412..73,280] ~ overlap (+1.0%) 110,153 ops/sec [109,627..110,690] → 97,628 ops/sec [95,959..99,358] 🔴 -11.4%
weak-collections.js — Interp: 🟢 10, 5 unch. · avg +46.1% · Bytecode: 🟢 15 · avg +59.5%
Benchmark Interpreted Δ Bytecode Δ
constructor from 50 entries 13,311 ops/sec [13,176..13,506] → 21,476 ops/sec [11,773..22,059] ~ overlap (+61.3%) 13,829 ops/sec [13,789..13,966] → 18,094 ops/sec [17,825..18,482] 🟢 +30.8%
set 50 object keys 4,709 ops/sec [4,676..4,763] → 7,970 ops/sec [7,931..8,054] 🟢 +69.3% 10,874 ops/sec [10,788..10,915] → 12,939 ops/sec [12,855..13,048] 🟢 +19.0%
get lookups (50 entries) 63,269 ops/sec [62,380..64,102] → 102,837 ops/sec [102,425..104,224] 🟢 +62.5% 97,634 ops/sec [96,329..164,858] → 197,794 ops/sec [195,765..200,786] 🟢 +102.6%
has checks (50 entries) 81,607 ops/sec [79,884..84,016] → 133,514 ops/sec [133,279..135,852] 🟢 +63.6% 124,238 ops/sec [123,218..127,875] → 251,968 ops/sec [250,237..253,827] 🟢 +102.8%
delete entries 4,535 ops/sec [4,441..4,598] → 7,336 ops/sec [7,297..7,499] 🟢 +61.8% 6,338 ops/sec [6,291..6,380] → 12,372 ops/sec [12,316..12,472] 🟢 +95.2%
non-registered symbol keys 10,837 ops/sec [10,683..10,950] → 17,801 ops/sec [17,476..18,092] 🟢 +64.3% 15,075 ops/sec [14,900..15,175] → 29,702 ops/sec [29,431..30,104] 🟢 +97.0%
getOrInsert 4,502 ops/sec [4,440..4,517] → 7,462 ops/sec [7,293..7,533] 🟢 +65.8% 5,786 ops/sec [5,745..5,820] → 11,351 ops/sec [11,255..11,433] 🟢 +96.2%
getOrInsertComputed 3,430 ops/sec [2,175..3,485] → 3,650 ops/sec [3,580..3,706] 🟢 +6.4% 2,872 ops/sec [2,771..2,944] → 5,734 ops/sec [5,673..5,756] 🟢 +99.6%
forced gc live-key retention 7,345 ops/sec [7,258..7,395] → 7,410 ops/sec [7,335..7,446] ~ overlap (+0.9%) 5,450 ops/sec [5,288..5,652] → 11,186 ops/sec [11,131..11,221] 🟢 +105.3%
constructor from 50 values 28,539 ops/sec [28,259..28,877] → 28,094 ops/sec [27,777..28,584] ~ overlap (-1.6%) 28,856 ops/sec [17,945..29,281] → 36,927 ops/sec [36,521..37,026] 🟢 +28.0%
add 50 object values 5,289 ops/sec [5,152..8,624] → 8,484 ops/sec [8,442..8,548] ~ overlap (+60.4%) 11,508 ops/sec [11,472..11,647] → 13,984 ops/sec [13,914..14,156] 🟢 +21.5%
has checks (50 values) 83,684 ops/sec [82,999..84,271] → 132,942 ops/sec [132,030..133,161] 🟢 +58.9% 209,921 ops/sec [206,497..211,497] → 254,851 ops/sec [252,058..257,149] 🟢 +21.4%
delete values 13,910 ops/sec [13,894..13,952] → 21,619 ops/sec [21,610..21,641] 🟢 +55.4% 24,890 ops/sec [24,315..25,322] → 31,445 ops/sec [31,011..31,938] 🟢 +26.3%
non-registered symbol values 11,621 ops/sec [11,450..11,781] → 18,811 ops/sec [18,536..18,968] 🟢 +61.9% 26,461 ops/sec [26,070..26,614] → 31,812 ops/sec [31,535..31,901] 🟢 +20.2%
forced gc pruning smoke 9,099 ops/sec [9,017..9,289] → 9,097 ops/sec [9,066..9,164] ~ overlap (-0.0%) 11,359 ops/sec [11,255..11,462] → 14,393 ops/sec [13,980..14,522] 🟢 +26.7%

Deterministic profile diff

Deterministic profile diff: no significant changes.

Measured on ubuntu-latest x64. Benchmark ranges compare cached main-branch min/max ops/sec with the PR run; overlapping ranges are treated as unchanged noise. Percentage deltas are secondary context.

The memoization hash used Knuth multiplicative constants that overflow
Cardinal, requiring {$Q-}{$R-} suppression. Replace with a shift-xor
hash that stays within Cardinal range — no compiler flags needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney force-pushed the t3code/dbc3a3af branch from 93a21a4 to eab29d3 Compare May 8, 2026 16:43
frostney and others added 13 commits May 8, 2026 18:37
Two bugs found via test262:

1. RX_ASSERT_START/END read raw bytes via Ord(AInput[Pos]) instead of
   decoding full UTF-8 code points. Multi-byte line terminators (U+2028,
   U+2029) were never recognized, and accessing continuation bytes
   triggered range check errors. Fix: use GetCodePointBefore and
   ReadInputCodePoint for proper UTF-8 decoding.

2. ReadInputCodePoint only decoded UTF-8 when the unicode flag was set.
   Without /u, multi-byte BMP characters (U+0085, U+2028, etc.) were
   read as single bytes, causing . to match one byte instead of one
   code point. Fix: always decode UTF-8 regardless of flag — the
   unicode flag only affects supplementary plane advancement in the
   scanner loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Convert RegExp flag properties (source, flags, global, ignoreCase,
  multiline, dotAll, unicode, sticky, unicodeSets, hasIndices) from
  per-instance data properties to spec-correct accessor getters on
  RegExp.prototype (ES2026 §22.2.6). Accessing them on the prototype
  itself returns undefined (or '(?:)' for source, '' for flags) per spec.

- Introduce ERegExpRuntimeError exception class so the regex VM's
  backtrack limit error is distinguishable from GocciaScript VM errors.
  Runtime.pas catches it and re-throws as a proper JS Error via
  ThrowError.

- Add JS test coverage: dotAll with multi-byte BMP characters, dot
  rejecting multi-byte line terminators, multiline anchors with
  multi-byte context, catastrophic backtracking throws Error, and
  large input regression test for #515.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The scanner loop and GetCodePointBefore only decoded UTF-8 when the
unicode flag was set. Without /u, multi-byte BMP characters (U+1680,
U+2000-200A, U+2028, U+2029, etc.) were read as individual bytes,
causing \S, \s, \b, ^, $ and . to misclassify them. This broke
test262's character-class-escape-non-whitespace test on CI.

Fix: always decode UTF-8 via TryReadUTF8CodePointAllowSurrogates
(which also handles lone surrogates correctly). Remove the now-unused
AUnicode parameter from ReadInputCodePoint and GetCodePointBefore.
The scanner loop also always advances by code point now.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The lookbehind handler called RunVM starting from PC=0, which re-executed
the entire regex pattern (including .*, quantifiers, etc.) instead of
just the lookbehind body. This caused exponential blowup even on tiny
inputs like "xabcd".match(/.*(?<=(..|...|....))(.*)/), hanging the
test262 CI runner.

Fix: RunVM now accepts AStartPC and AEndPos parameters. Lookahead and
lookbehind pass PC+1 as the start (skipping the assertion instruction
to execute only the body up to RX_MATCH). Lookbehind uses AEndPos to
check where the sub-match ended rather than checking capture slots.

Also bounds the lookbehind scan distance to MAX_LOOKBEHIND_DISTANCE
(256 positions) to prevent O(n) RunVM calls on large inputs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The negation flag for (?!...) and (?<!...) was encoded as bit 7 of the
instruction word, which overlaps with the opcode byte — corrupting the
opcode from 12 (RX_LOOKAHEAD) to 140 (invalid). The VM's case-else
branch silently skipped the instruction, making all negative assertions
pass unconditionally.

Fix: encode the negation flag as bit 23 of the Bx field (LOOK_NEGATED_FLAG
= $800000), matching the BACKREF_STRICT_FLAG convention. InsertSplitAt
preserves the flag when adjusting PC targets.

Also adds JS tests for positive/negative lookahead, positive/negative
lookbehind, lookbehind with alternation and quantifiers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Syntax validation:
- Reject dangling quantifiers (a**, ??, +) — nothing to repeat
- Reject invalid char class ranges ([z-a], [b-ac-e]) where start > end
- Reject {min,max} where min > max ({2,1})
- Reject trailing backslash (\)
- Reject \c without letter in unicode mode
- Reject invalid identity escapes in unicode mode
- Reject quantified assertions ((?=.)*) in unicode mode

Quantifier body relocation:
- EmitBodyAt adjusts absolute PC targets (SPLIT, JUMP, LOOKAHEAD,
  LOOKBEHIND) by the offset between original and destination positions.
  Without this, alternation inside * quantifiers had stale SPLIT targets,
  causing /(aa|aabaac|ba|b|c)*/ to return ["",null] instead of
  ["aaba","ba"].

Zero-width loop detection:
- RX_SPLIT records (PC, InputPos) in the memoization table on each
  visit. When revisited at the same position (zero-width iteration),
  takes the exit branch instead of looping. Prevents infinite loops on
  patterns like /(a*)b\1+/ where the backreference matches empty.
  Also makes catastrophic patterns like /^(a+)+$/ terminate with null
  instead of hitting the backtrack limit.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Encode ignoreCase flag per-backref instruction (BACKREF_ICASE_FLAG =
  $400000) so (?i:\1) case-folds the backreference comparison while
  \1 outside a modifier group does not. The flag is set at compile time
  from FModifier.IgnoreCase, giving correct scoping to modifier groups.

- Cap ParseDecimalEscape at 1M to prevent integer overflow on huge
  quantifiers like {2147483648} — avoids range check error on the
  staging/sm/RegExp/regress-yarr-regexp.js test262 test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cover every regression fix with explicit tests:
- Dangling quantifiers (a**, ??, +, *) throw SyntaxError
- Invalid char class ranges ([z-a], [b-ac-e]) throw SyntaxError
- Quantifier min > max ({2,1}) throws SyntaxError
- Trailing backslash throws SyntaxError
- Huge quantifier ({2147483648}) does not crash
- Greedy * with alternation picks correct match path
- Greedy * with char class quantifier backtracks correctly
- Zero-length backref with + quantifier does not hang
- (?i:\1) case-folds backreference, (?-i:\1) disables it
- \c without letter in /u throws SyntaxError
- Quantified assertions in /u throw SyntaxError

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backref backtracking:
- Revert the zero-width memo-on-SPLIT-entry that prevented legitimate
  backtracking through (a+) in /^(a+)\1*,\1+$/. Instead detect
  zero-width loops via JUMP: when jumping back to a SPLIT and the top
  backtrack entry has the same exit target and input position, the
  iteration consumed nothing — take the exit directly.

Char class \c in unicode mode:
- CompileEscape (character class variant) now handles \c with the same
  validation as CompileEscapeAtom: \c without a-zA-Z throws SyntaxError
  in unicode mode. Also reject invalid identity escapes inside character
  classes in unicode mode.

Dynamic step limit:
- Step limit is now max(10M, inputLength * 100) instead of a fixed 10M.
  This prevents false positives on legitimate large inputs (e.g. test262
  property-escapes/generated/ASCII.js tests \P{ASCII} against 1M+ chars)
  while still catching catastrophic backtracking on small inputs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Every fix should ship with its test. These were missing from the
previous commit:
- Backref backtracking through (a+) in /^(a+)\1*,\1+$/
- String.replace with backreference capture
- \c inside character class in unicode mode throws SyntaxError
- \p{ASCII} on large input does not hit step limit

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The property-escapes/generated/ASCII.js test262 test builds a 1.1M
code point non-ASCII string and tests \P{ASCII}+ against it. The
greedy + pushes one backtrack entry per code point, exceeding the 1M
backtrack stack cap.

Fix: raise DEFAULT_BACKTRACK_CAP from 1M to 10M. Also inline
ReadInputCodePoint with a fast path for ASCII bytes (< 0x80) to avoid
the function call overhead of TryReadUTF8CodePointAllowSurrogates on
every character of large inputs. CharClassContainsLinear is also
marked inline.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dead code removal:
- Remove FlagIgnoreCase/FlagMultiline/FlagDotAll from TRegExpProgram
  (modifier state is encoded per-instruction, these fields are unread)
- Remove EncodeInstr (duplicate of EncodeOpBx), AddCharClassFromDynamic
  (duplicate of AddCharClass), CharClassContains (unused binary search),
  CaseFold (unused method)

SIGSEGV fix:
- IsRegExpValue now checks HasOwnProperty('source') and
  HasOwnProperty('flags') instead of Symbol.toStringTag. An object
  created via Object.create(RegExp.prototype) inherits the tag but has
  no internal regex state — the prototype getters would recurse
  infinitely trying to read source/flags, causing stack overflow.

Memo correctness:
- Backref match failure now restores InputPos before calling MemoAdd,
  so the memo records the correct (PC, pos) pair instead of the
  partially-advanced position.
- Invalid opcodes now raise ERegExpRuntimeError instead of silently
  skipping via Inc(PC).

Lazy memo allocation:
- MemoInit no longer allocates the 65K-entry table. MemoAdd allocates
  on first use, MemoContains returns false if unallocated. This avoids
  ~1MB allocation per lookbehind sub-call (up to 256 calls per
  assertion).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Simplify pass from three-agent review:

Dead code:
- Remove FlagUnicode from TRegExpProgram (unread since per-instruction
  encoding handles unicode behavior)
- Remove FFlags field from TRegExpCompiler (parsed into FModifier/FUnicode
  in constructor, never read after)
- Remove AInCharClass parameter from CompileEscape (never referenced)
- Remove dead ClassIdx variable from EmitUnicodePropertyClass
- Remove duplicate AddCharClassFromDynamic (identical to AddCharClass)

Shared constants:
- Move BACKREF_STRICT_FLAG, BACKREF_ICASE_FLAG, LOOK_NEGATED_FLAG and
  their mask companions to the Compiler interface section so the VM
  uses named constants instead of raw hex at 8 decode sites.

Efficiency:
- PushBacktrack reuses existing Slots array when length matches
  (avoids heap allocation per push on hot path)
- FillChar($FF) replaces per-element slot init loop

Correctness:
- \P{...} inside character classes now throws SyntaxError instead of
  silently treating it as \p{...} (misleading comment removed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@frostney frostney marked this pull request as ready for review May 9, 2026 10:21
@coderabbitai coderabbitai Bot added bug Something isn't working new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification performance Performance improvement internal Refactoring, CI, tooling, cleanup labels May 9, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
source/units/Goccia.RegExp.VM.pas (1)

408-411: 💤 Low value

Minor: Redundant assignment to RefEnd.

Line 410 re-reads RefEnd from slots, but it was already assigned at line 396. This is harmless but unnecessary.

♻️ Remove redundant assignment
           RefPos := RefStart;
           LookMatched := True;
-          RefEnd := ASlots[BackrefGroup * 2 + 1];
           I := InputPos;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/units/Goccia.RegExp.VM.pas` around lines 408 - 411, Remove the
redundant re-assignment of RefEnd in the block that sets RefPos, LookMatched and
I: eliminate the line "RefEnd := ASlots[BackrefGroup * 2 + 1];" since RefEnd is
already set earlier (same slot index); keep the other assignments (RefPos :=
RefStart, LookMatched := True, I := InputPos) unchanged and ensure no other
logic relies on a second read of ASlots in this scope.
source/units/Goccia.RegExp.Engine.pas (1)

143-146: 💤 Low value

Minor: Redundant EMPTY_REGEX check.

Lines 143-145 check if PatternToCompile = EMPTY_REGEX and set it to '', but line 132 already returns early when APattern = EMPTY_REGEX. This code path is unreachable.

♻️ Simplify by removing unreachable check
-  PatternToCompile := APattern;
-  if PatternToCompile = EMPTY_REGEX then
-    PatternToCompile := '';
-  Prog := CompileRegExp(PatternToCompile, AFlags);
+  Prog := CompileRegExp(APattern, AFlags);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@source/units/Goccia.RegExp.Engine.pas` around lines 143 - 146, The check that
sets PatternToCompile to '' when PatternToCompile = EMPTY_REGEX is unreachable
because the function already returns when APattern = EMPTY_REGEX; remove the
redundant if block (the comparison against EMPTY_REGEX and the assignment to '')
so the code simply sets PatternToCompile := APattern and then calls
CompileRegExp(PatternToCompile, AFlags) without the extra conditional; reference
symbols: PatternToCompile, EMPTY_REGEX, APattern, CompileRegExp.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/decision-log.md`:
- Line 20: The decision-log entry introduces a new area tag "engine" which isn't
in the documented valid tag list; either add "engine" to the contributor guide's
allowed tags list or change the entry's tag from `engine` to one of the existing
tags (e.g., `bytecode`, `interpreter`, or `runtime`) so the tag set remains
consistent; update the contributor guide's tag enumeration accordingly and
ensure the decision-log entry's tag matches that list (look for the tag list in
the contributor guide and the new tag in the decision-log entry line containing
`engine`).

In `@source/units/Goccia.RegExp.Compiler.pas`:
- Around line 519-535: ParseGroupName currently accepts any text until '>' but
must enforce ECMAScript RegExpIdentifierName rules: the first character must be
RegExpIdentifierStart (UnicodeIDStart or '$' or '_') and subsequent characters
must be RegExpIdentifierPart (UnicodeIDContinue or '$' or '_'), and empty names
are invalid; update TRegExpCompiler.ParseGroupName to validate the captured name
after reading (or validate per-char while reading) and raise a SyntaxError for
empty or invalid names (reject digit-first, hyphens, etc.). Also ensure the same
validation is invoked for backreference parsing that consumes names (e.g. the
method handling "\k<...>") so both named-capture and \k<> use the same
identifier check and produce SyntaxError on invalid input.
- Around line 356-453: GetUnicodePropertyRanges currently uses incomplete
hard-coded ranges; replace this brittle table with a dynamic, standards-correct
implementation inside GetUnicodePropertyRanges that iterates Unicode code points
(0..$10FFFF), uses the platform Unicode API (e.g.
System.Character.TCharacter.GetUnicodeCategory or a UnicodeData.txt lookup) to
test each code point against the requested property name (APropertyName like
'L','Lu','Ll','Nd','White_Space', etc.), accumulate contiguous matching code
points into ranges via AddRange(ARanges, ARangeCount, ...), and if the caller
requested the negated form (regex \P{...} — detect an uppercase leading 'P' or a
provided negation flag per caller convention) invert the resulting ranges to
return the complement; ensure Nd and other categories are derived from Unicode
general categories rather than ASCII-only lists and preserve performance by
batching contiguous code points into ranges.
- Around line 601-620: ParseDecimalEscape currently clamps parsed quantifiers to
MAX_QUANTIFIER but CompileQuantifier still unrolls the atom body per repetition
causing O(bound) code emission; change CompileQuantifier to detect large/clamped
counts (the value produced by ParseDecimalEscape / MAX_QUANTIFIER) and avoid
per-iteration unrolling by emitting a compact loop construct instead (e.g., emit
a single copy of the atom body plus a counter-driven jump/loop or a new
REPEAT_N/LOOP_N bytecode) rather than emitting SPLIT and the full atom for each
repetition; update code paths that generate SPLIT/unrolled bodies (the loop
around lines referenced in CompileQuantifier) to use the counter-loop emission
so large quantified counts no longer expand into many emitted instructions.

In `@tests/built-ins/RegExp/unicode.js`:
- Around line 218-230: The test cases use string literals containing raw
newlines inside calls to /^abc/m.test(...) and /abc$/m.test(...), which causes a
syntax error; replace those raw multi-line string literals with either escaped
newline sequences (\"\\n\") or template literals (backticks) so the strings are
valid (e.g., "xyz\\nabc" or `xyz\nabc`) for all four expect(...) calls
referencing /^abc/m.test and /abc$/m.test, preserving the same content and
assertions.

---

Nitpick comments:
In `@source/units/Goccia.RegExp.Engine.pas`:
- Around line 143-146: The check that sets PatternToCompile to '' when
PatternToCompile = EMPTY_REGEX is unreachable because the function already
returns when APattern = EMPTY_REGEX; remove the redundant if block (the
comparison against EMPTY_REGEX and the assignment to '') so the code simply sets
PatternToCompile := APattern and then calls CompileRegExp(PatternToCompile,
AFlags) without the extra conditional; reference symbols: PatternToCompile,
EMPTY_REGEX, APattern, CompileRegExp.

In `@source/units/Goccia.RegExp.VM.pas`:
- Around line 408-411: Remove the redundant re-assignment of RefEnd in the block
that sets RefPos, LookMatched and I: eliminate the line "RefEnd :=
ASlots[BackrefGroup * 2 + 1];" since RefEnd is already set earlier (same slot
index); keep the other assignments (RefPos := RefStart, LookMatched := True, I
:= InputPos) unchanged and ensure no other logic relies on a second read of
ASlots in this scope.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f20a955-72f5-4f75-9f94-a0ef198213fc

📥 Commits

Reviewing files that changed from the base of the PR and between 5f6ca2a and c640541.

📒 Files selected for processing (16)
  • .github/workflows/ci.yml
  • .github/workflows/toolchain.yml
  • docs/build-system.md
  • docs/built-ins.md
  • docs/decision-log.md
  • scripts/run_test262_suite.ts
  • source/units/Goccia.Builtins.GlobalRegExp.pas
  • source/units/Goccia.RegExp.Compiler.pas
  • source/units/Goccia.RegExp.Engine.pas
  • source/units/Goccia.RegExp.Runtime.pas
  • source/units/Goccia.RegExp.Unicode.pas
  • source/units/Goccia.RegExp.VM.pas
  • tests/built-ins/RegExp/constructor.js
  • tests/built-ins/RegExp/modifiers.js
  • tests/built-ins/RegExp/prototype/exec.js
  • tests/built-ins/RegExp/unicode.js
💤 Files with no reviewable changes (2)
  • scripts/run_test262_suite.ts
  • source/units/Goccia.RegExp.Unicode.pas

Comment thread docs/decision-log.md
Comment thread source/units/Goccia.RegExp.Compiler.pas
Comment thread source/units/Goccia.RegExp.Compiler.pas
Comment thread source/units/Goccia.RegExp.Compiler.pas
Comment thread source/units/Goccia.RegExp.Compiler.pas
Comment thread tests/built-ins/RegExp/unicode.js
@frostney frostney merged commit cd11813 into main May 9, 2026
61 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working internal Refactoring, CI, tooling, cleanup new feature New feature or request performance Performance improvement spec compliance Mismatch against official JavaScript/TypeScript specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Engine SIGSEGV: RegExp.prototype.test trailing-input edge case

1 participant