RHGの逆襲 第11回

ラクリナックスさんにて。よしおかさんいつも会場の提供ありがとうございます。

http://qwik.jp/rhg-strikes-back/94.html

第11回のテーマは「VMの命令実行とスタック」。
さわださんの発表資料はこちらで公開されています。
今回はわたしも stack caching をネタにして発表しました。発表資料はちょっと手直ししてPDF化したのをとりあえずここに置いておきます。

第11回はRuby VMの基本的な動作についてに留めて、いくつかややこしいところは次回以降に残すということに。

  • さわださんがVMの基本的な動作について発表。資料が公開されているからそちらを見たほうがいいです。
    • rb_thread_t::iseq_encoded に配列化されたワードコード
    • rb_thread_t::stack - 値スタック
    • rb_thread_t::cfp - コントロールフレームスタック
    • rb_control_frame_t - pc, sp, bp 等のレジスタlfp, dfp の変数テーブルのポインタ
    • コールツリーは前回のコンパイルの続きから vm_iseq_eval_main -> vm_set_main_stack -> vm_set_eval_stack -> rb_vm_set_finish_end -> vm_push_frame(このへんまで前処理) -> vm_exec(例外処理はここに絡む?) -> vm_exec_core (ここの中がマクロで高度に抽象化されたVMの命令ディスパッチ部分)
    • あとは個別の命令後の実行部分(vm.inc)があるだけ。
    • VMレジスタ
      • cfp はフレームスタックをさす
      • pc は iseq_encoded の中をさす
      • sp, bp, lfp, dfp は値スタックの中をさす
        • lfp, dfp の領域(ローカル変数、ブロック変数)も値スタック上に確保されている。
    • 変数アクセス(ローカル変数、ブロック変数、特殊変数、インスタンス変数、クラス変数、定数、グローバル変数とそれぞれ命令語が異なる)
    • メソッド呼び出し(CALL_METHODマクロ)
      • Cで実装されているメソッドの場合はそのまま呼ばれる
      • VM で実行される(iseqで定義されているメソッド)の場合はちょっと違う。コントロールフレームが積まれて、pcが変更されてそこにジャンプする。 -> leave で抜けてきたらスタックトップにメソッドの戻り値が乗ってるというわけ。
    • 例外処理(さらっと)
      • 例外発生は raise メソッド(rb_f_raise)で行なわれている。JUMP_TAG(longjmp)。
      • TAG_RETURN などはもう不要? -> C で実装されたブロックを受けとるメソッドなど、VMとC実装が交互になってる時に関係あるかも
      • rescue 節があると catch table にエントリが作られる
        • 捕捉範囲(pcの範囲)と処理内容iseqなどが含まれる
        • pc が捕捉範囲内だったらコントロールフレームを積んでジャンプ(メソッド呼び出しとだいたい同じ)
      • vm_throw関数とTHROW_EXCEPTIONマクロ
  • Yugui さんが threaded code などの概観とマクロの展開結果等についての発表
    • threaded code については YARV Maniacs 第3回を読む
    • マクロが頑張ってるのはコードを読む。
    • 現在の Ruby VM は安直な while+switch の実装と、call threaded code, token threaded code, direct threaded code の3種類の threaded code のスイッチがある。多分最近の gcc を使っていれば direct threaded code がデフォルトになる。
  • わたしの発表。Ruby VM の Stack Caching Optimization オプションについて
    • Ruby VM (現在の実装)では2つのレジスタをスタックトップのキャッシュ用に用意している。
    • キャッシュ状態を XX, AX, BX, AB, BA の5種類にして、コンパイル時にこの状態遷移を管理している(多分 Static Stack Caching というやつだと思う)
    • なのでレジスタの状態毎に命令語(INSN)を派生させている
    • pop, swap 命令は省略できることがある -> iseq のサイズが小さくなる
    • 少し手を入れないとコンパイルできない
    • 実はまだちゃんとビルドできてない。miniruby のコンパイルはできるけどスクリプトの実行で失敗してしまう。
    • 以下後日調べた結果
      • ビルドに失敗していたのは例外処理時のスタックポインタが不正になってしまっていたため。[ruby-dev:38035]
      • でもまだ失敗する。これは branchif, branchunless 等でジャンプ先のレジスタ状態が一致していない場合があるため。発表資料の「疑問点」の「ジャンプ先の状態が同じことはどうやって保証してるのでしょう?」は「(現状の実装だと)保証されてない(から動かない)」が答えだった。<- いまここ

次回は 3/14(土)予定。テーマは今回先延ばしにした難しいところ3つくらいをできるだけ。

  • 例外処理をもう少し詳しく
  • ブロック呼び出し(おそらく control frame の各レジスタがどんなふうになるかってあたりがややこしいと思います)
  • 定数探索に関係する cref の積み方

たぶん残りはこの3つくらいだったと思います。
わたしは例外処理と next, break 等の制御文のあたりを重点的に読んでおこうかと思います。

あと ustream の録画を観てたらやたら話がくどい人がいるなぁと思ったら自分でした。がっかりした。