狐狸的小小窝

我是小狐狸~欢迎来我的小窝!
Multi-OS Engine 就是炫

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:

SubstrateVM on iOS - compile with default settings
Compile with default settings

Ok that wasn’t too hard. Now let’s try using the LLVM backend by adding -H:CompilerBackend=llvm option to the command:

SubstrateVM on iOS - llvm compile failed
LLVM compile failed with no extra options

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:

SubstrateVM on iOS - LLVM compiled with HomeFinderFeature enabled
LLVM compiled with HomeFinderFeature enabled

Ok. Now let’s try compile it into a library by adding --shared at the end:

SubstrateVM on iOS - compile into shared library
Compile into shared library

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

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据