Original address: medium.com/flutter-com…

Original author: medium.com/@jpnurmi

Published: 21 September 2020-4 minutes to read

Dart is a nice modern programming language that not only compiles to JavaScript, but can also be AOT compiled as a cost-independent executable.

dart.dev/platforms

When building the Dart SDK, one can easily choose the desired target architecture, such as X64 and/or ARM. Although one can cross-compile the entire SDK in this way, unfortunately the dart2native compiler that is part of the SDK does not support cross-compilation. To compile Dart code into native ARM binaries, one would have to run the compiler on the target, which is not the solution we’re looking for.

The question is, what do I need to do to enable dart2native to cross-compile ARM binaries for Raspberry Pi on X64 hosts? Well, let’s take a look at what the compiler actually does. From the source code we can see that it.

  1. Build an AOT kernel.
  2. Generates an AOT snapshot and
  3. Combine the results with the Dart AOT runtime.

There is an excellent wiki page on GitHub that summarizes the different types of snapshots in Dart. It explains that kernel snapshots are CPU architecture-independent, while AOT snapshots can be run with Dart TrunTime.

When it comes to dart2native, steps 1 and 2 listed above just invoke external commands, more or less all of them.

<sdk>/bin/dart <sdk>/bin/snapshots/gen_kernel.dart.snapshot \
               --platform <sdk>/lib/_internal/vm_platform_strong_product.dill \
               --aot \
               -Ddart.vm.product=true \
               -o <tmp>/kernel.dill \
               <source>.dart
Copy the code

Generating AOT kernel (gen_kernel)

<sdk>/bin/utils/gen_snapshot --snapshot-kind=app-aot-elf \
                             --elf=<tmp>/snapshot.aot \
                             <tmp>/kernel.dill
Copy the code

Generating AOT Snapshot (gen_snapshot)

Step 3, on the other hand, is not executing an external command, but programmatically append a) the snapshot generated in steps 1-2 and b) the Dart AOT runtime. For reference, here is the final structure of an AOT-compiled Dart executable.

Dart AOT executable file – Structure

It is worth noting that the final step, merging the final application snapshot with the AOT runtime, is optional. It is only needed to compile a completely separate executable. In addition, one can execute AOT snapshots directly with the AOT runtime, which is itself an executable file. This way, one can share the same AOT runtime for multiple Dart applications, which may be preferred to save disk space on a resource-limited system. If you want to know how this works in practice, take a closer look at your Dart SDK’s bin-directory.;)


Now that we know what dart2native does under the hood, we can start tinkering to see if we can generate ARM binaries on the X64. As noted earlier, the AOT kernel is CPU architecturally independent, so it doesn’t matter which Dart SDK gen_kernel tool we use. Let’s assume we already have a Dart SDK built for X64.

# sdk (x64)
$ ./tools/build.py -a x64 -m product create_sdk
Copy the code

For the next step, we need a gen_snapshot tool that can execute on the X64 host architecture and generate AOT snapshots for the ARM target architecture. This is not as complicated as it sounds, because you can simply build the tools for a target architecture called SIMARM to achieve this goal.

# gen_snapshot (simarm)
$ ./tools/build.py -a simarm -m product copy_gen_snapshot
Copy the code

Note that we don’t necessarily need to build the entire SDK, as we only need the gen_snapshot tool. Last but not least, we need a DartaoTruntime binary for the ARM target architecture.

# dartaotruntime (arm)
$ ./tools/build.py -a arm -m product copy_dartaotruntime
Copy the code

Just out of curiosity, let’s quickly check what we’ve got so far.

$ file out/ProductX64/dart-sdk/bin/dart2native [...] /dart2native: Bourne-Again shell script, [...]  $ file out/ProductX64/dart-sdk/bin/dart [...] /dart: ELF 64-bit LSB shared object, x86-64, [...]  $ file out/ProductSIMARM/dart-sdk/bin/utils/gen_snapshot [...] /gen_snapshot: ELF 32-bit LSB executable, Intel 80386, [...]  $ file out/ProductXARM/dart-sdk/bin/dartaotruntime [...] /dartaotruntime: ELF 32-bit LSB shared object, ARM, [...]Copy the code

As you can see, dart2native is really just a script that executes its own snapshot. Still, it looks like we have 32-bit gen_snapshot, which should be able to generate AOT snapshots for ARM, and we also have a DartaoTruntime for ARM. At this point, we basically have all the necessary building blocks. We could manually execute gen_kernel + gen_snapshot, read DartaoTruntime, and finally write the executable by hand, but this is not so simple as it is not just a matter of attaching two files, but of including the correct padding, snapshot offset, and a magic number. What currently prevents us from doing this with the dart2native tool is that it does not allow us to specify gen_snapshot and dartaotruntime that we want to use, but instead finds them relative to dart2native. Therefore, I prepared a simple dart2native patch that allows us to specify both externally.

Github.com/jpnurmi/dar…

With dart2native rebuilt with the patch above, we are ready to start testing cross-compilation. To test this, we’ll use the following little Dart code to read and print the CPU model from /proc/cpuinfo.

import 'dart:io';

void main() {
  final lines = File('/proc/cpuinfo').readAsLinesSync();
  final model = lines.firstWhere((line) => line.startsWith('model name'));
  print(model.split(':').last.trim());
}
Copy the code

Dart Test snippet

Finally!

$ DART2NATIVE_X64=out/ProductX64/dart-sdk/bin/dart2native
$ GEN_SNAPSHOT_SIMARM=out/ProductSIMARM/dart-sdk/bin/utils/gen_snapshot
$ DARTAOTRUNTIME_XARM=out/ProductXARM/dart-sdk/bin/dartaotruntime

# x64
$ $DART2NATIVE_X64cpu.dart -o cpu_x64 Generated: [...] /cpu_x64 $ file cpu_x64 cpu_x64: ELF 64-bit LSB shared object, x86-64, [...] $./ CPU_x64 Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz# arm
$ $DART2NATIVE_X64 cpu.dart --gen-snapshot $GEN_SNAPSHOT_SIMARM --aot-runtime $DARTAOTRUNTIME_XARM-o cpu_arm Generated: [...] /cpu_arm $ file cpu_arm cpu_arm: ELF 32-bit LSB shared object, ARM, [...]# on rpi
pi@raspberrypi:~ $ ./cpu_arm 
ARMv7 Processor rev 3 (v7l)
Copy the code

It worked! 🙂

If you want to make this problem easier to solve, give the following question a thumbs up on GitHub. 👍

Github.com/dart-lang/s…


Translation via www.DeepL.com/Translator (free version)