New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
kenju opened this Issue Aug 31, 2017 · 12 comments

Comments

Projects
None yet
1 participant
@kenju
Contributor

kenju commented Aug 31, 2017

概要

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

コアコンセプト

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

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

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

タスク

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

  • フレームスタックをログ出力
    • まずはprintfで標準出力
    • Debugオプションで提供
  • 中間表現の設計
    • VMの実装を再現可能なデータ構造を設計
    • まずは、シンプルにlog/framestacks.txtのようなファイルに出力
    • 余裕があれば、JSON出力に対応すれば利用の幅が増えそう
  • 中間表現のデータ整形
    • RubyかなにかでJSONに変換するブリッジを作る
      • JavaScriptなどのWeb系言語でも後に扱いやすいようにする
  • openFrameworksなどのグラフィクスツールにつなぎこみ
    • ここから先は C++&oF での作業
    • 一連のシーケンスで表現できるはずなので、スライダーなどで前後移動できるインタラクティブなUXにする

後日TODO

@kenju

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

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

  • vm_insnshelper.c:vm_push_frame
  • vm_insnshelper.c:vm_pop_frame
Contributor

kenju commented Aug 31, 2017

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

  • vm_insnshelper.c:vm_push_frame
  • vm_insnshelper.c:vm_pop_frame
@kenju

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

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を変更する方法:

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

Contributor

kenju commented Aug 31, 2017

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

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

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>
Contributor

kenju commented Aug 31, 2017

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

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor
2.times do
  p 'hello world'
end

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

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

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

Contributor

kenju commented Aug 31, 2017

2.times do
  p 'hello world'
end

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

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

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

@kenju

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

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]

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

Contributor

kenju commented Aug 31, 2017

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

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

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" }
        // ...
      ]
    }
  ]
}
Contributor

kenju commented Aug 31, 2017

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

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

JSON Expression

controlFrames > stacks:

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

NOTE:

  • 'posbuf' は position buffer. 'iseqName' (= instruction sequence name)の方が良いかも
Contributor

kenju commented Aug 31, 2017

JSON Expression

controlFrames > stacks:

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

NOTE:

  • 'posbuf' は position buffer. 'iseqName' (= instruction sequence name)の方が良いかも
@kenju

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

[Q]

  • magicType "IFUNC"とは
    • IFUNC = Iterator Functions
    • Cで実装したブロック
      • Rubyで実装したブロックはBLOCKtypeになるが、Enumarator#sumなど、Cでイテレーターを書いてある箇所がある
  • magicType "case 0"になることはあるのか?
    • 多分ない。デバッグ時に書いていた名残かと

その他

  • 例えばruby foo.rbを実行した時の最初の制御フレームのtypeはTOPではない。
    • 昔はTOP -> EVALだったが、Top level bindingを実装する時にEVAL -> TOPに変わった
    • RubyのTop level binding を真面目に実装し始めると、このような設計にならざるを得なかった
Contributor

kenju commented Aug 31, 2017

[Q]

  • magicType "IFUNC"とは
    • IFUNC = Iterator Functions
    • Cで実装したブロック
      • Rubyで実装したブロックはBLOCKtypeになるが、Enumarator#sumなど、Cでイテレーターを書いてある箇所がある
  • magicType "case 0"になることはあるのか?
    • 多分ない。デバッグ時に書いていた名残かと

その他

  • 例えばruby foo.rbを実行した時の最初の制御フレームのtypeはTOPではない。
    • 昔はTOP -> EVALだったが、Top level bindingを実装する時にEVAL -> TOPに変わった
    • RubyのTop level binding を真面目に実装し始めると、このような設計にならざるを得なかった
@kenju

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

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

https://github.com/kenju/vm_stackexplorer

Contributor

kenju commented Aug 31, 2017

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

https://github.com/kenju/vm_stackexplorer

@kenju

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

[作業報告]

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

最後のvisualize部分に移る。

Contributor

kenju commented Aug 31, 2017

[作業報告]

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

最後のvisualize部分に移る。

@kenju

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Aug 31, 2017

Contributor

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

screen shot 2017-08-31 at 18 04 03

Contributor

kenju commented Aug 31, 2017

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

screen shot 2017-08-31 at 18 04 03

@kenju kenju referenced this issue Aug 31, 2017

Open

アンケート #22

@kenju

This comment has been minimized.

Show comment
Hide comment
@kenju

kenju Sep 4, 2017

Contributor

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

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

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

Contributor

kenju commented Sep 4, 2017

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

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

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

@kenju kenju closed this Sep 4, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment