FFIの分類 / RPCとFFIの類似点

nmoshのFFI(pffi)は今のところ非常に原始的なC APIしかサポートしていない。

FFIの分類

FFIインターフェースの分類を考えてみた:

  • Type 0 : 組み込み関数、FFIと通常呼ばれない物

nmoshは比較的リッチなセットのPOSIX wrapperを提供している。(元々のmoshに由来する)
例えば、(fork)と書くとPOSIXなfork()を呼ぶことができる。
このバインディングは静的に記述されていて、Schemeスクリプトによって拡張することは出来ない。nmoshでこのタイプの手続きを作るには、VMのCProcedureオブジェクトを生成する必要がある。この生成方法はScheme APIとしては用意されていない。
Schemeオブジェクトを割り当てると、ヒープ実装がimplicitにmmap()によってOSからメモリを確保することがある。これは間接的に外部の関数を呼んでいる。
いわゆる言語エクステンションはここに分類できる。PerlのXSや、HyperCard/HyperTalkのXTND/XFCN等。

  • Type 1 : Static binding + 埋め込み

呼び出し規約の実装を伴わないFFI。ホスト言語とのブリッジがC言語コードとして存在し、コンパイルする必要がある。これはnmoshに特徴的なFFIで、特定のAPI様式に従ったC関数のみを限定的にサポートする。たとえば引数の型等に制限が有る。
Type 1の存在意義は、"実行コードの動的生成が禁止されている環境"を良くサポートするためにある。つまり、任意引数のコールバック等をDynamicに生成することは出来無いので、この部分を制約として予めAPIに織り込んでいる。
Gambitのようなコンパイラ型の実装では、このタイプのFFIを実装している事が多い。つまり、Cに対する呼び出しI/Fを宣言し、コンパイラに適切なbridgeルーチンを出力させる。( http://gambitscheme.org/wiki/index.php/Using_Gambit_with_External_Libraries)

  • Type 2 : Dynamic binding(通常のFFI)

ホスト言語のAPIで、C APIの"シグネチャ"を記述し、呼び出し可能な関数を生成する。JNIやlibffiやdyncallのようなライブラリでサポートできるFFIはここに分類される。
Type 1に比べて、ABIを手で実装しなければならないので、アーキテクチャ独立に書くことが出来ないという課題がある。(Type 1の場合は、呼び出し規約はCコンパイラが面倒を見る)
また、スクリプトが任意の手続きを呼び出す可能性があるため、ABI互換性も動的に解決する必要がある。(過去のライブラリには存在しないC APIを使用していても、実際に呼び出されるまでわからない)
Type 2がこの業界ではもっとも普及している気がする。Type 2 FFIは地味に実装が難しい。可変長引数やC++呼び出し規約等の問題を解決しないといけない。

  • Type 3 : リフレクション、ブリッジ

ターゲット言語がリフレクションやintrospectionをサポートしているなら、Type 2 FFIのためのシグネチャ記述は必要なくなる。
COMにおけるMSIDLや、OS Xのbridge support、GObjectのGObject introspectionなど最近の大概のデスクトップ環境はこの手のFFI機能を提供している。
Androidでは、例えばXamarinのJar2xml( https://github.com/xamarin/jar2xml )が有る。

RPCとFFIの類似点

良いFFI APIはRPCと同等の記述力が必要に思える。つまり、あるC APIをRPCとしてwrapできる程度の記述力があれば、そのwrapperはFFIのwrapperとしても使用できる公算が高いと言えるのではないか。
例えば、ThriftのようなRPCプロトコルは:

  • APIの入出力の記述
  • マーシャリングのルール
  • enum

を記述するための方法をIDLとして提供していて、これはFFIでも同様に必要となる情報と言える。つまり、RPCとFFIのためのIDLは同じものを使えるはずで、これによって新しいアプリケーションやオプティマイザを作ることができるかもしれない。
OpenGLは元々ネットワーク透過なプロトコルとされることも想定されていたので、RPCにしやすいAPIとなっている。このため、FFIもこの性質を利用してより"薄い"FFI層によってwrapできる可能性がある。