Skip to content

fix(engine): audit object-reference threadvars and rebuild prototype members per realm#904

Merged
frostney merged 10 commits into
mainfrom
claude/relaxed-beaver-4e332c
Jun 29, 2026
Merged

fix(engine): audit object-reference threadvars and rebuild prototype members per realm#904
frostney merged 10 commits into
mainfrom
claude/relaxed-beaver-4e332c

Conversation

@frostney

@frostney frostney commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #885 / #891 (ADR 0078), which released managed threadvars at worker-thread exit but deliberately scoped out object-reference threadvars. This audits those (#892). FPC never auto-finalizes object-reference threadvars.

Object-reference container threadvars

  • Remove the dead, write-only GSymbolRegistry (Goccia.Values.SymbolValue): every TGocciaSymbolValue added itself and nothing ever read the map, so it grew unboundedly per thread for no purpose.
  • Route the genuinely-leaking containers — Goccia.ObjectModel.GPublishedGetterHosts (a TObjectList that owns its entries) and Goccia.Compiler.Statements' GUsingResources / GLabelControls working-state lists — through Goccia.ThreadCleanupRegistry with FreeAndNil-based cleanups, released on both the worker-exit and main-thread-finalization paths (the same mechanism fix(engine): release managed threadvars on worker-thread exit #891 established). The other compiler working-state lists are balanced (nil at rest) and documented as such.
  • Document the already-safe object-reference threadvars in place: the reference-counted GSharedSymbolRegistry (freed when the last TGocciaGlobalSymbol on the thread dies), the non-owning "current" pointers (CurrentRealm, active generator continuation, active bytecode generator, RegExp prototype cache, source-pipeline scope), and the explicitly-shut-down per-thread Instance singletons.

Manually-pinned prototype method hosts

  • Migrate 7 host-bearing value types — Object, Array, String, Number, Boolean, Symbol, Iterator — from a process-wide-pinned threadvar host (never unpinned) to a per-realm slot. The realm pins the host on SetSlot and unpins it on Destroy, so it is reclaimed with its realm just like the prototype object; member definitions are rebuilt per realm because they bind to the per-realm host. Their now-dead fix(engine): release managed threadvars on worker-thread exit #891 ClearThreadvarMembers registrations are dropped.
  • BigInt is deliberately excluded, and its redundant write-only host threadvar removed: its host is BigIntZero — the 0n process-wide singleton (a class var), the same fixed-value-singleton category as the primitive singletons and already pinned process-wide. Routing it through a realm slot made the realm unpin 0n on Destroy, freeing the shared singleton and breaking every engine created after the first on a thread (surfaced during development; see the ADR).

Constraints / non-goals: threadvar lifetime only — no JS-observable behavior or semantics change. The only behavioral delta is that the 7 host-bearing types rebuild their member definitions per realm instead of caching them once per thread (bounded to the units this issue names; the other ~57 cached units are untouched, a possible later migration). Decision recorded in ADR 0084.

Closes #892

Testing

  • Verified no regressions and confirmed the new feature or bugfix in end-to-end JavaScript/TypeScript tests — clean build, then GocciaTestRunner tests and --mode=bytecode both 11046/11046 (100%). (No JS-observable behavior; the regression was caught here because workers run many engines per thread.)
  • Updated documentation — added ADR 0084 + index entry, and per-threadvar "why safe" comments across the audited units.
  • Verified no regressions and confirmed the new feature or bugfix in native Pascal tests (value types changed) — Goccia.ThreadCleanupLeak.Test extended with an idempotency case proving the new FreeAndNil container cleanups survive a repeated registry drain (2/2); Goccia.Threading.Test (12/12); Goccia.Engine.Realm.Test (18/18); Goccia.GarbageCollector.Test (1/1). ./format.pas --check clean.
  • Optional: Verified no benchmark regressions or confirmed benchmark coverage for the change — N/A (lifetime-only change; no hot-path impact).

Follow-up: the other ~30 TGocciaSharedPrototype units

These units (Map, Set, the weak collections, Promise, ArrayBuffer/SharedArrayBuffer, all Intl*/Temporal*, Headers, Response, TextDecoder, the FFI values, FinalizationRegistry) kept a cross-realm FPrototypeMembers cache bound to a realm-owned (freeable) host — the combination ADR 0084 calls unsafe.

I first confirmed it was not observably exploitable: every bound callback re-derives its receiver from the JS this, none is virtual, so a later realm reusing the cache passes the earlier realm's freed host as Self but never dereferences it (verified by a sweep of all 30 units plus a multi-realm repro test). But it relied on that implicit invariant, with two callbacks (ZonedDateTimeSubtract/YearMonthSubtract) one refactor away from a real UAF.

So I applied the deep fix: extend #892's per-realm member rebuild to all ~30 units — drop the cross-realm FPrototypeMembers cache, rebuild member definitions per realm bound to the current realm's host, and drop their #891 ClearThreadvarMembers. This removes the hazard class entirely (no reliance on the invariant) and makes the two Subtract callbacks safe for free. The investigation + migration are folded into ADR 0084 (renumbered from 0083 on baseline merge — main landed its own ADR 0083), and Goccia.SharedPrototypeRealmReuse.Test (many realms on one thread + forced GC collect + heap stomp) guards against a regression. Both JS modes stay 11046/11046, and the per-realm rebuild's allocation cost is negligible and allocator-mitigated (benchmarked in ADR 0084).

Code review

A high-effort review of the branch caught one regression the deep fix introduced: SharedArrayBuffer.prototype.byteLength used AddPublishedGetter, which appends to the never-pruned thread-global GPublishedGetterHosts list. Harmless while the prototype was built once per thread, but once members are rebuilt per realm it leaks one getter host per realm. Switched it to AddAccessor over the already-present (previously unused) SharedArrayBufferByteLengthGetter callback — matching ArrayBuffer and SharedArrayBuffer's own sibling getters; byteLength behavior is unchanged (SharedArrayBuffer/ArrayBuffer built-ins 218/218). The review also fixed two stale comments that still described the removed cross-realm cache, corrected an ADR claim (the two Temporal Subtract callbacks are made safe, not removed), and flagged a pre-existing instance of the same cross-realm-cache pattern in Goccia.Builtins.GlobalRegExp (a constructor-style builtin, different mechanism) — audited and fixed below.

RegExp builtin — the one genuinely exploitable instance

Auditing the flagged Goccia.Builtins.GlobalRegExp showed its host (TGocciaGlobalRegExp) is realm-owned — TGocciaEngine frees it on Destroy — and it cached FPrototypeMembers/FStaticMembers cross-realm, so later realms bound to the first realm's freed host. Crucially, unlike the ~30 TGocciaSharedPrototype units (whose callbacks never touch the host), two RegExp prototype callbacks dereference it: RegExp.prototype[Symbol.matchAll] and [Symbol.split] read the host's FRegExpConstructor and use it on the SpeciesConstructor fallback (receiver constructor = undefined). That makes it the one genuinely exploitable instance of the pattern — a multi-realm repro crashes with an access violation.

Fixed the same way: drop both caches, rebuild per realm bound to the current host, drop the ThreadCleanupRegistry wiring. Goccia.SharedPrototypeRealmReuse.Test gains RegExp cases + an FPC-heap stomp (the host is freed to the FPC heap, not the GC heap) + species-fallback exploits for both host-dereferencing callbacks ([Symbol.matchAll] and [Symbol.split]) — it faults before the fix and passes after. ADR 0084 records the finding. (A second code-review pass then hardened this gate — split coverage + documented stomp magnitudes.)

🤖 Generated with Claude Code

Follow-up to #885/#891 (managed threadvars), auditing object-reference
threadvars per #892. FPC never auto-finalizes object-reference threadvars.

- Remove the dead, write-only GSymbolRegistry threadvar (every symbol added
  itself, nothing read it, so it grew unboundedly per thread).
- Route the leaking object-reference container threadvars
  (ObjectModel.GPublishedGetterHosts, Compiler.Statements GUsingResources /
  GLabelControls) through Goccia.ThreadCleanupRegistry; document the
  already-safe ones (refcounted symbol registry, non-owning current pointers).
- Migrate 7 prototype method-host threadvars (Object/Array/String/Number/
  Boolean/Symbol/Iterator) to per-realm slots so the realm pins the host on
  SetSlot and unpins it on Destroy; rebuild member definitions per realm.
- Drop BigInt's redundant write-only host threadvar (its host is the 0n
  process singleton, already pinned process-wide, deliberately not migrated).
- Extend Goccia.ThreadCleanupLeak.Test with an idempotency case; add ADR 0083.

Closes #892

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 28, 2026

Copy link
Copy Markdown

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 Jun 29, 2026 7:43pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 333f938e-c68f-409f-b140-df58dbf440e7

📥 Commits

Reviewing files that changed from the base of the PR and between 0945b95 and c016464.

📒 Files selected for processing (5)
  • docs/adr/0084-realm-owned-prototype-method-hosts.md
  • docs/adr/README.md
  • source/units/Goccia.SharedPrototype.pas
  • source/units/Goccia.SharedPrototypeRealmReuse.Test.pas
  • source/units/Goccia.ThreadCleanupLeak.Test.pas
✅ Files skipped from review due to trivial changes (3)
  • docs/adr/README.md
  • docs/adr/0084-realm-owned-prototype-method-hosts.md
  • source/units/Goccia.SharedPrototype.pas
🚧 Files skipped from review as they are similar to previous changes (2)
  • source/units/Goccia.ThreadCleanupLeak.Test.pas
  • source/units/Goccia.SharedPrototypeRealmReuse.Test.pas

📝 Walkthrough

Walkthrough

Across ~30 built-in value units, per-thread threadvar-cached prototype member definitions and process-pinned method hosts are replaced with per-realm slots (RegisterRealmOwnedSlot/RegisterRealmSlot) that are freed when the realm is destroyed. GlobalRegExp is fixed to always rebuild member collections per-constructor call. Two container threadvar leaks (compiler working state, published getter hosts) are plugged via RegisterThreadvarCleanup. A new regression test program validates multi-realm reuse with heap poisoning, an idempotency test is added to the existing leak suite, and ADR 0084 documents the audit.

Changes

Realm-owned prototype method hosts and threadvar audit

Layer / File(s) Summary
ADR 0084 and SharedPrototype docs
docs/adr/0084-realm-owned-prototype-method-hosts.md, docs/adr/README.md, source/units/Goccia.SharedPrototype.pas
New ADR document records the audit scope, GlobalRegExp crash, BigInt exclusion, and verification; ADR index updated; TGocciaSharedPrototype gains inline comments on realm ownership and per-realm member rebuild.
Non-owning threadvar documentation
source/units/Goccia.Realm.pas, source/units/Goccia.RegExp.Runtime.pas, source/units/Goccia.Generator.Continuation.pas, source/units/Goccia.SourcePipeline.pas, source/units/Goccia.VM.pas
Explanatory comments inserted on non-owning GC-managed threadvar pointers to document save/restore semantics and audit reference #892; no runtime logic changed.
Container threadvar leak fixes
source/units/Goccia.Compiler.Statements.pas, source/units/Goccia.ObjectModel.pas
ClearCompilerWorkingState frees GUsingResources/GLabelControls; ClearPublishedGetterHosts frees GPublishedGetterHosts; both registered via RegisterThreadvarCleanup to prevent one container per thread leaking on worker exit.
GlobalRegExp: remove threadvar caching, fix stale-host crash
source/units/Goccia.Builtins.GlobalRegExp.pas
FPrototypeMembers/FStaticMembers threadvars and ClearThreadvarMembers removed; TGocciaGlobalRegExp.Create always builds fresh TGocciaMemberCollection instances, eliminating stale cross-realm host dereferences.
Per-realm method-host slots for host-backed prototypes
source/units/Goccia.Values.ArrayValue.pas, source/units/Goccia.Values.BooleanObjectValue.pas, source/units/Goccia.Values.NumberObjectValue.pas, source/units/Goccia.Values.ObjectValue.pas, source/units/Goccia.Values.StringObjectValue.pas, source/units/Goccia.Values.SymbolValue.pas, source/units/Goccia.Values.IteratorValue.pas
New GXxxMethodHostSlot realm slots introduced; InitializePrototype stores Self into the realm slot and rebuilds member definitions locally per realm; GC pinning and threadvar method-host caching removed; CreateGlobalObject for Iterator likewise bound to realm method host.
BigInt: partial method-host migration
source/units/Goccia.Values.BigIntValue.pas
FMethodHost threadvar removed; FPrototypeMembers threadvar retained; InitializePrototype defensively re-pins Self on first per-thread member-definition setup instead of storing a process-lifetime host.
TGocciaSharedPrototype-backed units: local PrototypeMembers rebuild
source/units/Goccia.Values.ArrayBufferValue.pas, source/units/Goccia.Values.SharedArrayBufferValue.pas, source/units/Goccia.Values.MapValue.pas, source/units/Goccia.Values.SetValue.pas, source/units/Goccia.Values.WeakMapValue.pas, source/units/Goccia.Values.WeakSetValue.pas, source/units/Goccia.Values.WeakRefValue.pas, source/units/Goccia.Values.FinalizationRegistryValue.pas, source/units/Goccia.Values.PromiseValue.pas, source/units/Goccia.Values.ResponseValue.pas, source/units/Goccia.Values.HeadersValue.pas, source/units/Goccia.Values.TextDecoderValue.pas, source/units/Goccia.Values.FFILibrary.pas, source/units/Goccia.Values.FFIPointer.pas, source/units/Goccia.Values.Intl*.pas, source/units/Goccia.Values.Temporal*.pas
FPrototypeMembers threadvars and ClearThreadvarMembers/RegisterThreadvarCleanup removed from all remaining shared-prototype-backed units; InitializePrototype builds a local PrototypeMembers array on first realm initialization and registers it directly.
Regression tests: realm reuse and idempotent drain
source/units/Goccia.SharedPrototypeRealmReuse.Test.pas, source/units/Goccia.ThreadCleanupLeak.Test.pas
New TRealmReuseTests program runs self-verifying JS across multiple realm cycles with FPC heap stomping and GC churn to detect stale host dereferences; TestRegistryDrainIsIdempotent added to TLeakTests to assert repeated RunThreadvarCleanups calls do not grow heap.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • frostney/GocciaScript#891: Directly overlaps on Goccia.Builtins.GlobalRegExp.pas—that PR added RegisterThreadvarCleanup for the threadvar member caches that this PR removes entirely.
  • frostney/GocciaScript#58: Both modify Goccia.Values.IteratorValue.pas; this PR rewires iterator method hosting to per-realm slots, building on the iterator infrastructure touched by that PR.
  • frostney/GocciaScript#277: Both modify Temporal value unit prototype/member-initialization code in the same files (TemporalPlainMonthDay, TemporalPlainDateTime, TemporalZonedDateTime, etc.).

Suggested labels

bug, documentation, internal

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: auditing object-reference threadvars and moving prototype member rebuilds to per-realm handling.
Description check ✅ Passed The description matches the template with Summary and Testing sections, includes constraints, issue link, and substantial test coverage.
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.

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

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Suite Timing

Test Runner (interpreted: 11,046 passed; bytecode: 11,046 passed)
Metric Interpreted Bytecode
Total 11046 11046
Passed 11046 ✅ 11046 ✅
Workers 4 4
Test Duration 11.42s 17.83s
Lex (cumulative) 244.1ms 481.1ms
Parse (cumulative) 187.9ms 375.4ms
Compile (cumulative) 786.2ms
Execute (cumulative) 33.8ms 41.88s
Engine Total (cumulative) 465.8ms 43.52s
Lex (avg/worker) 61.0ms 120.3ms
Parse (avg/worker) 47.0ms 93.8ms
Compile (avg/worker) 196.5ms
Execute (avg/worker) 8.4ms 10.47s
Engine Total (avg/worker) 116.4ms 10.88s

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 284.48 MiB 281.89 MiB
GC Peak Live 344.28 MiB 318.03 MiB
GC Allocated During Run 531.91 MiB 475.89 MiB
GC Limit 7.81 GiB 7.81 GiB
GC Collections 195 195
GC Collected Objects 2,609,034 2,176,141
Heap Start Allocated 182.6 KiB 182.6 KiB
Heap End Allocated 3.61 MiB 3.61 MiB
Heap Delta Allocated 3.44 MiB 3.44 MiB
Heap Delta Free 1.72 MiB 1.72 MiB
Benchmarks (interpreted: 437; bytecode: 437)
Metric Interpreted Bytecode
Total 437 437
Workers 4 4
Duration 3.01min 2.71min

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 6.29 MiB 6.29 MiB
GC Peak Live 104.58 MiB 93.82 MiB
GC Allocated During Run 16.48 GiB 9.68 GiB
GC Limit 7.81 GiB 7.81 GiB
GC Collections 3,799 3,432
GC Collected Objects 256,672,505 217,873,102
Heap Start Allocated 3.32 MiB 3.32 MiB
Heap End Allocated 3.32 MiB 3.32 MiB
Heap Delta Allocated 128 B 128 B

Measured on ubuntu-latest x64.

@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

437 benchmarks · PR vs same-runner main build

Interpreted: 🟢 27 improved · 🔴 38 regressed · 372 unchanged · avg -0.2%
Bytecode: 🟢 30 improved · 🔴 60 regressed · 347 unchanged · avg -0.8%

Typical per-run noise (median variance): interpreted ±3.6%, bytecode ±2.1%. Deltas within noise overlap and read as unchanged.

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

Deterministic profile diff

Deterministic profile diff: no significant changes.

Measured on ubuntu-latest x64. Each PR run also builds the main base and benchmarks it back-to-back on the same runner after a warm-up discard, so the ranges compare two runs measured under the same conditions; overlapping min/max ranges are treated as unchanged noise. Percentage deltas are secondary context. See docs/adr/0076.

@github-actions

github-actions Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

test262 Conformance

Category Run Passed Δ Pass Failed Pass-rate Δ Rate
built-ins 23,642 22,618 +1 1,024 95.7% ±0pp
harness 116 116 ±0 0 100.0% ±0pp
intl402 3,341 3,334 ±0 7 99.8% ±0pp
language 23,711 23,711 ±0 0 100.0% ±0pp
staging 1,482 1,111 ±0 371 75.0% ±0pp
total 52,292 50,890 +1 1,402 97.3% ±0pp

Areas closest to 100%

Area Pass rate Δ vs main Passing
built-ins/TypedArray 99.9% ±0pp 1,445 / 1,446
intl402/Temporal 99.9% ±0pp 2,026 / 2,029
built-ins/Object 99.7% ±0pp 3,401 / 3,411
Per-test deltas (+1 / -0)

Newly passing (1):

  • built-ins/Number/prototype/toExponential/undefined-fractiondigits.js

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.

frostney and others added 4 commits June 29, 2026 09:01
…safety

Follow-up to #892 / ADR 0083. The ~30 value units that build their prototype
via TGocciaSharedPrototype.Create(Self) keep a cross-realm FPrototypeMembers
cache bound to a realm-owned (freeable) host — the combination ADR 0083 calls
unsafe. Investigated whether it is an exploitable use-after-free.

It is not: every bound prototype callback re-derives its receiver from the JS
`this` (AThisValue) and never dereferences the bound host, and none are virtual,
so a later realm reusing the cache passes the earlier realm's freed host as Self
but never reads it. A sweep of all 30 units confirmed this (the only two
deviations, ZonedDateTimeSubtract / YearMonthSubtract, call a non-virtual
sibling that also re-binds AThisValue, so the freed host is still never read).

- Add Goccia.SharedPrototypeRealmReuse.Test: runs many realms on one thread with
  a forced GC collect + heap stomp between them and exercises the shared-prototype
  types in later realms; a callback that began dereferencing the freed host would
  crash or corrupt there.
- Document the invariant at the binding site (Goccia.SharedPrototype) and record
  the investigation + the discrepancy with ADR 0083's rationale in ADR 0084.

No production behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…otype units

Deep fix for the cross-realm member-cache hazard investigated as a #892
follow-up (ADR 0083). The ~30 value units that build their prototype via
TGocciaSharedPrototype.Create(Self) had a realm-owned (freeable) host but cached
their member definitions in a cross-realm FPrototypeMembers threadvar, so a later
realm reused callbacks bound to an earlier realm's freed host. Not observably
exploitable (callbacks take their receiver from the JS `this`, never the host),
but it relied on that implicit invariant.

Apply #892's per-realm rebuild to all ~30 units: drop the cross-realm
FPrototypeMembers cache, rebuild member definitions per realm bound to the
current realm's host, and drop their #891 ClearThreadvarMembers. This removes the
hazard class outright and incidentally makes the only two host-referencing
callbacks (ZonedDateTimeSubtract / YearMonthSubtract) safe.

- Fold the follow-up investigation + this migration into ADR 0083; delete the
  separate ADR 0084 (one ADR per workstream; ADRs lock at merge, not commit).
- Update the TGocciaSharedPrototype invariant comment and the regression-test
  header to reflect per-realm rebuild.

Full JS suite 11046/11046 both modes; SharedPrototypeRealmReuse / ThreadCleanupLeak
/ Threading Pascal tests green; format clean. No production behavior change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the engine-construction A/B result for the thirty-unit per-realm member
rebuild: no measurable wall-clock change and a deterministic +0.2-0.3% bytes
allocated per engine (allocator-mitigated), with no per-engine retention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…teLength

Code-review follow-up on the per-realm prototype rebuild (PR #904). With the
cross-realm member cache removed, SharedArrayBuffer's InitializePrototype now
runs per realm; its byteLength getter used AddPublishedGetter, which appends to
the thread-global, never-pruned GPublishedGetterHosts list — so it leaked one
getter host per realm. Switch byteLength to
AddAccessor(SharedArrayBufferByteLengthGetter) (the callback already existed,
unused), matching ArrayBuffer and its sibling getters: no GPublishedGetterHosts
growth, behavior unchanged.

Also from the review:
- Fix stale comments that still described the removed cross-realm cache
  (Goccia.Values.MapValue header; the SharedPrototypeRealmReuse test body).
- Correct ADR 0083: the two Temporal *.Subtract callbacks are made safe (their
  Self is now the current realm's live host), not removed; and record
  GlobalRegExp as a tracked, out-of-scope follow-up with the same pattern.

JS suite 11046/11046 both modes; SharedArrayBuffer/ArrayBuffer 218/218; format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@frostney frostney changed the title fix(engine): release object-reference threadvars on worker-thread exit fix(engine): audit object-reference threadvars and rebuild prototype members per realm Jun 29, 2026
frostney and others added 2 commits June 29, 2026 13:01
…UAF)

Goccia.Builtins.GlobalRegExp cached its prototype and static member definitions
in cross-realm threadvars (FPrototypeMembers/FStaticMembers, build-once guard),
so every later realm's RegExp prototype bound to the FIRST realm's
TGocciaGlobalRegExp host. That host is realm-owned — TGocciaEngine frees it on
Destroy — so a second realm on the same thread dispatched cached callbacks
against a freed host.

Unlike the ~30 TGocciaSharedPrototype units migrated in this PR (whose callbacks
never dereference the host), two RegExp prototype callbacks do:
RegExp.prototype[Symbol.matchAll] and [Symbol.split] read the host's
FRegExpConstructor and dereference it on the SpeciesConstructor fallback path
(receiver constructor = undefined). That makes GlobalRegExp the one genuinely
exploitable instance of the pattern — a multi-realm repro crashes with an
access violation.

Drop both caches and rebuild the member definitions per realm bound to the
current host (same shape as the TGocciaSharedPrototype migration), and drop the
unit's ThreadCleanupRegistry wiring. Goccia.SharedPrototypeRealmReuse.Test gains
RegExp cases, an FPC-heap stomp (the host is freed to the FPC heap, not the GC
heap), and the species-fallback exploit: it faults before this change and passes
after. ADR 0083 records the finding.

Pre-existing; surfaced by the PR #904 code review. JS suite 11046/11046 both
modes; SharedPrototypeRealmReuse/ThreadCleanupLeak/Threading green; format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code-review follow-ups on the GlobalRegExp per-realm rebuild test:
- Add a RegExp.prototype[Symbol.split] species-fallback case so BOTH
  host-dereferencing callbacks (matchAll and split) are gated; the comments
  claimed both but only matchAll was actually exercised on the fallback path.
- Document the stomp magnitudes (cycles / per-cycle FPC allocations / GC churn)
  as empirical generous floors, not tuned thresholds.
- Correct "caught below" -> caught by the shared test runner (the catch is in
  the test runner, not this file).

Test compiles and passes (3/3 deterministic); format clean. No production code
change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@frostney frostney marked this pull request as ready for review June 29, 2026 16:25
@coderabbitai coderabbitai Bot added bug Something isn't working internal Refactoring, CI, tooling, cleanup labels Jun 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
source/units/Goccia.SharedPrototypeRealmReuse.Test.pas (1)

62-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add direct SharedArrayBuffer byteLength coverage.

This regression gate covers ArrayBuffer.byteLength, but the PR also fixes a SharedArrayBuffer.prototype.byteLength getter-host leak. Add a small SharedArrayBuffer assertion here so that exact fixed path is locked down.

Suggested test addition
     'const ab = new ArrayBuffer(8); if (ab.byteLength !== 8) throw new Error("ArrayBuffer");' + sLineBreak +
     'const ab2 = ab.slice(0, 4); if (ab2.byteLength !== 4) throw new Error("ArrayBuffer.slice");' + sLineBreak +
+    'const sab = new SharedArrayBuffer(8); if (sab.byteLength !== 8) throw new Error("SharedArrayBuffer");' + sLineBreak +
🤖 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.SharedPrototypeRealmReuse.Test.pas` around lines 62 - 63,
Add a direct SharedArrayBuffer byteLength assertion in the existing test string
so the fix is covered on the exact getter path. Update the JavaScript snippet
built in the test around the ArrayBuffer checks to also create a
SharedArrayBuffer and verify its byteLength is correct, using the same style as
the current ArrayBuffer assertions in Goccia.SharedPrototypeRealmReuse.Test.
This should live alongside the existing byteLength coverage so the
SharedArrayBuffer.prototype.byteLength host-leak regression is locked down.
source/units/Goccia.Values.TemporalZonedDateTime.pas (1)

2039-2066: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the shared Temporal property-name constants in the rebuilt member list.

These changed lines reintroduce hardcoded Temporal property names that already have constants (PROP_CALENDAR_ID, PROP_YEAR, PROP_MONTH, PROP_MONTH_CODE, PROP_DAY, and time-unit constants). Prefer the shared constants here.

♻️ Proposed direction
-    Members.AddAccessor('calendarId', GetCalendarId, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_CALENDAR_ID, GetCalendarId, nil, [pfConfigurable]);
...
-    Members.AddAccessor('year', GetYear, nil, [pfConfigurable]);
-    Members.AddAccessor('month', GetMonth, nil, [pfConfigurable]);
-    Members.AddAccessor('monthCode', GetMonthCode, nil, [pfConfigurable]);
-    Members.AddAccessor('day', GetDay, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_YEAR, GetYear, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_MONTH, GetMonth, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_MONTH_CODE, GetMonthCode, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_DAY, GetDay, nil, [pfConfigurable]);
...
-    Members.AddAccessor('hour', GetHour, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_HOUR, GetHour, nil, [pfConfigurable]);

Based on learnings, Temporal-related units should use the shared Temporal property-name constants from Goccia.Constants.PropertyNames instead of hardcoded Temporal property names.

🤖 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.TemporalZonedDateTime.pas` around lines 2039 -
2066, The rebuilt member list in the TemporalZonedDateTime setup is using
hardcoded Temporal property names again; switch these AddAccessor calls to the
shared constants from Goccia.Constants.PropertyNames. Update the relevant member
registrations in the TemporalZonedDateTime member-building routine so symbols
like GetCalendarId, GetYear, GetMonth, GetMonthCode, GetDay, and the time-unit
accessors all reference the existing PROP_* constants instead of string
literals.

Source: Learnings

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

290-295: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Temporal property-name constants here.

calendarId, year, month, and monthCode should use the shared constants to match the rest of the Temporal units and avoid drift.

Proposed cleanup
-    Members.AddAccessor('calendarId', GetCalendarId, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_CALENDAR_ID, GetCalendarId, nil, [pfConfigurable]);
     Members.AddAccessor('era', GetEra, nil, [pfConfigurable]);
     Members.AddAccessor('eraYear', GetEraYear, nil, [pfConfigurable]);
-    Members.AddAccessor('year', GetYear, nil, [pfConfigurable]);
-    Members.AddAccessor('month', GetMonth, nil, [pfConfigurable]);
-    Members.AddAccessor('monthCode', GetMonthCode, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_YEAR, GetYear, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_MONTH, GetMonth, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_MONTH_CODE, GetMonthCode, nil, [pfConfigurable]);

Based on learnings, Temporal-related units should use shared property-name constants instead of hardcoded Temporal property names.

🤖 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.TemporalPlainYearMonth.pas` around lines 290 -
295, The accessor registrations in the TemporalPlainYearMonth setup are using
hardcoded property names, which should be replaced with the shared Temporal
property-name constants to stay consistent with the other Temporal units. Update
the Members.AddAccessor calls for calendarId, year, month, and monthCode in the
TemporalPlainYearMonth registration code to use the existing constants instead
of string literals, matching the pattern used elsewhere in the Temporal value
classes.

Source: Learnings

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

271-276: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the shared time-unit property constants here.

The rebuilt prototype member list still hardcodes 'hour', 'minute', 'second', 'millisecond', 'microsecond', and 'nanosecond'. Use PROP_HOUR, PROP_MINUTE, etc., to keep Temporal property names centralized. Based on learnings, Temporal units should use shared property-name constants from Goccia.Constants.PropertyNames instead of hardcoded Temporal property strings. <retrieved_learnings>

♻️ Suggested direction
-    Members.AddAccessor('hour', GetHour, nil, [pfConfigurable]);
-    Members.AddAccessor('minute', GetMinute, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_HOUR, GetHour, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_MINUTE, GetMinute, nil, [pfConfigurable]);
🤖 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.TemporalPlainTime.pas` around lines 271 - 276, The
rebuilt prototype member list in TemporalPlainTime still hardcodes the time-unit
accessor names, so update the member registration calls in the AddAccessor block
to use the shared constants from Goccia.Constants.PropertyNames instead of
literal strings. Replace the inline property names with PROP_HOUR, PROP_MINUTE,
PROP_SECOND, PROP_MILLISECOND, PROP_MICROSECOND, and PROP_NANOSECOND so the
Temporal property names stay centralized and consistent.

Source: Learnings

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

612-633: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use Temporal property-name constants in the rebuilt member list.

The changed registration block still hardcodes Temporal names like 'calendarId', 'year', 'month', and the time-unit properties. Prefer the shared PROP_* constants where available so the per-realm rebuild stays aligned with the rest of Temporal. Based on learnings, Temporal units should use shared property-name constants from Goccia.Constants.PropertyNames instead of hardcoded Temporal property strings. <retrieved_learnings>

♻️ Example replacement pattern
-    Members.AddAccessor('calendarId', GetCalendarId, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_CALENDAR_ID, GetCalendarId, nil, [pfConfigurable]);
...
-    Members.AddAccessor('hour', GetHour, nil, [pfConfigurable]);
+    Members.AddAccessor(PROP_HOUR, GetHour, nil, [pfConfigurable]);
🤖 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.TemporalPlainDateTime.pas` around lines 612 - 633,
The rebuilt member registration in TemporalPlainDateTime still hardcodes
Temporal property names instead of using the shared property-name constants.
Update the accessor setup in the member list to reference the matching PROP_*
symbols from Goccia.Constants.PropertyNames wherever they exist, keeping the
registrations in sync with the rest of Temporal and avoiding duplicated string
literals in the TemporalPlainDateTime rebuild path.

Source: Learnings

🤖 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.ThreadCleanupLeak.Test.pas`:
- Around line 103-108: The idempotency assertion in the thread cleanup test is
too strict because GetHeapStatus.TotalAllocated can fluctuate between collector
and heap-manager calls. Update the check around RunThreadvarCleanups and
TGarbageCollector.Instance.Collect in Goccia.ThreadCleanupLeak.Test to allow a
small bounded delta instead of relying on exact non-increase, while still
proving the second drain does not crash and does not cause meaningful growth.

---

Nitpick comments:
In `@source/units/Goccia.SharedPrototypeRealmReuse.Test.pas`:
- Around line 62-63: Add a direct SharedArrayBuffer byteLength assertion in the
existing test string so the fix is covered on the exact getter path. Update the
JavaScript snippet built in the test around the ArrayBuffer checks to also
create a SharedArrayBuffer and verify its byteLength is correct, using the same
style as the current ArrayBuffer assertions in
Goccia.SharedPrototypeRealmReuse.Test. This should live alongside the existing
byteLength coverage so the SharedArrayBuffer.prototype.byteLength host-leak
regression is locked down.

In `@source/units/Goccia.Values.TemporalPlainDateTime.pas`:
- Around line 612-633: The rebuilt member registration in TemporalPlainDateTime
still hardcodes Temporal property names instead of using the shared
property-name constants. Update the accessor setup in the member list to
reference the matching PROP_* symbols from Goccia.Constants.PropertyNames
wherever they exist, keeping the registrations in sync with the rest of Temporal
and avoiding duplicated string literals in the TemporalPlainDateTime rebuild
path.

In `@source/units/Goccia.Values.TemporalPlainTime.pas`:
- Around line 271-276: The rebuilt prototype member list in TemporalPlainTime
still hardcodes the time-unit accessor names, so update the member registration
calls in the AddAccessor block to use the shared constants from
Goccia.Constants.PropertyNames instead of literal strings. Replace the inline
property names with PROP_HOUR, PROP_MINUTE, PROP_SECOND, PROP_MILLISECOND,
PROP_MICROSECOND, and PROP_NANOSECOND so the Temporal property names stay
centralized and consistent.

In `@source/units/Goccia.Values.TemporalPlainYearMonth.pas`:
- Around line 290-295: The accessor registrations in the TemporalPlainYearMonth
setup are using hardcoded property names, which should be replaced with the
shared Temporal property-name constants to stay consistent with the other
Temporal units. Update the Members.AddAccessor calls for calendarId, year,
month, and monthCode in the TemporalPlainYearMonth registration code to use the
existing constants instead of string literals, matching the pattern used
elsewhere in the Temporal value classes.

In `@source/units/Goccia.Values.TemporalZonedDateTime.pas`:
- Around line 2039-2066: The rebuilt member list in the TemporalZonedDateTime
setup is using hardcoded Temporal property names again; switch these AddAccessor
calls to the shared constants from Goccia.Constants.PropertyNames. Update the
relevant member registrations in the TemporalZonedDateTime member-building
routine so symbols like GetCalendarId, GetYear, GetMonth, GetMonthCode, GetDay,
and the time-unit accessors all reference the existing PROP_* constants instead
of string literals.
🪄 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 Plus

Run ID: ef6ca06c-f17c-43b9-9a26-7cf83af8b351

📥 Commits

Reviewing files that changed from the base of the PR and between b42672c and 337dac7.

📒 Files selected for processing (51)
  • docs/adr/0083-realm-owned-prototype-method-hosts.md
  • docs/adr/README.md
  • source/units/Goccia.Builtins.GlobalRegExp.pas
  • source/units/Goccia.Compiler.Statements.pas
  • source/units/Goccia.Generator.Continuation.pas
  • source/units/Goccia.ObjectModel.pas
  • source/units/Goccia.Realm.pas
  • source/units/Goccia.RegExp.Runtime.pas
  • source/units/Goccia.SharedPrototype.pas
  • source/units/Goccia.SharedPrototypeRealmReuse.Test.pas
  • source/units/Goccia.SourcePipeline.pas
  • source/units/Goccia.ThreadCleanupLeak.Test.pas
  • source/units/Goccia.VM.pas
  • source/units/Goccia.Values.ArrayBufferValue.pas
  • source/units/Goccia.Values.ArrayValue.pas
  • source/units/Goccia.Values.BigIntValue.pas
  • source/units/Goccia.Values.BooleanObjectValue.pas
  • source/units/Goccia.Values.FFILibrary.pas
  • source/units/Goccia.Values.FFIPointer.pas
  • source/units/Goccia.Values.FinalizationRegistryValue.pas
  • source/units/Goccia.Values.HeadersValue.pas
  • source/units/Goccia.Values.IntlCollator.pas
  • source/units/Goccia.Values.IntlDateTimeFormat.pas
  • source/units/Goccia.Values.IntlDisplayNames.pas
  • source/units/Goccia.Values.IntlDurationFormat.pas
  • source/units/Goccia.Values.IntlListFormat.pas
  • source/units/Goccia.Values.IntlLocale.pas
  • source/units/Goccia.Values.IntlNumberFormat.pas
  • source/units/Goccia.Values.IntlPluralRules.pas
  • source/units/Goccia.Values.IntlRelativeTimeFormat.pas
  • source/units/Goccia.Values.IteratorValue.pas
  • source/units/Goccia.Values.MapValue.pas
  • source/units/Goccia.Values.NumberObjectValue.pas
  • source/units/Goccia.Values.ObjectValue.pas
  • source/units/Goccia.Values.PromiseValue.pas
  • source/units/Goccia.Values.ResponseValue.pas
  • source/units/Goccia.Values.SetValue.pas
  • source/units/Goccia.Values.SharedArrayBufferValue.pas
  • source/units/Goccia.Values.StringObjectValue.pas
  • source/units/Goccia.Values.SymbolValue.pas
  • source/units/Goccia.Values.TemporalDuration.pas
  • source/units/Goccia.Values.TemporalInstant.pas
  • source/units/Goccia.Values.TemporalPlainDateTime.pas
  • source/units/Goccia.Values.TemporalPlainMonthDay.pas
  • source/units/Goccia.Values.TemporalPlainTime.pas
  • source/units/Goccia.Values.TemporalPlainYearMonth.pas
  • source/units/Goccia.Values.TemporalZonedDateTime.pas
  • source/units/Goccia.Values.TextDecoderValue.pas
  • source/units/Goccia.Values.WeakMapValue.pas
  • source/units/Goccia.Values.WeakRefValue.pas
  • source/units/Goccia.Values.WeakSetValue.pas

Comment thread source/units/Goccia.ThreadCleanupLeak.Test.pas Outdated
Address a CodeRabbit review on PR #904: the second-drain idempotency assertion
used exact heap-total non-increase (AfterSecondDrain <= AfterFirstDrain), which
GetHeapStatus.TotalAllocated can fluctuate on across collector/heap-manager
calls -- a potential CI flake unrelated to cleanup idempotency. Allow a bounded
1 KiB delta (MAX_IDEMPOTENT_DRAIN_NOISE_BYTES, local to the test) so the check
still catches a double-free re-allocation or a real leak without flaking on
bookkeeping noise.

Test passes (3/3); format clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai coderabbitai Bot added documentation Improvements or additions to documentation and removed bug Something isn't working internal Refactoring, CI, tooling, cleanup labels Jun 29, 2026
main landed #893 (migrate explicit per-thread cache clears into the cleanup
registry, ADR 0083) and bumped the test262 / toml-test / tc39-mcp pins.
Resolutions:
- ADR number collision: renumber this PR's ADR 0083 -> 0084 (realm-owned
  prototype method hosts) so main's #893 keeps 0083; update docs/adr/README.md
  and all references, and add a sibling cross-link between the two #891 follow-ups.
- Goccia.ThreadCleanupLeak.Test: keep all three tests -- this PR's idempotency
  (#892, with the bounded-tolerance fix) and reclaim tests, plus main's
  migrated-cache-cleanup-registration test (#893).

Verified after merge: clean testrunner build, JS suite 11046/11046 in both
interpreter and bytecode modes, and ThreadCleanupLeak / SharedPrototypeRealmReuse
/ Threading Pascal tests green; format clean.
@coderabbitai coderabbitai Bot added bug Something isn't working internal Refactoring, CI, tooling, cleanup labels Jun 29, 2026
@frostney frostney merged commit 11ce658 into main Jun 29, 2026
15 checks passed
@frostney frostney deleted the claude/relaxed-beaver-4e332c branch June 29, 2026 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation internal Refactoring, CI, tooling, cleanup

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audit object-reference threadvars for worker-exit lifetime

1 participant