定数テーブルの取得 - nmosh / larceny / guile / sagittarius / racket / chibi-scheme


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等はpointerオブジェクトを直接FFIライブラリからexportしている。integer→pointerのような操作も行え、pointerにオフセットを加えてデータのpeek/pokeを行うこともできる。
yuniの互換レイヤは基本的にnmosh基準で作られているので、一番自然にフィットするのもこれらの処理系ということになる。

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はcpointer?プレディケートを持っているが、bytevectorや偽値(#f)に暗黙の変換を提供しているためこれらを外しておく必要がある。

(define (ptr? x) (and (cpointer? x)
                      ;; In Racket, bytevector(byte strings) and
                      ;; #f(treated as NULL) are also cpointer.
                      ;; We exclude them here for now.
                      (not (bytevector? x))
                      (not (eq? #f x))))
(define (integer->ptr x)
  ;; Add an integer offset against NULL pointer
  (ptr-add #f x))

Racketのpointerオブジェクトは直接数値から変換する方法が提供されていない。しかし、pointerに関連付けたoffsetを持つことができるため、NULL(Racketでは#fで表現される)にoffsetを加えることで間接的にinteger→pointerを実装できる。

chibi-schemeでの互換レイヤ実装

最も混沌としているのはchibi-schemeで、基本的には何もかも拡張モジュールに渡して実装している。
chibi-schemeFFI機構は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)。

次の一手

次は、

  • 構造体→S式 変換
  • S式→構造体 変換
  • 数値のシンボル化
  • シンボルの数値化

の4点。ひとまず、補完のことは忘れて、SDL2のイベントを受信するのに十分な部分の実装を目指す。もっとも、SDL2のイベントはevent typeを見て適切なunionを選んでデコードする必要が有るため、実装自体はそれなりに作業が有る。