Skip to content

Instantly share code, notes, and snippets.

@jrudolph
Last active January 22, 2022 12:24
Show Gist options
  • Save jrudolph/f51fb94a7084601777b3161bd8b819ba to your computer and use it in GitHub Desktop.
Save jrudolph/f51fb94a7084601777b3161bd8b819ba to your computer and use it in GitHub Desktop.
Scala Native for Raspberry Pi 1

Here's a quick report that I used this branch to compile a binary for Raspberry Pi 1 using https://github.com/scala-native/scala-native/commit/18114562b28e9301c944f7e142ac92d7edd1058c from scala-native/scala-native#1571.

In a quest to update my daughter's music box based on a Raspberry Pi 1, I tried to use scala-native instead of a regular OpenJDK for running the custom software.

Ultimately, I got something working but it was quite a rocky road (and I'm not sure if I would recommend it instead of using Zulu OpenJDK). As mentioned before somewhere in the PR I had to change NativeConfig to default to a 32-bit architecture.

I'm using this config on the project:

nativeCompileOptions ++= Seq("-target", "arm-linux-gnueabihf", "-fno-builtin", "-fno-exceptions", "-march=armv6", "-v")
nativeLinkingOptions ++= Seq("-target", "arm-linux-gnueabihf", "-v", "-fno-builtin", "-march=armv6")
nativeGC := "boehm"

I didn't manage to get cross-compilation working (i.e. I finally got a binary which seems to be hanging). So, what I did was using a raspbian image, qemu-user-static and qemu-system-arm, and a chroot environment (systemd-nspawn), to create an emulated armhf environment on my computer to run the build. Alternatively, you can probably run the build on a Raspberry Pi 1 itself, but it's probably even slower. I use zulu11.52.13-ca-jdk11.0.13-linux_aarch32hf as a openjdk to run sbt in the chroot environment.

Running nativeLink in that environment almost works, it fails during linking, it seems because clang does not come with builtins for the armhf architecture. The solution for this problem is to run with the -v option which spits out the whole linking command before it fails. In the command, you then have to replace the -lm /usr/lib/llvm-7/lib/clang/7.0.1/lib/linux/libclang_rt.builtins-armhf.a -lc /usr/lib/llvm-7/lib/clang/7.0.1/lib/linux/libclang_rt.builtins-armhf.a at the end with -lc -lm -lgcc, i.e. using libgcc for the built-ins. Running that command in the chroot environment will produce a binary that runs on the RPi1.

Some observations:

  • I had to remove Unwind_AppleExtras.cpp does not compile on armhf but can just be removed from the target folder.
  • Only boehm GC works, immix, commix, and even none segfault in different stages of running the program. This was somewhat hard to debug because immix fails not immediately but only later with a jump to NULL which is hard to debug.
  • Build times are excruciating slow, a simple Scala application takes at least 1 minute to build in the emulated chroot.
  • The speedup is much less than expected. The only reason to try scala-native at all is that I didn't find any OpenJDK that supports hotspot for aarch32. The best one I found is the above zulu version of the OpenJDK which comes with the C1 compiler. This works flawlessly and without surprises but is somewhat slow. My app parses a few files at startup. This takes ~ 16s with OpenJDK, the native binary still takes 8s. The overall roundtrip-time for developing is (currently?) much worse with scala-native than with OpenJDK, because with OpenJDK I can compile and copy the jar file in a few seconds and then have to wait 16s to see if it works while it takes more than a minute with the native compilation pipeline.
  • In my case, missing thread and process spawn support in scala-native means I have to rewrite parts of the program.
  • Calling out to C-libraries is basically as simple with OpenJDK/JNA as with scala-native. It's probably faster with scala-native but that doesn't matter in practice. Since my program already has native parts, it's easy enough to avoid too frequent calls into native code.

Cross-compilation would help a lot with compilation times. I just added --sysroot commands to the above compile and link commands to make clang aware of the raspbian environment.

Clang itself cross-compiles flawlessly it seems, however linking was a big issue. It seems by default clang wants to use the host linker (ld) which cannot link for the target. I'm by far no expert in clang but I found a post explaining how clang's ld.lld can be used to cross-link. That produced some problems because boehm requires threads and for some reason the linker chose to link pthread and dl statically which does not work. I replaced -lpthread and -ldl in the linker command line by absolute paths to the libraries in the chroot environment which finally produced a binary. Unfortunately, the binary is broken and seems to hang without doing anything.

I guess a mixed mode might be possible doing cross-compiling on the host machine and only doing the linking step in the target environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment