SlideShare a Scribd company logo
HASKELL FFI
РАБОТА С ВНЕШНИМ КОДОМ
Автор /Вершилов Александр @qnikst
ОСНОВНЫЕ ПРОБЛЕМЫ В RTS
ЯЗЫКЕ
1. Эффекты ввода-вывода
2. Исключения
3. Конкурентность (concurrency)
4. Вызов внешнего кода
КОРОТКО ОБО ВСЕМ
{­# LANGUAGE ForeignFunctionsInterface #­}
foreign import ccall [safe|unsafe]
        "header.h function­name" c_function :: type
foreign export ccall "function­name" hs_function :: type
   Направление вызова
   Соглашение о вызовах
   Безопасность вызова
   Описание символа
   Имя в Haskell
   Тип функции в Haskell
СОГЛАШЕНИЯ О ВЫЗОВАХ
foreign import ccall safe "header.h function­name" c_function :: type
ccall — соглашение о вызовах C
stdcall — Win32 API
cplusplus — соглашение о вызовах С++
dotnet — соглашение о вызовах .Net
jvm — соглашение о вызовах JVM
БЕЗОПАСНОСТЬ ВЫЗОВОВ
foreign import ccall [safe|unsafe] "header.h function­name" c_function :: type
Безопасный вызов (safe)
[+] Сохраняет состояние Haskell системы
[+] Можно вызывать callback-и в Haskell
[-] Медленный
Небезопасный вызов (unsafe)
[+] Быстрый
[-] Вызов Haskell код-а - undeined behaviour
[-] Блокирует capability
Вызов pid_t getppid(void)
safe unsafe
1 thread sequential
non-threaded 70.ns 2.78ns
threaded 109.7ns 2.78ns
1 thread 1000x concurrent (async)
non-threaded 2.494ms 2.482ms
threaded 3.750ms 3.630ms
ОПИСАНИЕ СИМВОЛА
foreign import ccall "header.h function­name" c_function :: type
описание зависит от используемого соглашения о
вызовах
может содержать специальные значения (напр. "&")
в ccallпросто описания символа в объектном
файле
ТИПЫ
foreign import ccall "header.h function­name" c_function :: type
Обычный тип в Haskell, где переменными могут быть
внешние типы
Внешние типы: CInt, CWord, Int8, Int16, Int32, Int64, Ptr
a, FunPtr a, StablePtr
В ccallвсегда обозначает функцию:
foreign import ccall "header.h foo" c_foo :: CInt
extern int foo();
IO VS PURE
foreign import ccall unsafe "math.h sin  c_sin    :: Double ­> Double
foreign import ccall unsafe "math.h sin" c_sin_io :: Double ­> IO Double
функция должна быть чистой (результат зависит
только от аргументов)
функция должна быть потокобезопасной (reentrant)
Типы передаваемые в Ptr
"Прикрепленные" (pinned):
[+] Можно напрямую передавать во внешний
код(*)
[-] Не перемещаются GC — приводят к
фрагментации памяти
[-] Приводят к повышенному потреблению памяти
"Не прикрепленные" (Unpinned):
[+] перемещаются GC
[-] нельзя напрямую передавать во внешний код(*)
Pinned Unpinned
ByteArray# ByteArray#
Data.ByteString Data.ShortByteString
Data.Vector.Storable Data.Vector.Unboxed, Data.Vector
  Data.Text
Foreign.Marshalбайты, массивы, строки
alloca, calloc, malloc
Pool — управление группой ресурсов
Foreign.StablePtr— структура данных
гарантирована не будет удалена, пока не будет
вызвано hs_free_stable_ptr, freeStablePtr
полезен, гарантировать управление памятью из
внешнего языка и удалению без возвращения
исполнения RTS
позволяет сохранять состояние при перезгрузке
ghci/модулей
сохранение функции с C коде для её
последующего запуска
typedef void *HsStablePtr;
РАБОТА СО СТРУКТУРАМИ
{­# LANGUAGE EmptyDataDecls #­}
data CStruct
newtype PtrEnv = PtrEnv (Ptr EnvHandle)
class Storable a where
  sizeOf :: a ­> Int
  alignment :: a ­> Int
  peekElemOff :: Ptr a ­> Int ­> IO a
  pokeElemOff :: Ptr a ­> Int ­> a ­> IO ()
  peekByteOff :: Ptr b ­> Int ­> IO a
  pokeByteOff :: Ptr b ­> Int ­> a ­> IO ()
  peek :: Ptr a ­> IO a
  poke :: Ptr a ­> a ­> IO ()
Для создания Storable структур отвеачающих
структура внешних языков нужна дополнительная
информация, такая как отступы, размеры структур и
другие бонусы.
hsc2hs
Дает использовать макросы
peek,poke,ptr,enum, sizeof
.hsc
build-tool: hsc2hs
достаточно низкоуровневый
ЭКСПОРТ ФУНКЦИЙ
module Foo where
foreign export ccall "function­name" hs_function :: type
Создаются файлы
Foo_stub.c
Foo_stub.h
Foo_stub.o
#include "Foo_stub.h"
#include <HsRTS>
hs_init(&argc, &argv);
hs_free();
grep ­A 1 "*** Linker" ghc_output | tail ­n 1 | grep ­i ­­ '­L.*' > link_options
ДИНАМИЧЕСКИЕ ВЫЗОВЫ
foreign import ccall "wrapper" :: haskelltype ­> IO (FunPtr haskelltype)
Освобождение:
freeHaskellFunPtr
hs_free_fun_ptr
Создание динамической функции из C:
foreign import ccall "&" :: FunPtr (FunPtr ­> IO ())
Static function
foreign import ccall "static stdlib.h system" system ::
Ptr CChar -> IO ()
Static address
foreign import ccall "errno.h &" errno :: Ptr CInt
Dynamic import
foreign import ccall "dynamic" mkFun :: FunPtr (CInt ->
IO ()) -> (CInt -> IO ())
Dynamic export
foreign import ccall "wrapper" :: (CInt -> IO ()) -> FunPtr
(CInt -> IO ())
ОПЯТЬ К БЕЗОПАСНОСТИ
void bottom() { for(;;) }
foreing import safe "bottom" c_safe_bottom :: IO ()
foreign import unsafe "bottom" c_unsafe_bottom :: IO ()
main :: IO ()
main = do
  forkIO $ safe_bottom 1
  yield
  print "Pass"
  forkOS $ unsafe_bottom 1 ­­ forkOn 2, forkIO
  yield
  print "Pass"
unsafe вызовы блокируют capability
для того, чтобы провести сборку мусора нужны все
capability
система должна вести себя так, как будто каждый
Haskell поток реализован потоком OS.
safe вызов блокирует только тот поток, который
выполняет вызов
программист может указывать, что группа вызовов
должна происходить из одного потока OS
Haskell потоки созданные вызовами из внешнего
языка должны быть независимы от времени жизни
вызвавшей их функции
МОДЕЛИ ОГРАНИЗАЦИИ
ПОТОКОВ
один-к-одному
каждому потоку Haskell соотвестсвует поток OS
все-к-одному
всем потокам Haskell соотвествует один поток OS
гибридная
каждому потоку OS соответствует нескольо
потоков Haskell
Для политики управления потоками RTS, нет разницы
какой способ создания потока использовался.
FORKIO, FORKOS, FORKON
  bound-to-
OS
bound-to-
HEC
bound-to-
CPU
forkOS + - *
forkOn - + -qa
forkIO - - -
callback + - *
CAPIFFI
Можно импортировать значения:
Происходит проверка типов
Аннотации типов
foreign import capi "pi.h value pi" :: Double
const double pi = 3.14
#define pi 3.14
/tmp/ghc3535_0/ghc_3.c:8:1:
     error: too few arguments to function ‘test’
          HsInt ghczuwrapperZC0ZCmainZCMainZCtest(HsInt a1) {return test(a1);}
pi.h:7:5:
     note: declared here
          int test(int,int);
data {­# CTYPE "unistd.h" "useconds_t" #­} T = ...
генерируемый файлC
#define IN_STG_CODE 0
#include "Rts.h"
#include "Stg.h"
#ifdef __cplusplus
extern "C" {
#endif
#include <pi.h>
  HsInt ghczuwrapperZC0ZCmainZCMainZCtest(HsInt a1) {return test(a1);}
#include <pi.h>
  HsDouble ghczuwrapperZC1ZCmainZCMainZCpi2(void) {return pi2;}
#include <pi.h>
  HsDouble ghczuwrapperZC2ZCmainZCMainZCpi(void) {return pi;}
#ifdef __cplusplus
}
#endif
СИГНАЛЫ
#include <unistd.h>
int bogus() {
    return usleep(100000000);
}
</unistd.h>
forkIO $ do
    print =<< getCurrentTime
    ret <­ hsBogus
    print =<< getCurrentTime
    en <­ getErrNo
    print ret
    print (errnoToIOError "none" Nothing Nothing)
2015­12­06 06:47:57.817476 UTC
2015­12­06 06:47:57.827309 UTC
­1
test: interrupted (Interrupted system call)
АВТОМАТИЗАЦИЯ
c2hs
специфический синтаксис
не работает со сложными типами (GADT, Kinds)
периодически проявляются баги на различных
архитектурах
библиотеки автоматической генерации байндингов
inline-c
не аккуратная сборка
использование TH - проблемы с линкером
скорее всего, это никогда не понадобится, но хорошо
знать, что оно есть
C--
Один из промежуточных языков при компиляции GHC.
https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Cmm
https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/Cm
Но на нём можно писать код!
#include "Cmm.h"
#define W_TO_SHORT(x) %lobits32(x)
crc16u { /* R1 uint32_t input, R2 uint16_t data, R3 Int size */
    I32 c;
    if (R3 == 0) goto end;
    c = R1;
    R4 = 0;
    loop:
    c  = c + W_TO_SHORT(bits16[R2+R4]);
    R4 = R4 + 2;
    if (R4<R3) goto loop;
    if (R4>R3) {
        c = c + W_TO_SHORT(bits8[R2+R3] << 8);
    }
    R1 = TO_W_(c);
    end:
    jump %ENTRY_CODE(Sp(0));
cabal файл
c-sources: cbits/crc16-prim.cmm
haskell файл
 
{­# LANGUAGE GHCForeignImportPrim #­}
{­# LANGUAGE MagicHash #­}
{­# LANGUAGE UnliftedFFITypes #­}
foreign import prim "crc16u" p_crc16_u#
  :: Word# ­> Addr# ­> Word# ­> Word#
icsum16p :: Word32 ­> Addr# ­> Int ­> Word32
icsum16p !(W32# w#) a# !(I# l#) =
  W32# (narrow32Word#
         (p_crc16_u# w# a# (int2Word# l#)))
  ...
  let (mem,_ofs,len) = BS.toForeignPtr (BS.pack (x++x))
      mem' = unsafeForeignPtrToPtr mem
      (Ptr a#) = mem'
  in icsum16p 0 a# len
ЗАЧЕМ ЭТО НУЖНО?
[-] микроптимизации
[+] атомарные инструкции (lock-free алгоритмы)
[+] низкоуровневое взаимодействие с runtime
системой
ЕЩЁ ЧУТЬ-ЧУТЬ?
Сборка:
Берём C-код
собираем clang:
меняем calling convention:
собираем компилятором llvm
clang ­emit­llvm ­S input.c ­O input.llp
sed ­e 's/call void/call cc10 void/; s/define void/define cc10 void/;'
llc ­O3 ­relocation­model=static ­filetype=obj input.llp ­o ITCHv41.o
foreign import prim "function" c_function
   :: Addr# ­> Addr# ­> (# Int#, Word#, Word# #)
typedef void (*HsCall)(int64_t*, int64_t*, int64_t*
    , int64_t, int64_t, int64_t, int64_t
    , int64_t, int64_t, int64_t*
    , float, float, float, float, double, double);
extern void in_word_setprim1(
  int64_t* restrict baseReg, int64_t* restrict sp,
  int64_t* restrict hp, const uint8_t* restrict buffer, // R1
  int64_t length, // R2
  int64_t r3, int64_t r4, int64_t r5, int64_t r6,
  int64_t* restrict spLim,
  float f1, float f2, float f3, float f4, double d1, double d2)
{
  const HsCall fun = (HsCall)sp[0];
  const int64_t iUndef; const float fUndef; const double dUndef;
  return fun(...);

More Related Content

PPTX
200 open source проектов спустя: опыт статического анализа исходного кода
PPTX
SSL/TLS: история уязвимостей
PPTX
C++ CoreHard Autumn 2018. Кодогенерация C++ кроссплатформенно. Продолжение - ...
PPTX
Григорий Демченко, Асинхронность и неблокирующая синхронизация
PDF
[Defcon Russia #29] Михаил Клементьев - Обнаружение руткитов в GNU/Linux
PDF
хитрости выведения типов
ODP
Программирование Linux
200 open source проектов спустя: опыт статического анализа исходного кода
SSL/TLS: история уязвимостей
C++ CoreHard Autumn 2018. Кодогенерация C++ кроссплатформенно. Продолжение - ...
Григорий Демченко, Асинхронность и неблокирующая синхронизация
[Defcon Russia #29] Михаил Клементьев - Обнаружение руткитов в GNU/Linux
хитрости выведения типов
Программирование Linux

What's hot (20)

PDF
Подводные камни System.Security.Cryptography
ODP
Программирование Linux
PDF
Конкурентные ассоциативные контейнеры
PPTX
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
PPTX
Когда в C# не хватает C++ . Часть 3.
PPTX
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
PPTX
Когда в C# не хватает C++ . Часть 2.
PPTX
Когда в C# не хватает C++
PPTX
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
PPTX
Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...
PDF
DI в C++ тонкости и нюансы
PDF
C++ exceptions
ODP
Программирование Linux
PDF
Использование C++ для низкоуровневой платформозависимой разработки — Кирилл ...
PPTX
Павел Беликов, Как избежать ошибок, используя современный C++
PDF
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
PPTX
Как мы уменьшили количество ошибок в Unreal Engine с помощью статического ана...
PPTX
SWIG — cоздание мультиязыковых интерфейсов для C/C++ библиотек
PDF
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
PDF
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Подводные камни System.Security.Cryptography
Программирование Linux
Конкурентные ассоциативные контейнеры
Александр Тарасенко, Использование python для автоматизации отладки С/C++ код...
Когда в C# не хватает C++ . Часть 3.
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...
Когда в C# не хватает C++ . Часть 2.
Когда в C# не хватает C++
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...
Обобщенное программирование в C++ или как сделать свою жизнь проще через стра...
DI в C++ тонкости и нюансы
C++ exceptions
Программирование Linux
Использование C++ для низкоуровневой платформозависимой разработки — Кирилл ...
Павел Беликов, Как избежать ошибок, используя современный C++
Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения
Как мы уменьшили количество ошибок в Unreal Engine с помощью статического ана...
SWIG — cоздание мультиязыковых интерфейсов для C/C++ библиотек
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++
Особенности создания XS-модулей на языке C++. Владимир Тимофеев. Moscow.pm 4 ...
Ad

Viewers also liked (6)

PDF
Монады для барабанщиков. Антон Холомьёв
PDF
Cloud Haskell. Александр Вершилов
PDF
Краткий экскурс в системы типов или как избежать дезинтеграции. Денис Редозубов
PDF
Зависимые типы в GHC 8. Максим Талдыкин
PDF
Ленивые вычисления: плюсы и минусы. Денис Шевченко
PDF
Анонимные записи в Haskell. Никита Волков
Монады для барабанщиков. Антон Холомьёв
Cloud Haskell. Александр Вершилов
Краткий экскурс в системы типов или как избежать дезинтеграции. Денис Редозубов
Зависимые типы в GHC 8. Максим Талдыкин
Ленивые вычисления: плюсы и минусы. Денис Шевченко
Анонимные записи в Haskell. Никита Волков
Ad

Возможности и проблемы FFI в Haskell. Александр Вершилов