on
A Haskell Cross Compiler for iOS
So far we have built a Haskell Cross Compiler for Raspberry Pi, as well as a Haskell Cross Compiler for Android. To round this off, we will build a cross compiler for iOS as well.
With the WWDC signaling the end of 32bit devices and the last 32bit
devices are the iPad (4th gen) and iPhone 5/iPhone 5C, we will only
build the 64bit cross compiler. Note that what Apple calls arm64
is
called aarch64
elsewhere. This is rather unfortunate.
The SDK & LLVM
Apple ships the iOS SDK with Xcode. Hence a recent copy of Xcode from
the AppStore is required. As Apple does not ship the opt
and llc
with Xcode, and GHC currently requires opt
and llc
from llvm4, we
need to obtain a copy from the LLVMs
release download website as
well.
Toolchain Wrapping
Apple provides the xcrun
utility with automatically sets up the
toolchain for the tools we need. We however will still need to provide
the target prefixed aliases for better autotools interop. Credit for the
initial work on setting this up goes to the
ghc-ios-scripts, we’ll
use a slightly modified version:
#!/bin/bash
name=${0##*/}
cmd=${name##*-}
target=${name%-*}
case $name in
*-cabal)
fcommon="--builddir=dist/${target}"
fcompile=" --with-ghc=${target}-ghc"
fcompile+=" --with-ghc-pkg=${target}-ghc-pkg"
fcompile+=" --with-gcc=${target}-clang"
fcompile+=" --with-ld=${target}-ld"
fcompile+=" --hsc2hs-options=--cross-compile"
fconfig="--disable-shared --configure-option=--host=${target}"
case $1 in
configure|install) flags="${fcommon} ${fcompile} ${fconfig}" ;;
build) flags="${fcommon} ${fcompile}" ;;
list|info|update) flags="" ;;
"") flags="" ;;
*) flags=$fcommon ;;
esac
;;
aarch64-apple-ios-clang|aarch64-apple-ios-ld)
flags="--sdk iphoneos ${cmd} -arch arm64"
cmd="xcrun"
;;
aarch64-apple-ios-*|aarch64-apple-ios-*)
flags="--sdk iphoneos ${cmd}"
cmd="xcrun"
;;
x86_64-apple-ios-clang|x86_64-apple-ios-ld)
flags="--sdk iphonesimulator ${cmd} -arch x86_64"
cmd="xcrun"
;;
x86_64-apple-ios-*)
flags="--sdk iphonesimulator ${cmd}"
cmd="xcrun"
;;
# default
*-nm|*-ar|*-ranlib) ;;
*) echo "Unknown command: ${0##*/}" >&2; exit 1;;
esac
exec $cmd $flags "$@"
The universal wrapper
can be obtained from the
zw3rk/toolchain-wrapper
repository. It contains not only the iOS part above, but also the
sections for Raspberry Pi and Android and can thus be used for all three
platforms.
Again we need to create the target prefixed aliases:
for target in "aarch64-apple-ios x86_64-apple-ios"; do
for command in "clang ld ld.gold nm ar ranlib cabal"; do
ln -s wrapper $target-$command
done
done
The bootstrap script from the zw3rk/toolchain-wrapper repository will generate all aliases for Android, iOS and Raspberry Pi.
Prerequisites
With the toolchain wrapper and aliases in PATH
all we need to build
GHC is ghc
and cabal
. Using homebrew getting a
recent enough copy of ghc
and llvm-3.7
should be as easy as
brew install ghc [email protected]
[email protected]
will install opt-3.7
and llc-3.7
, which we need for the
bootstrap compiler. However homebrew installs ghc
without the version
suffixes for llc
and opt
. This can simply be fixed by replacing
("LLVM llc command", "llc"),
("LLVM opt command", "opt")
with
("LLVM llc command", "llc-3.7"),
("LLVM opt command", "opt-3.7")
in the settings
file, which in the case of ghc
from homebrew is
located at
/usr/local/opt/ghc/lib/ghc-8.0.2/settings
We also need alex
and happy
, which can be installed with cabal
cabal install alex happy
As we did for Raspberry Pi and Android, we also need libffi for iOS
git clone https://github.com/zw3rk/libffi.git
cd libffi
./autogen.sh
CC="aarch64-apple-ios-clang" \
CXX="aarch64-apple-ios-clang" \
./configure \
--prefix=/path/to/libffi/aarch64-apple-ios \
--host=aarch64-apple-ios \
--enable-static=yes --enable-shared=yes
make && make install
git clean -f -x -d
./autogen.sh
CC="x86_64-apple-ios-clang" \
CXX="x86_64-apple-ios-clang" \
./configure \
--prefix=/path/to/libffi/x86_64-apple-ios \
--host=x86_64-apple-ios \
--enable-static=yes --enable-shared=yes
make && make install
Note: we need to use the
zw3rk/libffi fork for -ios
support until the
libffi/libffi#307 /pull
request has been merged into the official libffi repository. Or a new
autoconf release (latest 2.69 is from 2012) is cut and widely
available./
Building GHC
With opt
and llc
from llvm4,=alex=, and happy
in PATH
export PATH=$HOME/.cabal/bin:$PATH
export PATH=/path/to/llvm4/bin:$PATH
export PATH=/path/to/wrapped-toolchain:$PATH
building GHC from the patched my-ghc
branch
git clone --recursive git://git.haskell.org/ghc.git
cd ghc
git remote add zw3rk https://github.com/zw3rk/ghc.git
git fetch zw3rk
git checkout zw3rk/my-ghc -b my-ghc
git submodule update --init --recursive
should require nothing more than
# set paths
export PREFIX=/my/prefix
export LIBFFI=/path/to/libffi
for target in "aarch64-apple-ios x86_64-apple-ios"; do
# Clean up the build tree
git clean -x -f -d
# Boot up the build system
./boot
# Configure a GHC that targets $target
./configure --target=$target \
--prefix=$PREFIX \
--disable-large-address-space \
--with-system-libffi \
--with-ffi-includes=$LIBFFI/$target/include \
--with-ffi-libraries=$LIBFFI/$target/lib
# Create a mk/build.mk and set the BuildFlavour to quick-cross
sed -E "s/^#(BuildFlavour[ ]+= quick-cross)$/\1/" \
mk/build.mk.sample > mk/build.mk
# Compile and install ghc
make -j && make install
done
and shiny new aarch64-apple-ios-ghc
and x86_64-apple-ios-ghc
should
appear in $PREFIX/bin
after 60–120 minutes, depending on your
hardware.
Compiling Hello World
Similar to how we built the Hello World library for Android, we are going to build the same Hello World library and wrap it into an iOS application.
The Lib.hs
lirbary with the following code
module Lib where
import Foreign.C (CString, newCString)
-- | export haskell function @chello@ as @hello@.
foreign export ccall "hello" chello :: IO CString
-- | Tiny wrapper to return a CString
chello = newCString hello
-- | Pristine haskell function.
hello = "Hello from Haskell"
exports the chello
C function, which we will be calling from the iOS
application to obtain a C string from Haskell. Nothing too exciting yet,
but it will demonstrate the basic interop.
Creating a simple Single View Application for iOS using swift with
Xcode should provide the neccessary Application Template for our Hello
World application. Using Objective-C would make calling chello
/a
bit easier. However as Apple has been pushing swift for a while now,
we’ll use swift./
We will need a bridging header to bring the C prototypes into swift. The
simplest way to do this is to add a new objective c file to the project,
e.g. tmp.m
, which in turn will cause Xcode to ask if it should create
a bridging header. Answer yes and delete tmp.h
. Add the prototypes we
need to the helloworld-Bridging-Header.h
extern void hs_init(int * argc, char ** argv[]);
extern char * hello();
In the AppDelegate.swift
, we will call hs_init
when the application
did finish launching:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
hs_init(nil,nil)
return true
}
Adding a Label
to the Main.storyboard
, connecting the IBOutlet
it
to the ViewController.swift
, and setting it’s text
property to the
result of hello
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
label.text = String(cString: hello())
}
}
should be sufficient. Before we can build and run the application on an actual device, we need to build the Haskell library and tell Xcode to link against it.
Compiling into a static libraries for aarch64
(device) & x86_64
(simulator)
aarch64-apple-ios-ghc -odir arm64 -hidir arm64 \
-lffi -L/path/to/libffi/aarch64-apple-ios/lib \
-staticlib -o hs-libs/arm64/libhs.a hs/Lib.hs
x86_64-apple-ios-ghc -odir x86_64 -hidir x86_64 \
-lffi -L/path/to/libffi/x86_64-apple-ios/lib \
-staticlib -o hs-libs/x86_64/libhs.a hs/Lib.hs
and turning them into a universal library with both architectures combined
lipo -create -output hs-libs/libhs.a \
hs-libs/arm64/libhs.a hs-libs/x86_64/libhs.a
should provide the libhs.a
static library in the hs-libs
folder.
Linking it in Xcode requires to add it, together with libiconv.tbd, to
the Link Binary With Libraries section in the Build Phases tab of
the helloworld project in Xcode. As we can not build bitcode only
libraries with GHC, we also need to set Enable Bitcode in the Build
Settings tab to No.
Finally running the application on the device should present us with