NODE_SUPER
最初は super のコンパイルです。親クラスの同じメソッドに処理を回すやつです。super には二種類あって、
super(100) # 引数つきsuper super # 引数なしsuper
引数つきの方は NODE_SUPER、引数なしの方は NODE_ZSUPER と構文解析の段階で分かれています。ZSUPER 版は、現在のメソッドに渡された引数をそのまま親メソッドに引き渡すという意味のRubyの構文です。
YARVアーキテクチャ の頃はYARVのマシン命令レベルでも、super と zsuper に分かれていたようですが、現在のバージョンでは invokesuper という同じ命令にまとまっています。
superとメソッド呼び出しとの違いは、おおまかには、invokesuper命令を使うかsuper命令を使うか、だけです。なので流れは先週と一緒ですね。コンパイル後の命令列は、以下の順番です。
- レシーバをスタックに積む
- 引数をスタックに積む
- ブロックが付属してればその処理
- 呼び出し (invokesuper)
case NODE_SUPER: case NODE_ZSUPER:{ ... 略 ... /* dummy reciever */ if (nd_type(node) == NODE_ZSUPER) { ADD_INSN1(ret, nd_line(node), putobject, Qfalse); } else { ADD_INSN1(ret, nd_line(node), putobject, Qtrue); }
あ、でも、superの場合レシーバはselfに決まってるので、レシーバは重要ではありません。現在の実装では、superとzsuperを区別するフラグを受け渡す用に使っていました。
DECL_ANCHOR(args); if (nd_type(node) == NODE_SUPER) { argc = setup_arg(iseq, args, node, &flag); } else { /* NODE_ZSUPER */ ... 省略 ... for (i = 0; i < liseq->argc; i++) { int idx = liseq->local_size - i; ADD_INSN1(args, nd_line(node), getlocal, INT2FIX(idx)); } } ADD_SEQ(ret, args);
続いて引数を積む命令列です。引数ありの場合は、メソッドの時と同じ補助関数setup_argが使えます。引数なしの場合は、現在のメソッドに渡された引数をコピーしてスタックに積みます。引数はローカル変数テーブルの端の方に詰まっているのでそれをgetlocalでスタックに積んでるようです。
ローカル変数は書き換わるかもしれないですが、その場合は書き換わった値が渡る...
class A def f(x); p x; end end class B<A def f(x); x="me"; super; end end B.new.f "you" # "me" を表示
...んですね。ちょっぴり意外でした。
/* block */ if (parent_block) { if (parent_block & 1) { flag |= VM_CALL_ARGS_BLOCKARG_BIT; ADD_SEQ(ret, (LINK_ANCHOR *)(parent_block & (~1))); } else { block = parent_block; } }
続いてブロックの準備。これはメソッド呼び出しの時と全く同じですね。
前回の宿題として、「この parent_block & 1 ってなんだろう?」が残ってました。じつは未だによくわからないんですけど、これは ブロックと Proc オブジェクトの分離 関係、かな。
Block と Proc を分離しない場合は、ブロックは常に「Procオブジェクトをスタックに積む」コードにコンパイルされてて、parent_block に最下位ビットONで入っていた。けど、それをやらなくなったのでこのコードは現時点では使われていない、ような気がする、みたいな。(あやふやです。)
この辺りはもう使われていないコードなので気にしなくて良いそうです。
ADD_INSN3(ret, nd_line(node), invokesuper, argc, block, INT2FIX(flag));
ともかく、最後はinvokesuper命令で終了です。