Skip to content

Flutter engine operation in AOT Mode

Michael Goderbauer edited this page Mar 23, 2022 · 3 revisions

Overview

The Flutter Engine in AOT mode requires four artifacts to run any given isolate. These are:

  • Dart VM Snapshot
    • Represents the initial state of the Dart heap shared between isolates.
    • Helps launch Dart isolates faster.
    • Does not contain any isolate specific information.
    • Mostly predefined Dart strings used by the VM
    • Should live in the data segment.
      • From the VM's perspective, this just needs to be loaded in memory with READ permissions and does not need WRITE or EXECUTE permissions. Practically this means it should end up in rodata when putting the snapshot in a shared library.
  • Dart VM Instructions
    • Contains AOT instructions for common routines shared between all Dart isolates in the VM.
    • This snapshot is typically extremely small and mostly contains stubs.
    • Must live in the text segment.
  • Isolate Snapshot
    • Represents the initial state of the Dart heap and includes isolate specific information.
    • Along with the VM snapshot, helps in faster launches of the specific isolate.
    • Should live in the data segment.
  • Isolate Instructions
    • Contains the AOT code that is executed by the Dart isolate.
    • Must live in the text segment.

The VM snapshot and instructions can be shared between all isolates. These must be available during the initialization of the Dart VM. The Dart VM is initialized when the first Flutter view initializes an instance of the Flutter shell. The Flutter shell manages thread safe initialization of the Dart VM. Once the Dart VM is initialized, multiple instances of the Flutter view reference the same VM to run their isolates. There can only be one VM running in the process at any given time.

Given this configuration, launching each root isolate (for a Flutter application) requires two artifacts along with two other artifacts that are shared between all the other isolates.

Isolates launched by Dart code (e.g., Isolate.spawn) inherit the snapshots of their parents.

Snapshot Generation

These artifacts on all platforms are generated by the same binary on the host. This binary is shipped with Flutter tools and is called gen_snapshot. The way these artifacts are packed and referenced on the device differs however.

iOS Configuration

gen_snapshot is invoked on the host by Xcode to generate the four binary blobs for each artifact.

Using the native toolchain in Xcode, these artifacts are compiled into a framework bundle that is packaged into the application. This framework is typically called App.framework and is present in the Frameworks/ folder of the application bundle. The name of the framework is configurable and if the embedder wishes to name it anything other than App.framework, the embedder can choose a custom name and specify the same in the FLTLibraryPath Info.plist key for the main application bundle.

The Flutter engine, itself in a Framework called Flutter.framework will dynamically open the resolved application framework and look for four specific symbols in that library. These symbols are called kDartVmSnapshotData, kDartVmSnapshotInstructions, kDartIsolateSnapshotData and kDartIsolateSnapshotInstructions. These refer to the four snapshots mentioned above.

Once snapshot resolution is finalized, the Flutter engine initializes the VM and launched the isolates.

This configuration was chosen because the Flutter engine is not able to mark pages as executable at runtime. Packaging the instruction in a binary makes sure all instructions are present in a separate dynamic library. This arrangement helps in isolate of code for a specific Flutter view’s root isolate.

Android Configuration

gen_snapshot is invoked by Gradle to generate the four binary blobs for each artifact.

Gradle actually invokes flutter build aot under the hood to generate these artifacts. These binary artifacts are packaged into the APK directly. These artifacts are named vm_snapshot_data, vm_snapshot_instr, isolate_snapshot_data and isolate_snapshot_instr. They refer to the four snapshots mentioned above.

The embedder may choose to put these artifacts in custom locations. In such cases, the embedder must place these artifacts in a directory and specify the location using the following 5 flags:

  • aot-snapshot-path: Path to the directory in the APK assets containing the snapshot.
  • vm-snapshot-data: Path to the VM snapshot in that directory.
  • vm-snapshot-instr: Path to the VM instructions in that directory.
  • isolate-snapshot-data: Path to the isolate snapshot in that directory.
  • isolate-snapshot-instr: Path to the isolate instructions in that directory.

Once the Flutter engine, itself packaged as a separate dynamic library called libflutter.so, resolves the locations of the required artifacts, it maps them in and makes sure the instructions are executable before launching the VM and isolate.

The tooling on Android does not require the presence of the Android NDK because the engine can mark pages as executable at runtime. If the embedder wishes to package the snapshots into a single dynamic library (and make the configuration look like the one used on iOS), it can do so and specify the library to the engine via the flag aot-shared-library-path. A native toolchain is necessary on the host to achieve this.

Notes

gen_snapshot can only generate AOT instructions for a specific architecture. For example, generating AOT instructions for armv7, aarch64, i386 and x86-64 requires four different gen_snapshot variants.

The VM and isolate snapshot data is specific to the gen_snapshot variant used and must also be generated along with the AOT instructions. Embedders may not mix and match the AOT instructions and data snapshots from different gen_snapshot variants. All four artifacts must typically be generated at the same time.

Using the current Flutter tools, only an i386 gen_snapshot on the host can generate armv7 AOT instructions and an x86-64 gen_snapshot on the host can generate aarch64 instructions. The mac builds take advantage of this quirk and lipo the two gen_snapshot variants and select the executable architecture correctly when generating AOT instructions for the target architecture. This can be inspected with the lipo tool on the host while analyzing the gen_snapshot binary. In reality, the word sizes of gen_snapshot on host and the engine running on the target must match. Theoretically, an armv7 gen_snapshot running on a Raspberry Pi host could generate an AOT snapshot for armv7 target. The tools don’t ship this configuration however.

Packaging multiple Flutter AOT application in the same application bundle will currently result in redundant copies of the VM data and instructions. However, these buffers are extremely small.

Flags to the Flutter engine are typically specified when the platform launches the underlying Flutter shell (platform agnostic way of interacting with the innards of the Flutter engine). This happens in FlutterViewController on iOS and FlutterNativeView in Java on Android.

To list all the flags currently supported by the Flutter engine, embedders may locate the flutter_tester binary in the tools distribution and pass the --help flag to the same. All currently supported flags along with a short help blob will be dumped to the console.

Flutter Wiki

Process

Framework repo

The Flutter CLI Tool

Engine repo

Packages repo

Engineering Productivity

User documentation

Clone this wiki locally