From Engine Patches to Running Apps on RISC-V Devices
Flutter works on many platforms: from Web and Mobile, to Desktop and Embedded. When it comes to Embedded however, support is still constrained by the CPU architecture used. In a past blog post, we made Flutter & Dart work on ARMv6 as a Proof of Concept. Now, we're going to look at something more modern: The RISC-V architecture is an upcoming, free and open-source CPU architecture, which shows great potential specifically for Embedded use-cases.
In cooperation with Cloud-V, we made it possible to run Flutter apps on RISC-V hardware. This involves a few parts: Possibly the largest one is RISC-V support in the Dart programminfg language. Dart has its own compiler backend which compiles Dart code into architecture-specific native code, so naturally, it is the component that contains the most architecture-specific parts. Fortunately, adding a RISC-V backend to the Dart SDK was already done by the Dart team in 2022: https://dart-review.googlesource.com/c/sdk/+/217289
In addition to that, changes in the Flutter Engine (more precisely the build scripts) are required to allow building it for RISC-V. And lastly, the part that brings it all together - and that actually allows building and running Apps on RISC-V devices - is the Flutter Tool. We are working on PRs for the official Flutter Tool & Engine, but for the meantime that functionality can be used via flutterpi_tool.
First, the flutter engine has to be built for RISC-V. Normally, this involves checking out the engine sources and then using the engines gn
script to generate the build files.
As an example, building for a Linux ARM64 machine roughly looks like this:
$ gclient sync
...
$ pushd engine/src && ./flutter/tools/gn \
--linux --linux-cpu arm64 \
--runtime-mode release \
--embedder-for-target --disable-desktop-embeddings \
--no-enable-unittests --no-build-glfw-shell \
--no-build-embedder-examples
Generating GN files in: out/linux_release_arm64
Generating compile_commands took 77ms
Done. Made 1178 targets from 345 files in 433ms
$ ninja -C out/linux_release_arm64
This gn
script that is being invoked there doesn't actually do that much, it just basically a convenience script that converts the given command-line arguments into a different form, an args.gn
file. This is then used by the actual gn
tool to generate the build files. So technically, it's not strictly necessary to modify it; it would also be possible to skip the gn
script and just craft a special args.gn
file for RISC-V manually. In this case we modified it to allow passing --linux-cpu riscv64
, for development convenience.
Of course, if you now try running a build like that, you'll undoubtedly run into errors. One of the first errors that'll appear is that the build scripts try to use a wrong sysroot for building the RISC-V 64 binaries.
The reason for that can be found in the sysroot.gni
^1, which is a gn
"header". This header has the job of resolving the correct sysroot to be used for cross-compilation. By default, the flutter build will download & use its own custom sysroot^2 from a google storage bucket. For platforms like linux-armv7, which are also not officially supported by the flutter build, it's enough to just manually invoke those scripts to download the required sysroot. For RISC-V however, this, unfortunately, does not work, because there's no RISC-V sysroot present that could be downloaded from the google storage bucket.
There's multiple ways to go forward here; you could, for example, build your own custom sysroot and use that. That could be done on a native RISC-V machine, in a VM, or in a Docker container. However, there's an easier way: installing a RISC-V gcc will also install a minimal RISC-V "sysroot" that can be used to build simple binaries. Fortunately, the Flutter engine intentionally doesn't have a lot of shared library decencies, which is ideal because we are using a minimal sysroot that might not have them. This approach was attempted and proved to work well enough for our purposes.
In practice that means compilation is just done without an explicit sysroot and clang auto-selects the RISC-V libc headers.
--- a/engine/src/build/config/sysroot.gni
+++ b/engine/src/build/config/sysroot.gni
@@ -21,7 +21,16 @@ if (current_toolchain == default_toolchain && target_sysroot != "") {diff
import("//build/config/android/config.gni")
sysroot = rebase_path("$android_toolchain_root/sysroot", root_build_dir)
} else if (is_linux && !is_chromeos) {
- if (use_default_linux_sysroot && !is_fuchsia) {
+ # install-sysroot.py doesn't provide a RISC-V 64 sysroot, which means
+ # we can't provide a default sysroot.
+ #
+ # If we make this fail here, when use_default_linux_sysroot == true and
+ # current_cpu == "riscv64", the sysroot choosing for arm/arm64/x64
+ # will fail as well, so we wouldn't be able to cross-compile the gen_snapshots
+ # and build the libflutter_engine.so in one build anymore.
+ #
+ # Instead we just silently use no sysroot for riscv64 at all.
+ if (use_default_linux_sysroot && !is_fuchsia && current_cpu != "riscv64") {
if (current_cpu == "x64") {
sysroot =
rebase_path("//build/linux/debian_sid_amd64-sysroot", root_build_dir)
One more thing we have to do is actually make the compiler target riscv64-linux-gnu
. There's a specific gn
file that does the compiler setup. The RISC-V target handling can just be added there:
--- a/engine/src/build/config/compiler/BUILD.gn
+++ b/engine/src/build/config/compiler/BUILD.gn
@@ -288,6 +288,11 @@ config("compiler") {
if (arm_tune != "") {
cflags += [ "-mtune=$arm_tune" ]
}
+ } else if (current_cpu == "riscv64") {
+ assert(is_clang)
+
+ cflags += [ "--target=riscv64-linux-gnu" ]
+ ldflags += [ "--target=riscv64-linux-gnu" ]
}
if (!is_android) {
Attempting to build the engine now will result in an error while resolving the freetype2
headers via pkg-config
. However, as far as I can tell, the engine checks out & builds its own copy of freetype2, so it seems like this resolved dependency isn't actually used. Simply removing the offending code fixes the issue:
--- a/engine/src/build/config/linux/BUILD.gn
+++ b/engine/src/build/config/linux/BUILD.gn
@@ -27,9 +27,9 @@ config("fontconfig") {
libs = [ "fontconfig" ]
}
-pkg_config("freetype2") {
- packages = [ "freetype2" ]
-}
+#pkg_config("freetype2") {
+# packages = [ "freetype2" ]
+#}
config("x11") {
libs = [
In addition to that, there's some engine headers that do architecture detection (using preprocessor defines), and RISC-V detection was added to them:
--- a/engine/src/flutter/assets/native_assets.cc
+++ b/engine/src/flutter/assets/native_assets.cc
@@ -17,6 +17,8 @@ namespace flutter {
#define kTargetArchitectureName "ia32"
#elif defined(FML_ARCH_CPU_X86_64)
#define kTargetArchitectureName "x64"
+#elif defined(FML_ARCH_CPU_RISCV64)
+#define kTargetArchitectureName "riscv64"
#else
#error Target architecture detection failed.
#endif
--- a/engine/src/flutter/fml/build_config.h
+++ b/engine/src/flutter/fml/build_config.h
@@ -96,6 +96,11 @@
#elif defined(__pnacl__)
#define FML_ARCH_CPU_32_BITS 1
#define FML_ARCH_CPU_LITTLE_ENDIAN 1
+#elif defined(__riscv) && __riscv_xlen == 64 && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define FML_ARCH_CPU_RISCV_FAMILY 1
+#define FML_ARCH_CPU_RISCV64 1
+#define FML_ARCH_CPU_64_BITS 1
+#define FML_ARCH_CPU_LITTLE_ENDIAN 1
#else
#error Please add support for your architecture in flutter/fml/build_config.h
#endif
--- a/engine/src/flutter/third_party/tonic/common/build_config.h
+++ b/engine/src/flutter/third_party/tonic/common/build_config.h
@@ -103,6 +103,11 @@
#define ARCH_CPU_32_BITS 1
#define ARCH_CPU_LITTLE_ENDIAN 1
#endif
+#elif defined(__riscv) && __riscv_xlen == 64 && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define ARCH_CPU_RISCV_FAMILY 1
+#define ARCH_CPU_RISCV64 1
+#define ARCH_CPU_64_BITS 1
+#define ARCH_CPU_LITTLE_ENDIAN 1
#else
#error Please add support for your architecture in build/build_config.h
#endif
All those patches were then pushed to a custom CI pipeline at https://github.com/ardera/flutter-ci. This CI essentially watches for flutter updates, and builds the engine with the applied compatibility changes, for a lot of different architectures:
gen_snapshot
) for a lot of different host & target combinations, including linux (arm/arm64/x64/riscv64), windows & mac targeting all the different linux architectures.The built artifacts for all the different architectures are then published as a GitHub release.
Now that all the necessary artifacts are present, they can be used to assemble flutter apps. Normally, this is done by the Flutter Tool, which downloads these artifacts from a google storage bucket maintained by the Flutter team. In this case, however, the modifications were made to flutterpi_tool.
In general, there was not much work involved here. Just recognizing riscv64
as a uname
output for architecture detection and adding the correct values to some enumerations:
https://github.com/ardera/flutterpi_tool/commit/443c8efbfdf884701a1e097726b7a6039a0c19d5
KDAB used a Banana Pi BPI-F3, with free compute provided by Cloud-V. This platform helped us to get Flutter running on RISC-V. You can try it yourself and speed up your own software development by getting a free RISC-V compute from Cloud-V.
Contact:
Cloud V <cloud-v@10xengineers.ai>
KDAB <https://www.kdab.com/contact/>
Thanks to Cloud-V (a project by 10xEngineers) for demonstrating how to run flutter on RISC-V hardware.
Leave a Comment
Your Email address will not be published
KDAB is committed to ensuring that your privacy is protected.
For more information about our Privacy Policy, please read our privacy policy