Cross Compiling Template Haskell

As laid out yesterday, there are some interesting questions pertaining to Template Haskell and Cross Compilation. Today we will put all the pieces together and cross compile template haskell to our Raspberry Pi with file and process IO!

The External Interpreter

GHCs External Interpreter splits GHC into two components the haskell compiler ghc and the interpreter server iserv. Passing -fexternal-interpreter to ghc will spawn an iserv instance and run all interpreted code through it. ghc can instruct iserv to load and link libraries as needed, and evaluate bytecode objects. iserv in turn can query ghc for the current compilation environment during the evaluation.

Splitting iserv

For the cross compilation scenario, we need a to split iserv into two parts, an iserv-proxy which serves as the iserv interface to GHC on the build machine. And an GHCSlave on the host machine. This means we need to move from

.---------------.
| GHC <-> iserv |
'---------------'
  build machine

to a setup that looks more like

    build machine            host machine
.-----------------------.   .------------.
| GHC <-> iserv-proxy <-+---+-> GHCSlave |
'-----------------------' ^ '------------'
       ^                  |
       |                  '-- communication via sockets
       '-- communication via pipes

Note: I will not go into the technical details of the iserv-proxy and iserv library to be used in the GHCSlave in this post.

Building GHC

In A Haskell Cross Compiler for Raspberry Pi we have seen how to build a cross compiling GHC. As this is still work in progress and not all diffs have been merged, or are updated and rebased onto GHCs master branch on a regular basis, you will need a very recent copy (as recent as today!).

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 -b zw3rk/my-ghc
git reset --hard zw3rk/my-ghc
git submodule update --init --recursive

Following the build instructions in A Haskell Cross Compiler for Raspberry Pi should yield an up to date arm-linux-gnueabihf-ghc.

Note: The hard reset is needed, as zw3rk/my-ghc is periodically force pushed. Please let me know of a better solution!

Building iserv

To build iserv-proxy we will use ghc (the GHC that builds on and for the build machine), as the proxy needs to be able to run on the build machine. The iserv-bin package, which contains the iserv-proxy is part of the ghc tree, and can be found in the iserv subfolder. We can simply build and install it by saying

ghc/iserv $ cabal install -flibrary -fproxy

This should build and install $HOME/.cabal/bin/iserv-proxy.

Building GHCSlave

Next we need the GHCSlave to run on the Raspberry Pi. For this we need the iserv library, to link into GHCSlave. To do this we need the arm-linux-gnueabihf-cabal we created in The Haskell Cabal and Cross Compilation.

ghc/iserv $ arm-linux-genueabihf-cabal install -flibrary

Next, we will build RPi/Slave.hs from the ghc-slave repository:

git clone https://github.com/zw3rk/ghc-slave.git
cd ghc-slave/RPi
arm-linux-gnueabihf-ghc Slave.hs

This should provide the Slave executable for the Raspberry Pi. Because the Slave binary links against the libffi shared object we built when building the arm-linux-gnueabihf-ghc, we need that on the Raspberry Pi as well:

scp path/to/libffi/arm-linux-gnueabihf/lib/libffi.so.7 \
    pi@raspberrypi:
scp Slave pi@raspberrypi:

On the Raspberry Pi, we will need a temporary folder $HOME/slave, where GHCSlave store libraries and objects needed for the evaluation of splices.

pi@raspberrypi:~ $ mkdir $HOME/slave

With all this properly set up we can start the GHCSlave on the Raspberry Pi

pi@raspberrypi:~ $ LD_LIBRARY_PATH=$HOME ./Slave $HOME/slave 5000

Compiling Template Haskell via GHCSlave

On to the exciting part! As we now have iserv-proxy on the build machine, and GHCSlave on the host, we finally have a fully functioning cross compiler with Template Haskell support!

Let us use David Luposchainsky poor man’s supercompiler as an example.

-- File: TH.hs
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE BangPatterns #-}
module TH where
import Language.Haskell.TH.Syntax
-- Calculate the n-th Fibonacci number's last 10 digits in O(n).
fibo :: Integer -> Integer
fibo = (`rem` 10^10) . fibo' (0, 1)
      where fibo' (a,_) 0 = a
            fibo' (!a,!b) n = fibo' (b, a + b) (n - 1)

-- Wraps an Integer in(to) a Q Exp
wrapTH :: Integer -> Q Exp
wrapTH n = [| n |]

-- | Wrap a certain Fibonacci number in an expression
fiboTH :: Integer -> Q Exp
fiboTH n = wrapTH (fibo n)

and the Main module:

-- File: Main.hs

{-# LANGUAGE TemplateHaskell #-}

import TH

myFibo :: Integer
myFibo = $( fiboTH (10^5) )

main = print myFibo

Compiling this should be as simple as

arm-linux-gnueabihf-ghc -fexternal-interpreter \
    -pgmi $HOME/.cabal/bin/iserv-proxy \
    -opti10.0.1.1 -opti5000 \
    TH.hs Main.hs

assuming your Raspberry Pis IP is 10.0.1.1.

Compiling a Cabal Package that required Template Haskell

As we have seen yesterday, the file-embed package as well as the gitrev package use Template Haskell (and the file system and git process). The initial patch to enable file system access and process IO tried to do this transparently by hooking into GHCs Runtime System. This proved to be rather inelegant. A revised patch extends the Quasi class. The drawback is that packages need to be adapted to the new Quasi class and be more explicit about the file system and process IO to work with cross compilation.

With some minor alterations to the file-embed and gitrev package, we can make them work with the new features, let’s install them.

git clone https://github.com/zw3rk/file-embed.git
(cd file-embed && arm-linux-gnueabihf-cabal install)
git clone https://github.com/zw3rk/gitrev.git
(cd gitrev && arm-linux-gnueabihf-cabal install)

So far so good. Now, let us try the contrived example from yesterday

git clone https://github.com/zw3rk/medium.git
cd medium/template-haskell-and-cross-compilation
# configure the th-example package without shared
arm-linux-gnueabihf-cabal configure --disable-shared
# build it with cabal
arm-linux-gnueabihf-cabal build \
   --ghc-options="-fexternal-interpreter \
                  -pgmi $HOME/.cabal/bin/iserv-proxy \
                  -opti10.0.1.1 -opti5000"

Note: you will need to have the the Slave process running on your Raspberry Pi at IP =10.0.1.1=/./

This will produce dist/arm-linux-gnueabihf/build/th-example/th-example. Copying it over to the Raspberry Pi and executing it should just work:

scp dist/arm-linux-gnueabihf/build/th-example/th-example \
    pi@raspberrypi:
ssh pi@raspberrypi 'LD_LIBRARY_PATH=. ./th-example --version'

And yield Version 32b8bbb75cf8910443397bfab6b194c8e6d36d47.

Current Limitations

As we saw, packages that use file or process IO, need to be adapted to use the new more explicit API to work. It should also be noted that all this is quite new code, and not all differentials have landed in master yet. I am quite certain there will be bugs, however I’d like to encourage you to try this out and provide feedback, PRs and bugreports!