4
votes

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;

  • built PETSc 3.5.3 with shared libraries with the command shown below, the test suite runs successfully
  • prepared a .hsc file #2 with the header imports, types and two example foreign function interfaces
  • prepared a Makefile #3 to automate the build; 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 allocation and the error management. Is this line of thought correct? Bad idea
  • Can a binary compiled with GHC be executed with mpirun? Yes

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
1

1 Answers

2
votes

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!