2015-12-17
Cross compile Pony programs for Android
Support for compiling Pony programs on ARM and 32-bit x86 landed recently. This allows compiling and running Pony on Raspberry Pi and other ARM devices. I was curious if it would be possible to compile Pony programs to run on Android and this post outlines how I got a "Hello World" example working.
The Pony compiler, ponyc, does not currently support cross compilation. It uses the C pre-processor to generate code for the platform it is running on. This has hardcoded assumptions for byte size (32 vs 64 bit) and processor support (ARM vs x86). Note that this is a proof of concept and hacks the compiler and runtime source to get things working. From this I hope to learn more elegant ways of supporting cross compiling.
Ponyc Android Cross Compiler
The first step was to build a version of ponyc
that would generate Android compatible ARM code without any host platform specific code being included. I built ponyc
for 32-bit x86, modified to not include any x86 specific code generation and to allow selecting the non-native LLVM backends. This ensures that the hard coded assumptions for the 32-bit size matches my target, 32-bit Android.
The changes for this are in the android_ponyc_cross branch of my github repository. The changes were:
- Modify the
Makefile
to use 32-bit flags to the compiler. - Add some LLVM initialization to allow selection of non-native backends so ARM can be generated on x86 hosts.
- Comment out some
PLATFORM_IS_X86
preprocessor statements to prevent x86 specific code being generated.
For a real cross compiler these would be changed to be runtime selectable in some way. For the proof of concept it suffices to create a hardcoded compiler specific for this example.
For me it was necessary to build a version of LLVM for 32-bit as I'm on a 64-bit host. This was done by doing the following:
$ sudo apt-get install libicu-dev:i386 libncurses5-dev:i386 libxml2-dev:i386
$ tar xvf /tmp/llvm-3.6.2.src.tar.xz
$ cd llvm-3.6.2.src/tools
$ tar xvf /tmp/cfe-3.6.2.src.tar.xz
$ mv cfe-3.6.2.src clang
$ cd ..
$ mkdir build
$ cd build
$ cmake -DLLVM_BUILD_32_BITS=ON -DCMAKE_INSTALL_PREFIX=/tmp/llvm ..
$ make
$ make install
Building the modified ponyc
used these steps:
$ git clone -b android_ponyc_cross https://github.com/doublec/ponyc android_ponyc_cross
$ cd android_ponyc_cross
$ LLVM_CONFIG=/tmp/llvm/bin/llvm-config CXX="g++ -m32" make config=release ponyc
This generates a ponyc
executable in the build/release
directory.
Android compatible Pony runtime
An Android compatible libponyrt
library is needed to link. The changes to build this are on the android_libponyrt branch. This must be built using an Android NDK standalone toolkit. A compatible standalone toolkit can be created with the following run from the NDK root directory:
$ ./build/tools/make-standalone-toolchain.sh --arch=arm \
--platform=android-21 --install-dir=/tmp/stk21
Add the resulting installation directory to the PATH
:
$ export PATH=/tmp/stk21/bin:$PATH
Make sure a recent ponyc
compiler is on the PATH
. This should not be the cross compiler built previously but a ponyc
compiler for the host platform. Build the runtime library with:
$ git clone -b android_libponyrt https://github.com/doublec/ponyc android_libponyrt
$ cd android_libponyrt
$ CC=arm-linux-androideabi-gcc make config=release libponyrt
The resulting library is in the directory build/release/libponyrt.a
.
Compiling to Android
With the above tasks complete it's now possible to build an Android compatible binary. I tested with a "Hello World" example:
actor Main
new create(env:Env) =>
env.out.print("Hello Android")
With that in a main.pony
file in a hello
directory, build with:
$ /tmp/android_ponyc_cross/build/release/ponyc --triple arm-linux-androideabi -rir hello
...ignore warnings about core-avx2 feature here...
$ llvm-as hello.ll
$ llc -mtriple=arm-linux-androideabi hello.bc -o hello.o -filetype=obj
$ arm-linux-androideabi-gcc -fPIE -pie hello.o -o hello1 -L/tmp/android_libponyrt/build/release/ -lponyrt
The first command instructs our cross compiler to generate code for Android and to only produce the LLVM assembly listing. This is compiled to bytecode with llvm-as
and then to an object file with llc
. The Android version of gcc
is used to link with the Android version of libponyrt
that was created earlier. The hello
binary that is produced can be copied to an Android device (I used a Note 3 running Lollipop) and run:
$ adb push hello1 /data/local/tmp/
$ adb shell
$ cd /data/local/tmp
$ ./hello1
Hello Android
Conclusion
I haven't tested any other features running on Android yet but this is a promising start. Using the Pony FFI and JNI it is hopefluly possible to write native Android applications.
The steps I used for getting ponyc
to generate LLVM instructions and using the LLVM tools to compile it were obtained from a gist by Darach Ennis. Similar steps would probably work on other LLVM compatible platforms.