Ignition Interpreter and JS Bytecode Tutorial
Ignition is V8's bytecode interpreter that delivers fast startup by executing compact instructions while collecting type feedback for TurboFan. Learn how it works.
Ignition is V8's bytecode interpreter. It takes the abstract syntax tree produced by the parser, generates compact bytecode instructions, and executes them immediately. Unlike V8's previous approach of compiling all JavaScript to machine code upfront, Ignition starts fast and uses less memory.
Ignition was introduced in 2016 to replace Full-Codegen, V8's earlier baseline compiler. The shift was driven by mobile: Full-Codegen generated machine code for every function, consuming megabytes of memory on devices with tight constraints. Ignition's bytecode is far more compact while its interpreter runs nearly as fast.
How Ignition Fits in the V8 Pipeline
Ignition sits between the parser and the optimizing compiler. It is the first tier that actually executes your code.
Ignition plays two roles. First, it executes bytecode immediately so the program starts quickly. Second, it collects type feedback: what types flow through each operation, which functions are called most often, and which property shapes appear repeatedly.
This feedback is what makes TurboFan's optimizations possible.
Register-Based Bytecode
Ignition uses register-based bytecode rather than stack-based bytecode. In register-based bytecode, operands refer to virtual registers that hold intermediate values. This produces fewer instructions for the same operation compared to a stack-based approach.
A simple addition like a + b might take one instruction: add the value in register r0 to register r1 and store the result in register r2. A stack-based bytecode would need separate push and pop instructions around the add.
Ignition's virtual registers are not real CPU registers. They are slots in a frame-local array that the interpreter manages. The interpreter maps these virtual registers to memory locations and accesses them as needed during execution.
The Ignition Instruction Set
Ignition has roughly 200 opcodes, each representing a specific JavaScript operation. Some common ones include LdaConstant to load a constant value, Add to add two values with JavaScript semantics, CallProperty to invoke a method, Star to store a value into a register, and Return to exit a function.
Each instruction is a few bytes. The opcode is one byte. Operands like register indices and constant pool references take a few more bytes.
A typical function compiles to dozens of bytes of bytecode, far smaller than the equivalent machine code.
The interpreter loop reads each instruction, decodes the opcode, fetches operands from the register file, performs the JavaScript operation (with all its type coercion and prototype chain semantics), and advances to the next instruction. This loop runs millions of times per second. For more on what bytecode looks like, see /javascript/javascript-bytecode-explained-complete-guide.
The Interpreter Dispatch Loop
The heart of Ignition is a hand-tuned dispatch loop written in assembly. A naive interpreter would use a switch statement over opcodes, but that introduces branch prediction misses. Ignition uses computed goto or threaded code techniques where each instruction handler jumps directly to the next handler.
The loop structure is roughly: read the next bytecode opcode, jump through a dispatch table to the handler for that opcode, execute the handler, and repeat. The dispatch table maps each opcode byte to the address of its handler code.
Hand-written assembly allows V8 engineers to control register allocation and instruction scheduling for the dispatch loop. This level of optimization makes interpreted bytecode competitive with simple compiled code.
Constant Pool and Register File
Ignition stores function-wide constants in a constant pool rather than embedding them in each instruction. String literals, number constants, and references to outer-scope variables all live in the constant pool. Instructions reference constants by index.
The register file holds local variables, temporary values, and the accumulator. Ignition uses an accumulator register for many operations: the result of an addition, the value loaded from a property, or the return value of a function call all pass through the accumulator.
This accumulator-based design is a middle ground between pure register-based and pure stack-based bytecode. It keeps instructions short while avoiding the push/pop overhead of stack machines.
How Ignition Collects Feedback
While interpreting bytecode, Ignition records what happens. For every property access like obj.x, it notes which hidden class the object had. For every binary operation like a + b, it notes what types were involved.
For every function call, it tracks how often the function is called.
This feedback is stored in feedback vectors associated with each function. A feedback vector is an array of slots, one per bytecode instruction position. Over time, each slot accumulates data about what types and shapes appeared at that position.
TurboFan reads these feedback vectors when optimizing. If a slot consistently shows that a parameter is a number, TurboFan generates numeric machine code. If a slot shows mixed types, TurboFan generates more general code or decides not to optimize at all.
For the full V8 compilation story, see /javascript/how-the-google-v8-engine-compiles-javascript.
Ignition and Deoptimization
When TurboFan's assumptions break, V8 deoptimizes back to Ignition. The optimized code is discarded. Execution falls back to interpreting the original bytecode.
Ignition bytecode serves as the deoptimization target. It is the canonical representation of what the function does. TurboFan's optimized code is a speculative version that assumes certain types and shapes.
When those assumptions fail, Ignition bytecode is the fallback that always produces the correct result.
This is why Ignition bytecode must handle every JavaScript edge case. The interpreter implements all the language semantics: prototype chain lookups, type coercion, getters and setters, Proxy traps, and the full this-binding rules.
Ignition vs Other Engine Interpreters
Every major JavaScript engine has an interpreter tier, and each makes its own tradeoff between interpreter speed and bytecode compactness.
| Engine | Interpreter | Design Priority |
|---|---|---|
| V8 | Ignition | Compact bytecode, since V8 targets mobile heavily |
| SpiderMonkey | Its own bytecode interpreter, predating Ignition | Slightly faster interpretation over compactness |
| JavaScriptCore | LLInt (Low-Level Interpreter) | Direct threaded interpretation |
The common thread is that all modern engines now use a multi-tier approach with an interpreter feeding an optimizing compiler. For more on engine architecture, see /javascript/what-is-a-javascript-engine-a-complete-guide.
Rune AI
Key Insights
- Ignition is V8's bytecode interpreter, replacing the older Full-Codegen baseline compiler in 2016.
- It generates register-based bytecode that is more compact and faster to interpret than walking the AST.
- Ignition collects type feedback during interpretation that TurboFan uses for optimization.
- The interpreter loop is hand-written assembly for maximum dispatch speed.
- Ignition bytecode uses about 200 different opcodes covering all JavaScript operations.
Frequently Asked Questions
Why did V8 switch from Full-Codegen to Ignition?
Can I see the bytecode Ignition generates?
Conclusion
Ignition is the engine inside the engine. It generates compact bytecode, interprets it immediately for fast startup, and collects the type feedback that makes TurboFan's optimizations possible. Understanding Ignition connects the gap between your source code and the optimized machine code that V8 eventually produces.
More in this topic
Using Decorators for Logging in JS Architecture
Learn how JavaScript decorators wrap class methods to add logging without touching the original code, including setup with Babel and how execution order works.
JavaScript While Loop Explained: A Complete Guide
A while loop repeats code as long as a condition stays true. Learn the syntax, see practical examples, and understand when a while loop is the right choice over a for loop.
JavaScript Loops Tutorial: for, while, do while
Loops let JavaScript repeat code without writing it twice. Learn the three core loop types, what each one does, and when to choose one over another.