DEV Community

Cover image for How Parsing, Compiling, and Executing work in the V8 Javascript Engine 🔥
Mohamed Shams El-Deen
Mohamed Shams El-Deen

Posted on

How Parsing, Compiling, and Executing work in the V8 Javascript Engine 🔥

Ever wondered what exactly happens when you run console.log("Hello, World!")?

JavaScript doesn't just magically run in your browser. It goes through some phases inside the JavaScript Engine (like Google's V8) before your CPU can understand it.


Let's break down this journey from raw text to execution: 👀

1. Parsing

Parsing is the process of transforming your raw JavaScript source code into a structured representation that the engine can understand.

The 3 Steps of Parsing:

  • Lexical Analysis (Tokenization / Scanning) The source code is broken down into small, meaningful chunks called tokens (keywords, identifiers, literals, operators, etc.). Example:
   let x = 10;
Enter fullscreen mode Exit fullscreen mode

Tokens generated: let (keyword), x (identifier), = (operator), 10 (literal), ; (punctuation).

  • Syntax Analysis (Building the AST) These tokens are then transformed into an Abstract Syntax Tree (AST). While building this tree, the engine discovers declarations (function, var, let, const). This is exactly where Hoisting and Automatic Semicolon Insertion (ASI) happen. Example:
VariableDeclaration
  ├─ Identifier: x
  └─ Literal: 10
Enter fullscreen mode Exit fullscreen mode
  • Semantic Analysis & Scope Building The engine checks if the code follows language rules. It builds scopes and catches early syntax errors (e.g., declaring the same let variable twice in the same scope) before the code even runs.

2. Compiling

Modern JavaScript engines don't just interpret code, nor do they compile it all ahead of time (like C++). They use Just-In-Time Compilation (JIT) that means compilation happens at runtime.

Here is how V8's JIT architecture handles this:

  1. The Interpreter (Ignition): Takes the AST, translates it into Bytecode, and executes it immediately. This allows your app to start running extremely fast without waiting for a compiler.
  2. Profiling: While the Bytecode runs, a background Profiler watches the code and detects hot code paths (functions or loops executed repeatedly).
  3. The Optimizing Compiler (TurboFan): Takes these "hot" paths and compiles them into highly optimized Machine Code.

3. Executing

Now, the Interpreter (Ignition) executes the unoptimized Bytecode, while the CPU directly executes the fast, optimized Machine Code. However, if the engine's assumptions turn out to be wrong later (e.g., a function that always took a string suddenly takes a number), it performs De-optimization and throws the code back to Ignition's Bytecode.


Important Notes

  • Bytecode always stays in memory. The engine never removes it after generating machine code. It acts as the ultimate source of truth because on de-optimization, execution simply jumps back from the invalid machine code to the existing bytecode.

Why keep bytecode around?

  • Assumptions can break at any time. A safe fallback is mandatory.
  • Bytecode is easier for debugging and garbage collection.
  • Re-parsing JavaScript text into bytecode over and over would be incredibly expensive for the CPU.

The Simplified Execution Map

JavaScript Source
       ↓ (Parsing)
      AST
       ↓ (Ignition)
    Bytecode ───────────────> Executed by Interpreter
       ↓ (Profiling Hot Code)
       ↓ (TurboFan JIT)
Optimized Machine Code ─────> Faster execution by CPU
       ↓ (Assumptions break)
 De-Optimization
       ↓
Back to Bytecode (which is still in memory)
Enter fullscreen mode Exit fullscreen mode

Top comments (0)