Advent of YARV: Part 18 - Super methods

This blog series is about how the CRuby virtual machine works. If you’re new to the series, I recommend starting from the beginning. This post is about super methods.

For the most part, invoking super methods is very similar to calling normal methods. In this way, invokesuper (the instruction used to invoke a super method) is very similar to send. There are, however, a couple of key differences.

Let’s look at the definition of the send:

/* invoke method. */
DEFINE_INSN
send
(CALL_DATA cd, ISEQ blockiseq)
(...)
(VALUE val)
// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd->ci);
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
{
    VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, false);
    val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);

    if (val == Qundef) {
        RESTORE_REGS();
        NEXT_INSN();
    }
}

And now the invokesuper instruction:

/* super(args) # args.size => num */
DEFINE_INSN
invokesuper
(CALL_DATA cd, ISEQ blockiseq)
(...)
(VALUE val)
// attr rb_snum_t sp_inc = sp_inc_of_sendish(cd->ci);
// attr rb_snum_t comptime_sp_inc = sp_inc_of_sendish(ci);
{
    VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, true);
    val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_super);

    if (val == Qundef) {
        RESTORE_REGS();
        NEXT_INSN();
    }
}

You’ll notice they’re incredibly similar. The two differences are the final argument to vm_caller_setup_arg_block which tells that function to forward the block handler if a block wasn’t explicitly given, and the final argument to vm_sendish which tells that function how to search for the method to call.

Explicit arguments

There are two ways to invoke super methods. If you pass any arguments or use parentheses, then Ruby assumes you are specifying the entire method signature. Let’s look at an example:

class Foo
  def perform(left, right)
    left + right
  end
end

class Bar < Foo
  def perform(left, right)
    super(left, right)
  end
end

The Bar#perform method disassembles to:

== disasm: #<ISeq:perform@test.rb:8 (8,2)-(10,5)> (catch: false)
local table (size: 2, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] left@0<Arg>[ 1] right@1<Arg>
0000 putself                                                          (   9)[LiCa]
0001 getlocal_WC_0                          left@0
0003 getlocal_WC_0                          right@1
0005 invokesuper                            <calldata!argc:2, FCALL|ARGS_SIMPLE|SUPER>, nil
0008 leave                                                            (  10)[Re]

You can see that the call data operand to invokesuper has the flag SUPER and an argument count of 2. This tells the VM that the method being invoked is a super method and that it has two arguments. The second operand is nil, since it has no block instruction sequence.

Implicit arguments

The second way to invoke super methods is to use no parentheses or arguments. In these cases, Ruby assumes you are passing all of the same arguments as you received in the current method. For example:

class Foo
  def perform(left, right)
    left + right
  end
end

class Bar < Foo
  def perform(left, right)
    super
  end
end

The Bar#perform method now disassembles to:

== disasm: #<ISeq:perform@test.rb:8 (8,2)-(10,5)> (catch: false)
local table (size: 2, argc: 2 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 2] left@0<Arg>[ 1] right@1<Arg>
0000 putself                                                          (   9)[LiCa]
0001 getlocal_WC_0                          left@0
0003 getlocal_WC_0                          right@1
0005 invokesuper                            <calldata!argc:2, FCALL|ARGS_SIMPLE|SUPER|ZSUPER>, nil
0008 leave                                                            (  10)[Re]

You can see the disassembly now includes the ZSUPER flag, which stands for “zero super”. This tells the VM that no arguments were passed and to assume the behavior of forwarding all of the arguments.

Wrapping up

In this post we covered the invokesuper instruction. We saw that it is effectively the same as the send instruction but with a different method lookup algorithm. A couple of things to remember from this post:

In the next post we’ll keep to the trend of looking at one instruction at a time and take a look at the defined instruction.

← Back to home