V8pedia

Ignition (interpreter)

Ignition is V8's bytecode interpreter and the entry point for all JavaScript. Before any JIT touches a function, Ignition runs it — executing bytecode and, just as importantly, gathering the type feedback that every later tier depends on. Understanding Ignition is understanding where V8's performance story begins.

::: info Ubiquitous language Bytecode: Ignition's instruction set — compact, register-machine ops. Accumulator: an implicit register most bytecodes read from or write to, keeping instructions short. Bytecode handler: the machine-code routine that executes one bytecode and jumps to the next. :::

A register machine with an accumulator

Ignition bytecode is a register machine (not a stack machine): operations name their register operands directly. To keep bytecodes small, one register is implicit — the accumulator — and most operations read or write it without naming it. The implicit-register usage is declared per bytecode:

V(Ldar, ImplicitRegisterUse::kWriteAccumulator, OperandType::kReg)
V(Star, ImplicitRegisterUse::kReadAccumulator,  OperandType::kRegOut)

src/interpreter/bytecodes.h

Ldar r3 loads register 3 into the accumulator; Star r5 stores the accumulator to register 5. Compact bytecode means smaller BytecodeArrays — less memory and better instruction-cache behavior, which matters because bytecode is what runs before anything is optimized.

Dispatch: a table of machine-code handlers

Ignition is not a giant switch. Each bytecode has its own handler — a chunk of machine code (a builtin, generated via CSA) — and the interpreter holds a dispatch table mapping (bytecode, operand-scale) to handler address:

static const int kDispatchTableSize = kNumberOfWideVariants * (kMaxUInt8 + 1);
Address dispatch_table_[kDispatchTableSize];

src/interpreter/interpreter.h#L50-L115

The table is filled at startup with each handler's entry address:

ForEachBytecode([=, this](Bytecode bytecode, OperandScale operand_scale) {
  Builtin builtin = BuiltinIndexFromBytecode(bytecode, operand_scale);
  Tagged<Code> handler = builtins->code(builtin);
  SetBytecodeHandler(bytecode, operand_scale, handler);
});

src/interpreter/interpreter.cc#L351-L391

Each handler does its work and then tail-calls the next handler by indexing the table with the next bytecode. This "threaded" dispatch avoids the central loop and its branch-prediction bottleneck — the CPU predicts each handler's tail jump independently. Handlers are written in CSA/Torque, so the interpreter's core is itself compiled by TurboFan's backend at build time.

::: details Why generate handlers instead of writing a C++ loop? A C++ switch-based interpreter has one indirect branch (the switch) that the CPU struggles to predict, and the C++ compiler controls the register allocation. By generating handlers with CSA, V8 (a) pins the interpreter's key state (bytecode pointer, accumulator, dispatch table base) to specific registers across all handlers, and (b) gets independent, more-predictable tail-call dispatch. This is a classic high-performance-interpreter technique. :::

The interpreter is the profiler

Here is the idea that ties Ignition to the rest of V8: as it executes, Ignition writes feedback. Operations that can specialize — property loads/stores, calls, binary ops — carry a feedback slot index, and their handlers record what they observe (the object's Map, the call target, operand type bits) into the function's FeedbackVector.

So running in Ignition is not wasted time before the "real" compilers — it is the measurement phase that tells Maglev and TurboFan what to bet on. A function that has never run has no feedback, which is exactly why V8 never optimizes cold code.

How a function gets here

The BytecodeGenerator produces the BytecodeArray from the AST and pre-allocates the feedback slots. The function's first calls run through the InterpreterEntryTrampoline, which sets up the interpreter frame and jumps into the first bytecode handler.

Performance characteristics

  • Zero compile cost, smallest memory footprint — ideal for code that runs rarely (which is most code).

  • Dispatch overhead per bytecode is the price; that is what Sparkplug removes by emitting straight-line machine code.

  • Feedback collection adds a little work but is the investment that makes all later speedups possible.

See also