Classクラスを使ったRuby/mrubyスクリプトのバイトコードを読んでみる
前回のKawasaki.rbでいつものパーフェクトRubyの読書で、下のような感じのコードがサンプルとして出てきた。
定数にClass.newの結果を代入すると、その定数名をクラス名とするクラスが定義される機能を初めて知った。
Rubyのバイトコードをちょっと覗いてみたいのもあり、Rubyとmrubyについて、どんな風にバイトコード上で表現されるのか、確認してみた。
サンプルコード
t="Konnichiwa"
MyClass = Class.new do |klass|
puts t
def hello
puts "Hello"
end
end
puts t
o = MyClass.new
o.hello
このコードを実行してみると。以下のような出力がされる。
Konnichiwa
Konnichiwa
Hello
Rubyのバイトコード
こちらは、Rubyのバイトコード。
https://qiita.com/nownabe/items/47cc5d95e8b4e01205a8
を参考にして、次のようなコマンドで出力した。
puts RubyVM::InstructionSequence.compile_file(ARGV[0], false).disasm
== disasm: @test.rb>=================
== catch table
| catch type: break st: 0005 ed: 0010 sp: 0000 cont: 0010
|------------------------------------------------------------------------
local table (size: 3, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 3] t [ 2] o
0000 putstring "Konnichiwa" ( 1)
0002 setlocal t, 0
0005 putnil ( 2)
0006 getconstant :Class
0008 send >
0010 putspecialobject 3
0012 setconstant :MyClass
0014 putself ( 9)
0015 getlocal t, 0
0018 send
0020 pop
0021 putnil ( 11)
0022 getconstant :MyClass
0024 send
0026 setlocal o, 0
0029 getlocal o, 0 ( 12)
0032 send
0034 leave
== disasm: @test.rb>========
== catch table
| catch type: redo st: 0000 ed: 0017 sp: 0000 cont: 0000
| catch type: next st: 0000 ed: 0017 sp: 0000 cont: 0017
|------------------------------------------------------------------------
local table (size: 2, argc: 1 [opts: 0, rest: -1, post: 0, block: -1] s3)
[ 2] klass
0000 putself ( 3)
0001 getlocal t, 1
0004 send
0006 pop
0007 putspecialobject 1 ( 4)
0009 putspecialobject 2
0011 putobject :hello
0013 putiseq hello
0015 send
0017 leave
== disasm: ==================
0000 putself ( 5)
0001 putstring "Hello"
0003 send
0005 leave
mrubyのバイトコード
こちらは、mrubyのバイトコードです。mrbcにverboseオプションをつけると出力できる。
mrbc --verbose a.rb
irep 0x7fe43940d390 nregs=6 nlocals=3 pools=1 syms=5 reps=1
file: test.rb
1 000 OP_STRING R1 L(0) ; "Konnichiwa" ; R1:t
2 001 OP_GETCONST R3 :Class
2 002 OP_LAMBDA R4 I(+1) block
2 003 OP_SENDB R3 :new 0
2 004 OP_SETCONST :MyClass R3
9 005 OP_LOADSELF R3
9 006 OP_MOVE R4 R1 ; R1:t
9 007 OP_SEND R3 :puts 1
11 008 OP_GETCONST R3 :MyClass
11 009 OP_SEND R3 :new 0
11 010 OP_MOVE R2 R3 ; R2:o
12 011 OP_SEND R3 :hello 0
12 012 OP_STOP
irep 0x7fe43940d6b0 nregs=6 nlocals=3 pools=0 syms=2 reps=1
file: test.rb
2 000 OP_ENTER 1:0:0:0:0:0:0
3 001 OP_LOADSELF R3
3 002 OP_GETUPVAR R4 1 0
3 003 OP_SEND R3 :puts 1
4 004 OP_TCLASS R3
4 005 OP_LAMBDA R4 I(+1) method
4 006 OP_METHOD R3 :hello
4 007 OP_LOADSYM R3 :hello
4 008 OP_RETURN R3 normal
irep 0x7fe43940d9a0 nregs=5 nlocals=2 pools=1 syms=1 reps=0
file: test.rb
4 000 OP_ENTER 0:0:0:0:0:0:0
5 001 OP_LOADSELF R2
5 002 OP_STRING R3 L(0) ; "Hello"
5 003 OP_SEND R2 :puts 1
5 004 OP_RETURN R2 normal
バイトコード読んでみる
mrubyのバイトコードを参考に、Rubyのバイトコードを読んでみる。自分用のメモなので、わかりにくいのは承知で、だらだらと書き連ねてみる。
Rubyのバイトコードをちゃんと見たのは初めてだけど、mrubyで読んでいたので、なんとなく内容は理解できた。正しく理解するため、Rubyのしくみをちゃんと最後まで読まねば、と思った。
mrubyのVMははレジスタマシンだが、RubyのVMはスタックマシンなので、オブジェクトをスタックに積んだり、取り出したりして、処理が行われる。
Class.newを用いたクラスの定義
getconstantで、:Classというシンボルに対応する定数。すなわちClassクラスのインスタンスをとってきて、それをレシーバとして、send(:new,block)しているようだ。
定数名がクラス名に化ける仕組みは、見てみるとsetconstantで:MyClassというシンボルを名前とする定数を定義して、それに対してClass.newの結果を代入している。
クラスオブジェクトも結局は定数であるので、定数に代入するだけで、クラスとしての名前付けと行わなくても、クラスオブジェクトとして使えていると理解した。
mrubyの場合は、newメソッドの戻り値(R3)を、OP_SETCONST
で設定した定数"MyClass"に代入している。この流れはRubyと同じようだ。
クラスを定義するブロック
ブロックの中身は、通常のクラス定義と同じような処理になっていて、defでは、実際には、iseqを引数にdefine_methodメソッドを呼び出す処理になっている。
対して、mrubyでは、OP_LAMBDAと、OP_METHOD命令でメソッドを定義している。mrubyでirepに相当するのが、iseqと思えばだいたいよさそう。
Rubyでは、クラスの定義といっても、上から順番にコードが実行されているだけなので、途中にputs t
とか定義に直接関係ないコードをおいても、クラス定義のタイミングにdef ... end
などといっしょに実行される。
クラスを定義するブロックの中での外部オブジェクト参照
もう一つ気になっていたのは、Class.newにブロックを引数として渡すと、その中でクラスの定義を行えるという処理と、ブロックであるので、クラス定義の途中で外のオブジェクトを参照できるという点。
バイトコード上でどう実装されているか見てみる。
t="Konnichiwa"
としてローカル変数に定義したtは、クラスの定義を行っているブロックの中では、getlocal t, 1
としてローカル変数から取得して使用されている。
send(:new)したときに、ローカル変数のスコープが引き継がれている、ということみたい。詳しくはsendの処理を更に理解する必要があると思われる。
mrubyの場合は、OP_GETUPVAR
で呼び出し元にレジスタをさかのぼってピンポイントで参照している。コンパイラが頑張っていることが伺える。
まとめ
Rubyとmrubyで、Classクラスを使った処理をバイトコード上で、比較してみた。
スタックマシンとレジスタマシンの違いはあるが、内容は結構似ていた。
ただ、ブロックのスコープの扱いについては違いが大きそうなので、もう少し調べてみたい。
ここまで触れていないが、Rubyのバイトコード命令中のspecialobjectが何なのかよくわからなかった。ささだんの過去の連載↓には出てきていないので、あとで追加されたもののようだ。調べてみよう。
「YARV Maniacs 【第 1 回】 『Ruby ソースコード完全解説』不完全解説」
https://magazine.rubyist.net/articles/0006/0006-YarvManiacs.html
ディスカッション
コメント一覧
まだ、コメントがありません