Haskell 中的 PETSc FFI 文库设计
library design of PETSc FFI in Haskell
我想通过 FFI 接口从 Haskell 提供 PETSc 库的(子集),以便对用户隐藏内存和错误管理;
- 使用如下所示的命令使用共享库构建 PETSc 3.5.3,测试套件成功运行
- 准备了一个 .hsc 文件 #2,其中包含头导入、类型和两个示例外部函数接口
- 准备了 Makefile #3 来自动构建;
make test1
通过加载的模块并启动 GHCi。
由于库在并行操作上大放异彩,由 MPI 和完全分布式数据结构支持,因此在大多数操作期间,Haskell 不应期望有大量数据流量(所有数据组装、计算和释放都应该完成由库原语)但仅限于 "data ready"。 PETSc 相关的 Haskell 函数将主要在 IO monad 中有值,因为我们不能保证纯度(例如,由于程序外部原因,返回的 C 错误代码可能会有所不同)
需要 unsafePerformIO
来包装内存 alloca
tion 和错误管理。这个思路正确吗? 坏主意
可以用mpirun
执行用GHC编译的二进制文件吗? 是
我愿意接受所有的建议和评论。提前谢谢你
-- 注意:
我们希望 GHC 生成一个 mpirun
可以执行的二进制文件:因为可以使用 -optl
标志(参考 here)从 GHC 命令行将选项传递给链接器,我一直建议使用 ghc -optl-static -lmpich
等组合。我会在试用后尽快添加更多相关信息。
1)配置命令:
$ ./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) 生成文件
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
雄心勃勃!我很想使用 C2HS 而不是 hsc2hs,因为它可以为您生成一些用于外国进口的样板。 (我是C2HS的维护者,所以你可以对我所说的持保留态度!)
例如,您可以像这样绑定 PetscInitialize
和 PetscFinalize
:
-- 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
这实际上是一个很难处理的 C2HS 示例,因为管理 char ***
参数到 PetscInitialize
的编组有点尴尬,但你明白了。大多数其他编组案例应该更直接——处理指针和数组非常简单,就像编组 C 字符串一样。 (如果您决定使用它,我很乐意帮助解决 C2HS 问题。)
一旦你有了这个,你可以这样称呼它:
-- This is Tst.hs or something...
module Main where
import PETSc
main :: IO ()
main = do
res1 <- initialize "" ""
print res1
res2 <- finalize
print res2
还不是很有用,但这是一个开始!像这样编译它:
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
(根据需要调整路径,obvs)。
回答您的其他问题:
不要使用unsafePerformIO
除非你真的确定你正在调用的函数是"effectively pure"——PetscInitialize
肯定不满足那个条件。如果你不想直接在 IO
monad 中拥有所有内容,你可以编写一个 PETSc
monad 作为 IO
的一种受限包装器,但你正在做的大部分事情都是 PETSc -wise 确实会在 IO
monad 中,因为您将通过调用 API 函数来设置内部 PETSc 状态位,并且您需要在 Haskell 函数。
运行 GHC 生成的带有 mpirun
的二进制文件应该不是问题。
我也不会编写 makefile。你应该可以用 Cabal 轻松完成这一切!
我想通过 FFI 接口从 Haskell 提供 PETSc 库的(子集),以便对用户隐藏内存和错误管理;
- 使用如下所示的命令使用共享库构建 PETSc 3.5.3,测试套件成功运行
- 准备了一个 .hsc 文件 #2,其中包含头导入、类型和两个示例外部函数接口
- 准备了 Makefile #3 来自动构建;
make test1
通过加载的模块并启动 GHCi。
由于库在并行操作上大放异彩,由 MPI 和完全分布式数据结构支持,因此在大多数操作期间,Haskell 不应期望有大量数据流量(所有数据组装、计算和释放都应该完成由库原语)但仅限于 "data ready"。 PETSc 相关的 Haskell 函数将主要在 IO monad 中有值,因为我们不能保证纯度(例如,由于程序外部原因,返回的 C 错误代码可能会有所不同)
-
需要
坏主意unsafePerformIO
来包装内存alloca
tion 和错误管理。这个思路正确吗?可以用是mpirun
执行用GHC编译的二进制文件吗?
我愿意接受所有的建议和评论。提前谢谢你
-- 注意:
我们希望 GHC 生成一个 mpirun
可以执行的二进制文件:因为可以使用 -optl
标志(参考 here)从 GHC 命令行将选项传递给链接器,我一直建议使用 ghc -optl-static -lmpich
等组合。我会在试用后尽快添加更多相关信息。
1)配置命令:
$ ./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) 生成文件
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
雄心勃勃!我很想使用 C2HS 而不是 hsc2hs,因为它可以为您生成一些用于外国进口的样板。 (我是C2HS的维护者,所以你可以对我所说的持保留态度!)
例如,您可以像这样绑定 PetscInitialize
和 PetscFinalize
:
-- 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
这实际上是一个很难处理的 C2HS 示例,因为管理 char ***
参数到 PetscInitialize
的编组有点尴尬,但你明白了。大多数其他编组案例应该更直接——处理指针和数组非常简单,就像编组 C 字符串一样。 (如果您决定使用它,我很乐意帮助解决 C2HS 问题。)
一旦你有了这个,你可以这样称呼它:
-- This is Tst.hs or something...
module Main where
import PETSc
main :: IO ()
main = do
res1 <- initialize "" ""
print res1
res2 <- finalize
print res2
还不是很有用,但这是一个开始!像这样编译它:
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
(根据需要调整路径,obvs)。
回答您的其他问题:
不要使用
unsafePerformIO
除非你真的确定你正在调用的函数是"effectively pure"——PetscInitialize
肯定不满足那个条件。如果你不想直接在IO
monad 中拥有所有内容,你可以编写一个PETSc
monad 作为IO
的一种受限包装器,但你正在做的大部分事情都是 PETSc -wise 确实会在IO
monad 中,因为您将通过调用 API 函数来设置内部 PETSc 状态位,并且您需要在 Haskell 函数。运行 GHC 生成的带有
mpirun
的二进制文件应该不是问题。
我也不会编写 makefile。你应该可以用 Cabal 轻松完成这一切!