Haskell FFI / C MPFR库包装器问题

4
为了创建一个任意精度的浮点数/Double的替代品,我尝试使用FFI包装MPFR,但是尽管我付出了所有努力,最简单的代码也无法正常工作。它可以编译,可以运行,但是在假装工作一段时间后,它会嘲笑地崩溃。一个简单的C版本的代码高兴地将数字“1”打印出来(640位小数)共计10,000次。当要求Haskell版本执行相同操作时,它在仅289次打印“1.0000...0000”后静默地破坏(?)数据,并在385次打印之后引发断言失败并中止。我不知道如何继续调试,因为它“应该工作”。
代码可在http://hpaste.org/10923上查看,并可在http://www.updike.org/mpfr-broken.tar.gz上下载。
我正在使用FreeBSD 6上的GHC 6.83和Mac OS X上的GHC 6.8.2。请注意,您需要安装正确路径的MPFR(已测试使用2.3.2),包括来自GMP的库和头文件(以及修改Makefile),以成功编译此代码。

问题

  • 为什么C版本可以工作,但Haskell版本会出现问题?在处理FFI时还需要注意什么?我尝试使用StablePtrs并得到了完全相同的结果。

  • 是否有其他人可以通过编译和运行我的代码验证这是一个仅限于Mac/BSD的问题?(C代码“works.c”是否有效?Haskell代码“noworks”是否有效?)任何在Linux和Windows上尝试编译/运行并查看是否获得相同结果的人都可以参与。

C代码:(works.c)

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  

#include <gmp.h>  
#include <mpfr.h>
#include "mpfr_ffi.c"  

int main()  
{  
  int i;  
  mpfr_ptr one;  

  mpf_set_default_prec_decimal(640);  

  one = mpf_set_signed_int(1);  
  for (i = 0; i < 10000; i++)
    {  
      printf("%d\n", i);
      mpf_show(one);
    }  
}  

Haskell代码:(Main.hs --- 不起作用)
module Main where  

import Foreign.Ptr            ( Ptr, FunPtr )  
import Foreign.C.Types        ( CInt, CLong, CULong, CDouble )  
import Foreign.StablePtr      ( StablePtr )  

data MPFR = MPFR  

foreign import ccall "mpf_set_default_prec_decimal"  
    c_set_default_prec_decimal          :: CInt -> IO ()  
setPrecisionDecimal                     :: Integer -> IO ()  
setPrecisionDecimal decimal_digits = do  
    c_set_default_prec_decimal (fromInteger decimal_digits)  

foreign import ccall "mpf_show"  
   c_show                               :: Ptr MPFR -> IO ()  

foreign import ccall "mpf_set_signed_int"  
   c_set_signed_int                     :: CLong -> IO (Ptr MPFR)  

showNums k n = do  
   print n  
   c_show k  

main = do  
   setPrecisionDecimal 640  
   one <- c_set_signed_int (fromInteger 1)  
   mapM_ (showNums one) [1..10000]  
3个回答

4
Judah Jacobsen在Haskell-cafe邮件列表中回答道:这是 GHC已知的问题,因为GHC内部使用GMP(来维护整数)。据说,在堆中的C数据在基本所有情况下都不会被GHC修改,除了使用FFI访问GMP或依赖GMP的任何C库的代码(例如我想使用的MPFR)。有一些解决方法(痛苦),但“正确”的方法要么是修改GHC(困难),要么是让Simons删除GHC对GMP的依赖(更困难)。

3

我也看到了问题,在

$ uname -a
Linux burnup 2.6.26-gentoo-r1 #1 SMP PREEMPT Tue Sep 9 00:05:54 EDT 2008 i686 Intel(R) Pentium(R) 4 CPU 2.80GHz GenuineIntel GNU/Linux
$ gcc --version
gcc (GCC) 4.2.4 (Gentoo 4.2.4 p1.0)
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 6.8.3

我看到输出从1.0000...000变成了1.0000...[垃圾]。
让我们看看,以下内容可以正常工作:
main = do
    setPrecisionDecimal 640
    mapM_ (const $ c_set_signed_int (fromInteger 1) >>= c_show) [1..10000]

这将问题缩小到在运行时某些部分被某种方式损坏的one部分。然而,查看ghc -Cghc -S的输出并没有给我任何提示。

嗯,./noworks +RTS -H1G也可以工作,并且对于不同值的[n]./noworks +RTS -k[n]k以不同的方式显示失败。

我没有确切的线索,但我想到了两种可能性:

  • GMP,即 GHC runtime 使用的库,以及 MPFR 之间有一些奇怪的交互
  • 在 GHC runtime 中调用 C 函数的栈空间大小受限制,而 MPFR 处理得不好

话虽如此...你为什么要自己编写绑定,而不是使用HMPFR


哦,你已经弄明白了。很高兴知道我有点在正确的方向上... - ephemient
HMPFR于9月底上传,而我在7月份开始了自己的绑定。感谢您的帮助调查。 - Jared Updike

1
Aleš Bizjak(HMPFR的维护人员)在haskell-cafe上发帖,介绍了如何防止GHC控制肢体分配(从而使它们保持不变,而不是被GC并被覆盖)。
mpfr_ptr mpf_new_mpfr()  
{  
  mpfr_ptr result = malloc(sizeof(__mpfr_struct));  
  if (result == NULL) return NULL;  
  /// these three lines:  
  mp_limb_t * limb = malloc(mpfr_custom_get_size(mpfr_get_default_prec()));  
  mpfr_custom_init(limb, mpfr_get_default_prec());  
  mpfr_custom_init_set(result, MPFR_NAN_KIND, 0, mpfr_get_default_prec(), limb);  
  return result;  
}

对我来说,这比加入编写 GHC 中 GMP 替代品的努力要容易得多,如果我真的想使用任何依赖于 GMP 的库,那么这将是唯一的选择。

HMPFR似乎还存在一个未解决的问题,当我展示它们时,仍然出现内存损坏。 - Edward Kmett
@Edward:那真是不幸啊...我已经有一段时间没有回去工作这个项目了。 - Jared Updike
看起来MPFR(以及GMP)使用全局状态。这是问题的原因吗? - Lemming

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接