V8 · The compilation pipeline

The pipeline, tier by tier

V8 does not choose between an interpreter and a compiler — it runs four tiers at once and moves each function between them by how hot it is. Cold code is interpreted, warm code is compiled cheaply, hot code is compiled speculatively, and code that breaks its own assumptions falls back down.

01

Parser & preparser

source → AST

The scanner reads the source once. The preparser skims function bodies it can defer, recording only the scope information needed to compile them later, and a full parse happens the first time a function is actually called. Lazy parsing is why a large bundle costs less than its byte count suggests.

function f() { … }   // pre-parsed, skimmed
f()                  // now fully parsed
02

Ignition

the interpreter

Compiles the AST into a compact register-based bytecode with one implicit accumulator, then interprets it. The handlers themselves are generated machine code, so the interpreter is fast — but the reason it exists is size. Bytecode is a fraction of what full machine code would cost in memory.

LdaSmi [1]      // accumulator ← 1
Star r0         // r0 ← accumulator
03

Sparkplug

the baseline tier

A non-optimising compiler that walks the bytecode once and emits machine code for each instruction. No IR, no register allocation, no analysis — and it keeps the interpreter’s exact stack frame layout, so a function can be swapped between tiers mid-run. It buys a flat speed-up for almost no compile time.

// bytecode → machine code, one pass
// same frame layout as Ignition
04

Maglev

the mid tier

The tier between baseline and full optimisation. It builds a real SSA control-flow graph and specialises on collected feedback, but skips the expensive global reordering TurboFan does. Warm functions get most of the win long before they are hot enough to justify the top tier.

--maglev   // default since Chrome 117
05

TurboFan

the optimising tier

A sea-of-nodes IR where value, effect and control are separate edge kinds, so most nodes float free of any order until scheduling places them. It optimises speculatively on feedback — this argument has only ever been a small integer — inlines aggressively, and guards every assumption it made.

// nodes float; only effect and control
// edges pin them to an order
06

Deoptimisation

the escape hatch

When a guard fails, V8 discards the optimised frame and rebuilds an interpreter frame from it in place, resuming in Ignition at the exact bytecode offset. Nothing observable happens. Speculation is only a sane strategy because being wrong is recoverable rather than fatal.

--trace-deopt
// deoptimizing … reason: wrong map