Ruby under a microscope - Control structures and method dispatch
Like Ruby, YARV has its own control structures. Instead id if or unless, uses two low-level instructions called branchif and branchunless. Instead of while end or until loops YARV has jump. With these instructions YARV can execute most of Ruby’s simple control structures.
When code calls a method, YARV uses the send instruction. This process is known as as method dispatch. This could be considered as another Ruby control structure.
How Ruby executes an if statement
Looking at YARV instructions, Ruby follows a pattern for implementing if else statements:
i = 0 if i < 10 else end puts "done"
- Evaluates condition, using opt_lt(optimized less-than) instruction
- Branchunless jump to false if condition is false. Ruby uses branchuless, not branchif, for if … else conditions, because the positive case is compiled to appear after the condition code. YARV needs to jump if the condition is false.
- True code, jump past false code. If conditions is true Ruby does not branch, just executes the positive case code. Once it is finished, it jumps down to the following if … else statement
- False code. Whether or not in branches, Ruby continues to execute the subsequent code.
Jumping from one scope to another
Ruby can jump from one scope to another, using break can be used to exit a loop or an iteration.
i = 0 while i<10 puts i i += 1 break end
10.times do |n| puts n break end puts "continue from here"
YARV can exit the while loop using jump instruction.
In the second example YARV needs to jump to the parent scope and continue the execution after the call to 10.times.
To implement jumping from one scope to another in the Ruby call stack, Ruby uses throw YARV instruction. Sends the execution path back to a higher scope. The throw instruction is using a catch table, or a table of pointers, that is attached to a YARV code snippet. Later, when YARV executes the throw instruction, it checks to see whether there is a catch table containing a break pointer for current YARV instruction. Ruby continues to iterate until it finds a catch table with a break pointer.
Return, rescue, ensure, retry, redo and next are using the catch table.
The break control structure is implemented similar with raising and rescuing exceptions.
Ruby has no for loop control structures, internally implements for loops using each.
The send instruction
The send instruction tells YARV to jump to another method and start executing it.
First in method lookup, Ruby searches for the method the code should call. This means looping through the classes and modules that make up the receiver object.
Internally there are 11 different types of methods. During the method dispatch process Ruby determines which type of method the code is trying to call.
Calling normal Ruby methods
When we call a standard method YARV creates a new stack frame and then starts executing the instructions in the target method.
It creates a table of local variables and arguments for each method. There are 5 types of arguments
def argument_types(a,b=1, *args, c, &d) ... end
- Standard argument
- Optional argument
- Splat argument array
- Post argument
- Block argument
Calling built-in Ruby methods
There are many methods built into the Ruby language which are CFUNC methods, implemented using C code.
Integer#times . When Ruby cals built-in CFUNC methods, doesn’t need to prepare the method arguments, it simply creates a new stack frame and calls the target method.
There are two special method types IVAR and ATTRSET to speed up the process of setting and accessing instance variables. When
attr_accessor are used, Ruby uses a C function to set the instance variable fast.