#51697: perf: build-time V8 code cache for the electron/js2c framework bundles
Description of Change
Builds a V8 code cache for Electron's embedded electron/js2c/* framework bundles at compile time and embeds it in the binary, so they're deserialized (kConsumeCodeCache) instead of parsed and compiled from source on every process start. The bundles are eagerly compiled at build time (kEagerCompile), so the cache covers every inner function — at runtime the consuming process never lazy-compiles any of the framework JS.
The win is in the sandboxed renderer: with no Node Environment, its pre-HTML-parser blocking window (DidClearWindowObject) is almost entirely the sandbox_bundle compile, and caching it cuts that by ~35% on every launch (~9.8 ms → ~6.4 ms cold, embedded in the binary, no warm-up needed). The bundles in the Node-env processes are also consumed — spec-verified — but the Node bootstrap dominates there so the perf delta is in the noise.
DidClearWindowObject (sandboxed renderer, cold) |
|
|---|---|
| no cache | 9.8 ms |
| lazy cache (top-level only) | 7.4 ms (−25%) |
| eager cache (this PR) | 6.4 ms (−35%) |
Also dedupes get-intrinsic (3× via package.json resolutions), shrinking the sandboxed renderer bundle 365 KB → 323 KB.
Stacks with #51602's preload bytecode cache — the bundle and the preload are independently cached.
How it works
V8's code-cache key includes FlagList::Hash() over non-default flags and the snapshot's read-only checksum, both of which differ per process type, so a single cache only works for one process flavor. We generate one variant per flavor and feed each process the matching one:
| flavor | process | flag delta |
|---|---|---|
sandbox |
sandboxed renderer (no Node env) | +SAB |
renderer |
normal renderer | +SAB +rehash-snapshot +no-freeze |
browser |
main | +rehash-snapshot +no-freeze |
utility |
utility | +rehash-snapshot +no-freeze |
worker |
node/web worker | +rehash-snapshot +no-freeze |
All flavors create their isolate from the v8 context snapshot (gin loads it process-wide). The flag deltas are deterministic; the key insight is that --no-freeze-flags-after-init (present iff the process has a Node Environment) lets V8 recompute the flag-hash after init, so the consume-time hash of every Node-env process includes --rehash-snapshot. A sandboxed renderer has no Node env, so it's a distinct flavor — discriminated at runtime by node::Environment::GetCurrent(context) != nullptr.
electron_natives_codecache is a build-time host tool (built in v8_snapshot_toolchain, like node_mksnapshot) that compiles each bundle through the exact node::builtins::BuiltinLoader path its runtime consumer uses — the caller-parameterized 4-arg LookupAndCompileFunction for util::CompileAndCall bundles, the 3-arg parameter_map path for the *_init bundles run by node::LoadEnvironment — so source/params hash matches by construction. The bundle ids and wrapper-function param names live in shell/common/js2c_bundle_ids.h, shared between the generator and the runtime call sites. A node patch makes the 4-arg LookupAndCompileFunction cache-aware (kConsumeCodeCache, graceful compile-from-source fallback). FeedEnvironmentCodeCache feeds the per-Environment loader before LoadEnvironment so the *_init bundles are consumed.
Cross-arch is supported: the generator is built in v8_snapshot_toolchain (V8_TARGET_ARCH set to the target) and reads the target's snapshot blob, so the cache it emits is keyed to the target's read-only checksum — the same mechanism that makes V8's mksnapshot cross-arch correct. Cross-OS is untested and gated off (electron_generate_js2c_code_cache = host_os == target_os).
Embedded cache size: ~3.5 MB across all 5 flavors (sandbox 1.7 MB, browser 1.1 MB, renderer 343 KB, utility 241 KB, worker 169 KB).
Testing
Test-only instrumentation (DCHECK / Electron Testing builds, compiled out of production): a per-process id → accepted status map exposed via v8_util.getJs2cCodeCacheStatus(), plus a sandboxed-preload shim. spec/api-js2c-code-cache-spec.ts spawns a clean Electron app — the spec runner injects --js-flags=--expose_gc suite-wide, which would change the flag-hash and (correctly) reject the production-flavor cache — and asserts every bundle is consumed in browser / sandboxed renderer / renderer / utility.
Checklist
- PR description included and stakeholders cc'd
-
npm testpasses - tests are changed or added
- PR title follows semantic commit guidelines
Release Notes
Notes: Reduced sandboxed renderer cold-start time (DidClearWindowObject) by ~35% by deserializing a build-time V8 code cache for Electron's framework bundles instead of parsing and compiling them from source on every launch.
Backports
No Backports Requested
This pull request doesn't have any backports requested or created for older release branches.
What are backports?
Backports are copies of changes made to the main branch that are applied to older release branches. They ensure that bug fixes and important changes are available in maintained older versions of Electron.
Semver Impact
Semantic Versioning helps users understand the impact of updates:
- Major (X.y.z): Breaking changes that may require code modifications
- Minor (x.Y.z): New features that maintain backward compatibility
- Patch (x.y.Z): Bug fixes that don't change the API
- None: Changes that don't affect using facing parts of Electron