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
Makefileto 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_X86preprocessor 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
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
$ 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
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
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.