on
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!