Skip to content

Shape-backed object literals inside generator/async frames when values cannot suspend#2596

Merged
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf-generator-literal-shape
Jul 7, 2026
Merged

Shape-backed object literals inside generator/async frames when values cannot suspend#2596
lahma merged 1 commit into
sebastienros:mainfrom
lahma:perf-generator-literal-shape

Conversation

@lahma

@lahma lahma commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Shape-backed object literals inside generator/async frames when values cannot suspend

Follow-up to #2571/#2593: with the per-yield handler rebuild fixed, the dominant remaining allocation in generator bodies that build object literals is the dictionary build path itself — one PropertyDescriptor + a PropertyDictionary (plus its ListDictionary head) per constructed object.

That path exists because a literal evaluated inside a suspendable frame can suspend mid-build if a property value contains yield/await, and the shape-backed fast build (slot fill, no descriptors) has no resume support. But the exclusion was frame-based, not literal-based: yield { a: i, b: i + 1 } — a literal that can never suspend its own build — paid the dictionary path merely for being inside a generator.

Change

At handler construction, prove per literal that no property value contains a lexical yield/await:

  • descent stops at function boundaries (function/arrow) — suspensions there belong to the nested frame;
  • class nodes are descended conservatively (computed member keys evaluate in the enclosing frame); in practice class-expression values already take the dictionary path via the existing function-definition exclusion;
  • proof only computed for literals already eligible for the fast build (static distinct keys, no spread/accessors/function shorthand).

Proven-suspend-free literals then take the existing shape-backed build (BuildObjectFastShapeBacked) even when ExecutionContext.Suspendable is set. Literals whose values do contain yield/await keep the dictionary path and its suspend/resume machinery unchanged.

Results

Census workload (generator yielding { a: i, b: i + 1, c: i + 2 } 100k times, fresh engine per iter):

main this PR
allocated/iter 53.0 MB 23.1 MB (−56%)
PropertyDescriptor share 18.3% eliminated
HybridDictionary/ListDictionary share 12.3% eliminated

Remaining allocations are user-visible: the literal objects themselves (45%), IteratorResult from explicit .next() calls (40%), boxed numbers (13%).

GeneratorBenchmark and AsyncFunctionExitBenchmark are neutral with byte-identical allocations (their bodies build no literals — the census script is the allocation authority for this pattern).

Testing

  • Full test262: 99,260 passed, 0 failed, 133 skipped (yield-inside-literal suspension/resume is heavily covered there)
  • Jint.Tests + Jint.Tests.PublicInterface green (net10.0 + net472)
  • Smoke-verified: mid-build suspension ({ first: 1, second: yield "mid", third: 3 }) resumes with correct values and key order; nested deep yield keeps the outer literal on the resumable path

🤖 Generated with Claude Code

https://claude.ai/code/session_01S2U2y9voMQV9rvCkYeQbka

…alues cannot suspend

Any object literal evaluated inside a suspendable frame previously took the
dictionary build path (per-property PropertyDescriptor + PropertyDictionary
per object) because a property value containing yield/await can suspend the
build mid-fill. Most literals in generator bodies have no such values: prove
at handler-construction time that the value expressions contain no lexical
yield or await (function boundaries excluded, class nodes descended
conservatively) and let those literals use the shared hidden-class shape +
flat slot array build.

Generator-literal census workload: 53 -> 23.1 MB/iter (-56%); the remaining
allocations are the user-visible objects and iterator results. Generator and
async-exit suites neutral with byte-identical allocations. Full test262
green; mid-build suspension for literals that do contain yield keeps the
dictionary path and resumes exactly as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2U2y9voMQV9rvCkYeQbka
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