前説
この記事は
Ruby スクリプトが CRuby(MRI) でどう変換されてどう実行されるのか、興味があって見てみたいと思って調べた結果についての記事、になるはずだったものです。
書きかけで投げ出してしまっているので、そういうのが嫌な人にはおすすめしません。
要するに
--dump に感動したのでみんな使おうぜ、あと使っててわかったことがあったら共有しようぜ、という、それだけです。あと、わかりにくい出力が一部あったので、そこについて説明しています。
CRuby のオプション --dump=XXX
とは
CRuby には、--dump=XXX というとても素敵なオプションを渡すことができます。
これを指定すると、Ruby スクリプトは実行されず、そのコードが ruby の内部でどのような形で表現されるのかを知ることができます。
XXX に入るもの
XXX の部分は本当に XXX と書くわけではなく、以下の 2 つのいずれかを渡すことができます。
- insns
- ruby の VM (YARV とか呼ばれている(いた?)あいつです) の命令列を見ることができます
- parsetree
- 上の VM 命令列にコンパイルされるより前の段階の、「ノード」と言われる中間表現をみることができます
動作例
スクリプトの用意
動作を確認するために、スクリプトを用意します。
とりあえず簡単に、a,b = nil
でいってみたいと思います。
(なぜ a = nil
でないかというと、多重代入の動きが見たかったからです)
まずはプレーン
最初に、--dump なしで動かしてみました。
$ ruby -e 'a, b = nil'
$
p
も puts
もしていないので何も出力されずに少し寂しいですが、想定通りです。
insns
次に、VM の命令列 insns です。
$ ruby --dump=insns -e 'a, b = nil'
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 3, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 3] a [ 2] b
0000 trace 1 ( 1)
0002 putnil
0003 dup
0004 expandarray 2, 0
0007 setlocal_OP__WC__0 3
0009 setlocal_OP__WC__0 2
0011 leave
$
なんだかいっぱい出ています。
a
と b
の 2 つのローカル変数が定義されていて、内部的には a を 3 番、b を 2 番と呼んでる、っぽいような、そんなことが 3 行目から読み取れる、ような気がします。
へー、タイプ 1 で trace
するんだー、スタックに nil
をおいて dup
してから expandarray
したものでそれぞれ setlocal
するんだー 3 番(ローカル変数 a)と 2 番(ローカル変数 b)にねー、ふーん、などと読んでいきます。
何言っているかわからないと思います。書いてる本人もわかりません。
補足
とりあえずここでは大まかなイメージだけわかればいいと思います。
これより深いところは VM の動作の領域であって今回の「内部表現をチラ見する」という趣旨とは外れるので割愛します。
本当は
わかってないので説明できないだけです。すみません。
詳しい人の詳しい話を見たり聞いたりしてください。
parsetree
次に、ノード表現 parsetree です。
$ ruby --dump=parsetree -e 'a, b = nil'
###########################################################
## Do NOT use this node dump for any purpose other than ##
## debug and research. Compatibility is not guaranteed. ##
###########################################################
# @ NODE_SCOPE (line: 1)
# +- nd_tbl: :a,:b
# +- nd_args:
# | (null node)
# +- nd_body:
# @ NODE_MASGN (line: 1)
# +- nd_value:
# | @ NODE_NIL (line: 1)
# +- nd_head:
# | @ NODE_ARRAY (line: 1)
# | +- nd_alen: 2
# | +- nd_head:
# | | @ NODE_DASGN_CURR (line: 1)
# | | +- nd_vid: :a
# | | +- nd_value:
# | | (null node)
# | +- nd_next:
# | @ NODE_ARRAY (line: 1)
# | +- nd_alen: 140562125983720
# | +- nd_head:
# | | @ NODE_DASGN_CURR (line: 1)
# | | +- nd_vid: :b
# | | +- nd_value:
# | | (null node)
# | +- nd_next:
# | (null node)
# +- nd_args:
# (null node)
$
はじめに注意書きが出力されます。互換性とか保証されないからデバッグと調査にしか使うなよーとのこと。
やはり最初に、a
と b
の 2 つのローカル変数があるということを言っている、ような気がします。
ははあ MASGN というのは multi assign、多重代入のことかな、nd_value には 右辺が、nd_head には左辺が入っているっぽいな、などと読んでいきます。
不審な数字
下の方の行に、nd_alen: 140562125983720 という、なんだかよくわからないものが出力されています。なんでしょうか。
といったところで
実はこの値は nd_alen じゃなくて nd_end なんだよー同じ NODE_ARRAY でも u2 の使い方が二通りあるんだよーという話を、どう書くのがいいかわからなくなってきました。ぶっちゃけ面倒になってきて投げ出しました。
ただ、別にバグではないんだよということと、--dump が便利だということは言いたかったので、もうこの状態で公開してしまいます。
詳しく知りたい人はソース読んだり詳しい人に聞いてみてください。
詳しい人はどこかで解説してくれたりすると嬉しいです。