Skip to content

Commit 387fdbc

Browse files
committed
Harness: Add skills outlining up to date v8 knowledge
1 parent ec6b224 commit 387fdbc

9 files changed

Lines changed: 1959 additions & 0 deletions
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
---
2+
title: V8 Compilation — Tiers, Deopts, Explicit Compile Hints
3+
description: How V8 compiles JS through Ignition → Sparkplug → Maglev → Turbofan/Turboshaft. Covers tier-up triggers, on-stack replacement, deopt causes, what Maglev/Turbofan can and cannot inline, the truth about try/catch in optimized code, arguments object vs rest params, explicit compile hints (//# allFunctionsCalledOnLoad, Chrome 136+), --disable-optimizing-compilers and --jitless reality, eval and CSP. Load for "why does this deopt", "why is first call slow", "should I eager-compile", "is X inlined" questions.
4+
keywords: [Ignition, Sparkplug, Maglev, Turbofan, Turboshaft, Turbolev, tier-up, OSR, on-stack replacement, deoptimization, deopt, inlining, feedback vector, IC, inline cache, try catch, arguments object, rest parameters, explicit compile hints, allFunctionsCalledOnLoad, jitless, disable-optimizing-compilers, code cache, eval, new Function, CSP]
5+
audience: authoring
6+
skill: performance-v8-compilation
7+
type: skill
8+
---
9+
10+
# V8 Compilation — Tiers, Deopts, Explicit Compile Hints
11+
12+
> **Skill:** `performance-v8-compilation`
13+
> **Purpose:** What V8 actually compiles, when, and what makes optimized code go away.
14+
15+
**Golden rule: Stability of feedback matters more than raw call count. Polymorphism that arrives early and stays consistent is fine; polymorphism that grows after the function is hot triggers deopts.**
16+
17+
Current as of Chrome 138, May 2026.
18+
19+
---
20+
21+
## The pipeline
22+
23+
A function starts at the bottom. It tiers up only if it gets hot with stable feedback.
24+
25+
| Tier | Description |
26+
|------|-------------|
27+
| **Ignition** | Interpreter. Parses → bytecode → executes. Fills *feedback vectors* — per-function slot arrays recording, for each IC site, what shapes and types have been seen. |
28+
| **Sparkplug** (Chrome 91+) | Baseline JIT. Single-pass bytecode → machine code. No IR, no optimizations, no inlining. ~10× faster than Ignition; ~20× slower to compile than nothing. Threshold is essentially "ran more than once." |
29+
| **Maglev** (default Chrome 117+) | Mid-tier optimizing JIT. SSA over CFG. Uses feedback to speculatively narrow types, emits shape-check + direct field load, inlines small monomorphic targets, registers dependencies on stable Maps and de-facto-constant globals. Compiles ~10× slower than Sparkplug, runs ~10× faster. This is where the bulk of "optimized" web JS lives. |
30+
| **Turbofan + Turboshaft IR** | Top tier. Turboshaft (CFG-based) replaced Sea-of-Nodes for the JS backend in Q1 2025 (v8.dev/blog/leaving-the-sea-of-nodes), roughly halving compile time. **Turbolev** — building Turboshaft graphs directly from Maglev's IR instead of from JS — is rolling out incrementally through 2025–2026. |
31+
32+
Maglev's presence makes V8 willing to wait longer before triggering Turbofan, so functions in the "warm but not hot" band stay on Maglev.
33+
34+
### On-Stack Replacement (OSR)
35+
36+
A long-running loop in an already-running function can tier up mid-execution: V8 swaps the in-progress frame for an optimized frame at a loop back-edge. Shared between Maglev and Turbofan. Useful for setup loops that run many iterations on first call.
37+
38+
---
39+
40+
## What "stable feedback" actually means
41+
42+
The framing for almost all deopt advice. Feedback stability matters more than raw call count.
43+
44+
✅ Polymorphism that arrives early and stays consistent — a site sees three shapes in its first 100 invocations and continues to see only those three. Maglev handles it well.
45+
46+
❌ Polymorphism that grows over time — new shapes appearing *after* Maglev/Turbofan have compiled. Optimized code's speculative checks fail, the optimized frame is discarded, the function reverts to a lower tier, and recompilation eventually runs again with the broader feedback. This cycle is the worst case.
47+
48+
### Practical consequences
49+
50+
✅ Construct all your shape variants early. Eagerly create the rare-but-real case during warmup so its shapes are in feedback before Maglev compiles.
51+
52+
❌ Lazy creation of a new "kind" of object after the page has been running deopts every consumer site that saw the previous shapes.
53+
54+
❌ Optional/conditional property addition. A property added to instances #5000+ but not to #1–4999 forks the shape tree and creates new polymorphism for downstream sites.
55+
56+
---
57+
58+
## What V8 can and cannot inline
59+
60+
Maglev and Turbofan inline call targets when:
61+
62+
- ✅ The call site is monomorphic or low-polymorphic.
63+
- ✅ The callee is small (the budget is an internal heuristic, not stable across releases — do not hard-code expectations).
64+
- ✅ The callee is not a generator and is not `async` (the async wrapper can be split into resumable state; the synchronous body before the first await can still be inlined).
65+
- ✅ The callee does not exceed recursion limits.
66+
67+
Maglev inlines less aggressively than Turbofan but does inline obvious small monomorphic targets.
68+
69+
### Practical patterns
70+
71+
✅ Keep hot primitives small — getter/setter pairs of 5–15 bytecode instructions; framework methods that are mostly one shape check + one field access.
72+
73+
❌ Do not embed debug or dev-only branches in hot code paths even if guarded by `if (DEBUG) …`. The branch is cheap but the dead code inflates the size estimate the inliner consults.
74+
75+
✅ Compile dev branches out (build flag) or factor them into a separate function that won't be inlined.
76+
77+
✅ Put shared methods on the prototype (one Function object) rather than assigning per instance in the constructor (one Function object per instance — bad on a hot class).
78+
79+
---
80+
81+
## try/catch is not a deopt killer in modern V8
82+
83+
Crankshaft (pre-2017) refused to optimize functions containing `try`/`catch`. TurboFan (2017+) lifted that with caveats. **All four current tiers handle try/catch.** The catch handler is generated as a cold path; no general "this function cannot be optimized" cost.
84+
85+
v8.dev/blog/leaving-the-sea-of-nodes (Mar 2025) explicitly cites poor exception handling as one of the reasons Crankshaft had to be replaced.
86+
87+
✅ Wrap user effect/callback invocations in `try`/`catch` for error isolation at framework boundaries.
88+
✅ Use `try`/`finally` for resource cleanup.
89+
⚠ Avoid stacking `try`/`catch` inside the *innermost* loop of measured hot code purely as a defensive measure — small constant cost, slight inlining-budget impact.
90+
91+
---
92+
93+
## arguments object
94+
95+
The historical "don't touch `arguments`" rule still applies in 2026. The solution has updated.
96+
97+
❌ Caching `arguments.length` in a local. The micro-optimization is irrelevant — the problem is using `arguments` at all.
98+
99+
❌ The `arguments` object in non-strict, non-arrow functions is a "mapped" exotic object aliased with named parameters. Touching it forces materialization.
100+
101+
✅ Use rest parameters: `function f(...args)`. Real Array, normal elements-kind machinery, sometimes elided by Turbofan via escape analysis when only iterated.
102+
103+
---
104+
105+
## Deopt triggers (the short list)
106+
107+
- A property previously type-checked as Smi receives a non-Smi value.
108+
- An object shape used at an IC site diverges from the shapes the compiled code expected.
109+
- A previously-stable Map transitions (e.g., a property is deleted, the object enters dictionary mode).
110+
- A global previously assumed constant is reassigned.
111+
- A class's prototype is mutated.
112+
- A `typeof` guard fails its speculative narrowing.
113+
- An out-of-bounds array read at a previously in-bounds site.
114+
- An IC transitions from polymorphic (≤4 shapes) to megamorphic (5+).
115+
116+
Each deopt invalidates the optimized code and reverts the function to a lower tier. One-time deopts during page warmup are normal; recurring deopts are pathological.
117+
118+
---
119+
120+
## Explicit Compile Hints — `//# allFunctionsCalledOnLoad`
121+
122+
**Status: shipped, default-on, Chrome 136 (April 2025).** Source: Marja Hölttä, v8.dev/blog/explicit-compile-hints, Apr 2025.
123+
124+
### What it does
125+
126+
V8 normally tries to defer compilation: it preparses each script to find function boundaries, then compiles each function lazily on first call. For functions that *will* be called during page load, that's wasted work — the preparse duplicates the eventual full parse, and eager compilation could have run on a background thread interleaved with network load.
127+
128+
The magic comment placed at the top of a JavaScript file tells V8 to compile every function in that file eagerly:
129+
130+
```js
131+
//# allFunctionsCalledOnLoad
132+
133+
function bootstrap() { /* ... */ }
134+
function defineCustomElements() { /* ... */ }
135+
// every function in this file is eagerly parsed and compiled
136+
```
137+
138+
Production test cited in the post: 17 of 20 popular pages improved, average foreground parse+compile time reduced by ~630 ms.
139+
140+
The hint forces eager **parse + bytecode generation** (Ignition-level compilation). It does **not** skip tier-up — Sparkplug/Maglev/Turbofan still happen normally on hot code.
141+
142+
### Interaction with code caching
143+
144+
Chrome's V8 compile cache stores eagerly-compiled bytecode on the second load. The post recommends testing with a clean user-data directory because the code cache otherwise masks the difference. In production, the first-load win is what the hint delivers; subsequent loads benefit from cached bytecode regardless.
145+
146+
✅ Combine the hint with long-cache `Cache-Control` on framework core files. Normal HTTP caching; V8 picks it up automatically.
147+
148+
### When to use it
149+
150+
✅ Apply to **a framework core file** — the reactive primitive, the scheduler, the base custom-element class, the bootstrap path. Anything called on every page in the first few hundred milliseconds.
151+
152+
❌ Do not apply to:
153+
- Large optional / lazy-loaded modules. Defeats lazy parsing entirely.
154+
- Files containing large constant tables (precomputed lookups embedded in JS). Eager parsing of the data literal is wasted work.
155+
156+
The post's own warning: *"This feature should be used sparingly though — compiling too much will consume time and memory."*
157+
158+
### Future direction
159+
160+
The post states V8 plans to support per-function compile hints. At time of the post, only file-level was available. Verify current status before recommending per-function granularity — see `performance-v8-uncertain-topics`.
161+
162+
---
163+
164+
## JIT-disabled scenarios
165+
166+
Chrome 126 (June 2024) introduced `--disable-optimizing-compilers`, the V8 flag that disables Maglev and Turbofan while keeping **Ignition and Sparkplug** running. The older `--jitless` flag is stricter: Ignition only. Both modes appear in the field:
167+
168+
- The Chrome 122+ user toggle at `chrome://settings/security` ("V8 optimizer") sets `--disable-optimizing-compilers` per site.
169+
- The enterprise policy `DefaultJavaScriptJitSetting` controls it organization-wide.
170+
- Some sandboxed configurations (certain WebView modes, locked-down enterprise machines) force jitless.
171+
172+
**Code that depends on Maglev/Turbofan-only optimizations must still be correct and acceptably fast when only Sparkplug is running.** This includes:
173+
174+
- Escape analysis (closure-allocation elision).
175+
- Speculative type narrowing.
176+
- Aggressive inlining.
177+
- Vectorized loops over TypedArrays.
178+
- IC-based speculative shape-check fast paths.
179+
180+
❌ Don't recommend patterns that are *worse* without optimization than the simpler alternative would be.
181+
182+
Source: Lamprey Labs, "Return of the JIT," documenting the Chrome 126 change.
183+
184+
---
185+
186+
## eval, new Function, dynamic code
187+
188+
-`eval` and `new Function(...)` produce code that participates normally in the tier pipeline. Not unoptimizable per se.
189+
- ❌ Blocked by strict CSP `script-src` policies that exclude `'unsafe-eval'`. A framework that depends on `new Function` won't work on CSP-strict sites.
190+
- ❌ Disabled entirely in `--jitless` configurations.
191+
- ❌ Defeat inlining (the caller cannot inline a function it doesn't statically see).
192+
- ❌ Make shape inference much harder for surrounding code.
193+
194+
✅ Acceptable for build-time code generation that runs once.
195+
❌ Not acceptable as a runtime optimization technique.
196+
197+
---
198+
199+
## Quick Reference
200+
201+
```js
202+
//# allFunctionsCalledOnLoad // ✅ Top of framework core file, Chrome 136+
203+
204+
// ✅ Rest params, not arguments
205+
function effect(...args) { /**/ }
206+
207+
// ❌ arguments object
208+
function effect() {
209+
for (let i = 0; i < arguments.length; i++) { /**/ }
210+
}
211+
212+
// ✅ Methods on prototype (one Function object shared)
213+
class Signal {
214+
get() { /**/ }
215+
set(v) { /**/ }
216+
}
217+
218+
// ❌ Methods assigned per-instance in constructor (one per instance)
219+
class Signal {
220+
constructor() {
221+
this.get = () => { /**/ };
222+
}
223+
}
224+
225+
// ✅ try/catch at framework boundary
226+
function runEffect(fn) {
227+
try { fn(); } catch (e) { reportError(e); }
228+
}
229+
```
230+
231+
---
232+
233+
## Primary sources
234+
235+
- v8.dev/blog/maglev — Dec 2023
236+
- v8.dev/blog/leaving-the-sea-of-nodes — Mar 2025
237+
- v8.dev/blog/sparkplug — 2021
238+
- v8.dev/blog/explicit-compile-hints — Marja Hölttä, Apr 2025
239+
- lampreylabs.com/posts/return-of-the-jit — Chrome 126 `--disable-optimizing-compilers`
240+
241+
---
242+
243+
## Related Skills
244+
245+
| Skill | Command | Use when... |
246+
|-------|---------|-------------|
247+
| **Performance Index** | `use_skill('performance-v8-overview')` | Need the tier model summary or routing. |
248+
| **Object Model** | `use_skill('performance-v8-object-model')` | The shape-stability rules that determine whether tier-up happens cleanly. |
249+
| **Stale Advice** | `use_skill('performance-v8-stale-advice')` | Verify a remembered rule about try/catch, arguments, inlining, or deopts. |
250+
| **Uncertain Topics** | `use_skill('performance-v8-uncertain-topics')` | About to claim specific tier-up tick counts or inlining budgets — don't. |

0 commit comments

Comments
 (0)