ko1 / rubyhackchallenge

475 stars 83 forks source link

フレームスタックの可視化に必要な中間表現の作成まで #15

Closed kenju closed 7 years ago

kenju commented 7 years ago

概要

VMの実行状況を可視化することで、VMの内部実装への理解が深まる手段を提供できるとともに、デバッグ時の有用なツールとなりうる。

コアコンセプト

YARVは、フレームスタックを利用してRubyの制御の流れを管理している(具体的にはrb_control_frame_t構造体のスタック)。

rb_control_frame_tは、SP(stack pointer)PC(program counter) といったポインタを持っているので、この一連の制御フレームがあればRubyの実行プロセスを再現可能なはず、という点に着目。

フレームスタックを何かしらの中間表現に出力し、それを元にインタラクティブに再生可能(例:アニメやDVDプレーヤー)なコンテンツを作成する。

タスク

今回は「中間表現の作成」まで。

後日TODO

kenju commented 7 years ago

VMへの制御フレームのPop/Pushの実行はAPI化されている。このメソッド内部で必要な情報をログするメソッドを挟み込めば行けそう。

kenju commented 7 years ago

Memo

vm_core.hVM Debug Level(VMDEBUG)オプション、1, 2以外は↓のエラーで動かない。余裕があったら修正する。

../ruby/vm_dump.c:292:12: error: no member named 'stack' in 'struct rb_thread_struct'
        if ((th)->stack + (th)->stack_size > (VALUE *)(cfp + 1)) {
            ~~~~  ^
../ruby/vm_dump.c:292:26: error: no member named 'stack_size' in 'struct rb_thread_struct'
        if ((th)->stack + (th)->stack_size > (VALUE *)(cfp + 1)) {
                          ~~~~  ^
2 errors generated.
make: *** [vm_dump.o] Error 1

VMDEBUGを変更する方法:

コンパイル時に見るオプションなので、変更するときはソースコードを書き換えた上で再コンパイル。

kenju commented 7 years ago

VMDEBUG=2でコンパイルした時に有効になる、vm_dump:rb_vmdebug_stack_dump_raw() (随所ではSDR()で呼び出されている)がデバッグ用のコントロールフレームをいい感じで出力している。このメソッドの実装を参考にする。

-- Control frame information -----------------------------------------------
c:0005 p:---- s:0017 e:000016 CFUNC  :inspect
c:0004 p:0005 s:0014 e:000012 BLOCK  ../ruby/test.rb:2 [FINISH]
c:0003 p:---- s:0010 e:000009 CFUNC  :times
c:0002 p:0008 s:0006 e:000005 EVAL   ../ruby/test.rb:1 [FINISH]
c:0001 p:0000 s:0003 E:000390 (none) [FINISH]

    | 0005 putstring        "hello world"
    | 0007 opt_send_without_block <callinfo!mid:p, argc:1, FCALL|ARGS_SIMPLE>, <callcache>
kenju commented 7 years ago
2.times do
  p 'hello world'
end

のコードに対して、シンプルにフレームスタックのみをログした出力結果が以下の通り↓

https://gist.github.com/kenju/8221df17ebafcef5be05eb7ef32e7d16

これを一次ソースとする。この次にRubyでデータ整形した上でoFに流し込めばよさそう。

kenju commented 7 years ago

Memo

2.times do
  p 'hello world'
end

のようなシンプルなコードに対しても、https://gist.github.com/kenju/8221df17ebafcef5be05eb7ef32e7d16 を見るとわかる通り、↓のような行が大量に出力される。

:inherited

c:0002 p:---- s:0006 e:000005 CFUNC  :inherited
c:0001 p:0000 s:0003 E:0012e0 (none) [FINISH]

これはrb_define_moduleなどでModuleのinheritedが呼ばれるため。そもそも表示させる必要はないので、スキップさせたい。これも余裕があれば or 後日対応したい。

:set_encoding

Push +
c:0002 p:---- s:0006 e:000005 CFUNC  :set_encoding
c:0001 p:0000 s:0003 E:0012e0 (none) [FINISH]

若干だが、エンコードセットでも呼ばれている。これも本来のコードにとっては不要。

kenju commented 7 years ago

JSON Expression

{
  "code": "2.times do\np 'hello world'\nend",
  "controlFrames": [
    { 
      "type": "push", 
      "stacks": [
        { "c": "0006", "p": "----", "s": "0021", "e": "000020", "magicType": "CFUNC", "insns": ":inspect" },
        { "c": "0005", "p": "----", "s": "0018", "e": "000017", "magicType": "CFUNC", "insns": ":inspect" },
        // ...
      ]
    },
    { 
      "type": "pop", 
      "stacks": [
        { "c": "0005", "p": "----", "s": "0018", "e": "000017", "magicType": "CFUNC", "insns": ":inspect" }
        // ...
      ]
    }
  ]
}
kenju commented 7 years ago

JSON Expression

controlFrames > stacks:

{ 
  "count": "0006", 
  "programCounter": "----", 
  "stackPointer": "0021", 
  "envPointer": "000020", 
  "magicType": "CFUNC", 
  "posbuf": ":inspect" 
}

NOTE:

kenju commented 7 years ago

[Q]

その他

kenju commented 7 years ago

出力したログファイルをJSONに変換するロジックは以下のレポジトリで担当させた。

https://github.com/kenju/vm_stackexplorer

kenju commented 7 years ago

[作業報告]

SDR()をベースに少し書き換えたVMスタックフレームの出力をログファイルに出力し、JSONに変換するところまでできた(コードはものすごくみづらいので、後日テスト書いてリファクタする)

最後のvisualize部分に移る。

kenju commented 7 years ago

時間が足りなかったので、スタックフレームを一旦oFではなくReact Appで実装。 あとはAnimationつければそれっぽくなる。

screen shot 2017-08-31 at 18 04 03
kenju commented 7 years ago

rubyhackchallenge期間でやりたかったことは達成したため、このIssuesはクローズします。

今後の開発は https://github.com/kenju/vm_stackexplorer で行います。

ありがとうございました!