Haskell FFI / C MPFR библиотека оболочка беды
Чтобы создать замену Double с плавающей точкой произвольной точности, я пытаюсь обернуть MPFR с помощью FFI, но, несмотря на все мои усилия, самый простой фрагмент кода не работает. Он компилируется, запускается, но насмешливо вылетает после того, как некоторое время притворяется, что работает. Простая C-версия кода с радостью печатает число от "1" до (640 десятичных знаков) в общей сложности 10000 раз. Версия на Haskell, когда ее просят сделать то же самое, молча повреждает (?) Данные только после 289 распечаток "1.0000...0000" и после 385 распечаток, это вызывает ошибку подтверждения и бомбы. Я в недоумении, как приступить к отладке этого, так как это "должно работать".
Код можно просмотреть по адресу http://hpaste.org/10923 и загрузить по адресу http://www.updike.org/mpfr-broken.tar.gz
Я использую GHC 6.83 во FreeBSD 6 и GHC 6.8.2 в Mac OS X. Обратите внимание, что вам понадобится MPFR (протестированный с 2.3.2) с правильными путями (измените Makefile) для библиотек libs и заголовочных файлов (вместе с этими из GMP), чтобы успешно скомпилировать это.
Вопросы
Почему работает версия C, а версия на Haskell не работает? Что еще мне не хватает при приближении к FFI? Я попробовал StablePtrs и получил точно такие же результаты.
Может ли кто-то еще проверить, является ли это проблемой только для Mac/BSD, скомпилировав и выполнив мой код? (Работает ли код на 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 ответа
Я тоже вижу проблему на
$ 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 -C
а также ghc -S
Тем не менее, не дает мне никаких намеков.
Хм, ./noworks +RTS -H1G
также работает, и ./noworks +RTS -k[n]k
для различных значений [n]
демонстрировать неудачи по-разному.
У меня нет надежных поводов, но есть две возможности, которые приходят мне в голову:
- GMP, который использует среда выполнения GHC, и MPFR со странным взаимодействием
- пространство стека для функций C, вызываемых во время выполнения GHC, ограничено, и MPFR работает плохо
При этом... есть ли причина, по которой вы скручиваете свои собственные привязки, а не используете HMPFR?
Джуда Якобсен ответил на это в списке рассылки Haskell-cafe:
Это известная проблема с GHC из-за того, что GHC использует GMP внутри (для поддержки целых чисел).
Очевидно, что данные C в куче оставлены GHC в основном во всех случаях, кроме кода, который использует FFI для доступа к GMP, или любой библиотеки C, которая использует GMP (например, MPFR, который я хотел использовать). Есть некоторые обходные пути (болезненные), но "правильным" способом было бы либо взломать GHC (тяжело), либо заставить Саймонов убрать зависимость GHC от GMP (сложнее).
Алеш Бизжак, сопровождающий HMPFR, отправил сообщение в haskell-cafe и показал, как не дать GHC контролировать распределение конечностей (и, следовательно, оставить их в покое, вместо того, чтобы собирать их и забивать их):
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;
}
Для меня это намного проще, чем объединить усилия для написания замены для GMP в GHC, которая была бы единственной альтернативой, если бы я действительно хотел использовать любую библиотеку, которая зависит от GMP.