mrubyバイトコードハンドブックv1.1公開と頂いた質問への返答

Fanzine, TechJapanese, mruby

だいぶ時間が立ってしまいましたが、技術書典6で新刊「mrubyバイトコードハンドブック」を頒布してきました。

PDF版について、技術書典で頒布したものの内容を微修正したものを、対面電書とBOOTHにて、PDF版v1.1として本日更新反映しました。

紙の本とPDF版をそれぞれBOOTHで販売してます。(紙版はv1.0、PDF版はv1.1)

技術書典6の様子

一万人を超える参加差があったようで、夕方まで会場は盛況でした。いろいろな人に実際に手にとって見てもらえるのはやはり刺激的です。訪れてくださった皆様、BOOTHから購入して頂いた方に心から感謝です。

またネタが溜まってきたら同人誌書いて参加したいです。

Tech book fest 6
Tech book fest 6
mruby bytecode hand book
mruby bytecode hand book

質問

@cloudear8さんから同人誌の内容について質問を頂いたので、分かる範囲で返答したいと思います。

  • (1) R(0)は常にselfが入ると書かれていたのに対して、OP_CALLではcallの戻り値をR(0)に代入すると書かれています。これは矛盾なく両立するのでしょうか?

常に、というのは少し語弊がありました。 通常、R(0)にselfが入っていますが、irepを抜けるとき、メソッドの戻り値をセットするための場所としても使用されています。OP_RETURNの処理を追うと該当する処理が見えます。

#define regs (mrb->c->stack)
//中略
acc = ci->acc;
mrb->c->stack = ci->stackent;
cipop(mrb);
//中略
regs[acc] = v;
  • 2) OP_ONERRは命令の位置を、OP_EPUSHはirepを引数に取るようになっているかと思うんですが、この仕様の違いってなぜなんでしょうか?

例外発生時に挙動を十分理解しているわけではないので、怪しい回答になっていることをご了承下さい。

3.19 節のサンプルコードからわかるように、rescueブロックは、例外のキャッチの分岐があるので、irepではなくて、case式の実装と同じようにOP_JMPを使用した分岐の命令列になります。
ensureブロックは、irepとして表現されています。ただ、このirepの中で使用した変数もirepローカルになることはなく、元irepと同じスコープとして扱われています。

rescueブロックは、irep上の命令番地がわかれば十分で、ensureブロックはirep単位でスタックに積む必要があるので、このような使い分けになっていると思います。

rescueブロックとensureブロックの実装方法の違いの理由については、私もよくわかりません。

  • 3) 「aliasのために特別に命令を用意するほどでもないかな?」と書かれてますが、aliasってalias以外の機能で完全に代替ってできるんでしょうか?

ここではaliasというメソッドは不要という意味ではなくて、OP_ALIASを準備せずとも、OP_SEND(:alias_method)でもよいのでは、という意味です。

mruby1.4.1で同じサンプルコードをバイトコードに変換した結果は以下のとおりです。OP_ALIASの代わりに、alias_methodが実行されていることがわかります。

def funcA
end
alias funcB funcA

00003     NODE_ALIAS funcB funcA:
irep 0x7fde445010c0 nregs=5 nlocals=1 pools=0 syms=3 reps=1
file: data/code/qa02.rb
    1 000 OP_TCLASS     R1
    1 001 OP_LAMBDA     R2      I(+1)   method
    1 002 OP_METHOD     R1      :funcA
    3 003 OP_TCLASS     R1
    3 004 OP_LOADSYM    R2      :funcB
    3 005 OP_LOADSYM    R3      :funcA
    3 006 OP_LOADNIL    R4
    3 007 OP_SEND       R1      :alias_method   2
    3 008 OP_STOP

irep 0x7fde44501390 nregs=3 nlocals=2 pools=0 syms=0 reps=0
file: data/code/qa02.rb
    1 000 OP_ENTER      0:0:0:0:0:0:0
    2 001 OP_LOADNIL    R2
    2 002 OP_RETURN     R2      normal

限られた命令IDの一つを割いているので、OP_ALIASがあったほうが効率が良いケースがあるのでは、と思っています。

  • 4) OP_ARYDUPがあるのに、本の中のバイトコードではOP_ARRAYとOP_ARYCATを使って同等のことを行っている箇所が多く見られます。これは新しい命令だからバイトコード吐く側がちゃんと対応してないみたいな感じなんですかね?

まつもとさんに聞いてみないとわからないですが、気になってた点をまず対応したのかと思います。
return *[1,2]“のユースケースを、比較してみると、命令の数が減っているのに加えて、レジスタが節約できていることがわかります。

def func
        return *[1,2]
end

#mruby2.0.0
irep 0x7f956ec0fcd0 nregs=3 nlocals=1 pools=0 syms=1 reps=1
    1 000 OP_TCLASS     R1
    1 002 OP_METHOD     R2      I(0:0x7f956ec0ff80)
    1 005 OP_DEF        R1      :func
    1 008 OP_LOADSYM    R1      :func
    1 011 OP_RETURN     R1
    1 013 OP_STOP

irep 0x7f956ec0ff80 nregs=4 nlocals=2 pools=0 syms=0 reps=0
local variable names:
  R1:&
    1 000 OP_ENTER      0:0:0:0:0:0:0
    2 004 OP_LOADI_1    R2
    2 006 OP_LOADI_2    R3
    2 008 OP_ARRAY      R2      2
    2 011 OP_ARYDUP     R2
    2 013 OP_RETURN     R2

#mruby1.4.1
irep 0x7fcfedc0b140 nregs=3 nlocals=1 pools=0 syms=1 reps=1
    1 000 OP_TCLASS     R1
    1 001 OP_LAMBDA     R2      I(+1)   method
    1 002 OP_METHOD     R1      :func
    1 003 OP_LOADSYM    R1      :func
    1 004 OP_STOP

irep 0x7fcfedc0b410 nregs=5 nlocals=2 pools=0 syms=0 reps=0
    1 000 OP_ENTER      0:0:0:0:0:0:0
    2 001 OP_ARRAY      R2      R2      0
    2 002 OP_LOADI      R3      1
    2 003 OP_LOADI      R4      2
    2 004 OP_ARRAY      R3      R3      2
    2 005 OP_ARYCAT     R2      R3
    2 006 OP_RETURN     R2      normal