Skip to content

Bypass interpreter bridge for blueprint-backed class construction#87

Merged
frostney merged 4 commits into
mainfrom
opt/native-class-construct
Mar 17, 2026
Merged

Bypass interpreter bridge for blueprint-backed class construction#87
frostney merged 4 commits into
mainfrom
opt/native-class-construct

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Mar 12, 2026

Summary

  • Move the native blueprint instantiation path before PushVMFramesToGoccia so standalone classes and blueprint-inherited classes never cross the interpreter bridge, eliminating O(VM_depth) frame iteration and the try..except wrapper per construction
  • Cache delegate chain setup so subsequent constructions of the same class skip the O(depth) no-op traversal
  • Hoist SetLength(VMArgs, 1) outside the field initializer loop to eliminate redundant dynamic array allocations

Test plan

  • All 3,422 interpreter tests pass
  • All 3,463 bytecode tests pass
  • CI benchmark comparison for class-heavy workloads

Made with Cursor

Summary by CodeRabbit

  • Refactor
    • Introduced dual-path blueprint handling to support cached and on-demand construction flows, improving consistency of instance creation.
    • Deferred bridge creation and strengthened delegate/prototype initialization so delegation chains are reliably established during construction.
    • Guarded and reordered field initialization and allocation to avoid redundant allocations and ensure field inits run in the correct order before constructor invocation.

Move the native blueprint instantiation path before PushVMFramesToGoccia
so standalone classes and blueprint-inherited classes never cross the
interpreter bridge. Three optimizations:

1. Eliminate bridge overhead: the common blueprint path exits early,
   skipping PushVMFramesToGoccia/PopVMFramesFromGoccia (O(VM_depth)
   per construction) and the try..except TGocciaThrowValue wrapper.

2. Delegate chain caching: guard the chain walk with
   `if not Assigned(Bp.Prototype.Delegate)` so subsequent constructions
   of the same class skip the O(depth) no-op traversal.

3. VMArgs reuse: move SetLength outside the field initializer loop to
   eliminate redundant dynamic array allocations per iteration.

Only blueprint classes without a built-in super (FBlueprintSuperValues)
take the fast path; built-in subclasses still use the full bridge.

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

coderabbitai Bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

Handle TSouffleBlueprint constructors early with a dual-path: cached-bridge lookup versus on-demand bridge creation. Allocate the instance record earlier, set Prototype/Method Delegates during prototype walking, guard and run field initializers in reverse, and separate blueprint vs non-blueprint constructor flows.

Changes

Cohort / File(s) Summary
Blueprint Construction Refactor
units/Goccia.Runtime.Operations.pas
Reworked Construct to detect TSouffleBlueprint early and split into cached-bridge vs on-demand bridge paths; allocate Rec earlier (once) for blueprint path; ensure Prototype.Delegate and Methods.Delegate are established during proto walk; guard FieldInit (only if >0) and run in reverse; defer bridge creation when using delegation-walker; update bridge-cache on creation.

Sequence Diagram(s)

sequenceDiagram
participant Caller
participant Runtime
participant Blueprint
participant BridgeCache
participant Bridge
participant VM

Note right of Runtime: Construct start
Caller->>Runtime: Construct(...)
Runtime->>Blueprint: is TSouffleBlueprint?
alt Blueprint path
    Runtime->>Blueprint: ConvertBlueprintToClassValue(...)
    Runtime->>BridgeCache: lookup bridge for blueprint
    alt cached
        BridgeCache-->>Runtime: cached bridge
        Runtime->>Bridge: invoke cached bridge with Rec
    else not cached
        Runtime->>Bridge: create bridge from blueprint
        Bridge-->>BridgeCache: write cache entry
        Runtime->>Bridge: invoke bridge with Rec
    end
else Non-blueprint path
    Runtime->>VM: Push frames / prepare context
    Runtime->>Bridge: build or locate bridge
    Runtime->>Bridge: invoke bridge with Rec
end
Bridge-->>Runtime: constructor result
Runtime-->>Caller: return instance
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I hopped through blueprints, bridges in tow,

Delegates sorted in a tidy row,
Fields set backward, one-by-one,
Cache or craft — constructors run,
A rabbit cheers: the runtime's aglow!

🚥 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 'Bypass interpreter bridge for blueprint-backed class construction' accurately captures the main optimization objective of the PR—avoiding the interpreter bridge for blueprint-based class instantiation.
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
  • Commit unit tests in branch opt/native-class-construct
📝 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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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

3342-3342: Use PROP_CONSTRUCTOR instead of a string literal.

This keeps constructor lookups aligned with the rest of the runtime constants.

♻️ Suggested change
-        if WalkBp.Methods.Get('constructor', CtorMethod) then
+        if WalkBp.Methods.Get(PROP_CONSTRUCTOR, CtorMethod) then

As per coding guidelines, "Use runtime constant units 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, Goccia.Constants for literal value strings"

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

In `@units/Goccia.Runtime.Operations.pas` at line 3342, Replace the hardcoded
string 'constructor' with the runtime constant PROP_CONSTRUCTOR so constructor
lookups use the shared constant; specifically update the call to
WalkBp.Methods.Get('constructor', CtorMethod) in
units/Goccia.Runtime.Operations.pas (the WalkBp.Methods.Get invocation that
fills CtorMethod) to use PROP_CONSTRUCTOR from the constants unit
(Goccia.Constants.PropertyNames or the module where PROP_CONSTRUCTOR is
defined).
🤖 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 3287-3288: The branch currently uses Assigned(Bp.SuperBlueprint)
which incorrectly treats derived blueprints as roots; change the gate to test
for the root (i.e., not Assigned(Bp.SuperBlueprint)) so the native fast-path
only runs for root blueprints and not for leaves. Update the condition "if
Assigned(Bp.SuperBlueprint) or not FBlueprintSuperValues.ContainsKey(Bp) then"
to "if not Assigned(Bp.SuperBlueprint) or not
FBlueprintSuperValues.ContainsKey(Bp) then" (and make the same change in the
other occurrences around the blocks referencing Bp, FBlueprintSuperValues,
__fields__, and delegate wiring to FVM.RecordDelegate at the noted ranges).
- Around line 3373-3377: The FBlueprintBridgeCache is being reused for two
different bridge conversions (BlueprintToClassValue vs
ConvertBlueprintToClassValue), causing lost behavior when
ConvertBlueprintToClassValue is required; in the branch that currently reads
FBlueprintBridgeCache for Bp (where CachedBridge is assigned for use by
ConvertBlueprintToClassValue), change the logic to avoid reusing entries
produced by the other converter — either bypass the cache and always call
ConvertBlueprintToClassValue for this path, or key into a separate cache (e.g.,
a new FBlueprintClassBridgeCache) so entries created by
UnwrapToGocciaValue/BlueprintToClassValue are not returned here; update the code
paths referencing UnwrapToGocciaValue, ConvertBlueprintToClassValue,
BlueprintToClassValue, FBlueprintBridgeCache, and CachedBridge accordingly so
the bridge used here always includes getters/setters, static fields, private
members and field initializers.

---

Nitpick comments:
In `@units/Goccia.Runtime.Operations.pas`:
- Line 3342: Replace the hardcoded string 'constructor' with the runtime
constant PROP_CONSTRUCTOR so constructor lookups use the shared constant;
specifically update the call to WalkBp.Methods.Get('constructor', CtorMethod) in
units/Goccia.Runtime.Operations.pas (the WalkBp.Methods.Get invocation that
fills CtorMethod) to use PROP_CONSTRUCTOR from the constants unit
(Goccia.Constants.PropertyNames or the module where PROP_CONSTRUCTOR is
defined).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c53d0e42-ca2a-49e0-9ec1-b22fad110d7b

📥 Commits

Reviewing files that changed from the base of the PR and between 6feb97a and cb97506.

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

Comment thread units/Goccia.Runtime.Operations.pas Outdated
Comment thread units/Goccia.Runtime.Operations.pas
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 12, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 3497 3497
Tests Passed 3456 ✅ 3497 ✅
Tests Skipped 41 0
Tests Execution 151.5ms 159.0ms
Tests Engine 309.3ms 669.3ms
Benchmarks Total 254 254
Benchmarks Duration 6.74min 11.49min

Measured on ubuntu-latest x64.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 12, 2026

Benchmark Results

254 benchmarks

Interpreted: 🟢 3 improved · 🔴 2 regressed · 249 unchanged · avg -1.5%
Bytecode: 🟢 9 improved · 🔴 15 regressed · 230 unchanged · avg -0.2%

arraybuffer.js — Interp: 14 unch. · avg -0.5% · Bytecode: 14 unch. · avg -0.6%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 447,306 → 443,982 -0.7% 147,311 → 137,205 -6.9%
create ArrayBuffer(64) 438,474 → 437,448 -0.2% 144,538 → 142,585 -1.4%
create ArrayBuffer(1024) 332,856 → 331,325 -0.5% 131,065 → 127,770 -2.5%
create ArrayBuffer(8192) 142,324 → 136,290 -4.2% 81,299 → 80,506 -1.0%
slice full buffer (64 bytes) 527,266 → 522,705 -0.9% 391,336 → 383,665 -2.0%
slice half buffer (512 of 1024 bytes) 445,849 → 439,641 -1.4% 342,123 → 340,064 -0.6%
slice with negative indices 454,883 → 448,048 -1.5% 361,501 → 367,377 +1.6%
slice empty range 513,460 → 510,320 -0.6% 386,354 → 386,114 -0.1%
byteLength access 1,572,618 → 1,574,259 +0.1% 1,257,193 → 1,319,679 +5.0%
Symbol.toStringTag access 1,175,305 → 1,178,174 +0.2% 621,350 → 640,038 +3.0%
ArrayBuffer.isView 749,533 → 791,091 +5.5% 503,196 → 497,593 -1.1%
clone ArrayBuffer(64) 389,251 → 388,538 -0.2% 332,320 → 332,152 -0.1%
clone ArrayBuffer(1024) 305,496 → 301,421 -1.3% 253,827 → 254,113 +0.1%
clone ArrayBuffer inside object 269,889 → 266,383 -1.3% 158,202 → 155,127 -1.9%
arrays.js — Interp: 19 unch. · avg -1.4% · Bytecode: 🟢 1, 18 unch. · avg +1.5%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 14,023 → 13,906 -0.8% 14,922 → 15,294 +2.5%
Array.from 10 elements 229,745 → 229,583 -0.1% 160,993 → 159,218 -1.1%
Array.of 10 elements 318,933 → 312,949 -1.9% 230,520 → 228,407 -0.9%
spread into new array 345,772 → 346,630 +0.2% 911,425 → 901,807 -1.1%
map over 50 elements 28,307 → 27,413 -3.2% 24,479 → 26,230 🟢 +7.2%
filter over 50 elements 23,959 → 23,480 -2.0% 25,118 → 24,317 -3.2%
reduce sum 50 elements 27,608 → 26,625 -3.6% 21,730 → 21,834 +0.5%
forEach over 50 elements 23,229 → 23,485 +1.1% 27,233 → 27,589 +1.3%
find in 50 elements 36,052 → 35,510 -1.5% 32,704 → 32,978 +0.8%
sort 20 elements 12,183 → 12,140 -0.4% 12,848 → 13,278 +3.3%
flat nested array 119,485 → 119,455 -0.0% 481,835 → 488,192 +1.3%
flatMap 74,373 → 73,936 -0.6% 316,753 → 323,033 +2.0%
map inside map (5x5) 21,930 → 21,241 -3.1% 106,299 → 107,799 +1.4%
filter inside map (5x10) 16,200 → 15,564 -3.9% 14,990 → 14,957 -0.2%
reduce inside map (5x10) 19,798 → 19,074 -3.7% 14,976 → 15,447 +3.1%
forEach inside forEach (5x10) 16,763 → 16,952 +1.1% 16,888 → 17,594 +4.2%
find inside some (10x10) 14,029 → 13,851 -1.3% 11,959 → 12,181 +1.9%
map+filter chain nested (5x20) 5,532 → 5,304 -4.1% 4,937 → 5,103 +3.4%
reduce flatten (10x5) 38,781 → 39,034 +0.7% 6,332 → 6,455 +1.9%
async-await.js — Interp: 6 unch. · avg -1.3% · Bytecode: 6 unch. · avg -1.6%
Benchmark Interpreted Δ Bytecode Δ
single await 385,555 → 380,467 -1.3% 289,542 → 281,507 -2.8%
multiple awaits 170,985 → 170,252 -0.4% 121,319 → 122,581 +1.0%
await non-Promise value 885,738 → 870,190 -1.8% 1,046,947 → 1,011,408 -3.4%
await with try/catch 378,032 → 370,394 -2.0% 280,169 → 281,478 +0.5%
await Promise.all 52,819 → 52,745 -0.1% 43,593 → 42,024 -3.6%
nested async function call 192,811 → 188,318 -2.3% 219,213 → 216,837 -1.1%
classes.js — Interp: 31 unch. · avg -1.1% · Bytecode: 🔴 1, 30 unch. · avg +0.0%
Benchmark Interpreted Δ Bytecode Δ
simple class new 116,942 → 116,617 -0.3% 397,491 → 414,321 +4.2%
class with defaults 94,296 → 93,861 -0.5% 279,469 → 291,907 +4.5%
50 instances via Array.from 5,551 → 5,515 -0.7% 6,874 → 7,167 +4.3%
instance method call 59,842 → 59,701 -0.2% 184,815 → 191,728 +3.7%
static method call 92,293 → 92,225 -0.1% 421,755 → 441,358 +4.6%
single-level inheritance 46,621 → 46,322 -0.6% 181,087 → 185,903 +2.7%
two-level inheritance 39,095 → 39,093 -0.0% 153,416 → 158,051 +3.0%
private field access 58,898 → 58,282 -1.0% 196,849 → 204,636 +4.0%
private methods 64,377 → 63,673 -1.1% 264,030 → 275,888 +4.5%
getter/setter access 66,679 → 66,157 -0.8% 195,356 → 198,238 +1.5%
class decorator (identity) 82,709 → 81,489 -1.5% 54,737 → 51,860 -5.3%
class decorator (wrapping) 47,503 → 46,955 -1.2% 38,388 → 38,310 -0.2%
identity method decorator 58,802 → 58,066 -1.3% 46,150 → 45,373 -1.7%
wrapping method decorator 48,462 → 47,761 -1.4% 38,803 → 41,096 +5.9%
stacked method decorators (x3) 34,744 → 34,349 -1.1% 28,794 → 25,838 🔴 -10.3%
identity field decorator 66,917 → 65,235 -2.5% 47,230 → 46,458 -1.6%
field initializer decorator 56,435 → 55,913 -0.9% 42,446 → 41,604 -2.0%
getter decorator (identity) 57,201 → 55,380 -3.2% 41,727 → 40,974 -1.8%
setter decorator (identity) 48,242 → 47,581 -1.4% 36,189 → 35,967 -0.6%
static method decorator 62,847 → 62,004 -1.3% 65,636 → 64,371 -1.9%
static field decorator 72,446 → 72,081 -0.5% 68,717 → 67,120 -2.3%
private method decorator 47,974 → 47,557 -0.9% 38,541 → 38,070 -1.2%
private field decorator 54,452 → 52,169 -4.2% 39,971 → 39,021 -2.4%
plain auto-accessor (no decorator) 90,281 → 89,903 -0.4% 55,352 → 54,966 -0.7%
auto-accessor with decorator 52,590 → 52,009 -1.1% 37,968 → 37,324 -1.7%
decorator writing metadata 43,367 → 42,720 -1.5% 43,419 → 42,752 -1.5%
static getter read 105,374 → 104,581 -0.8% 453,598 → 446,249 -1.6%
static getter/setter pair 79,978 → 79,230 -0.9% 241,432 → 235,261 -2.6%
inherited static getter 58,859 → 58,916 +0.1% 306,062 → 301,374 -1.5%
inherited static setter 64,511 → 63,544 -1.5% 237,311 → 233,185 -1.7%
inherited static getter with this binding 53,996 → 53,719 -0.5% 181,360 → 181,863 +0.3%
closures.js — Interp: 11 unch. · avg -2.0% · Bytecode: 11 unch. · avg -0.7%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 132,203 → 131,557 -0.5% 798,508 → 796,288 -0.3%
closure over multiple variables 122,054 → 118,952 -2.5% 489,326 → 485,256 -0.8%
nested closures 126,264 → 126,717 +0.4% 707,950 → 707,042 -0.1%
function as argument 96,456 → 95,194 -1.3% 705,524 → 704,813 -0.1%
function returning function 120,424 → 119,413 -0.8% 760,121 → 773,129 +1.7%
compose two functions 72,835 → 72,109 -1.0% 468,619 → 474,049 +1.2%
fn.call 151,906 → 147,014 -3.2% 144,931 → 143,913 -0.7%
fn.apply 115,121 → 111,410 -3.2% 99,602 → 96,845 -2.8%
fn.bind 140,277 → 134,149 -4.4% 151,652 → 143,035 -5.7%
recursive sum to 50 11,986 → 11,669 -2.6% 53,100 → 52,439 -1.2%
recursive tree traversal 20,016 → 19,397 -3.1% 75,466 → 76,343 +1.2%
collections.js — Interp: 🟢 3, 9 unch. · avg +1.4% · Bytecode: 🟢 1, 🔴 1, 10 unch. · avg +1.1%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 7,228 → 7,419 +2.6% 5,792 → 6,029 +4.1%
has lookup (50 elements) 89,207 → 87,461 -2.0% 88,367 → 90,105 +2.0%
delete elements 47,673 → 48,797 +2.4% 36,902 → 37,290 +1.1%
forEach iteration 16,368 → 16,101 -1.6% 17,093 → 17,032 -0.4%
spread to array 33,550 → 33,158 -1.2% 153,847 → 155,977 +1.4%
deduplicate array 43,035 → 42,054 -2.3% 47,803 → 48,390 +1.2%
set 50 entries 5,267 → 5,303 +0.7% 6,117 → 6,143 +0.4%
get lookup (50 entries) 80,954 → 88,177 🟢 +8.9% 91,477 → 101,707 🟢 +11.2%
has check 122,887 → 133,292 🟢 +8.5% 151,309 → 156,941 +3.7%
delete entries 43,155 → 46,425 🟢 +7.6% 35,357 → 34,813 -1.5%
forEach iteration 16,307 → 16,246 -0.4% 17,448 → 17,069 -2.2%
keys/values/entries 9,027 → 8,488 -6.0% 22,828 → 21,063 🔴 -7.7%
destructuring.js — Interp: 22 unch. · avg -0.9% · Bytecode: 🟢 1, 🔴 2, 19 unch. · avg -0.7%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 439,362 → 421,595 -4.0% 1,076,709 → 982,360 🔴 -8.8%
with rest element 288,832 → 297,594 +3.0% 779,532 → 716,917 🔴 -8.0%
with defaults 452,776 → 426,042 -5.9% 993,040 → 974,380 -1.9%
skip elements 449,817 → 463,079 +2.9% 1,232,955 → 1,148,440 -6.9%
nested array destructuring 190,239 → 184,735 -2.9% 524,469 → 519,064 -1.0%
swap variables 544,467 → 535,993 -1.6% 1,397,374 → 1,418,860 +1.5%
simple object destructuring 306,518 → 292,695 -4.5% 623,641 → 627,390 +0.6%
with defaults 367,528 → 373,477 +1.6% 330,146 → 329,573 -0.2%
with renaming 314,400 → 324,421 +3.2% 732,212 → 738,235 +0.8%
nested object destructuring 152,868 → 153,537 +0.4% 295,149 → 292,538 -0.9%
rest properties 188,722 → 188,546 -0.1% 245,407 → 240,608 -2.0%
object parameter 92,326 → 92,369 +0.0% 207,831 → 212,390 +2.2%
array parameter 126,167 → 124,202 -1.6% 436,353 → 442,617 +1.4%
mixed destructuring in map 35,161 → 36,290 +3.2% 38,387 → 39,201 +2.1%
forEach with array destructuring 69,013 → 66,949 -3.0% 186,323 → 203,353 🟢 +9.1%
map with array destructuring 70,241 → 68,957 -1.8% 256,424 → 260,243 +1.5%
filter with array destructuring 73,112 → 71,189 -2.6% 301,203 → 306,895 +1.9%
reduce with array destructuring 79,417 → 75,453 -5.0% 287,791 → 286,679 -0.4%
map with object destructuring 80,294 → 81,074 +1.0% 80,389 → 81,596 +1.5%
map with nested destructuring 66,249 → 66,645 +0.6% 69,669 → 69,518 -0.2%
map with rest in destructuring 39,755 → 38,198 -3.9% 24,371 → 23,606 -3.1%
map with defaults in destructuring 60,029 → 60,829 +1.3% 37,692 → 36,137 -4.1%
fibonacci.js — Interp: 8 unch. · avg -1.5% · Bytecode: 8 unch. · avg -2.0%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 326 → 321 -1.2% 1,479 → 1,453 -1.7%
recursive fib(20) 29 → 29 +0.5% 134 → 132 -1.4%
recursive fib(15) typed 330 → 323 -1.9% 1,907 → 1,972 +3.4%
recursive fib(20) typed 29 → 29 +1.3% 174 → 178 +2.7%
iterative fib(20) via reduce 12,648 → 12,239 -3.2% 9,070 → 8,660 -4.5%
iterator fib(20) 9,763 → 9,654 -1.1% 15,926 → 15,101 -5.2%
iterator fib(20) via Iterator.from + take 15,084 → 14,827 -1.7% 17,547 → 16,420 -6.4%
iterator fib(20) last value via reduce 11,659 → 11,149 -4.4% 13,020 → 12,657 -2.8%
for-of.js — Interp: 7 unch. · avg -1.9% · Bytecode: 🔴 1, 6 unch. · avg -4.2%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 49,232 → 48,393 -1.7% 165,514 → 164,614 -0.5%
for...of with 100-element array 5,660 → 5,526 -2.4% 24,940 → 24,716 -0.9%
for...of with string (10 chars) 35,793 → 35,366 -1.2% 131,257 → 132,090 +0.6%
for...of with Set (10 elements) 49,787 → 48,771 -2.0% 174,658 → 177,302 +1.5%
for...of with Map entries (10 entries) 31,614 → 31,070 -1.7% 56,530 → 45,903 🔴 -18.8%
for...of with destructuring 42,642 → 41,490 -2.7% 82,965 → 79,164 -4.6%
for-await-of with sync array 47,168 → 46,530 -1.4% 149,255 → 139,327 -6.7%
iterators.js — Interp: 20 unch. · avg -2.1% · Bytecode: 🔴 3, 17 unch. · avg -3.8%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 15,045 → 14,858 -1.2% 18,613 → 16,440 🔴 -11.7%
Iterator.from({next}).toArray() — 50 elements 6,585 → 6,563 -0.3% 8,432 → 7,721 🔴 -8.4%
spread pre-wrapped iterator — 20 elements 11,823 → 11,682 -1.2% 16,886 → 16,485 -2.4%
Iterator.from({next}).forEach — 50 elements 4,688 → 4,524 -3.5% 5,974 → 5,578 -6.6%
Iterator.from({next}).reduce — 50 elements 4,740 → 4,586 -3.3% 5,622 → 5,265 -6.3%
wrap array iterator 180,285 → 178,322 -1.1% 110,385 → 106,882 -3.2%
wrap plain {next()} object 10,586 → 10,410 -1.7% 12,093 → 11,637 -3.8%
map + toArray (50 elements) 4,831 → 4,654 -3.7% 5,526 → 5,494 -0.6%
filter + toArray (50 elements) 4,687 → 4,561 -2.7% 5,550 → 5,224 -5.9%
take(10) + toArray (50 element source) 27,737 → 27,056 -2.5% 29,407 → 27,684 -5.9%
drop(40) + toArray (50 element source) 6,613 → 6,538 -1.1% 9,052 → 8,314 🔴 -8.1%
chained map + filter + take (100 element source) 8,748 → 8,489 -3.0% 10,063 → 9,582 -4.8%
some + every (50 elements) 2,727 → 2,668 -2.2% 3,537 → 3,360 -5.0%
find (50 elements) 5,951 → 5,819 -2.2% 7,495 → 7,441 -0.7%
array.values().map().filter().toArray() 9,264 → 8,827 -4.7% 10,249 → 10,394 +1.4%
array.values().take(5).toArray() 218,477 → 218,568 +0.0% 149,742 → 147,177 -1.7%
array.values().drop(45).toArray() 204,508 → 208,183 +1.8% 146,228 → 143,747 -1.7%
map.entries() chained helpers 11,271 → 10,890 -3.4% 5,554 → 5,510 -0.8%
set.values() chained helpers 19,188 → 18,540 -3.4% 22,202 → 22,200 -0.0%
string iterator map + toArray 14,674 → 14,280 -2.7% 22,770 → 22,645 -0.6%
json.js — Interp: 20 unch. · avg -2.8% · Bytecode: 🟢 1, 🔴 1, 18 unch. · avg -0.3%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 172,764 → 171,131 -0.9% 138,358 → 144,245 +4.3%
parse nested object 109,431 → 106,263 -2.9% 93,608 → 95,650 +2.2%
parse array of objects 57,580 → 56,114 -2.5% 50,583 → 48,727 -3.7%
parse large flat object 51,046 → 48,946 -4.1% 45,236 → 45,168 -0.2%
parse mixed types 73,062 → 70,397 -3.6% 65,443 → 60,695 🔴 -7.3%
stringify simple object 201,662 → 194,291 -3.7% 150,355 → 149,696 -0.4%
stringify nested object 113,996 → 110,977 -2.6% 86,398 → 85,189 -1.4%
stringify array of objects 65,300 → 62,174 -4.8% 48,643 → 51,869 +6.6%
stringify mixed types 87,525 → 87,544 +0.0% 71,001 → 71,093 +0.1%
reviver doubles numbers 45,946 → 44,720 -2.7% 41,156 → 45,751 🟢 +11.2%
reviver filters properties 39,445 → 38,654 -2.0% 46,511 → 46,717 +0.4%
reviver on nested object 52,374 → 51,193 -2.3% 52,580 → 51,826 -1.4%
reviver on array 29,617 → 29,361 -0.9% 29,986 → 29,958 -0.1%
replacer function doubles numbers 49,078 → 48,279 -1.6% 51,299 → 52,542 +2.4%
replacer function excludes properties 62,299 → 61,469 -1.3% 63,032 → 61,441 -2.5%
array replacer (allowlist) 118,165 → 112,755 -4.6% 98,854 → 96,744 -2.1%
stringify with 2-space indent 100,950 → 98,445 -2.5% 80,899 → 78,367 -3.1%
stringify with tab indent 102,163 → 97,215 -4.8% 82,317 → 78,629 -4.5%
parse then stringify 55,977 → 53,420 -4.6% 51,693 → 50,128 -3.0%
stringify then parse 33,410 → 32,303 -3.3% 31,277 → 30,106 -3.7%
jsx.jsx — Interp: 21 unch. · avg -2.8% · Bytecode: 21 unch. · avg +0.5%
Benchmark Interpreted Δ Bytecode Δ
simple element 204,451 → 202,300 -1.1% 790,592 → 800,067 +1.2%
self-closing element 216,509 → 209,427 -3.3% 805,492 → 820,255 +1.8%
element with string attribute 175,040 → 171,640 -1.9% 567,486 → 569,428 +0.3%
element with multiple attributes 151,806 → 145,326 -4.3% 478,647 → 483,586 +1.0%
element with expression attribute 166,544 → 162,834 -2.2% 555,915 → 553,142 -0.5%
text child 208,803 → 200,386 -4.0% 793,208 → 794,060 +0.1%
expression child 204,794 → 196,840 -3.9% 784,096 → 792,923 +1.1%
mixed text and expression 190,859 → 192,227 +0.7% 707,515 → 718,556 +1.6%
nested elements (3 levels) 76,108 → 75,075 -1.4% 314,636 → 320,397 +1.8%
sibling children 57,321 → 56,663 -1.1% 229,447 → 234,764 +2.3%
component element 146,986 → 145,393 -1.1% 540,796 → 550,654 +1.8%
component with children 90,240 → 89,173 -1.2% 343,791 → 356,715 +3.8%
dotted component 123,482 → 121,243 -1.8% 408,237 → 404,140 -1.0%
empty fragment 218,679 → 210,597 -3.7% 808,046 → 826,951 +2.3%
fragment with children 56,974 → 56,118 -1.5% 233,598 → 237,290 +1.6%
spread attributes 113,691 → 106,316 -6.5% 116,270 → 113,421 -2.4%
spread with overrides 98,247 → 93,348 -5.0% 86,320 → 83,226 -3.6%
shorthand props 160,162 → 151,518 -5.4% 530,137 → 521,568 -1.6%
nav bar structure 26,987 → 26,637 -1.3% 100,155 → 101,938 +1.8%
card component tree 33,281 → 30,955 -7.0% 110,630 → 107,167 -3.1%
10 list items via Array.from 14,621 → 14,415 -1.4% 25,714 → 25,533 -0.7%
numbers.js — Interp: 🔴 1, 10 unch. · avg -0.5% · Bytecode: 11 unch. · avg +2.7%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 593,341 → 543,542 🔴 -8.4% 2,009,068 → 2,143,676 +6.7%
floating point arithmetic 575,973 → 591,755 +2.7% 2,251,833 → 2,408,187 +6.9%
number coercion 186,058 → 185,388 -0.4% 140,817 → 138,011 -2.0%
toFixed 104,056 → 102,516 -1.5% 224,655 → 225,448 +0.4%
toString 162,600 → 159,007 -2.2% 835,934 → 892,593 +6.8%
valueOf 235,600 → 231,437 -1.8% 1,302,369 → 1,376,599 +5.7%
toPrecision 149,809 → 146,783 -2.0% 458,383 → 462,944 +1.0%
Number.isNaN 296,962 → 312,821 +5.3% 175,018 → 179,497 +2.6%
Number.isFinite 290,785 → 299,621 +3.0% 171,624 → 171,059 -0.3%
Number.isInteger 287,854 → 295,517 +2.7% 187,695 → 191,042 +1.8%
Number.parseInt and parseFloat 245,237 → 237,641 -3.1% 153,610 → 153,296 -0.2%
objects.js — Interp: 7 unch. · avg -1.9% · Bytecode: 🔴 2, 5 unch. · avg -3.5%
Benchmark Interpreted Δ Bytecode Δ
create simple object 483,500 → 460,408 -4.8% 1,005,558 → 993,735 -1.2%
create nested object 222,166 → 221,387 -0.4% 446,117 → 434,535 -2.6%
create 50 objects via Array.from 9,156 → 9,130 -0.3% 8,786 → 8,820 +0.4%
property read 626,843 → 618,341 -1.4% 937,386 → 919,271 -1.9%
Object.keys 299,860 → 291,793 -2.7% 188,584 → 182,581 -3.2%
Object.entries 108,949 → 108,860 -0.1% 57,956 → 53,314 🔴 -8.0%
spread operator 195,785 → 188,730 -3.6% 213,388 → 196,047 🔴 -8.1%
promises.js — Interp: 12 unch. · avg -0.9% · Bytecode: 🔴 3, 9 unch. · avg -4.2%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 555,821 → 574,426 +3.3% 348,397 → 340,063 -2.4%
new Promise(resolve => resolve(value)) 199,301 → 196,811 -1.2% 150,559 → 141,798 -5.8%
Promise.reject(reason) 563,519 → 551,819 -2.1% 341,904 → 309,791 🔴 -9.4%
resolve + then (1 handler) 177,125 → 177,788 +0.4% 155,720 → 144,693 🔴 -7.1%
resolve + then chain (3 deep) 68,679 → 66,520 -3.1% 64,852 → 60,648 -6.5%
resolve + then chain (10 deep) 22,133 → 21,610 -2.4% 22,031 → 21,761 -1.2%
reject + catch + then 99,590 → 98,607 -1.0% 89,964 → 94,165 +4.7%
resolve + finally + then 86,372 → 86,092 -0.3% 77,761 → 72,349 -7.0%
Promise.all (5 resolved) 34,099 → 34,420 +0.9% 25,556 → 25,401 -0.6%
Promise.race (5 resolved) 35,937 → 35,339 -1.7% 27,459 → 27,646 +0.7%
Promise.allSettled (5 mixed) 28,421 → 27,843 -2.0% 24,341 → 22,016 🔴 -9.6%
Promise.any (5 mixed) 33,387 → 32,747 -1.9% 28,708 → 26,939 -6.2%
strings.js — Interp: 🔴 1, 10 unch. · avg -3.5% · Bytecode: 🟢 1, 10 unch. · avg +3.2%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 421,276 → 413,284 -1.9% 413,494 → 428,009 +3.5%
template literal 728,613 → 633,650 🔴 -13.0% 754,498 → 785,941 +4.2%
string repeat 421,667 → 413,159 -2.0% 1,143,090 → 1,139,257 -0.3%
split and join 140,038 → 134,408 -4.0% 341,991 → 339,379 -0.8%
indexOf and includes 174,623 → 171,834 -1.6% 762,389 → 787,822 +3.3%
toUpperCase and toLowerCase 263,009 → 258,247 -1.8% 781,113 → 844,556 🟢 +8.1%
slice and substring 164,598 → 158,268 -3.8% 860,232 → 888,409 +3.3%
trim operations 195,535 → 192,717 -1.4% 709,380 → 744,216 +4.9%
replace and replaceAll 216,338 → 208,178 -3.8% 658,906 → 662,723 +0.6%
startsWith and endsWith 141,238 → 138,016 -2.3% 641,482 → 680,667 +6.1%
padStart and padEnd 205,090 → 198,950 -3.0% 696,763 → 714,477 +2.5%
typed-arrays.js — Interp: 22 unch. · avg -1.8% · Bytecode: 🟢 4, 🔴 1, 17 unch. · avg +3.3%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 339,724 → 332,043 -2.3% 129,856 → 125,748 -3.2%
new Int32Array(100) 314,576 → 306,395 -2.6% 125,476 → 120,502 -4.0%
new Int32Array(1000) 174,872 → 172,752 -1.2% 78,446 → 72,617 🔴 -7.4%
new Float64Array(100) 278,927 → 272,591 -2.3% 112,275 → 104,884 -6.6%
Int32Array.from([...]) 184,204 → 186,447 +1.2% 157,486 → 159,417 +1.2%
Int32Array.of(1, 2, 3, 4, 5) 327,980 → 319,247 -2.7% 239,182 → 251,661 +5.2%
sequential write 100 elements 3,708 → 3,636 -2.0% 13,841 → 15,329 🟢 +10.8%
sequential read 100 elements 3,802 → 3,758 -1.1% 10,932 → 11,694 +7.0%
Float64Array write 100 elements 3,444 → 3,419 -0.7% 13,460 → 14,343 +6.6%
fill(42) 45,037 → 47,888 +6.3% 43,657 → 46,344 +6.2%
slice() 212,706 → 208,389 -2.0% 187,983 → 185,397 -1.4%
map(x => x * 2) 8,352 → 8,115 -2.8% 7,099 → 8,262 🟢 +16.4%
filter(x => x > 50) 8,873 → 8,368 -5.7% 7,966 → 8,484 +6.5%
reduce (sum) 8,364 → 8,031 -4.0% 6,670 → 7,389 🟢 +10.8%
sort() 178,019 → 167,898 -5.7% 150,707 → 156,728 +4.0%
indexOf() 458,980 → 451,742 -1.6% 363,634 → 374,590 +3.0%
reverse() 341,487 → 336,536 -1.4% 278,503 → 278,879 +0.1%
create view over existing buffer 413,640 → 404,009 -2.3% 138,484 → 133,683 -3.5%
subarray() 472,425 → 448,770 -5.0% 349,946 → 366,419 +4.7%
set() from array 601,673 → 603,108 +0.2% 233,331 → 263,004 🟢 +12.7%
for-of loop 5,355 → 5,298 -1.0% 20,123 → 21,084 +4.8%
spread into array 18,955 → 18,934 -0.1% 56,405 → 56,183 -0.4%

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

Resolve conflict in Goccia.Runtime.Operations.pas: take the PR's
restructured guard (Assigned(Bp.SuperBlueprint) or not in
FBlueprintSuperValues) which routes native-pathable blueprints to the
fast path. The built-in super class bridge code from main is already
handled by the fallthrough to PushVMFramesToGoccia.

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

♻️ Duplicate comments (2)
units/Goccia.Runtime.Operations.pas (2)

3381-3385: ⚠️ Potential issue | 🔴 Critical

Avoid reusing FBlueprintBridgeCache for two different blueprint converters.

At Line 3381, this cache can already contain entries created by BlueprintToClassValue (from UnwrapToGocciaValue), but this branch requires ConvertBlueprintToClassValue. Reusing the same entry can drop constructor-path behavior.

🔧 Minimal safe fix
-      if not FBlueprintBridgeCache.TryGetValue(Bp, CachedBridge) then
-      begin
-        CachedBridge := ConvertBlueprintToClassValue(Bp, Self);
-        FBlueprintBridgeCache.Add(Bp, CachedBridge);
-      end;
+      CachedBridge := ConvertBlueprintToClassValue(Bp, Self);
🤖 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 3381 - 3385,
FBlueprintBridgeCache is being reused for entries created by two different
conversion paths (ConvertBlueprintToClassValue vs BlueprintToClassValue called
from UnwrapToGocciaValue), which can cause incorrect constructor behavior;
change the caching so entries are distinguished by converter or conversion path
— e.g., introduce a separate cache (like FBlueprintConvertBridgeCache) or
include the converter id/type in the cache key, and update the lookup/insert in
the code around ConvertBlueprintToClassValue and UnwrapToGocciaValue to use the
appropriate cache (or key) so ConvertBlueprintToClassValue never reuses entries
created by BlueprintToClassValue.

3295-3296: ⚠️ Potential issue | 🔴 Critical

Gate native construction by the root blueprint mapping, not the leaf.

At Line 3295, this condition still routes derived blueprints into the native path even when their root has a wrapped super mapping, which can bypass wrapped super constructor semantics.

🔧 Minimal safe fix
-    if Assigned(Bp.SuperBlueprint) or
-       not FBlueprintSuperValues.ContainsKey(Bp) then
+    WalkBp := Bp;
+    while Assigned(WalkBp.SuperBlueprint) do
+      WalkBp := WalkBp.SuperBlueprint;
+    if not FBlueprintSuperValues.ContainsKey(WalkBp) then

Based on learnings: “Classes extending built-in constructors must be compiled natively via FBlueprintSuperValues … and TGocciaSuperCallHelper.”

🤖 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 3295 - 3296, The condition
currently gates native construction using the leaf blueprint (Bp) which allows
derived blueprints to take the native path even when their root has a wrapped
super mapping; change the check to determine the root-most blueprint first
(e.g., walk SuperBlueprint pointers until no further SuperBlueprint exists or
call a GetRootBlueprint helper) and then use
FBlueprintSuperValues.ContainsKey(rootBlueprint) (and keep the
TGocciaSuperCallHelper semantics for classes extending built-ins) so that the
decision to use native construction is based on the root blueprint mapping
rather than the leaf Bp.
🤖 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 3408-3410: The branch that calls SyncArraysBack(Self,
Context.Scope) then clears FArrayBridgeCache must also reset the corresponding
dirty flag: after FArrayBridgeCache.Clear add logic to set FArrayBridgeDirty to
False (or 0, matching how the sibling branch resets it) so the state is
consistent; update the code near SyncArraysBack, Context.Scope,
FArrayBridgeCache.Clear to mirror the sibling branch's handling of
FArrayBridgeDirty.

---

Duplicate comments:
In `@units/Goccia.Runtime.Operations.pas`:
- Around line 3381-3385: FBlueprintBridgeCache is being reused for entries
created by two different conversion paths (ConvertBlueprintToClassValue vs
BlueprintToClassValue called from UnwrapToGocciaValue), which can cause
incorrect constructor behavior; change the caching so entries are distinguished
by converter or conversion path — e.g., introduce a separate cache (like
FBlueprintConvertBridgeCache) or include the converter id/type in the cache key,
and update the lookup/insert in the code around ConvertBlueprintToClassValue and
UnwrapToGocciaValue to use the appropriate cache (or key) so
ConvertBlueprintToClassValue never reuses entries created by
BlueprintToClassValue.
- Around line 3295-3296: The condition currently gates native construction using
the leaf blueprint (Bp) which allows derived blueprints to take the native path
even when their root has a wrapped super mapping; change the check to determine
the root-most blueprint first (e.g., walk SuperBlueprint pointers until no
further SuperBlueprint exists or call a GetRootBlueprint helper) and then use
FBlueprintSuperValues.ContainsKey(rootBlueprint) (and keep the
TGocciaSuperCallHelper semantics for classes extending built-ins) so that the
decision to use native construction is based on the root blueprint mapping
rather than the leaf Bp.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c42a987-72bf-4421-ad90-414a7dafd5b4

📥 Commits

Reviewing files that changed from the base of the PR and between cb97506 and 4260c58.

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

Comment thread units/Goccia.Runtime.Operations.pas
- Walk blueprint chain to root before checking FBlueprintSuperValues,
  so multi-level chains (B extends A extends Array) correctly fall
  through to the bridge instead of taking the native fast path.
- Add missing FArrayBridgeDirty := False after FArrayBridgeCache.Clear
  in the Construct bridge path, matching every other call site.

Made-with: Cursor
@frostney
Copy link
Copy Markdown
Owner Author

Re: comment 4047936711

Acknowledged. Bytecode benchmarks show avg +1.3% improvement (16 improved, 5 regressed). Interpreted side shows avg -1.6% which is within noise. The typed-array and class construction paths show the expected gains from bypassing the bridge.

@frostney
Copy link
Copy Markdown
Owner Author

Re: comment 4047936460

Acknowledged. All 3497 tests pass in both modes.

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.

♻️ Duplicate comments (1)
units/Goccia.Runtime.Operations.pas (1)

3383-3387: ⚠️ Potential issue | 🔴 Critical

Don't share FBlueprintBridgeCache between BlueprintToClassValue and ConvertBlueprintToClassValue.

At Line 3383 this can reuse a BlueprintToClassValue entry inserted by UnwrapToGocciaValue, but this branch needs the richer ConvertBlueprintToClassValue bridge. If a wrapped-super blueprint is unwrapped before new, InstantiateClass at Line 3407 can silently lose accessors, static members, private members, and __fields__ initializers.

🔧 Minimal safe fix
-      if not FBlueprintBridgeCache.TryGetValue(Bp, CachedBridge) then
-      begin
-        CachedBridge := ConvertBlueprintToClassValue(Bp, Self);
-        FBlueprintBridgeCache.Add(Bp, CachedBridge);
-      end;
+      CachedBridge := ConvertBlueprintToClassValue(Bp, Self);
🤖 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 3383 - 3387, The bug is
that FBlueprintBridgeCache can return a lightweight BlueprintToClassValue
created by UnwrapToGocciaValue which lacks accessors/static/private members and
__fields__ initializers required by ConvertBlueprintToClassValue and
InstantiateClass; to fix it, change the lookup/insert logic around
FBlueprintBridgeCache in the block with ConvertBlueprintToClassValue so that you
either use a separate cache for converted bridges or detect the existing entry's
kind (e.g., test if CachedBridge is a BlueprintToClassValue vs a full
ConvertBlueprintToClassValue result) and if it's the lightweight form, call
ConvertBlueprintToClassValue to produce the richer bridge and replace the cache
entry before returning; update the code that uses FBlueprintBridgeCache (the
retrieval before calling ConvertBlueprintToClassValue and the add after
conversion) so InstantiateClass always receives the full converted bridge.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@units/Goccia.Runtime.Operations.pas`:
- Around line 3383-3387: The bug is that FBlueprintBridgeCache can return a
lightweight BlueprintToClassValue created by UnwrapToGocciaValue which lacks
accessors/static/private members and __fields__ initializers required by
ConvertBlueprintToClassValue and InstantiateClass; to fix it, change the
lookup/insert logic around FBlueprintBridgeCache in the block with
ConvertBlueprintToClassValue so that you either use a separate cache for
converted bridges or detect the existing entry's kind (e.g., test if
CachedBridge is a BlueprintToClassValue vs a full ConvertBlueprintToClassValue
result) and if it's the lightweight form, call ConvertBlueprintToClassValue to
produce the richer bridge and replace the cache entry before returning; update
the code that uses FBlueprintBridgeCache (the retrieval before calling
ConvertBlueprintToClassValue and the add after conversion) so InstantiateClass
always receives the full converted bridge.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e7355fe-c649-45bf-95f6-6767e8863f98

📥 Commits

Reviewing files that changed from the base of the PR and between 4260c58 and c463328.

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

@frostney frostney merged commit 78b24cd into main Mar 17, 2026
9 checks passed
@frostney frostney deleted the opt/native-class-construct branch March 17, 2026 16:30
@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