I would like to make (a subset of) the PETSc library available from Haskell via a FFI interface in order to hide the memory and error management from the user;
make test1
passes and fires up GHCi with the loaded module.Since the library shines on parallel operations, enabled by MPI and fully distributed data structures, one should not expect much traffic of data with Haskell during most operations (all data assembly, computation and deallocation should be done by the library primitives) but only at "data ready". The PETSc-related Haskell functions will mostly have values in the IO monad, since we cannot guarantee purity (e.g. returned C error codes can vary due to reasons external to the program)
unsafePerformIO
would be needed to wrap the memory alloca
tion and the error management. Is this line of thought correct?mpirun
? I am open to all suggestions and remarks. Thank you in advance
-- NOTES:
We want GHC to produce a binary that mpirun
can execute: Since one can pass options from the GHC command line to the linker with the -optl
flag (reference here), I've been suggested a combination such as ghc -optl-static -lmpich
. I'll add more about this as soon as I can try it out.
1) config command:
$ ./configure --with-cc=gcc --with-cxx=g++ --with-fc=gfortran --with-shared-libraries=1 --download-mpich --download-fblaslapack
2) PETSC.hsc
{-# LANGUAGE CPP, ForeignFunctionInterface, EmptyDataDecls #-}
module PETSc where
import Foreign
import Foreign.Ptr
import Foreign.C.Types
import Foreign.C.String
#include <petscksp.h>
#include <petscsys.h>
newtype PetscErrCode = PetscErrCode {unPetscErrCode :: CInt} deriving (Eq, Show)
newtype PetscInt = PetscInt {unPetscInt :: CInt} deriving (Eq, Show)
data Petsc
-- PetscErrorCode PetscInitialize(int *argc,char ***args,const char file[],const char help[])
foreign import ccall unsafe "petscsys.h PetscInitialize"
c_petscInitialize :: Ptr CInt -> Ptr (Ptr CString) -> CString -> CString -> IO PetscErrCode
-- PetscErrorCode PetscFinalize(void)
foreign import ccall unsafe "petscsys.h PetscFinalize"
c_petscFinalize :: IO PetscErrCode
3) Makefile
PETSC_DIR_ARCH = ${PETSC_DIR}/arch-darwin-c-debug
PETSc.hs:
hsc2hs PETSc.hsc -I ${PETSC_DIR}/include -I ${PETSC_DIR_ARCH}/include
test1: PETSc.hs
ghci -dynamic PETSc.hs -L${PETSC_DIR_ARCH}/lib
Ambitious! I'd be tempted to use C2HS instead of hsc2hs, since it can produce some of the boilerplate for foreign imports for you. (I'm the maintainer of C2HS, so you can take whatever I say with a grain of salt!)
As an example, you can bind PetscInitialize
and PetscFinalize
like this:
-- This is in PETSc.chs
module PETSc (initialize, finalize) where
import Foreign
import Foreign.Ptr
import Foreign.C.Types
import Foreign.C.String
import System.Environment (getArgs)
#include <petscksp.h>
#include <petscsys.h>
-- Marshalling helpers for PETSc error codes.
newtype ErrCode = ErrCode { unErrCode :: Int }
deriving (Eq, Show)
convErrCode :: CInt -> ErrCode
convErrCode = ErrCode . fromIntegral
{#typedef PetscErrorCode CInt#}
{#default out `ErrCode' [PetscErrorCode] convErrCode#}
-- Marshalling helpers for "char ***" argument to PetscInitialize.
withCStrings :: [String] -> ([CString] -> IO a) -> IO a
withCStrings ss f = case ss of
[] -> f []
(s:ss') -> withCString s $ \cs -> do
withCStrings ss' $ \css -> f (cs:css)
withCStringArray :: [String] -> (Ptr CString -> IO a) -> IO a
withCStringArray ss f = withCStrings ss $ \css -> withArray css f
withCStringArrayPtr :: [String] -> (Ptr (Ptr CString) -> IO a) -> IO a
withCStringArrayPtr ss f = withCStringArray ss $ \css -> with css f
-- Low-level function hooks.
{#fun PetscInitialize as internal_initialize
{`Int', withCStringArrayPtr* `[String]', `String', `String'}
-> `ErrCode'#}
{#fun PetscFinalize as finalize {} -> `ErrCode'#}
-- Better API for initialization.
initialize :: String -> String -> IO ErrCode
initialize file help = do
args <- getArgs
internal_initialize (length args) args file help
This is actually a pretty tough example to do with C2HS because managing the marshalling of the char ***
argument to PetscInitialize
is a bit awkward, but you get the idea. Most other marshalling cases should be much more straightforward -- dealing with pointers and arrays is pretty easy, as is marshalling C strings. (I'm happy to help with C2HS questions if you decide to use it.)
Once you have this, you can call it like this:
-- This is Tst.hs or something...
module Main where
import PETSc
main :: IO ()
main = do
res1 <- initialize "" ""
print res1
res2 <- finalize
print res2
Not very useful yet, but it's a start! Compile it like this:
c2hs -C -I/opt/petsc/arch-linux2-cxx-opt/include PETSc.chs
ghc --make Tst.hs PETSc.hs -L/opt/petsc/arch-linux2-cxx-opt/lib/ -lpetsc
(adjusting paths as necessary, obvs).
To answer your other questions:
Don't use unsafePerformIO
unless you're really sure that the functions you're calling are "effectively pure" -- PetscInitialize
certainly doesn't fulfil that condition. You could write a PETSc
monad as a sort of restricted wrapper around IO
if you don't want to have everything in the IO
monad directly, but most of what you're doing PETSc-wise really will be in the IO
monad, since you're going to be setting bits of internal PETSc state by calling API functions, and you need to capture that effectfulness in the types of your Haskell functions.
Running GHC-produced binaries with mpirun
shouldn't be a problem.
I'd also not be writing makefiles. You should be able to do this all with Cabal without too much trouble!