Compilers
V8: JavaScript from Parsing to Machine Code
In 2008 Google shipped Chrome with V8, and JavaScript got 10x faster in a single year. Before V8, JS only ran in an interpreter. V8 figured out how to compile a dynamic language with unknown types into native code through speculative assumptions. That changed web development forever and led to Node.js.
- **Node.js** is V8 outside the browser. The same Ignition + TurboFan + Hidden Classes make server-side JavaScript competitive with Java and Go for I/O-bound work. The npm registry (millions of packages) runs on Node.js.
- **Deno** and **Bun** are alternatives to Node.js. Deno is built on V8, Bun on JavaScriptCore. Bun uses JavaScriptCore (Apple's JS engine) with a more aggressive JIT and beats V8 on some workloads.
- **V8 Turboshaft** (2023) is a rewrite of TurboFan onto a more traditional CFG + SSA IR. Sea of Nodes turned out to be too complex to maintain. Turboshaft is easier to understand and to extend, and it already shows 5-10% speedups on some benchmarks.
Ignition: Bytecode Interpreter
Ignition is V8's bytecode interpreter (since 2016). It parses JS -> AST -> bytecode, then interprets. It is a register-based VM with an accumulator. Ignition gathers a profile: argument types, call frequency. That data feeds TurboFan for JIT optimisation.
Before Ignition (2016), V8 compiled JS straight to native code through Crankshaft, with no intermediate bytecode. That caused problems: Crankshaft did not support some ES6 constructs (try/catch, generators). Ignition separated the concerns: fast startup (interpretation), profiling, and JIT (TurboFan). Ignition bytecode uses 2-3x less memory than Crankshaft's native code.
Why does Ignition gather feedback during interpretation?
TurboFan: Optimizing JIT
TurboFan is V8's optimising JIT compiler (since 2017, fully replacing Crankshaft). It uses a Sea of Nodes IR (combined data and control flow graph), aggressive optimisations, and feedback from Ignition for speculative optimisation.
Sea of Nodes is an IR where control flow and data flow live in a single graph. Proposed by Cliff Click (Java HotSpot C2, 1995). V8 TurboFan inherited the idea. Upside: global optimisations without a separate CFG analysis; a node simply floats in the graph, the scheduler places it optimally. Downside: harder to debug and understand. That is why V8 is considering replacing TurboFan with Turboshaft, a more traditional CFG+IR design.
What is deoptimisation in V8 TurboFan?
Hidden Classes (Maps)
A hidden class (or Map in V8 terminology) is an internal structure that describes the 'shape' of a JavaScript object: the order of properties and their types. Objects that add properties in the same order share one hidden class. That lets the JIT access fields at fixed offsets.
V8 stores hidden classes in the heap as special Map objects. Each JS object carries a pointer to its Map. Transitions between Maps are created lazily: if many objects walk the same creation path, the transitions are effectively cached. SpiderMonkey (Firefox) calls them 'Shapes', JavaScriptCore (Safari/WebKit) calls them 'Structures'. Same idea, originally proposed by Lars Bak in the Self VM (1991) as a way to optimise dynamic objects.
Why is `delete obj.prop` an antipattern from V8's optimisation point of view?
Inline Caches (ICs)
An Inline Cache (IC) caches the result of a property lookup at the call site. Instead of walking the prototype chain on every `obj.x`, an IC caches: 'for objects with Map M1, field x is at byte offset 8'. The next call accesses the field directly through the offset.
ICs were first implemented in Smalltalk-80 (Deutsch & Schiffman, 1984), where it was called 'inline caching of method lookup'. Lars Bak (the creator of V8) worked on the Self VM in the 1990s, where ICs were perfected. SpiderMonkey, JavaScriptCore, and Chakra (IE Edge) all use ICs. Modern ICs in V8 are called 'feedback vector slots': a structured store for all the type information at each call site.
What happens when an IC enters the MEGAMORPHIC state?
Summary
- **Ignition** is a register-based bytecode interpreter with an accumulator. It collects type feedback for TurboFan. Fast startup and small memory footprint.
- **TurboFan** is the optimising JIT on a Sea of Nodes IR. Speculative compilation driven by feedback. Deoptimisation when assumptions break.
- **Hidden classes (Maps)** let V8 access fields at fixed offsets. Objects initialised in the same order share a Map. `delete` destroys the Map.
- **Inline Caches** cache property access by object type. Monomorphic = fast. Polymorphic = acceptable. Megamorphic = slow fallback.
Related topics
V8 brings together every compiler concept for a dynamic language:
- JVM: architecture — JVM and V8 solve similar JIT compilation problems but for statically vs dynamically typed languages
- Bytecode and virtual machines — Ignition is a concrete implementation of a register-based VM with an accumulator
- Global optimisations — TurboFan applies the same optimisations (inlining, LICM, escape analysis) as static compilers
Вопросы для размышления
- V8 makes speculative assumptions about types and deoptimises when they break. TypeScript provides static types, but they are erased at runtime. Could TypeScript help V8 make better assumptions, and why does that not happen?
- Hidden classes are a brilliant solution for optimising dynamic objects. But what happens to Maps in modern JS patterns (spread, object destructuring, Object.assign)? Do they create new hidden classes?
- V8 Turboshaft moves from Sea of Nodes to CFG+SSA. If Sea of Nodes is better for optimisations (no explicit ordering), why abandon it? What exactly is lost in the move to CFG?