Kategorie

Nejnovější článek

Archiv

Odkazy


Hledání




Building a shared library in Haskell

A lot has been written on the topic of Foreign Function Interface and its use for calling Haskell from C, yet the official GHC and FFI documentation lack a description of an easy way to create a shared library that may be used from C in a regular way. In this article I present a little bit hacky, but still very simple and comfortable way to do this on Linux (which hopefully applies to all similar Unixes as well).

The FFI documentation as well as the GHC manual explain that you have to call hs_init before calling into Haskell. I will present a C helper that does this automatically, I shall explain why it really works and I will provide a Makefile that makes this all pretty easy. In the end, we're going to be able to simply write Haskell modules with a few foreign export declarations and get working shared libraries out of the box.

First, let me show you the documentation that we have (it shall help you with FFI, which I'm not explaining here, and it has a few notes about the topic I discuss as well):

Okay, we have the idea, let's get started with a Haskell function that we want to export into a shared library:

import Foreign.C.Types
 
hsfun :: CInt -> IO CInt
hsfun x = do
    putStrLn "Hello World"
    return (42 + x)

This works in GHCi very well:

$ ghci Test.hs
GHCi, version 6.8.2: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Ok, modules loaded: Test.
Prelude Test> hsfun 5
Hello World
47

Since we want to export this function to C, let's just add the foreign export declaration. This is what the complete Test.hs source looks like:

{-# LANGUAGE ForeignFunctionInterface #-}
module Test where
 
import Foreign.C.Types
 
hsfun :: CInt -> IO CInt
hsfun x = do
    putStrLn "Hello World"
    return (42 + x)
 
foreign export ccall
    hsfun :: CInt -> IO CInt

We can now try to compile and link it, and here comes the first trick -- using the -no-hs-main -optl '-shared' options makes ghc create a shared library for us:

$ ghc -O2 --make -no-hs-main -optl '-shared' -o Test.so Test.hs
[1 of 1] Compiling Test             ( Test.hs, Test.o )
Linking Test.so ...

You can try calling hsfun from this library, but it will segfault right away since we didn't call hs_init and we really have to. Well, all we need to do is attach a C helper that calls hs_init and hs_add_root (this seems to be GHC specific, but I don't care) when the library is loaded and hs_exit when unloaded. However, the FFI spec says that although we may nest calls to hs_init, we should not call these in arbitrary order -- we're supposed to call all hs_inits first and only after that all hs_exits. Looking into the implementation of these functions in GHC suggests that GHC does not have this requirement, therefore we simply ignore it. Also, there is no problem in calling hs_init after hs_exit completely deinitialized the Haskell runtime.

EDIT: Later I realized that we don't need to care about this at all since the Haskell runtime is statically linked and therefore every single shared library we make has its own separated instance of RTS. This for example means that accesses to random number generator from two modules do not interfere with each other and that if you ask for profiling, you get separate output for each library, the same for runtime statistics.

Finally, this is the module_init.c file that solves this stuff for us (download link):

#define CAT(a,b) XCAT(a,b)
#define XCAT(a,b) a ## b
#define STR(a) XSTR(a)
#define XSTR(a) #a
 
#include <HsFFI.h>
 
extern void CAT(__stginit_, MODULE)(void);
 
static void library_init(void) __attribute__((constructor));
static void library_init(void)
{
    /* This seems to be a no-op, but it makes the GHCRTS envvar work. */
    static char *argv[] = { STR(MODULE) ".so", 0 }, **argv_ = argv;
    static int argc = 1;
 
    hs_init(&argc, &argv_);
    hs_add_root(CAT(__stginit_, MODULE));
}
 
static void library_exit(void) __attribute__((destructor));
static void library_exit(void)
{
    hs_exit();
}

Let's compile and link it with this helper:

$ ghc -O2 --make \
      -no-hs-main -optl '-shared' -optc '-DMODULE=Test' \
      -o Test.so Test.hs module_init.c
[1 of 1] Compiling Test             ( Test.hs, Test.o )
Linking Test.so ...

And try calling hsfun from C:

$ cat >test.c
#include <stdio.h>
#include <dlfcn.h>
void test(void) {
    void *dl = dlopen("./Test.so", RTLD_LAZY);
    printf("%d\n", ((int(*)(int)) dlsym(dl, "hsfun"))(5));
    dlclose(dl);
}
int main(int argc, char *argv[]) {
    test(); test(); test();
    return 0;
}
$ cc test.c -ldl -o test
$ ./test
Hello World
47
Hello World
47
Hello World
47

Wow, it works indeed. Now, the promised Makefile rule:

%.so: %.hs
        $(HC) $(HCFLAGS) --make \
                -no-hs-main -optl '-shared' -optc '-DMODULE='$(*F) \
                -o $@ $< module_init.c

I hope that's all you need to start playing with shared libraries in Haskell!

EDIT: I should add that I did all this with ghc 6.8.2 on 32-bit Debian Linux.

Autor: Tomáš Janoušek Prosinec 2008


Dva komentáře k článku „Building a shared library in Haskell“

Michal Says: 9. 4. 2009, 7:49

Dobrý den,
mám problém s programem v Haskellu. Má generovat permutace, nevím vůbec jak začít, na internetu jsem našel pouze základy, jsem docela bezradný. Myslíte. že by jste mi mohl pomoct? Prosím ozvěte se mi na mail. Díky

Michal Says: 9. 4. 2009, 7:49

filipmic@seznam.cz

Shakeeb Says: 11. 10. 2009, 14:40

Thanks for that! It was exactly what I was looking for (-:

Přidání komentáře