Version 9 (modified by StephenBlackheath, 4 years ago) (diff)


Building a GHC cross-compiler for Apple iOS targets

Status of cross-compilation to iOS is in ticket #7724.

The pull request for changes to Cabal ( has now been integrated into mainline Cabal, but this is not in GHC at the time of writing. Type 'git log' in the libraries/Cabal directory and search for these commits:, and

If they're not there, then do this to work around it:

git remote add lukes
git pull lukes master

Currently we do not build fat binaries (we'd like to fix this), which means you need to choose the right architecture for your device.


1. Read ARM-specific notes

See Cross-compiling GHC at the bottom. In particular, you need to install llvm version 3.0 or >= 3.2.

2. Scripts

Place these scripts somewhere in your path:




TARGET_CFLAGS="-isysroot $TARGET_PLATFORM -march=armv7 -mcpu=cortex-a8 -mfpu=neon"









exec $TARGET_NM "$@"

arm-apple-darwin10-cabal (not needed during the build, but useful afterwards)

exec cabal --with-ghc=arm-apple-darwin10-ghc --with-ghc-pkg=arm-apple-darwin10-ghc-pkg --with-ld=arm-apple-darwin10-ld \
--configure-option=--host=arm-apple-darwin10 --host-arch=arm --host-os=ios \

Edit these scripts to ensure:

  1. The -march option is correct for your device
  1. The platform version matches what you are compiling to in Xcode

3. Check out GHC

Check out as described at Building and Porting GHC, except use the following for your sync-all to omit dph packages, because Template Haskell doesn't work yet, and dph depends on it:

./sync-all --no-dph get
perl boot

4. Create a file

GHC requires you to write a mk/ file, and the following one works. integer-simple must be used, because the default implementation doesn't compile on iOS. Stage1Only is needed for cross-compiling.

SPLIT_OBJS         = NO
INTEGER_LIBRARY    = integer-simple
Stage1Only 	   = YES

5. Configure & build

./configure --target=arm-apple-darwin10 --prefix=/usr/local/ghc-ios/
sudo mkdir -p /usr/local/ghc-ios/
sudo make install

6. Create an Xcode project

Create a new skeleton Xcode project using the wizard, and make sure it runs on your device.

7. Compile your Haskell code

Open a terminal and add /usr/local/ghc-ios/bin to your PATH environment variable.

Here's a skeleton haskell.hs to get you started:

{-# LANGUAGE ForeignFunctionInterface #-}

import Foreign
import Foreign.C

foreign import ccall safe "c_main" c_main :: IO ()

main = do
    putStrLn "Haskell start"

The main() function in main.m must be changed to something like this, because Haskell's main now runs first.

int c_main(void)
    int argc = 1;
    char* argv[2];
    argv[0] = "dummy";
    argv[1] = NULL;		
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));

Now compile it:

arm-apple-darwin10-ghc haskell.hs -threaded

Ignore the copious warnings about "truncation and blank padding" and "has no symbols" (until someone fixes them).

This will create (in this example) haskell.a.

8. Set up your Xcode project for Haskell

Now configure it as follows:

  • Click on the top node in the project tree, then go to the Build Settings tab. Set Dead Code Stripping to No. This is needed because GHC generates "tables next to code", and without this setting, Xcode thinks the tables are dead code and strips them, causing a crash.
  • Click on the top node in the project tree, then go to the Build Phases tab. Click on Link Binary With Libraries to open it then click +. Choose libiconv.dylib then click Add.
  • When you've compiled your Haskell code to a .a (e.g. haskell.a) file, add it to the project anywhere in the hierarchy with Add files to (project) in the right-mouse button menu.

9. Build and run

Run the project again as usual, and Xcode will pick up the haskell.a file and your Haskell code should now run on your iOS device. Anything printed with putStrLn will appear in the Xcode runtime console.

Each time you modify your Haskell code you'll need to re-compile from the command line before re-building in Xcode. It is possible to automate this in Xcode if you wish.

Adjustor pools

A declaration for a "wrapper" callback looks like this:

foreign import ccall safe "wrapper"
        mkDelegate :: IO () -> IO (FunPtr (IO ()))

To implement these, GHC normally generates a small piece of executable code at runtime, called an "adjustor". The purpose of these "wrapper" declarations is to generate a C-callable function pointer that executes Haskell code.

Due to Apple's requirements for code-signing, the iOS kernel enforces a ban on self-modifying code.

We solve this conundrum by pre-compiling a pool of functions, and allocating from it. Because the pool size for each wrapper is fixed, this creates the problem that the pool can run out. If this happens, the application will die with this message:

HaskellDraw: internal error: createPooledAdjustor - adjustor pool 'Main_d1tU' is empty (capacity 32)

(The name of the module where the "wrapper" was declared appears before the underscore character.)

Each "wrapper" declaration has its own pool, whose size defaults to 32. This means that at any one time, there can exist no more than 32 adjustors created by the defined wrapper constructor function (in this example, mkDelegate). Foreign.Ptr.freeHaskellFunPtr is the IO action to free an adjustor, and in this implementation this returns it to the pool.

If the pool is too small for a given application, you can increase it by using a {-# POOLSIZE x #-} pragma, which must appear after the "wrapper" token. e.g.

foreign import ccall safe "wrapper" {-# POOLSIZE 100 #-}
        mkDelegate :: IO () -> IO (FunPtr (IO ()))

Because pool sizes are limited, it should be considered unsafe to call freeHaskellFunPtr in a finalizer, because garbage collection is not predictable.

The POOLSIZE pragma generates a compiler warning only on GHC versions where it isn't supported, so in practice it's portable.

Loose ends

Outstanding issues we should fix in rough priority order.

  • Fat binaries
  • Cross-compiler for the iOS simulator
  • Template Haskell for cross compilers!
  • Packaging with the wrapper scripts and perhaps release of binaries of official ghc releases
  • Would be nice to not have to disable dead-code removal. (Simon Marlow says "we have special hacks so that you don't have to disable dead-code removal on OS X, in the native code generator and (I presume) in the LLVM backend. Perhaps this just needs to be adapted to work on iOS too?")
  • Test cabal more, as there are likely to be some areas where cross-compiling isn't quite right yet
  • Fix the copious link warnings
  • Could we have a global adjustor pool instead of one per "wrapper"?
  • Stop llvm generating an unnecessary 'bx lr' (return) instruction after the GHC calling convention (which is actually a goto)