Skip to content

[JSC] Give generator and async function bodies the inferred name while parsing - #820

Open
robobun wants to merge 2 commits into
mainfrom
robobun/e99ebd49/async-body-names
Open

robobun wants to merge 2 commits into
mainfrom
robobun/e99ebd49/async-body-names

Conversation

@robobun

@robobun robobun commented Oct 16, 2026

Copy link
Copy Markdown
Collaborator

Replaces #426. That PR copied the name after code generation. The review there asked for it to be done while parsing.

Problem

  • A stack frame of an async function or generator whose name was inferred from its binding has no name. Bun prints at async <anonymous> for const f = async () => { await g(); }, Node prints at async f, and f.name is "f".
  • Error.captureStackTrace(err, f) in Bun looks for the frame of f by name. It finds none, so err.stack is Error: boom alone.
  • The cause is in generateUnlinkedFunctionCodeBlock (bytecode/UnlinkedFunctionExecutable.cpp). It reparses the function with executable->name(), the declared name. An inferred name is only in ecmaName() and never reaches the parser.

Fix

  • generateUnlinkedFunctionCodeBlock passes the inferred name of a function without a declared name to the parser (Parser::setInferredFunctionName).
  • parseInner names the body function after the declared name, or else after the inferred one. The inferred name is not declared as a callee in the function's scope.
  • A private name is not used (export default async function () {} is *default*), as for a declared name.
  • Verified: JSTests/stress/generator-and-async-body-function-name.js in default, no-jit, dfg-eager and ftl-eager runs. It fails without the change. The Bun tests are in the Bun PR that pins this build.

Background

  • A generator, an async generator and an async function that awaits are compiled as a wrapper function. Each call makes a body function that runs the code.
  • The body function is the callee of the frames that run that code and of the at async frames. Its name is what stack traces, the sampling profiler and the inspector show. It is never visible to JavaScript.
  • ecmaName is the name a function has for Function.prototype.name. The parser infers it from const f = ..., { f: ... }, a class field or an assignment.

Downsides

  • A name that exists only at run time stays unnamed: a computed key, or a name redefined with Object.defineProperty.
  • Error.captureStackTrace(err, fn) in Bun can now cut at the frame of another async function with the inferred name of fn. It already does that for declared names.
  • Bytecode cache size and parse cost: measured numbers are in the Bun PR.

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Preview build of 1dce549: autobuild-preview-pr-820-1dce5490

@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.

Beyond the inline findings, I also checked the lifetime of the raw inferredName pointer handed to the parser: it points at the UnlinkedFunctionExecutable's m_ecmaName (materialized first if deferred), and the executable is held by the caller for the whole reparse, so the pointer stays valid; isSymbol() also correctly keeps *default* out of the body name.

Extended reasoning...

The change is confined to USE(BUN_JSC_ADDITIONS) blocks in the parser and UnlinkedFunctionExecutable reparse path plus one new stress test, and touches no security-sensitive surface. The two confirmed inline findings (an existing stress test asserting the old empty frame name, and the new test's loop count versus the 200ms budget) are what keep this from an approve.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread Source/JavaScriptCore/parser/Parser.cpp Resolved
@coderabbitai

coderabbitai Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 9929aee7-2f3d-4e8f-9460-a9d56614454a
📥 Commits

Reviewing files that changed from the base of the PR and between b1e30d0 and af807fa.

📒 Files selected for processing: 4
  • JSTests/stress/generator-and-async-body-function-name.js
  • Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp
  • Source/JavaScriptCore/parser/Parser.cpp
  • Source/JavaScriptCore/parser/Parser.h

Included review availability: This review used your included allowance. Your plan provides up to 2 included reviews per hour; 1 remain after this review.

Walkthrough

The change adds Bun-conditional inferred-name propagation for async-function and generator wrapper parsing. A stress test captures stack traces and checks inferred names across several function forms.

Changes

Async and generator stack-frame names

Layer / File(s) Summary
Propagate inferred names into wrapper parsing
Source/JavaScriptCore/parser/Parser.h, Source/JavaScriptCore/bytecode/UnlinkedFunctionExecutable.cpp, Source/JavaScriptCore/parser/Parser.cpp
Under USE(BUN_JSC_ADDITIONS), parsing accepts an optional inferred name. Unnamed async-function, generator, and async-generator wrappers use that name when available.
Check inferred names in stack traces
JSTests/stress/generator-and-async-body-function-name.js
The stress test checks captured stack-frame names across declarations, expressions, properties, class fields, assignments, nested functions, and await contexts. It repeats the checks and drains microtasks.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to af807

The change gives generator and async function bodies their inferred names in stack frames, and it only takes effect in Bun builds. I found no concrete merge-blocking risk. A non-Bun test run could in principle fail the new stress test, but nothing supplied suggests one exists.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: passing inferred names to generator and async function bodies during parsing.
Description check Passed The description is detailed and directly explains the problem, fix, behavior, testing, and trade-offs. It does not include the required Bugzilla link, review status line, or changed-file list from the…
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.
Full details: Description check

Explanation

The description is detailed and directly explains the problem, fix, behavior, testing, and trade-offs. It does not include the required Bugzilla link, review status line, or changed-file list from the repository template, but it is otherwise mostly complete.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@robobun
robobun force-pushed the robobun/e99ebd49/async-body-names branch from af807fa to 35d17ed Compare October 17, 2026 10:39
robobun added a commit to oven-sh/bun that referenced this pull request Oct 18, 2026
…bodies

autobuild-preview-pr-820-35d17ed5 is the preview build of oven-sh/WebKit#820.
This pin is for the draft only: the merged sha replaces it.
…he inferred name, while parsing

A generator, an async generator and an async function that awaits are a
wrapper function that makes a separate body function. The body function
is the callee of the frames that run the function's code, and of the
frames in the await chain, so stack traces, the sampling profiler and the
inspector name those frames after the body's ecmaName.

The parser gives the body only the declared name of the wrapper: the
wrapper is reparsed with executable->name() as the callee name. A name
that was inferred from the binding (`const f = async () => { await g(); }`)
is in the wrapper's ecmaName only, so the frames of such a function had
no name while f.name is "f".

generateUnlinkedFunctionCodeBlock() now hands the inferred name of a
wrapper without a declared name to the parser, and parseInner() names the
body function after it. A declared name is used as before. An inferred
name is not a callee name, so it is not declared in the function's scope.
A private name (`export default async function () { }` is `*default*`) is
not used, as for a declared name.

This replaces the earlier attempt that copied the name onto the body
executable after code generation (#426).
no-tail-call-in-generator-and-async-function-bodies.js expected a resumed
async arrow body to have no name. It has the inferred name now.

generator-and-async-body-function-name.js ran every check testLoopCount
times. A frame's name does not depend on the tier, so it runs once.
@robobun
robobun force-pushed the robobun/e99ebd49/async-body-names branch from 35d17ed to 1dce549 Compare October 20, 2026 09:01

@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.

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