V8pedia

Sparkplug (baseline JIT)

Sparkplug is V8's baseline compiler. It does no optimization at all — it walks the bytecode once and emits machine code almost one-to-one. That sounds pointless until you see the trade it makes: for nearly the cost of a memcpy, it deletes the interpreter's per-bytecode dispatch overhead. It is the cheapest rung on the tier ladder and a beautiful example of doing the minimum that helps.

::: info Why "baseline"? A baseline JIT compiles every function the same dumb way, fast, without profiling-driven optimization. It is the floor above interpretation. Contrast with the optimizing tiers (Maglev, TurboFan), which compile selectively and speculatively. :::

Almost 1:1 with bytecode

Sparkplug's compiler is deliberately tiny:

class BaselineCompiler {
 public:
  void GenerateCode();
  MaybeHandle<Code> Build();
  static base::CheckedNumeric<int> EstimateInstructionSize(
      Tagged<BytecodeArray> bytecode);
};

src/baseline/baseline-compiler.h#L51-L71

There is no intermediate representation, no graph, no register allocation pass, no optimization. For each bytecode it emits a short, fixed sequence of machine instructions — often a call to the same builtin the interpreter handler would use. Because there is nothing to analyze, compilation is roughly linear in bytecode size and extremely fast, and it can run on a background thread.

The key trick: same frame as Ignition

Sparkplug code uses the same stack frame layout as the Ignition interpreter — same register file, same slots. This is the design decision that makes the baseline tier cheap to enter and leave:

  • Tiering up from Ignition to Sparkplug needs no frame translation.

  • Sparkplug keeps the same feedback-collection behavior, so the FeedbackVector keeps filling exactly as before.

  • Deopt and OSR interactions stay simple.

To map a machine PC back to a bytecode offset (needed for deopt, stack traces, and on-stack replacement), Sparkplug builds a compact bytecode-offset table, VLQ-encoded as differences:

void AddPosition(size_t pc_offset) {
  size_t pc_diff = pc_offset - previous_pc_;
  base::VLQEncodeUnsigned(&bytes_, static_cast<uint32_t>(pc_diff));
  previous_pc_ = pc_offset;
}

src/baseline/baseline-compiler.h#L31-L49

::: details Why VLQ-encode the offset table? Most bytecode→PC steps are small, so storing differences with a variable-length quantity encoding makes the table tiny. The table is rarely read (only on deopt / stack walks), so a compact representation that trades a little decode time for a lot less memory is exactly right. The result is stored in a TrustedByteArray so it cannot be tampered with after compilation. :::

What Sparkplug buys you

  • Removes dispatch overhead. No more indirect jump per bytecode; control flow is straight-line machine code. This alone is a meaningful speedup on warm code.

  • Near-free to produce. No optimization passes means compile time is predictable and low — cheap enough to apply broadly, early.

  • A smooth on-ramp. Code that later proves very hot moves on to Maglev / TurboFan; code that does not still got a real speedup for almost nothing.

What it does not do: inline, specialize on types, unbox numbers, or eliminate redundant checks. Every property access still goes through an inline cache; every number is still tagged. Those wins require the optimizing tiers.

Where it sits

Ignition  ──(warm)──►  Sparkplug  ──(hot)──►  Maglev  ──(hot & stable)──►  TurboFan
   same frame layout ──┘

See also