はじめに
自分が書いたRubyのコードがどのような道のりを辿って実行されるのか興味を持ったことはあるでしょうか。
RubyのしくみによるとRubyコードは以下の流れでYARV命令へと変換されます。
Rubyコード
↓
字句解析
↓
構文解析
↓
コンパイル
↓
YARV命令
この記事では、YARV命令の部分をピックアップし、YARV命令がコードによってどう変わるのか簡単に見ていきます。
なお、今回利用したRuby処理系、Rubyバージョンは以下の通りです。
Ruby処理系 | Rubyバージョン |
---|---|
MRI | 2.6.1 |
YARV命令表示
まず、YARV命令を表示してみます。
簡単なRubyのコード 4 + 3
のYARV命令をみてみましょう。YARV命令は RubyVM::InstructionSequence
を使うことで見ることができます。
irb(main):001:0> puts RubyVM::InstructionSequence.compile('4+3').disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,3)> (catch: FALSE)
0000 putobject 4 ( 1)[Li]
0002 putobject 3
0004 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0007 leave
=> nil
putobject
でスタックに値をプッシュし、opt_plus
で加算を実行するのが確認できます。
すなわち、以下の流れでコードが実行されます。
-
4
をスタックにプッシュ -
3
をスタックにプッシュ - 加算(
4 + 3
)
4 + 3 * 2
vs (4 + 3) * 2
続いて、4 + 3 * 2
と(4 + 3) * 2
のYARV命令を表示して結果を比べてみます。
irb(main):001:0> puts RubyVM::InstructionSequence.compile('4+3*2').disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,5)> (catch: FALSE)
0000 putobject 4 ( 1)[Li]
0002 putobject 3
0004 putobject 2
0006 opt_mult <callinfo!mid:*, argc:1, ARGS_SIMPLE>, <callcache>
0009 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0012 leave
=> nil
irb(main):001:0> puts RubyVM::InstructionSequence.compile('(4+3)*2').disasm
== disasm: #<ISeq:<compiled>@<compiled>:1 (1,0)-(1,7)> (catch: FALSE)
0000 putobject 4 ( 1)[Li]
0002 putobject 3
0004 opt_plus <callinfo!mid:+, argc:1, ARGS_SIMPLE>, <callcache>
0007 putobject 2
0009 opt_mult <callinfo!mid:*, argc:1, ARGS_SIMPLE>, <callcache>
0012 leave
=> nil
上記2つを比べるとopt_plus
の位置が変わっていることに気がつきます。
それぞれ以下の流れでコードが実行されます。
・ 4 + 3 * 2
-
4
をスタックにプッシュ -
3
をスタックにプッシュ -
2
をスタックにプッシュ - 乗算(
3 * 2
) - 加算(
4 + 6
)
・ (4 + 3) * 2
-
4
をスタックにプッシュ -
3
をスタックにプッシュ - 加算(
4 + 3
) -
2
をスタックにプッシュ - 乗算(
7 * 2
)
命令の順番によって、計算結果が変わることが確認できます。
まとめ
コードを少し変えることでYARV命令がどう変わるのかみてきました。
普段YARVの内部スタックの状態を意識することはなかったですが、より深いところまで理解できるよう知識を深めていきたいものです。