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 (-:

What is Jmeno? Says: 7. 4. 2011, 22:50

First of all, thanks for sharing this. It’s *almost* exactly what I need.

Unfortunately, though, I tried your examples (copying and pasting them verbatim), and I get the following error:

———————————
/usr/bin/ld: Test.o: relocation R_X86_64_32S against `stg_CAF_BLACKHOLE_info’ can not be used when making a shared object; recompile with -fPIC
Test.o: could not read symbols: Bad value
collect2: ld returned 1 exit status
———————————

So I recompile with -fPIC. No dice.
———————————
$ ghc -O2 -fforce-recomp -fPIC –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 …
/usr/bin/ld: /home/dnm/ghc/ghc-6.12.3/lib/ghc-6.12.3/base-4.2.0.2/libHSbase-4.2.0.2.a(Base__127.o): relocation R_X86_64_32S against `stg_upd_frame_info’ can not be used when making a shared object; recompile with -fPIC
/home/dnm/ghc/ghc-6.12.3/lib/ghc-6.12.3/base-4.2.0.2/libHSbase-4.2.0.2.a: could not read symbols: Bad value
collect2: ld returned 1 exit status
———————————

Hmm…maybe adding the -dymaic and -shared flags will help.

—————————–
$ ghc -O2 -fforce-recomp -fPIC -dynamic -shared –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 …
——————————-

Success!…Or so I thought:

——————————-
$ cc test.c -ldl -o test
$ ./test
Segmentation fault
——————————–

Do you have an advice? To give a bit of background, I am running 64-bit Ubuntu (Intrepid Ibex — yes, I know I should upgrade) on a Dell Studio 15, and I had these errors before and after manually re-installing GHC (6.12.3 — yes, I know I should upgrade this too) from source with CFLAGS=”-fPIC” and CPPFLAGS=”-fPIC” (this, because of the second error message).

It looks like either (1) GHC is not building entirely for my 64-bit architecture (which, in this day and age is an unforgivable sin), or (2) it is not entirely statically linked, even though I pass the -fPIC flags, or (3) both (or something else entirely?).

Any thoughts?

What is Jmeno? Says: 7. 4. 2011, 22:55

Also: yes, I googled this problem, but there was no definitive answer anywhere to be found (and I looked over a matter of days). No-one seems to care about calling Haskell from C (and downstream, from Java JNIs, e.g.), but that seems like a place where Haskell could shine.

Andy Says: 9. 9. 2011, 17:54

Does this work on ghc7 with the by-default shared object packages/runtime?

alFReD NSH Says: 1. 9. 2012, 5:35

I just did as “What is Jmeno?” above did, and I realized that dlopen returns an error:

/usr/lib/ghc/ghc-prim-0.2.0.0/libHSghc-prim-0.2.0.0-ghc7.4.1.so: undefined symbol: stg_forkOnzh

Probably because part of haskell is not linked to the shared library. Adding `-L/usr/lib/ghc` didn’t help also.

alFReD NSH Says: 2. 9. 2012, 16:27

Adding this made it work `-L/usr/lib/ghc -l libHSrts-ghc7.4.1.so`

alFReD NSH Says: 2. 9. 2012, 16:27

`-L/usr/lib/ghc -l libHSrts-ghc7.4.1`* (no so)

Herbert Says: 25. 9. 2012, 15:27

Thank you very much for sharing this. It helped me to smoothly interface Haskell (GCC 7.6.x) and C on OS X 10.8 after struggling with that subject for a couple of days.

Přidání komentáře