Skip to content

Add Souffle-value-backed Map/Set types and native callback fast paths#109

Merged
frostney merged 4 commits intomainfrom
feature/map-set-souffle-backing
Mar 23, 2026
Merged

Add Souffle-value-backed Map/Set types and native callback fast paths#109
frostney merged 4 commits intomainfrom
feature/map-set-souffle-backing

Conversation

@frostney
Copy link
Copy Markdown
Owner

@frostney frostney commented Mar 23, 2026

Summary

  • Add TGocciaSouffleMap and TGocciaSouffleSet Souffle-value-backed types in Goccia.Runtime.Collections.pas — stores TSouffleValue key/value pairs directly, eliminating per-operation wrap/unwrap in native callbacks
  • Add TGocciaSouffleMapIterator and TGocciaSouffleSetIterator with proper Next(out ADone) protocol and GC marking
  • Add iterator delegate tables (MAP_ITERATOR_METHODS / SET_ITERATOR_METHODS) with next method returning {value, done} records
  • Add GetProperty fast paths for iterator delegates
  • Wire GetIterator to recognize native Map/Set types and native iterators as self-iterating
  • Wire IteratorNext fast paths for native iterators
  • Add native fast paths in keys(), values(), entries() methods returning native iterators

Current state

The native types and callbacks are defined and tested. Callbacks check both native (TGocciaSouffleMap) and wrapped (TGocciaWrappedValue<TGocciaMapValue>) types, falling back to bridge for whichever is present. ToSouffleValue still wraps as TGocciaWrappedValue since native types don't yet support all operations (getOrInsert, Set operations, subclassing, cloning). Future work: intercept Construct() to produce native types directly for non-subclassed Map/Set.

Test plan

  • All tests pass in interpreter mode
  • All tests pass in bytecode mode
  • Zero regressions from main

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added in-memory map and set types with insert/update, lookup, membership, delete and clear.
    • Added iterators for keys/values/entries with standard iteration and done semantics; iteration can return {value, done} records.
    • Introduced value-equality semantics treating two NaNs as equal.
    • Runtime exposes collection size, converts host containers to these collections, and uses native fast-paths for faster iteration and method dispatch.
    • Collections participate in GC/reference traversal and provide debug string output.

Introduce TGocciaSouffleMap and TGocciaSouffleSet in the Goccia runtime
layer (not Souffle VM core) that store entries as TSouffleValue directly,
eliminating per-operation UnwrapToGocciaValue/ToSouffleValue conversion.

- Add Goccia.Runtime.Collections.pas with TGocciaSouffleMap and
  TGocciaSouffleSet backed by TSouffleValue key/value storage
- Implement SouffleSameValueZero for key equality (ES SameValueZero
  semantics at the Souffle value level)
- Add fast paths in native Map callbacks (get, set, has, delete, clear,
  forEach) that check for TGocciaSouffleMap first — zero wrap/unwrap
- Add fast paths in native Set callbacks (has, add, delete, clear,
  forEach) that check for TGocciaSouffleSet first
- Add TGocciaSouffleMap/Set handling in GetProperty bridge for .size
- Keep TGocciaWrappedValue fallback for interpreter-mode Map/Set
  instances and operations not yet ported (keys, values, entries,
  iterators, Set operations like union/intersection)

Construction path integration (creating TGocciaSouffleMap/Set instead of
TGocciaWrappedValue<TGocciaMapValue>) is deferred to a follow-up — the
callback fast paths will activate automatically once instances are
created with the new types.

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

coderabbitai Bot commented Mar 23, 2026

📝 Walkthrough

Walkthrough

Added a new unit providing in-memory Souffle-backed map/set types and iterators, plus runtime changes to register iterator delegates, convert Goccia collections to Souffle-native objects, recognize native map/set types for property/iterator access, and provide native fast-paths for map/set methods and iterator next.

Changes

Cohort / File(s) Summary
Collections Unit
units/Goccia.Runtime.Collections.pas
New unit implementing TGocciaSouffleMap, TGocciaSouffleSet, iterators (TGocciaSouffleMapIterator, TGocciaSouffleSetIterator) and SouffleSameValueZero. Implements linear-search operations (insert, lookup, delete, clear), indexed accessors, MarkReferences, DebugString, and iterator-entry modes returning Souffle arrays.
Runtime Operations
units/Goccia.Runtime.Operations.pas
Added iterator delegate registrations and fields, conversion helpers to create Souffle-native maps/sets from Goccia collections, updated GetProperty/GetIterator/IteratorNext to handle native Souffle types, and implemented native fast-paths for map/set methods and iterator next (including record allocation for { value, done }).

Sequence Diagram(s)

mermaid
sequenceDiagram
participant VM as Client (VM)
participant Ops as RuntimeOperations
participant Iter as TGocciaSouffleMapIterator
participant Map as TGocciaSouffleMap
participant GC as GC / Allocator
Note over VM,Ops: Iterator.Next flow for Souffle-backed map
VM->>Ops: IteratorNext(call)
Ops->>Iter: call Next(out ADone)
Iter->>Map: read key/value at index
alt returning entry record
Iter->>GC: allocate 2-field record (array)
GC-->>Iter: record ref
Iter-->>Ops: record (value), ADone=false
else done
Iter-->>Ops: SouffleNilWithFlags(0), ADone=true
end
Ops-->>VM: result (value/done)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • for of array fast path #64: Overlaps in iterator subsystem changes and native iterator implementations (delegate wiring and fast-path iterator logic).

Poem

🐰 I hopped through arrays and keys tonight,

NaNs matched under soft moonlight,
Maps and Sets now bound in hops,
Iterators count my tiny stops,
A rabbit's cheer for code done right 🥕

🚥 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 describes the main changes: adding Souffle-value-backed Map/Set types and implementing native callback fast paths in the new Collections unit and Operations unit.
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 feature/map-set-souffle-backing

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

Benchmark Results

263 benchmarks

Interpreted: 🟢 3 improved · 🔴 95 regressed · 165 unchanged · avg -5.0%
Bytecode: 🟢 45 improved · 🔴 3 regressed · 215 unchanged · avg +3.0%

arraybuffer.js — Interp: 🔴 12, 2 unch. · avg -11.9% · Bytecode: 14 unch. · avg -0.7%
Benchmark Interpreted Δ Bytecode Δ
create ArrayBuffer(0) 522,844 → 453,038 🔴 -13.4% 161,103 → 163,164 +1.3%
create ArrayBuffer(64) 509,206 → 440,741 🔴 -13.4% 158,908 → 161,716 +1.8%
create ArrayBuffer(1024) 394,611 → 335,201 🔴 -15.1% 140,923 → 143,200 +1.6%
create ArrayBuffer(8192) 171,622 → 144,505 🔴 -15.8% 84,280 → 82,984 -1.5%
slice full buffer (64 bytes) 607,266 → 517,486 🔴 -14.8% 395,878 → 388,823 -1.8%
slice half buffer (512 of 1024 bytes) 512,441 → 449,296 🔴 -12.3% 348,923 → 341,355 -2.2%
slice with negative indices 526,210 → 457,629 🔴 -13.0% 373,395 → 371,609 -0.5%
slice empty range 587,173 → 513,915 🔴 -12.5% 389,547 → 385,391 -1.1%
byteLength access 1,690,271 → 1,547,627 🔴 -8.4% 1,271,149 → 1,210,960 -4.7%
Symbol.toStringTag access 1,157,846 → 1,163,389 +0.5% 694,430 → 679,801 -2.1%
ArrayBuffer.isView 871,217 → 816,021 -6.3% 556,746 → 551,124 -1.0%
clone ArrayBuffer(64) 495,624 → 433,918 🔴 -12.5% 391,081 → 387,559 -0.9%
clone ArrayBuffer(1024) 391,336 → 328,299 🔴 -16.1% 280,872 → 279,170 -0.6%
clone ArrayBuffer inside object 315,006 → 273,522 🔴 -13.2% 190,255 → 194,392 +2.2%
arrays.js — Interp: 🔴 19 · avg -10.9% · Bytecode: 🟢 1, 18 unch. · avg +3.9%
Benchmark Interpreted Δ Bytecode Δ
Array.from length 100 15,334 → 13,339 🔴 -13.0% 15,104 → 15,075 -0.2%
Array.from 10 elements 264,367 → 235,208 🔴 -11.0% 175,482 → 184,669 +5.2%
Array.of 10 elements 365,627 → 329,077 🔴 -10.0% 244,117 → 253,032 +3.7%
spread into new array 374,551 → 335,556 🔴 -10.4% 855,763 → 846,106 -1.1%
map over 50 elements 31,377 → 27,253 🔴 -13.1% 24,326 → 24,427 +0.4%
filter over 50 elements 25,925 → 22,963 🔴 -11.4% 23,977 → 23,797 -0.8%
reduce sum 50 elements 29,327 → 26,237 🔴 -10.5% 21,821 → 20,830 -4.5%
forEach over 50 elements 25,057 → 22,547 🔴 -10.0% 28,406 → 27,617 -2.8%
find in 50 elements 39,167 → 34,015 🔴 -13.2% 31,608 → 32,409 +2.5%
sort 20 elements 13,209 → 11,953 🔴 -9.5% 12,784 → 13,002 +1.7%
flat nested array 130,931 → 118,490 🔴 -9.5% 488,024 → 470,057 -3.7%
flatMap 83,433 → 72,758 🔴 -12.8% 202,838 → 323,552 🟢 +59.5%
map inside map (5x5) 24,230 → 21,890 🔴 -9.7% 96,800 → 103,065 +6.5%
filter inside map (5x10) 17,772 → 15,929 🔴 -10.4% 14,629 → 14,973 +2.3%
reduce inside map (5x10) 21,519 → 19,440 🔴 -9.7% 15,291 → 15,266 -0.2%
forEach inside forEach (5x10) 18,243 → 16,880 🔴 -7.5% 17,170 → 17,489 +1.9%
find inside some (10x10) 15,665 → 13,764 🔴 -12.1% 12,019 → 12,086 +0.6%
map+filter chain nested (5x20) 6,014 → 5,415 🔴 -10.0% 4,826 → 4,944 +2.4%
reduce flatten (10x5) 44,866 → 38,776 🔴 -13.6% 6,619 → 6,613 -0.1%
async-await.js — Interp: 🔴 6 · avg -10.2% · Bytecode: 6 unch. · avg +0.1%
Benchmark Interpreted Δ Bytecode Δ
single await 417,327 → 375,153 🔴 -10.1% 287,297 → 283,391 -1.4%
multiple awaits 187,363 → 169,628 🔴 -9.5% 120,013 → 120,628 +0.5%
await non-Promise value 959,958 → 821,474 🔴 -14.4% 1,010,057 → 981,046 -2.9%
await with try/catch 405,423 → 367,070 🔴 -9.5% 278,209 → 283,329 +1.8%
await Promise.all 55,734 → 51,029 🔴 -8.4% 46,579 → 46,826 +0.5%
nested async function call 208,594 → 189,799 🔴 -9.0% 211,037 → 214,984 +1.9%
classes.js — Interp: 🔴 30, 1 unch. · avg -10.7% · Bytecode: 🟢 1, 30 unch. · avg +1.2%
Benchmark Interpreted Δ Bytecode Δ
simple class new 152,991 → 136,389 🔴 -10.9% 412,034 → 408,510 -0.9%
class with defaults 120,172 → 108,438 🔴 -9.8% 292,662 → 290,855 -0.6%
50 instances via Array.from 6,323 → 5,574 🔴 -11.8% 7,296 → 7,589 +4.0%
instance method call 75,472 → 66,573 🔴 -11.8% 185,268 → 181,745 -1.9%
static method call 127,040 → 110,798 🔴 -12.8% 422,832 → 425,969 +0.7%
single-level inheritance 61,534 → 53,951 🔴 -12.3% 174,689 → 175,865 +0.7%
two-level inheritance 52,860 → 46,585 🔴 -11.9% 148,604 → 143,081 -3.7%
private field access 80,341 → 69,766 🔴 -13.2% 194,876 → 195,066 +0.1%
private methods 87,566 → 75,640 🔴 -13.6% 260,598 → 252,790 -3.0%
getter/setter access 84,954 → 75,000 🔴 -11.7% 188,730 → 190,330 +0.8%
class decorator (identity) 104,161 → 92,148 🔴 -11.5% 61,064 → 57,881 -5.2%
class decorator (wrapping) 60,874 → 53,607 🔴 -11.9% 36,933 → 43,954 🟢 +19.0%
identity method decorator 73,148 → 63,978 🔴 -12.5% 48,379 → 45,052 -6.9%
wrapping method decorator 58,559 → 51,762 🔴 -11.6% 43,768 → 44,790 +2.3%
stacked method decorators (x3) 38,618 → 35,218 🔴 -8.8% 29,026 → 29,624 +2.1%
identity field decorator 80,055 → 71,115 🔴 -11.2% 49,940 → 50,553 +1.2%
field initializer decorator 66,855 → 59,958 🔴 -10.3% 43,707 → 45,711 +4.6%
getter decorator (identity) 73,592 → 64,685 🔴 -12.1% 44,145 → 45,333 +2.7%
setter decorator (identity) 61,196 → 54,604 🔴 -10.8% 38,192 → 39,962 +4.6%
static method decorator 79,425 → 70,410 🔴 -11.4% 71,026 → 72,361 +1.9%
static field decorator 91,896 → 81,396 🔴 -11.4% 72,675 → 74,672 +2.7%
private method decorator 59,588 → 53,005 🔴 -11.0% 41,293 → 41,924 +1.5%
private field decorator 66,032 → 58,439 🔴 -11.5% 41,741 → 43,088 +3.2%
plain auto-accessor (no decorator) 112,005 → 103,150 🔴 -7.9% 60,001 → 61,510 +2.5%
auto-accessor with decorator 61,572 → 55,420 🔴 -10.0% 39,530 → 40,899 +3.5%
decorator writing metadata 49,509 → 44,994 🔴 -9.1% 43,833 → 45,126 +3.0%
static getter read 140,757 → 127,820 🔴 -9.2% 433,641 → 430,563 -0.7%
static getter/setter pair 108,900 → 101,368 -6.9% 230,737 → 226,512 -1.8%
inherited static getter 84,260 → 77,733 🔴 -7.7% 299,751 → 299,964 +0.1%
inherited static setter 91,261 → 84,230 🔴 -7.7% 226,529 → 227,427 +0.4%
inherited static getter with this binding 77,689 → 71,634 🔴 -7.8% 179,155 → 177,176 -1.1%
closures.js — Interp: 🔴 6, 5 unch. · avg -7.0% · Bytecode: 11 unch. · avg +2.0%
Benchmark Interpreted Δ Bytecode Δ
closure over single variable 143,454 → 133,001 🔴 -7.3% 797,521 → 813,728 +2.0%
closure over multiple variables 129,223 → 121,504 -6.0% 482,052 → 481,754 -0.1%
nested closures 137,745 → 126,668 🔴 -8.0% 705,746 → 732,973 +3.9%
function as argument 104,889 → 96,508 🔴 -8.0% 714,797 → 719,163 +0.6%
function returning function 132,015 → 120,838 🔴 -8.5% 781,144 → 779,707 -0.2%
compose two functions 77,220 → 71,902 -6.9% 479,443 → 485,900 +1.3%
fn.call 159,215 → 151,442 -4.9% 139,416 → 145,954 +4.7%
fn.apply 126,271 → 114,288 🔴 -9.5% 98,508 → 103,528 +5.1%
fn.bind 151,599 → 138,124 🔴 -8.9% 141,186 → 147,884 +4.7%
recursive sum to 50 12,405 → 11,956 -3.6% 51,885 → 52,147 +0.5%
recursive tree traversal 21,335 → 20,051 -6.0% 78,561 → 77,913 -0.8%
collections.js — Interp: 🔴 1, 11 unch. · avg -4.4% · Bytecode: 12 unch. · avg +1.2%
Benchmark Interpreted Δ Bytecode Δ
add 50 elements 7,501 → 7,182 -4.2% 5,710 → 5,886 +3.1%
has lookup (50 elements) 92,541 → 91,662 -1.0% 87,351 → 88,587 +1.4%
delete elements 49,719 → 49,125 -1.2% 36,894 → 38,913 +5.5%
forEach iteration 17,311 → 15,804 🔴 -8.7% 17,170 → 17,404 +1.4%
spread to array 30,434 → 28,706 -5.7% 150,363 → 144,504 -3.9%
deduplicate array 41,489 → 39,463 -4.9% 49,752 → 52,612 +5.7%
set 50 entries 5,481 → 5,145 -6.1% 6,181 → 6,101 -1.3%
get lookup (50 entries) 89,182 → 87,025 -2.4% 99,754 → 98,028 -1.7%
has check 134,745 → 129,512 -3.9% 157,346 → 153,417 -2.5%
delete entries 46,872 → 46,054 -1.7% 36,030 → 36,318 +0.8%
forEach iteration 17,016 → 15,938 -6.3% 17,230 → 17,257 +0.2%
keys/values/entries 8,251 → 7,739 -6.2% 21,278 → 22,527 +5.9%
destructuring.js — Interp: 🔴 14, 8 unch. · avg -8.0% · Bytecode: 🟢 1, 21 unch. · avg +0.7%
Benchmark Interpreted Δ Bytecode Δ
simple array destructuring 453,706 → 404,864 🔴 -10.8% 1,035,515 → 1,017,042 -1.8%
with rest element 299,668 → 274,482 🔴 -8.4% 776,133 → 764,758 -1.5%
with defaults 449,297 → 418,145 -6.9% 996,477 → 951,791 -4.5%
skip elements 478,019 → 425,898 🔴 -10.9% 1,197,316 → 1,170,499 -2.2%
nested array destructuring 184,901 → 177,397 -4.1% 513,990 → 510,349 -0.7%
swap variables 553,429 → 515,363 -6.9% 1,338,381 → 1,408,069 +5.2%
simple object destructuring 344,944 → 320,330 🔴 -7.1% 637,101 → 649,129 +1.9%
with defaults 406,896 → 365,741 🔴 -10.1% 336,523 → 338,404 +0.6%
with renaming 362,959 → 326,450 🔴 -10.1% 737,333 → 760,918 +3.2%
nested object destructuring 154,939 → 147,041 -5.1% 289,836 → 295,263 +1.9%
rest properties 209,033 → 193,237 🔴 -7.6% 228,800 → 247,957 🟢 +8.4%
object parameter 104,515 → 95,687 🔴 -8.4% 211,765 → 211,039 -0.3%
array parameter 134,330 → 127,561 -5.0% 425,082 → 418,469 -1.6%
mixed destructuring in map 39,101 → 35,109 🔴 -10.2% 37,390 → 38,792 +3.8%
forEach with array destructuring 69,086 → 66,354 -4.0% 199,756 → 194,710 -2.5%
map with array destructuring 71,462 → 67,984 -4.9% 256,790 → 241,780 -5.8%
filter with array destructuring 76,963 → 71,045 🔴 -7.7% 278,736 → 291,259 +4.5%
reduce with array destructuring 80,421 → 75,415 -6.2% 285,892 → 276,245 -3.4%
map with object destructuring 89,060 → 77,895 🔴 -12.5% 77,776 → 81,652 +5.0%
map with nested destructuring 72,182 → 64,506 🔴 -10.6% 65,786 → 68,166 +3.6%
map with rest in destructuring 41,378 → 38,386 🔴 -7.2% 23,535 → 23,683 +0.6%
map with defaults in destructuring 66,108 → 59,056 🔴 -10.7% 40,685 → 40,855 +0.4%
fibonacci.js — Interp: 🔴 1, 7 unch. · avg -4.4% · Bytecode: 🟢 1, 🔴 1, 6 unch. · avg +0.9%
Benchmark Interpreted Δ Bytecode Δ
recursive fib(15) 333 → 323 -3.1% 1,477 → 1,450 -1.9%
recursive fib(20) 30 → 29 -4.7% 133 → 133 -0.5%
recursive fib(15) typed 338 → 327 -3.4% 1,981 → 1,776 🔴 -10.3%
recursive fib(20) typed 30 → 29 -4.4% 179 → 173 -3.2%
iterative fib(20) via reduce 12,684 → 12,316 -2.9% 9,053 → 9,777 🟢 +8.0%
iterator fib(20) 9,859 → 9,533 -3.3% 16,278 → 17,072 +4.9%
iterator fib(20) via Iterator.from + take 16,374 → 15,053 🔴 -8.1% 17,810 → 18,390 +3.3%
iterator fib(20) last value via reduce 12,100 → 11,482 -5.1% 13,289 → 14,219 +7.0%
for-of.js — Interp: 7 unch. · avg -0.9% · Bytecode: 🟢 1, 6 unch. · avg +0.8%
Benchmark Interpreted Δ Bytecode Δ
for...of with 10-element array 46,355 → 45,753 -1.3% 166,215 → 162,634 -2.2%
for...of with 100-element array 5,333 → 5,261 -1.4% 24,649 → 23,743 -3.7%
for...of with string (10 chars) 34,566 → 34,206 -1.0% 126,498 → 131,897 +4.3%
for...of with Set (10 elements) 46,801 → 46,386 -0.9% 177,682 → 172,218 -3.1%
for...of with Map entries (10 entries) 29,761 → 29,554 -0.7% 46,646 → 50,914 🟢 +9.2%
for...of with destructuring 39,707 → 39,590 -0.3% 77,727 → 79,824 +2.7%
for-await-of with sync array 44,462 → 44,274 -0.4% 138,333 → 135,697 -1.9%
helpers/bench-module.js — Interp: 0 · Bytecode: 0
Benchmark Interpreted Δ Bytecode Δ
iterators.js — Interp: 20 unch. · avg -1.1% · Bytecode: 🟢 7, 13 unch. · avg +8.3%
Benchmark Interpreted Δ Bytecode Δ
Iterator.from({next}).toArray() — 20 elements 16,123 → 15,411 -4.4% 18,106 → 19,205 +6.1%
Iterator.from({next}).toArray() — 50 elements 6,996 → 6,759 -3.4% 7,984 → 8,439 +5.7%
spread pre-wrapped iterator — 20 elements 11,824 → 11,445 -3.2% 15,734 → 19,044 🟢 +21.0%
Iterator.from({next}).forEach — 50 elements 4,913 → 4,778 -2.7% 5,827 → 6,084 +4.4%
Iterator.from({next}).reduce — 50 elements 4,887 → 4,788 -2.0% 5,152 → 5,798 🟢 +12.5%
wrap array iterator 178,314 → 175,442 -1.6% 108,627 → 119,062 🟢 +9.6%
wrap plain {next()} object 11,075 → 10,900 -1.6% 12,361 → 14,607 🟢 +18.2%
map + toArray (50 elements) 5,003 → 4,872 -2.6% 5,529 → 6,470 🟢 +17.0%
filter + toArray (50 elements) 4,810 → 4,669 -2.9% 5,437 → 6,126 🟢 +12.7%
take(10) + toArray (50 element source) 29,087 → 27,877 -4.2% 27,981 → 32,695 🟢 +16.8%
drop(40) + toArray (50 element source) 7,025 → 6,862 -2.3% 9,544 → 9,789 +2.6%
chained map + filter + take (100 element source) 8,858 → 8,829 -0.3% 10,274 → 10,826 +5.4%
some + every (50 elements) 2,798 → 2,808 +0.3% 3,652 → 3,886 +6.4%
find (50 elements) 5,964 → 6,097 +2.2% 7,427 → 7,814 +5.2%
array.values().map().filter().toArray() 9,195 → 9,094 -1.1% 9,899 → 10,572 +6.8%
array.values().take(5).toArray() 212,941 → 215,494 +1.2% 149,888 → 149,922 +0.0%
array.values().drop(45).toArray() 199,803 → 199,588 -0.1% 138,453 → 144,701 +4.5%
map.entries() chained helpers 10,764 → 10,954 +1.8% 5,566 → 5,601 +0.6%
set.values() chained helpers 18,705 → 18,973 +1.4% 21,445 → 22,266 +3.8%
string iterator map + toArray 13,779 → 14,207 +3.1% 21,791 → 23,036 +5.7%
json.js — Interp: 🔴 1, 19 unch. · avg -3.2% · Bytecode: 🟢 6, 🔴 2, 12 unch. · avg +3.1%
Benchmark Interpreted Δ Bytecode Δ
parse simple object 180,568 → 175,612 -2.7% 144,212 → 155,735 🟢 +8.0%
parse nested object 104,779 → 104,949 +0.2% 97,218 → 104,575 🟢 +7.6%
parse array of objects 63,610 → 60,087 -5.5% 55,359 → 65,161 🟢 +17.7%
parse large flat object 68,858 → 69,011 +0.2% 59,772 → 64,236 🟢 +7.5%
parse mixed types 79,624 → 78,608 -1.3% 68,269 → 71,016 +4.0%
stringify simple object 216,635 → 201,260 🔴 -7.1% 187,451 → 171,313 🔴 -8.6%
stringify nested object 113,893 → 109,916 -3.5% 98,069 → 91,223 -7.0%
stringify array of objects 59,969 → 62,353 +4.0% 56,082 → 54,046 -3.6%
stringify mixed types 89,093 → 87,421 -1.9% 82,030 → 77,073 -6.0%
reviver doubles numbers 50,342 → 48,401 -3.9% 52,218 → 48,332 🔴 -7.4%
reviver filters properties 43,846 → 41,083 -6.3% 52,582 → 51,950 -1.2%
reviver on nested object 54,045 → 51,573 -4.6% 54,001 → 55,529 +2.8%
reviver on array 34,762 → 33,162 -4.6% 33,071 → 36,285 🟢 +9.7%
replacer function doubles numbers 49,651 → 48,460 -2.4% 53,086 → 56,144 +5.8%
replacer function excludes properties 62,291 → 61,139 -1.8% 64,800 → 67,311 +3.9%
array replacer (allowlist) 119,525 → 116,244 -2.7% 101,472 → 106,753 +5.2%
stringify with 2-space indent 103,746 → 98,437 -5.1% 81,175 → 88,202 🟢 +8.7%
stringify with tab indent 102,780 → 97,214 -5.4% 87,590 → 89,347 +2.0%
parse then stringify 59,087 → 56,060 -5.1% 52,744 → 56,049 +6.3%
stringify then parse 36,507 → 34,716 -4.9% 32,538 → 34,610 +6.4%
jsx.jsx — Interp: 21 unch. · avg -3.3% · Bytecode: 🟢 13, 8 unch. · avg +7.7%
Benchmark Interpreted Δ Bytecode Δ
simple element 223,393 → 211,509 -5.3% 739,312 → 815,748 🟢 +10.3%
self-closing element 222,971 → 219,614 -1.5% 836,822 → 847,780 +1.3%
element with string attribute 188,471 → 176,177 -6.5% 549,045 → 594,483 🟢 +8.3%
element with multiple attributes 162,040 → 155,650 -3.9% 483,723 → 512,926 +6.0%
element with expression attribute 175,006 → 168,300 -3.8% 555,711 → 578,207 +4.0%
text child 219,101 → 213,210 -2.7% 760,083 → 818,360 🟢 +7.7%
expression child 210,883 → 210,128 -0.4% 771,131 → 813,202 +5.5%
mixed text and expression 198,836 → 198,890 +0.0% 715,690 → 766,164 🟢 +7.1%
nested elements (3 levels) 82,613 → 78,672 -4.8% 294,824 → 328,557 🟢 +11.4%
sibling children 62,105 → 59,684 -3.9% 227,128 → 246,615 🟢 +8.6%
component element 159,204 → 150,599 -5.4% 518,434 → 568,110 🟢 +9.6%
component with children 97,141 → 92,252 -5.0% 322,482 → 362,428 🟢 +12.4%
dotted component 132,245 → 125,672 -5.0% 393,628 → 419,190 +6.5%
empty fragment 225,466 → 223,691 -0.8% 812,952 → 852,427 +4.9%
fragment with children 61,445 → 58,723 -4.4% 227,766 → 249,378 🟢 +9.5%
spread attributes 115,946 → 110,537 -4.7% 123,711 → 134,987 🟢 +9.1%
spread with overrides 100,242 → 95,963 -4.3% 96,227 → 102,324 +6.3%
shorthand props 166,136 → 162,570 -2.1% 520,829 → 547,998 +5.2%
nav bar structure 27,928 → 27,616 -1.1% 95,393 → 105,542 🟢 +10.6%
card component tree 32,386 → 32,240 -0.5% 103,558 → 113,447 🟢 +9.5%
10 list items via Array.from 15,119 → 14,764 -2.3% 25,666 → 27,465 🟢 +7.0%
modules.js — Interp: 9 unch. · avg -4.1% · Bytecode: 9 unch. · avg -1.6%
Benchmark Interpreted Δ Bytecode Δ
call imported function 506,533 → 489,012 -3.5% 1,879,597 → 1,852,274 -1.5%
call two imported functions 286,233 → 277,763 -3.0% 1,363,007 → 1,344,301 -1.4%
read imported constant 1,751,721 → 1,629,272 -7.0% 4,045,621 → 3,973,240 -1.8%
read imported string 1,671,086 → 1,604,495 -4.0% 4,048,049 → 3,862,335 -4.6%
read JSON string property 1,710,643 → 1,641,345 -4.1% 4,035,905 → 3,999,984 -0.9%
read JSON number property 1,683,433 → 1,606,057 -4.6% 4,033,658 → 3,987,875 -1.1%
read JSON boolean property 1,704,004 → 1,609,949 -5.5% 4,042,891 → 4,004,213 -1.0%
read JSON array property 1,708,693 → 1,612,797 -5.6% 4,031,663 → 3,985,030 -1.2%
read multiple JSON properties 958,481 → 964,604 +0.6% 3,447,766 → 3,411,478 -1.1%
numbers.js — Interp: 11 unch. · avg -4.2% · Bytecode: 🟢 2, 9 unch. · avg +1.7%
Benchmark Interpreted Δ Bytecode Δ
integer arithmetic 555,782 → 539,381 -3.0% 2,152,551 → 2,099,865 -2.4%
floating point arithmetic 582,579 → 572,435 -1.7% 2,380,999 → 2,333,473 -2.0%
number coercion 200,161 → 189,090 -5.5% 159,355 → 165,671 +4.0%
toFixed 109,680 → 103,015 -6.1% 227,336 → 227,533 +0.1%
toString 164,599 → 160,281 -2.6% 882,076 → 851,848 -3.4%
valueOf 239,026 → 225,737 -5.6% 1,321,951 → 1,284,181 -2.9%
toPrecision 147,115 → 145,749 -0.9% 466,336 → 462,977 -0.7%
Number.isNaN 346,061 → 330,859 -4.4% 208,564 → 220,249 +5.6%
Number.isFinite 342,079 → 322,131 -5.8% 205,704 → 220,878 🟢 +7.4%
Number.isInteger 341,356 → 326,693 -4.3% 220,980 → 233,941 +5.9%
Number.parseInt and parseFloat 275,185 → 256,799 -6.7% 169,727 → 182,286 🟢 +7.4%
objects.js — Interp: 7 unch. · avg +0.1% · Bytecode: 🟢 3, 4 unch. · avg +5.5%
Benchmark Interpreted Δ Bytecode Δ
create simple object 508,777 → 484,731 -4.7% 982,946 → 1,042,975 +6.1%
create nested object 227,848 → 228,216 +0.2% 420,100 → 452,092 🟢 +7.6%
create 50 objects via Array.from 9,816 → 9,300 -5.3% 8,754 → 9,489 🟢 +8.4%
property read 590,579 → 604,370 +2.3% 862,501 → 874,535 +1.4%
Object.keys 287,649 → 290,013 +0.8% 195,708 → 207,663 +6.1%
Object.entries 98,355 → 101,284 +3.0% 72,555 → 70,874 -2.3%
spread operator 182,651 → 190,609 +4.4% 176,892 → 196,514 🟢 +11.1%
promises.js — Interp: 🟢 2, 10 unch. · avg +3.1% · Bytecode: 🟢 7, 5 unch. · avg +11.4%
Benchmark Interpreted Δ Bytecode Δ
Promise.resolve(value) 546,052 → 557,231 +2.0% 370,586 → 374,763 +1.1%
new Promise(resolve => resolve(value)) 191,398 → 187,954 -1.8% 151,062 → 158,429 +4.9%
Promise.reject(reason) 559,341 → 544,300 -2.7% 342,068 → 360,064 +5.3%
resolve + then (1 handler) 170,052 → 173,313 +1.9% 136,340 → 160,452 🟢 +17.7%
resolve + then chain (3 deep) 66,250 → 68,623 +3.6% 59,937 → 69,123 🟢 +15.3%
resolve + then chain (10 deep) 19,586 → 22,230 🟢 +13.5% 20,129 → 21,427 +6.4%
reject + catch + then 97,956 → 99,623 +1.7% 76,999 → 95,116 🟢 +23.5%
resolve + finally + then 84,689 → 84,041 -0.8% 62,510 → 77,567 🟢 +24.1%
Promise.all (5 resolved) 30,399 → 31,799 +4.6% 25,088 → 28,766 🟢 +14.7%
Promise.race (5 resolved) 32,386 → 33,755 +4.2% 28,316 → 29,796 +5.2%
Promise.allSettled (5 mixed) 25,857 → 26,710 +3.3% 21,669 → 23,338 🟢 +7.7%
Promise.any (5 mixed) 29,488 → 31,752 🟢 +7.7% 25,192 → 27,803 🟢 +10.4%
strings.js — Interp: 🟢 1, 10 unch. · avg +2.7% · Bytecode: 🟢 1, 10 unch. · avg +3.5%
Benchmark Interpreted Δ Bytecode Δ
string concatenation 326,897 → 403,396 🟢 +23.4% 383,560 → 401,361 +4.6%
template literal 605,076 → 630,618 +4.2% 667,299 → 756,862 🟢 +13.4%
string repeat 433,913 → 414,311 -4.5% 1,102,748 → 1,140,320 +3.4%
split and join 131,517 → 137,291 +4.4% 328,386 → 342,074 +4.2%
indexOf and includes 178,913 → 173,327 -3.1% 760,145 → 759,157 -0.1%
toUpperCase and toLowerCase 248,816 → 258,778 +4.0% 736,415 → 776,036 +5.4%
slice and substring 158,684 → 162,159 +2.2% 896,862 → 870,407 -2.9%
trim operations 188,003 → 190,497 +1.3% 647,574 → 680,083 +5.0%
replace and replaceAll 210,902 → 213,791 +1.4% 623,104 → 638,246 +2.4%
startsWith and endsWith 142,018 → 137,035 -3.5% 648,035 → 670,379 +3.4%
padStart and padEnd 203,122 → 203,565 +0.2% 699,935 → 697,267 -0.4%
typed-arrays.js — Interp: 🔴 5, 17 unch. · avg -1.5% · Bytecode: 🟢 1, 21 unch. · avg +1.2%
Benchmark Interpreted Δ Bytecode Δ
new Int32Array(0) 316,016 → 327,193 +3.5% 133,486 → 138,526 +3.8%
new Int32Array(100) 295,685 → 304,422 +3.0% 130,638 → 133,212 +2.0%
new Int32Array(1000) 195,284 → 173,151 🔴 -11.3% 78,017 → 79,666 +2.1%
new Float64Array(100) 276,996 → 272,582 -1.6% 112,634 → 117,767 +4.6%
Int32Array.from([...]) 200,836 → 188,839 -6.0% 163,998 → 160,919 -1.9%
Int32Array.of(1, 2, 3, 4, 5) 304,210 → 323,877 +6.5% 242,560 → 254,781 +5.0%
sequential write 100 elements 3,440 → 3,609 +4.9% 15,015 → 14,542 -3.2%
sequential read 100 elements 3,457 → 3,648 +5.5% 11,039 → 10,566 -4.3%
Float64Array write 100 elements 3,237 → 3,316 +2.5% 14,200 → 14,127 -0.5%
fill(42) 58,063 → 47,869 🔴 -17.6% 44,871 → 46,275 +3.1%
slice() 218,513 → 206,580 -5.5% 184,619 → 186,085 +0.8%
map(x => x * 2) 8,047 → 8,075 +0.3% 7,943 → 8,207 +3.3%
filter(x => x > 50) 8,227 → 8,315 +1.1% 8,043 → 8,337 +3.7%
reduce (sum) 7,703 → 7,895 +2.5% 7,147 → 7,325 +2.5%
sort() 200,300 → 169,959 🔴 -15.1% 160,164 → 152,297 -4.9%
indexOf() 493,249 → 450,606 🔴 -8.6% 375,569 → 371,119 -1.2%
reverse() 366,688 → 340,340 🔴 -7.2% 283,854 → 277,470 -2.2%
create view over existing buffer 406,531 → 416,738 +2.5% 152,093 → 155,623 +2.3%
subarray() 456,752 → 462,934 +1.4% 357,393 → 355,769 -0.5%
set() from array 617,558 → 600,151 -2.8% 248,370 → 278,182 🟢 +12.0%
for-of loop 4,708 → 4,976 +5.7% 20,151 → 19,930 -1.1%
spread into array 16,272 → 16,783 +3.1% 55,332 → 55,756 +0.8%

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 23, 2026

Suite Timing

Suite Metric Interpreted Bytecode
Tests Total 3510 3510
Tests Passed 3469 ✅ 3510 ✅
Tests Skipped 41 0
Tests Execution 144.9ms 167.7ms
Tests Engine 282.5ms 598.9ms
Benchmarks Total 263 263
Benchmarks Duration 7.22min 12.22min

Measured on ubuntu-latest x64.

…ratorNext fast paths

- Add TGocciaSouffleMapIterator and TGocciaSouffleSetIterator to
  Goccia.Runtime.Collections with proper GC marking
- Add NativeMapIteratorNext/NativeSetIteratorNext callbacks with
  MAP_ITERATOR_METHODS/SET_ITERATOR_METHODS delegate tables
- Add FMapIteratorDelegate/FSetIteratorDelegate fields with GC marking
- Wire GetProperty to resolve .next() on native iterators
- Wire GetIterator to recognize native Map/Set types and return
  native iterators (entries for Map, values for Set)
- Wire GetIterator to recognize native iterators as self-iterating
  (Symbol.iterator returns self)
- Wire IteratorNext fast paths for native Map/Set iterators
- Add native fast paths for keys/values/entries methods returning
  native iterators instead of bridging to InvokeGocciaMethod

Note: ToSouffleValue still wraps Map/Set as TGocciaWrappedValue since
the native types don't yet support all operations (getOrInsert, Set
operations, subclassing). The callback fast paths check both native
and wrapped types so they're ready for future activation.

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

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (1)
units/Goccia.Runtime.Operations.pas (1)

5894-5983: ⚠️ Potential issue | 🟠 Major

Pass undefined to native Map/Set methods when arguments are omitted.

The native fast paths for Map.prototype.get, Map.prototype.has, Map.prototype.delete and Set.prototype.has, Set.prototype.delete, Set.prototype.add currently exit early when AArgCount < 1, but ECMAScript passes undefined as the key/value. This causes interpreter and native code paths to diverge: m.set(undefined, 1); m.get() will find the entry in interpreter mode but not with a native Map receiver.

Treat missing arguments as undefined by always passing them through to the underlying GetEntry, HasKey, DeleteEntry, Contains, Delete, and Add methods, matching interpreter-mode behavior.

🤖 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 5894 - 5983, Native
fast-paths (NativeMapGet, NativeMapSet, NativeMapHas, NativeMapDelete) currently
early-exit when AArgCount < 1, but ECMAScript semantics treat missing args as
undefined; update the SM (GetSouffleMap) branches to synthesize a TSouffleValue
= SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED) when arguments are missing and pass
that value to SM.GetEntry, SM.HasKey, SM.DeleteEntry (and to SM.SetEntry for
missing key/value), and for NativeMapSet ensure zero-arg and one-arg cases call
SM.SetEntry with undefined for the missing key/value and still return AReceiver;
keep using the existing AArgs^ when present and reference the named symbols
GetEntry, HasKey, DeleteEntry and SetEntry to locate changes.
🤖 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 6008-6020: When AReceiver is a native Souffle collection (detected
by GetSouffleMap/GetSouffleSet and SouffleIsReference) do not call
InvokeGocciaMethod; instead validate that the first argument is a callable
function (throw a proper TypeError if missing or not callable), capture the
optional thisArg (second parameter) and pass it into the per-entry invocation
(preserving context) when calling GNativeArrayJoinRuntime.Invoke (or
equivalent), and only call InvokeGocciaMethod for non-native/wrapped instances;
update the Map.prototype.forEach/Set.prototype.forEach branch to perform these
checks and use the native iteration path instead of routing to
InvokeGocciaMethod.
- Around line 6086-6091: Iterator result records created in
NativeMapIteratorNext and NativeSetIteratorNext (where TSouffleRecord.Create is
used to build Rec with 'value' and 'done') are missing the delegate causing
Object.prototype methods to fail in bytecode mode; after creating Rec (and
before allocating/returning it) set Rec.Delegate :=
GNativeArrayJoinRuntime.VM.RecordDelegate and replace literal property names
('value' and 'done') with the appropriate property name constants used elsewhere
in the codebase so the put calls use those constants instead of string literals.
- Around line 2774-2805: GetProperty contains branches for
TGocciaSouffleMap/Set/MapIterator/SetIterator but other bridge methods
(UnwrapToGocciaValue, HasProperty, SetProperty, DeleteProperty,
GetSymbolOnNativeObject) and ToSouffleValue still treat
TGocciaMapValue/TGocciaSetValue as TGocciaWrappedValue, so native heap types are
never returned; either extend those methods to recognize and handle
TGocciaSouffleMap and TGocciaSouffleSet (and the iterator types) the same way
GetProperty does, and call ConvertGocciaMapToSouffle/ConvertGocciaSetToSouffle
from ToSouffleValue when encountering TGocciaMapValue/TGocciaSetValue, or add a
clear TODO comment referencing the tracking issue and ensure all places
consistently wrap rather than return native heap types; update
UnwrapToGocciaValue, HasProperty, SetProperty, DeleteProperty,
GetSymbolOnNativeObject and ToSouffleValue to use the same detection logic as
GetProperty (checking SouffleIsReference and AsReference is
TGocciaSouffleMap/Set/MapIterator/SetIterator) so native collections are fully
supported.

---

Outside diff comments:
In `@units/Goccia.Runtime.Operations.pas`:
- Around line 5894-5983: Native fast-paths (NativeMapGet, NativeMapSet,
NativeMapHas, NativeMapDelete) currently early-exit when AArgCount < 1, but
ECMAScript semantics treat missing args as undefined; update the SM
(GetSouffleMap) branches to synthesize a TSouffleValue =
SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED) when arguments are missing and pass
that value to SM.GetEntry, SM.HasKey, SM.DeleteEntry (and to SM.SetEntry for
missing key/value), and for NativeMapSet ensure zero-arg and one-arg cases call
SM.SetEntry with undefined for the missing key/value and still return AReceiver;
keep using the existing AArgs^ when present and reference the named symbols
GetEntry, HasKey, DeleteEntry and SetEntry to locate changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc561677-bf34-4a8f-a4a2-61cc792c0154

📥 Commits

Reviewing files that changed from the base of the PR and between f2e66e5 and 2a66e00.

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

Comment thread units/Goccia.Runtime.Operations.pas
Comment thread units/Goccia.Runtime.Operations.pas
Comment thread units/Goccia.Runtime.Operations.pas
frostney and others added 2 commits March 23, 2026 15:49
Resolve uses clause conflict: keep both Goccia.Parser (from main)
and Goccia.Runtime.Collections (from this branch).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set Rec.Delegate := VM.RecordDelegate on iterator result {value, done}
records so Object.prototype methods work in bytecode mode. Use
PROP_VALUE/PROP_DONE constants instead of string literals.

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

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
units/Goccia.Runtime.Operations.pas (1)

6153-6168: ⚠️ Potential issue | 🟡 Minor

Treat omitted Map arguments as undefined.

These guards turn map.get(), map.has(), and map.set() into early returns/no-ops, but the collection APIs are supposed to operate on an undefined key/value when an argument is omitted. As written, an undefined key cannot be observed or created through these paths. Normalize missing arguments to GOCCIA_NIL_UNDEFINED here, and mirror the same fix in the wrapped branch immediately below so both receivers stay aligned.

Also applies to: 6178-6201, 6211-6221

🤖 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 6153 - 6168, The code
currently treats omitted map arguments as early exits, preventing
observing/creating undefined keys; change the argument handling in both the
SouffleMap branch (when SM := GetSouffleMap(AReceiver) is Assigned) and the
unwrapped branch (M := UnwrapMapFromReceiver(AReceiver)) so that when AArgCount
< 1 you do not return but instead synthesize a key/value of GOCCIA_NIL_UNDEFINED
(use SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED) or the equivalent Goccia value
used by GNativeArrayJoinRuntime.UnwrapToGocciaValue) before calling SM.GetEntry
/ M.FindEntry and accessing M.Entries; apply the same normalization to the
corresponding code paths for map.get, map.has, and map.set (also in the nearby
blocks at the indicated ranges) so both SM and M branches behave identically
with omitted args.
🧹 Nitpick comments (2)
units/Goccia.Runtime.Operations.pas (2)

7833-7839: Use PROP_NEXT in the iterator delegate tables.

The new iterator tables reintroduce a raw property name. Switching these entries to PROP_NEXT keeps the property key centralized with the rest of the bridge.

As per coding guidelines "Use constant names from split 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, and 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` around lines 7833 - 7839, Replace the
hardcoded 'next' property name in the iterator method tables with the
centralized constant PROP_NEXT: update MAP_ITERATOR_METHODS and
SET_ITERATOR_METHODS entries so their Name uses PROP_NEXT (leaving Arity and
Callback as-is pointing to NativeMapIteratorNext and NativeSetIteratorNext) to
conform with Goccia.Constants.PropertyNames usage.

6145-6487: Add ES spec headers to the new collection callbacks.

This whole native Map/Set/iterator block is spec-facing behavior, but it lands without the // ES2026 §... annotations used elsewhere in the unit. Adding those headers will make later semantic changes much easier to audit.

As per coding guidelines "Annotate ECMAScript-specified behavior with spec references using format // ESYYYY §X.Y.Z SpecMethodName(specParams) immediately above the function body, with inline annotations for multi-step algorithms".

🤖 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 6145 - 6487, The new native
Map/Set/iterator functions lack ECMAScript spec headers; add ES spec comment
lines immediately above each affected function (e.g. NativeMapGet, NativeMapSet,
NativeMapHas, NativeMapDelete, NativeMapClear, NativeMapForEach, NativeMapKeys,
NativeMapValues, NativeMapEntries, NativeMapIteratorNext, NativeSetIteratorNext,
NativeSetHas, NativeSetAdd, NativeSetDelete, NativeSetClear, NativeSetForEach,
NativeSetValues, NativeSetEntries) using the project convention "// ES2026
§X.Y.Z SpecMethodName(specParams)" (and inline-step annotations where the
algorithm maps to multi-step spec text) so each function is annotated with the
corresponding ECMAScript clause just above the function declaration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@units/Goccia.Runtime.Operations.pas`:
- Around line 6153-6168: The code currently treats omitted map arguments as
early exits, preventing observing/creating undefined keys; change the argument
handling in both the SouffleMap branch (when SM := GetSouffleMap(AReceiver) is
Assigned) and the unwrapped branch (M := UnwrapMapFromReceiver(AReceiver)) so
that when AArgCount < 1 you do not return but instead synthesize a key/value of
GOCCIA_NIL_UNDEFINED (use SouffleNilWithFlags(GOCCIA_NIL_UNDEFINED) or the
equivalent Goccia value used by GNativeArrayJoinRuntime.UnwrapToGocciaValue)
before calling SM.GetEntry / M.FindEntry and accessing M.Entries; apply the same
normalization to the corresponding code paths for map.get, map.has, and map.set
(also in the nearby blocks at the indicated ranges) so both SM and M branches
behave identically with omitted args.

---

Nitpick comments:
In `@units/Goccia.Runtime.Operations.pas`:
- Around line 7833-7839: Replace the hardcoded 'next' property name in the
iterator method tables with the centralized constant PROP_NEXT: update
MAP_ITERATOR_METHODS and SET_ITERATOR_METHODS entries so their Name uses
PROP_NEXT (leaving Arity and Callback as-is pointing to NativeMapIteratorNext
and NativeSetIteratorNext) to conform with Goccia.Constants.PropertyNames usage.
- Around line 6145-6487: The new native Map/Set/iterator functions lack
ECMAScript spec headers; add ES spec comment lines immediately above each
affected function (e.g. NativeMapGet, NativeMapSet, NativeMapHas,
NativeMapDelete, NativeMapClear, NativeMapForEach, NativeMapKeys,
NativeMapValues, NativeMapEntries, NativeMapIteratorNext, NativeSetIteratorNext,
NativeSetHas, NativeSetAdd, NativeSetDelete, NativeSetClear, NativeSetForEach,
NativeSetValues, NativeSetEntries) using the project convention "// ES2026
§X.Y.Z SpecMethodName(specParams)" (and inline-step annotations where the
algorithm maps to multi-step spec text) so each function is annotated with the
corresponding ECMAScript clause just above the function declaration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ebf79230-eb56-4986-bb3c-0f8de2f7fd93

📥 Commits

Reviewing files that changed from the base of the PR and between b215df3 and 6718d63.

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

@frostney frostney merged commit a46d89f into main Mar 23, 2026
9 checks passed
@frostney frostney deleted the feature/map-set-souffle-backing branch March 23, 2026 16:13
@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