Skip to content

src: let embedders supply a builtin code cache without a snapshot - #65352

Open
codebytere wants to merge 1 commit into
nodejs:mainfrom
codebytere:embedder/builtin-code-cache-seed
Open

src: let embedders supply a builtin code cache without a snapshot#65352
codebytere wants to merge 1 commit into
nodejs:mainfrom
codebytere:embedder/builtin-code-cache-seed

Conversation

@codebytere

Copy link
Copy Markdown
Member

An embedder that creates its contexts and Environments without Node's snapshot (its own isolate, no
EmbedderSnapshotData) compiles every builtin the bootstrap touches from source in each such process, and then
serializes a fresh code cache for each of them that only a later worker thread ever reads. This adds a way to hand
Node.js a cache built ahead of time, plus a flag to skip the runtime serialization. embedtest's start-to-exit goes
from 58 to 39 ms with a supplied cache, and the flag alone saves 6 ms; node itself is unchanged.

embedtest, process start → exit, n=40 interleaved      script: `0`              script: require http, fs/promises,
                                                                                   child_process, url, util, stream
  base → this branch, nothing supplied            57.9 → 58.1 ms   n.s.          76.0 → 76.8 ms   n.s.
  + cache from GenerateBuiltinCodeCache()              → 39.3 ms   −32 %  ***         → 47.3 ms   −38 %  ***
  + kNoHarvestBuiltinCodeCache                         → 51.5 ms   −11 %  ***         → 67.7 ms   −11 %  ***
  + both                                               → 39.5 ms   −32 %  ***         → 46.6 ms   −39 %  ***
node -e 0 / node --no-node-snapshot -e 0, base → branch    24.2 → 23.9 / 59.1 → 58.9 ms, both n.s.

With the supplied cache all 114 functions the bootstrap and NewContext() compile are accepted from it,
internal/per_context/* included (NODE_DEBUG_NATIVE=CODE_CACHE).

  • node::GenerateBuiltinCodeCache(context) compiles every builtin in a context made with node::NewContext() in the
    kind of isolate the cache is for (same V8, flags and read-only snapshot) and returns id + bytes for a build step to
    embed.
  • node::SetBuiltinCodeCache(entries) installs process-wide entries that every BuiltinLoader created afterwards
    starts with: each Environment's, and the loader NewContext() uses for the per-context scripts. A snapshot's entries
    still merge on top, so RefreshCodeCache() now merges with insert_or_assign instead of asserting a single call.
  • ProcessInitializationFlags::kNoHarvestBuiltinCodeCache stops LookupAndCompile() from serializing a cache for
    builtins compiled without one. The default stays as it is because worker threads start from that harvested cache.

embedtest gets --builtin-code-cache-create <file>, --builtin-code-cache <file> and
--no-harvest-builtin-code-cache so the test drives all three through an embedder binary: it generates a cache in one
process, checks that another process's bootstrap and per-context scripts compile from it, and that a worker started
with and without the flag does and doesn't find a harvested cache.

Tests: the new embedding test and two cctests (RefreshCodeCache merges, the process cache seeds new loaders);
embedding, cctest and the default suite pass.


Disclosure: the code, tests, measurements and this description were written by Claude Code, directed and reviewed by @codebytere.

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/startup

@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. labels Aug 17, 2026
Comment thread src/node_builtins.cc Outdated
Comment thread src/node_builtins.cc Outdated
Contexts and Environments created from the built-in snapshot get the
builtins' code cache from that snapshot. An embedder that bootstraps
them from scratch (its own isolate and context, no EmbedderSnapshotData)
has no way to provide one: every builtin the bootstrap touches, and the
per-context scripts NewContext() runs, are compiled from source in
every such process, and each of them then serializes a fresh cache
(SaveCodeCache) that only a later worker thread would ever consume.

Add a small public API for that case:

- node::GenerateBuiltinCodeCache(context) compiles every builtin in a
  context of the right kind of isolate and returns the caches, for a
  build step that embeds them.
- node::SetBuiltinCodeCache(entries) installs process-wide entries that
  every BuiltinLoader created afterwards starts with, i.e. each
  Environment's and the loader for the per-context scripts. Entries a
  snapshot provides still merge on top (RefreshCodeCache() now merges
  with insert_or_assign instead of assuming a single call).
- ProcessInitializationFlags::kNoHarvestBuiltinCodeCache stops
  serializing caches for builtins compiled without one, for embedders
  that supply their own or never create workers. The default is
  unchanged because worker threads copy the harvested cache.

embedtest gains --builtin-code-cache-create, --builtin-code-cache and
--no-harvest-builtin-code-cache, and a test that generates a cache in
one process, checks that the bootstrap and per-context scripts of
another compile with it, and that a worker does or does not find a
harvested cache depending on the flag. On x64 Linux embedtest's
start-to-exit goes from ~64 to ~44 ms with a supplied cache; not
harvesting alone saves ~5 ms on a snapshot-less start and is a no-op
with the snapshot.

Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
@codebytere
codebytere force-pushed the embedder/builtin-code-cache-seed branch from 1b99e86 to 136d2ad Compare August 17, 2026 13:25
@codebytere codebytere added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 17, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 17, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.13%. Comparing base (30bff4a) to head (136d2ad).
⚠️ Report is 23 commits behind head on main.

Files with missing lines Patch % Lines
src/node_builtins.cc 91.83% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65352      +/-   ##
==========================================
- Coverage   90.13%   90.13%   -0.01%     
==========================================
  Files         752      752              
  Lines      251568   251864     +296     
  Branches    47270    47355      +85     
==========================================
+ Hits       226759   227012     +253     
- Misses      16168    16182      +14     
- Partials     8641     8670      +29     
Files with missing lines Coverage Δ
src/node.cc 76.62% <100.00%> (+0.08%) ⬆️
src/node.h 91.48% <ø> (-0.82%) ⬇️
src/node_builtins.h 100.00% <ø> (ø)
src/node_builtins.cc 77.35% <91.83%> (+1.02%) ⬆️

... and 34 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.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants