

chibi-schemeとnmoshで同じライブラリ(yuniffi yunistub graphics SDL2-constants)を読み込み、SDL2の定数SDL_TRUEとSDL_FALSEをクエリしたところ。

というわけで、nmosh / larceny / guile / sagittarius / racket / chibi-schemeの各種R6RS/R7RSでFFIする準備が整った。yuniはR6RS用のR7RSライブラリも含んでいるので、FFIを使ったスクリプトがこれらの処理系で使い回せる環境ができたと言える。(dynamic bindingなFFIと違ってDLLをコンパイルする手間は有るけど)

後はbytevectorに構造体を構築するとか、S式とbytevectorの相互変換といった(処理系固有でない)Scheme側ライブラリの作業なのでもうちょっと良いペースで進むはず。。

chibi-scheme以外はR6RSなので、bytevectorのword巾アクセスはR6RSのものをそのまま使えば良いが、chibi-schemeでは符号付きアクセスは一切用意されて無いのでほぼ全部自前で実装する必要が有った。。

Gaucheは今のところモジュールのビルドとCMakeビルドをうまく統合する方法を思いついていないので保留、VicareはFFI呼び出しを行うと何故か落ちるのでデバッグ中。

pointerオブジェクト C APIによっては構造体をAPI側で確保するケースも有るため、yuniFFIのランタイムはunmanagedな領域 - つまり、schemeヒープの外部 - を直接アクセスできるようにする必要がある。

これが意外と曲者で、処理系の個性が出るところとなっている。 nmosh / sagittarius / Guile / Vicare nmosh等はpointerオブジェクトを直接FFIライブラリからexportしている。integer→pointerのような操作も行え、pointerにオフセットを加えてデータのpeek/pokeを行うこともできる。

yuniの互換レイヤは基本的にnmosh基準で作られているので、一番自然にフィットするのもこれらの処理系ということになる。 Larceny - https://github.com/okuoku/yuni/blob/b18eaaf8ffddd572e83bd0c5b64ba89d03c9d176/lib-compat/larceny-yuni/compat/ffi/primitives.sls Larcenyのpointerオブジェクトは単なるrecordで、peek-bytes/poke-bytesの低レベルアクセス手続きはアドレスとしてintegerを取る。

というわけで、pointer→integerが必要になるが、これはライブラリとしては存在しないのでrecordに直接アクセスする形で実装している。 ( define ptr? ( record-predicate void*-rt )) ( define ( integer->ptr x ) (( record-constructor void*-rt ) x )) ( define ( ptr->integer x ) (( record-accessor void*-rt ' ptr ) x )) pointerがわざわざrecordとして提供されているのは、LarcenyのFFI機構が型チェックやキャストを(Schemeレベルで)実装しているため。

Larcenyは何故かDLLの読み取り失敗をtrapする方法を提供していない。何故..? Racket - https://github.com/okuoku/yuni/blob/b18eaaf8ffddd572e83bd0c5b64ba89d03c9d176/lib-compat/racket-yuni/compat/ffi/primitives.sls Racketはcpointer?プレディケートを持っているが、bytevectorや偽値(#f)に暗黙の変換を提供しているためこれらを外しておく必要がある。 ( define ( ptr? x ) ( and ( cpointer? x ) ( not ( bytevector? x )) ( not ( eq? #f x )))) ( define ( integer->ptr x ) ( ptr-add #f x )) Racketのpointerオブジェクトは直接数値から変換する方法が提供されていない。しかし、pointerに関連付けたoffsetを持つことができるため、NULL(Racketでは#fで表現される)にoffsetを加えることで間接的にinteger→pointerを実装できる。

chibi-schemeでの互換レイヤ実装 Scheme側: https://github.com/okuoku/yuni/blob/b18eaaf8ffddd572e83bd0c5b64ba89d03c9d176/lib-compat/chibi-yuni/compat/ffi/primitives.sls

拡張モジュール: https://github.com/okuoku/yuni/blob/b18eaaf8ffddd572e83bd0c5b64ba89d03c9d176/yunistub/chibi/yuniffi_stub.c

dlopen/dlsym stub (C): https://github.com/okuoku/yuni/blob/b18eaaf8ffddd572e83bd0c5b64ba89d03c9d176/yunistub/common/bootstrap.inc.c

dlopen/dlsym stub (Scheme): https://github.com/okuoku/yuni/blob/b18eaaf8ffddd572e83bd0c5b64ba89d03c9d176/lib/yuni/ffi/runtime/bootstraploader.sls 最も混沌としているのはchibi-schemeで、基本的には何もかも拡張モジュールに渡して実装している。

chibi-schemeのFFI機構はcpointer型を提供しているが、cpointer型のプレディケートは提供されていない。というわけで拡張モジュールに実装している。またinteger→pointerも簡単には実装できないのでC側に置いた。

cpointerを介したメモリの読み書きもFFI関数として実装しているが、FFI関数側でオブジェクトを確保する良い方法が無いので、intで表現できない64bit読み書きはScheme側で実装している。このため、ランタイムはlittle endianを仮定するしかなく、Emscriptenとの互換性が犠牲になる。ちなみに、POSIXはintが32bit以上あることは保証しているため、32bit以下はCインターフェースで直接実装できる。

dlopen/dlsymも直接は公開されていないため自前のstubが必要になる。これはGaucheでも必要になるので、NCCCの簡単なwrapperを書いて共通化できるように配慮した(dlopen/dlsym stub)。