Skip to content

Save and restore generator locals in bulk with dedicated bytecodes - #525

Open
sosukesuzuki wants to merge 1 commit into
mainfrom
claude/generator-bulk-save-restore-locals
Open

Save and restore generator locals in bulk with dedicated bytecodes#525
sosukesuzuki wants to merge 1 commit into
mainfrom
claude/generator-bulk-save-restore-locals

Conversation

@sosukesuzuki

@sosukesuzuki sosukesuzuki commented Aug 27, 2026

Copy link
Copy Markdown
Member

Summary

Generatorification currently spills every live local with its own op_put_to_scope at every suspension point and reloads each with op_get_from_scope on resume, so generator / async-function bytecode grows as live locals × suspension points. Large async functions and async generators end up with bytecode that is mostly this save/restore code, plus a proportional amount of Baseline machine code and 48 bytes of linked metadata per local per suspension point.

This PR replaces the per-local sequences with two bytecodes, op_save_generator_locals and op_restore_generator_locals, which copy all live locals at once:

  • Each op names a per-suspension-point liveness bit vector and the function-wide union of live locals. Both live in the existing UnlinkedCodeBlock rare-data bit vector table, deduplicated by content.
  • A local's slot in the generator frame is its rank within the union, so slots stay stable across suspension points and the frame is sized by the number of ever-live locals. Locals no longer need per-local identifiers, SymbolTableEntrys, watchpoint sets, or metadata.
  • LLInt: one C++ slow path per op, with a single write barrier for the whole save.
  • Baseline: an unrolled load/store sequence; restore also stores into the op's run of consecutive value profiles.
  • DFG/FTL: the parser expands the ops into the same PutClosureVar / GetClosureVar nodes as before, so optimized code is unchanged. OSR-exit value feedback is routed through lazy operand profiles keyed by the restored local (Graph::methodOfGettingAValueProfileFor).
  • Bytecode liveness (BytecodeUseDef) treats the save as using the live locals and the restore as defining them.

Option

The new encoding is gated by Options::useGeneratorBulkSaveRestore (default true). With --useGeneratorBulkSaveRestore=0, generatorification emits the previous per-local op_put_to_scope / op_get_from_scope sequences. Every tier executes both encodings regardless of the option, so bytecode cached under one setting still runs under the other (verified both directions with --diskCachePath + --forceDiskCache=1).

Measurements

Same RelWithDebInfo jsc binary, option on vs off (arm64 macOS, best of 5, 2M resumes, N = locals live across each suspension point).

Bytecode for an async function with 100 locals and 200 awaits:

instructions bytes
option off (per-local) 32,302 527,786
option on (bulk) 2,006 11,683

ns per yield, LLInt only (--useJIT=0):

N off on
3 103.0 98.3
35 249.9 183.7
100 532.1 390.9

ns per yield, Baseline only (--useDFGJIT=0):

N off on
3 33.1 29.8
35 65.6 43.8
100 137.2 77.8

All tiers enabled: flat within noise for both yield and await (e.g. N=100: 40.6 → 40.2 ns/yield, 54.5 → 55.9 ns/await), as expected since the DFG graph is identical. Since bytecodeCost drops accordingly, large async functions that previously sat above the DFG size limit can now tier up.

Test plan

  • New JSTests/stress/generator-save-restore-locals.js: mixed-type locals resumed with next/throw, live sets of 3/9/70/300 locals (out-of-line bit vectors, wide operands), TDZ locals across a yield, locals sharing the frame with captured variables, nothing live, async function / async generator / yield*, and a tier-up-then-type-change case. Runs under default, --useJIT=0, --useDFGJIT=0, eager tier-up, --useGeneratorBulkSaveRestore=0, and runBytecodeCache.
  • Locally (DebugNoAsan): the new test under all of the above plus --validateGraph=1 and --useLOLJIT=1; the 174 existing generator* / async* / yield* / await* stress tests under default, eager tier-up, --useJIT=0, and --useGeneratorBulkSaveRestore=0 — no failures.
  • Bytecode cache: write with one option value, read back with --forceDiskCache=1 under the other — passes both ways.

@sosukesuzuki
sosukesuzuki force-pushed the claude/generator-bulk-save-restore-locals branch from 32f962c to 998a225 Compare August 27, 2026 16:10
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 23 days. After that, they cost $0.25 per reviewed file.

Or wait 1 minute for your next included review.

View limit details

Limit details: You’ve used all 5 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 82f450d6-454f-4f6a-abf7-a58f993230b1

📥 Commits

Reviewing files that changed from the base of the PR and between ceb9f90 and c2fd836.

📒 Files selected for processing (17)
  • JSTests/stress/generator-save-restore-locals.js
  • Source/JavaScriptCore/bytecode/BytecodeGeneratorification.cpp
  • Source/JavaScriptCore/bytecode/BytecodeGeneratorification.h
  • Source/JavaScriptCore/bytecode/BytecodeList.rb
  • Source/JavaScriptCore/bytecode/BytecodeUseDef.cpp
  • Source/JavaScriptCore/bytecode/BytecodeUseDef.h
  • Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
  • Source/JavaScriptCore/dfg/DFGGraph.cpp
  • Source/JavaScriptCore/jit/JIT.cpp
  • Source/JavaScriptCore/jit/JIT.h
  • Source/JavaScriptCore/jit/JITInlines.h
  • Source/JavaScriptCore/jit/JITOpcodes.cpp
  • Source/JavaScriptCore/llint/LowLevelInterpreter.asm
  • Source/JavaScriptCore/lol/LOLJIT.cpp
  • Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
  • Source/JavaScriptCore/runtime/CommonSlowPaths.h
  • Source/JavaScriptCore/runtime/OptionsList.h

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f0fe9ec-5769-47c5-9877-4ed4c1093c79

📥 Commits

Reviewing files that changed from the base of the PR and between 86e19b8 and eea3372.

📒 Files selected for processing (17)
  • JSTests/stress/generator-save-restore-locals.js
  • Source/JavaScriptCore/bytecode/BytecodeGeneratorification.cpp
  • Source/JavaScriptCore/bytecode/BytecodeGeneratorification.h
  • Source/JavaScriptCore/bytecode/BytecodeList.rb
  • Source/JavaScriptCore/bytecode/BytecodeUseDef.cpp
  • Source/JavaScriptCore/bytecode/BytecodeUseDef.h
  • Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp
  • Source/JavaScriptCore/dfg/DFGGraph.cpp
  • Source/JavaScriptCore/jit/JIT.cpp
  • Source/JavaScriptCore/jit/JIT.h
  • Source/JavaScriptCore/jit/JITInlines.h
  • Source/JavaScriptCore/jit/JITOpcodes.cpp
  • Source/JavaScriptCore/llint/LowLevelInterpreter.asm
  • Source/JavaScriptCore/lol/LOLJIT.cpp
  • Source/JavaScriptCore/runtime/CommonSlowPaths.cpp
  • Source/JavaScriptCore/runtime/CommonSlowPaths.h
  • Source/JavaScriptCore/runtime/OptionsList.h

Included review availability: 4 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.


Walkthrough

Changes

The change adds bulk and per-local generator-local save/restore bytecodes, integrates them across JavaScriptCore execution and optimization tiers, and adds stress coverage for synchronous, asynchronous, delegated, TDZ, closure, and tiered generator execution.

Generator local persistence

Layer / File(s) Summary
Bytecode generation strategy
Source/JavaScriptCore/bytecode/BytecodeGeneratorification.*, Source/JavaScriptCore/bytecode/BytecodeList.rb, Source/JavaScriptCore/runtime/OptionsList.h
Generatorification selects bulk or per-local persistence, assigns generator-frame slots, and emits save and restore bytecodes for live locals.
Interpreter and JIT execution
Source/JavaScriptCore/jit/*, Source/JavaScriptCore/llint/LowLevelInterpreter.asm, Source/JavaScriptCore/lol/LOLJIT.cpp, Source/JavaScriptCore/runtime/CommonSlowPaths.*
The execution tiers save locals to lexical environments, restore them to virtual registers, apply write barriers, and record value profiles.
Use-def and DFG analysis
Source/JavaScriptCore/bytecode/BytecodeUseDef.*, Source/JavaScriptCore/dfg/*
Use-def analysis tracks saved and restored locals. DFG parsing and profile lookup support closure-variable restoration and OSR-safe state updates.
Generator suspension coverage
JSTests/stress/generator-save-restore-locals.js
The stress test covers mixed local types, wide live-local sets, TDZ, closures, empty live sets, asynchronous generators, yield*, tiering, caching, and microtask completion.

Merge Risk: ⚪ Minimal · up to eea33

The change introduces bulk generator-local save and restore bytecodes without any supplied evidence of a concrete correctness or merge-readiness issue. It is merge-ready after normal checks, with generated-artifact confirmation as a routine follow-up.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description gives a detailed rationale, implementation summary, measurements, and test plan. However, it does not follow the required template because it omits the bug title and Bugzilla link, the… Add the associated bug title and Bugzilla URL, include the required "Reviewed by NOBODY (OOPS!)." line or actual reviewer information, and list each changed path with relevant functions or classes.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding dedicated bytecodes to bulk-save and restore generator locals.
Full details: Description check

Explanation

The description gives a detailed rationale, implementation summary, measurements, and test plan. However, it does not follow the required template because it omits the bug title and Bugzilla link, the review status line, and the required changed-file and function list.

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

Preview Builds

Commit Release Date
c2fd8368 autobuild-preview-pr-525-c2fd8368 2026-08-28 14:27:18 UTC
eea33720 autobuild-preview-pr-525-eea33720 2026-08-28 00:55:43 UTC
998a2251 autobuild-preview-pr-525-998a2251 2026-08-27 17:01:01 UTC

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review found no issues

No high-confidence issues detected in this change.

@sosukesuzuki
sosukesuzuki force-pushed the claude/generator-bulk-save-restore-locals branch from 998a225 to eea3372 Compare August 28, 2026 00:02
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
The new head renames the option to useGeneratorBulkSaveRestore and drops
the option-off eager tier-up run from the stress test. The fixture and the
bytecode round trip follow. The bytecode portability snapshot is unchanged.
Generatorification spills every live local with its own op_put_to_scope at
every suspension point and reloads each with op_get_from_scope on resume, so
generator and async-function bytecode grows as live locals x suspension
points. Large async generators compile to bytecode that is mostly this
save/restore code, plus Baseline machine code and linked metadata per local
per suspension point.

Replace the per-local sequences with two bytecodes,
op_save_generator_locals and op_restore_generator_locals, which copy all live
locals at once. Each op names a per-suspension-point liveness bit vector and
the function-wide union of live locals (both stored in the existing
UnlinkedCodeBlock rare-data bit vector table, deduplicated by content); a
local's frame slot is its rank within the union, so slots stay stable across
suspension points and the generator frame is sized by the number of
ever-live locals. Locals no longer need per-local identifiers,
SymbolTableEntries, watchpoint sets, or metadata. LLInt runs one C++ slow
path per op with a single write barrier; the Baseline JIT emits an unrolled
load/store sequence (restore also stores into the op's run of consecutive
value profiles); the DFG parser expands the ops into the same PutClosureVar /
GetClosureVar nodes as before, so optimized code is unchanged, with OSR-exit
value feedback routed through lazy operand profiles keyed by the restored
local.

The new encoding is behind Options::useGeneratorBulkSaveRestore (default
true). With the option off, generatorification emits the previous per-local
op_put_to_scope / op_get_from_scope sequences. Every tier executes both
encodings, so cached bytecode produced under either setting runs regardless
of the current value.
@robobun
robobun force-pushed the claude/generator-bulk-save-restore-locals branch from eea3372 to c2fd836 Compare August 28, 2026 13:54
@robobun

robobun commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Rebased onto main at ceb9f90fb774 (one commit, c2fd8368c3) so that the preview includes the provideModule changes bun main now needs. Two things needed a hand:

  • BytecodeUseDef.h: main now passes the functor straight to compute{Uses,Defs}ForBytecodeIndexImpl (no scopedLambda wrap). Kept that and added the op_save_generator_locals / op_restore_generator_locals blocks after the call.
  • DFGByteCodeParser.cpp, the op_restore_generator_locals case: main dropped the ConcurrentJSLocker parameter from ValueProfile::computeUpdatedPrediction() and LazyOperandValueProfileParser::prediction(), so the restore hunk now calls both without a locker, as the rest of the parser does.

Everything else applied cleanly. oven-sh/bun#40667 re-pins to this preview once it is published.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code review found no issues

No high-confidence issues detected in this change.

robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
…locals in bulk

Generatorification spilled every live local with its own op_put_to_scope
at every suspension point and reloaded each with op_get_from_scope on
resume, so generator and async-function bytecode grew as live locals x
suspension points. oven-sh/WebKit#525 replaces the per-local sequences
with op_save_generator_locals and op_restore_generator_locals, behind
Options::useGeneratorBulkSaveRestoreLocals (default on). This pins its
preview build.

Tests:
- test/js/bun/jsc-stress/fixtures/generator-save-restore-locals.js is
  the stress test from that PR. The harness now runs a fixture once per
  `//@ run*` directive, as run-jsc-stress-tests does, so it runs under
  the default options, --useJIT=0, --useDFGJIT=0, eager tier-up, and
  with the option off (x2).
- A `bun build --bytecode` round trip of the fixture: a cache built with
  the option on or off must load (Cache hit) and run with it on or off.
- preload.js gains drainMicrotasks from bun:jsc.
robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
The updated head renames the option to useGeneratorBulkSaveRestore and
drops the option-off eager tier-up run from the stress test. The fixture
and the bytecode round trip follow.
robobun added a commit to oven-sh/bun that referenced this pull request Aug 28, 2026
…estore ops

Only the .jsc sizes and hashes move (26 entries), the source hashes do not.
Regenerated against autobuild-preview-pr-525-c2fd8368, which is main's
WebKit pin (ceb9f90fb774) plus the oven-sh/WebKit#525 commit.
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.

2 participants