SubstrateVM on iOS
This article records my attempts & findings when using SubstrateVM to run JVM compatible languages on iOS. The goal is to replace the outdated Android Runtime (ART) currently used by MOE with SubstrateVM. Currently it’s still in a very early stage and this article is not meant to be a complete guide on running Java app on iOS, but to share my progress and give anyone who’s interested in this something to start with.
I will keep updating this article once I made any more progress, you could join the discussion by leaving comments down below, or join the Multi-OS Engine Community discord channel: https://discord.gg/m5t3qNXTWj
Motivation
Current version of MOE is based on the ART from Android 6 and support Java 7 features only. With retrolambda we get limited support of Java 8 features like Lambda and default methods. However the lack of a true Java 8 support makes it harder and harder to use as nowadays more and more 3rd libraries moved to Java 8 (and even higher).
It is possible that we update the ART to a later version (such as from Android 7+), however this requires a huge amount of effort because:
- The Android code is not well documented
- The code structure changes significantly between each Android major releases
- ART has its own native code generator, instead of LLVM, so no bitcode support
- ART is not designed to be run on any OS other than Android, and requires a lot of modifications to be able to run on iOS because Apple does things differently than Google
- ART is not fully compatible with any “official” JDK release (Google likes to mix features from different JDK versions)
On the contrary, SubstrateVM is:
- Created & supported by Oracle
- Support iOS (and may other platforms) by design
- Based on official OpenJDK
- Currently support Java 8 and Java 11
- Better stacktrace
This gives a clear advantage to SubstrateVM over ART as a much more reliable and futureproof solution.
SubstrateVM does come with some downsides, namely:
- Not production-ready yet
- Binary size is larger reportedly
- Slower and more memory consumption during compiling
But those issues should be resolved eventually so I’m not that worried about at the moment.
Problem to be Solved
Here are some problems that need to be figured out before we could confidently use this in MOE:
- How to compile Java code into a static shared library / iOS framework / Mach-O file so it can be linked to a normal iOS app (partly solved, see below)
- Figure out the difference to the (modified) ART used in MOE currently, and how this could cause issues when trying porting existing MOE project to this new VM
- How to debug the application once it has been compiled to native binary
- How bitcode could be supported
Acknowledgments
The work I’ve done so far largely based on the awesome work from the Gluon Substrate project: https://github.com/gluonhq/substrate.
Gluon is a framework that allows you to right JavaFX application that can be run on multiple platforms, including iOS (which is based on SubstrateVM). I highly recommend to check out their awesome project!
In the following parts I might include some code fragments from this project and might also uses certain files directly from Gluon’s website for demonstration purpose.
However, this article and any work I did/will do in the future are not directly related to Gluon or any of their product.
Preparation
Most of the work I’ve done are under MacOS, so all the tools I mention later are all MacOS versions. I’ll assume most of you have access to a Mac and are familiar with some basic dev tools & processes on Mac, so I won’t explain everything in this article. Again, this is not a complete guide.
Download the official prebuilt GraalVM from https://github.com/graalvm/graalvm-ce-builds/releases/tag/vm-21.0.0.2. This is the version I’m using while writing this part. Download the graalvm-ce-java11-darwin-amd64-21.0.0.2.tar.gz and extract it somewhere.
This is a complete JDK distribution, so I simply added it to the jenv for easier version switching. Once the GraalVM is activated as current JDK, run the following command to install the SubstrateVM (i.e. native-image):
gu install --jvm native-image gu install --jvm llvm-toolchain
Hello World
I created a very simple hello world in Java:
public class Test { public static void main(String[] args) { System.out.println("hello world!"); } }
Then compile it to class file:
javac Test.java
Java Bytecode to Binary
The document of SubstrateVM states by using the native-image
command, one can compile the java bytecode (the .class file) into either an executable (the default option) or a shared (dynamic) library. However none of these options fits our needs because:
- We need to link the generated binary into a normal iOS application, so it has to be a library, not a standalone executable
- iOS does not allow loading our own dynamic library at runtime, so the library needs to be statically linked to our app
Before I even worry about this problem, as a proof of concept, I first tried to compile it into a x86_64 Darwin executable and ran it using native-image Test
:
Ok that wasn’t too hard. Now let’s try using the LLVM backend by adding -H:CompilerBackend=llvm
option to the command:
Oops! A quick search of ImageSingletons do not contain key org.graalvm.home.HomeFinder
lead me to this github issue, which tells me to add --features=org.graalvm.home.HomeFinderFeature
to the command:
Ok. Now let’s try compile it into a library by adding --shared
at the end:
As you can see, a dynamic library has been generated (“test.dylib”), with few C header files. By looking at the content of the “test.h”, I found a function int run_main(int argc, char** argv);
which is pretty clear to me that I need to call this function in my iOS app to execute the Java main function, similar to the moevm(argc, argv);
we called from the main function in MOE.
Then what’s next is pretty clear: I need to somehow make that dynamic library into a static library so we could link it in iOS project.
Or Maybe an Object File Instead…?
TBC