yarv2llvmの拡張のドキュメントをここに書く
AOベンチの高速化は結局手詰まりになってしまいました。今は、数ヶ月ほったらかしにしていたThreadのランタイムを再開しています。ジャイアントロックの無いThreadのランタイムと排他制御処理の自動挿入を実現してCPUをこき使えるといいなと思っています。
Thread周りのランタイムは結構低レベルの処理が満載でRubyではなかなか書けそうもないです。そこで、節操無く言語仕様を拡張して無理やり書こうとしています。この言語仕様の拡張はどこにもドキュメントが(ソースを除いて)無いので、多分すぐ忘れてしまうと思います。下手すると、ドキュメントを書いても書いたことを忘れてしまうので、Google先生には覚えておいてもらおうという魂胆です。
Threadのランタイム(runtime/thread.rb)はこんな感じのコードです。
# # Runtime Library # Thread class --- for create native-thread # # This file uses Unsafe objects # module LLVM::Runtime YARV2LLVM::define_macro :define_thread_structs do |system| if system[0][0].type.constant == :WIN32 then native_thread_t = "VALUE" rb_thread_lock_t = "VALUE" else native_thread_t = "P_VALUE, VALUE" rb_thread_lock_t = "VALUE" end <<`EOS` VALUE = RubyHelpers::VALUE LONG = LLVM::Type::Int32Ty VOID = LLVM::Type::VoidTy P_VALUE = LLVM::pointer(VALUE) 中略 RB_THREAD_T = LLVM::struct [ VALUE, # self RB_VM_T, # VM 中略 [LONG, :state], # state RB_BLOCK_T, # passed_block VALUE, # top_self VALUE, # top_wrapper 中略 #{native_thread_t}, # native_thread_data P_VALUE, # blocking_region_buffer VALUE, # thgroup VALUE, # value VALUE, # errinfo VALUE, # thrown_errinfo LONG, # exec_signal LONG, # interrupt_flag #{rb_thread_lock_t}, # interrupt_lock VALUE, # unblock_func VALUE, # unblock_arg VALUE, # locking_mutex 中略 ] EOS end define_thread_structs(:LINUX) def y2l_create_thread type = LLVM::function(VALUE, [VALUE]) YARV2LLVM::LLVMLIB::define_external_function(:rb_thread_alloc, 'rb_thread_alloc', type) thval = rb_thread_alloc(Thread) thval2 = YARV2LLVM::LLVMLIB::unsafe(thval, RDATA) th = YARV2LLVM::LLVMLIB::unsafe(thval2[4], RB_THREAD_T) st = th.address_of :state st0 = th[:state] st[0] thval end end
ThreadのランタイムはOS等によって異なる構造体等を用いる必要があります。つまり、条件コンパイルを行わないといけないのですが、これをマクロによって行います。
YARV2LLVM::define_macro :define_thread_structs do |system|
引数(system)にアーキテクチャを入れます。RUBY_PLATFORMあたりがいいのかなと思います。今回は適当です。
if system[0][0].type.constant == :WIN32 then native_thread_t = "VALUE" rb_thread_lock_t = "VALUE" else native_thread_t = "P_VALUE, VALUE" rb_thread_lock_t = "VALUE" end
systemはいろんなメタ情報を含めて渡ってきますので、値を取り出すには、system[0][0].type.constantとします。この辺はあんまりな仕様なので変わるかもしれない、というかいい案があれば変えます。
アーキテクチャによって構造体を変えたり出来ます。プログラムは文字列で記述しておきます。
その後、テンプレート中に#{native_thread_t}という形で埋め込みます。
<<`EOS` VALUE = RubyHelpers::VALUE LONG = LLVM::Type::Int32Ty VOID = LLVM::Type::VoidTy P_VALUE = LLVM::pointer(VALUE) RB_THREAD_T = LLVM::struct [ 中略 #{native_thread_t}, # native_thread_data P_VALUE, # blocking_region_buffer
yarv2llvmで構造体を定義するには、LLVM::structにメンバーの配列を渡します。メンバーはLLVMの型オブジェクトか、[型オブジェクト, シンボル]の配列で、配列で渡した場合、ディリファレンス時にシンボルでメンバーを指定できます。実装を簡単にするためLLVM::structが実際に生成するのは、構造体へのポインタになります。
また、LLVM::pointerを使うと、ポインターを定義できます。
ランタイムを書くには、Cの関数を呼んだりCの構造体を扱ったりする必要があります。
Cの関数を呼ぶには
type = LLVM::function(VALUE, [VALUE]) YARV2LLVM::LLVMLIB::define_external_function(:rb_thread_alloc, 'rb_thread_alloc', type) thval = rb_thread_alloc(Thread)
とします。ここで、YARV2LLVM::LLVMLIB::define_external_functionという長いメソッドがキーになります。これは、次のような3つの引数をとります。
- yarv2llvmのメソッドの名前
- Cの関数名
- 関数の型
こうすると、あたかもCの関数をyarv2llvmのメソッドのように扱うことができます。
この場合はVALUE型というRubyレベルで扱えるデータが返ってくるのでいいのですが、関数によってはGCがコアを吐いちゃうような値を返す場合があります。ランタイムを作るにはこのような関数も呼ばなければなりません。
そのような目的の為、Unsafeというオブジェクトを用意しています。UnsafeオブジェクトはCのポインタや構造体をWrapしたもので、メソッドしてデリファレンスと代入しか用意していません。静的に他のオブジェクトと区別できますので、インスタンス変数とかGCがコアを吐いちゃう恐れのあるところには入らないようにコンパイル時にチェックします。
thval = rb_thread_alloc(Thread) thval2 = YARV2LLVM::LLVMLIB::unsafe(thval, RDATA) th = YARV2LLVM::LLVMLIB::unsafe(thval2[4], RB_THREAD_T) st = th.address_of :state st0 = th[:state] st[0] thval
これで、thval2にはthvalの値でRDATA構造体を型としたUnsafeオブジェクトが入ります。例えば、@foo = thval2とかやると、エラーが起きます。YARV2LLVM::LLVMLIB::unsafeはちょうどCのキャスト演算子みたいな動きをします。
Unsafeオブジェクトは次の3つのメソッドを持ちます
- [](番号またはシンボル) 構造体ならばメンバーの値を返す。ポインタならばオフセットをつけてディリファレンスする。
- =(番号またはシンボル, 値) の値を書き換える
- address_of(番号またはシンボル) 構造体の場合、メンバーのアドレスを返す
これらの結果は、Unsafeオブジェクトですが結果をYARV2LLVM::LLVMLIB::unsafeをつかってキャストすることもできます。また、この結果がVALUE型で安全という保障があれば、YARV2LLVM::LLVMLIB::safeメソッドを使ってインスタンス変数等に代入できる普通のオブジェクトに変換することもできます。
いずれにしても、どれもこれも非常に危ないので失敗すると即コア吐きます。コンパイラの挙動も怪しいので生成されたLLVM IRとにらめっこして正しいコードかチェックする必要があります。