あなとみー おぶ mrubyのJIT (その10)

お久しぶりです。ここんとこしばらくProcオブジェクトのサポートを作りこんでました。これが無いとイテレータとかみんなVMに戻ってしまって性能が上がらないのです。実はProcオブジェクトをサポートしてもあまり性能が上がらなかったのですが…。
で、この作業ですごくとりにくいバグがいっぱい出て数カ月デバッグ三昧という感じでした。おかげてうまく動くようになると却って落ち着かないという状態なのですが、それはそれとしてそのデバッグで作ったツールを紹介したいと思います。全国に31名くらいいると思われるmrubyでJITコンパイラを作っている人たちに参考になれば幸いです。

デバッグしていて困るのはどの命令を実行していた時にバグったのかが分からないことです。vm.cで実行していた場合は命令毎に処理が分かれているのでまだいいのですが、ネイティブコードでバグった場合(例えばセグフォしたばあいとか)、mrubyのどの命令がコンパイルされたものでバグったのか分からないわけです。
幸い、mrubyのJITではVMに処理を渡すためにmrubyのVMのプログラムカウンタ(pc)と実行中のメソッドのirepをこまめに更新しています。これらを頼りに実行位置が付きとめられます。ところが、pcとirepがつじつまが合っていないという場合があって、この場合ほぼ確実にセグフォするのですがpcに対応するirepが分からないからいろいろ不便です。また、バイナリを見てmruby VMのどの命令か判断するのは結構出来るようになったのですが、とてもむなしい作業です。

そんなこんなで次のような関数を作ってデバッグ効率を上げました。

  • search_irep(mrb, pc)       pcに対応するirepを探す
  • disasm_irep(mrb, irep)      irepを逆アセンブルする
  • disasm_once(mrb, irep, 命令)   1命令を逆アセンブルする

実装は、https://github.com/miura1729/mruby/blob/95dc9d1c5596c96aae6a6814e98e09954f0c96f4/src/jit.cの398行目以降です。

こんな感じで使います

For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/miura/work/mruby/bin/mruby...done.

aoベンチを実行

(gdb) r benchmark/ao-render.rb
Starting program: /home/miura/work/mruby/bin/mruby benchmark/ao-render.rb
[New Thread 7272.0x2f00]
[New Thread 7272.0x3118]
P6
64 64
255

ちなみに今のバージョンはちゃんと動きます。説明用にバグを仕込ませています。

Program received signal SIGSEGV, Segmentation fault.
0x3d24418b in ?? ()

取り合えず、irep, pcがアクセスできそうな関数を探す。mrbjit_dispatchではpcはppcという変数名でpcへのポインタという形で持っている。

(gdb) where
#0  0x3d24418b in ?? ()
#1  0x00429041 in mrbjit_dispatch (status=0x22a8c0, mrb=0x20039920)
    at C:\cygwin\home\miura\work\mruby\src\vm.c:682
#2  mrb_run (mrb=0x20039920, proc=0x2003b798, self=...)
    at C:\cygwin\home\miura\work\mruby\src\vm.c:2325
#3  0x00419ecf in load_exec (mrb=0x20039920, p=0x200c01a0, c=<optimized out>) at src/parse.y:5206
#4  0x00427474 in mrb_load_file_cxt (mrb=0x20039920, f=0x200bff34, c=0x200c0148) at src/parse.y:5215
#5  0x004017ac in main (argc=2, argv=0x22ac40)
    at C:\cygwin\home\miura\work\mruby\tools\mruby\mruby.c:281

mrbjit_dispatchに対象フレームを移す

(gdb) up
#1  0x00429041 in mrbjit_dispatch (status=0x22a8c0, mrb=0x20039920)
    at C:\cygwin\home\miura\work\mruby\src\vm.c:682
682           asm volatile("call *%0\n\t"

pcに対応するirepを探す。

(gdb) p search_irep (mrb, *ppc)
$1 = (mrb_irep *) 0x200f3ec8

irepの命令列を逆アセンブルする

(gdb) p disasm_irep (mrb, $1)
   0 OP_ENTER   1:0:0:0:0:0:0
   1 OP_GETIV   R4      @x
   2 OP_MOVE    R5      R1
   3 OP_SEND    R5      :x      0
   4 OP_NOP
   5 OP_MUL     R4      :*      1
   6 OP_NOP
   7 OP_GETIV   R5      @y
   8 OP_MOVE    R6      R1
   9 OP_SEND    R6      :y      0
   a OP_NOP
   b OP_MUL     R5      :*      1
   c OP_NOP
   d OP_ADD     R4      :+      1
   e OP_NOP
   f OP_GETIV   R5      @z
  10 OP_MOVE    R6      R1
  11 OP_SEND    R6      :z      0
  12 OP_NOP
  13 OP_MUL     R5      :*      1
  14 OP_NOP
  15 OP_ADD     R4      :+      1
  16 OP_NOP
  17 OP_MOVE    R3      R4
  18 OP_RETURN  R3
$2 = void

どこを実行していたかはこんな感じで調べられる

(gdb) p *ppc - $1->iseq
$3 = 4

そんな感じです。またお会いしましょう。