JavaScript code execution in modern engines involves parsing, interpretation, and Just-In-Time (JIT) compilation, with each engine employing unique architectures for optimization.
At a high level, all JavaScript engines follow a similar workflow: The source code is first fed into a parser, which performs lexical analysis to break the code into tokens and then syntactic analysis to build an Abstract Syntax Tree (AST) . This AST is a tree-like representation of the code's structure. From here, the path diverges slightly based on the engine's architecture, but the goal is the same: to get the code running as efficiently as possible.
The AST is then passed to an interpreter, which generates and executes platform-independent bytecode. This allows execution to start immediately. While the code runs, the engine monitors its behavior, identifying 'hot' functions or code paths that are executed frequently . These hot spots are then sent to a compiler to be optimized into fast machine code. This combination of an interpreter and an optimizing compiler is the essence of JIT compilation. If the compiler's assumptions about the code (e.g., the types of variables) turn out to be incorrect, it performs 'deoptimization,' reverting to the interpreted bytecode .
V8 uses a two-tier execution model with the Ignition interpreter and the TurboFan optimizing compiler .
Ignition quickly generates and executes bytecode from the AST, collecting type feedback (e.g., 'the variable 'a' in sum() is always a number') .
TurboFan uses this feedback to generate highly-optimized machine code for hot functions. It performs aggressive optimizations like function inlining and type specialization .
If the optimized code encounters a situation that violates its assumptions (like 'a' suddenly becoming a string), TurboFan deoptimizes it, and execution falls back to Ignition .
SpiderMonkey employs a more complex multi-tier architecture: Interpreter → Baseline Compiler → IonMonkey Compiler .
The interpreter executes bytecode and monitors for hot code. Once a threshold is hit, the Baseline compiler generates a simply-optimized version of the code quickly .
For extremely hot code, the IonMonkey compiler steps in to perform deep, aggressive optimizations, taking more time for potentially greater performance gains .
This layered approach allows SpiderMonkey to better balance startup speed, steady-state performance, and the need to support all JavaScript language features robustly, including older or more complex patterns .
In addition to JIT compilation, engines manage memory through garbage collection. V8 uses a generational garbage collector called Orinoco, which employs different strategies for the 'young' and 'old' generations to minimize pause times . SpiderMonkey also uses a sophisticated generational collector. A key difference observed in empirical studies is that the 'Compiler' component in V8 is the most bug-prone, while the 'DOM' (Document Object Model) component is the most bug-prone in SpiderMonkey, reflecting their different host environments and optimization focuses .