Hatena::ブログ(Diary)

miura1729の日記 このページをアンテナに追加 RSSフィード

2013-05-03

luajitの実力

20:37 |  luajitの実力を含むブックマーク  luajitの実力のブックマークコメント

追記

LuaJITの作者Mike Pall氏より、twitterで次のようなアドバイスをいただきました。

1. No compiler is allowed to make this optimization. Floating-point arithmetic ist NOT associative.

2. Please use 'local' functions when publishing Lua benchmarks.

3. Please use the current version of LuaJIT.

訳(かなり怪しい)

1.このような最適化出来るコンパイラは無いよ。浮動小数点数の算術命令は結合的じゃないから

2. Luaベンチマークを取るなら局所関数を使ってください

3. 最新バージョンのLuaJITを使ってください

そういうわけで、ベンチマークを取り直します。

ベンチマークをやり直しました。functionの前にlocalを入れて、LuaJITを最新にしています。Mike Pall氏には、ベンチマークのやり直しにあたりアドバイスをいただきました。ありがとうございます。

$ luajit -v

LuaJIT 2.0.1 -- Copyright (C) 2005-2013 Mike Pall. http://luajit.org/

最適化

$ time luajit spline0.lua

real    0m1.275s
user    0m1.170s
sys     0m0.031s

最適化

$ time luajit spline1.lua

real    0m0.806s
user    0m0.732s
sys     0m0.015s

最適化前が3%ほど速くなって最適化後との差が少し縮みました。JIT無しのLuaでの結果です。

$ time ./lua ../../luajit.org/spline0.lua

real    0m1.273s
user    0m1.185s
sys     0m0.045s

$ time ./lua ../../luajit.org/spline1.lua

real    0m0.327s
user    0m0.249s
sys     0m0.046s

追記終わり

それはあまりにもコンパイラ最適化に期待し過ぎです。実際に吐き出したコードを読んでみましょう。あなたがコンパイラの作者だったら、あなたがJITの作者だったら、入って来たコードから同じような最適化ができるでしょうか。まず無理です。どんな高度な最適化コンパイラも、所詮は人間の作ったコードです。コンパイラは神ではないのです。あくまでも人間の創りだした不完全な道具のひとつに過ぎません。

よくわかる最適化 (http://d.hatena.ne.jp/shi3z/20130502/1367490202) より

うう、luajitなら、luajitならやってくれる。と信じて確かめてみました。

結果、

最適化

$ time luajit-2.0.0-beta10 spline0.lua

real    0m1.268s
user    0m1.200s
sys     0m0.016s

最適化

$ time luajit-2.0.0-beta10 spline1.lua

real    0m0.795s
user    0m0.733s
sys     0m0.046s

理論値3倍のはずなのでかなり盛り返しています。

ちなみに、JIT無しのluaだとこんな感じ。

時間がかかってしょうがないのでループを1/100にしています。

最適化

$ time ./lua ../../luajit.org/spline0.lua

real    0m1.309s
user    0m1.216s
sys     0m0.046s

最適化

$ time ./lua ../../luajit.org/spline1.lua

real    0m0.331s
user    0m0.249s
sys     0m0.061s

ちゃんと、最適化は出来ているようです。

まとめ

 luajitはとても頑張っているが完璧ではない。

移植したソースコードです

最適化

function catmullRom(p0, p1, p2, p3, t)
  local v0 = (p2 - p0) / 2.0
  local v1 = (p3 - p1) / 2.0
  return ((2.0 * p1 - 2.0 * p2) + v0 + v1) * t * t * t + 
          ((-3.0 * p1 + 3.0 * p2) - 2.0 * v0 - v1) * t * t + v0 * t + p1
end

function main(xp0, xp1, xp2, xp3, yp0, yp1, yp2, yp3, pp0, pp1, pp2, pp3)
  local d = math.sqrt((xp1 - xp2) * (xp1 - xp2) + (yp1 - yp2) * (yp1 - yp2))
  local num = math.ceil((d / 5.0) + 0.5)
  local x,y,p
  local invertNum = 1.0/num
  local deltaT = 0
  for i = 0, num do
    deltaT = deltaT + invertNum
    x = catmullRom(xp0,xp1,xp2,xp3, deltaT)
    y = catmullRom(yp0,yp1,yp2,yp3, deltaT)
    p = catmullRom(pp0,pp1,pp2,pp3, deltaT)
  end
end

for j = 0, 10000 do
  main(1.0, 100.0, 200.0, 200.0, 300.0, 100.0, 0.0, 200.0, 0.0, 100.0, 200.0, 300.0)
end

最適化

function main(xp0, xp1, xp2, xp3, yp0, yp1, yp2, yp3, pp0, pp1, pp2, pp3)
local dx = xp1-xp2
local dy = yp1-yp2
local d = math.sqrt(dx*dx+dy*dy) 
local num = math.ceil((d*0.2) + 0.5)
local x,y,p
local invertNum = 1.0/num
local deltaT = 0
local xv0 = (xp2-xp0)*0.5
local xv1 = (xp3-xp1)*0.5
local xfact1=((xp1 - xp2)*2.0 + xv0 + xv1)
local xfact2=((xp2 - xp1)*3.0 - 2.0 * xv0 - xv1) 
local yv0 = (yp2-yp0)*0.5
local yv1 = (yp3-yp1)*0.5
local yfact1=((yp1 - yp2)*2.0 + yv0 + yv1)
local yfact2=((yp2 - yp1)*3.0 - 2.0 * yv0 - yv1) 
local pv0 = (pp2-pp0)*0.5
local pv1 = (pp3-pp1)*0.5
local pfact1=((pp1 - pp2)*2.0 + pv0 + pv1)
local pfact2=((pp2 - pp1)*3.0 - 2.0 * pv0 - pv1)
local xfact1n =0
local yfact1n =0
local pfact1n =0
local xFact1step = xfact1 * invertNum
local yFact1step = yfact1 * invertNum
local pFact1step = pfact1 * invertNum
  for i = 0, num do
     deltaT = deltaT + invertNum
     x =((xfact1n + xfact2) * deltaT + xv0) * deltaT + xp1
     y =((yfact1n + yfact2) * deltaT + yv0) * deltaT + yp1
     p =((pfact1n + pfact2) * deltaT + pv0) * deltaT + pp1
     xfact1n = xfact1n + xFact1step
     yfact1n = yfact1n + xFact1step
     pfact1n = pfact1n + xFact1step
  end
end

for j = 0, 1000000 do
  main(1.0, 100.0, 200.0, 200.0, 300.0, 100.0, 0.0, 200.0, 0.0, 100.0, 200.0, 300.0)
end

2013-04-28

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

07:41 |  あなとみー おぶ mrubyのJIT (その10)を含むブックマーク  あなとみー おぶ 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

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

monamour555monamour555 2013/04/29 14:52 JITに限らず有用性が高いので,monami-ya.mrb では bundled gems に含めました.素晴らしい hack に感謝いたします.
https://bitbucket.org/mruby/monami-ya.mrb/commits/54e2edc2a040bdfbbbdce42850f3789194e9b04c

miura1729miura1729 2013/04/29 15:35 おお!ありがとうございます。この記事を書いた甲斐がありました。

2013-03-13

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

19:49 |  あなとみー おぶ mrubyのJIT (その9)を含むブックマーク  あなとみー おぶ mrubyのJIT (その9)のブックマークコメント

今回からmrubyのJITで実際にどういうX86のコードを生成するのか説明する予定なのですが、その前に大変素晴らしいXbyakの宣伝とともにmrubyのJITで共通して使っているテクニックを紹介したいと思います。

Xbyakは光成滋生氏によって開発されたx86(IA32), x64(AMD64, x86-64)のマシン語命令を生成するC++のクラスライブラリです。詳しくは http://homepage1.nifty.com/herumi/soft/xbyak.html を読んでください。非常に使いやすく安定しているので開発が捗ることでしょう。

個人的に気になった点としては実行時のアセンブルエラーは例外をキャッチしないとどこで発生したかとかどういうエラーかが分からないことがあります。ただ、これはエラー時のハンドリングの柔軟性とのトレードオフなので難しいところです。

mrubyのJITで使っているテクニックを紹介します。

L("@@")は便利

 ドキュメント(http://homepage1.nifty.com/herumi/soft/xbyak.html)にあるようにXbyakMASM由来のL("@@")という無名のラベルが使えます。"@f"で前方の最寄りのL("@@")に"@b"で後方の最寄りのL("@@")を参照します。

mrubyのJITでは変数が想定したクラスのオブジェクトかどうかなどのチェックを行うガードを多用します。ガードはあちこちに似た形で存在しますのでユニークなラベルの名前を付けるのが困難です。こんな時はL("@@")が便利です。

例えば、eaxが指すオブジェクトが想定した型であるかどうかをチェックするガードを生成するgen_type_guardを見てみましょう。t

  void 
    gen_type_guard(mrb_state *mrb, enum mrb_vtype tt, mrb_code *pc)
  {
    /* Input eax for type tag */
    if (tt == MRB_TT_FLOAT) {
      cmp(eax, 0xfff00000);
      jb("@f");
    } 
    else {
      cmp(eax, 0xfff00000 | tt);
      jz("@f");
    }

    /* Guard fail exit code */
    gen_exit(pc, 1);

    L("@@");
  }

ttが想定した型で、mrb_vtypeはalue.hで定義されています。ここで見たとおりL("@@")が使われています。こうすることで何個ガードを生成しても問題が起こることはありません。

構造体のアクセス

mrubyのJITが生成した機械語コードはCで記述されたVMの状態を読み書きしながら実行するためCの構造体のメンバーにアクセスすることが必要です。機械語レベルで構造体のメンバーにアクセスすることはCコンパイラによって構造体のレイアウトが異なる場合があったりして煩雑なのですが、Xbyakでは構造体の先頭からのメンバーのオフセットを求めるOffsetOfとの合わせ技で比較的簡単に行うことが出来ます。

例えば、機械語コードからmrubyのVMに戻る処理を生成するgen_exitを見てみましょう。

  void 
    gen_exit(mrb_code *pc, int is_clr_rc)
  {
    const void* exit_ptr = getCurr();

    mov(eax, dword [ebx + OffsetOf(mrbjit_vmstatus, pc)]);
    mov(dword [eax], (Xbyak::uint32)pc);
    if (is_clr_rc) {
      xor(eax, eax);
    }
    mov(edx, (Xbyak::uint32)exit_ptr);
    ret();
  }

gen_exitでは機械語コード実行に伴って古いpcはつじつまが合わないので更新する必要があります。そこで、pcの値()を引数で受け取って設定するようにしています。この時VMで使っているpcという変数のアドレスはstatus構造体に格納されているのでここからpcというメンバーにアクセスする必要があります。これが、次の部分です。

ebxにはstatus構造体の先頭アドレスが入っています。

    mov(eax, dword [ebx + OffsetOf(mrbjit_vmstatus, pc)]);
    mov(dword [eax], (Xbyak::uint32)pc);

このように少し表記は煩雑ですがOffsetOfを使うことでコンパイラの違いを意識することなく構造体のメンバーにアクセスできます。

なお、OffsetOfはoffsetofという名前でC99であstddef.hで定義された標準のようですが、私の使っているCygwinではないので次のように定義して使っています。

#define OffsetOf(s_type, field) ((size_t) &((s_type *)0)->field) 

追記

 私の使っているCygwinにもstddef.hにありました。教えてくださった、egtraさん、herumiさんありがとうございます。

続く

egtraegtra 2013/03/20 18:36 Cygwinにもoffsetofはあると思います、C89の時点ですでに存在しますので。私の環境では、/usr/lib/gcc/i686-pc-cygwin/4.5.3/include/stddef.hにありました。

miura1729miura1729 2013/04/02 02:31 ずいぶん遅い返事で申し訳ありません。確認しました。私の環境にもありました。/usr/includeにはstddef.hがないので気づきませんでした。なぜないのだろう?

2013-03-04

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

23:22 |  あなとみー おぶ mrubyのJIT (その8)を含むブックマーク  あなとみー おぶ mrubyのJIT (その8)のブックマークコメント

かなり間が空いちゃいました。その間にmrubyのJITをコーティングしたりお祭りの資料を作ったりしていました。結構内容が変わって、例えばこれまで説明していたものがjit.cからvm.cに移ったりしています。そんなこんなで結構速度が上がって(多くがwannabe53さんのおかげ)、もうすぐオリジナルの2倍速くらいか(単純なループなら4倍くらいだけどメソッドコールが入ると速度が落ちる)というところまで来ています。

さて、今回はjitcode.ccの説明です。

const void *
mrbjit_emit_code(mrb_state *mrb, mrbjit_vmstatus *status)
{
  MRBJitCode *code = (MRBJitCode *)mrb->compile_info.code_base;
  const void *rc = mrbjit_emit_code_aux(mrb, status, code);
  if (rc == NULL && code == NULL) {
    mrb->compile_info.code_base = NULL;
  }

  return rc;
}

これが、コード生成部のトップレベルmrbjit_emit_codeです。最初に、

  MRBJitCode *code = (MRBJitCode *)mrb->compile_info.code_base;

でCレベルで持っているvoid *型のXbyakのCodeGeneratorオブジェクトC++オブジェクトに変換します。変換って言っても単にキャストですが。なんか、すごい勢いで怒られそうなコードですがとりあえず動いているからいいかって感じです。

  const void *rc = mrbjit_emit_code_aux(mrb, status, code);
  if (rc == NULL && code == NULL) {
    mrb->compile_info.code_base = NULL;
  }

ここで、実際の処理を行うmrbjit_emit_code_auxを呼び出します。mrbjit_emit_code_auxはコード生成をするとそのコードの命令列の先頭のアドレスを返します。コードが生成され合い場合はNULLを返します。

その後のif文はなぜいるのか忘れてしまった説明出来ないです。すみません・・・。これがないと動かないのですが…

次に、mrbjit_emit_code_auxです。

static const void *
mrbjit_emit_code_aux(mrb_state *mrb, mrbjit_vmstatus *status, MRBJitCode *code)
{
  mrb_irep *irep = *status->irep;
  mrb_value *regs = *status->regs;
  mrb_code **ppc = status->pc;
  const void *entry;

  if (code == NULL) {
    code = the_code;
    mrb->compile_info.code_base = code;
    entry = code->gen_entry(mrb, irep);
  }

初めてmrbjit_emit_codeを呼び出したとき、codeはNULLになっているのでCodeGeneratorオブジェクトを設定します。今のところ、CodeGeneratorオブジェクトは1つでthe_codeというグローバルのstatic変数に入っています。ただ、複数のCodeGeneratorオブジェクトを管理できるようにするため、the_codeを直接アクセスする箇所を出来る限り減らしています。code->gen_entry(mrb, irep)は今のところ何もしないのですが、CodeGenratorオブジェクトになんか設定する場合とかを想定しています。

  switch(GET_OPCODE(**ppc)) {
  case OP_NOP:
    return code->emit_nop(mrb, irep, ppc);
    
  case OP_MOVE:
    return code->emit_move(mrb, irep, ppc);

  case OP_LOADL:
    return code->emit_loadl(mrb, irep, ppc);

次に実行しようとする命令に対応するコードを生成します。emit_*というメソッドはjitcode.hに定義されていてほとんどはそれを呼び出すだけです。

例外は、OP_ENTERとOP_RETURNでそれぞれ次のようになっています。

  case OP_ENTER:
    mrb->compile_info.nest_level++;
    return code->emit_enter(mrb, status);

  case OP_RETURN:
    mrb->compile_info.nest_level--;
    if (mrb->compile_info.nest_level < 0) {
      return code->emit_return(mrb, status);
    }
    else {
      return code->emit_return_inline(mrb, status);
    }

OP_ENTERはメソッドの先頭、OP_RETURNはメソッドの最後の処理です。mrb->compile_info.nest_levelは現在のメソッドの呼び出しのネストレベルを表します。このレベルはネイティブコードで実行する場合で途中でVMに戻る場合は0に戻されます。また、メソッドがインライン化しない場合も0戻されます。つまり、OP_RETURNの時点でnest_levelが1以上の場合はOP_ENTERからOP_RETURNまでVMに戻らず、しかもインライン化するメソッドということになります。そういう場合は特別扱いして高速なコードを生成します。特別扱いするコード生成のメソッドが、emit_return_inlineです。

  default:
    mrb->compile_info.nest_level = 0;
    return NULL;
  }

普通この手のdefaultはnot reachedでエラーチェックのためにあるって感じですが、現時点のmrubyのJITは普通に実行されます。サポートされていない命令はVMJITで実行されます。ちなみに、emit_*においてもNULLを返すとその命令はVMで実行されます。例えば、OP_SENDで可変引数とかコード生成が超面倒な割には使用頻度が少ないのでNULLを返してVMで実行してもらっています。

そういうことで、今回は終わり

gdgdですがまあ仕方がない。次回はあるのか???

多分続く

2013-02-20

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

03:43 |  あなとみー おぶ mrubyのJIT (その7)を含むブックマーク  あなとみー おぶ mrubyのJIT (その7)のブックマークコメント

祝その7、前回(ytljitの型推論)も前々回(yarv2llvm)も第6回くらいで挫折したんだよな。まああいつらよりは説明しやすいのですが。

そういうことで、無事にかは知らんけど拡張asmを超えて続きを説明します。ちなみに、今やっているのはjit.cのmrbjit_dispatchです。前回2回使って70行くらいしか説明していなんだな。どうでもいいことだけど。

ネイティブコードを実行して、無事戻ってきたところからです。

      irep = *status->irep;
      regs = *status->regs;
      n = ISEQ_OFFSET_OF(*ppc);
      if (irep->ilen < NO_INLINE_METHOD_LEN) {
	caller_pc = mrb->ci->pc;
      }
      else {
	caller_pc = NULL;
	mrb->compile_info.nest_level = 0;
      }
      if (rc) {
	mrb->compile_info.prev_pc = *ppc;
	return rc;
      }
      ci = search_codeinfo_prev(irep->jit_entry_tab + n, prev_pc, caller_pc);

ごちゃごちゃと後始末が続きます。

あれ、これって見たことがあると思った方、記憶力がいいですね。うらやましい。mrbjit_dispatchの先頭で出てきました。ローカル変数初期化とcode_infoのサーチ処理です。ネイティブコードがirep, regsなどを書き換えているため、ローカル変数を再設定しているのです。

      if (rc) {
	mrb->compile_info.prev_pc = *ppc;
	return rc;
      }

これは出てないです。rcはネイティブコードから返される値の1つです。内容は、例外など特別に対処が必要なことがあるとそのハンドラのアドレスが返ってきます。ハンドラはmrb_runのgotoのラベルで、第3回(http://d.hatena.ne.jp/miura1729/20130211/1360607189)で説明した通り、mrbjit_dispatchの戻り値はその後mrb_run中でgotoされますからrcの値をそのまま返します。rcがNULLなら正常運転なので次に続きます。

ここからは実際にコンパイルするところです。ちなみにネイティブコードを呼んだ場合もここを通ります。

  if (irep->prof_info[n]++ > COMPILE_THRESHOLD) {
    //      printf("size %x %x %x\n", irep->jit_entry_tab[n].size, *ppc, prev_pc);
    if (ci == NULL) {
      //printf("p %x %x\n", *ppc, prev_pc);
      ci = add_codeinfo(mrb, irep->jit_entry_tab + n);
      ci->prev_pc = prev_pc;
      ci->caller_pc = caller_pc;
      ci->code_base = mrb->compile_info.code_base;
      ci->entry = NULL;
      ci->used = -1;
    }

    if (ci->used < 0) {
      entry = mrbjit_emit_code(mrb, status);
      if (prev_entry && entry) {
	//printf("patch %x %x \n", prev_entry, entry);
	cbase = mrb->compile_info.code_base;
	mrbjit_gen_jmp_patch(cbase, prev_entry, entry);
      }

      if (entry) {
	ci->entry = entry;
	ci->used = 1;
      }
      else {
	/* record contination patch entry */
	if (cbase) {
	  ci->entry = mrbjit_get_curr(cbase);
	}
	//	printf("set %x %x \n", ci->entry, entry);
	ci->used = -1;
	// printf("%x %x %x\n", ci->entry, *ppc, ci);
      }
    }
  }

コメントアウトされたprintfが痛いですね。

  if (irep->prof_info[n]++ > COMPILE_THRESHOLD) {

何回ここを通ったかをカウントします。現状ではCOMPILE_THRESHOLDの値は1000です。

    if (ci == NULL) {
      //printf("p %x %x\n", *ppc, prev_pc);
      ci = add_codeinfo(mrb, irep->jit_entry_tab + n);
      ci->prev_pc = prev_pc;
      ci->caller_pc = caller_pc;
      ci->code_base = mrb->compile_info.code_base;
      ci->entry = NULL;
      ci->used = -1;
    }

何度も出てきてます、既存のcode infoがあるかどうかのチェックです。無い場合は新たに作ります。usedが-1なのは確保されているけどネイティブコードは入っていないという意味です。

    if (ci->used < 0) {
      entry = mrbjit_emit_code(mrb, status);
      if (prev_entry && entry) {
	//printf("patch %x %x \n", prev_entry, entry);
	cbase = mrb->compile_info.code_base;
	mrbjit_gen_jmp_patch(cbase, prev_entry, entry);
      }

usedがさっそく出てきました。コンパイルは当然(でもないが)まだコンパイルしていない場所が対象です。

mrbjit_emit_codeはネイティブコード生成の心臓部です。今後説明する予定ですが、jitcode.ccで定義されているので興味のある人は見てみてください。少なくとも今説明しているmrbjit_dispatchよりは読みやすいと思います。mrbjit_emit_codeは生成したネイティブコードの先頭を返します。これをentryという変数に入れます。

     if (prev_entry && entry) {
	//printf("patch %x %x \n", prev_entry, entry);
	cbase = mrb->compile_info.code_base;
	mrbjit_gen_jmp_patch(cbase, prev_entry, entry);
      }

prev_entryとは何でしょう?これは、ネイティブコードからの戻り値の1つで終了処理の先頭アドレスが入っています。つまり、ネイティブコードが実行されていなければこの値はNULLです。

このことから、このif文は次のような条件の判定になります。

 何らかの理由でコンパイル出来ず一旦VMに戻るコードを生成してしまったけど、
再度挑戦したらコンパイルに成功して続けてネイティブコードで実行出来るようになった

その場合は、邪魔な終了処理をJMP命令に書き換えて(出た!)VMに戻らずコードを直通させるようにしています。

      if (entry) {
	ci->entry = entry;
	ci->used = 1;
      }
      else {
	/* record contination patch entry */
	if (cbase) {
	  ci->entry = mrbjit_get_curr(cbase);
	}
	//	printf("set %x %x \n", ci->entry, entry);
	ci->used = -1;
	// printf("%x %x %x\n", ci->entry, *ppc, ci);
      }

ネイティブコードが生成出来たらcode infoにそのエントリーを入れます。あとは、ネイティブコードが入っているよっていう印のusedに1を入れます。

else節って意味あったけかな?いろいろ試行錯誤した跡が残ってしまった感じだから良く分からない (ヲイ)

mrbjit_dispatchの終わりのところです。

  if (cbase && entry == NULL) {
    /* Finish compile */
    mrbjit_gen_exit(cbase, mrb, irep, ppc);
    mrb->compile_info.code_base = NULL;
    mrb->compile_info.nest_level = 0;
  }

if文はコンパイル中でネイティブコードが作られなかった場合という条件で、この場合はVMに戻る処理を生成して、もはやコンパイル中ではないということを表すためcode_baseをNULLにします。そして、nest_levelを0にしてこのメソッドの中でOP_RETURNが出てきてもコンパイルしないようにします。

  mrb->compile_info.prev_pc = *ppc;

prev_pcに現在のpcの値を入れておきます。つぎに、mrbjit_dispatchが呼ばれた時はmrb_runでpcが更新されて、prev_pcと*ppcは違う値になっているはずです。

  return status->optable[GET_OPCODE(**ppc)];

正常に実行された場合のとび先を求めてmrb_runに返します。

いやー面倒なところが終わった!次は緩くjitcode.ccに行きます。OP_SENDまでは楽出来そうだ。

続く♪