| |

Dart 1 🎯 Dart Runtime Engine, JIT/AOT Compilers, and Execution Architecture

Dart is a client-optimized programming language designed for building fast applications on any platform. What makes Dart distinctive is not just its syntax or type system, but its execution architecture: a runtime that can run code in fundamentally different ways depending on the target platform and the development phase. The same Dart code can be interpreted on the fly during development, compiled to native machine code for production, or translated to JavaScript for the browser. Understanding how the runtime engine, the JIT compiler, and the AOT compiler work together is the foundation for understanding everything else about Dart.

The previous chapters in this series have covered JavaScript, TypeScript, and the React ecosystem. This chapter begins the Dart series with the execution model that sits underneath the language. Dart is the language that powers Flutter, but its runtime architecture was designed independently and evolved alongside the language itself. The VM that runs Dart today is a different machine from the one that ran the first version of the language in 2011.

Key point: Dart has two primary compilation modes: JIT (Just-in-Time) and AOT (Ahead-of-Time). The JIT compiler runs during development and provides sub-second hot reload by compiling code incrementally as the program executes. The AOT compiler runs before deployment and produces native machine code with instant startup and consistent performance. The Dart VM includes both modes, and the choice between them is determined by the target platform and the development phase, not by the language itself.


Why the Execution Architecture Matters

A programming language is not just its syntax. It is also the machine that runs it. The execution architecture determines startup time, peak performance, memory usage, and the development workflow. Dart’s architecture is unusual because it supports two very different execution models within a single runtime.

The development workflow problem. During development, the developer changes code and wants to see the result immediately. A compiler that takes seconds to rebuild the entire application makes iteration painful. Dart’s JIT compiler solves this with incremental recompilation: it compiles only the functions that changed, and it can swap the compiled code into the running program without restarting. This is what makes hot reload possible .

The production problem. In production, startup time and consistent performance matter more than iteration speed. A user opening an app wants it to load instantly. A server handling requests cannot afford JIT warmup pauses. Dart’s AOT compiler solves this by compiling the entire application to native machine code before deployment. The compiled code starts instantly and runs at consistent speed from the first instruction .

The platform problem. Some platforms forbid JIT compilation. iOS, for example, does not allow applications to generate and execute code at runtime. Dart’s AOT compiler makes it possible to run Dart on these platforms by compiling everything ahead of time .

The trade-off. JIT compilation has access to runtime type information and execution profiles, which allows it to apply speculative optimizations that make the code faster. AOT compilation does not have this information, so it must make conservative assumptions. The result is that JIT-compiled code can have better peak performance, while AOT-compiled code has better startup time and consistent performance. Dart’s architecture provides both, and the choice depends on the phase and platform .


a. The Dart VM and Isolates

The Dart Virtual Machine is the runtime that executes Dart code. It is not a single program but a collection of components: a JIT compiler, an AOT compiler, a garbage collector, a set of core libraries, and the execution engine that coordinates them. The VM is embedded in different hosts depending on the platform. On mobile and desktop, Flutter embeds the VM. On the command line, the dart executable embeds the VM. In the browser, the Dart code is compiled to JavaScript and runs in the browser’s JavaScript engine, not the Dart VM .

The VM’s fundamental unit of execution is the isolate. An isolate is an independent worker with its own heap and its own event loop. Isolates do not share memory. They communicate only by sending messages through ports. This model is similar to the actor model in other languages: each isolate is a self-contained universe that processes events and sends messages to other isolates .

The relationship between isolates and operating system threads is subtle. An OS thread can enter an isolate, execute Dart code, and then leave. It can then enter a different isolate. The VM guarantees that only one thread executes Dart code in a given isolate at any moment. The isolate can also have auxiliary threads for background compilation, garbage collection, and other internal tasks, but these threads do not execute Dart code .

The isolate model has significant implications for concurrency. Because isolates do not share memory, there are no data races. Because they communicate by message passing, the developer must explicitly define the messages that cross isolate boundaries. This is stricter than the shared-memory model of Java or C++, but it eliminates an entire class of concurrency bugs. Dart’s async/await syntax and Future and Stream types are built on top of the isolate model, providing a high-level abstraction for asynchronous programming .


b. The JIT Compiler: Development Mode

The JIT compiler is the component that makes Dart’s development workflow fast. It compiles functions on demand, observes how the program executes, and recompiles hot code paths with aggressive optimizations. The result is a system that starts running quickly and gets faster as it runs.

The JIT pipeline has two compilers. The unoptimizing compiler produces machine code as quickly as possible. It walks the serialized AST of each function, builds a control flow graph, and lowers the intermediate language instructions directly to machine code. It performs no optimizations. Its goal is to get the program running, not to make it fast .

The unoptimized code collects execution profiles as it runs. Inline caches record the receiver types observed at each call site. Execution counters track how often each function and basic block runs. When a function’s counter reaches a threshold, the function is submitted to the optimizing compiler .

The optimizing compiler starts from the same AST but translates it into static single assignment (SSA) form. It then applies speculative specializations based on the collected type feedback: inlining, range analysis, type propagation, representation selection, and other classical optimizations. The resulting code is much faster than the unoptimized version, but it relies on assumptions about the types observed at runtime. If those assumptions are violated — for example, if a class that was never extended gains a subclass through dynamic code loading — the VM deoptimizes: it discards the optimized code and returns to the unoptimized version .

The JIT compiler runs in a background thread, so compilation does not block the application. When compilation completes, the background thread requests the mutator thread to enter a safepoint and attaches the optimized code to the function. The next call to the function uses the optimized version. For long-running loops, the VM can perform on-stack replacement (OSR): it swaps the unoptimized frame for an optimized frame while the function is still running .

The JIT mode is what enables hot reload. The frontend_server process compiles the changed libraries to a Kernel binary, and the VM replaces the compiled code in the running isolate without restarting the application. The developer sees the change immediately .


c. The AOT Compiler: Production Mode

The AOT compiler produces native machine code before the application runs. It is used for production deployments on mobile and desktop, and it is required on platforms that forbid JIT compilation.

The AOT pipeline begins with type flow analysis (TFA). The compiler analyzes the entire program to determine which functions are reachable from the known entry points, which classes are allocated, and how types flow through the code. These analyses are conservative: they err on the side of correctness. The compiler must compile code for every function that could be invoked, even if it probably will not be .

After TFA, the compiler compiles every reachable function to native machine code. It does not apply speculative optimizations, because there is no runtime profile to guide them. Instead, it uses the type flow information to specialize the code — devirtualizing calls where the receiver type is known, for example. The resulting code is not as fast as JIT-optimized code, but it starts instantly and performs consistently .

Once all functions are compiled, the VM takes a snapshot of the heap. The snapshot is a low-level serialization of the object graph, optimized for fast startup. When the application starts, the VM maps the snapshot into memory and the application is immediately ready to run. There is no parsing, no compiling, no warmup .

The AOT-compiled application runs on a precompiled runtime, a variant of the Dart VM that excludes the JIT compiler and dynamic code loading facilities. This reduces the size of the runtime and ensures that the application cannot generate or execute code at runtime, which is required on platforms like iOS .

AOT compilation is invoked through the dart compile command. The dart compile exe subcommand produces a native executable. The dart compile aot-snapshot subcommand produces an AOT module that can be run with the dartaotruntime executable .


Complete Example Session

This session demonstrates the JIT and AOT compilation modes on a simple Dart application.

// ============================================
// PART 1: THE APPLICATION
// ============================================

// hello.dart
void main() {
  print('Hello, Developer!');
}

// ============================================
// PART 2: RUNNING WITH JIT
// ============================================

// dart run hello.dart
// Output: Hello, Developer!

// The JIT compiler parses and compiles the code on the fly.
// The program starts quickly but the code is unoptimized.

// ============================================
// PART 3: COMPILING WITH AOT
// ============================================

// dart compile exe hello.dart -o hello
// Output: Generated: /path/to/hello

// The AOT compiler produces a native executable.
// The executable includes the compiled code and a snapshot of the heap.

// ============================================
// PART 4: RUNNING THE AOT EXECUTABLE
// ============================================

// time ./hello
// Hello, Developer!
// real    0m0.012s

// The AOT-compiled application starts in 12 milliseconds.
// The JIT-compiled version takes longer to start because it must compile first.

// ============================================
// PART 5: THE JIT SNAPSHOT
// ============================================

// dart compile jit-snapshot hello.dart -o hello.jit
// Output: Compiling hello.dart to jit-snapshot file hello.jit.

// The JIT snapshot contains the parsed classes and compiled code
// from a training run. Starting from the snapshot skips parsing
// and compilation for the code that ran during training.

// ============================================
// PART 6: THE AOT SNAPSHOT
// ============================================

// dart compile aot-snapshot hello.dart -o hello.aot
// Output: Generated: /path/to/hello.aot

// The AOT snapshot is architecture-specific.
// It contains executable code for every reachable function.
// It is run with the dartaotruntime executable.

// ============================================
// PART 7: THE SNAPSHOT FORMAT
// ============================================

// A snapshot is a serialized object graph.
// The format is low-level and optimized for fast startup.
// It is essentially a list of objects to create and instructions
// on how to connect them together.

// The idea has roots in Smalltalk images.
// The VM uses clustered serialization.
// Initially snapshots did not include machine code.
// The AOT compiler added the capability to include code sections.

// ============================================
// PART 8: THE DEOPTIMIZATION MECHANISM
// ============================================

// The JIT compiler makes speculative assumptions.
// Example: a class C is never extended.
// The optimizing compiler uses this to propagate types.
// If C gains a subclass at runtime, the assumption is invalid.
// The runtime finds and discards optimized code that relied on it.
// Frames on the execution stack are marked for lazy deoptimization.
// They deoptimize when execution returns to them.

// ============================================
// PART 9: THE ISOLATE MODEL
// ============================================

// Isolates are independent workers with their own heap.
// They do not share memory.
// They communicate by message passing through ports.
// One OS thread executes Dart code in an isolate at a time.
// The isolate can have auxiliary threads for compilation and GC.

// ============================================
// PART 10: THE COMPILATION COMPARISON
// ============================================

// JIT:
//   - Compiles during execution
//   - Observes runtime behavior
//   - Applies speculative optimizations
//   - Can deoptimize when assumptions break
//   - Best peak performance, slow startup
//   - Enables hot reload

// AOT:
//   - Compiles before execution
//   - No runtime profile
//   - Conservative optimizations
//   - No deoptimization needed
//   - Best startup time, consistent performance
//   - Required on platforms that forbid JIT

The ten parts cover the application, running with JIT, compiling with AOT, running the AOT executable, the JIT snapshot, the AOT snapshot, the snapshot format, the deoptimization mechanism, the isolate model, and the compilation comparison.


Quick Reference

The Compilation Modes

ModeWhenStartupPeak PerformanceUse Case
JITDuring executionSlowBestDevelopment, hot reload
AOTBefore executionInstantGoodProduction, iOS

The Compilation Commands

CommandOutputPurpose
dart runNoneRun with JIT
dart compile exeNative executableAOT compile for production
dart compile aot-snapshot.aot fileAOT module
dart compile jit-snapshot.jit fileJIT module
dart compile kernel.dill fileKernel binary

The Snapshot Types

TypeContainsArchitecture-Specific
KernelSerialized ASTNo
JITParsed classes, compiled codeYes
AOTExecutable code, heap snapshotYes

The Isolate Model

AspectBehavior
MemoryNot shared between isolates
CommunicationMessage passing through ports
ThreadingOne mutator thread per isolate
ConcurrencyIsolates run independently

Best Practices

✅ Do This:

# Use dart run for development
dart run hello.dart                                            # ✅
# Use dart compile exe for production
dart compile exe hello.dart -o hello                           # ✅
# Use AOT for iOS and other JIT-restricted platforms
dart compile aot-snapshot hello.dart                           # ✅
# Use isolates for CPU-intensive work
Isolate.spawn(heavyComputation, data)                          # ✅

❌ Don’t Do This:

# Don't use JIT for production on mobile
dart run hello.dart  # slow startup, warmup pauses            # ❌
# Don't share memory between isolates
# Isolates do not share memory                                  # ❌
# Don't expect JIT on iOS
# iOS forbids JIT compilation                                   # ❌

Common Pitfalls

PitfallWhy It HappensFix
Slow startup in productionUsing JITUse AOT
iOS build failsJIT not allowedUse AOT
Hot reload not workingJIT not enabledUse dart run
Memory not freedIsolates hold referencesSend messages, terminate isolates
Deoptimization pausesSpeculative assumptions brokenAccept or restructure code

Real-World Examples

1. Run with JIT

dart run hello.dart

2. Compile to Executable

dart compile exe hello.dart

3. Run the Executable

./hello

4. Compile to AOT Snapshot

dart compile aot-snapshot hello.dart

5. Compile to JIT Snapshot

dart compile jit-snapshot hello.dart

6. Spawn an Isolate

Isolate.spawn(workerFunction, message);

7. Send a Message

SendPort.send(data);

8. Receive a Message

ReceivePort.listen((message) { });

9. AOT for iOS

flutter build ios --release

10. Hot Reload

flutter run

Visual

The JIT Pipeline

┌──────────────────────────────────────────────┐
│  JIT PIPELINE                                │
│                                              │
│  Source Code                                 │
│    │                                         │
│    ▼                                         │
│  Kernel AST (dill file)                      │
│    │                                         │
│    ▼                                         │
│  Unoptimizing Compiler                       │
│    └─ Fast, no optimizations                 │
│    └─ Produces executable code               │
│    │                                         │
│    ▼                                         │
│  Execution + Profile Collection              │
│    └─ Inline caches                          │
│    └─ Execution counters                     │
│    │                                         │
│    ▼                                         │
│  Optimizing Compiler (background)            │
│    └─ SSA form, speculative optimizations    │
│    └─ Deoptimization support                 │
│    │                                         │
│    ▼                                         │
│  Optimized Code                              │
│                                              │
└──────────────────────────────────────────────┘

The AOT Pipeline

┌──────────────────────────────────────────────┐
│  AOT PIPELINE                                │
│                                              │
│  Source Code                                 │
│    │                                         │
│    ▼                                         │
│  Kernel AST (dill file)                      │
│    │                                         │
│    ▼                                         │
│  Type Flow Analysis (TFA)                    │
│    └─ Conservative, global analysis          │
│    └─ Determines reachable code              │
│    │                                         │
│    ▼                                         │
│  Compile All Functions                       │
│    └─ No speculative optimizations           │
│    └─ Use TFA for specialization             │
│    │                                         │
│    ▼                                         │
│  Snapshot with Code                          │
│    └─ Heap serialization                     │
│    └─ Executable code section                │
│    │                                         │
│    ▼                                         │
│  Precompiled Runtime                         │
│    └─ No JIT, no dynamic loading             │
│    └─ Instant startup                        │
│                                              │
└──────────────────────────────────────────────┘

The Isolate Model

┌──────────────────────────────────────────────┐
│  ISOLATE MODEL                               │
│                                              │
│  Isolate A                Isolate B          │
│  ┌─────────┐              ┌─────────┐        │
│  │  Heap   │              │  Heap   │        │
│  └────┬────┘              └────┬────┘        │
│       │                        │             │
│       │  Message Passing       │             │
│       └────────────────────────┘             │
│                                              │
│  No shared memory.                           │
│  Communication through ports.                │
│                                              │
└──────────────────────────────────────────────┘

JIT vs AOT Comparison

┌──────────────────────────────────────────────┐
│  JIT vs AOT                                  │
│                                              │
│  JIT:                                        │
│    ├─ Startup: slow (warmup)                 │
│    ├─ Peak: best                             │
│    ├─ Profile: available                     │
│    ├─ Deoptimization: yes                    │
│    └─ Hot reload: yes                        │
│                                              │
│  AOT:                                        │
│    ├─ Startup: instant                       │
│    ├─ Peak: good                             │
│    ├─ Profile: not available                 │
│    ├─ Deoptimization: no                     │
│    └─ Hot reload: no                         │
│                                              │
└──────────────────────────────────────────────┘

Summary

ItemValue
RuntimeDart VM
JIT modeJust-in-time, during execution
AOT modeAhead-of-time, before execution
JIT strengthPeak performance, hot reload
AOT strengthInstant startup, consistent performance
JIT weaknessSlow startup, warmup
AOT weaknessNo speculative optimizations
Execution unitIsolate
Isolate memoryNot shared
Isolate communicationMessage passing
SnapshotSerialized heap, fast startup
DeoptimizationJIT fallback when assumptions break

Key takeaways:

  • Dart has two compilation modes: JIT and AOT. The JIT compiler runs during development and provides sub-second hot reload by compiling code incrementally as the program executes. The AOT compiler runs before deployment and produces native machine code with instant startup and consistent performance .
  • The JIT compiler observes runtime behavior and applies speculative optimizations. The unoptimizing compiler produces code quickly. As the code runs, it collects execution profiles through inline caches and counters. When a function becomes hot, the optimizing compiler recompiles it with aggressive optimizations based on the collected type feedback .
  • Deoptimization is the JIT’s safety net. When speculative assumptions are violated — for example, a class that was never extended gains a subclass — the runtime discards the optimized code and falls back to the unoptimized version. Frames on the execution stack are marked for lazy deoptimization .
  • The AOT compiler uses type flow analysis instead of runtime profiles. TFA is a global, conservative analysis that determines which functions are reachable and how types flow through the program. All reachable functions are compiled without speculative optimizations. The result starts instantly and performs consistently .
  • AOT compilation is required on platforms that forbid JIT. iOS does not allow applications to generate and execute code at runtime. Dart’s AOT compiler makes it possible to run Dart on these platforms by compiling everything ahead of time .
  • The fundamental unit of Dart execution is the isolate. An isolate is an independent worker with its own heap and event loop. Isolates do not share memory. They communicate only by sending messages through ports. This eliminates data races and enforces explicit message passing .
  • Snapshots provide fast startup by serializing the heap. A snapshot is a low-level serialization of the object graph, optimized for fast startup. Instead of parsing source code and creating internal data structures, the VM maps the snapshot into memory and the application is immediately ready to run .

Remember: Dart’s execution architecture is built around two compilers. The JIT compiler makes development fast with hot reload and runtime profiling. The AOT compiler makes production fast with instant startup and consistent performance. The isolate model provides concurrency without shared memory. The snapshot format provides fast startup by skipping parsing and compilation. The choice between JIT and AOT is determined by the platform and the phase, not by the language. And the deoptimization mechanism ensures that the JIT compiler’s speculative optimizations never compromise correctness.


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!