週刊nmosh - 非同期FFI関数呼び出し / makeIntegerとC++オーバーロード
Win64対応で逆に32bitビルドが壊れたので修正に追われるでござるの巻。
async FFI call
pthreadでは非同期FFI関数呼び出しをサポートしている...といってもこれは単純に通常のFFI呼び出しをメッセージパッシングインターフェースにしただけ。つまり、通常のFFI呼び出しが:
- call(関数ポインタ, パラメタ, 戻り値バッファ)
の3点セットで行われるので、これをキューに詰めているだけとなる。
非同期FFI callのメリットは通常の同期FFI呼び出しと完全に等価であることで、プログラム自体の修正を伴わずに一部のFFI関数だけ非同期呼び出しに変えるといったことが出来る。
もちろん、単純に非同期呼び出しに変えるだけではあまり意味が無いが、chime( http://d.hatena.ne.jp/mjt/20120916/p1 )と組み合わせることで単一のbindingを同期/非同期の両呼び出しに対応させることができる。
...これはVMがマルチスレッド化されていれば、VM自体を複数のスレッドで走らせることで実現できることでもある。ただ、近代的なスレッディング様式ではVMの多重度とスレッドの多重度にギャップを生じるケースがある。典型的な例はスレッドプールAPIと言える:
pthread_workqueueはDarwinで採用されているpthread向けのスレッドプールAPIでlibpthread_workqueueはWin32や他のpthreadで動作するポータブルなライブラリになっている。pthread workqueueインターフェースは、単純に関数ポインタをenqueueできるだけのインターフェースとなっており、キュー毎にVMコンテキストを作る隙が無い。
Vista以降のWin32にはもっと複雑なThread pool APIが有る。
Thread pool APIはIOCPや他の同期オブジェクトと統合されているのでかなり強力だが、APIの移植性を保ったままマッピングするのが地味に難しい。
C++オーバーロードの難しさ
コードではmakeInteger関数がC++コード中でscheme整数オブジェクトを作るのに使われている。
問題は、makeIntegerが様々なオブジェクトに対して使われているので、移植性を持つためには全部のパターンが必要なこと。
これだけのバリエーションが有る:
static Object makeInteger(int n) { if (Fixnum::canFit(n)) { return Object::makeFixnum(n); } else { return Object::makeBignum(n); } } static Object makeInteger(unsigned int n) { ... } // FIXME: We don't need (u)intptr_t makeInteger for // sizeof(int) == sizeof(uintptr_t) archs #if MOSH_BIGNUM_SIZEOF_INTPTR_T != 4 static Object makeInteger(intptr_t n) { ... } static Object makeInteger(uintptr_t n) { ... } #else // ... But we need long int variants static Object makeInteger(long int n) { ... } static Object makeInteger(unsigned long int n) { ... } #endif // MOSH_BIGNUM_SIZEOF_INTPTR_T != 4
なんてこった。(...の部分は中身が同じなので省略)
if - endifで切っているのは、uintptr_tとunsigned longのオーバーロードが共存できるかどうかが環境によって異なるため(!)。uintptr_tとunsigned longって違う型なんじゃないかと思うけど、環境によってはuintptr_tはunsigned longのtypedefなので。。
原理的にはsize_tなどまだまだ多くのバリアントで必要になってしまう。簡単には、(u)intptr_tとfixedintだけ持ち、他の型ではCスタイルまたはstatic_castなキャストを使うことで改善できるが、如何せん使用箇所が多いので。。