130 likes | 271 Views
第 12 回 RHG の逆襲. 2009/3/14 澤田 淳二. 今日のテーマ. ブロック周りの実装 メソッドへのブロックの渡し方 yield 時の処理 変数アクセス next, redo, break. サンプルソース. (1) C でのブロック呼び出し. (2) Ruby でのブロック呼び出し. def foo yield(1) yield(2) yield(3) yield(4) yield(5) end x = 1 foo {|i| next if i == 2 break if i == 3
E N D
第12回 RHGの逆襲 2009/3/14 澤田 淳二
今日のテーマ • ブロック周りの実装 • メソッドへのブロックの渡し方 • yield時の処理 • 変数アクセス • next, redo, break
サンプルソース (1) Cでのブロック呼び出し (2) Rubyでのブロック呼び出し def foo yield(1) yield(2) yield(3) yield(4) yield(5) end x = 1 foo {|i| next if i == 2 break if i == 3 redo if i == 6 p [x, i] } x = 1 (1..5).each do |i| (1..5).each do |j| next if i == 2 break if i == 3 redo if i == 6 p [x, i, j] end end ローカル変数 外のブロック変数 自分のブロック変数
YARVコードへの変換結果 でYARVコードが出力されます ruby --dump=insn スクリプト
rubyのトレース実行 • Emacsの場合 • makeするとき最適化フラグは切っておくとよい • M-x gdbでgdb起動 • set argsでコマンドライン • breakでブレークポイント(ファイル:行数 or 関数) • runで実行開始 • stepで1行実行(関数に入る) • nextでも1行実行(関数には入らない) • finishで関数抜けだし • btでバックトレース
メソッドへのブロックの渡し方(1) • コンパイル時 • iseq_compile_each()のNODE_ITERおよびNODE_CALL参照 • NODE_ITER • ブロックのNODEツリーをコンパイルし compile_data->current_blockに設定 • NODE_CALL • send命令の引数としてcurrent_blockを設定
メソッドへのブロックの渡し方(2) • 実行時 • ブロックの準備:caller_setup_args() • ブロックのrb_iseq_tをセットした rb_block_tを準備 • cfpの一部をrb_block_t用の領域として使用 • メソッドの呼び出し:vm_push_frame() • rb_block_tをspecvalに設定
図解 スタックは の向きに伸びる VALUEスタック rb_control_frame_t rb_block_t lfp specval ブロックの rb_iseq_t iseq 実行中のcfp 1つ前のcfp cfpスタック
yield時の処理 • specvalからrb_block_t取り出し • cfpをpush • 1つ前のcfpのlfp(rb_block_t.lfp)をlfpに設定 • 1つ前のcfpのdfpをspecvalに設定
図解 サンプルソース(1)で中のブロック実行時のスタック状態 スタックは の向きに伸びる dfp specval(1つ前のdfp) j specval(rv_block_t) specval(1つ前のdfp) dfp i specval(rb_block_t) lfp 中のブロックのcfp specval lfp x 中のeachのcfp 外のブロックのcfp lfp, dfp 外のeachのcfp <main>のcfp
変数アクセス • dfpを利用して変数の位置を特定 • 自ブロックの外にある変数もdfpをたどることでアクセス可能(たどるレベルはコンパイル時にわかる)
next, redo • next • 処理:次のループに進む • コンパイル結果:leave • redo • 処理:ブロック実行をやり直す • コンパイル結果:jump 0 ちなみに、retryは使えなくなりました
break • 処理:ループ(yieldしたメソッド)を終了する • コンパイル結果:throw 2 • break時の1つ前のdfpとマッチするcfp(メソッドを呼び出したcfp)を検索 • マッチしたcfpのpcから実行を再開 • ブロック呼び出しがC関数の場合、JUMP_TAGを利用してCのコールスタックを巻き戻し