Skip to content

fix(transform): scan PutValueSet closures when seeding generator func/local ids - #4366

Merged
proggeramlug merged 1 commit into
mainfrom
fix-generator-exec
Jun 4, 2026
Merged

proggeramlug merged 1 commit into
mainfrom
fix-generator-exec

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

A module containing any generator (function*) mis-compiled every function expression assigned to a member via strict PutValue — obj.f = function(){...} — into the generator's synthesized iterator. Calling obj.f(...) returned {value: undefined, done: true} instead of invoking the function.

This is a single-root-cause codegen bug with broad blast radius: it breaks the common proto.method = function(){...} pattern (including the test262 assert.js harness's assert._isSameValue), which is why it surfaced as ~36 "generator semantics" failures that were actually function-dispatch failures.

Root cause

The generator state-machine transform seeds its synthesized next/return/throw closure ids from compute_max_func_id(module) + 1. But scan_expr_for_max_func (and its local-id sibling scan_expr_for_max_local) had no PutValueSet arm — obj.f = function(){} lowers to Expr::PutValueSet { value: Closure{...}, .. }, so the assigned Closure's func_id was invisible to the scan. With no other functions present max_id stayed 0, so the synthesized closures got func_id 1 — colliding with the user closure's func_id 1. At codegen there's one LLVM function per func_id, so obj.f(1,2) dispatched into the generator's next body.

HIR (before fix):
  generator g's "next" closure → func_id: 1
  o.f = function(a,b){return a+b} → Closure { func_id: 1 }   // collision

Fix

Add a PutValueSet arm to both scan_expr_for_max_func and scan_expr_for_max_local in generator/id_scan.rs, descending into target/key/value/receiver. This mirrors the existing collision fixes for PropertySet / ArrayPush / NativeMethodCall / array fast-paths (#393/#531/#154/#1824).

Results

test262, uncapped via scripts/parity_gap_report.py:

category before after
built-ins/GeneratorPrototype 50 24
built-ins/AsyncGeneratorPrototype 36 26
built-ins/GeneratorFunction 12 12
built-ins/AsyncGeneratorFunction 12 12

−36 across generator categories, no regressions.

Verified Node-identical in both PERRY_NO_AUTO_OPTIMIZE=1 and default builds:

function* g(){ yield 1; }
var o = {}; o.f = function(a,b){ return a+b; };
o.f(1,2)   // before: { value: undefined, done: true }   after: 3

Async functions + member-assigned closures and non-generator modules are unaffected (their dispatch was already correct / the transform only consumes this count when synthesizing generator closures). cargo test -p perry-transform 29/29, -p perry-runtime 977/977.

The remaining generator failures are separate, deeper semantics gaps (try/finally resumption sequencing, .return/.throw state transitions, method length/name descriptors).

…/local ids

A module containing any generator (`function*`) mis-compiled every
function expression assigned to a member via strict PutValue
(`obj.f = function(){...}`) into the generator's synthesized iterator:
calling `obj.f(...)` returned `{value: undefined, done: true}` instead of
invoking the function.

Root cause: the generator state-machine transform seeds its synthesized
`next`/`return`/`throw` closure ids from `compute_max_func_id(module) + 1`,
but `scan_expr_for_max_func` (and its local-id sibling) had no `PutValueSet`
arm — the assigned Closure was invisible to the scan, so `max_id` stayed
below it and the synthesized closures reused its func_id. At codegen one
LLVM function per func_id wins, so `obj.f` dispatched into the generator's
`next`. Adds `PutValueSet` arms to both scanners (mirrors the existing
PropertySet / ArrayPush / NativeMethodCall collision fixes).

test262: built-ins/GeneratorPrototype 50->24, AsyncGeneratorPrototype
36->26 (-36 across generator categories, no regressions). Verified
Node-identical in both PERRY_NO_AUTO_OPTIMIZE and default builds; async
functions and non-generator modules unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant