src: let embedders supply a builtin code cache without a snapshot - #65352
src: let embedders supply a builtin code cache without a snapshot#65352codebytere wants to merge 1 commit into
Conversation
|
Review requested:
|
1b99e86 to
136d2ad
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #65352 +/- ##
==========================================
- Coverage 90.13% 90.12% -0.02%
==========================================
Files 752 752
Lines 251568 252370 +802
Branches 47270 47454 +184
==========================================
+ Hits 226759 227438 +679
- Misses 16168 16255 +87
- Partials 8641 8677 +36
🚀 New features to boost your workflow:
|
136d2ad to
d33c3df
Compare
| ProcessCodeCache& pcc = GetProcessCodeCache(); | ||
| Mutex::ScopedLock lock(pcc.mutex); | ||
| harvest_code_cache_ = pcc.harvest; | ||
| if (!pcc.entries.empty()) RefreshCodeCache(pcc.entries); |
There was a problem hiding this comment.
Lines 868 to 874 in 9f0ce45
A worker env inherits the cached entries from its parent env's built-in loader. With the current PR, a worker copies two code cache maps, at the Environment constructor, and at this BuiltinLoader constructor.
This can an option on node::CreateIsolateData and feed into the parent env, then inherited by workers, with one copy of the cached entries.
There was a problem hiding this comment.
Eh, I think it might be worse than my first impression. CopySourceAndCodeCacheReferenceFrom copies a shared_ptr to the BuiltinCodeCache. The parent env might already refreshed the code caches (if they were rejected), and this refershing from ProcessCodeCache might invalidate the latest cache and cache again.
There was a problem hiding this comment.
@legendecas the clobbering can't happen as it stands: builtin_loader_ is a member, so its constructor seeds its own fresh map before the Environment constructor body swaps in the parent's shared_ptr for workers; nothing ever refreshes a shared map. you're right that a worker then seeds a map it immediately drops though, and hiding that in the constructor is what made it look dangerous - moved it to an explicit SeedFromProcessCodeCache() for the two loaders that don't inherit (the main-thread env's, ahead of the snapshot merge, and the per-context one), workers just inherit. on hanging it off CreateIsolateData instead: the per-context loader in NewContext() has no IsolateData to read, and that's the loader an embedder can't reach today, so it needs to be process-scoped i think.
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>
d33c3df to
e512f86
Compare
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;nodeitself is unchanged.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 withnode::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 everyBuiltinLoadercreated afterwards starts with: each Environment's, and the loaderNewContext()uses for the per-context scripts. A snapshot's entries still merge on top, soRefreshCodeCache()now merges withinsert_or_assigninstead of asserting a single call.ProcessInitializationFlags::kNoHarvestBuiltinCodeCachestopsLookupAndCompile()from serializing a cache for builtins compiled without one. The default stays as it is because worker threads start from that harvested cache.embedtestgets--builtin-code-cache-create <file>,--builtin-code-cache <file>and--no-harvest-builtin-code-cacheso 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 (
RefreshCodeCachemerges, 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.