Psellos
Life So Short, the Craft So Long to Learn

Compile OCaml for iOS (iPhone, iPad)

April 17, 2012

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. The current version of OCamlXARM is 1.0.15.

If you don’t want to build the compiler yourself, you can download a prebuilt package from Psellos.

The OCamlXARM compiler can be used to produce real-world iOS apps. We used it to create our released iOS apps, Master Schnapsen/66 and Cassino.

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 Compile Ocaml for iOS Simulator. In fact the Simulator is useful while developing for actual iOS devices, as it provides quicker turnarounds in a self-contained environment (with no need for an extra test device).

 

Our OCaml Programming page has many more resources for doing OCaml programming for iOS. For example, I have released sources for five full example applications (shown in miniature at left) that you can compile and run on an iOS device or in the iOS Simulator.

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.

This version of OCamlXARM runs under OS X 10.7 (Lion) and is based on the toolchain of Xcode 4.3.2. Previous versions of OCamlXARM, for earlier versions of OS X and Xcode, can be found in the OCaml Programming Archives.

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. The Xcode tools are free, but there is a yearly fee to register as an iOS developer. If you want to get started without paying the fee, you can develop for the iOS Simulator using OCamlXSim, described in Compile Ocaml for iOS Simulator. To register as an iOS developer, see Apple’s iOS Developer Program page.

You can download Xcode (for free) from the Mac App Store. See Apple’s Xcode page for more details. After installing Xcode, go to the Downloads page of its Preferences, and also download the “Command Line Tools.”

In addition to Xcode, you also need an OCaml compiler, which is the subject of this note. 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 a recent version of Xcode (4.2 or later) 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. The intervening sections describe how to build the OCamlXARM compiler yourself.

If you want to build OCamlXARM from sources, you should have a native C compiler at the following path:

/usr/bin/llvm-gcc-4.2

You should find a C cross compiler at the following path:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/llvm-gcc-4.2

To verify that the compilers are present, ask for their versions:

$ /usr/bin/llvm-gcc-4.2 --version | head -1
i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.9.00)
$ /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/llvm-gcc-4.2 -arch armv6 --version | head -1
arm-apple-darwin10-llvm-gcc-4.2 (GCC) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2377.00)

(Note, again, that the second command line given here is very long. Take care to enter it as a single line if you type it by hand.)

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 Apr 17 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.15.diff
$ ls -l native-1.0.15.diff
-rw-r--r-- 1 psellos staff 2931 Apr 17 15:42 native-1.0.15.diff

Download patches for the cross compiler from Psellos:

$ curl -O -s http://psellos.com/pub/ocamlxarm/cross-1.0.15.diff
$ ls -l cross-1.0.15.diff
-rw-r--r-- 1 psellos staff 67729 Apr 17 15:53 cross-1.0.15.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.15.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 'llvm-gcc-4.2 -arch i386'
Configuring for a i686-apple-darwin11.3.0 ...
    . . .
** Objective Caml configuration completed successfully **

make clean
    . . .
make world bootstrap opt > world.log 2>&1

Note that -cc 'llvm-gcc-4.2 -arch i386' is specified in 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  206380 Apr 17 16:08 byterun/ocamlrun
-rwxr-xr-x  1 psellos  staff   13460 Apr 17 16:08 otherlibs/str/dllstr.so
-rwxr-xr-x  1 psellos  staff   48736 Apr 17 16:08 otherlibs/unix/dllunix.so
-rwxr-xr-x  1 psellos  staff   81804 Apr 17 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.15.diff
patching file configure
patching file Makefile.xarm
patching file VERSION
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 also specifies the version of the iOS SDK to use with Apple’s C cross compiler. For Xcode 4.3.2, the iOS SDK version is 5.1. Make sure that the specified SDK is available on your system. To change it, modify the following line of Makefile.xarm:

XARMSDK = /Developer/SDKs/iPhoneOS5.1.sdk

The specified value is a subdirectory of /Applications/Xcode.app/Contents/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  1205206 Apr 17 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.

$ 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 5.1 SDK, but you may be using a later Xcode version, which comes with a later iPhoneOS SDK. Or you may have built OCamlXARM yourself, and 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 future iPhoneOS 5.2 SDK, for example:

$ PLAT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform
$ PLATBIN=$PLAT/Developer/usr/bin
$ SDK=$PLAT/Developer/SDKs/iPhoneOS5.2.sdk
$ OCCC="$PLATBIN/llvm-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 Portland: Which Way Is Up on iOS? and Slide24: Sliding Tile Puzzle for iOS show how to build simple iOS apps 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 three other sample applications that you can try.

See the OCaml Programming page for a full list of our OCaml resources.

If you have any questions, comments, or corrections, feel free to leave them below, or email me at jeffsco@psellos.com.