Psellos
Contemporary Development With Functional Programming

Compile OCaml for iOS (iPhone, iPad)

August 24, 2014

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 4.0, based on OCaml 4.01.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 an OCamlXARM package for Mavericks (OS X 10.9) from this link:

The package installs OCamlXARM under /usr/local/ocamlxarm. It was built under Mavericks using Xcode 5.1.1, and builds against the iOS 7.1 SDK by default. You can use the compiler with a different SDK version by specifying the proper -ccopt flags, explained just below under Test.

If you want more control over the installation and the default SDK, you can build OCamlXARM from sources as described at the end of this page under Building from Sources.

To a large extent, OCamlXARM is a modification of the OCaml 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.

The ARM architectures supported by OCamlXARM are dictated by those supported by the OCaml ARM code generator. OCamlXARM 4.0 builds apps for the arm7 architecture, which can run on all iOS devices introduced since September 2009. Support for arm64 should be available in the next major release (OCamlXARM 5.0), as support for 64-bit ARM is being added in OCaml 4.02.0.

The OCamlXARM compiler can be used to produce real-world iOS apps. At Psellos we used 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 strengths of OCaml, and more resources for doing OCaml programming for iOS. For example, I’ve released sources for five example apps (shown in miniature at left) that you can compile and run on an iOS device or in the iOS Simulator.

Previous versions of OCamlXARM, for earlier versions of OS X and Xcode, can be found in the OCaml Programming Archives.

Test

After installing, you might want to compile a tiny test program as follows. 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 -ccopt -Wl,-no_pie -o hello hello.ml
$ file hello
hello: Mach-O executable arm

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

$ $BIN/ocamlopt -ccopt -Wl,-no_pie -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 -ccopt -Wl,-no_pie -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 4.0.2 compiler is built using the iOS 7.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 4.0.2 with the iOS 8.0 SDK, for example:

$ HIDEOUT=/Applications/Xcode.app/Contents/Developer
$ PLT=$HIDEOUT/Platforms/iPhoneOS.platform
$ SDK=/Developer/SDKs/iPhoneOS8.0.sdk
$ OCOPTS="-ccopt -isysroot -ccopt $PLT$SDK -ccopt -Wl,-no_pie"
$ $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

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

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 OCamlXSim note has links to three sample apps that you can run in the Simulator.

Also see our OCaml Programming page, mentioned above.

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

Appendix: Building from Sources

For maximum flexibility, you can build OCamlXARM from sources. This is a little more complicated than a standard OCaml build, but I’ve created a shell script that does it for you.

It’s complicated because the standard OCaml release is not designed to run as a cross compiler. 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. Recent versions of Xcode contain the traditional Unix command-line tools, which you will also need to build OCamlXARM. (In earlier versions of Xcode, the command-line tools were downloaded separately.)

To simplify the command lines below, I’ll define some shell variables as abbreviations for some of the key paths of development tools. If you’re following along at the keyboard, you should define them yourself for later use:

$ HIDEOUT=/Applications/Xcode.app/Contents/Developer
$ TOOLDIR=$HIDEOUT/Toolchains/Xcodedefault.xctoolchain/usr/bin
$ PLT=$HIDEOUT/Platforms/iPhoneOS.platform

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:

$ (cd $TOOLDIR; ls -l as)
-rwxr-xr-x  1 root  wheel  27808 Mar 11 04:37 as

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

In recent years, Apple has switched from the GNU C compiler gcc to a new compiler clang, based on LLVM. If you want to build OCamlXARM from sources, you should have a native C compiler and a C cross compiler for iOS as follows:

$ (cd /usr/bin; ls -l clang)
-rwxr-xr-x  1 root  wheel  14224 Oct 30  2013 clang
$ (cd $TOOLDIR; ls -l clang)
-rwxr-xr-x  1 root  wheel  36874304 May  9 21:09 clang

If these compilers aren’t present, make sure you have installed Xcode as described above, and that your version of Xcode includes the command-line tools.

Create Sources from Patches

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

Download the sources for the OCaml 4.01.0 release from INRIA:

$ curl -O -s http://caml.inria.fr/pub/distrib/ocaml-4.01/ocaml-4.01.0.tar.gz
$ ls -l ocaml-4.01.0.tar.gz
-rw-r--r--  1 jeffsco  staff  4397871 Aug 24 10:18 ocaml-4.01.0.tar.gz

Download patches from Psellos:

$ curl -O -s http://psellos.com/pub/ocamlxarm/ocamlxarm-4.0.2.diff
$ ls -l ocamlxarm-4.0.2.diff
-rw-r--r--  1 jeffsco  staff  55018 Aug 24 10:19 ocamlxarm-4.0.2.diff

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

Unpack the OCaml sources and apply the patches.

$ tar -xf ocaml-4.01.0.tar.gz
$ cd ocaml-4.01.0
$ patch -p0 < ../ocamlxarm-4.0.2.diff
patching file configure
patching file VERSION
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 asmcomp/interf.ml
patching file tools/make-package-macosx
patching file 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 4.0.2 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 4.0.2:

$ svn co svn://svn.psellos.com/tags/ocamlxarm-4.0.2 ocamlxarm-4.0
$ cd ocamlxarm-4.0

These sources are identical to what you get if you apply the 4.0.2 patches to the INRIA 4.01.0 release, as above.

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:

export HIDEOUT=/Applications/Xcode.app/Contents/Developer
export TOOLDIR=$HIDEOUT/Toolchains/XcodeDefault.xctoolchain/usr/bin
export PLT=$HIDEOUT/Platforms/iPhoneOS.platform
export SDK=/Developer/SDKs/iPhoneOS7.1.sdk
export SIMPLT=$HIDEOUT/Platforms/iPhoneSimulator.platform
export SIMSDK=/Developer/SDKs/iPhoneSimulator7.1.sdk
export XARMTARGET=/usr/local/ocamlxarm
export OSXARCH=i386

The values are as follows:

HIDEOUT Secret hideout of Apple developer files (inside the Xcode app).
TOOLDIR Location of developer tools.
PLT Location of iOS platform directory.
SDK Desired iOS SDK version (a subdirectory of PLT—normally, the most recent available).
SIMPLT Location of iOS Simulator platform directory.
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 4.0 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,840 lines of output, ending with something like this:

../../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 make install
Password:
    . . .
  install /usr/local/ocamlxarm/v7/lib/ocaml/ocamlbuild/ocamlbuild.cmo
  install /usr/local/ocamlxarm/v7/man/man1/ocamlbuild.1
$

The installation process produces around 285 lines of output.

Now you’re ready to test the compiler as described above under Test.

Comments

blog comments powered by Disqus