The Dart VM uses adaptive optimizing compilation driven by runtime execution profiles to generate high-performance code.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/dart-lang/sdk/llms.txt
Use this file to discover all available pages before exploring further.
Compilation Pipeline Overview
The VM has two compilers:- Unoptimizing Compiler - Fast compilation, collects type feedback
- Optimizing Compiler - Slower compilation, applies speculative optimizations
Unoptimizing Compiler
When a function is first called, it’s compiled by the unoptimizing compiler:Pipeline
- Parse Kernel AST - Walk serialized function body
- Build CFG - Generate control flow graph with basic blocks
- Generate IL - Stack-based intermediate language instructions
- Emit code - Direct one-to-many lowering to machine code
Goals
- Compile as quickly as possible
- No optimizations applied
- Collect execution profile:
- Inline caches - Track receiver types at call sites
- Execution counters - Track hot functions and basic blocks
Lazy Compilation
All functions initially point toLazyCompileStub:
Inline Caching
Dynamic calls use inline caching for fast method resolution:Structure
- ICData object - Maps receiver class → method + frequency counter
- Lookup stub - Searches cache, increments counter, tail-calls method
- Runtime miss handler - Resolves method, updates cache
Example
Cache States
- Monomorphic - One class observed (fastest)
- Polymorphic - Few classes observed (fast)
- Megamorphic - Many classes observed (slower, switches to different dispatch)
Optimizing Compiler
When a function’s execution counter reaches threshold (optimization_counter_threshold), it’s submitted to the background optimizing compiler.
Pipeline
- Build unoptimized IL - Same as unoptimizing compiler
- Convert to SSA - Static single assignment form
- Apply optimizations - Multiple passes using type feedback
- Lower to machine code - Linear scan register allocation + lowering
Optimization Passes
Major optimizations include:Inlining
- Replace function calls with function body
- Reduces call overhead
- Enables further optimizations
- Controlled by heuristics (size, depth, hotness)
Type Propagation
- Propagate type information through IL graph
- Uses type feedback from inline caches
- Enables devirtualization and specialization
Range Analysis
- Infer integer value ranges
- Eliminate bounds checks on array access
- Eliminate overflow checks
Representation Selection
- Choose optimal representation (boxed vs unboxed)
- Unbox integers and doubles where possible
- Reduces allocation and improves performance
Common Subexpression Elimination (CSE)
- Eliminate redundant computations
- Reuse previously computed values
Loop-Invariant Code Motion (LICM)
- Move computations out of loops
- Reduces work in hot loops
Load/Store Forwarding
- Forward stored values to subsequent loads
- Eliminate redundant memory accesses
Global Value Numbering (GVN)
- Identify equivalent computations globally
- Eliminate duplicates
Allocation Sinking
- Delay or eliminate temporary object allocations
- Move allocations to where actually needed
Speculative Optimizations
Optimizations based on runtime feedback:- Call specialization - Convert dynamic calls to direct calls based on observed types
- Class hierarchy analysis (CHA) - Use class hierarchy assumptions
- Unboxing - Assume Smi or double based on feedback
Deoptimization
When optimized code encounters a case it can’t handle, it deoptimizes to unoptimized code.Types of Deoptimization
Eager Deoptimization
Inline checks fail at the use site:Lazy Deoptimization
Global guards trigger when runtime state changes:- Class finalization adds subclass (violates CHA assumptions)
- Dynamic code loading invalidates assumptions
- Runtime finds invalid optimized code on stack
- Frames marked for deoptimization, applied on return
Deoptimization Process
- Match deopt ID - Maps optimized code position → unoptimized code position
- Reconstruct state - Build unoptimized frame(s) from optimized state
- Transfer execution - Continue in unoptimized code
- Discard optimized code - Usually discarded, will reoptimize later with updated feedback
Deopt Instructions
Deoptimization uses mini-interpreter executing deopt instructions:- Generated during compilation at each potential deopt location
- Describe how to reconstruct unoptimized state from optimized state
- Handle multiple unoptimized frames from single optimized frame (inlining)
On-Stack Replacement (OSR)
For long-running loops, switch from unoptimized to optimized code while function is running:- Loop executes in unoptimized code
- Loop back-edge counter reaches threshold
- Background compile optimized version with OSR entry point
- On next iteration, jump to optimized code
- Stack frame transparently replaced
Optimization Control Flags
Compilation Control
Debugging Output
Feature Flags
Optimization Levels
The--optimization_level flag controls which optimizations are applied:
Level 1 (Os - Optimize for Size)
- Skip O2 optimizations that increase code size
- Introduce optimizations favoring code size over speed
- Example: Less aggressive inlining
Level 2 (O2 - Default)
- Balanced compile-time, code speed, and code size
- All standard optimizations with proper heuristics
- Default for production
Level 3 (O3 - Optimize for Speed)
- More detailed analysis for speed improvements
- Accept longer compile-time and larger code size
- More aggressive optimization heuristics
Example: Optimization in Action
a and b observed as Smi):
Optimized IL:
Key Source Files
runtime/vm/compiler/compiler_pass.cc- Optimization pass pipelineruntime/vm/compiler/jit/compiler.cc- JIT compiler entry pointsruntime/vm/compiler/jit/jit_call_specializer.cc- Type feedback specializationruntime/vm/compiler/backend/il.h- IL instruction definitionsruntime/vm/compiler/backend/inliner.cc- Inlining logicruntime/vm/compiler/backend/range_analysis.cc- Range analysisruntime/vm/compiler/backend/type_propagator.cc- Type propagationruntime/vm/deopt_instructions.cc- Deoptimization machineryruntime/docs/compiler/optimization_levels.md- Optimization level design