JavaScriptCore Multi-Tier Execution: LLInt and DFG

JavaScriptCore, the JavaScript engine powering WebKit and Safari, achieves high performance and fast startup times through a sophisticated multi-tier execution pipeline. This article explores how JavaScriptCore orchestrates execution between its lowest tier, the Low-Level Interpreter (LLInt), and its optimizing mid-tier compiler, the Data Flow Graph (DFG) JIT. By balancing rapid startup via lightweight interpretation with speculative optimizations powered by runtime type profiling, JavaScriptCore dynamically accelerates hot code paths while minimizing memory and compilation overhead.

The Multi-Tier Execution Pipeline

JavaScriptCore uses a four-tier architecture designed to execute code immediately and selectively optimize functions that run frequently. The tiers are:

  1. Low-Level Interpreter (LLInt): Immediate execution with zero compilation latency.
  2. Baseline JIT: Fast, template-based compilation for lightly used code.
  3. Data Flow Graph (DFG) JIT: Optimizing JIT using type speculation and control-flow analysis.
  4. Faster Than Light (FTL) JIT: High-level optimization utilizing LLVM or the B3 backend for long-running code.

While the Baseline JIT often acts as an intermediate step, the transition dynamics between the LLInt and the DFG JIT demonstrate the core principles of how JavaScriptCore manages profiling, tier-up triggers, and speculative compilation.

The Low-Level Interpreter (LLInt)

The LLInt is written in a low-level domain-specific assembly language called offlineasm. It operates directly on JavaScriptCore bytecode without requiring a traditional Just-In-Time compilation step.

Tiering Up: Triggering Compilation

JavaScriptCore uses execution counters embedded within functions and loops to monitor code execution frequency. When a function or loop crosses predefined execution thresholds in the LLInt or Baseline tier:

  1. Counter Evaluation: Every function entry and loop backedge increments a tier-up counter.
  2. Heuristic Evaluation: When the counter reaches its threshold, the engine evaluates whether the collected profiling data is stable enough to justify compilation.
  3. Triggering the DFG: If the criteria are met, the engine invokes the DFG JIT in a background thread to compile the target function or loop.

The Data Flow Graph (DFG) JIT

The DFG JIT is an optimizing compiler that converts bytecode into a high-level intermediate representation (IR) structured as a directed graph representing data flow.

On-Stack Replacement and Deoptimization

Transitions between execution tiers are managed through On-Stack Replacement (OSR):