Haskell中PETSc FFI的库设计

Posted

技术标签:

【中文标题】Haskell中PETSc FFI的库设计【英文标题】:library design of PETSc FFI in Haskell 【发布时间】:2015-06-02 03:59:58 【问题描述】:

我想通过 FFI 接口从 Haskell 提供 PETSc 库(的一个子集),以便对用户隐藏内存和错误管理;

使用如下所示的命令使用共享库构建 PETSc 3.5.3,测试套件运行成功 准备了一个 .hsc 文件 #2,其中包含头文件导入、类型和两个示例外部函数接口 准备了一个 Makefile #3 来自动化构建; make test1 使用加载的模块传递并启动 GHCi。

由于库在 MPI 和完全分布式数据结构支持的并行操作上大放异彩,因此在大多数操作期间不应期望使用 Haskell 进行大量数据流量(所有数据组装、计算和释放都应由库原语完成),但是仅在“数据准备就绪”时。 PETSc 相关的 Haskell 函数大多在 IO monad 中有值,因为我们无法保证纯度(例如,返回的 C 错误代码可能因程序外部原因而有所不同)

需要unsafePerformIO 来包装内存allocation 和错误管理。这种思路正确吗? 坏主意 用 GHC 编译的二进制文件可以用mpirun 执行吗? 是的

我愿意接受所有建议和评论。提前谢谢你

-- 注释: 我们希望 GHC 生成mpirun 可以执行的二进制文件:由于可以使用-optl 标志(参考here)将选项从GHC 命令行传递给链接器,因此有人建议我使用@ 等组合987654329@。我会尽快添加更多关于此的内容。

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

【问题讨论】:

【参考方案1】:

雄心勃勃!我很想使用 C2HS 而不是 hsc2hs,因为它可以为您生成一些外国进口的样板。 (我是 C2HS 的维护者,所以我说什么你都信以为真!)

例如,您可以像这样绑定PetscInitializePetscFinalize

-- 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,除非你真的确定你调用的函数是“有效纯”的——PetscInitialize当然不满足这个条件。你可以写一个PETSc monad 作为IO 周围的一种受限制的包装器,如果你不想把所有东西都直接放在IO monad 中,但是你在PETSc 方面所做的大部分事情真的会在IO monad,因为您将通过调用 API 函数来设置内部 PETSc 状态的位,并且您需要在 Haskell 函数的类型中捕捉这种有效性。

使用 mpirun 运行 GHC 生成的二进制文件应该不是问题。

我也不会编写 makefile。使用 Cabal 应该可以轻松完成这一切!

【讨论】:

抱歉 - 应该提到您需要最新的 C2HS:版本 0.25.1 或 GitHub HEAD。 typedef 和 default 的东西是相当新的。

以上是关于Haskell中PETSc FFI的库设计的主要内容,如果未能解决你的问题,请参考以下文章

haskell 生成 FFI 导出包装代码

Haskell FFI/C 的性能考虑?

Haskell趣学指南

安全执行不受信任的 Haskell 代码

工具rest:Haskell的REST开源框架

-bash: ghci: 找不到命令(Haskell 交互式 shell,Haskell 安装)