Visual Studioビルドのcallback対応
結局こういう形になってしまう。。
今(0.2.6に)有るcallback対応はypsilon由来で、"gasで書いたstubを呼ぶJMP命令だけをJITで生成する"ことで行っている。
- https://github.com/higepon/mosh/blob/83a40e6e6cf9ea82967fe988e540a2b3a1898cf5/src/FFIProcedures.cpp#L233
- mov2つとjmpだけがJITに生成される。
callbackとは、Cで書かれた関数からSchemeのclosureや継続を実行する仕組み。moshのcallbackでは、ある特定のclosureをApplyし、結果をSchemeオブジェクトからCオブジェクトに変換するC関数を動的に生成し、その関数ポインタをSchemeプログラムから取り扱えるようにしている。
このC言語関数とSchemeの境界に有るのがstubで、これをC言語で書くのは困難なので代わりに(移植性のない)アセンブラで書かれている。
VisualStudioには(当然)gasは無いので、そのままではVisualStudioにgasで書いたstubを持ってくることはできない。gasで書かれたstubをMASMに移植するか、gas構文をアセンブルできるyasmをビルドツールに加える必要がある。
(今回の対応はもっと低レベルで、gasでアセンブルしたstubをそのままデータとして打ち込んでいる。。)
これ以上ビルドツールを増やすのは選択肢でないし、流石にgas版とMASM版の両方をメンテナンスするのは面倒なので、この辺はSchemeに移行してしまった方がよさそう。
Stubの生成をScheme中で行いたい
要するに必要なのは、
の2つで、この2つが有ると、現状Visual Studioビルドでは対応できていないDirect-threadingのような問題も解決できる。
Schemeで書かれたアセンブラにはいくつか選択肢が有ったが、逆アセンブラが無かったりライセンス上の問題が有ったりで自作する方向。
(効率性のためには、アセンブラ自体を一種のマクロにして複数命令を同時にemitしなければならないので、逆アセンブラが無いとデバッグが悪夢になる。)
moshの実行可能メモリ管理フレームワークには既にExecutableMemoryクラスが有るが、これは"C++のnew演算子で確保したメモリを部分的に実行可能にする"という男らしいデザインになっており、アロケータによってはinsecure*1なので、これも追加しないと。。
(ExecutableMemoryもWindows版は毎回実行可能ページを確保するように実装を変更している。但し解放は未実装なので、現状ではffi-stubを解放してもメモリは解放されない。)
Stubはアセンブラでないといけないのか?
Stubをアセンブラで書かなくても良い限定的なケースが有る。
大抵のライブラリでは、callbackとして使う関数は次のようなプロトタイプ(mosh用語ではsignature)をしていて、"callbackに渡すユーザデータ"を指定できるようになっている。
int register_callback(void* context, void* (*callback)(void* arg, void* user_data), void* user_data);
よって、原理的には対応すべきcallback引数の数×user_dataとして使われる可能性のある引数位置の数ぶんの静的なstubさえ用意すればStubを動的に生成する必要は無い。。
もし、callbackを複数有効にすることが無いのなら、user_dataはグローバル変数にできるので、必要なstubの数を抑えることもできる。
もう少しクレバーな方法が有るような気がしているが、なかなか現実的な妥協点が見つからない。。*2
この手の工夫は、iPhoneやゲーム機のように、実行可能メモリへの書き込みをセキュリティの都合から禁止しているようなケースで必要になる。