Skip to content

Add --compat-traditional-for-loop opt-in flag#531

Merged
frostney merged 17 commits into
mainfrom
t3code/0f65db35
May 6, 2026
Merged

Add --compat-traditional-for-loop opt-in flag#531
frostney merged 17 commits into
mainfrom
t3code/0f65db35

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented May 5, 2026

Summary

  • Adds traditional C-style for(init; test; update) loops behind a new --compat-traditional-for-loop flag, mirroring the existing --compat-var / --compat-function posture (off by default; --compat-all ORs it in).
  • let/const in for-init create a per-iteration lexical environment per ES2026 §14.7.4.4, so closures captured during iteration N pin to that iteration's binding (the textbook fns.push(() => i) case yields [0, 1, 2], not [3, 3, 3]). var in for-init requires both --compat-var and the new flag, hoists out of the loop, and is shared across iterations.
  • Bytecode mode includes a counted-loop fast path mirroring CompileCountedForOf for the common for(let i = N; i <op> M; i++ | i--) shape; the general path uses an outer scope for the canonical loop slot plus a per-iteration BeginScope/EndScope cycle with OP_CLOSE_UPVALUE for captures.
  • while and do...while remain excluded — they share the same stub status but were intentionally split into a separate iteration.

Testing

  • Verified no regressions and confirmed the new feature or bugfix in end-to-end JavaScript/TypeScript tests
    • New tests/language/for-loop/ directory: 26/26 in interpreter + bytecode (basic-counter, empty-parts, let-per-iteration, break-continue, return-from-loop, destructuring-init, with-var/var-shared-binding).
    • CLI integration block added in scripts/test-cli-config.ts: per-file config across loader (interp + bytecode), test runner (interp + bytecode), bundler, and benchmark runner; plus a flag-off → warning + no-op assertion.
    • Full suite: 8586/8591 in both modes (5 failures are pre-existing FFI fixture-loading errors unrelated to this change; tests/fixtures/ffi/libfixture.dylib is not built locally).
    • Regression check on related suites (tests/language/var, tests/language/for-of, tests/language/function-keyword): 239/239 in both modes.
  • Updated documentation (docs/language.md § Traditional for Loop, docs/language-tables.md, docs/goals.md, docs/decision-log.md 2026-05-05 entry)
  • 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

🤖 Generated with Claude Code

Adds traditional C-style for(init; test; update) loops behind a new
compatibility flag, mirroring the existing --compat-var / --compat-function
posture. let/const declarations in for-init create per-iteration lexical
environments per ES2026 §14.7.4.4 so closure capture pins per iteration.
var declarations require both --compat-var and the new flag, hoist out of
the loop, and share a single binding. Bytecode mode includes a counted-loop
fast path mirroring CompileCountedForOf for for(let i = N; i <op> M; i++|i--)
shapes.

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

vercel Bot commented May 5, 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 6, 2026 7:05am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8bb4d743-a5db-4c9f-866f-604ab1acb381

📥 Commits

Reviewing files that changed from the base of the PR and between aad6ba9 and b21a0e9.

📒 Files selected for processing (2)
  • tests/language/for-loop-var/goccia.json
  • tests/language/for-loop-var/var-shared-binding.js

📝 Walkthrough

Walkthrough

This PR adds opt-in traditional C-style for-loop support (for (init; test; update)) behind a --compat-traditional-for-loop flag, with per-iteration lexical scoping for let/const and hoisted var. The implementation spans configuration, parsing, compilation, evaluation, and all CLI entry points, plus comprehensive tests. Secondary changes enhance ECMAScript conformance in Math, Number, and Array built-ins.

Changes

Traditional For-Loop Support

Layer / File(s) Summary
Configuration & Engine Integration
source/shared/CLI.Options.pas, source/units/Goccia.Engine.pas, source/units/Goccia.Interpreter.pas
New CompatTraditionalFor option flag added to CLI options; TGocciaCompatibility enum expanded to include cfTraditionalFor; engine exposes TraditionalForLoopsEnabled property with getter/setter; interpreter propagates flag to module loader.
Parser Recognition & Handling
source/units/Goccia.Parser.pas
Added TraditionalForLoopsEnabled property and private field; new helpers LooksLikeTraditionalForHeader and ParseTraditionalForBody detect and parse traditional for-loop syntax; ForStatement path routes to ParseTraditionalForBody when flag enabled; ParseInterpolationExpression propagates flag through template literal parsing.
AST & Binding Collection
source/units/Goccia.AST.BindingPatterns.pas, source/units/Goccia.AST.Statements.pas
CollectVarBindingNamesFromNode extended to handle TGocciaForStatement by traversing init and body; ForStatement.Execute now delegates to EvaluateFor instead of returning undefined.
Bytecode Compilation
source/units/Goccia.Compiler.pas, source/units/Goccia.Compiler.Statements.pas
New public CompileForStatement method added; TryCompileCountedFor detects counted-for pattern and establishes per-iteration scoping with disposal semantics; DoCompileStatement routes TGocciaForStatement to CompileForStatement; HoistVarLocals extended to handle for-statement hoisting.
Runtime Evaluation
source/units/Goccia.Evaluator.pas
New public function EvaluateFor implements traditional for-loop semantics including header setup, per-iteration lexical scope creation, variable binding, condition/update handling, and generator/yield integration; Goccia.Bytecode.Chunk added to uses clause.
Loader & Module Wiring
source/units/Goccia.Modules.Loader.pas, source/app/GocciaScriptLoader.dpr, source/app/GocciaREPL.dpr, source/app/GocciaTestRunner.dpr, source/app/GocciaBenchmarkRunner.dpr
Module loader gains TraditionalForLoopsEnabled property and propagates to parser; script loader ParseSource extended to accept ATraditionalForLoopsEnabled parameter; REPL, test runner, and benchmark runner propagate engine flag to parser during bytecode parsing.
Bundler & Bare Loader Integration
source/app/GocciaBundler.dpr, source/app/GocciaScriptLoaderBare.dpr, source/app/Goccia.CLI.Application.pas
Bundler.ParseSource accepts new ATraditionalForLoopsEnabled parameter; CompileSource resolves effective config with precedence (CLI > per-file > root > default); bare loader adds CompatTraditionalFor field and wires CLI option; CLI app applies per-file/config flag via ApplyFileConfigToEngine.
Configuration Tests
scripts/test-cli-config.ts
New test block validates compat-traditional-for-loop flag across loader (interpreted/bytecode), test runner (interpreted/bytecode), bundler, and benchmark runner using test.js containing traditional for-loop syntax.
Language Tests
tests/language/for-loop/*, tests/language/for-loop-var/*
Five test files validate traditional for-loop behavior: basic-counter.js (iteration counting), break-continue.js (control flow), destructuring-init.js (pattern matching), empty-parts.js (omitted header parts), let-per-iteration.js (per-iteration binding); var-shared-binding.js tests hoisting; return-from-loop.js tests unwinding; goccia.json enables both compat-traditional-for-loop and compat-var flags for testing.
Documentation
docs/decision-log.md, docs/goals.md, docs/language-tables.md, docs/language.md
Decision log documents the opt-in flag and per-iteration scoping; goals.md lists new toggles (--compat-var, --compat-function, --compat-traditional-for-loop) and excludes while/do...while; language-tables.md separates traditional for-loop (opt-in) from while/do...while (excluded); language.md adds Traditional for(init; test; update) Loop section with opt-in behavior and per-iteration semantics, and new while/do...while Excluded section.

Built-in ECMAScript Enhancements

Layer / File(s) Summary
Math Function Conformance
source/units/Goccia.Builtins.Math.pas
Math.floor enhanced to preserve signed zeros (−0) by early-returning for NaN/±∞/−0; Math.ceil introduces local V variable and preserves −0 when n ∈ (−1, 0); Math.pow reimplemented with full ES2026 algorithm handling NaN, ±0, ±∞, and negative bases with non-integer exponents via intermediate B, E, AbsB, IntPart, and ExpIsOddInteger variables.
Number Parsing & Conversion
source/units/Goccia.Builtins.GlobalNumber.pas, source/units/Goccia.Values.NumberObjectValue.pas
Number.parseInt and Number.parseFloat now use TrimECMAScriptWhitespace instead of Trim; parseFloat enhanced with exponent support (ExpSign, ExpValue, Mantissa) using IntPower normalization; NumberToString gains radix 2–36 support via new internal helpers IntegerToRadixString and FractionToRadixString, replacing previous radix-16-only shortcut.
Array Sort Validation
source/units/Goccia.Values.ArrayValue.pas
ArrayToSorted and ArraySort now validate comparator is callable (if provided and not undefined) before sorting, throwing TypeError with callback suggestion; refactored to build temporary array of present elements, sort, then write back and delete trailing indices to preserve holes.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CLI as CLI App<br/>(e.g., Bundler)
    participant Config as Config<br/>Resolution
    participant Parser as Parser
    participant Compiler as Compiler
    participant Evaluator as Evaluator
    participant Engine as Engine

    User->>CLI: --compat-traditional-for-loop
    CLI->>Config: Resolve per-file/root/CLI flags
    Config->>Engine: Set TraditionalForLoopsEnabled
    Engine->>Parser: Parser.TraditionalForLoopsEnabled := true
    
    User->>CLI: Source code with for(;;)
    CLI->>Parser: ParseSource(...)
    Parser->>Parser: LooksLikeTraditionalForHeader?
    alt Traditional For Header
        Parser->>Parser: ParseTraditionalForBody()
        Parser->>Parser: Create TGocciaForStatement
    else Not Traditional For
        Parser->>Parser: Emit compatibility warning
    end
    
    CLI->>Compiler: CompileForStatement(AStmt)
    Compiler->>Compiler: TryCompileCountedFor?
    alt Counted For Pattern
        Compiler->>Compiler: Establish per-iteration scope
        Compiler->>Compiler: Setup init/condition/update
        Compiler->>Compiler: Emit bytecode with disposal
    else Generic For Loop
        Compiler->>Compiler: Fall back to existing logic
    end
    
    User->>Engine: Execute bytecode
    Engine->>Evaluator: EvaluateFor(ForStatement, Context)
    Evaluator->>Evaluator: Setup lexical environment
    loop Per Iteration
        Evaluator->>Evaluator: Create per-iteration scope
        Evaluator->>Evaluator: Evaluate condition
        alt Condition true
            Evaluator->>Evaluator: Execute body
            Evaluator->>Evaluator: Evaluate update
        else Exit
            Evaluator->>Evaluator: Break loop
        end
    end
    Evaluator-->>Engine: ControlFlow result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • frostney/GocciaScript#286: Introduces engine-level compatibility flag infrastructure (cfASI, cfVar, cfFunction) that this PR extends with cfTraditionalFor.
  • frostney/GocciaScript#373: Establishes per-file config propagation and CLI option parsing machinery that this PR reuses for --compat-traditional-for-loop flag wiring.
  • frostney/GocciaScript#368: Implements similar opt-in compatibility feature pattern by adding new flags through the same engine/parser/compiler/CLI/test-runner call paths.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding an opt-in --compat-traditional-for-loop flag to enable traditional C-style for loops.
Description check ✅ Passed The description comprehensively covers all required template sections: Summary explains the feature and design constraints, Testing documents comprehensive verification with specific test counts and regression checks, and Documentation updates are listed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification internal Refactoring, CI, tooling, cleanup labels May 5, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Benchmark Results

407 benchmarks

Interpreted: 🟢 16 improved · 🔴 223 regressed · 168 unchanged · avg -2.5%
Bytecode: 🟢 248 improved · 🔴 46 regressed · 113 unchanged · avg +5.5%

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

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.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

Suite Timing

Test Runner (interpreted: 8,667 passed; bytecode: 8,667 passed)
Metric Interpreted Bytecode
Total 8667 8667
Passed 8667 ✅ 8667 ✅
Workers 4 4
Test Duration 1.80s 2.00s
Lex (cumulative) 234.9ms 161.7ms
Parse (cumulative) 256.0ms 259.2ms
Compile (cumulative) 569.4ms
Execute (cumulative) 1.76s 1.63s
Engine Total (cumulative) 2.25s 2.62s
Lex (avg/worker) 58.7ms 40.4ms
Parse (avg/worker) 64.0ms 64.8ms
Compile (avg/worker) 142.3ms
Execute (avg/worker) 438.8ms 406.8ms
Engine Total (avg/worker) 561.5ms 654.4ms

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 177.22 MiB 171.99 MiB
GC Peak Live 177.22 MiB 171.99 MiB
GC Allocated During Run 181.52 MiB 176.23 MiB
GC Limit 7.81 GiB 7.81 GiB
GC Collections 1 1
GC Collected Objects 74 74
Heap Start Allocated 144.2 KiB 144.2 KiB
Heap End Allocated 1.34 MiB 1.34 MiB
Heap Delta Allocated 1.20 MiB 1.20 MiB
Heap Delta Free 464.6 KiB 464.6 KiB
Benchmarks (interpreted: 407; bytecode: 407)
Metric Interpreted Bytecode
Total 407 407
Workers 4 4
Duration 2.38min 2.38min

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 2.75 MiB 2.74 MiB
GC Peak Live 93.31 MiB 66.57 MiB
GC Allocated During Run 13.18 GiB 10.17 GiB
GC Limit 7.81 GiB 7.81 GiB
GC Collections 2,824 2,654
GC Collected Objects 243,824,486 246,273,672
Heap Start Allocated 1.11 MiB 1.11 MiB
Heap End Allocated 1.11 MiB 1.11 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 5, 2026

test262 Conformance

🚫 Regression vs cached main baseline. 353 previously-passing test(s) now fail; pass count Δ +6. This run blocks merge — see "Newly failing" below.

Category Run Passed Δ Pass Failed Pass-rate Δ Rate
built-ins 23,448 13,355 -17 10,092 57.0% -0.1pp
harness 116 69 ±0 47 59.5% ±0pp
intl402 3,324 165 -242 3,159 5.0% -7.3pp
language 23,635 12,044 +268 11,591 51.0% +1.1pp
staging 1,483 441 -3 1,040 29.7% -0.2pp
total 52,006 26,074 +6 25,929 50.1% ±0pp

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 (+359 / -353)

Newly failing (353):

  • built-ins/Array/prototype/sort/S15.4.4.11_A2.1_T3.js
  • built-ins/Array/prototype/sort/S15.4.4.11_A2.2_T3.js
  • built-ins/Array/prototype/sort/S15.4.4.11_A3_T1.js
  • built-ins/Array/prototype/sort/S15.4.4.11_A3_T2.js
  • built-ins/Array/prototype/toSorted/comparefn-not-a-function.js
  • built-ins/Array/S15.4_A1.1_T10.js
  • built-ins/Atomics/add/bad-range.js
  • built-ins/Atomics/add/bigint/bad-range.js
  • built-ins/Atomics/and/bad-range.js
  • built-ins/Atomics/and/bigint/bad-range.js
  • built-ins/Atomics/compareExchange/bad-range.js
  • built-ins/Atomics/compareExchange/bigint/bad-range.js
  • built-ins/Atomics/exchange/bad-range.js
  • built-ins/Atomics/exchange/bigint/bad-range.js
  • built-ins/Atomics/load/bad-range.js
  • built-ins/Atomics/load/bigint/bad-range.js
  • built-ins/Atomics/notify/bad-range.js
  • built-ins/Atomics/notify/bigint/bad-range.js
  • built-ins/Atomics/or/bad-range.js
  • built-ins/Atomics/or/bigint/bad-range.js
  • built-ins/Atomics/store/bad-range.js
  • built-ins/Atomics/store/bigint/bad-range.js
  • built-ins/Atomics/store/bigint/good-views.js
  • built-ins/Atomics/store/good-views.js
  • built-ins/Atomics/sub/bad-range.js
  • built-ins/Atomics/sub/bigint/bad-range.js
  • built-ins/Atomics/wait/bad-range.js
  • built-ins/Atomics/wait/bigint/bad-range.js
  • built-ins/Atomics/xor/bad-range.js
  • built-ins/Atomics/xor/bigint/bad-range.js
  • built-ins/BigInt/prototype/toString/a-z.js
  • built-ins/decodeURI/S15.1.3.1_A2.2_T1.js
  • built-ins/decodeURI/S15.1.3.1_A2.5_T1.js
  • built-ins/decodeURIComponent/S15.1.3.2_A2.5_T1.js
  • built-ins/encodeURI/S15.1.3.3_A2.1_T1.js
  • built-ins/encodeURIComponent/S15.1.3.4_A2.1_T1.js
  • built-ins/Iterator/zip/padding-iteration.js
  • built-ins/Iterator/zipKeyed/results-object-from-array.js
  • built-ins/Math/floor/S15.8.2.9_A7.js
  • built-ins/Object/keys/15.2.3.14-5-15.js
  • built-ins/Object/keys/15.2.3.14-5-16.js
  • built-ins/RegExp/character-class-escape-non-whitespace.js
  • built-ins/RegExp/prototype/exec/failure-lastindex-set.js
  • built-ins/RegExp/prototype/exec/S15.10.6.2_A1_T6.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.13_A2_T4.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.5_A1_T4.js
  • built-ins/RegExp/S15.10.2.8_A3_T16.js
  • built-ins/RegExp/unicode_restricted_identity_escape_alpha.js
  • built-ins/RegExp/unicode_restricted_identity_escape.js
  • built-ins/String/prototype/localeCompare/15.5.4.9_CE.js
  • built-ins/String/prototype/match/S15.5.4.10_A1_T6.js
  • built-ins/String/prototype/match/S15.5.4.10_A1_T7.js
  • built-ins/Temporal/ZonedDateTime/prototype/monthCode/no-leap-months.js
  • built-ins/TypedArray/out-of-bounds-get-and-set.js
  • built-ins/TypedArray/out-of-bounds-has.js
  • built-ins/TypedArray/prototype/lastIndexOf/negative-index-and-resize-to-smaller.js
  • built-ins/TypedArray/prototype/slice/resize-count-bytes-to-zero.js
  • built-ins/TypedArray/prototype/toSorted/comparefn-not-a-function.js
  • built-ins/TypedArrayConstructors/internals/DefineOwnProperty/conversion-operation-consistent-nan.js
  • intl402/Intl/getCanonicalLocales/invalid-tags.js
  • intl402/String/prototype/localeCompare/that-arg-coerced-to-string.js
  • intl402/String/prototype/localeCompare/this-value-coerced-to-string.js
  • intl402/String/prototype/toLocaleUpperCase/special_casing_Lithuanian.js
  • intl402/Temporal/PlainDate/from/basic-hebrew.js
  • intl402/Temporal/PlainDate/from/calc-epoch-year-hebrew.js
  • intl402/Temporal/PlainDate/from/hebrew-keviah.js
  • intl402/Temporal/PlainDate/from/leap-year-hebrew.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-buddhist.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-chinese.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-coptic.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-dangi.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-ethioaa.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-ethiopic.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-hebrew.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-indian.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-islamic-civil.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-islamic-tbla.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-islamic-umalqura.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-persian.js
  • intl402/Temporal/PlainDate/prototype/daysInMonth/basic-roc.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-buddhist.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-coptic.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-ethioaa.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-ethiopic.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-indian.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-islamic-civil.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-islamic-tbla.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-islamic-umalqura.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-persian.js
  • intl402/Temporal/PlainDate/prototype/daysInYear/basic-roc.js
  • intl402/Temporal/PlainDate/prototype/inLeapYear/basic-buddhist.js
  • intl402/Temporal/PlainDate/prototype/inLeapYear/basic-chinese.js
  • intl402/Temporal/PlainDate/prototype/inLeapYear/basic-coptic.js
  • intl402/Temporal/PlainDate/prototype/inLeapYear/basic-dangi.js
  • intl402/Temporal/PlainDate/prototype/inLeapYear/basic-ethioaa.js
  • intl402/Temporal/PlainDate/prototype/inLeapYear/basic-ethiopic.js
  • … 253 more

Newly passing (359):

  • built-ins/Array/prototype/indexOf/coerced-searchelement-fromindex-grow.js
  • built-ins/Array/prototype/lastIndexOf/coerced-position-grow.js
  • built-ins/Array/prototype/sort/comparefn-nonfunction-call-throws.js
  • built-ins/Array/prototype/sort/S15.4.4.11_A1.5_T1.js
  • built-ins/Array/prototype/toLocaleString/user-provided-tolocalestring-grow.js
  • built-ins/Array/S15.4.1_A2.2_T1.js
  • built-ins/Array/S15.4.2.1_A2.2_T1.js
  • built-ins/BigInt/constructor-from-binary-string.js
  • built-ins/Iterator/concat/many-arguments.js
  • built-ins/Iterator/concat/single-argument.js
  • built-ins/Iterator/from/get-next-method-only-once.js
  • built-ins/Iterator/prototype/drop/underlying-iterator-advanced-in-parallel.js
  • built-ins/Iterator/prototype/every/predicate-returns-non-boolean.js
  • built-ins/Iterator/prototype/every/predicate-returns-truthy-then-falsey.js
  • built-ins/Iterator/prototype/filter/underlying-iterator-advanced-in-parallel.js
  • built-ins/Iterator/prototype/find/predicate-returns-falsey-then-truthy.js
  • built-ins/Iterator/prototype/flatMap/flattens-iterable.js
  • built-ins/Iterator/prototype/flatMap/underlying-iterator-advanced-in-parallel.js
  • built-ins/Iterator/prototype/map/returned-iterator-yields-mapper-return-values.js
  • built-ins/Iterator/prototype/map/underlying-iterator-advanced-in-parallel.js
  • built-ins/Iterator/prototype/some/predicate-returns-falsey-then-truthy.js
  • built-ins/Iterator/prototype/take/underlying-iterator-advanced-in-parallel.js
  • built-ins/Math/ceil/S15.8.2.6_A6.js
  • built-ins/Math/floor/S15.8.2.9_A3.js
  • built-ins/Number/prototype/toExponential/undefined-fractiondigits.js
  • built-ins/parseFloat/S15.1.2.3_A1_T2.js
  • built-ins/parseFloat/S15.1.2.3_A2_T3.js
  • built-ins/parseFloat/S15.1.2.3_A2_T8.js
  • built-ins/parseFloat/S15.1.2.3_A2_T9.js
  • built-ins/parseFloat/S15.1.2.3_A4_T2.js
  • built-ins/parseFloat/S15.1.2.3_A4_T5.js
  • built-ins/parseFloat/S15.1.2.3_A4_T6.js
  • built-ins/parseFloat/S15.1.2.3_A4_T7.js
  • built-ins/parseFloat/S15.1.2.3_A5_T2.js
  • built-ins/parseFloat/S15.1.2.3_A5_T3.js
  • built-ins/parseFloat/S15.1.2.3_A5_T4.js
  • built-ins/parseFloat/tonumber-numeric-separator-literal-dd-dot-dd-ep-sign-minus-dd-nsl-dd.js
  • built-ins/parseFloat/tonumber-numeric-separator-literal-dd-dot-dd-ep-sign-minus-dds-nsl-dd.js
  • built-ins/parseFloat/tonumber-numeric-separator-literal-dd-dot-dd-ep-sign-plus-dd-nsl-dd.js
  • built-ins/parseFloat/tonumber-numeric-separator-literal-dd-dot-dd-ep-sign-plus-dds-nsl-dd.js
  • built-ins/parseInt/S15.1.2.2_A2_T3.js
  • built-ins/parseInt/S15.1.2.2_A2_T8.js
  • built-ins/parseInt/S15.1.2.2_A2_T9.js
  • built-ins/TypedArray/prototype/indexOf/coerced-searchelement-fromindex-grow.js
  • built-ins/TypedArray/prototype/lastIndexOf/coerced-position-grow.js
  • built-ins/TypedArray/prototype/toLocaleString/user-provided-tolocalestring-grow.js
  • language/expressions/class/accessor-name-inst-computed-in.js
  • language/expressions/class/accessor-name-static-computed-in.js
  • language/expressions/conditional/in-branch-1.js
  • language/expressions/optional-chaining/iteration-statement-for.js
  • language/expressions/tagged-template/cache-same-site-top-level.js
  • language/expressions/yield/within-for.js
  • language/literals/regexp/inequality.js
  • language/module-code/instn-local-bndng-for.js
  • language/statements/break/12.8-1.js
  • language/statements/break/line-terminators.js
  • language/statements/break/S12.8_A9_T2.js
  • language/statements/const/syntax/const-invalid-assignment-next-expression-for.js
  • language/statements/continue/line-terminators.js
  • language/statements/continue/nested-let-bound-for-loops-inner-continue.js
  • language/statements/continue/nested-let-bound-for-loops-outer-continue.js
  • language/statements/continue/no-label-continue.js
  • language/statements/continue/S12.7_A9_T2.js
  • language/statements/continue/shadowing-loop-variable-in-same-scope-as-continue.js
  • language/statements/for/12.6.3_2-3-a-ii-1.js
  • language/statements/for/12.6.3_2-3-a-ii-10.js
  • language/statements/for/12.6.3_2-3-a-ii-17.js
  • language/statements/for/12.6.3_2-3-a-ii-19.js
  • language/statements/for/12.6.3_2-3-a-ii-2.js
  • language/statements/for/12.6.3_2-3-a-ii-20.js
  • language/statements/for/12.6.3_2-3-a-ii-21.js
  • language/statements/for/12.6.3_2-3-a-ii-3.js
  • language/statements/for/12.6.3_2-3-a-ii-4.js
  • language/statements/for/12.6.3_2-3-a-ii-5.js
  • language/statements/for/12.6.3_2-3-a-ii-6.js
  • language/statements/for/12.6.3_2-3-a-ii-7.js
  • language/statements/for/12.6.3_2-3-a-ii-8.js
  • language/statements/for/12.6.3_2-3-a-ii-9.js
  • language/statements/for/dstr/const-ary-init-iter-close.js
  • language/statements/for/dstr/const-ary-init-iter-get-err.js
  • language/statements/for/dstr/const-ary-init-iter-no-close.js
  • language/statements/for/dstr/const-ary-name-iter-val.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-elem-init.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-elem-iter.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-elision-init.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-elision-iter.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-empty-init.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-empty-iter.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-rest-init.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-rest-iter.js
  • language/statements/for/dstr/const-ary-ptrn-elem-ary-val-null.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-init-exhausted.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-init-hole.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-init-skipped.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-init-throws.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-init-undef.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-init-unresolvable.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-iter-complete.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-iter-done.js
  • language/statements/for/dstr/const-ary-ptrn-elem-id-iter-step-err.js
  • … 259 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.

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: 8

🧹 Nitpick comments (1)
scripts/test-cli-config.ts (1)

611-629: ⚡ Quick win

Add an explicit bytecode assertion for the flag-off no-op path.

This block validates default mode only. Adding the same check with --mode=bytecode would close a small regression gap for disabled behavior.

💡 Suggested test addition
   const stderr = await $`${LOADER} ${join(tmp, "no-flag.js")} 2>&1`.text();
   if (!stderr.includes("Traditional 'for(;;)' loops are not supported")) {
     throw new Error(`Loader without flag should emit warning, got: ${stderr}`);
   }
   if (stderr.includes("should not run")) {
     throw new Error(`Loader without flag should not execute the loop body, got: ${stderr}`);
   }
+
+  const stderrBc = await $`${LOADER} ${join(tmp, "no-flag.js")} --mode=bytecode 2>&1`.text();
+  if (!stderrBc.includes("Traditional 'for(;;)' loops are not supported")) {
+    throw new Error(`Bytecode loader without flag should emit warning, got: ${stderrBc}`);
+  }
+  if (stderrBc.includes("should not run")) {
+    throw new Error(`Bytecode loader without flag should not execute the loop body, got: ${stderrBc}`);
+  }
🤖 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 `@scripts/test-cli-config.ts` around lines 611 - 629, Add a parallel assertion
that runs the same "no-flag.js" scenario with bytecode mode to ensure the
default no-op behavior is preserved under --mode=bytecode: after the existing
block that creates no-flag.js and invokes `${LOADER} ${join(tmp,
"no-flag.js")}`, add a try/finally scoped invocation using `${LOADER}
--mode=bytecode ${join(tmp, "no-flag.js")} 2>&1` (use the same tmp,
makeTmp/clean, writeFileSync helpers) and assert the stderr includes
"Traditional 'for(;;)' loops are not supported" and does NOT include "should not
run" (throw errors with clear messages on failure), mirroring the checks in the
existing block so the bytecode-disabled path is explicitly tested.
🤖 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 `@source/units/Goccia.Compiler.Statements.pas`:
- Around line 2143-2145: The synthetic iteration locals created with
ACtx.Scope.BeginScope / DeclareLocal (e.g., the pattern StartReg :=
ACtx.Scope.DeclareLocal(LoopName, False) followed by EmitInstruction(...
OP_LOAD_INT ...)) must copy the original header binding's type metadata and
flags so ResolveLocal sees the same TypeHint, strictness, array-typing and
annotations as the loop variable declared in the header; update the
synthetic-local creation to locate the header binding (the variable declared by
the loop header / CompileVariableDeclaration), and transfer its TypeHint,
strictness/flags, array typing and any annotations onto the new scope entry
after DeclareLocal, and apply the same fix at the other synthetic-binding sites
referenced (the other blocks using the same DeclareLocal pattern around
iteration creation and the counted fast-path) so type-driven
checks/optimizations continue to apply.
- Around line 2153-2158: The fast-path currently snapshots the loop bound by
calling ACtx.CompileExpression(CondExpr.Right, LimitReg) once before entering
the loop (see LimitReg, ACtx.CompileExpression, CondExpr.Right), which breaks
semantics when the RHS has side effects or is mutable; fix by either moving the
ACtx.CompileExpression(CondExpr.Right, LimitReg) into the loop body so the RHS
is re-evaluated each iteration, or add a guard before selecting the fast path
that only allows it when CondExpr.Right is provably invariant (e.g.,
literal/constant or a syntactic pure expression) and fall back to the safe path
otherwise. Ensure any change references LimitReg, OneReg, CmpReg and preserves
register allocation semantics and deallocation in ACtx.Scope.
- Around line 2093-2099: The early-return condition currently exits for both
IsConst and IsVarKeyword, preventing `var` loops from reaching the per-iteration
(lexical) fallback; change the conditional so only const immediately Exit (keep
"if IsConst then Exit") and remove IsVarKeyword from this unconditional
fast-path rejection, allowing the later code that builds per-iteration bindings
for non-IsVar to run and for compatibility flags to decide whether `var` should
be treated like `let`; apply the same fix in the second occurrence around the
2363-2384 region so both sites use IsConst-only for the early Exit and let the
downstream logic handle IsVarKeyword/compat flags.
- Around line 2184-2206: Create a dedicated per-iteration cleanup target
immediately before the ACtx.Scope.EndScope(...) / OP_CLOSE_UPVALUE loop (i.e.,
place an "iteration cleanup" label right where ClosedLocals/ClosedCount are
processed) and change the BreakJumps patching so PatchJumpTarget(ACtx,
BreakJumps[I]) targets that iteration-cleanup label instead of the final
ExitJump; then let the cleanup code run EndScope and emit the OP_CLOSE_UPVALUE
loop and only after that jump/patch to ExitJump as before—apply the same change
to the other occurrence around lines 2454-2487. This uses PatchJumpTarget,
BreakJumps, ExitJump, ACtx.Scope.EndScope and the OP_CLOSE_UPVALUE/ClosedLocals
loop as the anchor points.

In `@source/units/Goccia.Evaluator.pas`:
- Around line 1936-2009: The classic for-loop evaluator (EvaluateFor) must
persist and restore loop phase and per-iteration bindings across generator
suspension so Init/Condition/Body/Update aren't re-run; before any call that may
yield (calls to EvaluateStatement(EvaluatEExpression) and
EvaluateLoopBodyStatement) capture a suspension record containing the current
loop phase (InitDone/Condition/Body/Update), the IterContext/HeaderContext
references, and the PerIterNames' values (PrevValues) and any binding type
hints, and store it on the current execution frame; on resume detect and consume
that record to restore IterScope/HeaderScope and PrevValues and jump to the
correct phase so you skip Init if already completed and continue at
Condition/Body/Update as appropriate (update the code paths around
AForStatement.Init, AForStatement.Condition, EvaluateLoopBodyStatement, and
AForStatement.Update to save before calls that may yield and to restore before
proceeding).
- Around line 1993-2008: The current loop copies iteration bindings into
HeaderScope before executing the for-loop Update, causing closures created in
Update to close over the shared header slot and allowing evaluator write-back to
fail; change the order so EvaluateExpression(AForStatement.Update, IterContext)
is executed while the per-iteration bindings remain in IterScope (use
IterContext), then after Update completes force-copy each PerIterNames binding's
final value from IterScope.GetBinding(Name).Value back into HeaderScope via a
direct copy/AssignBinding to update the header slot for the next iteration; keep
the IsLexical check around the per-iteration binding handling but ensure Update
is always evaluated in IterContext and the write-back occurs only after
EvaluateExpression returns.

In `@source/units/Goccia.Parser.pas`:
- Around line 4020-4022: The parser currently gates recognizing a gttVar token
in traditional for init on FVarDeclarationsEnabled; change the branch condition
around Check(gttLet)/Check(gttConst) so that Check(gttVar) is accepted
unconditionally (remove the FVarDeclarationsEnabled guard) so `for (var ... )`
is parsed rather than skipped; ensure you make the same change in the analogous
block handling lines 4087-4094 and leave the later compatibility/hoisting
decision to the code that inspects the declaration kind after parsing (do not
emit the compat warning or skip parsing here).
- Around line 232-233: The TraditionalForLoopsEnabled flag added as the property
(FTraditionalForLoopsEnabled / TraditionalForLoopsEnabled) must be propagated
into any nested/child parser instances created inside
ParseInterpolationExpression and the template call sites; update
ParseInterpolationExpression where it constructs child parsers (copying
ASI/var/function flags) to also copy FTraditionalForLoopsEnabled into the new
parser instance, and likewise pass FTraditionalForLoopsEnabled from the tagged
and non-tagged template call sites so nested template expressions inherit the
traditional-for-loops setting.

---

Nitpick comments:
In `@scripts/test-cli-config.ts`:
- Around line 611-629: Add a parallel assertion that runs the same "no-flag.js"
scenario with bytecode mode to ensure the default no-op behavior is preserved
under --mode=bytecode: after the existing block that creates no-flag.js and
invokes `${LOADER} ${join(tmp, "no-flag.js")}`, add a try/finally scoped
invocation using `${LOADER} --mode=bytecode ${join(tmp, "no-flag.js")} 2>&1`
(use the same tmp, makeTmp/clean, writeFileSync helpers) and assert the stderr
includes "Traditional 'for(;;)' loops are not supported" and does NOT include
"should not run" (throw errors with clear messages on failure), mirroring the
checks in the existing block so the bytecode-disabled path is explicitly tested.
🪄 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: 28a9db76-8768-438b-9a89-a257dc1ea3e2

📥 Commits

Reviewing files that changed from the base of the PR and between 250ba9d and 517be02.

📒 Files selected for processing (30)
  • docs/decision-log.md
  • docs/goals.md
  • docs/language-tables.md
  • docs/language.md
  • scripts/test-cli-config.ts
  • source/app/Goccia.CLI.Application.pas
  • source/app/GocciaBenchmarkRunner.dpr
  • source/app/GocciaBundler.dpr
  • source/app/GocciaREPL.dpr
  • source/app/GocciaScriptLoader.dpr
  • source/app/GocciaTestRunner.dpr
  • source/shared/CLI.Options.pas
  • source/units/Goccia.AST.BindingPatterns.pas
  • source/units/Goccia.AST.Statements.pas
  • source/units/Goccia.Compiler.Statements.pas
  • source/units/Goccia.Compiler.pas
  • source/units/Goccia.Engine.pas
  • source/units/Goccia.Evaluator.pas
  • source/units/Goccia.Interpreter.pas
  • source/units/Goccia.Modules.Loader.pas
  • source/units/Goccia.Parser.pas
  • tests/language/for-loop/basic-counter.js
  • tests/language/for-loop/break-continue.js
  • tests/language/for-loop/destructuring-init.js
  • tests/language/for-loop/empty-parts.js
  • tests/language/for-loop/goccia.json
  • tests/language/for-loop/let-per-iteration.js
  • tests/language/for-loop/return-from-loop.js
  • tests/language/for-loop/with-var/goccia.json
  • tests/language/for-loop/with-var/var-shared-binding.js

Comment thread source/units/Goccia.Compiler.Statements.pas
Comment thread source/units/Goccia.Compiler.Statements.pas
Comment thread source/units/Goccia.Compiler.Statements.pas
Comment thread source/units/Goccia.Compiler.Statements.pas
Comment thread source/units/Goccia.Evaluator.pas
Comment thread source/units/Goccia.Evaluator.pas
Comment thread source/units/Goccia.Parser.pas
Comment thread source/units/Goccia.Parser.pas
frostney and others added 2 commits May 5, 2026 22:04
The bare loader has its own option-parsing and engine-config path,
so the flag has to be threaded through it explicitly (mirrors the
existing --compat-var / --compat-function plumbing). Without this,
test262 (which runs through the bare loader with --compat-all) was
not picking up the flag and only one extra traditional-for test passed
across the suite. With the wiring, language/statements/for/ goes from
60/385 to 325/385 in bytecode mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TryCompileCountedFor now bails when the init has a type annotation or
  the condition RHS isn't a literal/identifier the body doesn't reassign;
  the spec re-evaluates the test each iteration, so snapshotting a
  side-effecting or mutable RHS once is wrong (#531 review).
- EvaluateFor's iteration→header sync now uses ForceUpdateBinding so a
  body that runs to completion under `for (const ...)` doesn't trip the
  evaluator's own writability check on the next iteration's snapshot.
- ParseInterpolationExpression threads TraditionalForLoopsEnabled into
  child parsers so an IIFE inside a template `${...}` can use traditional
  for when the surrounding source has it enabled.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes a regression caused by my own dispatch — `for (lhs of expr)` heads
where `lhs` is an assignment target (not a declaration) were previously
warned-and-skipped, but now fell through to ParseTraditionalForBody and
syntax-errored on `Expected ';'` before the `of`. Same shape:
`for ([a, b] of arr)`, `for ({x} of objs)`, etc. Several test262
suites use these forms in harness/feature tests.

LooksLikeTraditionalForHeader is a pure peek that returns True only when
a top-depth ';' exists inside the for-header parens. for-of/for-in
heads contain no top-level ';', so they fall back to the
warn-and-skip path. Also handles the edge case in
language/statements/for/head-init-async-of.js where `for (async of => {};
...; ...)` is a traditional for whose init is an arrow with parameter
named `of` — the prior heuristic would have misclassified that as a
for-of shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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: 2

♻️ Duplicate comments (1)
source/units/Goccia.Compiler.Statements.pas (1)

2163-2165: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Synthetic iteration bindings still drop the init binding's metadata.

The recreated per-iteration locals are plain DeclareLocal(...); OP_MOVE ... bindings. They never copy the header binding's type hint, strict flag, or annotation/array metadata, so the body resolves i to an untyped shadow instead of the initialized binding metadata.

Also applies to: 2205-2207, 2463-2471

🤖 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.Compiler.Statements.pas` around lines 2163 - 2165,
Synthetic per-iteration locals created with ACtx.Scope.DeclareLocal (e.g., the
StartReg/LoopName pattern) are missing the init/header binding metadata; update
the spots where you recreate the loop-local (lines around the StartReg/LoopName
code and the other occurrences noted) to copy the original binding's type hint,
strict flag, and annotation/array metadata onto the new local after DeclareLocal
(retrieve the header binding from the scope or the symbol for the loop variable,
then assign its TypeHint, IsStrict (or equivalent), Annotations and
ArrayMetadata fields to the newly declared local) so the body resolves the
per-iteration variable with the same metadata as the initializer.
🤖 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 `@source/units/Goccia.Compiler.Statements.pas`:
- Around line 2201-2221: The per-iteration lexical scope is opened too late:
BeginScope/DeclareLocal (ACtx.Scope.BeginScope and ACtx.Scope.DeclareLocal) are
called only around the loop body so the test and afterthought compile against
the shared canonical slot; move the per-iteration scope so it encloses the test,
body and update. Concretely, open the scope (call ACtx.Scope.BeginScope and
create the iteration Slot via ACtx.Scope.DeclareLocal) immediately after
computing StartReg/LimitReg/CmpReg (before EmitInstruction of ExitOpcode and
before compiling the test and update), use that Slot for the body and for the
update/step emission (where currently OuterSlot/Slot are used), and only call
ACtx.Scope.EndScope/EmitInstruction(OP_CLOSE_UPVALUE...) after the update/jump
so closures in test/update capture the per-iteration binding; keep existing
PatchJumpTarget(ContinueJumps) and loop jump logic (LoopStart, ExitJump,
ContinueJumps) but adjust their positions to match the moved
BeginScope/EndScope.
- Around line 2124-2131: The current fast-path wrongly trusts a bare
TGocciaIdentifierExpression on CondExpr.Right because ForBodyAssignsIdentifier
doesn't detect nested writes (IIFEs, assignment expressions, call-returned
assignments); make the guard conservative: only allow the fast-path when
CondExpr.Right is a TGocciaLiteralExpression (remove/disable the identifier-RHS
branch) or else fall back to the safe slow path; apply the same change where the
same pattern appears (the block around ForBodyAssignsIdentifier and the
identical checks in the 2248-2356 region) so LimitReg is only computed once when
the RHS is provably constant.

---

Duplicate comments:
In `@source/units/Goccia.Compiler.Statements.pas`:
- Around line 2163-2165: Synthetic per-iteration locals created with
ACtx.Scope.DeclareLocal (e.g., the StartReg/LoopName pattern) are missing the
init/header binding metadata; update the spots where you recreate the loop-local
(lines around the StartReg/LoopName code and the other occurrences noted) to
copy the original binding's type hint, strict flag, and annotation/array
metadata onto the new local after DeclareLocal (retrieve the header binding from
the scope or the symbol for the loop variable, then assign its TypeHint,
IsStrict (or equivalent), Annotations and ArrayMetadata fields to the newly
declared local) so the body resolves the per-iteration variable with the same
metadata as the initializer.
🪄 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: 6b53b757-1d98-4b24-ac08-4f1c6c7b1f46

📥 Commits

Reviewing files that changed from the base of the PR and between b284ecf and 7b1cf49.

📒 Files selected for processing (3)
  • source/units/Goccia.Compiler.Statements.pas
  • source/units/Goccia.Evaluator.pas
  • source/units/Goccia.Parser.pas

Comment thread source/units/Goccia.Compiler.Statements.pas Outdated
Comment thread source/units/Goccia.Compiler.Statements.pas
Bundling traditional `for(;;)` into --compat-all re-runs loop bodies
that warn-and-skipped on baseline, surfacing 367 unrelated engine gaps
(Atomics, Intl, Math.pow edge cases, RegExp tests) that were trivially
passing before. Test262 runs --compat-all unconditionally through the
bare loader, so the bundling forced a -31 net merge-blocking regression.

Make the flag strictly opt-in: callers who want traditional `for(;;)`
must pass --compat-traditional-for-loop explicitly (or set the
config / engine property). --compat-all continues to OR in --compat-var
and --compat-function as before. Test262 baseline is restored.

Also tighten TryCompileCountedFor's condition-RHS guard to literals
only (#531 review): `ForBodyAssignsIdentifier` doesn't see writes
through IIFEs/callbacks/property setters, so `i < limit` was unsafe to
fast-path even when the body looked clean. Falls through to the
general path where the condition is re-evaluated each iteration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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: 1

♻️ Duplicate comments (1)
source/units/Goccia.Compiler.Statements.pas (1)

2446-2452: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Synthetic per-iteration locals still drop the header binding’s type metadata.

These inner bindings only copy the slot value. Inside the body, ResolveLocal now hits the synthetic local, so typed loop vars lose their TypeHint, strictness, and array/type-annotation metadata for the body even though the counted fast path now bails out for annotated headers.

Capture the outer local record before BeginScope and mirror its metadata onto each DeclareLocal result here, not just the value.

Also applies to: 2457-2465

🤖 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.Compiler.Statements.pas` around lines 2446 - 2452, The
synthetic per-iteration locals currently copy only the slot value
(OuterSlots/InnerSlots assignment using
ACtx.Scope.GetLocal(ACtx.Scope.ResolveLocal(Name)).Slot), which drops the header
binding’s type metadata; fix by capturing the outer local record (call
ACtx.Scope.GetLocal(ACtx.Scope.ResolveLocal(Name)) once before BeginScope) and
when creating each synthetic local via DeclareLocal mirror the captured record’s
metadata (TypeHint, strictness, array/type-annotation fields) onto the new local
instead of only copying Slot; apply the same change to the second similar loop
(the block around lines 2457-2465) so both per-iteration synthetic locals
preserve the original header metadata.
🤖 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 `@source/units/Goccia.Compiler.Statements.pas`:
- Around line 2117-2125: The current fast-path guard only checks that
CondExpr.Right is a TGocciaLiteralExpression but the emitted code uses integer
ops (OP_GTE_INT/OP_LTE_INT), so update the guard to require an integral numeric
literal: when CondExpr.Right is a TGocciaLiteralExpression also verify it
represents a numeric value with no fractional part (e.g., IsNumber and
Trunc(value)=value or an explicit IsInteger helper) and only then take the fast
path; apply the same stricter check at the other occurrence noted around lines
2171-2172 so both fast-path branches only accept integral numeric literals
compatible with OP_GTE_INT/OP_LTE_INT.

---

Duplicate comments:
In `@source/units/Goccia.Compiler.Statements.pas`:
- Around line 2446-2452: The synthetic per-iteration locals currently copy only
the slot value (OuterSlots/InnerSlots assignment using
ACtx.Scope.GetLocal(ACtx.Scope.ResolveLocal(Name)).Slot), which drops the header
binding’s type metadata; fix by capturing the outer local record (call
ACtx.Scope.GetLocal(ACtx.Scope.ResolveLocal(Name)) once before BeginScope) and
when creating each synthetic local via DeclareLocal mirror the captured record’s
metadata (TypeHint, strictness, array/type-annotation fields) onto the new local
instead of only copying Slot; apply the same change to the second similar loop
(the block around lines 2457-2465) so both per-iteration synthetic locals
preserve the original header metadata.
🪄 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: fc47aa34-e3b5-417d-9031-99ca4d32492f

📥 Commits

Reviewing files that changed from the base of the PR and between 7b1cf49 and cd0f7bb.

📒 Files selected for processing (5)
  • docs/decision-log.md
  • docs/language.md
  • source/app/Goccia.CLI.Application.pas
  • source/app/GocciaScriptLoaderBare.dpr
  • source/units/Goccia.Compiler.Statements.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/app/GocciaScriptLoaderBare.dpr

Comment thread source/units/Goccia.Compiler.Statements.pas
frostney and others added 2 commits May 5, 2026 22:57
Brings the bot review fix forward over the decouple revert. The
identifier-RHS branch in TryCompileCountedFor used
ForBodyAssignsIdentifier as a safety check, but that walker doesn't
see writes through IIFEs, callbacks, property setters, or eval-like
reaches — so a bare-identifier RHS was unsafe for `i < limit` shapes
even when the body looked clean. Falls through to the general path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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: 1

🤖 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`:
- Around line 20-21: The decision log entry for the new flag
--compat-traditional-for-loop contradicts the PR objectives: update the decision
text so it matches the implementation and PR intent by stating that
--compat-traditional-for-loop is included (ORed) into --compat-all; specifically
edit the decision-log.md paragraph to change the "Not bundled into --compat-all"
wording to reflect that the flag is ORed into --compat-all (and note the
reverted decoupling mentioned in the PR objectives), ensuring the language
references the flag name --compat-traditional-for-loop and --compat-all so
readers can verify consistency with the PR objectives.
🪄 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: a162039a-839c-44ef-a639-ffda69a5d888

📥 Commits

Reviewing files that changed from the base of the PR and between cd0f7bb and 317e417.

📒 Files selected for processing (4)
  • docs/decision-log.md
  • docs/language.md
  • source/app/Goccia.CLI.Application.pas
  • source/app/GocciaScriptLoaderBare.dpr
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/app/Goccia.CLI.Application.pas

Comment thread docs/decision-log.md Outdated
frostney and others added 5 commits May 5, 2026 23:12
Both bugs were silently passing test262 because traditional for(;;)
loops in their test bodies warn-and-skipped on baseline. Restoring
--compat-all coverage in #531 surfaced both as honest regressions;
fix at the source.

Math.pow (§6.1.6.1.3 Number::exponentiate)
  Previous impl forwarded directly to FreePascal's `Power(B, E)`,
  which uses `exp(E * ln(B))` and returns NaN for any base ≤ 0. Spec
  has 30+ special cases: NaN/+0/-0 exponent, ±∞ base, ±∞ exponent,
  signed-zero/Infinity sign rules driven by "exponent is an odd
  integer". Adds explicit branches for §6.1.6.1.3 steps 1-13 and
  guards Round() against doubles above 2^53 (oddness can only be
  detected up to that magnitude — Round(1.8e308) overflows Int64).
  Fixes Math/pow/* tests for ±Infinity^x and x^±Infinity.

parseFloat (§21.1.2.13.1 StrUnsignedDecimalLiteral)
  Previous impl parsed integer + fractional digits but no
  ExponentPart, so `parseFloat("0.1e1")` returned 0.1 instead of 1.
  Adds optional `(e|E)[+-]?DecimalDigits` parsing after the fraction;
  malformed exponent (stray 'e' with no digits) returns the mantissa
  alone per the longest-valid-prefix rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ES2026 §11.2 WhiteSpace and §11.3 LineTerminator include Unicode
characters beyond ASCII space — Tab/LF/CR/VT/FF/SP/NBSP/USP plus the
ZWNBSP and LS/PS line terminators. parseInt/parseFloat call
TrimString(string, start) which uses StrWhiteSpace, so a Unicode
whitespace prefix like `\u3000` (ideographic space) must be skipped
before scanning digits.

Goccia's parseInt/parseFloat were calling FreePascal's `Trim()`, which
only strips ASCII whitespace (#0..#32). `parseInt("\u20001")` returned
NaN instead of 1. Switch both to `TrimECMAScriptWhitespace` from
`TextSemantics`, the same helper String.prototype.trim and BigInt
parsing already use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Number.prototype.toString (§21.1.3.6 / §6.1.6.1.20)
  Previous impl only handled radix 10 (default ToString) and radix 16
  (IntToHex two's-complement, which gave `(-255).toString(16) =
  'ffffffffffffff01'` instead of `-ff`). Every radix in 11-15, 17-36
  fell through to decimal output. Implements the generalized
  decimal-to-radix algorithm: optional sign + integer-part-digits +
  optional `.fraction-digits`, using `0-9a-z` for digits 10-35.
  IntegerToRadixString and FractionToRadixString are file-local helpers
  shared with the integer path; the fraction conversion uses 52
  digits (enough to round-trip a binary64 mantissa) with trailing-zero
  trimming. Fixes Number/prototype/toString/a-z and any test using
  non-10 / non-16 radix.

Math.ceil (§21.3.2.10)
  Per spec step 3, when the argument is in (-1, 0) the result is -0𝔽
  (preserves signed-zero). Goccia returned `+0` because it skipped
  straight to FreePascal's Ceil(), which collapses signed zeros. Adds
  the explicit "negative-fractional → -0" branch so
  `Math.ceil(-0.1) === -Math.floor(0.1)` per Object.is. Fixes
  Math/ceil/S15.8.2.6_A7 (the 2000-iteration ceil(x) ≡ -floor(-x)
  identity).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ES2026 §21.3.2.16: Math.floor(-0𝔽) is -0𝔽, not +0𝔽. Goccia was passing
through to FreePascal's `Floor()` which collapses signed zeros.
Returns the argument directly when it is -0 or +0 so the
ceil(x) ≡ -floor(-x) identity (Math.floor/S15.8.2.6_A7) holds at the
zero boundary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both methods had two divergences from ES2026 §23.1.3.{29,34}:

1. Validation order: spec step 1 throws TypeError when comparefn is
   non-undefined and non-callable, BEFORE LengthOfArrayLike reads
   `this.length`. Goccia read length first, so a getter that throws on
   length was observed even when the comparator argument was already
   bad. test262 specifically covers this with a `getLengthThrow` object
   plus invalidComparators list (null/true/false/""/regex/number/
   bigint/[]/{}/Symbol).

2. Error type: ThrowError was throwing a generic Error rather than
   TypeError, so `assert.throws(TypeError, ...)` saw the wrong
   constructor. Switch to ThrowTypeError to match the spec's
   "throw a TypeError exception".

Also pull comparator extraction up so the undefined-comparator branch
falls through to the default ascending compare without re-checking
AArgs.Length downstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`parseFloat("0.1e-1")` returned `0.010000000000000002` instead of
`0.01`. Multiplying 0.1 by IntPower(10, -1) = 0.1 yields the imprecise
product (0.1 * 0.1 has FP error). Dividing by IntPower(10, 1) = 10
lands exactly on the closest IEEE 754 double to 0.01.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
source/units/Goccia.Builtins.GlobalNumber.pas (1)

190-325: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clamp exponent parsing to prevent Integer overflow.

On line 116-129, ExpValue accumulates unbounded into an Integer without saturation. Inputs with lengthy exponents like 1e2147483648 overflow before reaching the IntPower call. In PRODUCTION mode (overflow checks off), this wraps silently to an incorrect exponent; in DEBUG mode, it raises an exception—both violate ECMAScript parseFloat semantics, which should saturate to Infinity or 0.

Cap exponent accumulation to a safe threshold (≈324) that covers Double underflow/overflow boundaries:

    ExpValue := 0;
    HasExpDigits := False;
    while I <= Length(InputStr) do
    begin
      C := InputStr[I];
      if (C >= '0') and (C <= '9') then
      begin
-       ExpValue := ExpValue * 10 + (Ord(C) - Ord('0'));
        HasExpDigits := True;
+       if ExpValue < 324 then
+       begin
+         ExpValue := ExpValue * 10 + (Ord(C) - Ord('0'));
+         if ExpValue > 324 then
+           ExpValue := 324;
+       end;
        Inc(I);
      end

Then treat any clamped positive exponent as saturation when applying it to Mantissa.

source/units/Goccia.Values.NumberObjectValue.pas (1)

279-293: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Treat explicit undefined as the default radix.

Line 281 coerces the first argument before checking whether it is undefined, so num.toString(undefined) falls into the non-decimal path instead of the Step 2 default-to-10 path. Right now that returns '' for a valid call shape.

Suggested fix
-  if AArgs.Length > 0 then
+  if (AArgs.Length > 0) and not (AArgs.GetElement(0) is TGocciaUndefinedLiteralValue) then
   begin
     Radix := Trunc(AArgs.GetElement(0).ToNumberLiteral.Value);
     // Step 3: If radixMV < 2 or radixMV > 36, throw a RangeError exception
     if (Radix < 2) or (Radix > 36) then
     begin
       Result := TGocciaStringLiteralValue.Create('');
       Exit;
     end;
     // Step 2: If radix is undefined or 10, return ! ToString(x)
     if Radix = 10 then
     begin
       Result := Prim.ToStringLiteral;
       Exit;
     end;
🤖 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.Values.NumberObjectValue.pas` around lines 279 - 293, The
code currently coerces the first argument with
AArgs.GetElement(0).ToNumberLiteral before checking for undefined, causing
num.toString(undefined) to be handled incorrectly; change the logic in the
method that builds Radix so you first inspect AArgs.Length and then check if
AArgs.GetElement(0).IsUndefined (or equivalent) and, if so, treat it as the
default radix 10 and return Prim.ToStringLiteral; only after confirming the
argument is not undefined should you call ToNumberLiteral and assign Radix, then
apply the existing range check that returns TGocciaStringLiteralValue.Create('')
for out-of-range values.
🧹 Nitpick comments (2)
docs/language.md (1)

774-774: ⚡ Quick win

Document the literal-only RHS guard in the counted-loop fast path.

Line 774 currently describes the fast path shape, but it omits the literal-only RHS safety restriction mentioned in this PR’s behavior notes. Please add that non-literal bounds fall back to the general path.

🤖 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 `@docs/language.md` at line 774, Update the docs sentence describing the
counted-loop fast path (referencing CompileCountedForOf and the
BeginScope/EndScope with OP_CLOSE_UPVALUE behavior) to state that the RHS bound
must be a literal constant; if the loop's upper bound is not a literal the
compiler will reject the fast path and fall back to the general
outer-scope/per-iteration path. Keep references to the same symbols
(CompileCountedForOf, BeginScope, EndScope, OP_CLOSE_UPVALUE) so readers can
locate the implementation.
source/units/Goccia.Builtins.Math.pas (1)

334-335: 💤 Low value

Nit: Misleading variable name IntPart.

The variable stores the result of Power(B, E) which is a general floating-point result, not an integer part. Consider renaming to PowResult for clarity.

♻️ Suggested rename
-  IntPart: Double;
+  PowResult: Double;
   ExpIsOddInteger: Boolean;

And at line 440:

-  IntPart := Power(B, E);
-  Result := TGocciaNumberLiteralValue.Create(IntPart);
+  PowResult := Power(B, E);
+  Result := TGocciaNumberLiteralValue.Create(PowResult);
🤖 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.Builtins.Math.pas` around lines 334 - 335, The variable
IntPart incorrectly implies "integer part" even though it holds Power(B, E);
rename IntPart to PowResult across the unit to improve clarity: update the
declaration (IntPart: Double) to PowResult: Double and replace every reference
to IntPart (including the expression that assigns Power(B, E) and subsequent
uses) with PowResult; ensure any associated comments or tests referring to
IntPart are updated to the new symbol so compilation and semantics remain
unchanged (look for references in functions/procedures in Goccia.Builtins.Math
that compute Power(B, E)).
🤖 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/language.md`:
- Line 787: The sentence conflates break and continue by implying both apply to
switch; update the wording so it explicitly states that break is valid in switch
statements while continue applies only to iteration constructs (for...of, for
await...of, traditional for(;;) when enabled) or to labeled loops. Edit the line
referencing `break` and `continue` to split into two clauses: one that declares
`break` is available inside `switch` statements, and another that declares
`continue` is available only inside loop constructs (and when applicable,
labeled iteration targets), mentioning `for...of`, `for await...of`, and the
optional `for(;;)` flag by name.

In `@source/units/Goccia.Values.NumberObjectValue.pas`:
- Around line 225-258: The FractionToRadixString function is incorrectly capped
at 52 fractional digits causing loss of exactness for some binary64 values;
replace the fixed MAX_DIGITS cap with a computed limit based on binary64
mantissa bits (use 53 bits) and the target radix (e.g. maxDigits := Ceil(53 /
Log2(ARadix))) and then loop until V = 0 or the computed maxDigits is reached;
apply the same change to the analogous routine referenced around lines 309–313
so both fractional-conversion paths use the dynamic mantissa-based digit limit
instead of the hard-coded 52.
- Around line 204-223: IntegerToRadixString currently takes an Int64 and will
overflow for finite numeric whole parts > Int64.MaxValue; change it to accept an
unsigned 64-bit type (e.g., QWord/UInt64) by updating the signature
IntegerToRadixString(const AValue: QWord; const ARadix: Integer): string and the
local V to QWord, and adjust the caller site that now casts the whole part using
an unsigned conversion instead of Round(IntPart) (e.g., use
QWord(Trunc(IntPart)) or an appropriate unsigned cast) so very large finite
whole parts are handled correctly when formatting non-decimal radices.

---

Outside diff comments:
In `@source/units/Goccia.Values.NumberObjectValue.pas`:
- Around line 279-293: The code currently coerces the first argument with
AArgs.GetElement(0).ToNumberLiteral before checking for undefined, causing
num.toString(undefined) to be handled incorrectly; change the logic in the
method that builds Radix so you first inspect AArgs.Length and then check if
AArgs.GetElement(0).IsUndefined (or equivalent) and, if so, treat it as the
default radix 10 and return Prim.ToStringLiteral; only after confirming the
argument is not undefined should you call ToNumberLiteral and assign Radix, then
apply the existing range check that returns TGocciaStringLiteralValue.Create('')
for out-of-range values.

---

Nitpick comments:
In `@docs/language.md`:
- Line 774: Update the docs sentence describing the counted-loop fast path
(referencing CompileCountedForOf and the BeginScope/EndScope with
OP_CLOSE_UPVALUE behavior) to state that the RHS bound must be a literal
constant; if the loop's upper bound is not a literal the compiler will reject
the fast path and fall back to the general outer-scope/per-iteration path. Keep
references to the same symbols (CompileCountedForOf, BeginScope, EndScope,
OP_CLOSE_UPVALUE) so readers can locate the implementation.

In `@source/units/Goccia.Builtins.Math.pas`:
- Around line 334-335: The variable IntPart incorrectly implies "integer part"
even though it holds Power(B, E); rename IntPart to PowResult across the unit to
improve clarity: update the declaration (IntPart: Double) to PowResult: Double
and replace every reference to IntPart (including the expression that assigns
Power(B, E) and subsequent uses) with PowResult; ensure any associated comments
or tests referring to IntPart are updated to the new symbol so compilation and
semantics remain unchanged (look for references in functions/procedures in
Goccia.Builtins.Math that compute Power(B, E)).
🪄 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: cb2aa9a1-a176-4381-b722-2354624f87a1

📥 Commits

Reviewing files that changed from the base of the PR and between cd0f7bb and 68defa6.

📒 Files selected for processing (8)
  • docs/decision-log.md
  • docs/language.md
  • source/app/Goccia.CLI.Application.pas
  • source/app/GocciaScriptLoaderBare.dpr
  • source/units/Goccia.Builtins.GlobalNumber.pas
  • source/units/Goccia.Builtins.Math.pas
  • source/units/Goccia.Values.ArrayValue.pas
  • source/units/Goccia.Values.NumberObjectValue.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/app/Goccia.CLI.Application.pas

Comment thread docs/language.md Outdated
Comment thread source/units/Goccia.Values.NumberObjectValue.pas Outdated
Comment thread source/units/Goccia.Values.NumberObjectValue.pas
- TryCompileCountedFor now also rejects non-integer literal RHS so
  `for(let i=0; i<3.5; i++)` doesn't take the OP_GTE_INT/OP_LTE_INT
  fast path; falls through to the general path which preserves IEEE
  754 compare semantics. (#531 review)
- IntegerToRadixString takes a Double, so values above Int64.MaxValue
  (which still produce a finite Number) no longer overflow on
  Round() and stringify via repeated div/mod in floating-point. The
  trailing-bit alignment for values past 2^53 is implementation-defined
  per ES2026 §6.1.6.1.20.
- FractionToRadixString sizes the digit budget per radix as
  `Ceil(53 / Log2(R)) + 4` so binary64-precision values capture their
  mantissa in any base 2..36 (the previous fixed 52-digit cap was too
  short for radix 2 and overshot for radix 36). Full subnormal range
  remains future work — a proper Steele-White / Grisu implementation
  is tracked separately.
- docs/language.md: clarify `continue` only applies to iteration
  constructs, never `switch`.
- docs/decision-log.md: spell out that `--compat-traditional-for-loop`
  is bundled into `--compat-all` and reference the engine-gap issues
  surfaced (#540 / #541 / #542).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/language.md (1)

806-818: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update the labeled-statements rationale; it’s now outdated.

Line 817 says traditional loops are excluded and labels serve no purpose, but traditional for(;;) is now available behind --compat-traditional-for-loop. This sentence should be revised to avoid contradicting the new loop section.

🤖 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 `@docs/language.md` around lines 806 - 818, The docs' "Labeled Statements"
rationale is outdated: update the paragraph that says "Since GocciaScript
excludes traditional loops, labels serve no purpose." to acknowledge the new
--compat-traditional-for-loop option and explain that labels remain relevant
when that compatibility flag enables traditional for-loops; adjust the example
references (`myLabel: x = 2;`, `outer: for (...)`) and the warning wording
("Warning: Labeled statements are not supported in GocciaScript") so the text
clarifies that labels are stripped/unwarned only when traditional loops are
disabled, but may be meaningful when --compat-traditional-for-loop is enabled.
🤖 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/language.md`:
- Around line 10-12: Update the doc sentence about "Graceful handling" to
clarify that only excluded loop forms are treated as no-ops: specify that
`while` and `do...while` loops (and the traditional `for(init; test; update)`
loop only when `--compat-traditional-for-loop` is not enabled) parse
successfully but execute as a no-op with a warning/suggestion; retain the note
about parser-recognized excluded syntax (`==`, `with`) while scoping the "no-op"
claim to those excluded loop forms.

In `@source/units/Goccia.Values.NumberObjectValue.pas`:
- Around line 223-231: The loop uses Trunc(...) which returns an Int64 and
overflows for very large Double values (e.g. 1e100); replace Trunc(V / ARadix)
and Trunc(V - Q * ARadix) with Int(V / ARadix) and Int(V - Q * ARadix)
respectively so Q and D stay as Double-compatible values and no Int64
intermediate is produced; update any local declarations for Q/D if needed to be
Double (or the same type used by V) and keep the rest of the logic that uses
DIGITS and Result unchanged.
- Around line 321-322: The Trunc(V) call can overflow for values beyond Int64
range; update the code around IntPart := Trunc(V); FracPart := V - IntPart to
first check whether V is within Int64 bounds (compare against High(Int64) and
Low(Int64)) and only call Trunc when it is safe; if V is outside that range,
skip Trunc and route the large-integer path (produce the integer part by using
the big-integer/string conversion flow instead of Trunc—e.g. call the routine
that eventually uses IntegerToRadixString or otherwise format the whole-number
portion directly) and set FracPart accordingly, so Trunc is never invoked on
out-of-range V.

---

Outside diff comments:
In `@docs/language.md`:
- Around line 806-818: The docs' "Labeled Statements" rationale is outdated:
update the paragraph that says "Since GocciaScript excludes traditional loops,
labels serve no purpose." to acknowledge the new --compat-traditional-for-loop
option and explain that labels remain relevant when that compatibility flag
enables traditional for-loops; adjust the example references (`myLabel: x = 2;`,
`outer: for (...)`) and the warning wording ("Warning: Labeled statements are
not supported in GocciaScript") so the text clarifies that labels are
stripped/unwarned only when traditional loops are disabled, but may be
meaningful when --compat-traditional-for-loop is enabled.
🪄 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: cee0f6e5-d89b-41bc-9440-d3ea4767c5f9

📥 Commits

Reviewing files that changed from the base of the PR and between 68defa6 and 4c95d37.

📒 Files selected for processing (4)
  • docs/decision-log.md
  • docs/language.md
  • source/units/Goccia.Compiler.Statements.pas
  • source/units/Goccia.Values.NumberObjectValue.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/units/Goccia.Compiler.Statements.pas

Comment thread docs/language.md
Comment thread source/units/Goccia.Values.NumberObjectValue.pas
Comment thread source/units/Goccia.Values.NumberObjectValue.pas Outdated
- IntegerToRadixString and the call site at NumberToString now use
  Int() instead of Trunc(). Trunc() returns Int64 and tripped a
  range-check error for any double past 2^63 (e.g.
  (1e100).toString(16)). Int() returns the truncated value as Double
  so arbitrarily large finite numbers stringify; Round() narrows just
  the small modulo digit. (#531 review)
- docs/language.md: replace "loops" in the Graceful Handling bullet
  with the precise excluded list — `while`/`do...while` always, and
  traditional `for(;;)` only when --compat-traditional-for-loop is
  off. The previous wording implied all loops are no-op which now
  conflicts with opt-in traditional-for support.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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: 1

🤖 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/language.md`:
- Around line 789-792: Update the doc text under the "### `while` and
`do...while`" section so it no longer claims traditional loops are globally
excluded; instead state that traditional for(;;) loops are opt-in via the
--compat-traditional-for-loop flag (or that traditional for is optional). Locate
the sentence that currently asserts "GocciaScript excludes traditional loops"
and change it to describe current behavior: parser accepts those syntaxes but
traditional for requires the compat flag and while/do...while are treated as
no-op unless the appropriate compatibility option is enabled. Make sure to
reference the existing phrasing around "for(;;) (with
`--compat-traditional-for-loop`), `for...of`, or array methods" and update that
explanatory line accordingly.
🪄 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: 2d390e95-ac1e-4639-97a9-c42bf2f5e58b

📥 Commits

Reviewing files that changed from the base of the PR and between 4c95d37 and a92ef4a.

📒 Files selected for processing (2)
  • docs/language.md
  • source/units/Goccia.Values.NumberObjectValue.pas
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/units/Goccia.Values.NumberObjectValue.pas

Comment thread docs/language.md
frostney and others added 2 commits May 6, 2026 00:47
Two follow-on doc consistencies after opt-in traditional `for(;;)`:

- docs/language.md § Labeled Statements: "Since GocciaScript excludes
  traditional loops, labels serve no purpose" was outdated — traditional
  for(;;) is now available behind --compat-traditional-for-loop.
  Rewrite to call out which loop forms exist and that labeled
  break/continue isn't implemented for any of them.
- docs/language-tables.md while/do...while row: list traditional
  for(;;) (with --compat-traditional-for-loop) alongside for-of and
  array methods as the suggested alternatives, matching the prose in
  language.md § while and do...while.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`tests/language/for-loop/with-var/` was a nested subdir; the goccia.json
inside it correctly only applied to its own tests, so isolation was
empirically fine. But the layout made it look like compat-var was a
sub-concern of for-loop, when it's really an orthogonal flag combination.
Move to a sibling at `tests/language/for-loop-var/` so the directory tree
makes the concern split obvious:

  tests/language/for-loop/        — only --compat-traditional-for-loop
                                    (let/const tests, default features)
  tests/language/for-loop-var/    — --compat-traditional-for-loop +
                                    --compat-var (combination tests)

Each dir's goccia.json controls its own scope; running either, both,
or neither still gives the same per-test config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@frostney frostney merged commit 5e5bb61 into main May 6, 2026
13 of 14 checks passed
@frostney frostney deleted the t3code/0f65db35 branch May 6, 2026 07:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation internal Refactoring, CI, tooling, cleanup new feature New feature or request spec compliance Mismatch against official JavaScript/TypeScript specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant