Skip to content

Promise and iterator optimisations#58

Merged
frostney merged 3 commits intomainfrom
promise-iterator-bytecode-optimisations
Mar 10, 2026
Merged

Promise and iterator optimisations#58
frostney merged 3 commits intomainfrom
promise-iterator-bytecode-optimisations

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Mar 10, 2026

Summary by CodeRabbit

  • New Features

    • Full native Promise support: prototype and static methods (then, catch, finally, resolve, reject, all, allSettled, race, any, withResolvers, try).
    • Promise chaining and finally helpers for clearer async flows.
  • Performance

    • Faster, direct iterator stepping across arrays, strings, maps, sets and lazy iterators.
    • Streamlined Promise resolution/await handling and reduced unnecessary work when no reactions exist.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Adds native Promise bridging and static/prototype delegates, wires Promise constructor and methods into the runtime and microtask flows, and refactors iterators to expose DirectNext(out ADone: Boolean) across concrete, generic, and lazy iterator types for direct value retrieval.

Changes

Cohort / File(s) Summary
Runtime / Promise bridging
units/Goccia.Runtime.Operations.pas
Adds FPromiseDelegate, FPromiseStaticDelegate, FPromiseConstructor; registers PROMISE_PROTOTYPE_METHODS and PROMISE_STATIC_METHODS; captures Promise constructor and static/prototype bridges; consults delegates for property access; adds Promise unwrapping, bridging helpers, native static/prototype implementations, and microtask interactions.
Promise value internals
units/Goccia.Values.PromiseValue.pas
Adds InvokeThen and InvokeFinally helpers; lazy-initializes FReactions and finally wrappers; refactors PromiseThen/PromiseFinally and early-exits when no reactions; minor state access refinements.
Iterator core (base & consumers)
units/Goccia.Values.IteratorValue.pas
Adds DirectNext(out ADone: Boolean) to TGocciaIteratorValue and migrates consuming functions (ForEach, Reduce, ToArray, Some, Every, Find, etc.) from IterResult/AdvanceNext to DirectNext with ADone handling.
Concrete iterators
units/Goccia.Values.Iterator.Concrete.pas
Implements DirectNext(out ADone: Boolean) for Array, String, Map, and Set iterators; returns appropriate TGocciaValue results and sets ADone on completion.
Generic iterator
units/Goccia.Values.Iterator.Generic.pas
Adds DirectNext(out ADone: Boolean) implementation that invokes NEXT, handles missing/non-callable NEXT, sets FDone and ADone, and returns direct TGocciaValue (no wrapper).
Lazy iterators
units/Goccia.Values.Iterator.Lazy.pas
Adds DirectNext(out ADone: Boolean) overrides for LazyMap, LazyFilter, LazyTake, LazyDrop, LazyFlatMap; drives source/inner iterators directly, manages ADone and inner iterator lifecycle.
GC / marking adjustments
units/... (iterator files)
Updates MarkReferences/GC marking paths to include newly referenced FSource/FInnerIterator where applicable.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(200,230,255,0.5)
    participant JS as JavaScript caller
    participant C as TGocciaRuntimeOperations (native bridge)
    participant P as Promise constructor/value
    participant MQ as MicrotaskQueue
    participant H as Reaction handlers (then/catch/finally)
    end
    JS->>C: new Promise(...) (bridged constructor)
    C->>P: create TGocciaPromiseValue, store FPromiseConstructor
    JS->>P: attach .then/.catch/.finally (resolved via delegates)
    P->>MQ: enqueue microtask on resolve/reject
    MQ->>H: run reaction callbacks
    H-->>JS: invoke user callbacks (possibly bridged callables)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰
Promises planted, tiny hops of code,
DirectNext fetches the next carrot on the road,
Delegates whisper then/catch/finally tunes,
Microtasks hum beneath quiet moons,
A rabbit cheers — async carrots bloom! 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Promise and iterator optimisations' accurately reflects the main changes in the pull request, which involve Promise integration scaffolding, iterator enhancements with DirectNext methods, and Promise-related optimizations across multiple files.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch promise-iterator-bytecode-optimisations

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 10, 2026

Benchmark Results

254 benchmarks

Interpreted: 🟢 30 improved · 🔴 176 regressed · 48 unchanged · avg -10.5%
Bytecode: 🟢 15 improved · 🔴 3 regressed · 🆕 31 new · 105 unchanged · avg +22.8%

arraybuffer.js — Interp: 🟢 4, 🔴 1, 9 unch. · avg +4.1% · Bytecode: 🔴 1, 13 unch. · avg -3.3%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 426,723 → 467,901 🟢 +9.6% 146,459 → 137,258 -6.3%
create ArrayBuffer(64) 393,785 → 437,208 🟢 +11.0% 145,367 → 135,460 -6.8%
create ArrayBuffer(1024) 294,082 → 358,326 🟢 +21.8% 129,997 → 120,563 🔴 -7.3%
create ArrayBuffer(8192) 141,171 → 171,052 🟢 +21.2% 82,610 → 79,596 -3.6%
slice full buffer (64 bytes) 510,631 → 535,493 +4.9% 378,710 → 364,837 -3.7%
slice half buffer (512 of 1024 bytes) 435,796 → 445,023 +2.1% 327,534 → 321,832 -1.7%
slice with negative indices 439,822 → 462,713 +5.2% 361,578 → 354,497 -2.0%
slice empty range 496,773 → 508,483 +2.4% 377,087 → 354,998 -5.9%
byteLength access 1,502,768 → 1,537,491 +2.3% 1,061,920 → 1,002,147 -5.6%
Symbol.toStringTag access 1,158,245 → 1,024,066 🔴 -11.6% 545,030 → 513,292 -5.8%
ArrayBuffer.isView 723,893 → 708,683 -2.1% 465,568 → 457,988 -1.6%
clone ArrayBuffer(64) 386,677 → 374,503 -3.1% 307,907 → 316,899 +2.9%
clone ArrayBuffer(1024) 296,888 → 295,244 -0.6% 248,486 → 253,487 +2.0%
clone ArrayBuffer inside object 261,320 → 246,138 -5.8% 154,923 → 153,844 -0.7%
arrays.js — Interp: 🟢 5, 🔴 5, 9 unch. · avg -0.3% · Bytecode: 19 unch. · avg -2.1%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 13,604 → 14,825 🟢 +9.0% 12,969 → 12,865 -0.8%
Array.from 10 elements 229,510 → 251,073 🟢 +9.4% 158,264 → 156,129 -1.3%
Array.of 10 elements 311,379 → 336,056 🟢 +7.9% 205,492 → 214,685 +4.5%
spread into new array 324,252 → 341,890 +5.4% 557,946 → 539,409 -3.3%
map over 50 elements 26,355 → 29,316 🟢 +11.2% 22,661 → 21,828 -3.7%
filter over 50 elements 22,273 → 24,594 🟢 +10.4% 21,565 → 20,500 -4.9%
reduce sum 50 elements 26,268 → 27,609 +5.1% 19,319 → 18,788 -2.7%
forEach over 50 elements 22,804 → 22,635 -0.7% 22,649 → 21,659 -4.4%
find in 50 elements 34,696 → 36,035 +3.9% 28,136 → 27,837 -1.1%
sort 20 elements 11,652 → 12,297 +5.5% 3,380 → 3,276 -3.1%
flat nested array 118,755 → 113,631 -4.3% 266,156 → 265,816 -0.1%
flatMap 73,105 → 69,037 -5.6% 191,898 → 184,102 -4.1%
map inside map (5x5) 21,397 → 19,371 🔴 -9.5% 68,304 → 66,311 -2.9%
filter inside map (5x10) 15,433 → 15,231 -1.3% 13,495 → 13,179 -2.3%
reduce inside map (5x10) 18,486 → 17,511 -5.3% 13,823 → 13,146 -4.9%
forEach inside forEach (5x10) 16,148 → 14,633 🔴 -9.4% 15,138 → 14,311 -5.5%
find inside some (10x10) 13,582 → 11,584 🔴 -14.7% 10,807 → 10,533 -2.5%
map+filter chain nested (5x20) 5,230 → 4,767 🔴 -8.9% 4,494 → 4,331 -3.6%
reduce flatten (10x5) 37,466 → 32,003 🔴 -14.6% 4,982 → 5,282 +6.0%
async-await.js — Interp: 🟢 4, 2 unch. · avg +9.9% · Bytecode: 🟢 5, 1 unch. · avg +11.9%
Benchmark Interpreted Δ Bytecode Δ
single await 335,665 → 382,055 🟢 +13.8% 246,701 → 275,249 🟢 +11.6%
multiple awaits 152,828 → 169,397 🟢 +10.8% 103,960 → 113,364 🟢 +9.0%
await non-Promise value 736,708 → 867,794 🟢 +17.8% 710,235 → 861,650 🟢 +21.3%
await with try/catch 329,609 → 353,785 🟢 +7.3% 236,308 → 265,000 🟢 +12.1%
await Promise.all 48,224 → 50,745 +5.2% 41,021 → 43,513 +6.1%
nested async function call 172,581 → 179,823 +4.2% 180,892 → 201,772 🟢 +11.5%
classes.js — Interp: 🟢 2, 🔴 19, 10 unch. · avg -9.3% · Bytecode: 🔴 1, 30 unch. · avg -2.5%
Benchmark Interpreted Δ Bytecode Δ
simple class new 114,123 → 125,252 🟢 +9.8% 338,192 → 330,833 -2.2%
class with defaults 91,514 → 96,772 +5.7% 236,450 → 226,992 -4.0%
50 instances via Array.from 5,602 → 5,823 +3.9% 6,091 → 6,020 -1.2%
instance method call 57,671 → 61,863 🟢 +7.3% 154,302 → 150,917 -2.2%
static method call 89,747 → 92,834 +3.4% 367,162 → 331,487 🔴 -9.7%
single-level inheritance 45,802 → 46,127 +0.7% 153,640 → 143,117 -6.8%
two-level inheritance 37,867 → 38,848 +2.6% 121,563 → 120,378 -1.0%
private field access 57,019 → 56,491 -0.9% 165,800 → 165,068 -0.4%
private methods 61,336 → 60,661 -1.1% 199,094 → 206,249 +3.6%
getter/setter access 63,681 → 59,807 -6.1% 161,259 → 165,584 +2.7%
class decorator (identity) 79,288 → 73,636 🔴 -7.1% 52,997 → 52,055 -1.8%
class decorator (wrapping) 45,754 → 43,678 -4.5% 36,515 → 35,474 -2.9%
identity method decorator 56,838 → 53,781 -5.4% 43,837 → 43,199 -1.5%
wrapping method decorator 49,612 → 42,316 🔴 -14.7% 39,488 → 38,358 -2.9%
stacked method decorators (x3) 33,422 → 30,217 🔴 -9.6% 28,253 → 27,538 -2.5%
identity field decorator 64,634 → 57,943 🔴 -10.4% 45,978 → 45,065 -2.0%
field initializer decorator 54,121 → 47,049 🔴 -13.1% 41,373 → 39,620 -4.2%
getter decorator (identity) 55,002 → 45,151 🔴 -17.9% 41,311 → 39,776 -3.7%
setter decorator (identity) 45,760 → 39,645 🔴 -13.4% 34,713 → 34,156 -1.6%
static method decorator 58,598 → 49,103 🔴 -16.2% 63,874 → 59,975 -6.1%
static field decorator 67,133 → 57,365 🔴 -14.5% 66,837 → 65,152 -2.5%
private method decorator 45,176 → 37,958 🔴 -16.0% 36,533 → 36,601 +0.2%
private field decorator 51,110 → 39,066 🔴 -23.6% 39,046 → 36,320 -7.0%
plain auto-accessor (no decorator) 86,379 → 75,011 🔴 -13.2% 53,582 → 51,549 -3.8%
auto-accessor with decorator 49,740 → 39,535 🔴 -20.5% 37,383 → 35,540 -4.9%
decorator writing metadata 42,823 → 34,438 🔴 -19.6% 42,141 → 41,213 -2.2%
static getter read 98,742 → 75,758 🔴 -23.3% 382,247 → 379,955 -0.6%
static getter/setter pair 75,465 → 61,555 🔴 -18.4% 204,355 → 196,022 -4.1%
inherited static getter 54,465 → 46,860 🔴 -14.0% 263,482 → 260,088 -1.3%
inherited static setter 60,336 → 49,993 🔴 -17.1% 206,135 → 205,907 -0.1%
inherited static getter with this binding 50,799 → 40,522 🔴 -20.2% 153,496 → 150,491 -2.0%
closures.js — Interp: 🔴 11 · avg -23.5% · Bytecode: 🟢 1, 10 unch. · avg +1.8%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 124,066 → 88,494 🔴 -28.7% 593,014 → 590,934 -0.4%
closure over multiple variables 116,314 → 82,989 🔴 -28.7% 397,704 → 379,782 -4.5%
nested closures 117,093 → 89,465 🔴 -23.6% 529,888 → 532,481 +0.5%
function as argument 89,259 → 66,847 🔴 -25.1% 493,168 → 484,334 -1.8%
function returning function 108,771 → 86,526 🔴 -20.5% 579,379 → 576,857 -0.4%
compose two functions 62,617 → 48,580 🔴 -22.4% 350,024 → 346,528 -1.0%
fn.call 138,872 → 106,209 🔴 -23.5% 103,119 → 129,341 🟢 +25.4%
fn.apply 108,181 → 82,062 🔴 -24.1% 86,073 → 88,018 +2.3%
fn.bind 134,198 → 99,544 🔴 -25.8% 134,233 → 136,955 +2.0%
recursive sum to 50 11,175 → 9,160 🔴 -18.0% 38,406 → 38,381 -0.1%
recursive tree traversal 18,467 → 15,084 🔴 -18.3% 59,237 → 57,912 -2.2%
collections.js — Interp: 🔴 7, 5 unch. · avg -14.6% · Bytecode: 🟢 4, 🔴 1, 7 unch. · avg +231.1%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 7,518 → 6,279 🔴 -16.5% 5,788 → 5,759 -0.5%
has lookup (50 elements) 96,228 → 92,401 -4.0% 91,551 → 88,904 -2.9%
delete elements 50,400 → 47,344 -6.1% 37,998 → 37,904 -0.2%
forEach iteration 15,071 → 12,510 🔴 -17.0% 14,865 → 15,192 +2.2%
spread to array 29,433 → 21,071 🔴 -28.4% 32,645 → 133,880 🟢 +310.1%
deduplicate array 40,297 → 30,621 🔴 -24.0% 33,701 → 45,735 🟢 +35.7%
set 50 entries 5,423 → 4,510 🔴 -16.8% 5,792 → 6,002 +3.6%
get lookup (50 entries) 92,120 → 91,612 -0.6% 403,893 → 102,253 🔴 -74.7%
has check 139,189 → 132,935 -4.5% 161,785 → 160,390 -0.9%
delete entries 48,829 → 46,223 -5.3% 36,593 → 35,098 -4.1%
forEach iteration 15,114 → 12,502 🔴 -17.3% 15,506 → 383,089 🟢 +2370.6%
keys/values/entries 8,126 → 5,312 🔴 -34.6% 8,897 → 20,811 🟢 +133.9%
destructuring.js — Interp: 🔴 22 · avg -25.7% · Bytecode: 🟢 1, 21 unch. · avg -1.3%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 393,327 → 281,563 🔴 -28.4% 623,664 → 610,384 -2.1%
with rest element 269,703 → 191,951 🔴 -28.8% 462,468 → 471,346 +1.9%
with defaults 388,365 → 284,212 🔴 -26.8% 665,672 → 640,788 -3.7%
skip elements 421,673 → 302,267 🔴 -28.3% 700,476 → 711,378 +1.6%
nested array destructuring 161,637 → 109,867 🔴 -32.0% 348,405 → 346,722 -0.5%
swap variables 473,979 → 335,023 🔴 -29.3% 977,914 → 961,828 -1.6%
simple object destructuring 284,353 → 242,410 🔴 -14.8% 513,532 → 514,354 +0.2%
with defaults 349,642 → 277,445 🔴 -20.6% 336,378 → 313,413 -6.8%
with renaming 299,703 → 252,204 🔴 -15.8% 580,578 → 564,140 -2.8%
nested object destructuring 149,989 → 106,817 🔴 -28.8% 256,834 → 252,528 -1.7%
rest properties 178,914 → 149,243 🔴 -16.6% 237,507 → 230,306 -3.0%
object parameter 87,743 → 69,617 🔴 -20.7% 190,446 → 182,104 -4.4%
array parameter 123,379 → 89,016 🔴 -27.9% 319,567 → 312,514 -2.2%
mixed destructuring in map 35,388 → 26,943 🔴 -23.9% 34,791 → 34,296 -1.4%
forEach with array destructuring 62,336 → 45,226 🔴 -27.4% 136,160 → 136,103 -0.0%
map with array destructuring 63,447 → 45,883 🔴 -27.7% 162,284 → 164,779 +1.5%
filter with array destructuring 67,318 → 47,892 🔴 -28.9% 173,137 → 189,510 🟢 +9.5%
reduce with array destructuring 74,417 → 50,895 🔴 -31.6% 165,864 → 166,288 +0.3%
map with object destructuring 74,326 → 59,622 🔴 -19.8% 68,222 → 66,968 -1.8%
map with nested destructuring 62,587 → 45,067 🔴 -28.0% 56,127 → 54,312 -3.2%
map with rest in destructuring 37,085 → 24,442 🔴 -34.1% 43,530 → 41,303 -5.1%
map with defaults in destructuring 58,936 → 44,481 🔴 -24.5% 36,087 → 35,198 -2.5%
fibonacci.js — Interp: 🟢 2, 🔴 4, 2 unch. · avg -7.1% · Bytecode: 🟢 4, 4 unch. · avg +16.4%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 307 → 255 🔴 -16.8% 1,129 → 1,132 +0.3%
recursive fib(20) 27 → 27 -1.2% 102 → 101 -0.8%
recursive fib(15) typed 304 → 258 🔴 -15.1% 1,423 → 1,437 +0.9%
recursive fib(20) typed 27 → 27 -1.0% 129 → 129 -0.5%
iterative fib(20) via reduce 11,321 → 8,474 🔴 -25.2% 18,106 → 20,791 🟢 +14.8%
iterator fib(20) 9,497 → 6,964 🔴 -26.7% 10,797 → 12,800 🟢 +18.6%
iterator fib(20) via Iterator.from + take 9,222 → 11,129 🟢 +20.7% 9,367 → 14,587 🟢 +55.7%
iterator fib(20) last value via reduce 7,384 → 8,019 🟢 +8.6% 7,947 → 11,272 🟢 +41.8%
for-of.js — Interp: 🔴 7 · avg -24.6% · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 45,596 → 33,741 🔴 -26.0%
for...of with 100-element array 5,214 → 3,760 🔴 -27.9%
for...of with string (10 chars) 34,437 → 24,884 🔴 -27.7%
for...of with Set (10 elements) 45,436 → 35,194 🔴 -22.5%
for...of with Map entries (10 entries) 28,594 → 21,852 🔴 -23.6%
for...of with destructuring 38,780 → 31,206 🔴 -19.5%
for-await-of with sync array 42,541 → 31,914 🔴 -25.0%
iterators.js — Interp: 🟢 12, 🔴 1, 7 unch. · avg +60.1% · Bytecode: 🆕 20
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 10,911 → 11,553 +5.9% 14,790 🆕
Iterator.from({next}).toArray() — 50 elements 4,670 → 5,252 🟢 +12.5% 6,631 🆕
spread pre-wrapped iterator — 20 elements 11,389 → 8,821 🔴 -22.5% 14,478 🆕
Iterator.from({next}).forEach — 50 elements 3,547 → 3,486 -1.7% 4,820 🆕
Iterator.from({next}).reduce — 50 elements 3,585 → 3,585 -0.0% 4,690 🆕
wrap array iterator 61,530 → 129,685 🟢 +110.8% 114,783 🆕
wrap plain {next()} object 8,030 → 8,138 +1.3% 11,976 🆕
map + toArray (50 elements) 3,248 → 3,615 🟢 +11.3% 5,144 🆕
filter + toArray (50 elements) 3,414 → 3,356 -1.7% 5,081 🆕
take(10) + toArray (50 element source) 16,614 → 20,770 🟢 +25.0% 27,098 🆕
drop(40) + toArray (50 element source) 4,428 → 5,023 🟢 +13.4% 7,181 🆕
chained map + filter + take (100 element source) 5,344 → 6,417 🟢 +20.1% 9,056 🆕
some + every (50 elements) 2,040 → 2,029 -0.5% 3,084 🆕
find (50 elements) 4,270 → 4,461 +4.5% 6,511 🆕
array.values().map().filter().toArray() 3,653 → 6,872 🟢 +88.1% 9,089 🆕
array.values().take(5).toArray() 64,857 → 150,308 🟢 +131.8% 152,644 🆕
array.values().drop(45).toArray() 17,111 → 137,989 🟢 +706.5% 130,990 🆕
map.entries() chained helpers 5,542 → 7,612 🟢 +37.4% 4,662 🆕
set.values() chained helpers 9,222 → 12,767 🟢 +38.4% 19,624 🆕
string iterator map + toArray 7,716 → 9,457 🟢 +22.6% 20,539 🆕
json.js — Interp: 🔴 19, 1 unch. · avg -15.6% · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
parse simple object 168,860 → 142,133 🔴 -15.8%
parse nested object 106,340 → 81,608 🔴 -23.3%
parse array of objects 57,959 → 45,020 🔴 -22.3%
parse large flat object 51,488 → 46,363 🔴 -10.0%
parse mixed types 74,260 → 60,715 🔴 -18.2%
stringify simple object 148,796 → 130,908 🔴 -12.0%
stringify nested object 84,177 → 68,546 🔴 -18.6%
stringify array of objects 39,194 → 39,665 +1.2%
stringify mixed types 69,046 → 62,028 🔴 -10.2%
reviver doubles numbers 45,087 → 35,277 🔴 -21.8%
reviver filters properties 37,452 → 30,866 🔴 -17.6%
reviver on nested object 50,408 → 40,084 🔴 -20.5%
reviver on array 28,367 → 23,192 🔴 -18.2%
replacer function doubles numbers 45,102 → 36,800 🔴 -18.4%
replacer function excludes properties 57,891 → 47,122 🔴 -18.6%
array replacer (allowlist) 108,556 → 90,659 🔴 -16.5%
stringify with 2-space indent 85,135 → 71,842 🔴 -15.6%
stringify with tab indent 80,771 → 71,333 🔴 -11.7%
parse then stringify 47,509 → 41,750 🔴 -12.1%
stringify then parse 29,348 → 25,850 🔴 -11.9%
jsx.jsx — Interp: 🔴 21 · avg -27.5% · Bytecode: 🆕 7
Benchmark Interpreted Δ Bytecode Δ
simple element 193,410 → 155,240 🔴 -19.7%
self-closing element 208,073 → 145,284 🔴 -30.2%
element with string attribute 168,562 → 120,932 🔴 -28.3%
element with multiple attributes 142,671 → 104,514 🔴 -26.7%
element with expression attribute 152,034 → 116,606 🔴 -23.3%
text child 193,639 → 135,386 🔴 -30.1%
expression child 188,557 → 135,433 🔴 -28.2%
mixed text and expression 178,841 → 126,492 🔴 -29.3%
nested elements (3 levels) 72,938 → 55,771 🔴 -23.5%
sibling children 54,473 → 37,953 🔴 -30.3%
component element 137,778 → 96,749 🔴 -29.8%
component with children 83,157 → 57,840 🔴 -30.4%
dotted component 114,350 → 79,142 🔴 -30.8%
empty fragment 195,422 → 142,380 🔴 -27.1%
fragment with children 51,722 → 37,601 🔴 -27.3% 145,532 🆕
spread attributes 100,900 → 79,188 🔴 -21.5% 101,987 🆕
spread with overrides 93,042 → 67,463 🔴 -27.5% 78,762 🆕
shorthand props 148,506 → 109,106 🔴 -26.5% 365,463 🆕
nav bar structure 25,624 → 18,080 🔴 -29.4% 68,384 🆕
card component tree 30,284 → 21,233 🔴 -29.9% 79,491 🆕
10 list items via Array.from 13,971 → 10,101 🔴 -27.7% 22,895 🆕
numbers.js — Interp: 🔴 9, 2 unch. · avg -14.6% · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 519,659 → 444,829 🔴 -14.4%
floating point arithmetic 538,683 → 456,745 🔴 -15.2%
number coercion 182,173 → 173,121 -5.0%
toFixed 107,871 → 85,931 🔴 -20.3%
toString 146,958 → 119,755 🔴 -18.5%
valueOf 215,368 → 158,323 🔴 -26.5%
toPrecision 141,931 → 106,477 🔴 -25.0%
Number.isNaN 300,668 → 265,164 🔴 -11.8%
Number.isFinite 288,845 → 266,133 🔴 -7.9%
Number.isInteger 278,144 → 261,355 -6.0%
Number.parseInt and parseFloat 245,498 → 221,719 🔴 -9.7%
objects.js — Interp: 🔴 7 · avg -30.0% · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
create simple object 439,337 → 317,880 🔴 -27.6%
create nested object 214,025 → 144,462 🔴 -32.5%
create 50 objects via Array.from 8,578 → 6,239 🔴 -27.3%
property read 634,983 → 450,367 🔴 -29.1%
Object.keys 277,629 → 211,355 🔴 -23.9%
Object.entries 97,625 → 57,171 🔴 -41.4%
spread operator 183,263 → 131,115 🔴 -28.5%
promises.js — Interp: 🔴 12 · avg -26.6% · Bytecode: 🆕 4
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 481,807 → 379,762 🔴 -21.2%
new Promise(resolve => resolve(value)) 172,347 → 122,420 🔴 -29.0%
Promise.reject(reason) 473,822 → 390,746 🔴 -17.5%
resolve + then (1 handler) 146,760 → 117,796 🔴 -19.7%
resolve + then chain (3 deep) 60,588 → 44,886 🔴 -25.9%
resolve + then chain (10 deep) 20,046 → 14,359 🔴 -28.4%
reject + catch + then 87,199 → 61,992 🔴 -28.9%
resolve + finally + then 70,480 → 51,839 🔴 -26.4%
Promise.all (5 resolved) 26,961 → 19,651 🔴 -27.1% 23,154 🆕
Promise.race (5 resolved) 29,815 → 21,260 🔴 -28.7% 26,621 🆕
Promise.allSettled (5 mixed) 24,839 → 16,209 🔴 -34.7% 22,022 🆕
Promise.any (5 mixed) 26,759 → 18,277 🔴 -31.7% 25,773 🆕
strings.js — Interp: 🔴 11 · avg -32.5% · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
string concatenation 389,077 → 239,429 🔴 -38.5%
template literal 426,844 → 324,455 🔴 -24.0%
string repeat 397,372 → 271,800 🔴 -31.6%
split and join 131,580 → 89,647 🔴 -31.9%
indexOf and includes 172,939 → 119,388 🔴 -31.0%
toUpperCase and toLowerCase 249,389 → 167,458 🔴 -32.9%
slice and substring 161,415 → 107,208 🔴 -33.6%
trim operations 185,596 → 120,911 🔴 -34.9%
replace and replaceAll 209,076 → 138,148 🔴 -33.9%
startsWith and endsWith 135,382 → 91,978 🔴 -32.1%
padStart and padEnd 204,941 → 135,939 🔴 -33.7%
typed-arrays.js — Interp: 🟢 1, 🔴 20, 1 unch. · avg -24.2% · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 302,149 → 193,727 🔴 -35.9%
new Int32Array(100) 283,734 → 188,243 🔴 -33.7%
new Int32Array(1000) 169,440 → 136,978 🔴 -19.2%
new Float64Array(100) 284,701 → 175,888 🔴 -38.2%
Int32Array.from([...]) 181,559 → 143,686 🔴 -20.9%
Int32Array.of(1, 2, 3, 4, 5) 317,272 → 200,917 🔴 -36.7%
sequential write 100 elements 3,718 → 2,564 🔴 -31.0%
sequential read 100 elements 3,604 → 2,583 🔴 -28.3%
Float64Array write 100 elements 3,319 → 2,473 🔴 -25.5%
fill(42) 45,389 → 53,073 🟢 +16.9%
slice() 206,365 → 157,993 🔴 -23.4%
map(x => x * 2) 8,106 → 5,560 🔴 -31.4%
filter(x => x > 50) 8,102 → 5,985 🔴 -26.1%
reduce (sum) 7,516 → 5,730 🔴 -23.8%
sort() 173,818 → 182,570 +5.0%
indexOf() 449,774 → 385,703 🔴 -14.2%
reverse() 343,582 → 303,831 🔴 -11.6%
create view over existing buffer 390,248 → 251,163 🔴 -35.6%
subarray() 456,549 → 313,662 🔴 -31.3%
set() from array 556,793 → 461,944 🔴 -17.0%
for-of loop 5,221 → 3,392 🔴 -35.0%
spread into array 17,120 → 11,035 🔴 -35.5%

Measured on ubuntu-latest x64. Changes within ±7% are considered insignificant.

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

🧹 Nitpick comments (2)
units/Goccia.Values.PromiseValue.pas (1)

409-473: Add ES spec anchors above the new Promise helper paths.

InvokeThen, InvokeFinally, and the rewritten prototype entry points are new Promise-semantics code paths, so they should carry the required // ESYYYY §... annotations immediately above the implementation bodies.

As per coding guidelines "When implementing ECMAScript-specified behavior, annotate each function or method with a comment referencing the relevant spec section using the format // ESYYYY §X.Y.Z SpecMethodName(specParams) where YYYY is the current edition year."

Also applies to: 477-527

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Values.PromiseValue.pas` around lines 409 - 473, Add ECMAScript
spec anchor comments above the new Promise helper implementations: place lines
like "// ES2021 §25.4.5.3 InvokeThen(spec?)" (use the correct year & section)
immediately above the TGocciaPromiseValue.InvokeThen implementation, similarly
add the appropriate "// ES2021 §25.4.5.4 InvokeFinally(...)" comment above
TGocciaPromiseValue.InvokeFinally, and add matching ES anchors above the
rewritten prototype entry-point methods referenced in this diff; ensure the
format follows the project convention "// ESYYYY §X.Y.Z
SpecMethodName(specParams)".
units/Goccia.Runtime.Operations.pas (1)

7259-7274: Please move the new Promise key strings into the constants units.

then, catch, finally, allSettled, withResolvers, etc. are runtime property names, and this unit already imports the constants units that centralize them.

As per coding guidelines, "Use constant units for runtime values instead of hardcoded string literals: Goccia.Constants.PropertyNames for property names, Goccia.Constants.TypeNames for type names, Goccia.Constants.ErrorNames for error names, Goccia.Constants.ConstructorNames for constructor names, Goccia.Constants.SymbolNames for symbol names, and Goccia.Constants for literal values."

Also applies to: 7491-7496

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Runtime.Operations.pas` around lines 7259 - 7274, Replace
hardcoded promise property name strings in the PROMISE_PROTOTYPE_METHODS and
PROMISE_STATIC_METHODS arrays with the centralized constants from the constants
units (e.g., use
Goccia.Constants.PropertyNames.<Then/Catch/Finally/AllSettled/WithResolvers/Try/All/Race/Any/Resolve/Reject>
as appropriate) instead of literal values; update the Callback references remain
unchanged (NativePromiseThen, NativePromiseCatch, NativePromiseFinally,
NativePromiseStaticResolve, NativePromiseStaticReject, NativePromiseStaticAll,
NativePromiseStaticAllSettled, NativePromiseStaticRace, NativePromiseStaticAny,
NativePromiseStaticWithResolvers, NativePromiseStaticTry) and ensure the unit
uses/imports the Goccia.Constants.PropertyNames unit (or the specific constants
unit already imported) so all runtime property names are sourced from the
constants module; apply the same replacement for the similar entries noted
around the other occurrence (the second array mentioned).
🤖 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.Runtime.Operations.pas`:
- Around line 7295-7298: Mark the newly created TSouffleRecord delegates
FPromiseDelegate and FPromiseStaticDelegate inside the MarkExternalRoots routine
so they cannot be collected; update MarkExternalRoots to check the runtime
fields (FPromiseDelegate, FPromiseStaticDelegate) and explicitly mark/root them
(or call the existing record-marking helper) if they are assigned, similar to
how other runtime delegates are rooted, ensuring the delegates produced by
BuildDelegate(PROMISE_PROTOTYPE_METHODS) and
BuildDelegate(PROMISE_STATIC_METHODS) are treated as external roots before GC
runs.
- Around line 3733-3747: The fast-path that clones TGocciaArrayValue from the
iterator is too broad; narrow it so you only clone plain array values (not
arrays that are themselves iterator-derived) by changing the condition in the
DirectNext handling: keep the check for (Value is TGocciaArrayValue) and
FArrayBridgeReverse but add a guard to skip cloning when Value is a
TGocciaIteratorValue (e.g. add "and not (Value is TGocciaIteratorValue)"); for
any array value that fails that guard, call ToSouffleValue(Value) instead so
iterator-produced arrays keep their original identity/mutation/Array prototype
behavior (and do not omit VM.ArrayDelegate).
- Around line 2791-2794: The current code returns early when AObject.AsReference
= FPromiseConstructor and FPromiseStaticDelegate.Get(AKey, Result) succeeds,
which bypasses the real object's property lookup and prevents wrapped Promises
from exposing changed/instance properties (e.g., p.then). Change the logic in
the block that checks AObject.AsReference, FPromiseConstructor and
FPromiseStaticDelegate.Get(AKey, Result) so it does not Exit immediately;
instead perform the normal property lookup on the underlying object first and
only apply the static delegate fallback if the object's lookup did not find the
property (i.e., call the usual lookup before or check that the object's lookup
returned false), and make the identical change for the analogous code at the
2821-2824 site.

In `@units/Goccia.Values.PromiseValue.pas`:
- Around line 457-472: The TGocciaPromiseValue.InvokeFinally creates wrapper
objects (WrapFulfilled, WrapRejected) that are only referenced via bound method
pointers and can be collected after this function returns; pin each wrapper with
the garbage collector immediately after creation (e.g. call
TGocciaGarbageCollector.Instance.PinValue(WrapFulfilled) and
PinValue(WrapRejected)) so they remain alive while reactions are queued, and
apply the same pinning for the TGocciaFinallyPassthrough instance created in the
wrapper's Invoke; alternatively, implement
TGocciaNativeFunctionValue.MarkReferences to mark its method-host TGocciaValue
so these wrappers are traced by GC.

---

Nitpick comments:
In `@units/Goccia.Runtime.Operations.pas`:
- Around line 7259-7274: Replace hardcoded promise property name strings in the
PROMISE_PROTOTYPE_METHODS and PROMISE_STATIC_METHODS arrays with the centralized
constants from the constants units (e.g., use
Goccia.Constants.PropertyNames.<Then/Catch/Finally/AllSettled/WithResolvers/Try/All/Race/Any/Resolve/Reject>
as appropriate) instead of literal values; update the Callback references remain
unchanged (NativePromiseThen, NativePromiseCatch, NativePromiseFinally,
NativePromiseStaticResolve, NativePromiseStaticReject, NativePromiseStaticAll,
NativePromiseStaticAllSettled, NativePromiseStaticRace, NativePromiseStaticAny,
NativePromiseStaticWithResolvers, NativePromiseStaticTry) and ensure the unit
uses/imports the Goccia.Constants.PropertyNames unit (or the specific constants
unit already imported) so all runtime property names are sourced from the
constants module; apply the same replacement for the similar entries noted
around the other occurrence (the second array mentioned).

In `@units/Goccia.Values.PromiseValue.pas`:
- Around line 409-473: Add ECMAScript spec anchor comments above the new Promise
helper implementations: place lines like "// ES2021 §25.4.5.3 InvokeThen(spec?)"
(use the correct year & section) immediately above the
TGocciaPromiseValue.InvokeThen implementation, similarly add the appropriate "//
ES2021 §25.4.5.4 InvokeFinally(...)" comment above
TGocciaPromiseValue.InvokeFinally, and add matching ES anchors above the
rewritten prototype entry-point methods referenced in this diff; ensure the
format follows the project convention "// ESYYYY §X.Y.Z
SpecMethodName(specParams)".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa4ce0f3-b148-4f3a-8896-4cfd0e42aa84

📥 Commits

Reviewing files that changed from the base of the PR and between d2aea53 and 680710c.

📒 Files selected for processing (6)
  • units/Goccia.Runtime.Operations.pas
  • units/Goccia.Values.Iterator.Concrete.pas
  • units/Goccia.Values.Iterator.Generic.pas
  • units/Goccia.Values.Iterator.Lazy.pas
  • units/Goccia.Values.IteratorValue.pas
  • units/Goccia.Values.PromiseValue.pas

Comment thread units/Goccia.Runtime.Operations.pas
Comment thread units/Goccia.Runtime.Operations.pas
Comment thread units/Goccia.Runtime.Operations.pas
Comment thread units/Goccia.Values.PromiseValue.pas
…tion

Add FPromiseDelegate and FPromiseStaticDelegate to MarkExternalRoots,
matching the pattern used by String, Number, Describe, and Test delegates.

Made-with: Cursor
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

🧹 Nitpick comments (1)
units/Goccia.Runtime.Operations.pas (1)

5994-6132: Add ES section annotations to the new Promise builtins.

These callbacks implement ECMAScript Promise behavior, but unlike the other spec-driven builtins in this unit they do not have // ES2026 §... markers above the function bodies and key steps.

As per coding guidelines: When implementing ECMAScript-specified behavior, add // ESYYYY §X.Y.Z spec annotations above each function body and at key algorithm steps within the body.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@units/Goccia.Runtime.Operations.pas` around lines 5994 - 6132, The new
Promise builtin callbacks lack the required ECMAScript spec annotations; add "//
ES2026 §X.Y.Z" style comments above each function (NativePromiseThen,
NativePromiseCatch, NativePromiseFinally, NativePromiseStaticResolve,
NativePromiseStaticReject, NativePromiseStaticAll,
NativePromiseStaticAllSettled, NativePromiseStaticRace, NativePromiseStaticAny,
NativePromiseStaticWithResolvers, NativePromiseStaticTry) and insert matching
"// ES2026 §X.Y.Z — step ..." markers at key algorithm steps inside each
function body to mirror the spec-driven builtins per the coding guidelines;
ensure the section numbers match the actual ECMAScript spec locations you relied
upon and place the annotations immediately above the function declaration and
before any important branches/steps.
🤖 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.Runtime.Operations.pas`:
- Around line 5959-6132: BridgePromiseStatic currently invokes the cached Goccia
function with an undefined receiver which loses subclass receivers (breaking
SubPromise.resolve/all/etc.); change BridgePromiseStatic to accept and forward
the AReceiver to TGocciaFunctionBase.Call instead of
TGocciaUndefinedLiteralValue.UndefinedValue, and update
NativePromiseStaticResolve and NativePromiseStaticReject to not construct
TGocciaPromiseValue directly but to invoke the constructor tied to the provided
receiver (i.e., call the promise constructor on AReceiver or route through the
modified BridgePromiseStatic/GPromise*Fn paths) so subclass-aware semantics are
preserved (references: BridgePromiseStatic, NativePromiseStaticResolve,
NativePromiseStaticReject,
GPromiseAllFn/GPromiseAnyFn/GPromiseRaceFn/GPromiseAllSettledFn/GPromiseWithResolversFn/GPromiseTryFn).

---

Nitpick comments:
In `@units/Goccia.Runtime.Operations.pas`:
- Around line 5994-6132: The new Promise builtin callbacks lack the required
ECMAScript spec annotations; add "// ES2026 §X.Y.Z" style comments above each
function (NativePromiseThen, NativePromiseCatch, NativePromiseFinally,
NativePromiseStaticResolve, NativePromiseStaticReject, NativePromiseStaticAll,
NativePromiseStaticAllSettled, NativePromiseStaticRace, NativePromiseStaticAny,
NativePromiseStaticWithResolvers, NativePromiseStaticTry) and insert matching
"// ES2026 §X.Y.Z — step ..." markers at key algorithm steps inside each
function body to mirror the spec-driven builtins per the coding guidelines;
ensure the section numbers match the actual ECMAScript spec locations you relied
upon and place the annotations immediately above the function declaration and
before any important branches/steps.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 90d2f770-eb28-4c3f-96a6-89ee0836b191

📥 Commits

Reviewing files that changed from the base of the PR and between 680710c and 2c75605.

📒 Files selected for processing (1)
  • units/Goccia.Runtime.Operations.pas

Comment thread units/Goccia.Runtime.Operations.pas
@frostney
Copy link
Copy Markdown
Owner Author

Re: comment 4029714496

Acknowledged. Benchmark report shows strong iterator and Promise bytecode gains. The widespread interpreted-mode regressions appear to be measurement noise from the CI runner (consistent ~20-30% across unrelated categories like JSON, strings, objects, JSX — unlikely to be caused by iterator/Promise changes).

@frostney frostney merged commit f977a1c into main Mar 10, 2026
7 checks passed
@frostney frostney deleted the promise-iterator-bytecode-optimisations branch March 10, 2026 11:23
@frostney frostney added the performance Performance improvement label Apr 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

performance Performance improvement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant