Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to run Haskell on GraalVM?

I tried to get Haskell running on GraalVM, but I wasn’t able to include the runtime environment. In Rust it’s just a matter of specifying the correct path for the Rust standard library, as described here.

Is there an equally simple solution for Haskell? Is it even possible at all?

like image 979
adius Avatar asked Jun 20 '19 07:06

adius


2 Answers

This is not a complete answer, but it's most of the way there. It is not too complicated:

The Haskell runtime is just a library that you can find in your GHC installation. On my Mac it's in $PREFIX/lib/ghc-$VERSION/rts, where $PREFIX is the installation prefix of GHC (e.g. /, /usr, /usr/local, etc.—the compiler executable should be $PREFIX/bin/ghc). You need to use one of the shared libraries (for me, they're called .dylibs). However, neither the Haskell runtime nor the compiled Haskell code contains main. GHC generates a stub C file:

#include "Rts.h"
extern StgClosure ZCMain_main_closure;
int main(int argc, char *argv[])
{
 RtsConfig __conf = defaultRtsConfig;
 __conf.rts_opts_enabled = RtsOptsSafeOnly;
 __conf.rts_opts_suggestions = true;
 __conf.rts_hs_main = true;
 return hs_main(argc,argv,&ZCMain_main_closure,__conf);
}

where ZCMain_main_closure refers to the main action written in Haskell and hs_main refers to a symbol from the RTS. You will need to compile this to bitcode with clang, compile the Haskell code with ghc, llvm-link them into one big .bc, then give it to GraalVM's lli. With the above in c_main.c, place an example program into Main.hs:

main = putStrLn "Hello, World!"

Compile and link:

$ clang -emit-llvm -I/usr/local/lib/ghc-8.6.5/include -c c_main.c
# Change -I path as needed
$ ghc -fllvm -keep-llvm-files -S Main.hs
$ llvm-link Main.ll c_main.bc -o prog.bc

Now, in a perfect world, the following would work:

$ lli --lib /usr/local/lib/ghc-8.6.5/rts/libHSrts-ghc8.6.5.dylib \
      --lib /usr/local/lib/ghc-8.6.5/base-4.12.0.0/libHSbase-4.12.0.0-ghc8.6.5.dylib \
      prog.bc
# Maybe you need more of the base libraries
# It's kind of hard to test because it doesn't work, anyway

However, this doesn't work because the libraries have mutual dependencies. base, written mostly in Haskell, needs the RTS. The RTS hooks into base to communicate with Haskell (e.g. with exceptions). GraalVM tries to dlopen them one at a time with RTLD_NOW, which tries and fails to strictly resolve the symbols. It would need to use RTLD_LAZY. This should be an easily fixable issue in GraalVM.

like image 82
HTNW Avatar answered Nov 10 '22 13:11

HTNW


Thanks HTNW for the pointers on how to produce the required bitcode. I can provide a bit more info on what would be needed on the GraalVM side.

First of all, for the problem with cyclic dependencies, RTLD_LAZY alone doesn't work, because that works only for unresolved functions, not for unresolved variables (you can verify that with a simple C program trying to dlopen libHSrts and libHSbase). But there is a very simple workaround for that: Just produce an empty shared object linking to both (gcc -shared libHSrts-.so libHSbase-.so). This shared object can then be dlopen-ed by GraalVM.

Unfortunately, it still doesn't work, GraalVM seems to run into a segfault somewhere inside the Haskell rts. One potential problem is the GC, we're probably messing up the stack quite a bit in GraalVM, preventing the GC from working correctly.

An interesting thing to try would be to compile everything (including rts and libraries) to bitcode. That would make debugging this problem a lot easier, not having any native-managed boundaries. The GC is still going to be a problem, because on GraalVM the stack will probably look very different from native. But maybe we can even patch the GC out, and just use the normal Java GC.

like image 4
Roland Schatz Avatar answered Nov 10 '22 12:11

Roland Schatz