Skip to main content
The Dart VM compiler applies a series of optimization passes to transform and improve code. Passes are defined in runtime/vm/compiler/compiler_pass.cc and executed in a specific order for maximum effectiveness.

Compilation Pipeline

The main compilation pipeline (RunPipeline) executes passes in the following order:

Phase 1: SSA Construction

File: compiler_pass.cc:368Transform the flow graph into Static Single Assignment (SSA) form.
flow_graph->ComputeSSA(nullptr);
SSA ensures each variable is assigned exactly once, enabling many optimizations.

Phase 2: Type Propagation and Specialization

File: compiler_pass.cc:386Propagate type information through the flow graph.
FlowGraphTypePropagator::Propagate(flow_graph);
Purpose:
  • Infer precise types for values
  • Enable type-based optimizations
  • Support better code generation
Runs: Multiple times throughout pipeline
File: compiler_pass.cc:389Apply class ID information from type propagation.
state->call_specializer->ApplyClassIds();
Enables class-based optimizations and specialization.
File: compiler_pass.cc:373Apply Inline Cache data from runtime profiling.
state->call_specializer->ApplyICData();
Uses runtime feedback to specialize calls based on observed types.

Phase 3: Inlining

File: compiler_pass.cc:377Set up inlining metadata before inlining begins.
FlowGraphInliner::SetInliningIdAndTryIndex(flow_graph, 0, kInvalidTryIndex);
File: compiler_pass.cc:381 Critical: Yes - major optimizationInline function calls to eliminate call overhead and enable further optimizations.
FlowGraphInliner inliner(flow_graph, state->precompiler);
state->inlining_depth = inliner.Inline();
Benefits:
  • Eliminates call overhead
  • Exposes optimization opportunities
  • Improves constant propagation
  • Enables better register allocation
Pipeline integration: After inlining, the pipeline runs TypePropagation, ApplyClassIds, and other passes again to optimize the newly inlined code.

Phase 4: Simplification and Canonicalization

File: compiler_pass.cc:397 Repeating: YesSimplify and normalize IL instructions.
if (flow_graph->Canonicalize()) {
  flow_graph->Canonicalize();
}
Transforms:
  • Strength reduction (e.g., x * 2x << 1)
  • Algebraic simplification (e.g., x + 0x)
  • Instruction normalization
Runs multiple times to reach fixed point.
File: compiler_pass.cc:375Pattern-based optimizations before representation selection.
flow_graph->TryOptimizePatterns();
Optimizes patterns like:
  • (a << b) & c bit manipulation
  • Instruction merging opportunities
File: compiler_pass.cc:404Simplify branch instructions.
BranchSimplifier::Simplify(flow_graph);
  • Remove redundant branches
  • Merge blocks
  • Eliminate empty blocks
File: compiler_pass.cc:406Convert simple if-then-else patterns to conditional moves.
IfConverter::Simplify(flow_graph);
Transforms:
// Before:
if (condition) x = a; else x = b;

// After:
x = condition ? a : b;  // Single instruction

Phase 5: Constant Propagation

File: compiler_pass.cc:408 Repeating: Yes (up to 2 rounds)Propagate constant values and evaluate constant expressions.
ConstantPropagator::Optimize(flow_graph);
return true;  // Repeatable
Optimizations:
  • Constant folding
  • Dead code elimination
  • Unreachable code removal
Example:
const x = 10;
const y = 20;
var z = x + y;  // Becomes: var z = 30;
File: compiler_pass.cc:466Optimize branches using constant propagation results.
ConstantPropagator::OptimizeBranches(flow_graph);
Eliminates branches with constant conditions.

Phase 6: Integer Optimizations

File: compiler_pass.cc:415Specialize loop phi nodes to Smi when profitable.
LICM licm(flow_graph);
licm.OptimisticallySpecializeSmiPhis();
Optimizes integer loop counters.

Phase 7: Representation Selection

File: compiler_pass.cc:420 Critical: Yes - affects performance significantlyChoose optimal representations for values (boxed vs unboxed).
flow_graph->SelectRepresentations();
Decisions:
  • Unbox doubles for arithmetic
  • Unbox integers where profitable
  • Minimize boxing/unboxing overhead
Example:
// Keeps value unboxed in registers:
double x = 1.0;
double y = x + 2.0;  // Unboxed double arithmetic
File: compiler_pass.cc:427Final representation selection after all optimizations.
flow_graph->SelectRepresentations();
flow_graph->disallow_unmatched_representations();
Ensures all inputs/outputs have matching representations.

Phase 8: Common Subexpression Elimination

File: compiler_pass.cc:438 Repeating: YesEliminate redundant computations.
return DominatorBasedCSE::Optimize(flow_graph);
Example:
var a = x + y;
var b = x + y;  // CSE eliminates second computation
// Becomes:
var a = x + y;
var b = a;

Phase 9: Loop Optimizations

File: compiler_pass.cc:440Move loop-invariant computations outside loops.
flow_graph->RenameUsesDominatedByRedefinitions();
LICM licm(flow_graph);
licm.Optimize();
flow_graph->RemoveRedefinitions(/*keep_checks*/ true);
Example:
// Before:
for (var i = 0; i < n; i++) {
  var x = y * 2;  // Invariant
  result += x;
}

// After:
var x = y * 2;  // Hoisted
for (var i = 0; i < n; i++) {
  result += x;
}

Phase 10: Dead Store Elimination

File: compiler_pass.cc:452Remove stores to variables that are never read.
DeadStoreElimination::Optimize(flow_graph);

Phase 11: Range Analysis

File: compiler_pass.cc:454 Critical: Yes - enables bounds check eliminationInfer integer value ranges to eliminate bounds checks.
RangeAnalysis range_analysis(flow_graph);
range_analysis.Analyze();
Benefits:
  • Eliminates redundant array bounds checks
  • Enables better integer optimizations
  • Proves overflow cannot occur
Example:
for (var i = 0; i < arr.length; i++) {
  arr[i] = 0;  // Bounds check eliminated
}

Phase 12: Dead Code Elimination

File: compiler_pass.cc:483Remove unused phi instructions.
DeadCodeElimination::EliminateDeadPhis(flow_graph);
File: compiler_pass.cc:486Remove unreachable code and unused definitions.
DeadCodeElimination::EliminateDeadCode(flow_graph);

Phase 13: Allocation Optimizations

File: compiler_pass.cc:488Delay allocations to reduce pressure on allocator.
DelayAllocations::Optimize(flow_graph);
File: compiler_pass.cc:490 Critical: Yes - major optimizationSink allocations to reduce heap pressure and enable scalar replacement.
state->sinking = new AllocationSinking(flow_graph);
state->sinking->Optimize();
Transforms:
  • Move allocations closer to use
  • Eliminate allocations that escape only to deopt
  • Enable stack allocation
File: compiler_pass.cc:498Detach materialization instructions after allocation sinking.
state->sinking->DetachMaterializations();

Phase 14: Environment and Stack Optimizations

File: compiler_pass.cc:476Optimize try-catch blocks.
OptimizeCatchEntryStates(flow_graph, /*is_aot=*/CompilerState::Current().is_aot());
File: compiler_pass.cc:481Remove deoptimization environments where safe.
flow_graph->EliminateEnvironments();
Reduces memory overhead of optimized code.
File: compiler_pass.cc:391Remove redundant stack overflow checks.
CheckStackOverflowElimination::EliminateStackOverflow(flow_graph);

Phase 15: Write Barrier Elimination

File: compiler_pass.cc:528Remove unnecessary write barriers for GC.
EliminateWriteBarriers(flow_graph);
Improves performance by eliminating GC overhead where safe.

Phase 16: Dispatch and Call Optimizations

File: compiler_pass.cc:434Replace instance calls with dispatch table calls.
state->call_specializer->ReplaceInstanceCallsWithDispatchTableCalls();
Faster dispatch in AOT mode.
File: compiler_pass.cc:473Optimize typed data array accesses.
TypedDataSpecializer::Optimize(flow_graph);

Phase 17: Code Generation Preparation

File: compiler_pass.cc:530Finalize the graph before code generation.
FlowGraphInliner::CollectGraphInfo(flow_graph, /*constants_count*/ 0,
                                   /*force*/ true, &instruction_count,
                                   &call_site_count);
flow_graph->RemoveRedefinitions();
Prepares metadata and cleans up SSA constructs.
File: compiler_pass.cc:526Reorder basic blocks for better code locality.
BlockScheduler::ReorderBlocks(flow_graph);
Improves instruction cache performance.
File: compiler_pass.cc:508 Critical: Yes - required for code generationAssign registers to values.
flow_graph->InsertMoveArguments();
flow_graph->GetLoopHierarchy();
FlowGraphAllocator allocator(*flow_graph);
allocator.AllocateRegisters();
Uses linear scan register allocation.

Phase 18: Final Transformations

File: compiler_pass.cc:563Lower high-level operations after code motion completes.
flow_graph->ExtractNonInternalTypedDataPayloads();
flow_graph->AddAsanMsanInstrumentation();
flow_graph->AddTsanInstrumentation();
Adds sanitizer instrumentation if enabled.
File: compiler_pass.cc:545Test IL serialization (if enabled).
if (FLAG_test_il_serialization && CompilerState::Current().is_aot()) {
  // Serialize and deserialize for testing
}
Final pass - validates IL can be serialized.

Pass Flags and Control

Control passes with --compiler-passes flag:
# Disable a pass
--compiler-passes=-Inlining

# Print IL after a pass
--compiler-passes=TypePropagation

# Print IL before a pass
--compiler-passes=[Canonicalize

# Print before and after
--compiler-passes=*CSE

# Print after all passes
--compiler-passes=*
Sticky flags with +:
--compiler-passes=Inlining+  # Print after Inlining and all subsequent passes

Inlining Sub-Pipeline

When inlining functions, a sub-pipeline runs on inlined code:
RunInliningPipeline(mode, pass_state):
  - ApplyClassIds
  - TypePropagation
  - ApplyICData
  - Canonicalize
  - ConstantPropagation
  - TryOptimizePatterns
This ensures inlined code is optimized before continuing the main pipeline.

Build docs developers (and LLMs) love