Flutter’s Compilation Patterns

stephenwzl
ProAndroidDev
Published in
5 min readAug 1, 2018

--

People who built App with Flutter must be confused about the exact product compiled by Flutter. Sometimes it’s a shared library, sometimes it was divided into several files. It’s hard to find out about it.

Classification of compilation patterns

Source code written in programming languages need to be compiled to execute. Generally, compilation patterns fall into two categories: JIT and AOT.

JIT

JIT is shorthand for “Just In Time”. A typical example is the V8 engine, it can compile and execute JavaScript source code in time. So you just need to input JavaScript source code string, the V8 engine can help to compile and execute the code. Generally, programming languages that support JIT can support the “eval” function, this contributes to executing source code dynamically.

Obviously, JIT pattern has some advantages: you can dispatch code dynamically to user end without considering user’s machine architecture. With this, you can provide rich and dynamic contents for your users.

But the disadvantages are also obvious: code with a large number of strings will cost JIT compilers a lot of time and memory to compile and execute. This will bring users the feeling that your application is slow to startup.

AOT

AOT represents “Ahead Of Time”. Some programming languages like C/C++ need to be compiled into a special format called “binary” to be loaded or executed by host process, this compilation pattern is called AOT.

AOT has the advantage of speed. With pre-compiled binary format, the code can be executed directly and quickly. This can help to do dense computing or graphics rendering, and bring very good experience for your users.

But AOT also has some disadvantages. When you compile code into binary format, you need to take care of users’ device architectures. You need to generate different binary code for different architectures, this will increase the application bundle size that users need to download. And also, the binary code need to obtain execution permissions from operating system superuser. So binary code cannot be dispatched dynamically by the application developer on some operating system with strict permissions such as iOS.

Dart’s Compilation Patterns

Flutter uses Dart as application development programming language, so Flutter’s compilation patterns cannot get rid of Dart’s. In the first place, we need to take a look at Dart’s compilation patterns.

  • Script: the most common JIT mode. Just like Node.js, you can execute dart source code directly by Dart VM command line tool.
  • Script Snapshot: JIT mode. Unlike the “Script” mode, “Script Snapshot” packs source code into tokenized code. This will save the time that “Lexer” cost during compiling.
  • Application Snapshot: JIT mode. Dart’s “Application Snapshot” is like a kind of dump from runtime. It includes parsed classes and functions from source code, so the runtime can do loading and execution sooner. But this kind of snapshot is architecture specific, you can not run snapshot generated for IA_32 on X64 platform.
  • AOT: AOT mode. In this mode, Dart source code will be translated into assembly files, then assembly files will be compiled into binary code for different architectures by the assembler.

In summary, Dart’s compilation patterns can be concluded in the following table.

Dart’s compilation patterns

Flutter’s compilation patterns

Flutter applications are fully built with Dart, so Flutter’s patterns should be consistent with Dart’s. But actually, due to the big difference between Android and iOS development, Flutter also derived many different patterns. Let’s have a look.

  • Script: The same as Dart’s “Script” mode. Flutter has “Script” mode support, but never used.
  • Script Snapshot: The same as Dart’s “Script Snapshot” mode. Flutter supports it, but also never used.
  • Kernel Snapshot: Dart bytecode mode. This makes Flutter’s Dart runtime like JVM in some degrees. Kernel Snapshot is also called Core Snapshot in the Flutter project. It is non-architecture specific.
  • Core JIT: A kind of binary format of Dart compiled code. Program data and instructions are packed into a specific binary format for Dart runtime to load. Actually, Core JIT is a kind of AOT pattern.
  • AOT Assembly: The same as Dart’s “AOT” mode.

As you can see, Flutter make this more complicated, so it’s hard to explain at once. We should figure it out from the different stage.

Compilation mode in the development stage

When developing Flutter Apps, we need its “HOT Reload” to accelerate UI development. At the same time, we need higher performance than normal JIT runtime to help view rendering. So Flutter uses Kernel Snapshot mode in this stage. After packed application bundle in debug mode, you’ll find these files:

  • isolate_snapshot_data: this file can speed up Dart Isolate startup, it is not related to your business.
  • platform.dill: this file is the Dart runtime kernel representation, only relates to Flutter engine.
  • vm_snapshot_data: this file can speed up Dart VM startup, it is not related with your business.
  • kernel_blob.bin: business related code.
compilation mode in debug stage

Compilation mode in release stage

In production mode, applications need to execute more quickly. So Flutter chose AOT mode when compiling your application code. However, due to different platform characteristics, the compilation mode is also very different. We can conclude these into a table.

compilation patterns in release stage

Firstly, we can easily understand the reasons for practice on iOS platform: App Store does not allow dispatch binary executable code. So many programming languages chose AOT compilation on iOS except JavaScript.

But on Android, Flutter’s compilation mode is a little interesting: both Core JIT and AOT Assembly are supported.

In Core JIT mode, there will be four packed files: isolate_snapshot_data, vm_snapshot_data, isolate_snapshot_instr, vm_snapshot_instr. “isolate_snapshot_instr” is instructions representation of Dart Isolate. “vm_snapshot_instr” is Dart VM’s instructions representation. After Dart runtime loads these data, they can be executed directly.

In Android AOT Assembly mode, it also needs to support multiple architectures binary code generation. This will increase the size of the application bundle without a doubt. Android Apps can only call the function in binary via JNI, it is not as convenient as that call functions in Core JIT binary by Java API. So Flutter chooses Core JIT pattern instead of AOT Assembly on the Android platform.

Flutter engine’s support for compilation patterns

Flutter’s engine contains the Dart runtime, so the compiled Flutter application code must match the engine. So engine provides support for application in different stages by such ways: (we concluded it into a table)

Conclusion

Up to this point, I can come to the conclusion: Flutter is a kind of high-performance, cross-platform, can be dynamically dispatched application development framework.

Its compiled product is interchangeable on both Android and iOS platform in Kernel Snapshot mode. But currently, the Flutter tools does not support for building release products in this mode.

--

--