Psellos
Life So Short, the Craft So Long to Learn

Cross Compiling OCaml to iOS (iPhone, iPad)

July 19, 2011

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.