Skip to content

Add ToObject coercion for primitives across all Object.* static methods#271

Merged
frostney merged 5 commits intomainfrom
feature/object-to-object-coercion
Apr 11, 2026
Merged

Add ToObject coercion for primitives across all Object.* static methods#271
frostney merged 5 commits intomainfrom
feature/object-to-object-coercion

Conversation

@frostney
Copy link
Copy Markdown
Owner

Summary

Fixes #263.

Per ES2026 §7.1.18 ToObject, all Object.* static methods must coerce their first argument to an object — wrapping primitives in their respective wrapper objects and only throwing TypeError for null and undefined. Previously, GocciaScript threw for all non-objects, including numbers, strings, and booleans.

Changes

Goccia.Builtins.GlobalObject.pas

  • Add a standalone ToObject() helper (ES2026 §7.1.18) that uses ThrowTypeError directly (ensuring errors are catchable as TypeError from JavaScript try...catch)
  • Update the following methods to call ToObject instead of throwing directly:
    • Object.keys, Object.values, Object.entries
    • Object.getOwnPropertyNames, Object.getOwnPropertyDescriptor, Object.getOwnPropertyDescriptors
    • Object.getOwnPropertySymbols (previously returned [] for all non-objects including null/undefined)
    • Object.getPrototypeOf, Object.hasOwn, Object.assign (target only)
  • Fix Object.setPrototypeOf to use RequireObjectCoercible semantics: throw TypeError for null/undefined, silently return the primitive unchanged for other non-objects (e.g. Object.setPrototypeOf(42, null)42)
  • Methods that already had correct primitive passthrough behavior unchanged: freeze, isFrozen, seal, isSealed, preventExtensions, isExtensible
  • Methods that intentionally throw for non-objects per spec unchanged: defineProperty, defineProperties

Goccia.Values.StringObjectValue.pas

  • Override GetEnumerablePropertyNames / GetEnumerablePropertyValues / GetEnumerablePropertyEntries to expose character indices as own enumerable properties
  • Override GetAllPropertyNames to include character indices and length
  • Override GetOwnPropertyDescriptor to return correct descriptors for indices (enumerable, non-writable, non-configurable) and length (non-enumerable)
  • Override HasOwnProperty to correctly report ownership of indices and length

This makes Object.keys('str')['0','1','2'], Object.getOwnPropertyNames('str')['0','1','2','length'], and Object.getOwnPropertyDescriptor('str', '0'){ value: 's', enumerable: true, writable: false, configurable: false }.

Expected behavior after fix

Object.keys(42)       // []
Object.keys('str')    // ['0', '1', '2']
Object.keys(true)     // []
Object.keys(null)     // TypeError ✓
Object.keys(undefined) // TypeError ✓

Testing checklist

  • All 254 tests/built-ins/Object/ tests pass (100%)
  • Full test suite passes with no new failures (4548/4601 pass; the 12 pre-existing failures are FFI and ASI tests unrelated to this change)
  • Tests added/updated for: keys, values, entries, getOwnPropertyNames, getOwnPropertyDescriptor, getOwnPropertyDescriptors, getOwnPropertySymbols, getPrototypeOf, hasOwn, assign, setPrototypeOf
  • Fixed existing incorrect test in getOwnPropertyDescriptors.js that expected TypeError for primitive arguments
  • Documentation: behavior matches ES2026 §7.1.18 annotations in code

🤖 Generated with Claude Code

…ds (fixes #263)

Per ES2026 §7.1.18, Object static methods must call ToObject on their first
argument, wrapping primitives in their respective wrapper objects and only
throwing TypeError for null and undefined.

Key changes:
- Add a standalone ToObject() helper in GlobalObject.pas that uses ThrowTypeError
  (not a generic callback) so errors are properly catchable as TypeError
- Update Object.keys/values/entries, getOwnPropertyNames, getOwnPropertyDescriptor/s,
  getOwnPropertySymbols, getPrototypeOf, hasOwn, and assign to use ToObject coercion
- Fix Object.setPrototypeOf to use RequireObjectCoercible semantics: throw for
  null/undefined, but silently return the primitive for other non-objects
- Add character-index enumeration to TGocciaStringObjectValue so that boxed
  strings expose their character indices as own enumerable properties:
  Object.keys('str') → ['0','1','2'], Object.getOwnPropertyNames('str') → ['0','1','2','length']
- Add GetOwnPropertyDescriptor and HasOwnProperty overrides so string index
  access via Object.getOwnPropertyDescriptor works correctly
- Fix the existing Object.getOwnPropertyDescriptors test that incorrectly
  expected TypeError for all primitive arguments
- Cover all changed methods with null/undefined throw-TypeError tests and
  primitive coercion tests

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

coderabbitai Bot commented Apr 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f198735e-9704-48dc-8a22-3ac30d0200d1

📥 Commits

Reviewing files that changed from the base of the PR and between 075beec and dba2c1d.

📒 Files selected for processing (2)
  • tests/built-ins/Object/hasOwn.js
  • units/Goccia.Builtins.GlobalObject.pas
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/built-ins/Object/hasOwn.js
  • units/Goccia.Builtins.GlobalObject.pas

📝 Walkthrough

Walkthrough

Implements ES ToObject coercion across many Object.* built-ins, updates prototype/setPrototype handling and proxy routing, adds StringExoticObject own-property semantics, and expands tests to cover primitive coercion, canonical string index behavior, symbol semantics, and null/undefined TypeError cases.

Changes

Cohort / File(s) Summary
Object Method Tests
tests/built-ins/Object/assign.js, tests/built-ins/Object/entries.js, tests/built-ins/Object/getOwnPropertyDescriptor.js, tests/built-ins/Object/getOwnPropertyDescriptors.js, tests/built-ins/Object/getOwnPropertyNames.js, tests/built-ins/Object/getOwnPropertySymbols.js, tests/built-ins/Object/getPrototypeOf.js, tests/built-ins/Object/hasOwn.js, tests/built-ins/Object/keys.js, tests/built-ins/Object/setPrototypeOf.js, tests/built-ins/Object/values.js
Added and expanded tests asserting ToObject coercion for primitives (numbers/booleans boxed; strings expose indices and length), canonical index handling, symbol-key behavior, and that null/undefined throw TypeError where specified.
ToObject coercion & Object built-ins
units/Goccia.Builtins.GlobalObject.pas
Added internal ToObject helper and refactored many Object.* static methods to call it; primitives are boxed and temporarily GC-rooted, proxy/prototype flows adjusted, symbol-aware checks updated, and setPrototypeOf validation/coercion changed to match spec.
String exotic object semantics
units/Goccia.Values.StringObjectValue.pas
Extended TGocciaStringObjectValue with overrides implementing StringExoticObject semantics: enumerable index enumeration, length as non-enumerable own property, index and length descriptors, HasOwnProperty for canonical indices, and import/use adjustments.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Caller as JavaScript Call Site
participant Global as TGocciaGlobalObject
participant Box as Boxed Primitive Object
participant GC as GC Rooting

Caller->>Global: call Object.*(O, ...)
alt O is null/undefined
    Global-->>Caller: throw TypeError
else
    Global->>Global: ToObject(O)
    alt O is primitive
        Global->>Box: box primitive (Number/String/Boolean)
        Global->>GC: AddTempRoot(Box)
        Global->>Box: perform builtin operation (keys/values/descriptor/etc.)
        Global->>GC: RemoveTempRoot(Box)
        Box-->>Global: operation result
    else
        Global->>Global: use O (already object) for operation
    end
    Global-->>Caller: return result

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

internal

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding ToObject coercion for primitives across Object.* static methods, directly matching the primary objective of the PR.
Description check ✅ Passed The description comprehensively covers all required template sections: detailed summary with issue reference, complete list of changes to both files, expected behavior examples, and comprehensive testing checklist with all items completed.
Linked Issues check ✅ Passed The PR fully implements ES2026 §7.1.18 ToObject semantics for all Object.* methods listed in issue #263. All targeted methods now properly coerce primitives to wrapper objects and throw TypeError only for null/undefined, matching the spec requirements and expected behavior examples.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing ToObject coercion for Object.* methods and string boxing behavior. Test additions comprehensively cover primitive coercion, null/undefined error cases, and string property semantics. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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


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

@coderabbitai coderabbitai Bot added the spec compliance Mismatch against official JavaScript/TypeScript specification label Apr 11, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/built-ins/Object/getPrototypeOf.js (1)

29-38: Tighten the primitive-prototype assertions.

proto !== undefined still passes if boxing regresses to null, so these tests won't catch a missing wrapper prototype. Assert the concrete wrapper prototype if that's stable here, or at least assert the result is neither undefined nor null.

✅ Minimal strengthening without depending on prototype identity
   test("with number coerces to wrapper — returns a value", () => {
     // Primitives are coerced via ToObject; boxed number has the number prototype
     const proto = Object.getPrototypeOf(42);
-    expect(proto !== undefined).toBe(true);
+    expect(proto === undefined).toBe(false);
+    expect(proto === null).toBe(false);
   });
@@
   test("with boolean coerces to wrapper — returns a value", () => {
     const proto = Object.getPrototypeOf(true);
-    expect(proto !== undefined).toBe(true);
+    expect(proto === undefined).toBe(false);
+    expect(proto === null).toBe(false);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/built-ins/Object/getPrototypeOf.js` around lines 29 - 38, The tests
named "with number coerces to wrapper — returns a value" and "with boolean
coerces to wrapper — returns a value" currently only assert proto !== undefined;
tighten these assertions to ensure the boxed wrapper prototype isn't null by
asserting the result of Object.getPrototypeOf(42) and
Object.getPrototypeOf(true) is neither undefined nor null (e.g., check proto !==
undefined && proto !== null or use two expectations) so regressions that return
null will fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.Builtins.GlobalObject.pas`:
- Around line 757-772: The current check for invalid proto uses ThrowError which
produces a generic error; replace that call with ThrowTypeError so invalid
prototype types (as in Object.setPrototypeOf({}, 1)) produce a JavaScript
TypeError. Locate the ProtoArg validation in
units/Goccia.Builtins.GlobalObject.pas (the block referencing ProtoArg and the
check "not (ProtoArg is TGocciaObjectValue) and not (ProtoArg is
TGocciaNullLiteralValue)"), and change the ThrowError(...) invocation to
ThrowTypeError(...) with the same message to use the centralized error
constructor.

In `@units/Goccia.Values.StringObjectValue.pas`:
- Around line 381-399: The string-index fast path currently uses TryStrToInt on
AName which accepts non-canonical forms like "01" and "-0"; change the check to
accept only canonical non-negative integer indices (either "0" or a sequence
starting with '1'..'9' followed by only digits) before converting to an integer.
Constrain the branch around the existing TryStrToInt/Index logic (the block
referencing AName, Index, StringValue, and TGocciaPropertyDescriptorData) so it
only triggers for canonical decimal keys; fall back to the ordinary-property
path (e.g., the PROP_LENGTH / ordinary lookup code) for any other AName.
- Around line 323-373: The current String exotic overrides
(GetEnumerablePropertyNames, GetEnumerablePropertyValues,
GetEnumerablePropertyEntries, GetAllPropertyNames) replace the base
FProperties-backed own properties; change each to append the ordinary own
properties from FProperties after the synthesized index keys (and after 'length'
for GetAllPropertyNames) instead of replacing them: build the index-based arrays
as you already do, then iterate FProperties to add its keys/values/entries (for
GetEnumerable* only include enumerable properties) while skipping duplicates
(numeric index strings already emitted and skip adding 'length' again), and for
GetAllPropertyNames include all FProperties keys (non-enumerable too) after the
indices; use the existing FProperties container methods to fetch keys/values and
ensure duplicate suppression so expando props like s.extra are preserved.

---

Nitpick comments:
In `@tests/built-ins/Object/getPrototypeOf.js`:
- Around line 29-38: The tests named "with number coerces to wrapper — returns a
value" and "with boolean coerces to wrapper — returns a value" currently only
assert proto !== undefined; tighten these assertions to ensure the boxed wrapper
prototype isn't null by asserting the result of Object.getPrototypeOf(42) and
Object.getPrototypeOf(true) is neither undefined nor null (e.g., check proto !==
undefined && proto !== null or use two expectations) so regressions that return
null will fail.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 93380e0c-bc11-467f-9cce-0d35047173d2

📥 Commits

Reviewing files that changed from the base of the PR and between e4e09c5 and e890c90.

📒 Files selected for processing (13)
  • tests/built-ins/Object/assign.js
  • tests/built-ins/Object/entries.js
  • tests/built-ins/Object/getOwnPropertyDescriptor.js
  • tests/built-ins/Object/getOwnPropertyDescriptors.js
  • tests/built-ins/Object/getOwnPropertyNames.js
  • tests/built-ins/Object/getOwnPropertySymbols.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Object/hasOwn.js
  • tests/built-ins/Object/keys.js
  • tests/built-ins/Object/setPrototypeOf.js
  • tests/built-ins/Object/values.js
  • units/Goccia.Builtins.GlobalObject.pas
  • units/Goccia.Values.StringObjectValue.pas

Comment thread units/Goccia.Builtins.GlobalObject.pas
Comment thread units/Goccia.Values.StringObjectValue.pas
Comment thread units/Goccia.Values.StringObjectValue.pas Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 11, 2026

Benchmark Results

364 benchmarks

Interpreted: 🟢 228 improved · 🔴 64 regressed · 72 unchanged · avg +2.2%
Bytecode: 🟢 100 improved · 🔴 74 regressed · 190 unchanged · avg +0.9%

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

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 11, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 4763 4763
Tests Passed 4722 ✅ 4763 ✅
Tests Skipped 41 0
Tests Test Duration 331.1ms 308.0ms
Tests Lex 92.7ms 62.4ms
Tests Parse 122.0ms 122.7ms
Tests Compile 75.5ms
Tests Execute 349.1ms 338.2ms
Tests Engine Total 563.8ms 598.8ms
Benchmarks Total 364 364
Benchmarks Duration 10.05min 8.68min

Measured on ubuntu-latest x64.

Three actionable issues fixed:

1. ThrowError → ThrowTypeError for invalid proto type in Object.setPrototypeOf
   (Object.setPrototypeOf({}, 1) was throwing a generic Error instead of TypeError)

2. String enumeration overrides now append FProperties-backed own properties after
   the synthesized index keys, so expando properties on String objects remain visible
   to Object.keys / Object.values / Object.entries / Object.getOwnPropertyNames

3. Canonical string-index check added to GetOwnPropertyDescriptor and HasOwnProperty:
   TryStrToInt matched non-canonical forms like "01" and "-0"; the additional
   `AName = IntToStr(Index)` guard now ensures round-trip canonicality per
   ES2026 §10.4.3

Nitpick addressed: tighten getPrototypeOf.js proto assertions from
`proto !== undefined` to `proto !== null && proto !== undefined`

Additional tests added:
- Object.setPrototypeOf({}, 1) → TypeError
- Object.getOwnPropertyDescriptor / hasOwn with non-canonical indices "01"/"-0"

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@frostney
Copy link
Copy Markdown
Owner Author

All CodeRabbit feedback addressed in 88fc31a:

🟠 Major — ThrowTypeError for invalid proto in Object.setPrototypeOf
ThrowErrorThrowTypeError on the proto argument validation so Object.setPrototypeOf({}, 1) throws a catchable JavaScript TypeError. Regression test added.

🟠 Major — String enumeration overrides preserve expando properties
All four overrides (GetEnumerablePropertyNames/Values/Entries, GetAllPropertyNames) now call inherited to collect FProperties-backed own properties and append them after the synthesised character-index keys, so properties like s.extra on a String object remain visible.

🟡 Minor — Canonical string-index guard in GetOwnPropertyDescriptor / HasOwnProperty
Added and (AName = IntToStr(Index)) to both overrides so non-canonical keys like "01" and "-0" correctly fall through to the ordinary-property path instead of matching a character index. Regression tests for "01" / "-0" added.

🧹 Nitpick — Tighten getPrototypeOf primitive assertions
Changed proto !== undefinedproto !== null && proto !== undefined so the test would also catch a regression where boxing returns a null prototype.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@units/Goccia.Builtins.GlobalObject.pas`:
- Around line 103-128: The ToObject function is returning a freshly boxed
primitive (via AValue.Box) that lives only on the Pascal stack and can be
collected by GC when callers perform allocations; protect that temporary boxed
wrapper with the engine's temp-root API. After Box returns and Boxed is assigned
in ToObject, call AddTempRoot(Boxed) immediately, ensure any early
ThrowTypeError paths still remove the temp root, and call RemoveTempRoot(Boxed)
just before returning the TGocciaObjectValue (use a try/finally around the
caller-facing work or ensure every Exit removes the temp root). Keep
ThrowTypeError behavior unchanged and only add AddTempRoot/RemoveTempRoot around
the AValue.Box / TGocciaObjectValue(Boxed) handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b6178612-93ca-4df0-b39d-5a1c1d45cc6d

📥 Commits

Reviewing files that changed from the base of the PR and between e890c90 and 88fc31a.

📒 Files selected for processing (6)
  • tests/built-ins/Object/getOwnPropertyDescriptor.js
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Object/hasOwn.js
  • tests/built-ins/Object/setPrototypeOf.js
  • units/Goccia.Builtins.GlobalObject.pas
  • units/Goccia.Values.StringObjectValue.pas
✅ Files skipped from review due to trivial changes (3)
  • tests/built-ins/Object/getPrototypeOf.js
  • tests/built-ins/Object/setPrototypeOf.js
  • tests/built-ins/Object/hasOwn.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/built-ins/Object/getOwnPropertyDescriptor.js
  • units/Goccia.Values.StringObjectValue.pas

Comment thread units/Goccia.Builtins.GlobalObject.pas
When ToObject boxes a primitive (boolean, number, string) the resulting
wrapper object lives only in a Pascal local and has no JavaScript scope
root. If a subsequent allocation in the same method body triggers
CollectIfNeeded, the wrapper could be swept before use.

Add try/finally guards around the work in each of the ten Object.*
methods that call ToObject — ObjectKeys, ObjectValues, ObjectEntries,
ObjectAssign, ObjectHasOwn, ObjectGetOwnPropertyNames,
ObjectGetOwnPropertyDescriptor, ObjectGetOwnPropertyDescriptors,
ObjectGetOwnPropertySymbols, and ObjectGetPrototypeOf — following the
AddTempRoot/RemoveTempRoot pattern already established in the codebase
(Goccia.Values.MockFunction, Goccia.Builtins.GlobalArray, etc.).

The guard is a no-op when the input was already a JS-scope object; it
only matters for freshly boxed primitives.

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

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
units/Goccia.Builtins.GlobalObject.pas (1)

349-352: ⚠️ Potential issue | 🟠 Major

Preserve symbol property keys in Object.hasOwn.

Both code paths convert P with ToStringLiteral instead of checking for symbol keys. Lines 361–367 (ClassObj path) and lines 373–377 (normal Obj path) will incorrectly stringify symbol arguments, so Object.hasOwn(obj, sym) checks "Symbol(...)" instead of the actual symbol slot. The symbol-aware split already exists in Object.getOwnPropertyDescriptor (lines 432–437); apply the same ToPropertyKey handling to both paths in Object.hasOwn.

🛠️ Proposed fix
 var
   Obj: TGocciaObjectValue;
   PropertyName: string;
   ClassObj: TGocciaClassValue;
+  SymbolKey: TGocciaSymbolValue;
 begin
   TGocciaArgumentValidator.RequireExactly(AArgs, 2, 'Object.hasOwn', ThrowError);

   // Step 1: Let obj be ? ToObject(O)
   if AArgs.GetElement(0) is TGocciaClassValue then
   begin
     ClassObj := TGocciaClassValue(AArgs.GetElement(0));
     // Step 2: Let key be ? ToPropertyKey(P)
-    PropertyName := AArgs.GetElement(1).ToStringLiteral.Value;
-    // Step 3: Return ? HasOwnProperty(obj, key)
-    if not (ClassObj.GetProperty(PropertyName) is TGocciaUndefinedLiteralValue) then
-      Result := TGocciaBooleanLiteralValue.TrueValue
+    if AArgs.GetElement(1) is TGocciaSymbolValue then
+    begin
+      SymbolKey := TGocciaSymbolValue(AArgs.GetElement(1));
+      if ClassObj.GetOwnSymbolPropertyDescriptor(SymbolKey) <> nil then
+        Result := TGocciaBooleanLiteralValue.TrueValue
+      else
+        Result := TGocciaBooleanLiteralValue.FalseValue;
+    end
     else
-      Result := TGocciaBooleanLiteralValue.FalseValue;
+    begin
+      PropertyName := AArgs.GetElement(1).ToStringLiteral.Value;
+      if not (ClassObj.GetProperty(PropertyName) is TGocciaUndefinedLiteralValue) then
+        Result := TGocciaBooleanLiteralValue.TrueValue
+      else
+        Result := TGocciaBooleanLiteralValue.FalseValue;
+    end;
     Exit;
   end;

   Obj := ToObject(AArgs.GetElement(0));
@@ -373,11 +390,23 @@
   try
     // Step 2: Let key be ? ToPropertyKey(P)
-    PropertyName := AArgs.GetElement(1).ToStringLiteral.Value;
-
-    // Step 3: Return ? HasOwnProperty(obj, key)
-    if Obj.HasOwnProperty(PropertyName) then
-      Result := TGocciaBooleanLiteralValue.TrueValue
+    if AArgs.GetElement(1) is TGocciaSymbolValue then
+    begin
+      SymbolKey := TGocciaSymbolValue(AArgs.GetElement(1));
+      if Obj.GetOwnSymbolPropertyDescriptor(SymbolKey) <> nil then
+        Result := TGocciaBooleanLiteralValue.TrueValue
+      else
+        Result := TGocciaBooleanLiteralValue.FalseValue;
+    end
     else
-      Result := TGocciaBooleanLiteralValue.FalseValue;
+    begin
+      PropertyName := AArgs.GetElement(1).ToStringLiteral.Value;
+      // Step 3: Return ? HasOwnProperty(obj, key)
+      if Obj.HasOwnProperty(PropertyName) then
+        Result := TGocciaBooleanLiteralValue.TrueValue
+      else
+        Result := TGocciaBooleanLiteralValue.FalseValue;
+    end;
   finally
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Builtins.GlobalObject.pas` around lines 349 - 352, The
Object.hasOwn implementation currently converts the property argument P using
ToStringLiteral which loses symbol keys; update both code paths (the ClassObj
branch handling ClassObj: TGocciaClassValue and the normal Obj:
TGocciaObjectValue branch) to use ToPropertyKey instead of ToStringLiteral so
symbol property keys are preserved (mirror the symbol-aware logic used in
Object.getOwnPropertyDescriptor). Replace the ToStringLiteral(P) usage where P
is converted to a string with a call that obtains the property key via
ToPropertyKey and then use that key for the subsequent property lookup/slot
checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@units/Goccia.Builtins.GlobalObject.pas`:
- Around line 349-352: The Object.hasOwn implementation currently converts the
property argument P using ToStringLiteral which loses symbol keys; update both
code paths (the ClassObj branch handling ClassObj: TGocciaClassValue and the
normal Obj: TGocciaObjectValue branch) to use ToPropertyKey instead of
ToStringLiteral so symbol property keys are preserved (mirror the symbol-aware
logic used in Object.getOwnPropertyDescriptor). Replace the ToStringLiteral(P)
usage where P is converted to a string with a call that obtains the property key
via ToPropertyKey and then use that key for the subsequent property lookup/slot
checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 095b2975-afb4-499f-b4a9-1eb03faafb96

📥 Commits

Reviewing files that changed from the base of the PR and between 88fc31a and b0d8f6b.

📒 Files selected for processing (1)
  • units/Goccia.Builtins.GlobalObject.pas

frostney and others added 2 commits April 11, 2026 20:05
The benchmark runner already protects setup-returned objects with
GC.AddTempRoot(SetupResult). When Object.keys(obj) was called in the
benchmark run callback, the unconditional RemoveTempRoot(obj) inside the
method stripped that protection mid-benchmark. On the next CollectIfNeeded
call (line 246 of Goccia.Builtins.Benchmark.pas), the setup object was
swept, causing an access violation on the subsequent iteration.

Fix: the temp-root guards must fire only when ToObject actually boxed a
primitive (i.e. the caller passed a non-object). Already-rooted arguments
(TGocciaObjectValue in scope) need no extra protection and must not have
their existing roots disturbed.

All ten methods now gate AddTempRoot/RemoveTempRoot behind:
  not (AArgs.GetElement(0) is TGocciaObjectValue)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Both branches of ObjectHasOwn were calling ToStringLiteral on the
property key argument, which coerces symbol keys to their string
representation instead of checking the actual symbol property slot.

Fix: mirror the symbol-aware branching used in ObjectGetOwnPropertyDescriptor.
- For the TGocciaObjectValue path: check HasSymbolProperty for symbol keys
  (own-property-only check via FSymbolDescriptors) and HasOwnProperty for
  string keys — unchanged.
- For the TGocciaClassValue path: check GetSymbolProperty for symbol keys
  and GetProperty for string keys — consistent with the existing chain-walk
  behaviour of that branch.

Tests added for symbol key detection (own vs inherited vs absent).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai coderabbitai Bot added the internal Refactoring, CI, tooling, cleanup label Apr 11, 2026
@frostney frostney merged commit d149d12 into main Apr 11, 2026
9 checks passed
@frostney frostney deleted the feature/object-to-object-coercion branch April 11, 2026 20:15
frostney added a commit that referenced this pull request Apr 11, 2026
Incorporates 5 commits from main:
- TextEncoder and TextDecoder built-ins (#272)
- Make import.meta tests account for Windows drive letters (#274)
- Fixes tagged template object identity (#275)
- Add Goccia.build platform metadata (#276)
- Add ToObject coercion for primitives across all Object.* static methods (#271)

Conflict resolution in 3 files (all "both sides added" — keep both):

Goccia.Engine.pas / Goccia.Runtime.Bootstrap.pas:
  - Added ggTextEncoder and ggTextDecoder to TGocciaGlobalBuiltin enum
  - Added both to DefaultGlobals alongside ggURL
  - Added FBuiltinTextEncoder/FBuiltinTextDecoder fields, Free calls,
    registration blocks, Expose* helpers, and constructor TypeDef blocks
    alongside the existing URL equivalents

Goccia.Values.ClassValue.pas:
  - Added TGocciaTextEncoderClassValue and TGocciaTextDecoderClassValue
    declarations, impl uses, and CreateNativeInstance implementations
    alongside the existing URL equivalents

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

internal Refactoring, CI, tooling, cleanup spec compliance Mismatch against official JavaScript/TypeScript specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add ToObject coercion for primitives across all Object.* static methods

1 participant