Skip to content

Eliminate redundant OP_MOVE in plain function call compilation#86

Merged
frostney merged 2 commits into
mainfrom
opt/dest-reg-propagation
Mar 13, 2026
Merged

Eliminate redundant OP_MOVE in plain function call compilation#86
frostney merged 2 commits into
mainfrom
opt/dest-reg-propagation

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Mar 12, 2026

Summary

  • Optimizes plain function call compilation to eliminate redundant `OP_MOVE` instructions.
  • When `ADest` is at the top of the register stack and not a local variable slot, the call is compiled directly into `ADest` instead of a temp register.
  • Fires for expression statements, binary expression operands, nested call arguments, and other contexts where a fresh temp was just allocated.
  • Method calls, super calls, and private member calls are left unchanged (different register layout makes this unsafe).

Test plan

  • All JS tests pass in interpreter mode
  • All JS tests pass in bytecode mode

Made with Cursor

Summary by CodeRabbit

  • Refactor
    • Improved register allocation and memory management during function call compilation for better efficiency.
    • Optimized conditional logic for register assignment to reduce unnecessary register operations.

When the destination register is at the top of the register stack and
not a local variable slot, CompileCall now compiles the call directly
into ADest instead of allocating a temp register and emitting OP_MOVE.
This eliminates one instruction per function call in common patterns
like expression statements, binary expression operands, and nested
call arguments.

Made-with: Cursor
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e2377881-ad43-4bd2-bf53-25f51926264d

📥 Commits

Reviewing files that changed from the base of the PR and between 7001181 and 1a6b8f4.

📒 Files selected for processing (1)
  • units/Goccia.Compiler.Expressions.pas

📝 Walkthrough

Walkthrough

Optimizes register allocation in the generic call path of CompileCall by introducing an IsLocalSlot helper function and adding conditional logic to prefer using ADest as BaseReg when appropriate, reducing unnecessary register allocations and moves.

Changes

Cohort / File(s) Summary
Register Allocation Optimization
units/Goccia.Compiler.Expressions.pas
Added IsLocalSlot helper function and modified CompileCall to conditionally use ADest as BaseReg when ADest + 1 equals Scope.NextSlot and isn't an existing local slot. Restructured post-call register operations to conditionally move and free registers only when BaseReg differs from ADest.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~15 minutes

Poem

🐰 A register hops with graceful ease,
No extra moves to steal the breeze,
When slots align in perfect measure,
Smart allocation is our treasure! ✨

🚥 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 accurately and clearly describes the main optimization: eliminating redundant OP_MOVE instructions in plain function call compilation.
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 opt/dest-reg-propagation
📝 Coding Plan
  • Generate coding plan for human review comments

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 12, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 3463 3463
Tests Passed 3422 ✅ 3463 ✅
Tests Skipped 41 0
Tests Execution 161.0ms 168.1ms
Tests Engine 320.9ms 693.0ms
Benchmarks Total 254 254
Benchmarks Duration 6.48min 10.88min

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 12, 2026

Benchmark Results

254 benchmarks

Interpreted: 🟢 87 improved · 🔴 3 regressed · 164 unchanged · avg +4.7%
Bytecode: 🟢 49 improved · 🔴 1 regressed · 204 unchanged · avg +4.6%

arraybuffer.js — Interp: 🟢 11, 3 unch. · avg +10.1% · Bytecode: 14 unch. · avg +3.4%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 442,834 → 496,029 🟢 +12.0% 141,265 → 142,760 +1.1%
create ArrayBuffer(64) 434,924 → 485,601 🟢 +11.7% 137,715 → 140,465 +2.0%
create ArrayBuffer(1024) 331,038 → 388,928 🟢 +17.5% 124,577 → 127,617 +2.4%
create ArrayBuffer(8192) 141,194 → 171,656 🟢 +21.6% 78,958 → 76,815 -2.7%
slice full buffer (64 bytes) 537,777 → 579,633 🟢 +7.8% 363,643 → 383,715 +5.5%
slice half buffer (512 of 1024 bytes) 453,499 → 506,228 🟢 +11.6% 317,704 → 333,326 +4.9%
slice with negative indices 458,507 → 510,050 🟢 +11.2% 349,120 → 361,748 +3.6%
slice empty range 525,604 → 576,272 🟢 +9.6% 362,574 → 375,391 +3.5%
byteLength access 1,593,949 → 1,669,253 +4.7% 1,075,299 → 1,125,637 +4.7%
Symbol.toStringTag access 1,188,971 → 1,144,483 -3.7% 548,033 → 565,394 +3.2%
ArrayBuffer.isView 780,799 → 808,887 +3.6% 456,323 → 476,176 +4.4%
clone ArrayBuffer(64) 400,522 → 438,418 🟢 +9.5% 306,455 → 327,265 +6.8%
clone ArrayBuffer(1024) 307,778 → 354,231 🟢 +15.1% 241,145 → 249,936 +3.6%
clone ArrayBuffer inside object 273,216 → 299,350 🟢 +9.6% 150,917 → 156,999 +4.0%
arrays.js — Interp: 🟢 12, 7 unch. · avg +7.8% · Bytecode: 🟢 5, 14 unch. · avg +4.6%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 14,336 → 15,314 +6.8% 12,844 → 12,541 -2.4%
Array.from 10 elements 234,159 → 255,762 🟢 +9.2% 148,782 → 159,735 🟢 +7.4%
Array.of 10 elements 319,137 → 344,978 🟢 +8.1% 219,792 → 223,727 +1.8%
spread into new array 348,480 → 370,286 +6.3% 568,531 → 661,103 🟢 +16.3%
map over 50 elements 28,476 → 31,006 🟢 +8.9% 22,174 → 22,235 +0.3%
filter over 50 elements 24,330 → 25,958 +6.7% 21,694 → 21,185 -2.3%
reduce sum 50 elements 27,611 → 29,388 +6.4% 19,723 → 19,478 -1.2%
forEach over 50 elements 23,411 → 24,648 +5.3% 22,664 → 23,198 +2.4%
find in 50 elements 35,726 → 38,299 🟢 +7.2% 28,180 → 27,696 -1.7%
sort 20 elements 12,416 → 13,219 +6.5% 3,180 → 3,208 +0.9%
flat nested array 117,560 → 128,995 🟢 +9.7% 292,411 → 370,778 🟢 +26.8%
flatMap 74,825 → 82,016 🟢 +9.6% 210,985 → 246,907 🟢 +17.0%
map inside map (5x5) 21,945 → 23,978 🟢 +9.3% 69,684 → 76,179 🟢 +9.3%
filter inside map (5x10) 16,417 → 17,794 🟢 +8.4% 13,543 → 13,690 +1.1%
reduce inside map (5x10) 19,812 → 21,387 🟢 +8.0% 13,852 → 14,079 +1.6%
forEach inside forEach (5x10) 16,971 → 17,733 +4.5% 15,032 → 15,572 +3.6%
find inside some (10x10) 14,051 → 15,311 🟢 +9.0% 10,716 → 10,790 +0.7%
map+filter chain nested (5x20) 5,536 → 5,989 🟢 +8.2% 4,433 → 4,513 +1.8%
reduce flatten (10x5) 39,704 → 43,605 🟢 +9.8% 5,954 → 6,177 +3.7%
async-await.js — Interp: 🟢 6 · avg +9.5% · Bytecode: 6 unch. · avg +3.2%
Benchmark Interpreted Δ Bytecode Δ
single await 381,269 → 417,744 🟢 +9.6% 268,293 → 278,695 +3.9%
multiple awaits 170,165 → 187,491 🟢 +10.2% 113,971 → 118,458 +3.9%
await non-Promise value 870,408 → 967,412 🟢 +11.1% 909,327 → 927,247 +2.0%
await with try/catch 370,025 → 407,205 🟢 +10.0% 264,985 → 271,200 +2.3%
await Promise.all 52,499 → 56,819 🟢 +8.2% 42,435 → 44,816 +5.6%
nested async function call 190,320 → 205,107 🟢 +7.8% 205,747 → 209,173 +1.7%
classes.js — Interp: 🟢 29, 2 unch. · avg +11.2% · Bytecode: 🟢 7, 24 unch. · avg +5.6%
Benchmark Interpreted Δ Bytecode Δ
simple class new 116,571 → 130,931 🟢 +12.3% 337,012 → 355,222 +5.4%
class with defaults 93,547 → 105,536 🟢 +12.8% 236,359 → 257,013 🟢 +8.7%
50 instances via Array.from 5,641 → 6,322 🟢 +12.1% 6,257 → 6,301 +0.7%
instance method call 59,503 → 67,042 🟢 +12.7% 147,565 → 162,464 🟢 +10.1%
static method call 92,175 → 102,305 🟢 +11.0% 352,299 → 376,722 +6.9%
single-level inheritance 46,362 → 52,275 🟢 +12.8% 147,419 → 162,142 🟢 +10.0%
two-level inheritance 39,573 → 44,625 🟢 +12.8% 123,586 → 138,342 🟢 +11.9%
private field access 58,425 → 65,415 🟢 +12.0% 163,573 → 178,521 🟢 +9.1%
private methods 63,715 → 71,230 🟢 +11.8% 207,363 → 229,976 🟢 +10.9%
getter/setter access 66,095 → 73,627 🟢 +11.4% 163,949 → 175,207 +6.9%
class decorator (identity) 81,089 → 91,857 🟢 +13.3% 51,978 → 53,582 +3.1%
class decorator (wrapping) 47,221 → 52,587 🟢 +11.4% 36,856 → 38,013 +3.1%
identity method decorator 58,297 → 65,572 🟢 +12.5% 42,380 → 44,813 +5.7%
wrapping method decorator 48,258 → 53,402 🟢 +10.7% 36,938 → 40,326 🟢 +9.2%
stacked method decorators (x3) 33,782 → 37,960 🟢 +12.4% 27,167 → 26,394 -2.8%
identity field decorator 65,943 → 73,383 🟢 +11.3% 44,852 → 46,695 +4.1%
field initializer decorator 56,121 → 62,435 🟢 +11.3% 40,069 → 42,008 +4.8%
getter decorator (identity) 56,459 → 63,174 🟢 +11.9% 39,258 → 40,764 +3.8%
setter decorator (identity) 47,953 → 53,244 🟢 +11.0% 34,385 → 35,758 +4.0%
static method decorator 61,819 → 69,144 🟢 +11.8% 60,474 → 64,320 +6.4%
static field decorator 71,309 → 80,117 🟢 +12.4% 64,326 → 67,726 +5.3%
private method decorator 47,730 → 52,941 🟢 +10.9% 36,375 → 37,983 +4.4%
private field decorator 52,893 → 58,521 🟢 +10.6% 37,737 → 39,195 +3.9%
plain auto-accessor (no decorator) 90,233 → 99,570 🟢 +10.3% 52,540 → 54,121 +3.0%
auto-accessor with decorator 51,206 → 58,876 🟢 +15.0% 36,623 → 37,877 +3.4%
decorator writing metadata 43,587 → 46,622 +7.0% 40,299 → 42,942 +6.6%
static getter read 104,954 → 114,818 🟢 +9.4% 377,131 → 400,964 +6.3%
static getter/setter pair 79,085 → 83,699 +5.8% 202,817 → 211,540 +4.3%
inherited static getter 59,580 → 65,897 🟢 +10.6% 260,565 → 271,240 +4.1%
inherited static setter 64,155 → 70,150 🟢 +9.3% 203,690 → 211,192 +3.7%
inherited static getter with this binding 54,330 → 58,191 🟢 +7.1% 151,331 → 161,886 +7.0%
closures.js — Interp: 11 unch. · avg +3.6% · Bytecode: 🟢 3, 8 unch. · avg +6.3%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 130,369 → 136,182 +4.5% 590,667 → 631,369 +6.9%
closure over multiple variables 121,538 → 124,547 +2.5% 370,442 → 388,979 +5.0%
nested closures 127,346 → 131,754 +3.5% 536,052 → 590,320 🟢 +10.1%
function as argument 98,824 → 100,501 +1.7% 490,382 → 529,707 🟢 +8.0%
function returning function 121,963 → 126,851 +4.0% 587,326 → 614,427 +4.6%
compose two functions 73,467 → 75,382 +2.6% 345,436 → 382,427 🟢 +10.7%
fn.call 153,902 → 159,766 +3.8% 133,133 → 136,349 +2.4%
fn.apply 114,515 → 121,902 +6.4% 89,255 → 94,931 +6.4%
fn.bind 138,189 → 146,830 +6.3% 134,106 → 141,829 +5.8%
recursive sum to 50 12,325 → 12,312 -0.1% 38,328 → 40,570 +5.9%
recursive tree traversal 19,901 → 20,817 +4.6% 61,432 → 63,414 +3.2%
collections.js — Interp: 🟢 2, 10 unch. · avg +5.4% · Bytecode: 12 unch. · avg +1.5%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 7,229 → 7,548 +4.4% 5,572 → 5,710 +2.5%
has lookup (50 elements) 90,027 → 94,669 +5.2% 84,775 → 88,347 +4.2%
delete elements 47,766 → 49,765 +4.2% 36,310 → 36,713 +1.1%
forEach iteration 16,120 → 17,004 +5.5% 15,910 → 15,986 +0.5%
spread to array 32,109 → 35,703 🟢 +11.2% 145,698 → 147,283 +1.1%
deduplicate array 41,953 → 44,632 +6.4% 45,587 → 46,991 +3.1%
set 50 entries 5,302 → 5,536 +4.4% 5,833 → 5,801 -0.6%
get lookup (50 entries) 87,782 → 89,258 +1.7% 96,294 → 97,327 +1.1%
has check 131,627 → 135,748 +3.1% 151,251 → 152,821 +1.0%
delete entries 45,578 → 46,375 +1.7% 34,899 → 35,113 +0.6%
forEach iteration 16,147 → 17,277 +7.0% 15,741 → 15,946 +1.3%
keys/values/entries 8,424 → 9,272 🟢 +10.1% 21,480 → 22,028 +2.5%
destructuring.js — Interp: 🟢 14, 8 unch. · avg +8.5% · Bytecode: 🟢 11, 11 unch. · avg +12.1%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 428,852 → 441,984 +3.1% 620,525 → 809,899 🟢 +30.5%
with rest element 284,559 → 309,104 🟢 +8.6% 470,504 → 569,500 🟢 +21.0%
with defaults 436,999 → 443,736 +1.5% 651,382 → 787,714 🟢 +20.9%
skip elements 437,069 → 476,936 🟢 +9.1% 704,177 → 929,344 🟢 +32.0%
nested array destructuring 185,261 → 187,457 +1.2% 317,594 → 428,319 🟢 +34.9%
swap variables 532,178 → 548,879 +3.1% 933,607 → 1,110,432 🟢 +18.9%
simple object destructuring 303,125 → 327,710 🟢 +8.1% 506,853 → 522,684 +3.1%
with defaults 363,799 → 410,519 🟢 +12.8% 308,410 → 318,203 +3.2%
with renaming 315,020 → 358,590 🟢 +13.8% 591,400 → 600,160 +1.5%
nested object destructuring 147,684 → 161,389 🟢 +9.3% 245,480 → 249,940 +1.8%
rest properties 188,108 → 205,183 🟢 +9.1% 214,083 → 225,131 +5.2%
object parameter 92,007 → 102,643 🟢 +11.6% 178,589 → 182,555 +2.2%
array parameter 125,528 → 137,959 🟢 +9.9% 293,827 → 351,125 🟢 +19.5%
mixed destructuring in map 35,009 → 39,328 🟢 +12.3% 32,750 → 33,838 +3.3%
forEach with array destructuring 67,269 → 71,032 +5.6% 133,154 → 159,284 🟢 +19.6%
map with array destructuring 69,395 → 74,032 +6.7% 164,667 → 192,949 🟢 +17.2%
filter with array destructuring 72,975 → 77,679 +6.4% 195,478 → 230,125 🟢 +17.7%
reduce with array destructuring 77,513 → 82,677 +6.7% 183,069 → 208,767 🟢 +14.0%
map with object destructuring 79,842 → 87,586 🟢 +9.7% 74,024 → 73,445 -0.8%
map with nested destructuring 65,017 → 71,455 🟢 +9.9% 61,841 → 61,086 -1.2%
map with rest in destructuring 38,044 → 42,770 🟢 +12.4% 22,307 → 22,343 +0.2%
map with defaults in destructuring 58,220 → 67,024 🟢 +15.1% 37,195 → 37,687 +1.3%
fibonacci.js — Interp: 🟢 2, 6 unch. · avg +2.8% · Bytecode: 🟢 3, 5 unch. · avg +6.2%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 338 → 339 +0.3% 1,099 → 1,150 +4.6%
recursive fib(20) 30 → 30 +0.0% 96 → 104 🟢 +7.8%
recursive fib(15) typed 337 → 338 +0.2% 1,386 → 1,458 +5.2%
recursive fib(20) typed 31 → 30 -2.2% 118 → 132 🟢 +11.4%
iterative fib(20) via reduce 12,287 → 12,792 +4.1% 8,256 → 8,940 🟢 +8.3%
iterator fib(20) 9,464 → 9,968 +5.3% 13,731 → 14,339 +4.4%
iterator fib(20) via Iterator.from + take 14,711 → 15,842 🟢 +7.7% 15,504 → 16,413 +5.9%
iterator fib(20) last value via reduce 11,291 → 12,101 🟢 +7.2% 12,005 → 12,275 +2.2%
for-of.js — Interp: 7 unch. · avg +0.8% · Bytecode: 🟢 1, 6 unch. · avg +5.2%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 47,432 → 48,242 +1.7% 147,145 → 149,769 +1.8%
for...of with 100-element array 5,562 → 5,521 -0.7% 20,920 → 21,445 +2.5%
for...of with string (10 chars) 35,377 → 35,856 +1.4% 114,503 → 121,028 +5.7%
for...of with Set (10 elements) 48,112 → 48,484 +0.8% 156,352 → 157,142 +0.5%
for...of with Map entries (10 entries) 30,920 → 31,144 +0.7% 44,393 → 50,911 🟢 +14.7%
for...of with destructuring 40,973 → 41,741 +1.9% 68,915 → 73,192 +6.2%
for-await-of with sync array 46,080 → 46,111 +0.1% 126,144 → 132,896 +5.4%
iterators.js — Interp: 🟢 2, 18 unch. · avg +2.7% · Bytecode: 🟢 5, 15 unch. · avg +3.7%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 14,800 → 15,849 🟢 +7.1% 15,815 → 17,003 🟢 +7.5%
Iterator.from({next}).toArray() — 50 elements 6,413 → 6,909 🟢 +7.7% 6,883 → 7,313 +6.2%
spread pre-wrapped iterator — 20 elements 11,503 → 11,827 +2.8% 15,277 → 15,252 -0.2%
Iterator.from({next}).forEach — 50 elements 4,656 → 4,846 +4.1% 4,856 → 5,296 🟢 +9.1%
Iterator.from({next}).reduce — 50 elements 4,680 → 4,844 +3.5% 4,804 → 4,987 +3.8%
wrap array iterator 177,929 → 184,921 +3.9% 102,623 → 109,680 +6.9%
wrap plain {next()} object 10,396 → 11,017 +6.0% 10,875 → 11,232 +3.3%
map + toArray (50 elements) 4,595 → 4,913 +6.9% 5,297 → 5,228 -1.3%
filter + toArray (50 elements) 4,557 → 4,701 +3.2% 5,418 → 5,134 -5.2%
take(10) + toArray (50 element source) 26,590 → 28,256 +6.3% 27,041 → 28,478 +5.3%
drop(40) + toArray (50 element source) 6,483 → 6,819 +5.2% 7,694 → 8,251 🟢 +7.2%
chained map + filter + take (100 element source) 8,682 → 8,784 +1.2% 9,161 → 9,710 +6.0%
some + every (50 elements) 2,703 → 2,779 +2.8% 3,031 → 3,253 🟢 +7.3%
find (50 elements) 5,939 → 6,007 +1.1% 6,943 → 7,118 +2.5%
array.values().map().filter().toArray() 9,420 → 9,341 -0.8% 9,315 → 9,396 +0.9%
array.values().take(5).toArray() 220,746 → 217,233 -1.6% 150,361 → 156,543 +4.1%
array.values().drop(45).toArray() 202,687 → 206,606 +1.9% 142,519 → 138,990 -2.5%
map.entries() chained helpers 11,122 → 11,090 -0.3% 4,666 → 5,232 🟢 +12.1%
set.values() chained helpers 19,224 → 19,082 -0.7% 20,284 → 19,991 -1.4%
string iterator map + toArray 14,579 → 13,751 -5.7% 20,568 → 21,147 +2.8%
json.js — Interp: 🟢 1, 19 unch. · avg +2.9% · Bytecode: 20 unch. · avg +2.4%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 171,605 → 173,420 +1.1% 135,986 → 138,140 +1.6%
parse nested object 108,130 → 108,941 +0.8% 87,164 → 88,698 +1.8%
parse array of objects 57,542 → 56,815 -1.3% 50,470 → 50,262 -0.4%
parse large flat object 49,527 → 51,793 +4.6% 45,849 → 45,641 -0.5%
parse mixed types 71,774 → 74,310 +3.5% 64,107 → 67,263 +4.9%
stringify simple object 198,522 → 204,565 +3.0% 159,420 → 165,937 +4.1%
stringify nested object 113,614 → 113,840 +0.2% 87,606 → 92,523 +5.6%
stringify array of objects 62,497 → 61,592 -1.4% 51,961 → 54,075 +4.1%
stringify mixed types 87,479 → 88,883 +1.6% 72,946 → 77,331 +6.0%
reviver doubles numbers 45,719 → 47,985 +5.0% 45,151 → 45,841 +1.5%
reviver filters properties 39,219 → 41,995 🟢 +7.1% 45,554 → 46,446 +2.0%
reviver on nested object 51,992 → 54,089 +4.0% 50,921 → 52,472 +3.0%
reviver on array 29,890 → 31,353 +4.9% 28,978 → 28,771 -0.7%
replacer function doubles numbers 49,202 → 51,408 +4.5% 51,629 → 52,620 +1.9%
replacer function excludes properties 61,708 → 63,765 +3.3% 60,584 → 61,713 +1.9%
array replacer (allowlist) 117,514 → 118,895 +1.2% 97,110 → 101,706 +4.7%
stringify with 2-space indent 98,737 → 101,663 +3.0% 79,555 → 80,158 +0.8%
stringify with tab indent 98,550 → 102,119 +3.6% 79,748 → 81,924 +2.7%
parse then stringify 53,911 → 56,887 +5.5% 51,091 → 51,888 +1.6%
stringify then parse 32,479 → 33,965 +4.6% 30,694 → 30,966 +0.9%
jsx.jsx — Interp: 21 unch. · avg +2.8% · Bytecode: 21 unch. · avg +2.8%
Benchmark Interpreted Δ Bytecode Δ
simple element 208,140 → 216,514 +4.0% 613,210 → 626,176 +2.1%
self-closing element 219,486 → 225,865 +2.9% 648,972 → 666,613 +2.7%
element with string attribute 174,756 → 184,262 +5.4% 446,737 → 464,082 +3.9%
element with multiple attributes 152,012 → 153,911 +1.2% 395,627 → 398,451 +0.7%
element with expression attribute 167,104 → 175,644 +5.1% 437,654 → 446,552 +2.0%
text child 213,562 → 216,071 +1.2% 619,712 → 623,139 +0.6%
expression child 209,388 → 209,760 +0.2% 597,618 → 614,614 +2.8%
mixed text and expression 195,746 → 196,140 +0.2% 543,127 → 557,347 +2.6%
nested elements (3 levels) 78,575 → 82,797 +5.4% 242,073 → 250,737 +3.6%
sibling children 58,458 → 61,803 +5.7% 182,248 → 186,282 +2.2%
component element 148,295 → 154,639 +4.3% 418,623 → 443,935 +6.0%
component with children 91,634 → 95,842 +4.6% 269,146 → 281,385 +4.5%
dotted component 123,403 → 129,788 +5.2% 308,720 → 324,429 +5.1%
empty fragment 223,977 → 222,511 -0.7% 635,725 → 662,460 +4.2%
fragment with children 58,359 → 60,774 +4.1% 178,651 → 183,722 +2.8%
spread attributes 112,482 → 112,758 +0.2% 103,884 → 106,546 +2.6%
spread with overrides 98,708 → 99,097 +0.4% 76,900 → 78,772 +2.4%
shorthand props 160,756 → 164,524 +2.3% 409,225 → 420,191 +2.7%
nav bar structure 27,251 → 28,160 +3.3% 76,162 → 77,943 +2.3%
card component tree 32,761 → 32,495 -0.8% 83,760 → 86,074 +2.8%
10 list items via Array.from 14,633 → 15,262 +4.3% 22,991 → 22,929 -0.3%
numbers.js — Interp: 🟢 1, 10 unch. · avg +2.7% · Bytecode: 🟢 4, 7 unch. · avg +5.5%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 572,098 → 543,622 -5.0% 1,551,168 → 1,565,663 +0.9%
floating point arithmetic 581,760 → 613,350 +5.4% 1,704,377 → 1,705,066 +0.0%
number coercion 188,790 → 195,496 +3.6% 130,958 → 132,646 +1.3%
toFixed 103,966 → 108,805 +4.7% 212,084 → 219,217 +3.4%
toString 161,763 → 163,004 +0.8% 691,907 → 746,571 🟢 +7.9%
valueOf 232,794 → 244,520 +5.0% 922,329 → 1,067,235 🟢 +15.7%
toPrecision 148,448 → 145,984 -1.7% 402,664 → 419,367 +4.1%
Number.isNaN 305,747 → 319,096 +4.4% 167,637 → 175,702 +4.8%
Number.isFinite 300,438 → 305,897 +1.8% 164,320 → 172,490 +5.0%
Number.isInteger 290,481 → 311,563 🟢 +7.3% 173,849 → 187,915 🟢 +8.1%
Number.parseInt and parseFloat 246,387 → 255,356 +3.6% 144,373 → 157,403 🟢 +9.0%
objects.js — Interp: 7 unch. · avg -2.0% · Bytecode: 🟢 2, 5 unch. · avg +6.3%
Benchmark Interpreted Δ Bytecode Δ
create simple object 475,636 → 453,660 -4.6% 826,631 → 866,863 +4.9%
create nested object 218,984 → 223,459 +2.0% 361,623 → 383,939 +6.2%
create 50 objects via Array.from 9,424 → 9,487 +0.7% 8,358 → 8,292 -0.8%
property read 631,573 → 604,225 -4.3% 646,798 → 740,219 🟢 +14.4%
Object.keys 299,899 → 293,041 -2.3% 197,699 → 203,920 +3.1%
Object.entries 108,111 → 107,359 -0.7% 60,083 → 61,796 +2.9%
spread operator 195,104 → 186,178 -4.6% 175,599 → 198,561 🟢 +13.1%
promises.js — Interp: 🔴 2, 10 unch. · avg -3.9% · Bytecode: 🟢 2, 🔴 1, 9 unch. · avg +0.7%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 572,558 → 572,880 +0.1% 337,322 → 350,556 +3.9%
new Promise(resolve => resolve(value)) 198,353 → 201,852 +1.8% 155,103 → 157,175 +1.3%
Promise.reject(reason) 554,217 → 538,808 -2.8% 334,101 → 340,277 +1.8%
resolve + then (1 handler) 174,111 → 170,718 -1.9% 147,480 → 158,510 🟢 +7.5%
resolve + then chain (3 deep) 68,657 → 62,233 🔴 -9.4% 66,475 → 62,079 -6.6%
resolve + then chain (10 deep) 21,947 → 19,314 🔴 -12.0% 23,314 → 21,612 🔴 -7.3%
reject + catch + then 99,537 → 95,216 -4.3% 86,362 → 95,299 🟢 +10.3%
resolve + finally + then 85,454 → 81,311 -4.8% 76,512 → 73,869 -3.5%
Promise.all (5 resolved) 32,761 → 31,164 -4.9% 26,910 → 25,804 -4.1%
Promise.race (5 resolved) 34,328 → 33,131 -3.5% 27,950 → 27,475 -1.7%
Promise.allSettled (5 mixed) 27,454 → 26,531 -3.4% 21,273 → 22,556 +6.0%
Promise.any (5 mixed) 31,475 → 30,793 -2.2% 25,947 → 25,997 +0.2%
strings.js — Interp: 🔴 1, 10 unch. · avg -3.4% · Bytecode: 🟢 5, 6 unch. · avg +6.6%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 419,347 → 410,314 -2.2% 431,509 → 429,093 -0.6%
template literal 699,166 → 598,694 🔴 -14.4% 630,905 → 649,136 +2.9%
string repeat 419,083 → 417,903 -0.3% 925,354 → 1,021,021 🟢 +10.3%
split and join 138,048 → 131,933 -4.4% 312,660 → 315,872 +1.0%
indexOf and includes 171,761 → 171,040 -0.4% 607,863 → 648,516 +6.7%
toUpperCase and toLowerCase 261,106 → 251,618 -3.6% 629,490 → 689,471 🟢 +9.5%
slice and substring 164,329 → 157,630 -4.1% 660,254 → 732,949 🟢 +11.0%
trim operations 193,220 → 190,543 -1.4% 739,605 → 816,828 🟢 +10.4%
replace and replaceAll 212,068 → 203,020 -4.3% 671,880 → 705,192 +5.0%
startsWith and endsWith 139,914 → 139,580 -0.2% 503,897 → 555,097 🟢 +10.2%
padStart and padEnd 202,793 → 198,478 -2.1% 581,746 → 615,122 +5.7%
typed-arrays.js — Interp: 🟢 7, 15 unch. · avg +3.2% · Bytecode: 🟢 1, 21 unch. · avg +1.6%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 334,573 → 315,273 -5.8% 125,768 → 128,188 +1.9%
new Int32Array(100) 306,590 → 301,525 -1.7% 121,655 → 123,750 +1.7%
new Int32Array(1000) 173,236 → 195,189 🟢 +12.7% 52,307 → 61,572 🟢 +17.7%
new Float64Array(100) 272,390 → 274,416 +0.7% 102,507 → 106,751 +4.1%
Int32Array.from([...]) 183,138 → 197,505 🟢 +7.8% 153,812 → 151,964 -1.2%
Int32Array.of(1, 2, 3, 4, 5) 320,102 → 315,935 -1.3% 242,109 → 233,711 -3.5%
sequential write 100 elements 3,657 → 3,641 -0.4% 12,929 → 13,803 +6.8%
sequential read 100 elements 3,792 → 3,715 -2.0% 10,041 → 10,595 +5.5%
Float64Array write 100 elements 3,400 → 3,489 +2.6% 12,465 → 12,998 +4.3%
fill(42) 45,916 → 57,032 🟢 +24.2% 43,915 → 43,580 -0.8%
slice() 207,134 → 221,821 🟢 +7.1% 181,441 → 185,953 +2.5%
map(x => x * 2) 8,444 → 8,404 -0.5% 7,710 → 7,186 -6.8%
filter(x => x > 50) 8,592 → 8,510 -1.0% 7,895 → 7,558 -4.3%
reduce (sum) 8,054 → 8,016 -0.5% 6,656 → 6,566 -1.4%
sort() 177,016 → 206,604 🟢 +16.7% 148,590 → 154,319 +3.9%
indexOf() 461,971 → 503,561 🟢 +9.0% 348,035 → 359,390 +3.3%
reverse() 338,894 → 369,842 🟢 +9.1% 263,421 → 271,336 +3.0%
create view over existing buffer 413,022 → 403,430 -2.3% 133,493 → 137,789 +3.2%
subarray() 468,241 → 462,053 -1.3% 342,683 → 349,703 +2.0%
set() from array 604,335 → 629,768 +4.2% 255,037 → 237,765 -6.8%
for-of loop 5,214 → 4,983 -4.4% 18,335 → 17,787 -3.0%
spread into array 18,421 → 17,923 -2.7% 53,001 → 53,981 +1.8%

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

@frostney frostney merged commit b5d8d1e into main Mar 13, 2026
9 checks passed
@frostney frostney deleted the opt/dest-reg-propagation branch March 13, 2026 09:51
@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