How does dart VM work?

Raahavajith
YavarTechWorks
6 min readAug 31, 2022

--

Hello Guys, today we are going to see how the virtual machine works in the amazing multi-platform and client-optimized language DART.

Come on let’s go we gonna see,

Dart Code Run

Dart Compiler is a tool that converts the source code we wrote in dart language to other intermediate languages or machine code that can run on a specific platform in a dart virtual machine. Like in the below figure

Dart Uses different compilers for different specific jobs. For example, the CPU architecture listed below is usually found on desktops and phones. Dart uses JIT(Just in Time Compiler) and AOT(Ahead Of Time Compiler) for native. On the web obviously, it uses a javascript compiler since it needs to translate the code into javascript language.

Now we gonna see the two stages of the development process,

  • Development Phase and
  • Production Phase.

Development Phase - Just Intime Compiler (JIT)

  • JIT just complies just the amount of code it needs.
  • JIT also comes with incremental recompilation so that it only recompiles the modified part when needed.
  • JIT compiler is the main star that enables the hot reload in dart during development.

Production Phase (AOT - Ahead Of Time)

  • The ahead-of-time compiler compiles the entire source code into the machine code supported natively by the platform. It does before the platform runs the program.
  • When you decided to promote your code from development state to production state you need to use this compiler to do its specialized jobs.
  • To benefit from the best and most optimized version of your code has drawbacks through compiling the same code from scratch over and over and it's not the best solution for developing an app into the development stage.

Dart VM

Dart VM provides an execution environment for the dart programming language.

The code within the VM is running within the same isolate and it is also called an isolated dart universe with its own memory known as heap its own thread of control called the mutator thread and its own helper thread.

The heap is a garbage collector that manages the memory storage for all the objects allocated by the code running. This isolates the garbage collector's try to reuse the memory which is allocated by the program but no longer referenced.

Each isolate has a single mutator thread that executes the dart code but benefits from multiple helper threads which handle VM’s internal tasks.

Components of Dart:

  • The Runtime System
  • Development experience components debugging and hot reload
  • JIT and AOT compilation pipelines

Dart VM can execute dart apps in 2 ways from source by using JIT and AOT compiler and from snapshots JIT, AOT, or kernel snapshots.

The source code had to be directed to the dart VM in order to be run but the dart VM doesn’t have the ability to run the raw dart code.

It expects some kernel binary also called .dill files. Which contains serialized abstract syntax tree known as kernel AST. The dart kernel is a small high-level intermediary language derived from dart therefore the kernel AST is actually based on this intermediary language.

The process of translating the dart source code to dart kernel AST is handled by a dart package called Common Front End or CFE.

For example kernel AST tree which is translated from dart source code,

The kernel AST is the file that’s being center-right to the VM in the next step the kernel binary is loaded into the dart VM. It is being parsed to create objects representing various program entities like classes and libraries however this is done lazily which means first and foremost it parses basic information about these entities each entity keeps a pointer back to the kernel binary so that later on it can be accessed if needed hence the name of JIT- Just In Time which is similar to just when it needed the information about the classes is fully deserialized only when the runtime needs it to keep in mind that all those entities lie inside the VM heap which is the VM allocated memory this stage the rest of the entities like fields functions procedures are read from the kernel binary however only their signatures are deserialized at this stage currently there’s enough information loaded from the kernel binary or the runtime to start invoking methods, for example, this is when the run time may start to invoke the main function.

From this step first time, our function is compiled it happens sub-optimally which means the compiler goes and retrieves a function's body from the kernel binary and converts it into an intermediate, and then later on this intermediate language is lowered directly without any optimization passes right into pure machine code the main goal of this approach is to produce executable code quickly, however, the next time this function is called it will use optimized code that means instead of directly lowering that intermediate language into machine code the optimized compiler based on the information gathered from the sub-optimal run proceeds to translate the unoptimized intermediate language through a sequence of classical dart specific optimizations.

For example inlining range analysis type propagation and so on and so forth finally the optimized intermediate language is again lowered into the machine code and run by the VM and that is it this is what actually happens when you run your dart program by typing in dart on command inside the dart CLI this git approach is used inside the development phase.

We have previously talked about since during this phase a fast developer cycle is critical for iteration therefore since the JIT compiler comes with incremental recompilation enabling hot reload multiple optimization techniques and rich debugging support. It’s the perfect choice for this job.

Let’s move on to what happens when we run the code from a source this time by using AOT compiler.

AOT compiler was originally introduced platforms which makes JIT compilation impossible. So, it was mainly an alternative to JIT.

Comparison between JIT and AOT

--

--