GHCi doesn't work with FFI export declarations

2019-02-17 06:55发布

问题:

I have a problem regarding FFI in Haskell and the interactive mode of GHC.

(Source is also available via a gist):

FFISo.hs:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module Main where

import qualified Data.ByteString.Char8 as B

foreign import ccall "callMeFromHaskell"
  callMeFromHaskell :: IO ()

foreign export ccall callMeFromC :: IO ()
callMeFromC :: IO ()
callMeFromC = B.putStrLn "callMeFromC"

main :: IO ()
main = do
  B.putStrLn "main"
  callMeFromHaskell
  return ()

c.c:

#include <stdio.h>

void callMeFromC(void);

void callMeFromHaskell(void)
{   
    printf("callMeFromHaskell\n");
    callMeFromC();
}

Makefile:

GHC_OPT := -Wall -O2 -fno-warn-unused-do-bind

all: ffiso

test: ffiso
    ./$<

ffiso: FFISo.hs c.c
    ghc --make $(GHC_OPT) $^ -o $@

clean:
    rm -rf *.hi *.o ffiso *_stub.*

ghci0: ffiso
    echo main | ghci FFISo.hs

ghci1: ffiso
    echo main | ghci FFISo.hs c.o

ghci2: ffiso
    echo main | ghci FFISo.hs c.o FFISo.o

Compiling and linking works fine:

$ make test
ghc --make -Wall -O2 -fno-warn-unused-do-bind FFISo.hs c.c -o ffiso
[1 of 1] Compiling Main             ( FFISo.hs, FFISo.o )
Linking ffiso ...
./ffiso
main
callMeFromHaskell
callMeFromC

However, if I want to use GHCi, it fails with this message:

$ make ghci0
echo main | ghci FFISo.hs
GHCi, version 7.4.1: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Ok, modules loaded: Main.
Prelude Main> Loading package bytestring-0.9.2.1 ... linking ... done.
<interactive>: FFISo.o: unknown symbol `callMeFromHaskell'

Prelude Main> Leaving GHCi.

Well, let's try giving GHCi the c.o objectfile.

$ make ghci1
echo main | ghci FFISo.hs c.o
GHCi, version 7.4.1: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading object (static) c.o ... done
ghc: c.o: unknown symbol `callMeFromC'
linking extra libraries/objects failed
make: *** [ghci1] Error 1
final link ... 

Oh okay... let's try with FFISo.o:

$ make ghci2
echo main | ghci FFISo.hs c.o FFISo.o
GHCi, version 7.4.1: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Loading object (static) c.o ... done
Loading object (static) FFISo.o ... done
ghc: FFISo.o: unknown symbol `bytestringzm0zi9zi2zi1_DataziByteStringziInternal_PS_con_info'
linking extra libraries/objects failed
make: *** [ghci2] Error 1
final link ... 

And that's where I'm stuck.

I tested it with two different environments with the same result:

$ # system 1
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 7.4.1
$ uname -a
Linux phenom 3.2.13-1-ARCH #1 SMP PREEMPT Sat Mar 24 09:10:39 CET 2012 x86_64 AMD Phenom(tm) II X6 1055T Processor AuthenticAMD GNU/Linux

$ # system 2
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 6.12.1
$ uname -a
Linux hermann 2.6.32-22-generic-pae #36-Ubuntu SMP Thu Jun 3 23:14:23 UTC 2010 i686 GNU/Linux

回答1:

You need to specify some more objects to link when invoking GHCi, because the C object c.o is particularly picky when it comes to linking order.

First, you need to add the bytestring package's object files. This is done by adding -package bytestring to the GHCi command line.

Then, you need to add the actual object file that defines callMeFromC. When FFISo.hs is compiled, it doesn't produce an object file that exports callMeFromC. It instead uses the GHC naming convention and exports Main_zdfcallMeFromCzuak4_closure, which actually is a static global variable pointing to the closure/"thunk" that contains the actual function definition and environment. This is so that you can't write something like this:

foregin export ccall foo :: IO ()
foo = undefined

...and have the runtime crash as soon as the program starts because the "function value" of foo can't be evaluated. The function definition is only inspected once the function is actually used.

GHC generates a stub file, which contains C code for calling the Haskell function from C. This file is called FFISo_stub.c, and is compiled into FFISo_stub.o for you. This object file exports the "C version" of callMeFromC that can be called directly. Feel free to inspect the generated code, it is quite interesting.

All in all, you need to use this command line when invoking GHCi:

ghci -package bytestring FFISo.o c.o FFISo_stub.o FFISo.hs


标签: haskell ffi ghci