2008-10-05 [長年日記]
λ. 第8回 RHGの逆襲
以下個人的なメモ。シグナル周りとスタックの構造に関する辺りとかは聞いてても良く分からなかったので、特にその辺りは変なこと書いてるかも。
リリース計画等
- 1.9.0-5
- 9/25予定だったのが10/3に
- make test 通らない www
- ちょっと残念
- リリースだって言っているのに、make test が通らないコミットをした人が……
- この期に及んで仕様を変えたいという意見が……ことによると1.9.1は1月にずれ込むかも
- 熱心な人は試してみてっ
- 昨日、幾つかのプラットフォームのサポートのコードを削った
- 削ったもの
- VMS : がんばると言っている人はいるので復活するかも
- human68k
- DJGPP (MS-DOS)
- WinCE : 力作だったので残念
- Classic MacOS
- 削らなかったもの
- OS/2
- bcc32
- BeOS
- 削ったもの
- 1.9.1 preview 1 (10/25予定)
- 仕様フリーズ、この仕様で完成度を高めて1.9.1リリース
- 何か言いたい人はここまでに
- ABI fix
- 大きいのは構造体のレイアウト
- 1.9.x まで保たれるかは不明
- 当初は保つ予定だったが、どう考えても無理ということがわかってきた。MVMとか
- MVMはRubyとは別物にするかも (sasada)
- やっぱり欲しい (yugui)
- 1.9.1 preview 2 (11/25予定)
- 1.9.1 (12/20予定)
install directory
- 1.8だと/usr/lib/ruby/1.8だった
- 今は /usr/lib/ruby/1.9.0
- 将来も .../1.9.1 に入れたほうがいいか?
- 1.9.1という名前で勝手に既成事実化して文句が出なければ良いかも。
- gtkだとABIが保たれるうちは古い名前で。
- シンボリックリンクを貼る? 両方見る?
- バージョン情報を拡張ライブラリに埋め込んで判断?
- dlsymとかでシンボルをみて判断?
- ABIが変わったら、そもそも拡張ライブラリからlibrubyのシンボルの参照を解決できなくてdlopen相当が出来ない可能性があるので、ロードしてdlsymとかで調べるというのは今の枠組みだと良くないのでは?
instruction sequence
- よくバイトコードといっているやつ
- 他と一緒なのはいやだったのでバイトコードにはしなかった
- バイトコードは通常一命令1バイトで出来るだけ小さく詰め込もうとしてるが、YARVでは1バイトにこだわらずワード(ポインタのサイズ)に。
バイトコード出力
やらなくちゃいけないこと:
- require/load出来るようにする
- 開始時に指定できるようにする
問題:
- 互換性
- まだまだバイトコードを変えたいと思っている
- バージョン限定で構わない (yugui)
- 安全性
- 今のyarvにはverifierがないので、バイトコードをいじると簡単にSEGV。JAVAだとverifierがあるので大丈夫。
- 責任をどう転嫁するか?
- verifierを作る
- 大体わかってるけど、まじめに考えてないので自信がない。
- ちゃんと考えて入れようとしたら1.9のリリースは遅れるだろう。
- 自己責任
- verifierを作る
- c.f. 遠藤さんの作ったverifier
いれちゃったら?
- ビジネスのニーズとしては、ソースを隠した気分になりたいというのがある。
- 出力する仕組みは入ってる
- ロードする側は作ったんだけど、Rubyから使う手段は削ってある (iseq_load)
- これを使って御社のエンタープライズソリューション(ry
- evil-ruby : yuguiさんが作っている邪悪なruby?
本当に難読化可能?
- 難読化は夢で、夢を買いたい人がいる。お金はあるけど、頭の悪い人。
- 契約で縛るときに意味がある。偶然見たりgrepで引っかかったりしないことが重要。なぜわざわざ見たのか? と主張できる。
ファイル構成とか
- ヘッダ /include/ruby/*
- インストールされる。これまで ruby.hだったのが ruby/ruby.h になった。node.h は ruby/1.9.1/node.h にインストールするというのもありでは?
- パーサとか : parse.y, lex.c.*, …
- コンパイラ : compile.c, iseq.c, …
- VM
- vm.c
- vm_*.c/h
- vm_core.h
- このなかに大事なVMの構造体とかがはいってる。これを見たら大体VMの構造がわかる。coreというファイル名はやめろという苦情あり。(rm *core* とかするひと)
- vm_core.hは宇宙が滅んでもインストールされないのか? ⇒ 1.9.1というディレクトリがあったら入れてもいいと思う。1.9.1内では変えないだろうし。
- *.def が増えている ==コード生成⇒ *.inc
- insns.def
- defsというディレクトリを作ったのでそっちに移しませんか?(yugui) ⇒ grep出来ないので嫌(sasada)
- いろいろ : main.c, ruby.c
- eval.c
- 1.8では1万行くらいあったのをeval*に分割、vm*, proc.c に移動などして、骨抜きにした。
- 組み込みライブラリとか: array.c, hash.c, string.c, encoding.c, transcode.c, complex.c, rational.c, …
Pythonだともっと厳密に分けられている
- 分けたほうがいいとも思うけど、signal.cとかどこ? というのは悩ましい。
- ディレクトリ間で相互依存してると何かまずいの? (sakai) ⇒ 気分とか(sasada)
prelude.rb
- 名前はhaskell由来
- 10階にいる髪の長い人が悪用して……
- 先にバイトコードorCにコンパイルしておきたい。そうすれば速くできるかなぁ。
VM (1)
- VMの教科書はコンパイラとバーチャルマシン (IT Text)(今城 哲二/岩沢 京子/千葉 雄司/布広 永示)という本はある。
例外処理をポータブルにやるにはどうやるか?
- setjmp/longjmp法
- 二返戻値法
- テーブル引き (JVMの方法)
YARVではsetjm;/longjmpとテーブル引きを併用。rubyで完結する場合にはテーブル引き。Cの部分をはさむ場合にはsetjmp/longjmpを使う。
- direct threaded code : 省略。詳しく読みたい人は YARV Maniacs 【第 3 回】 命令ディスパッチの高速化 等を参照。
- 命令融合
- 9? 年にこうやったらいいよ、という提案があったのでそれに乗っかった。
- opt_insn_unif.def に書いておくと融合。
- こういうことをやりたかったのでdefからコード生成する変換機を作った。
- オペランド融合
- opt_operand.def
- 命令融合とオペランド融合とで組み合わせが爆発
- 今のYARVでは全部出力 七十何命令から命令から二百何十命令になる。コンパイルが遅くなる。コードが大きくなる。
- 今は両方ともオフにしている (vm_opts.hにオプションがあり)
- この仕組みがあるので、YARVではgeneralな命令しか用意していない
- 近々に早くしたい人はこれを使うといいかも。JITとか入ったら意味無くなるけど。
- コンパイルオプションで制御できる。
- オフにした状態でも、1.8と1.9で active support のパース・コンパイルにかかる時間が、 違いがわかるぐらい違う
- VM屋さんにとっては拘っているところ
- 命令融合があると、命令の番号が変わっちゃう
- 処理の流れ
- オペランド融合は生成しながら変換
- 命令融合は生成後に探索して変換
- notはピープホール変換しちゃまずい? メソッド呼び出しににコンパイルされているので大丈夫っぽい。
VM (2)
- vm_opts.h でオプションを設定。SUPPORT_JOKEでgotoが使えるようになる。
- builtin_expect
- 分岐予測のヒント
- VCにはたぶんない ... 残念
- エラー処理とかに使う
- if (LIKELY(x)) { ... } else { ... }
- Intel系では最初にくる側の分岐をデフォルトで分岐予測するので、エラー処理が後にくる効かない
- エラー処理が最初にくるときに if (UNLIKELY(x)) { ... } else { ... } にすると効く
- intel compiler にしたら速くならないの?
- なる……けど、コンパイル出来なかった。
- 二段階のコンパイルがintel compiler の想定している使い方でなくて、相性が悪いっぽい。
- instruction sequence の種類
- top
- method (def ... end)
- class (class ... end)
- block ({ ... })
- メソッドの引数とか、topだと意味無いけど、どの種類でも持っている。
- 引数情報
- 1.9 から post argument を入れる話が出てきたりして大変だった。
- メソッドが呼ばれるごとにarg_*をチェック。Rubyが遅いひとつの要因。
- stack_max
- stack size は argument は含まれない?
- pushの数を数えて、popは数えてないので、大き目の値がはいってる
- block inlining を試みた残骸
- メソッドの定義とブロックの定義を持ってきて、というのがひとつの方法だけど、rubyだと意味を保ったままインライン化するのが難しい(面倒)。evalとかbindingとか。breakしたときとか。
- bindingはあまり出てこないので、binding を予約語にして検出する手はある。
- 再定義されたときにフラッシュしなくちゃいけない。
- スタックトレースでメソッドが現れなくなる。けど、sendの最適化が入っていて、obj.send(:foo) で、sendがスタックとレースに出なくなったという前例はあるので、許されやすいかも。
VM (3)
- struct rb_vm_struct
- GVLを明示的に手放す方法 BLOCKING_REGION
- そういうスレッドをとめるには?
- pythonではサポートされていない
- yarvでは待ちを解除するときに呼んでほしい関数を指定して貰ってそれを呼ぶ。たとえば read だったら signal 。
- signal で安全にとめるのはすごく難しい。ロックを手放してからreadに入る前にシグナルが飛んでくると取りこぼしてしまう。readの前にflag sense する方法は、flag sense してから read する前に 飛んでくる可能性があるので何も解決しない。結局、解除されるまで何回も送る方法をとっている。髪の毛の長い人に「これは怖い」と言われて実装。
- windows だったらシグナルオブジェクトと一緒に指定できて素晴らしいけどstdioには使えない(?)。今の1.9ではusaさんがそれを使うようにしてくれて、止まるようになった。
- VMはスレッドの集合を持っている
- src_encoding_index : これ知らなかった
- control frame いわゆる method frame : これを指すのがcfp
- 割り込みが実行される可能性のある幾つかのパターン
- Thread#kill
- シグナルハンドラ (シグナルはメインスレッドに届く)
- Cメソッドを実行中に割り込みが発生すると不味いことになるので、割り込みしていいかとかを interrupt_flag とかで管理。スレッド移す際にGVLを手放す前に...
- 1.9からは set_trace_func をスレッドローカルに出来るように拡張されてる。
- 1.8ではCHECK_INTSでシグナルをチェックしていた。1.9ではRUBY_VM_CHECK_INTSと名前が少し長くなった。UNLIKELYがこのマクロのなかにはいっている。スレッド切り替えも契機にもなっている。
- set timer interrupt
- rubyのスレッドの他に timer thread がいて、定期的に polling、時間がきたら「お前ちょっと自重しろよ」と...
- Thread#kill は pthread_kill で実装している
- 直接送る?
- SIGVTALRMでpthread_kill
- null signal handler を登録しておいて……
- read とかは EINTER で帰る、帰ったところの CHECK_INTS でチェック
- Procに必要なデータをスタックからヒープに移す部分など、なんで今動いてるのか分からない。
- ここらへんのコードはあまり見ないほうが良い。
- デバッグしてもらえると助かる。
- 説明するのが面倒でD論とかでも詳細は省いている。(こういう構造体を使っている。以上。)
- 1.8の方がひどいと思うので、それよりはまし。
- なんでスタックをひとつにまとめないのか?
- まとめようとして挫折。
- メモリのローカリティ的には良くないので頭痛い。
- 工夫: 二つのスタックを両側から伸ばしてる
その他
- パーサーのどのあたりがVMと相容れず変更する必要があったのか
- MVMはシグナルとか大変
- vm.cとvm_insnhelper.[ch]はほんとに使い分けられているの?
- スコープの管理
- レキシカルという原則があるのに、それを破壊する悪魔module_eval
- レキシカルなのにClass.dup すると、違うクラスがはいってるってどうよ。
今後の予定
二次会
- 形式的意味論 ⇔ 民主的意味論
- FFTによる多倍長整数の乗算