7

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?

adius
  • 13,685
  • 7
  • 45
  • 46
  • 2
    "GraalVM offers a comprehensive ecosystem supporting a large set of languages (Java and other JVM-based languages, JavaScript, Ruby, Python, R, and C/C++ and **other LLVM-based languages**) " https://github.com/llvm-hs might be a place to start. – chepner Jun 20 '19 at 11:37
  • @chepner That doesn't seem related. You use `llvm-hs` if you want to actually interface with LLVM. OP wants to compile Haskell *to* LLVM and run that with GraalVM's alternate `lli`. – HTNW Jun 20 '19 at 14:52
  • @HTNW Thanks. Pretty sure there is a project somewhere, though, that does compile Haskell to LLVM; just mistook the link above for that project. I'll try to find the one I meant. – chepner Jun 20 '19 at 15:14
  • Sigh. I may have been thinking of http://www.stephendiehl.com/llvm/, which is *also* not a Haskell-to-LLVM compiler. – chepner Jun 20 '19 at 15:15
  • 2
    @chepner Erm... GHC is the Haskell->LLVM compiler? – HTNW Jun 20 '19 at 15:15
  • @HTNW Ah. Then it might be easier than I thought :) – chepner Jun 20 '19 at 15:16

2 Answers2

7

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.

HTNW
  • 27,182
  • 1
  • 32
  • 60
4

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.