Проблемы с оболочкой библиотеки Haskell FFI/C MPFR
-
05-07-2019 - |
Вопрос
Чтобы создать замену произвольной точности с плавающей запятой/дропом для Double, я пытаюсь обернуть МПФР используя FFI, но, несмотря на все мои усилия, самый простой фрагмент кода не работает.Он компилируется, запускается, но издевательски вылетает после того, как какое-то время притворяется, что работает.Простая версия кода на C успешно печатает число «1» до (640 десятичных знаков) в общей сложности 10 000 раз.Версия 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) для библиотек и файлов заголовков (наряду с файлами из GMP).
Вопросы
Почему версия C работает, а версия Haskell не работает?Что еще я упускаю при обращении в FFI?Я попробовал StablePtrs и получил точно такие же результаты.
Может ли кто-нибудь еще проверить, является ли это проблемой только Mac/BSD, скомпилировав и запустив мой код?(Работает ли код C?Работает ли код Haskell «noworks»?) Может ли кто-нибудь в Linux и Windows попытаться скомпилировать/запустить и посмотреть, получите ли вы те же результаты?
Код С:(работает.с)
#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);
}
}
Хаскелл-код:(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]
Решение
Я тоже вижу проблему, на
$ 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.