Skip to content

src: use eager compilation for embedder main scripts - #66122

Open
jml6m wants to merge 9 commits into
nodejs:mainfrom
jml6m:eager-compile-embedder-main
Open

jml6m wants to merge 9 commits into
nodejs:mainfrom
jml6m:eager-compile-embedder-main

Conversation

@jml6m

@jml6m jml6m commented Sep 19, 2026

Copy link
Copy Markdown

CompileFunctionForCJSLoader has a long-standing TODO about allowing optional eager compilation for the ScriptCompiler::CompileFunction call. For the embedder-supplied main scripts (SEA or a generic embedder application): when there's no code cache to consume, compile with kEagerCompile instead of kNoCompileOptions, since we know in advance that most of an embedder main script's code is about to run — unlike an ordinary require()'d module, where much of it may never execute.

Measured against real builds of both main and this branch, using a synthetic CJS script with N top-level functions each invoked once, packaged as a SEA with no code cache:

N functions main (ms) branch (ms) improvement
100 34.7 33.5 3.5%
2,000 47.5 43.2 9.1%
50,000 ~510 ~323 ~37%

The performance improvement scales with the number of distinct functions actually invoked — lazy compilation defers each function's real compile to its first call, eager pays that cost upfront instead. Peak memory was ~22.5% higher on the 50,000-function case, the expected trade-off for compiling more upfront.

jml6m-bot and others added 9 commits September 19, 2026 00:19
CompileFunctionForCJSLoader now compiles with kEagerCompile instead
of kNoCompileOptions when there is no code cache to consume and the
caller indicates the compiled script is an embedder-supplied main
script (SEA or otherwise). Resolves a long-standing TODO about
allowing optional eager compilation.

This is scoped to embedderRunCjs's is_embedder flag: that function
only ever compiles the one script an embedded process exists to run,
unlike require()'d modules where much of the loaded code may never
execute. is_embedder is a strict superset of the prior is_sea_main
signal (SEA only ever runs through embedderRunCjs), so this also
covers non-SEA embedder applications that were previously unaffected.

kEagerCompile is mutually exclusive with kConsumeCodeCache at the V8
level, so the decision is made after cached_data is fully resolved
(including the NODE_COMPILE_CACHE lookup), not before it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses review feedback: the trailing bool didn't self-document
what it meant, unlike this file's convention for other unlabeled
positional args.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addresses review feedback: ContainsModuleSyntax no longer needs to
pass an explicit false now that the parameter defaults to it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mirrors the existing "eagerly"/"lazily" debug line in
BuiltinLoader::LookupAndCompile (node_builtins.cc), so
NODE_DEBUG_NATIVE=CODE_CACHE now shows which strategy was used for
CJS loader compiles too, not just builtins. Makes the eager path
observable directly instead of only inferable from timing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not part of this change — a throwaway script for comparing main vs
this branch's SEA startup time on a real build. Will be removed in a
follow-up commit before this PR is considered done.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…erge)

Not part of this change. Reuses the binaries bench-branch.sh already
built to check whether the eager-compile improvement holds at
realistic (smaller) distinct-function counts, or is specific to the
original 50,000-function test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Removes the CODE_CACHE debug line added during development (was
useful for confirming the eager/lazy branch during validation, not
needed in the final change). Adds a one-line comment on why
is_embedder currently doubles as the eager-compile preference,
so a future caller with different needs knows where to look.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Not part of this change — these were validation-only tooling used
to compare main vs this branch on real hardware. Preserved on the
bench-scripts-archive branch for reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. vm Issues and PRs related to the vm subsystem. labels Sep 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Node.js, and thank you for your first contribution!

Before review, please take a moment to read:

Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal.

@jml6m

jml6m commented Sep 19, 2026

Copy link
Copy Markdown
Author

Design

  • New GetCompileOptionsForCJS(cached_data, prefer_eager) resolves the actual ScriptCompiler::CompileOptions in one place, called from inside CompileFunctionForCJSLoader after cached_data is fully resolved (including the NODE_COMPILE_CACHE on-disk lookup) — kEagerCompile and kConsumeCodeCache are mutually exclusive at the V8 level, so the decision has to happen after that resolution, not before it.
  • is_embedder currently doubles as both the host-defined-options selector and the eager-compile preference because today's only caller needs both; a comment at the call site flags this for whoever adds a caller that needs them to diverge.
  • ContainsModuleSyntax (the other caller of the shared helper) always passes prefer_eager = false (via the parameter's default) — its compiles are throwaway detection probes, never executed, so eager compilation would be pure waste there.

Testing

  • No existing test asserts on this function's internals, so nothing needed updating.
  • No new test added. Dozens of existing test/sea/test-single-executable-application-*.js tests already build and run a plain SEA without code caching, so they exercise the new kEagerCompile branch incidentally. I considered adding a targeted test for a genuine behavioral difference this introduces (eager compilation can surface a syntax error in a never-called function immediately instead of leaving it undiscovered under lazy parsing) but didn't have a way to verify locally that a specific test case actually crosses that boundary, so left it out rather than land an unverified test.
  • No public API, CLI flag, or documented behavior changed, so no documentation update was needed.

@codecov

codecov Bot commented Sep 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.27%. Comparing base (b125121) to head (7f50c9a).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #66122      +/-   ##
==========================================
- Coverage   90.29%   90.27%   -0.03%     
==========================================
  Files         790      790              
  Lines      271689   271693       +4     
  Branches    51856    51858       +2     
==========================================
- Hits       245312   245261      -51     
- Misses      16878    16933      +55     
  Partials     9499     9499              
Files with missing lines Coverage Δ
src/node_contextify.cc 81.87% <100.00%> (+0.31%) ⬆️

... and 28 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. vm Issues and PRs related to the vm subsystem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants