Skip to content

[JSC] Eagerly build AST for likely IIFEs#62019

Merged
webkit-commit-queue merged 1 commit into
WebKit:mainfrom
ast-hugger:eng/JSC-Eagerly-build-AST-for-likely-IIFEs
May 1, 2026
Merged

[JSC] Eagerly build AST for likely IIFEs#62019
webkit-commit-queue merged 1 commit into
WebKit:mainfrom
ast-hugger:eng/JSC-Eagerly-build-AST-for-likely-IIFEs

Conversation

@ast-hugger
Copy link
Copy Markdown
Contributor

@ast-hugger ast-hugger commented Apr 4, 2026

709e4e7

[JSC] Eagerly build AST for likely IIFEs
https://bugs.webkit.org/show_bug.cgi?id=311459
rdar://174062165

Reviewed by Yusuke Suzuki.

This patch detects likely IIFEs (immediately invoked function expressions) and builds
their AST right away, bypassing the usual initial syntax check.

In general, we can't tell if a function expression is immediately invoked until after we
parse it, but the following heuristic catches most real world cases. A function expression
is expected to be an IIFE if the token preceding the `function` keyword is either `(` or
`!` (`!` is commonly used by minifiers to introduce a function expression using one fewer
character than wrapping it in parentheses). Another potential heuristic is if the
`function` keyword is preceded by `,` and the expression before the comma was an IIFE.
This heuristic is not used at this time because the cost/benefit tradeoff is not as clear
in this case.

Key changes:

- A FunctionNode created when eagerly parsing an IIFE is independent from the AST created
  for the containing code and has its own lifetime. To support this, we change ParserArena
  and ParserArenaRoot so that an arena can be shared by multiple roots. A root no longer
  takes ownership of the arena memory, instead it retains a Ref to the arena.

- Eager IIFE parsing is controlled by two classes, EagerIIFEParseScope and
  EagerIIFEParseState. The scope is created in Parser::parseFunctionInfo() when the IIFE
  prediction heuristic triggers. The scope contains an ASTBuilder to use for the IIFE and
  an EagerIIFEParseState. The state is registered with the parser as an indication of the
  special parsing mode it is currently in. It collects parsing artifacts such as function
  parameter and source elements until we are ready to create the final FunctionNode.
  When parseFunctionInfo exits, the scope is destroyed and its destructor returns the
  parser into the original state.

- At the end of parseFunctionInfo, if an IIFE AST was eagerly built, we create a
  FunctionNode stored in m_eagerIIFERegistry. Parser::parse() checks the cache when the
  function is invoked and takes the ready-to-use AST from the cache.

Testing:
    - Regression-checked by existing tests.
    - Eager parsing is not directly testable in stress tests.
    - Added `JSTests/stress/eager-iife-column-tracking.js` to test the source column
    adjustment required on the first line of IIFEs, as uncovered by one of the layout
    tests.

Canonical link: https://commits.webkit.org/312444@main

2e6a77f

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows Apple Internal
✅ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 win ✅ 🛠 ios-apple
✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2 ❌ 🧪 win-tests ✅ 🛠 mac-apple
✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ✅ 🧪 api-mac ✅ 🧪 api-wpe ✅ 🛠 vision-apple
✅ 🧪 ios-wk2-wpt ❌ 🧪 api-mac-debug ✅ 🛠 gtk3-libwebrtc
✅ 🛠 🧪 jsc ✅ 🧪 api-ios ✅ 🧪 mac-wk1 ✅ 🛠 gtk
✅ 🛠 🧪 jsc-debug-arm64 ✅ 🛠 ios-safer-cpp ✅ 🧪 mac-wk2 ✅ 🧪 gtk-wk2
✅ 🛠 vision ✅ 🧪 mac-AS-debug-wk2 ✅ 🧪 api-gtk
✅ 🛠 🧪 merge ✅ 🛠 vision-sim ✅ 🧪 mac-wk2-stress ✅ 🛠 playstation
✅ 🧪 vision-wk2 ✅ 🧪 mac-intel-wk2 ✅ 🛠 jsc-armv7
✅ 🛠 tv ✅ 🛠 mac-safer-cpp ✅ 🧪 jsc-armv7-tests
✅ 🛠 tv-sim
✅ 🛠 watch
✅ 🛠 watch-sim

@ast-hugger ast-hugger self-assigned this Apr 4, 2026
@ast-hugger ast-hugger requested a review from a team as a code owner April 4, 2026 05:58
@ast-hugger ast-hugger added the JavaScriptCore For bugs in JavaScriptCore, the JS engine used by WebKit, other than kxmlcore issues. label Apr 4, 2026
@ast-hugger ast-hugger marked this pull request as draft April 4, 2026 05:59
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 4, 2026
@ast-hugger ast-hugger removed the merging-blocked Applied to prevent a change from being merged label Apr 9, 2026
@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from 5c05986 to 6579d3c Compare April 9, 2026 20:45
@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from 6579d3c to d0b057b Compare April 9, 2026 20:52
@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from d0b057b to b8a616b Compare April 9, 2026 20:59
@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 9, 2026
@ast-hugger ast-hugger removed the merging-blocked Applied to prevent a change from being merged label Apr 15, 2026
@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from b8a616b to 3c8578f Compare April 15, 2026 01:43
@ast-hugger ast-hugger marked this pull request as ready for review April 15, 2026 03:14
Comment thread Source/JavaScriptCore/parser/Parser.h Outdated
}

// Cache line 0 (hot)
ALWAYS_INLINE void setCurrentArena(ParserArena& arena) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Align to WebKit coding style ({ should be in the next line).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Interesting why the style checker didn't complain about this one.

Comment thread Source/JavaScriptCore/parser/Parser.cpp Outdated
{ }

private:
ParserArena m_arena;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we have multiple ParserArena in one parsing implementation? Currently Identifier* is tied to ParserArena's IdentifierArena's allocated Identifier, which means that this pointer will be invalidated when arena gets destroyed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right, but with this change identifier creation is funneled into whatever is m_currentArena->identifierArena(). setCurrentArena() also reassigns the lexer's identifier arena to point at it. So an IIFE is parsed using a temporary arena, and then its FunctionNode takes ownership of that arena's contents and the parser goes back to its own m_arena as m_currentArena.

Comment thread Source/JavaScriptCore/parser/Parser.cpp Outdated
Comment on lines +2807 to +2817
// If this is a nested function inside an eagerly parsed IIFE, adjust the
// start column. During eager parsing columns are absolute, but column counting
// logic expects them relative to the containing function's '('.
if (m_iifeParseState
&& !m_iifeParseState->isAvailable()
&& tokenLineStart() <= m_iifeParseState->startOffset()) [[unlikely]] {

int adjustment = m_iifeParseState->startOffset() - tokenLineStart();
startColumn -= adjustment;
functionInfo.parametersStartColumn = startColumn;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you describe a bit detail about this with the example code?
I feel like needing a hack here sounds weird and error-prone.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In normal lazy parsing when a function is nested, columns on its first line are tracked relative to its parameter list start because that's where the SourceCode seen by the parser begins. UnlinkedFunctionExecutable expects that column on the first line are relative. When eagerly parsing, the parser sees the broader uses the original SourceCode that contains the function expression, and the columns are absolute.

This was uncovered by a layout test js/dom/line-column-numbers-expected.html. The test I added with this PR reproduces it. Case 1 is the simplest example. I agree this looks weird. Maybe we should revise column tracking so it's always absolute in the top-level source?

Comment thread Source/JavaScriptCore/parser/Parser.cpp Outdated
endLocation.startOffset = functionInfo.endOffset;
endLocation.lineStartOffset = m_lastTokenLocation.lineStartOffset;

FunctionNodeUniquePtr functionNode(new FunctionNode(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use makeUnique.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can't use makeUnique here because FunctionNodeUniquePtr needs a custom deleter, which it needs because SourceProviderCache.h is a project header and it can't include Nodes.h directly, which it would need to have the full definition of FunctionNode. I don't like that custom deleter overall, but not sure which alternative to choose - keep IIFE cache somewhere else, or promote the header, or something else. Suggestions welcome.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed - IIFE registry is now a separate entity from SourceProviderCache with its separate header file, so it can include Nodes.h and have FunctionNode fully defined, and the use the standard std::unique_ptr and makeUnique.

@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from 3c8578f to 535d24e Compare April 22, 2026 04:20
@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from 535d24e to 7301bb0 Compare April 29, 2026 18:16
@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from 7301bb0 to 71b8516 Compare April 29, 2026 18:33
@webkit-ews-buildbot
Copy link
Copy Markdown
Collaborator

macOS Safer C++ Build #98550 (71b8516)

❌ Found 1 failing file with 1 issue. Please address these issues before landing. See WebKit Guidelines for Safer C++ Programming.
(cc @rniwa)

@webkit-ews-buildbot webkit-ews-buildbot added the merging-blocked Applied to prevent a change from being merged label Apr 29, 2026
@webkit-ews-buildbot
Copy link
Copy Markdown
Collaborator

iOS Safer C++ Build #16882 (71b8516)

❌ Found 1 failing file with 1 issue. Please address these issues before landing. See WebKit Guidelines for Safer C++ Programming.
(cc @rniwa)

@ast-hugger ast-hugger removed the merging-blocked Applied to prevent a change from being merged label Apr 30, 2026
@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from 71b8516 to cf77145 Compare April 30, 2026 01:13
Copy link
Copy Markdown
Member

@Constellation Constellation left a comment

Choose a reason for hiding this comment

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

r=me with comments.

Comment thread Source/JavaScriptCore/parser/Parser.cpp Outdated
Comment on lines +2981 to +3002
iifeFeatures = m_iifeParseState->features();
iifeNumConstants = m_iifeParseState->numConstants();
iifeLexFeatures = functionScope->lexicallyScopedFeatures();
iifeInnerFeatures = functionScope->innerArrowFunctionFeatures();
if (functionScope->shadowsArguments())
iifeFeatures |= ShadowsArgumentsFeature;
if (functionScope->hasNonSimpleParameterList())
iifeFeatures |= NonSimpleParameterListFeature;
if (m_seenTaggedTemplateInNonReparsingFunctionMode)
iifeFeatures |= NoEvalCacheFeature;
if (functionScope->usesImportMeta())
iifeFeatures |= ImportMetaFeature;
if (m_seenArgumentsDotLength && functionScope->hasDeclaredGlobalArguments())
iifeFeatures |= ArgumentsFeature;
if (functionScope->asyncFunctionBodyDoesNotUseAwait())
iifeFeatures |= AsyncFunctionWithoutAwaitFeature;

iifeVarDeclarations = functionScope->declaredVariables();
IdentifierSet capturedVariables;
functionScope->getCapturedVars(capturedVariables);
for (auto& entry : capturedVariables)
iifeVarDeclarations.markVariableAsCaptured(entry.get());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe these things can be unified with the normal Parser's final code? IIRC, we have similar code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok, I'll look into that.

@ast-hugger ast-hugger force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from cf77145 to 2e6a77f Compare April 30, 2026 18:43
@ast-hugger ast-hugger added the merge-queue Applied to send a pull request to merge-queue label May 1, 2026
https://bugs.webkit.org/show_bug.cgi?id=311459
rdar://174062165

Reviewed by Yusuke Suzuki.

This patch detects likely IIFEs (immediately invoked function expressions) and builds
their AST right away, bypassing the usual initial syntax check.

In general, we can't tell if a function expression is immediately invoked until after we
parse it, but the following heuristic catches most real world cases. A function expression
is expected to be an IIFE if the token preceding the `function` keyword is either `(` or
`!` (`!` is commonly used by minifiers to introduce a function expression using one fewer
character than wrapping it in parentheses). Another potential heuristic is if the
`function` keyword is preceded by `,` and the expression before the comma was an IIFE.
This heuristic is not used at this time because the cost/benefit tradeoff is not as clear
in this case.

Key changes:

- A FunctionNode created when eagerly parsing an IIFE is independent from the AST created
  for the containing code and has its own lifetime. To support this, we change ParserArena
  and ParserArenaRoot so that an arena can be shared by multiple roots. A root no longer
  takes ownership of the arena memory, instead it retains a Ref to the arena.

- Eager IIFE parsing is controlled by two classes, EagerIIFEParseScope and
  EagerIIFEParseState. The scope is created in Parser::parseFunctionInfo() when the IIFE
  prediction heuristic triggers. The scope contains an ASTBuilder to use for the IIFE and
  an EagerIIFEParseState. The state is registered with the parser as an indication of the
  special parsing mode it is currently in. It collects parsing artifacts such as function
  parameter and source elements until we are ready to create the final FunctionNode.
  When parseFunctionInfo exits, the scope is destroyed and its destructor returns the
  parser into the original state.

- At the end of parseFunctionInfo, if an IIFE AST was eagerly built, we create a
  FunctionNode stored in m_eagerIIFERegistry. Parser::parse() checks the cache when the
  function is invoked and takes the ready-to-use AST from the cache.

Testing:
    - Regression-checked by existing tests.
    - Eager parsing is not directly testable in stress tests.
    - Added `JSTests/stress/eager-iife-column-tracking.js` to test the source column
    adjustment required on the first line of IIFEs, as uncovered by one of the layout
    tests.

Canonical link: https://commits.webkit.org/312444@main
@webkit-commit-queue webkit-commit-queue force-pushed the eng/JSC-Eagerly-build-AST-for-likely-IIFEs branch from 2e6a77f to 709e4e7 Compare May 1, 2026 18:33
@webkit-commit-queue
Copy link
Copy Markdown
Collaborator

Committed 312444@main (709e4e7): https://commits.webkit.org/312444@main

Reviewed commits have been landed. Closing PR #62019 and removing active labels.

@webkit-commit-queue webkit-commit-queue merged commit 709e4e7 into WebKit:main May 1, 2026
@webkit-commit-queue webkit-commit-queue removed the merge-queue Applied to send a pull request to merge-queue label May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

JavaScriptCore For bugs in JavaScriptCore, the JS engine used by WebKit, other than kxmlcore issues.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants