Dynamic loading of Haskell modules
Even though I don’t have any particular compelling use case for dynamic loading of Haskell modules, it is something that I’ve been wanting to do for quite some time. Sadly I have never been able to produce anything but crashes so far. There is the plugins package but I have not gotten that to work either. The question seems to come up from time to time, e.g. on reddit, but I have not seen an example that works so far. This morning I decided to give it another shot and finally managed to get it to work!
Let us take the following module as an example:
module Plugin(f) where
f :: String
f = "Monads are just monoids in the category of endofunctors, what’s the problem?"
We want to load the module in our main executable and print the string
f
. The code is surprisingly simple and pretty much the same that is
also used in plugins
and similar to the code used in GHCi
. We
first need a function to create the ELF
symbol name in our
executable from the package, module and Haskell
symbol name.
mangleSymbol :: Maybe String -> String -> String -> String
mangleSymbol pkg module' valsym =
prefixUnderscore ++
maybe "" (\p -> zEncodeString p ++ "_") pkg ++
zEncodeString module' ++ "_" ++ zEncodeString valsym ++ "_closure"
For the details of prefixUnderscore
take a look at the
complete code. GHCi
also has a similar function called
nameToCLabel
which can probably be used if you have a Name
instead of dumb
strings.
To load our module we now only need to initialize the linker, load our object file and lookup the symbol of the corresponding name.
main :: IO ()
main =
do initObjLinker
loadObj "plugin.o"
_ret <- resolveObjs
ptr <- lookupSymbol (mangleSymbol Nothing "Plugin" "f")
case ptr of
Nothing -> putStrLn "Couldn’t load symbol"
Just (Ptr addr) -> case addrToAny# addr of
(# hval #) -> putStrLn hval
If you are confused by (# hval #)
that’s just syntax for unboxed
tuples. Also note that this is not at all typesafe. It is up to you to
ensure that the symbol has the correct type.
We can now compile the plugin module using ghc plugin.hs
and our
main module using ghc -package ghc test.hs
. However if we run ./test
we get a cryptic error:
test: plugin.o: unknown symbol `ghczmprim_GHCziCString_unpackCStringUtf8zh_closure'
zsh: segmentation fault (core dumped) ./test
Why is this symbol not found, isn’t that a standard symbol that should always be available? This is the point at which I gave up on my previous tries.
Luckily GHC has a
test
doing something similar (I have no idea why I have not found it
before). The solution is to simply compile our executable using ghc -package ghc -rdynamic test.hs
.
If we now run test
we see the popular useful fact used to confuse
beginners (please don’t do that):
Monads are just monoids in the category of endofunctors, what’s the problem?
You can change the text in plugin.hs
, recompile it and rerun
./test
(notably without recompiling test.hs
) and it will show the
new text.
Since I’ve never used rdynamic
before I did a bit of digging. The
reason for the error is actually independent of Haskell. It turns out
that there is a so called dynamic symbol table
in an ELF
executable. Dynamically loaded code can only access symbols in that
table. However by default not every symbol in the executable is added
to the dynamic symbol table
. Passing rdynamic
tells the linker to
add all symbols to that table no matter if they’re used or not. That
way the dynamically loaded module has access to it.
You can also unload a modul using unloadObj
. Thanks to Simon Marlow
the GC then
unloads the object code.
Sadly I could only test this on Linux so I have no idea if it works on Windows or OS X.
I hope this is useful for someone and look forward to see if and what people use it for.