Back to V8

Ignition: V8's Interpreter

docs/interpreter/interpreter-ignition.md

15.0.109.6 KB
Original Source

Ignition: V8's Interpreter

Ignition is V8's interpreter. It is the first tier in V8's execution pipeline, responsible for executing JavaScript code quickly after parsing without waiting for expensive compilation. For a high-level overview of Ignition, see the V8 blog post. It was originally written using the backend of TurboFan.

Architecture & Design

Ignition is designed as a high-performance interpreter with several key architectural choices:

1. Register-Based with Accumulator

Unlike stack-based interpreters (like the JVM) that push and pop values from an expression stack, Ignition uses a register file mapped to the native stack.

  • Virtual Registers: Function locals, parameters, and temporaries are mapped to fixed slots in the stack frame.
  • The Accumulator: To keep bytecodes compact, V8 uses a dedicated accumulator register. Many operations implicitly read from or write to the accumulator, avoiding the need to specify register indices in the bytecode operands.

2. Fixed-Size Stack Frame

Ignition uses a fixed-size stack frame for a given function, calculated at compile time by the BytecodeGenerator. The frame layout (defined in src/execution/frame-constants.h) includes (from high to low addresses):

  • Parameters: Passed by the caller (negative offsets from FP).
  • Fixed Header: Return address, saved frame pointer (FP points here), [Constant Pool] (optional), Context, JSFunction, and argument count (argc).
  • Unoptimized Header:
    • BytecodeArray: Pointer to the bytecode being executed.
    • offset / cell: The current bytecode offset or FeedbackCell.
    • FBV: Pointer to the FeedbackVector.
  • Register File: The virtual registers used by the function (positive offsets from FP).

3. Threaded Dispatch

Ignition does not use a centralized switch statement in a loop for interpretation. Instead, it uses threaded dispatch:

  • Every bytecode handler is a standalone chunk of machine code.
  • At the end of each handler, it fetches the next bytecode, looks up its handler address in a dispatch table (stored in a dedicated CPU register for fast access), and directly jumps to it.
  • This minimizes branch mispredictions compared to a giant switch loop.

4. Wide and Extra-Wide Bytecodes

To keep the common case compact, standard bytecodes use 8-bit operands (supporting up to 256 registers or constant pool indices). However, large functions may exceed these limits.

  • V8 solves this using prefix bytecodes: Wide and ExtraWide.
  • If a bytecode needs to reference a register index > 255, the generator emits a Wide prefix followed by the bytecode. The Wide prefix signals the interpreter to read 16-bit operands for the next instruction.
  • ExtraWide allows for 32-bit operands.

5. The Constant Pool

Complex objects, large integers, and strings are not embedded directly in the bytecode stream. Instead, they are stored in a FixedArray called the Constant Pool attached to the BytecodeArray. Bytecodes reference constants via their index in this pool. The ConstantArrayBuilder manages this, placing constants in 8-bit, 16-bit, or 32-bit slices based on frequency and size, coordinating with Wide/ExtraWide bytecodes.

Bytecode Example

To understand how Ignition executes code, let's look at a concrete JavaScript function and the bytecode V8 generates for it.

javascript
function foo(obj, x) {
  if (x > 0) {
    return obj.y + x;
  }
  return 0;
}

Here is the generated bytecode (simplified for readability):

text
         0 : LdaZero                           // Accumulator = 0
         1 : TestGreaterThan a1, [0]           // Compare a1 (x) with accumulator (0)
         5 : JumpIfFalse [13] (at 18)          // If false, jump to offset 18
         7 : GetNamedProperty a0, [0], [1]     // Load obj.y into accumulator (a0 is obj, [0] is name "y")
        11 : Star0                             // Store accumulator (obj.y) in register r0
        12 : Ldar a1                           // Load a1 (x) into accumulator
        14 : Add r0, [0]                       // Add r0 (obj.y) to accumulator (x)
        17 : Return                            // Return accumulator
        18 : LdaZero                           // Accumulator = 0
        19 : Return                            // Return accumulator

Explanation of Bytecodes:

  • LdaZero: Loads the constant 0 into the accumulator.
  • TestGreaterThan a1, [0]: Compares the value in register a1 (parameter x) with the accumulator (0). The [0] is a feedback slot index for the Inline Cache.
  • JumpIfFalse [13]: If the previous test was false, jumps 13 bytes forward to offset 18.
  • GetNamedProperty a0, [0], [1]: Loads a named property from object in a0 (obj). [0] is the index in the constant pool for the string "y". [1] is the feedback slot. The result is placed in the accumulator.
  • Star0: Stores the value in the accumulator into virtual register r0.
  • Ldar a1: Loads the value from register a1 (x) into the accumulator.
  • Add r0, [0]: Adds the value in r0 to the accumulator. [0] is the feedback slot.
  • Return: Returns the value currently in the accumulator.

Bytecode Generation

The process of turning JavaScript source code into bytecode is handled by the BytecodeGenerator in src/interpreter/bytecode-generator.cc.

The AST Visitor

BytecodeGenerator is an Abstract Syntax Tree (AST) visitor. It inherits from AstVisitor<BytecodeGenerator> and implements Visit... methods for all AST node types (e.g., VisitBlock, VisitBinaryOperation, VisitCall).

When GenerateBytecode is called:

  1. It traverses the AST starting from the root function literal.
  2. For each node, it calls the corresponding Visit method.
  3. The Visit method emits bytecodes using the BytecodeArrayBuilder.

Registers and The Accumulator

  • Register Allocation: The generator manages a set of virtual registers. It uses a BytecodeRegisterAllocator to allocate and free registers as it evaluates expressions.
  • Accumulator Optimization: The generator tries to keep values in the accumulator as much as possible to reduce bytecode size. For example, when evaluating a + b:
    1. Visit a for accumulator value (loads a into accumulator).
    2. Store accumulator to a temporary register r0.
    3. Visit b for accumulator value (loads b into accumulator).
    4. Emit Add r0, which adds r0 (containing a) to the accumulator (containing b).

Control Flow

  • Labels: For jumps (loops, conditionals), the generator uses BytecodeLabel to mark jump targets. The BytecodeArrayWriter patches these labels with actual offsets later.
  • Scopes: The generator tracks the current lexical scope and emits bytecodes to create and link context objects (LdaContext, StaContext) when entering blocks with new variables.

Optimizations

  • Star Lookahead: Ignition includes optimizations like StarDispatchLookahead. It checks if the next bytecode is a short Star bytecode (storing accumulator to register). If so, it can inline the effect to avoid a full dispatch.

Example Bytecodes

Here are a few examples of Ignition bytecodes (defined in src/interpreter/bytecodes.h):

  • LdaSmi <imm>: Load a Small Integer immediate into the accumulator.
  • Star <r>: Store the value in the accumulator into register <r>.
  • Ldar <r>: Load the value from register <r> into the accumulator.
  • Add <r>, [slot]: Add the value in register <r> to the accumulator, updating feedback in the specified slot.
  • CallProperty <r>, <r_list>, <r_count>, [slot]: Call a property on an object.

Interaction with Feedback

As Ignition executes bytecode, it collects type feedback for operations that can benefit from optimization (like property accesses and arithmetic).

  • The bytecode contains indices to slots in the FeedbackVector.
  • Handlers write feedback to these slots (e.g., recording the Map of an object seen at a property load).
  • This feedback is later used by Maglev and TurboFan to generate optimized code.

File Structure

  • src/interpreter/interpreter.cc: Setup code and dispatch table initialization.
  • src/interpreter/interpreter-assembler.cc: Contains the actual bytecode handler implementations (using CodeStubAssembler).
  • src/interpreter/bytecode-generator.cc: Generates bytecode from AST.
  • src/interpreter/bytecodes.h: Defines all bytecodes and their properties.

External Resources

Talks

Articles

Design Docs

See Also