Back to V8

Sparkplug: V8's Baseline Compiler

docs/compiler/sparkplug/compiler-sparkplug.md

15.0.104.8 KB
Original Source

Sparkplug: V8's Baseline Compiler

Sparkplug is V8's baseline compiler, introduced to fill the performance gap between the Ignition interpreter and the highly-optimizing TurboFan compiler.

Motivation

While V8's Ignition interpreter is highly optimized, interpreters have inherent overheads such as bytecode decoding and dispatch. On the other hand, the TurboFan optimizing compiler can generate highly optimized code, but it requires time to run and needs stable type feedback to be effective.

Sparkplug fills this gap by providing a fast compiler that generates machine code quickly, allowing for aggressive tier-up without waiting for stable feedback.

Architecture

Sparkplug is a non-optimizing compiler that compiles Ignition bytecode directly into machine code.

  • Compiles from Bytecode: Sparkplug "cheats" by not compiling directly from JavaScript source. Instead, it uses the bytecode generated by the Ignition compiler. This means it doesn't have to worry about variable resolution, desugaring, or other complex parsing tasks.
  • No Intermediate Representation (IR): Sparkplug does not build an AST or a control flow graph. It iterates over the bytecode and generates machine code on the fly in a single linear pass. The compiler is essentially a switch statement inside a loop, dispatching to fixed per-bytecode machine code generation functions.
  • No Register Allocation: It does not perform complex register allocation. Instead, it mirrors the register state of the Ignition interpreter. Virtual registers mapped to the stack in Ignition remain on the stack in Sparkplug-generated code. The accumulator register is mapped to a physical register.
  • Architecture Dependent: Because there is no intermediate architecture-independent stage (due to the lack of IR), the entire implementation must be ported separately to each supported architecture. However, because the compiler is simple, this is relatively easy.

Compilation Process

The BaselineCompiler (defined in src/baseline/baseline-compiler.h) drives the compilation:

  1. It iterates through the BytecodeArray using a BytecodeArrayIterator.
  2. For each bytecode, it dispatches to a specific visitor method (e.g., VisitAdd, VisitStar).
  3. The visitor methods use a BaselineAssembler to emit the corresponding machine code instructions.

Interpreter-Compatible Frames

Sparkplug maintains stack frames that are compatible with the Ignition interpreter. This design choice simplifies many aspects of integrating a new compiler:

  • Frame Layout: The stack frame layout for a Sparkplug-compiled function is identical to that of an interpreted function. Whenever the interpreter would have stored a register value, Sparkplug stores one too.
  • Seamless Integration: Because Sparkplug frames look like interpreter frames, the debugger, profiler, exception stack unwinding, and stack trace printing continue to work with almost no changes.
  • Trivial On-Stack Replacement (OSR): Swapping between the interpreter and Sparkplug code can be done with almost zero frame translation overhead.
  • Bytecode Offset Mapping: Sparkplug does not keep the bytecode offset up-to-date during execution. Instead, it maintains a two-way mapping between Sparkplug code address ranges and corresponding bytecode offsets. This mapping is used when the stack walker needs to know the bytecode offset.
  • FeedbackCell Caching: The stack slot that would normally hold the bytecode offset in an interpreter frame is repurposed to cache the FeedbackCell for the currently executing function. (The FeedbackVector is also available in the frame, as in interpreter frames).

Deferring to Builtins

Sparkplug actually generates very little of its own code for complex operations. JavaScript semantics are complex, and regenerating this code inline would increase compile times and memory consumption.

Instead, Sparkplug code mostly calls into "builtins" (small snippets of machine code embedded in the binary) to do the work. These builtins are often the same ones used by the interpreter or share most of their code with the interpreter's bytecode handlers. Sparkplug code is essentially a sequence of builtin calls and control flow.

Benefits

  • Interpretation Overhead Removal: Sparkplug removes unremovable interpreter overheads like operand decoding and next-bytecode dispatch.
  • Quick Startup: It provides a performance boost very quickly after startup, before TurboFan has time to compile the code.

File Structure

  • src/baseline/: Core Sparkplug implementation.
  • src/baseline/baseline-compiler.cc: Contains the main compiler loop and bytecode visitors.
  • src/baseline/baseline-assembler.h: Provides an abstraction over the macro-assembler for Sparkplug.

See Also