Psellos
Contemporary Development With Functional Programming

Compile OCaml for iOS (iPhone, iPad)

October 15, 2012

OCamlXARM is a version of OCaml that cross-compiles to iOS devices. It runs on OS X, and builds apps for the iPhone, iPad, and iPod touch. The current version of OCamlXARM is 3.1, based on OCaml 4.00.0.

If you’re not familiar with OCaml, it’s a functional language in the same family as Haskell, Scala, and (especially) Standard ML. It’s powerful, flexible, efficient, and rigorous. I’ve found programming in OCaml for iOS to be productive and even delightful.

You can download a prebuilt OCamlXARM package for Lion (OS X 10.7) or Mountain Lion (OS X 10.8), as follows:

Both of these packages install OCamlXARM under /usr/local/ocamlxarm. The first was built under Lion using Xcode 4.3.3. The second was built under Mountain Lion using Xcode 4.5.1. The only other difference between them is that the 3.1.7 version builds against the iOS 5.1 SDK by default, and the 3.1.8 version builds against iOS 6.0. You can use either compiler with a different SDK version by specifying the proper -ccopt flags, explained below under Test.

If you want more control over the installation and the default SDK, you can build OCamlXARM from sources. Below I describe how to download a set of patches to be applied to the official OCaml 4.00.0 release. Or you can download sources for OCamlXARM from our public Subversion repository.

I’ve also put together a compiler named OCamlXSim that can be used to compile OCaml apps 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 invaluable while developing for actual iOS devices—it provides quicker turnarounds in a self-contained environment (with no need for an extra test device).

The 3.1 OCamlXARM release builds apps for the ARMv7 architecture, used by all iOS devices introduced since September 2009, including all iPads. If you want to build apps for older devices (with ARMv6 architecture), you can use OCamlXARMv6, described in Compile OCaml for Early iOS. Sadly, these devices are essentially obsolete; the latest Xcode has dropped support for them.

The OCamlXARM compiler can be used to produce real-world iOS apps. At Psellos we use OCamlXARM to build our commercial iOS apps, Master Schnapsen/66 and Cassino, and it has been used to build iOS apps by other developers as well. For one example, see SEAiq, a suite of iPad apps for marine navigation (in current use on several of the seven seas).

 

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

OCamlXARM 3.1 is based on OCaml version 4.00.0, with some bug fixes merged from OCaml 4.00.1. To a great extent, OCamlXARM is a modest modification of the new OCaml 4 ARM code generator written by Benedikt Meurer. I modified the compiler to do cross compiling from the Mac to iOS, and modified Meurer’s code generator for the iOS toolchain and ABI.

Early testers of OCamlXARM 3.1 uncovered two problems in the OCaml 4.00.0 base release, which were recently fixed in OCaml 4.00.1. To make OCamlXARM 3.1 as useful as possible, I merged the two fixes from 4.00.1. In fact, I did this before 4.00.1 was released, so you might say that OCamlXARM 3.1 is really based on a prerelease of OCaml 4.00.1. You can read about the two fixes in the OCaml bug tracking system, problem reports 5731 and 5757.

As I said above, OCamlXARM 3.1 was built and tested under OS X 10.7 (Lion) using Xcode 4.3.3 and under OS X 10.8 (Mountain Lion) using Xcode 4.5.1. If you build it yourself, it should work with these or any later versions. If you want to use a prebuilt package with a later version of the iOS SDK, you will need to specify the desired iOS SDK as described below under Test.

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 need to distinguish between generating the compiler itself, which we want to run on the Mac, and generating the runtime, which needs to run on iOS.

The trick I’ve used in OCamlXARM is to build two distinct copies of the OCaml runtime. The first copy is targeted at OS X, and powers the cross compiler itself. The second copy is targeted at iOS, and powers the iOS apps. By lucky chance, there are no parts of the runtime that absolutely need to work in both environments. As a result, the two runtimes can coexist inside a single OCaml release. (This would not be the case if, for example, I wanted to support the bytecode interpreter in iOS apps. For now, I’m sticking with native code apps.)

A second trick, which isn’t strictly necessary, is that I use the iOS Simulator execution environment (though not the actual simulator itself) to create the initial configuration settings for building the iOS runtime. The Simulator embodies a reasonably faithful copy of the properties of iOS (available system calls, sizes of integers, endianness, and so on), so it works as an environment for setting up most of the configuration automatically.

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.”

The OCaml native code compiler (ocamlopt) uses an external assembler to produce its final object files. In the same way, OCamlXARM’s ocamlopt uses the cross-assembler of the iOS SDK to produce iOS object files. After you’ve installed Xcode, you should find an assembler for the iOS platform:

$ PLT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform
$ (cd $PLT/Developer/usr/bin; ls -lL as)
-rwxr-xr-x  1 root  wheel  55632 Jun 12 13:19 as

This is the assembler OCamlXARM will use, so it must be present. If it isn’t, check that you’ve installed Xcode (version 4.2 or later). If you’ve installed it in a non-standard location (other than /Applications), you’ll need to modify paths in the following discussion accordingly.

The simplest way to get OCamlXARM is to download and install one of the previously mentioned prebuilt packages from Psellos. They install OCamlXARM 3.1.7 or 3.1.8 for the ARMv7 architecture in /usr/local/ocamlxarm/v7. 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 and a C cross compiler for iOS at the following paths:

$ (cd /usr/bin; ls -lL gcc)
-rwxr-xr-x  1 root  wheel  117152 Jun 12 13:27 gcc
$ PLT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform
$ (cd $PLT/Developer/usr/bin; ls -lL gcc)
-rwxr-xr-x  1 root  wheel  106064 Jun 12 13:19 gcc

If these compilers aren’t present, make sure you have installed the Command Line Tools as described above.

Create Sources from Patches

To create sources from patches, you need to download the OCaml 4.00.0 release from INRIA and the patches from Psellos and then apply the patches.

Download the sources for the OCaml 4.00.0 release from INRIA:

$ curl -O -s http://caml.inria.fr/pub/distrib/ocaml-4.00/ocaml-4.00.0.tar.gz
$ ls -l ocaml-4.00.0.tar.gz
-rw-r--r-- 1 psellos staff 3394054 Sep 12 15:41 ocaml-4.00.0.tar.gz

Download patches from Psellos:

$ curl -O -s http://psellos.com/pub/ocamlxarm/ocamlxarm-3.1.7.diff
$ ls -l ocamlxarm-3.1.7.diff
-rw-r--r-- 1 psellos staff 62503 Sep 12 15:42 ocamlxarm-3.1.7.diff

You can also download a patch named ocamlxarm-3.1.8.diff. It’s identical to the 3.1.7 patch except for two lines in the build script, which specify the iOS 6.0 SDK rather than iOS 5.1.

To save typing, you can download directly from your browser:

Unpack the OCaml sources and apply the patches.

$ tar -xzf ocaml-4.00.0.tar.gz
$ cd ocaml-4.00.0
$ patch -p0 < ../ocamlxarm-3.1.7.diff
patching file VERSION
patching file asmcomp/schedgen.ml
patching file asmcomp/arm/arch.ml
patching file asmcomp/arm/emit.mlp
patching file asmcomp/arm/proc.ml
patching file asmcomp/arm/selection.ml
patching file tools/make-package-macosx
patching file Makefile
patching file byterun/intern.c
patching file byterun/memory.c
patching file byterun/freelist.c
patching file byterun/freelist.h
patching file byterun/compact.c
patching file byterun/major_gc.c
patching file testsuite/tests/regression/pr5757/pr5757.ml
patching file testsuite/tests/regression/pr5757/pr5757.reference
patching file testsuite/tests/regression/pr5757/Makefile
patching file xarm-build
patching file asmrun/signals_osdep.h
patching file asmrun/arm.S
patching file asmrun/Makefile
$

Check out Sources from Repository

You can also check out the sources for OCamlXARM 3.1.7 or OCamlXARM 3.1.8 from Psellos’s public Subversion repository. This is simpler, but it doesn’t give you the opportunity to examine the patches separately before applying them.

Check out sources for OCamlXARM 3.1.7:

$ svn co svn://svn.psellos.com/tags/ocamlxarm-3.1.7 ocamlxarm-3.1
$ cd ocamlxarm-3.1

These sources are identical to what you get if you apply the 3.1.7 patches to the INRIA 4.00.0 release, as above. You can also check out the 3.1.8 sources; just replace 3.1.7 with 3.1.8.

Build OCamlXARM

Once you have the sources, you’re ready to build OCamlXARM. The file xarm-build is a shell script that does the building. You may want to modify the script before running it. At the beginning of the script are the following lines (these are from the 3.1.7 sources):

export PLT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform
export SDK=/Developer/SDKs/iPhoneOS5.1.sdk
export SIMPLT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform
export SIMSDK=/Developer/SDKs/iPhoneSimulator5.1.sdk
export XARMTARGET=/usr/local/ocamlxarm
export OSXARCH=i386

In the 3.1.8 sources, the two occurrences of 5.1 above are 6.0 instead. In fact (as I mentioned) this is the only difference between the sources of the two OCamlXARM releases.

The values are as follows:

PLT Location of iOS platform directory (inside the Xcode app)
SDK Desired iOS SDK version (a subdirectory of PLT—normally, the most recent available).
SIMPLT Location of iOS Simulator platform directory (inside the Xcode app).
SIMSDK Desired iOS Simulator SDK version (a subdirectory of SIMPLT—should be the same version number as SDK).
XARMTARGET Where OCamlXARM should be installed.
OSXARCH Architecture for OS X executables (i386 or x86_64).

The value of XARMTARGET is a directory where OCamlXARM should be installed. This location is compiled into the OCamlXARM tools; that is, they are aware of their own installation location. This is convenient, but it also means that the tools actually need to be installed in this one specific place. It’s not possible (or at least not convenient at all) to move them somewhere else later on.

OCamlXARM 3.1 will be installed in a subdirectory of XARMTARGET named v7. Future releases will create more subdirectories for other ARM architectures as necessary.

Ordinarily, xarm-build will create 32-bit (i386 architecture) OS X executables. This is simplest, because they work on all Intel Macs. If you want to create 64-bit executables, change the value of OSXARCH to x86_64. In my initial testing, I’ve found that the 64-bit executables make compiles go a little faster—but only around 10%.

To build OCamlXARM all in one step:

$ sh xarm-build all > xarm-build.log 2>&1

If things go well, xarm-build.log will contain around 2,740 lines of output, ending with something like this:

ln -s -f ../unix/unix.mli unix.mli
../../ocamlcomp.sh -I ../unix -warn-error A -c unix.ml
../../boot/ocamlrun ../../tools/ocamlmklib -ocamlc '../../ocamlcomp.sh -I ../unix' -o unix -linkall unix.cmo ../unix/unixLabels.cmo
make: Nothing to be done for `allopt'.

If there is a problem, it may be possible to figure out what went wrong by looking at the error messages in xarm-build.log. It’s also possible to build OCamlXARM in smaller stages—see the script for details.

Now install the cross compiler.

$ sudo -s
Password:
# make install
    . . .
  install /usr/local/ocamlxarm/v7/lib/ocaml/ocamlbuild/ocamlbuild.cmo
  install /usr/local/ocamlxarm/v7/man/man1/ocamlbuild.1
# exit
$

The installation process produces around 280 lines of output.

Test

To verify the installation, compile a test program. For convenience, I start by defining the shell variable BIN, used throughout this section.

$ BIN=/usr/local/ocamlxarm/v7/bin
$ cat > hello.ml
let main () = Printf.printf "Hello, world!\n"

let () = main ()
^D
$ $BIN/ocamlopt -o hello hello.ml
$ file hello
hello: Mach-O executable arm

To test the camlp4 family of processors, try preprocessing with camlp4o.

$ $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:

$ $BIN/ocamlopt -o hello hello.ml
ld: library not found for -lcrt1.o
collect2: ld returned 1 exit status
File "caml_startup", line 1:
Error during linking

This most likely indicates that OCamlXARM was built using an iOS SDK version that is not currently present on your system. This doesn’t necessarily indicate a problem. For example, the prebuilt OCamlXARM 3.1.7 compiler is built using the iOS 5.1 SDK, but you may very well be using a later SDK.

The solution is to specify explicit options to ocamlopt telling it which SDK version to use. To use the pre-built OCamlXARM 3.1.7 with the iOS 6.0 SDK, for example:

$ PLT=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform
$ SDK=/Developer/SDKs/iPhoneOS6.0.sdk
$ OCOPTS="-ccopt -isysroot -ccopt $PLT$SDK"
$ $BIN/ocamlopt $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 ever-evolving iOS SDK. A solution that may be better in some cases is to rebuild OCamlXARM from sources, specifying the new SDK values in xarm-build.

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 apps 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.

Comments

blog comments powered by Disqus