Cross Compiling OCaml to iOS (iPhone, iPad)
Note: This is an archived version of the OCamlXARM page, for those interested in earlier versions. The most recent version is at Compile OCaml for iOS.
This note describes how to build an OCaml 3.10.2 cross compiler for iOS devices using patches developed by Psellos and others. The resulting compiler runs on a Macintosh, and builds apps for the iPhone, iPad, and iPod touch. I’ll call the compiler OCamlXARM for short.
If you don’t want to build the compiler yourself, you can download a prebuilt package from Psellos. The current version is named ocaml-3.10.2+xarm12.
I’ve also put together a compiler named OCamlXSim that can be used to compile OCaml applications and run them in the iOS Simulator. This is a simpler way to get started, as it doesn’t require an iOS device or registration with Apple. OCamlXSim is described in this accompanying note. In fact the Simulator is useful even when targeting actual iOS devices, as it’s often a quicker development environment.
The cross compiler OCamlXARM is based on OCaml release 3.10.2 and its ARM code generator. Modifications for generating native ARM floating point and for cross compiling from a Unix system came from Toshiyuki Maeda at Tokyo University. Modifications specifically for Mac and iOS were developed by Psellos.
The latest releases of OCaml, 3.12.
x, have an entirely rewritten ARM
code generator. We expect to switch to using this compiler when time
allows. In fact, our modifications to the 3.10.2 code generator adopt
the register conventions of the 3.12 code generator.
Overview
The standard OCaml release is not designed to run as a cross compiler, so it takes some extra work to build it as one. In essence, the OCaml build system doesn’t ordinarily distinguish between generating parts of the compiler itself (which run on the host system), and generating code (including libraries) to run on the target system.
The trick used in OCamlXARM is to make two complete builds from the same compiler sources. The first build is the usual, native OCaml compiler for Mac OS X. The second build is the cross compiler. Any binary part of the cross compiler that needs to execute on the host is replaced with the corresponding part from the first build; i.e., it is replaced with the identically functioning part that was compiled by a native C or OCaml compiler. (This technique is inherited from the patches provided by Toshiyuki Maeda.)
Another complication is that since Mac OS X 10.6, it is possible to
build either 32-bit or 64-bit native executables on Mac systems. The
default is 64 bits on systems that can execute them, and 32 bits
otherwise. To keep things simple, the instructions given here always
generate 32-bit executables by passing the -arch i386
flag to the C
compiler (gcc
) and the assembler (as
) when run directly. These two
changes are part of a small patch for the native compiler.
Please note that a few of the command lines of the following discussion are too long to fit on a single line of a typical browser window. In a lot of cases there is no good place to split them into smaller lines, usually because of a long filename or URL. Take care that you enter them as a single line if you are typing them in by hand.
Preliminaries
To develop and run code on iOS devices, you need an installation of Apple’s Xcode programming tools and you need to register with Apple as an iOS developer. As I write this, the current version of Xcode is 4.0.2. However, if you want to build OCamlXARM yourself, you have to use Xcode 3, the previous version. Xcode 4.0.2 can’t be used to build a 32-bit version of OCaml 3.10.2 due to a linker bug in Xcode. This is a known problem, which you can follow on Mantis, the OCaml bug tracker, here.
If you just want to build and test iOS apps, you can download and install the previously mentioned prebuilt OCamlXARM package from Psellos and use any recent version of Xcode (3 or 4) to build your apps. In that case, you can skip down to the Test section below to verify that your copy of OCamlXARM is installed correctly.
Apple has made it pretty easy to develop for the Mac, which means that there are many ways to get Xcode. You may already have a recent Xcode release on your Mac, or you may be able to install it from a DVD that came with your Mac. If not, you can download Xcode 3 and Xcode 4 from Apple for free if you’re a registered Apple developer. Or you can buy Xcode 4 from the Mac App Store for a small charge. See Apple’s Xcode page for more details.
After installing Xcode you should find a native C compiler at the following path:
/usr/bin/gcc-4.2
You should find a C cross compiler at the following path:
/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.2
To verify that the compilers are present, ask for their versions:
$ /usr/bin/gcc-4.2 --version | head -1
i686-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)
$ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/gcc-4.2 -arch armv6 --version | head -1
arm-apple-darwin10-gcc-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664) (dot 2)
(Note, again, that the second command line given here is quite long. Take care to enter it as a single line if you type it by hand.)
As mentioned above, more recent Xcode releases can’t (yet) be used to build OCaml, so be careful of the version numbers.
Get OCaml Sources and Patches
Choose a place to work (an empty directory would be good).
$ cd <good place to work>
Download the sources for the OCaml 3.10.2 release from INRIA:
$ curl -O -s http://caml.inria.fr/pub/distrib/ocaml-3.10/ocaml-3.10.2.tar.gz
$ ls -l ocaml-3.10.2.tar.gz
-rw-r--r-- 1 psellos staff 2785669 Jul 18 15:41 ocaml-3.10.2.tar.gz
Download patches for the native compiler from Psellos:
$ curl -O -s http://psellos.com/pub/ocamlxarm/native-1.0.12.diff
$ ls -l native-1.0.12.diff
-rw-r--r-- 1 psellos staff 2921 Jul 18 15:42 native-1.0.12.diff
Download patches for the cross compiler from Psellos:
$ curl -O -s http://psellos.com/pub/ocamlxarm/cross-1.0.12.diff
$ ls -l cross-1.0.12.diff
-rw-r--r-- 1 psellos staff 64153 Jul 18 15:53 cross-1.0.12.diff
To save typing, you can access these three links directly from your browser:
Unpack the OCaml sources into two adjacent directories named OCamlBase and OCamlXARM.
$ mkdir OCamlBase
$ cd OCamlBase
$ tar --strip-components=1 -xzf ../ocaml-3.10.2.tar.gz
$ cd ..
$ mkdir OCamlXARM
$ cd OCamlXARM
$ tar --strip-components=1 -xzf ../ocaml-3.10.2.tar.gz
$ cd ..
Build Native Compiler
As noted above, a small patch is needed for building the native compiler. It makes sure that all the executables are 32-bit, and creates two extra Makefiles.
$ cd OCamlBase
$ patch -p0 < ../native-1.0.12.diff
patching file configure
patching file Makefile.base
patching file asmcomp/i386/proc.ml
patching file Makefile
patching file Makefile.xarmtarg
$
The file Makefile.xarmtarg
specifies the directory where OCamlXARM
should be installed, /usr/local/ocamlxarm
by default:
XARMTARGET = /usr/local/ocamlxarm
If you want to install OCamlXARM in a different location, modify this line.
The file Makefile.base
specifies how to build the base compiler. The
patch causes it to be included by the main Makefile
. To build the
base compiler:
$ make base-build
./configure \
-bindir /usr/local/ocamlxarm/bin \
-libdir /usr/local/ocamlxarm/lib/ocaml \
-mandir /usr/local/ocamlxarm/man/man1 \
-no-curses \
-no-tk \
-cc 'gcc-4.2 -arch i386'
Configuring for a i686-apple-darwin10.7.0 ...
. . .
** Objective Caml configuration completed successfully **
make clean
. . .
make world bootstrap opt > world.log 2>&1
Note that -cc 'gcc-4.2 -arch i386'
is specified on the configure
step. This asks for 32-bit binaries to be produced.
Output of the build is saved in a file named world.log
. If it fails,
it might be possible to figure out what went wrong by looking there. If
the build is successful, world.log
will be approximately 3820 lines
long, and will end roughly as follows:
boot/ocamlrun ./ocamlopt -nostdlib -a -I stdlib ocamlbuild/ocamlbuild_pack.cmx ocamlbuild/ocamlbuild_plugin.cmx ocamlbuild/ocamlbuild_executor.cmx ocamlbuild/ocamlbuild_unix_plugin.cmx -o ocamlbuild/ocamlbuildlib.cmxa
boot/ocamlrun ./ocamlopt -nostdlib -a -I stdlib ocamlbuild/ocamlbuild_pack.cmx ocamlbuild/ocamlbuild_plugin.cmx -o ocamlbuild/ocamlbuildlightlib.cmxa
You might want to verify the existence of the four files required for building the cross compiler:
$ ls -l byterun/ocamlrun yacc/ocamlyacc otherlibs/unix/dllunix.so otherlibs/str/dllstr.so
-rwxr-xr-x 1 psellos staff 141056 Jul 18 16:08 byterun/ocamlrun
-rwxr-xr-x 1 psellos staff 13492 Jul 18 16:08 otherlibs/str/dllstr.so
-rwxr-xr-x 1 psellos staff 49128 Jul 18 16:08 otherlibs/unix/dllunix.so
-rwxr-xr-x 1 psellos staff 77352 Jul 18 16:08 yacc/ocamlyacc
Build Cross Compiler
Apply the cross compilation patch to the second copy of the OCaml sources.
$ cd ../OCamlXARM
$ patch -p0 < ../cross-1.0.12.diff
patching file configure
patching file Makefile.xarm
patching file asmcomp/arm/emit.mlp
patching file asmcomp/arm/proc.ml
patching file asmcomp/arm/selection.ml
patching file configure.answers
patching file Makefile
patching file byterun/interp.c
patching file byterun/Makefile
patching file config/auto-aux/runtest
patching file asmrun/arm.S
Makefile.xarm
automates the tedious process of building the cross
compiler while replacing appropriate components with those of the native
compiler.
This file specifies the version of the iOS SDK to use with Apple’s C
cross compiler. For Xcode 3.2.5, the iOS SDK version is 4.2. Make sure
that the specified SDK is available on your system. To change it,
modify the following line of Makefile.xarm
:
XARMSDK = /Developer/SDKs/iPhoneOS4.2.sdk
The specified value is a subdirectory of
/Developer/Platforms/iPhoneOS.platform
. You probably want to specify
the most recent version of the SDK that you find there.
The Makefile.xarm
file includes Makefile.xarmtarg
from OCamlBase, so
the installation directory will be the same for all the compiler
components.
Build the cross compiler.
$ make xarm-build
. . .
make world > world.log.5 2>&1
make opt > opt.log 2>&1
touch xarm-build
There are 4 expected errors in the process, one for each component that
must be copied from the native compiler. This breaks the process into 5
stages, plus a final stage for building the ARM code generator back end.
Output of the process is saved in files named world.log.
N (for N =
1
, 2
, 3
, 4
, 5
) and opt.log
. If things don’t go as expected,
it may be possible to fix things by examining these files.
Verify the existence of the cross compiler:
$ ls -l ocamlopt
-rwxr-xr-x 1 psellos staff 1204757 Jul 18 16:42 ocamlopt
Install the cross compiler.
$ sudo -s
Password:
# make install
. . .
install /usr/local/ocamlxarm/lib/ocaml/ocamlbuild/ocamlbuild.cmo
install /usr/local/ocamlxarm/man/man1/ocamlbuild.1
# exit
$
The installation process produces around 265 lines of output.
Test
To verify the installation, compile a test program.
$ cat > hello.ml
let main () = Printf.printf "Hello, world!\n"
let () = main ()
^D
$ /usr/local/ocamlxarm/bin/ocamlopt -o hello hello.ml
$ file hello
hello: Mach-O executable arm
Now try with camlp4, which was not working in previous OCamlXARM releases.
$ BIN=/usr/local/ocamlxarm/bin
$ $BIN/ocamlopt -pp $BIN/camlp4o -o hello hello.ml
$ file hello
hello: Mach-O executable arm
If the compiler produces ARM executable binaries as shown here, it is almost certainly working correctly.
You might instead see the first test fail as follows:
$ /usr/local/ocamlxarm/bin/ocamlopt -o hello hello.ml
ld: library not found for -lcrt1.o
collect2: ld returned 1 exit status
Error during linking
This most likely indicates that OCamlXARM was built using an iPhoneOS SDK version that is not currently present on your system. This doesn’t necessarily indicate a problem. For example, the prebuilt OCamlXARM compiler from Psellos is built using the iPhoneOS 4.2 SDK, but you may be using the latest Xcode version, which comes with the iPhoneOS 4.3 SDK. Or you may have built OCamlXARM yourself, but later upgraded to a more recent Xcode version.
The solution is to specify explicit options to ocamlopt
telling it
which SDK version to use. To use the pre-built OCamlXARM with the
iPhoneOS 4.3 SDK, for example:
$ PLAT=/Developer/Platforms/iPhoneOS.platform
$ PLATBIN=$PLAT/Developer/usr/bin
$ SDK=$PLAT/Developer/SDKs/iPhoneOS4.3.sdk
$ OCCC="$PLATBIN/gcc-4.2 -arch armv6"
$ OCOPTS="-ccopt -isysroot -ccopt $SDK -cclib -Wl,-syslibroot,$SDK"
$ /usr/local/ocamlxarm/bin/ocamlopt -cc "$OCCC" $OCOPTS -o hello hello.ml
$ file hello
hello: Mach-O executable arm
Although the SDK options are somewhat painful to specify, it’s probably best to think of them as part of using OCamlXARM with Apple’s iOS SDK.
Further Information
Building the OCaml cross compiler is only the first step. The accompanying notes Code an OCaml iPhone App: Which Way Is Up? and Code an OCaml iPhone App: Sliding Tile Puzzle show how to build simple iOS applications and run them on an iOS device.
If you’re interested in running OCaml apps in the iOS Simulator, see the accompanying note Compile OCaml for iOS Simulator. This note has links to two other sample applications that you can try.
If you have any questions, comments, or corrections, feel free to email me at jeffsco@psellos.com.