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

@codebytere codebytere commented Aug 17, 2026

Copy link
Copy Markdown
Member

An embedder that creates its 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 attach a cache built ahead of time to an IsolateData, plus a flag to skip the runtime serialization; node itself is unchanged.

CreateEnvironment() + LoadEnvironment(env, "0"), fresh context, linux x64, n=6 ms
no cache 37.7
EmbedderBuiltinCodeCache attached to the IsolateData 12.9 (−66 %)
  • node::EmbedderBuiltinCodeCache holds entries pairing a builtin id with a v8::ScriptCompiler::CachedData. EmbedderBuiltinCodeCache::Generate(context) compiles every builtin in a context made with node::NewContext() and returns them for a build step to embed; node::SetBuiltinCodeCache(isolate_data, cache) attaches them next to where a snapshot's code cache lives, so every Environment created from that IsolateData afterwards starts with them (they share the buffers; the cache object can be freed after the call). The setter runs CachedData::CompatibilityCheck() against the isolate and returns the result, leaving the IsolateData untouched for a cache made with another V8 version, flag set or read-only snapshot. A snapshot's entries still merge with it, so RefreshCodeCache() 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.

The per-context scripts NewContext() runs (internal/per_context/*) are outside an Environment and keep compiling from source.

Tests: a cctest generates a cache, attaches it, checks that a new Environment compiles internal/bootstrap/node and everything outside internal/per_context/* from it, and that a corrupted cache is refused and a nullptr clears it; a cctest for the RefreshCodeCache merge; embedtest gains --no-harvest-builtin-code-cache for an embedding test that a worker started with and without the flag does and doesn't find a harvested cache. 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
@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 93.93939% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.05%. Comparing base (884f9cd) to head (d1c9826).
⚠️ Report is 27 commits behind head on main.

Files with missing lines Patch % Lines
src/node_builtins.cc 92.72% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #65352      +/-   ##
==========================================
- Coverage   90.06%   90.05%   -0.01%     
==========================================
  Files         754      754              
  Lines      255742   255803      +61     
  Branches    48314    48332      +18     
==========================================
+ Hits       230341   230373      +32     
- Misses      16534    16556      +22     
- Partials     8867     8874       +7     
Files with missing lines Coverage Δ
src/env.cc 85.48% <100.00%> (+0.03%) ⬆️
src/env.h 98.36% <100.00%> (+0.14%) ⬆️
src/node.cc 76.53% <100.00%> (+0.09%) ⬆️
src/node.h 91.66% <ø> (-0.79%) ⬇️
src/node_builtins.h 100.00% <ø> (ø)
src/node_builtins.cc 77.75% <92.72%> (+1.24%) ⬆️

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

@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Comment thread test/embedding/test-embedding-builtin-code-cache.js Outdated
@codebytere
codebytere force-pushed the embedder/builtin-code-cache-seed branch from 136d2ad to d33c3df Compare August 20, 2026 20:34
@legendecas legendecas added semver-minor PRs that contain new features and should be released in the next minor version. request-ci Add this label to start a Jenkins CI on a PR. labels Aug 20, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 20, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Comment thread src/node_builtins.cc Outdated
@codebytere
codebytere force-pushed the embedder/builtin-code-cache-seed branch from d33c3df to e512f86 Compare August 21, 2026 06:06
@codebytere codebytere added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 21, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 22, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Comment thread src/node.h Outdated
@codebytere
codebytere force-pushed the embedder/builtin-code-cache-seed branch from e512f86 to 189fbb1 Compare August 26, 2026 14:16
@codebytere
codebytere requested a review from joyeecheung August 26, 2026 14:18
@codebytere codebytere added the request-ci Add this label to start a Jenkins CI on a PR. label Aug 27, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

Environments created from the built-in snapshot get the builtins' code
cache from that snapshot. An embedder that bootstraps an Environment
from scratch (its own isolate and context, no EmbedderSnapshotData) has
no way to provide one: every builtin the bootstrap touches is 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 node::EmbedderBuiltinCodeCache for that case. Its entries pair a
builtin id with a v8::ScriptCompiler::CachedData; Generate(context)
compiles every builtin in a context of the right kind of isolate and
returns them for a build step to embed, and
SetBuiltinCodeCache(isolate_data, cache) attaches them to an
IsolateData, next to where a snapshot's code cache lives, so that every
Environment created from it afterwards starts with them. The setter
runs CachedData::CompatibilityCheck() against the isolate first and
returns the result, leaving the IsolateData untouched for a cache made
with another V8 version, flag set or read-only snapshot. Entries from a
snapshot still merge with it (RefreshCodeCache() now merges instead of
assuming a single call). CreateEnvironment() plus LoadEnvironment() of
an empty script goes from about 38 ms to 13 ms with a cache attached.

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.

A cctest generates a cache, attaches it, checks that a new
Environment's bootstrap compiles from it and that a corrupted cache is
refused; embedtest gains --no-harvest-builtin-code-cache for a test
that a worker does or does not find a harvested cache depending on the
flag.

Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
@codebytere
codebytere force-pushed the embedder/builtin-code-cache-seed branch from 189fbb1 to d1c9826 Compare August 30, 2026 15:58
@codebytere codebytere removed the request-ci Add this label to start a Jenkins CI on a PR. label Aug 31, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@codebytere

codebytere commented Aug 31, 2026

Copy link
Copy Markdown
Member Author

@legendecas @joyeecheung PTAL

@codebytere codebytere added request-ci Add this label to start a Jenkins CI on a PR. and removed needs-ci PRs that need a full CI run. request-ci Add this label to start a Jenkins CI on a PR. labels Aug 31, 2026
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++. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants