Kategorie
Nejnovější článek
- Building a shared library in Haskell
- Real World Haskell se šíří Brnem
- Benchmarky pro quad-core na shootoutu
- fotopaste.cz: webová služba psaná v Haskellu
- Haskell a Ruby on Rails II: sessions
- Google summer of code 2008
- Haskell a Ruby on Rails I.
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):
- Using the FFI with GHC -- a section in the GHC manual which mentions calling Haskell from C
- GHC/Using the FFI -- a page on Haskell Wiki dealing with a similar problem on Win32
- The Haskell FFI 1.0 docs -- official FFI docs (section 6 mentions
hs_init, I'll refer to that later)
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
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.
Does this work on ghc7 with the by-default shared object packages/runtime?
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.
Adding this made it work `-L/usr/lib/ghc -l libHSrts-ghc7.4.1.so`
`-L/usr/lib/ghc -l libHSrts-ghc7.4.1`* (no so)
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