第十五章:方法

h1. Chapter 15: Methods

In this chapter, I’ll talk about method searching and invoking.

h2. Searching methods

h3. Terminology

In this chapter, both method calls and method definitions are discussed, and there will appear really various “arguments”. Therefore, to make it not confusing, let’s strictly define terms here:

  1. m(a) # a is a "normal argument"
  2. m(*list) # list is an "array argument"
  3. m(&block) # block is a "block argument"
  4.  
  5. def m(a) # a is a "normal parameter"
  6. def m(a=nil) # a is an "option parameter", nil is "it default value".
  7. def m(*rest) # rest is a "rest parameter"
  8. def m(&block) # block is a "block parameter"

In short, they are all “arguments” when passing and “parameters” when receiving, and each adjective is attached according to its type.

However, among the above things, the “block arguments” and the “block parameters” will be discussed in the next chapter.

h3. Investigation

▼The Source Program

  1. obj.method(7,8)

▼Its Syntax Tree

  1. NODE_CALL
  2. nd_mid = 9049 (method)
  3. nd_recv:
  4. NODE_VCALL
  5. nd_mid = 9617 (obj)
  6. nd_args:
  7. NODE_ARRAY [
  8. 0:
  9. NODE_LIT
  10. nd_lit = 7:Fixnum
  11. 1:
  12. NODE_LIT
  13. nd_lit = 8:Fixnum
  14. ]

The node for a method call is NODE_CALL. The nd_args holds the arguments as a list of NODE_ARRAY.

Additionally, as the nodes for method calls, there are also NODE_FCALL and NODE_VCALL. NODE_FCALL is for the “method(args)“ form, NODE_VCALL corresponds to method calls in the “method“ form that is the same form as the local variables. FCALL and VCALL could actually be integrated into one, but because there’s no need to prepare arguments when it is VCALL, they are separated from each other only in order to save both times and memories for it.

Now, let’s look at the handler of NODE_CALL in rb_eval().

rb_eval()NODE_CALL

  1. 2745 case NODE_CALL:
  2. 2746 {
  3. 2747 VALUE recv;
  4. 2748 int argc; VALUE *argv; /* used in SETUP_ARGS */
  5. 2749 TMP_PROTECT;
  6. 2750
  7. 2751 BEGIN_CALLARGS;
  8. 2752 recv = rb_eval(self, node->nd_recv);
  9. 2753 SETUP_ARGS(node->nd_args);
  10. 2754 END_CALLARGS;
  11. 2755
  12. 2756 SET_CURRENT_SOURCE();
  13. 2757 result = rb_call(CLASS_OF(recv),recv,node->nd_mid,argc,argv,0);
  14. 2758 }
  15. 2759 break;
  16.  
  17. (eval.c)

The problems are probably the three macros, BEGIN_CALLARGS SETUP_ARGS() END_CALLARGS. It seems that rb_eval() is to evaluate the receiver and rb_call() is to invoke the method, we can roughly imagine that the evaluation of the arguments might be done in the three macros, but what is actually done? BEGIN_CALLARGS and END_CALLARGS are difficult to understand before talking about the iterators, so they are explained in the next chapter “Block”. Here, let’s investigate only about SETUP_ARGS().

h3. SETUP_ARGS()

SETUP_ARGS() is the macro to evaluate the arguments of a method. Inside of this macro, as the comment in the original program says, the variables named argc and argv are used, so they must be defined in advance. And because it uses TMP_ALLOC(), it must use TMP_PROTECT in advance. Therefore, something like the following is a boilerplate:

  1. int argc; VALUE *argv; /* used in SETUP_ARGS */
  2. TMP_PROTECT;
  3.  
  4. SETUP_ARGS(args_node);

args_node is (the node represents) the arguments of the method, turn it into an array of the values obtained by evaluating it, and store it in argv. Let’s look at it:

SETUP_ARGS()

  1. 1780 #define SETUP_ARGS(anode) do {\
  2. 1781 NODE *n = anode;\
  3. 1782 if (!n) {\ no arguments
  4. 1783 argc = 0;\
  5. 1784 argv = 0;\
  6. 1785 }\
  7. 1786 else if (nd_type(n) == NODE_ARRAY) {\ only normal arguments
  8. 1787 argc=n->nd_alen;\
  9. 1788 if (argc > 0) {\ arguments present
  10. 1789 int i;\
  11. 1790 n = anode;\
  12. 1791 argv = TMP_ALLOC(argc);\
  13. 1792 for (i=0;ind_head);\
  14. 1794 n=n->nd_next;\
  15. 1795 }\
  16. 1796 }\
  17. 1797 else {\ no arguments
  18. 1798 argc = 0;\
  19. 1799 argv = 0;\
  20. 1800 }\
  21. 1801 }\
  22. 1802 else {\ both or one of an array argument
  23. 1803 VALUE args = rb_eval(self,n);\ and a block argument
  24. 1804 if (TYPE(args) != T_ARRAY)\
  25. 1805 args = rb_ary_to_ary(args);\
  26. 1806 argc = RARRAY(args)->len;\
  27. 1807 argv = ALLOCA_N(VALUE, argc);\
  28. 1808 MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc);\
  29. 1809 }\
  30. 1810 } while (0)
  31. (eval.c)

This is a bit long, but since it clearly branches in three ways, not so terrible actually. The meaning of each branch is written as comments.

We don’t have to care about the case with no arguments, the two rest branches are doing similar things. Roughly speaking, what they are doing consists of three steps:

  • allocate a space to store the arguments
  • evaluate the expressions of the arguments
  • copy the value into the variable space

If I write in the code (and tidy up a little), it becomes as follows.

  1. /***** else if clause、argc!=0 *****/
  2. int i;
  3. n = anode;
  4. argv = TMP_ALLOC(argc); /* 1 */
  5. for (i = 0; i < argc; i++) {
  6. argv[i] = rb_eval(self, n->nd_head); /* 2,3 */
  7. n = n->nd_next;
  8. }
  9.  
  10. /***** else clause *****/
  11. VALUE args = rb_eval(self, n); /* 2 */
  12. if (TYPE(args) != T_ARRAY)
  13. args = rb_ary_to_ary(args);
  14. argc = RARRAY(args)->len;
  15. argv = ALLOCA_N(VALUE, argc); /* 1 */
  16. MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc); /* 3 */

TMP_ALLOC() is used in the else if side, but ALLOCA_N(), which is ordinary alloca(), is used in the else side. Why? Isn’t it dangerous in the C_ALLOCA environment because alloca() is equivalent to malloc() ?

The point is that “in the else side the values of arguments are also stored in args“. If I illustrate, it would look like Figure 1.

!images/ch_method_anchor.jpg(Being in the heap is all right.)!

If at least one VALUE is on the stack, others can be successively marked through it. This kind of VALUE plays a role to tie up the other VALUEs to the stack like an anchor. Namely, it becomes “anchor VALUE“. In the else side, args is the anchor VALUE.

For your information, “anchor VALUE“ is the word just coined now.

h3. rb_call()

SETUP_ARGS() is relatively off the track. Let’s go back to the main line. The function to invoke a method, it is rb_call(). In the original there’re codes like raising exceptions when it could not find anything, as usual I’ll skip all of them.

rb_call() (simplified)

  1. static VALUE
  2. rb_call(klass, recv, mid, argc, argv, scope)
  3. VALUE klass, recv;
  4. ID mid;
  5. int argc;
  6. const VALUE *argv;
  7. int scope;
  8. {
  9. NODE *body;
  10. int noex;
  11. ID id = mid;
  12. struct cache_entry *ent;
  13.  
  14. /* search over method cache */
  15. ent = cache + EXPR1(klass, mid);
  16. if (ent->mid == mid && ent->klass == klass) {
  17. /* cache hit */
  18. klass = ent->origin;
  19. id = ent->mid0;
  20. noex = ent->noex;
  21. body = ent->method;
  22. }
  23. else {
  24. /* cache miss, searching step-by-step */
  25. body = rb_get_method_body(&klass, &id, &noex);
  26. }
  27.  
  28. /* ... check the visibility ... */
  29.  
  30. return rb_call0(klass, recv, mid, id,
  31. argc, argv, body, noex & NOEX_UNDEF);
  32. }

The basic way of searching methods was discussed in chapter 2: “Object”. It is following its superclasses and searching m_tbl. This is done by search_method().

The principle is certainly this, but when it comes to the phase to execute actually, if it searches by looking up its hash many times for each method call, its speed would be too slow. To improve this, in ruby, once a method is called, it will be cached. If a method is called once, it’s often immediately called again. This is known as an experiential fact and this cache records the high hit rate.

What is looking up the cache is the first half of rb_call(). Only with

  1. ent = cache + EXPR1(klass, mid);

this line, the cache is searched. We’ll examine its mechanism in detail later.

When any cache was not hit, the next rb_get_method_body() searches the class tree step-by-step and caches the result at the same time. Figure 2 shows the entire flow of searching.

!images/ch_method_msearch.jpg(Method Search)!

h3. Method Cache

Next, let’s examine the structure of the method cache in detail.

▼Method Cache

  1. 180 #define CACHE_SIZE 0x800
  2. 181 #define CACHE_MASK 0x7ff
  3. 182 #define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK)
  4. 183
  5. 184 struct cache_entry { /* method hash table. */
  6. 185 ID mid; /* method's id */
  7. 186 ID mid0; /* method's original id */
  8. 187 VALUE klass; /* receiver's class */
  9. 188 VALUE origin; /* where method defined */
  10. 189 NODE *method;
  11. 190 int noex;
  12. 191 };
  13. 192
  14. 193 static struct cache_entry cache[CACHE_SIZE];
  15.  
  16. (eval.c)

If I describe the mechanism shortly, it is a hash table. I mentioned that the principle of the hash table is to convert a table search to an indexing of an array. Three things are necessary to accomplish: an array to store the data, a key, and a hash function.

First, the array here is an array of struct cache_entry. And the method is uniquely determined by only the class and the method name, so these two become the key of the hash calculation. The rest is done by creating a hash function to generate the index (0x000 ~ 0x7ff) of the cache array form the key. It is EXPR1(). Among its arguments, c is the class object and m is the method name (ID). (Figure 3)

!images/ch_method_mhash.jpg(Method Cache)!

However, EXPR1() is not a perfect hash function or anything, so a different method can generate the same index coincidentally. But because this is nothing more than a cache, conflicts do not cause a problem. It just slows its performance down a little.

h4. The effect of Method Cache

By the way, how much effective is the method cache in actuality? We could not be convinced just by being said “it is known as …”. Let’s measure by ourselves.

|. Type |. Program |_. Hit Rate | | generating LALR(1) parser | racc ruby.y | 99.9% | | generating a mail thread | a mailer | 99.1% | | generating a document | rd2html rubyrefm.rd | 97.8% |

Surprisingly, in all of the three experiments the hit rate is more than 95%. This is awesome. Apparently, the effect of “it is know as …” is outstanding.

h2. Invocation

h3. rb_call0()

There have been many things and finally we arrived at the method invoking. However, this rb_call0() is huge. As it’s more than 200 lines, it would come to 5,6 pages. If the whole part is laid out here, it would be disastrous. Let’s look at it by dividing into small portions. Starting with the outline:

rb_call0() (Outline)

  1. 4482 static VALUE
  2. 4483 rb_call0(klass, recv, id, oid, argc, argv, body, nosuper)
  3. 4484 VALUE klass, recv;
  4. 4485 ID id;
  5. 4486 ID oid;
  6. 4487 int argc; /* OK */
  7. 4488 VALUE *argv; /* OK */
  8. 4489 NODE *body; /* OK */
  9. 4490 int nosuper;
  10. 4491 {
  11. 4492 NODE *b2; /* OK */
  12. 4493 volatile VALUE result = Qnil;
  13. 4494 int itr;
  14. 4495 static int tick;
  15. 4496 TMP_PROTECT;
  16. 4497
  17. 4498 switch (ruby_iter->iter) {
  18. 4499 case ITER_PRE:
  19. 4500 itr = ITER_CUR;
  20. 4501 break;
  21. 4502 case ITER_CUR:
  22. 4503 default:
  23. 4504 itr = ITER_NOT;
  24. 4505 break;
  25. 4506 }
  26. 4507
  27. 4508 if ((++tick & 0xff) == 0) {
  28. 4509 CHECK_INTS; /* better than nothing */
  29. 4510 stack_check();
  30. 4511 }
  31. 4512 PUSH_ITER(itr);
  32. 4513 PUSH_FRAME();
  33. 4514
  34. 4515 ruby_frame->last_func = id;
  35. 4516 ruby_frame->orig_func = oid;
  36. 4517 ruby_frame->last_class = nosuper?0:klass;
  37. 4518 ruby_frame->self = recv;
  38. 4519 ruby_frame->argc = argc;
  39. 4520 ruby_frame->argv = argv;
  40. 4521
  41. 4522 switch (nd_type(body)) {
  42. /* ... main process ... */
  43. 4698
  44. 4699 default:
  45. 4700 rb_bug("unknown node type %d", nd_type(body));
  46. 4701 break;
  47. 4702 }
  48. 4703 POP_FRAME();
  49. 4704 POP_ITER();
  50. 4705 return result;
  51. 4706 }
  52.  
  53. (eval.c)

First, an ITER is pushed and whether or not the method is an iterator is finally fixed. As its value is used by the PUSH_FRAME() which comes immediately after it, PUSH_ITER() needs to appear beforehand. PUSH_FRAME() will be discussed soon.

And if I first describe about the “… main process …” part, it branches based on the following node types and each branch does its invoking process.

| NODE_CFUNC | methods defined in C | | NODE_IVAR | attr_reader | | NODE_ATTRSET | attr_writer | | NODE_SUPER | super | | NODE_ZSUPER | super without arguments | | NODE_DMETHOD | invoke UnboundMethod | | NODE_BMETHOD | invoke Method | | NODE_SCOPE | methods defined in Ruby |

Some of the above nodes are not explained in this book but not so important and could be ignored. The important things are only NODE_CFUNC, NODE_SCOPE and NODE_ZSUPER.

h3. PUSH_FRAME()

PUSH_FRAME() POP_FRAME()

  1. 536 #define PUSH_FRAME() do { \
  2. 537 struct FRAME _frame; \
  3. 538 _frame.prev = ruby_frame; \
  4. 539 _frame.tmp = 0; \
  5. 540 _frame.node = ruby_current_node; \
  6. 541 _frame.iter = ruby_iter->iter; \
  7. 542 _frame.cbase = ruby_frame->cbase; \
  8. 543 _frame.argc = 0; \
  9. 544 _frame.argv = 0; \
  10. 545 _frame.flags = FRAME_ALLOCA; \
  11. 546 ruby_frame = &_frame
  12.  
  13. 548 #define POP_FRAME() \
  14. 549 ruby_current_node = _frame.node; \
  15. 550 ruby_frame = _frame.prev; \
  16. 551 } while (0)
  17.  
  18. (eval.c)

First, we’d like to make sure the entire FRAME is allocated on the stack. This is identical to module_setup(). The rest is basically just doing ordinary initializations.

If I add one more description, the flag FRAME_ALLOCA indicates the allocation method of the FRAME. FRAME_ALLOCA obviously indicates “it is on the stack”.

h3. rb_call0() - NODE_CFUNC

A lot of things are written in this part of the original code, but most of them are related to trace_func and substantive code is only the following line:

rb_call0()NODE_CFUNC (simplified)

  1. case NODE_CFUNC:
  2. result = call_cfunc(body->nd_cfnc, recv, len, argc, argv);
  3. break;

Then, as for call_cfunc()

call_cfunc() (simplified)

  1. 4394 static VALUE
  2. 4395 call_cfunc(func, recv, len, argc, argv)
  3. 4396 VALUE (*func)();
  4. 4397 VALUE recv;
  5. 4398 int len, argc;
  6. 4399 VALUE *argv;
  7. 4400 {
  8. 4401 if (len >= 0 && argc != len) {
  9. 4402 rb_raise(rb_eArgError, "wrong number of arguments(%d for %d)",
  10. 4403 argc, len);
  11. 4404 }
  12. 4405
  13. 4406 switch (len) {
  14. 4407 case -2:
  15. 4408 return (*func)(recv, rb_ary_new4(argc, argv));
  16. 4409 break;
  17. 4410 case -1:
  18. 4411 return (*func)(argc, argv, recv);
  19. 4412 break;
  20. 4413 case 0:
  21. 4414 return (*func)(recv);
  22. 4415 break;
  23. 4416 case 1:
  24. 4417 return (*func)(recv, argv[0]);
  25. 4418 break;
  26. 4419 case 2:
  27. 4420 return (*func)(recv, argv[0], argv[1]);
  28. 4421 break;
  29. 4475 default:
  30. 4476 rb_raise(rb_eArgError, "too many arguments(%d)", len);
  31. 4477 break;
  32. 4478 }
  33. 4479 return Qnil; /* not reached */
  34. 4480 }
  35.  
  36. (eval.c)

As shown above, it branches based on the argument count. The maximum argument count is 15.

Note that neither SCOPE or VARS is pushed when it is NODE_CFUNC. It makes sense because a method defined in C does not use Ruby’s local variables. But it simultaneously means that if the “current” local variables are accessed by C, they are actually the local variables of the previous FRAME. And in some places, say, rb_svar (eval.c), it is actually done.

h3. rb_call0() - NODE_SCOPE

NODE_SCOPE is to invoke a method defined in Ruby. This part forms the foundation of Ruby.

rb_call0()NODE_SCOPE (outline)

  1. 4568 case NODE_SCOPE:
  2. 4569 {
  3. 4570 int state;
  4. 4571 VALUE *local_vars; /* OK */
  5. 4572 NODE *saved_cref = 0;
  6. 4573
  7. 4574 PUSH_SCOPE();
  8. 4575
  9. /* (A)forward CREF */
  10. 4576 if (body->nd_rval) {
  11. 4577 saved_cref = ruby_cref;
  12. 4578 ruby_cref = (NODE*)body->nd_rval;
  13. 4579 ruby_frame->cbase = body->nd_rval;
  14. 4580 }
  15. /* (B)initialize ruby_scope->local_vars */
  16. 4581 if (body->nd_tbl) {
  17. 4582 local_vars = TMP_ALLOC(body->nd_tbl[0]+1);
  18. 4583 *local_vars++ = (VALUE)body;
  19. 4584 rb_mem_clear(local_vars, body->nd_tbl[0]);
  20. 4585 ruby_scope->local_tbl = body->nd_tbl;
  21. 4586 ruby_scope->local_vars = local_vars;
  22. 4587 }
  23. 4588 else {
  24. 4589 local_vars = ruby_scope->local_vars = 0;
  25. 4590 ruby_scope->local_tbl = 0;
  26. 4591 }
  27. 4592 b2 = body = body->nd_next;
  28. 4593
  29. 4594 PUSH_VARS();
  30. 4595 PUSH_TAG(PROT_FUNC);
  31. 4596
  32. 4597 if ((state = EXEC_TAG()) == 0) {
  33. 4598 NODE *node = 0;
  34. 4599 int i;
  35.  
  36. /* ……(C)assign the arguments to the local variables …… */
  37.  
  38. 4666 if (trace_func) {
  39. 4667 call_trace_func("call", b2, recv, id, klass);
  40. 4668 }
  41. 4669 ruby_last_node = b2;
  42. /* (D)method body */
  43. 4670 result = rb_eval(recv, body);
  44. 4671 }
  45. 4672 else if (state == TAG_RETURN) { /* back via return */
  46. 4673 result = prot_tag->retval;
  47. 4674 state = 0;
  48. 4675 }
  49. 4676 POP_TAG();
  50. 4677 POP_VARS();
  51. 4678 POP_SCOPE();
  52. 4679 ruby_cref = saved_cref;
  53. 4680 if (trace_func) {
  54. 4681 call_trace_func("return", ruby_last_node, recv, id, klass);
  55. 4682 }
  56. 4683 switch (state) {
  57. 4684 case 0:
  58. 4685 break;
  59. 4686
  60. 4687 case TAG_RETRY:
  61. 4688 if (rb_block_given_p()) {
  62. 4689 JUMP_TAG(state);
  63. 4690 }
  64. 4691 /* fall through */
  65. 4692 default:
  66. 4693 jump_tag_but_local_jump(state);
  67. 4694 break;
  68. 4695 }
  69. 4696 }
  70. 4697 break;
  71.  
  72. (eval.c)

(A) CREF forwarding, which was described at the section of constants in the previous chapter. In other words, cbase is transplanted to FRAME from the method entry.

(B) The content here is completely identical to what is done at module_setup(). An array is allocated at local_vars of SCOPE. With this and PUSH_SCOPE() and PUSH_VARS(), the local variable scope creation is completed. After this, one can execute rb_eval() in the exactly same environment as the interior of the method.

==(C)== This sets the received arguments to the parameter variables. The parameter variables are in essence identical to the local variables. Things such as the number of arguments are specified by NODE_ARGS, all it has to do is setting one by one. Details will be explained soon. And,

(D) this executes the method body. Obviously, the receiver (recv) becomes self. In other words, it becomes the first argument of rb_eval(). After all, the method is completely invoked.

h3. Set Parameters

Then, we’ll examine the totally skipped part, which sets parameters. But before that, I’d like you to first check the syntax tree of the method again.

  1. % ruby -rnodedump -e 'def m(a) nil end'
  2. NODE_SCOPE
  3. nd_rval = (null)
  4. nd_tbl = 3 [ _ ~ a ]
  5. nd_next:
  6. NODE_BLOCK
  7. nd_head:
  8. NODE_ARGS
  9. nd_cnt = 1
  10. nd_rest = -1
  11. nd_opt = (null)
  12. nd_next:
  13. NODE_BLOCK
  14. nd_head:
  15. NODE_NEWLINE
  16. nd_file = "-e"
  17. nd_nth = 1
  18. nd_next:
  19. NODE_NIL
  20. nd_next = (null)

NODE_ARGS is the node to specify the parameters of a method. I aggressively dumped several things, and it seemed its members are used as follows:

| nd_cnt | the number of the normal parameters | | nd_rest | the variable ID of the rest parameter. -1 if the rest parameter is missing | | nd_opt | holds the syntax tree to represent the default values of the option parameters. a list of NODE_BLOCK |

If one has this amount of the information, the local variable ID for each parameter variable can be uniquely determined. First, I mentioned that 0 and 1 are always $_ and $~. In 2 and later, the necessary number of ordinary parameters are in line. The number of option parameters can be determined by the length of NODE_BLOCK. Again next to them, the rest-parameter comes.

For example, if you write a definition as below,

  1. def m(a, b, c = nil, *rest)
  2. lvar1 = nil
  3. end

local variable IDs are assigned as follows.

  1. 0 1 2 3 4 5 6
  2. $_ $~ a b c rest lvar1

Are you still with me? Taking this into considerations, let’s look at the code.

rb_call0()NODE_SCOPE −assignments of arguments

  1. 4601 if (nd_type(body) == NODE_ARGS) { /* no body */
  2. 4602 node = body; /* NODE_ARGS */
  3. 4603 body = 0; /* the method body */
  4. 4604 }
  5. 4605 else if (nd_type(body) == NODE_BLOCK) { /* has body */
  6. 4606 node = body->nd_head; /* NODE_ARGS */
  7. 4607 body = body->nd_next; /* the method body */
  8. 4608 }
  9. 4609 if (node) { /* have somewhat parameters */
  10. 4610 if (nd_type(node) != NODE_ARGS) {
  11. 4611 rb_bug("no argument-node");
  12. 4612 }
  13. 4613
  14. 4614 i = node->nd_cnt;
  15. 4615 if (i > argc) {
  16. 4616 rb_raise(rb_eArgError, "wrong number of arguments(%d for %d)",
  17. 4617 argc, i);
  18. 4618 }
  19. 4619 if (node->nd_rest == -1) { /* no rest parameter */
  20. /* counting the number of parameters */
  21. 4620 int opt = i; /* the number of parameters (i is nd_cnt) */
  22. 4621 NODE *optnode = node->nd_opt;
  23. 4622
  24. 4623 while (optnode) {
  25. 4624 opt++;
  26. 4625 optnode = optnode->nd_next;
  27. 4626 }
  28. 4627 if (opt < argc) {
  29. 4628 rb_raise(rb_eArgError,
  30. 4629 "wrong number of arguments(%d for %d)", argc, opt);
  31. 4630 }
  32. /* assigning at the second time in rb_call0 */
  33. 4631 ruby_frame->argc = opt;
  34. 4632 ruby_frame->argv = local_vars+2;
  35. 4633 }
  36. 4634
  37. 4635 if (local_vars) { /* has parameters */
  38. 4636 if (i > 0) { /* has normal parameters */
  39. 4637 /* +2 to skip the spaces for $_ and $~ */
  40. 4638 MEMCPY(local_vars+2, argv, VALUE, i);
  41. 4639 }
  42. 4640 argv += i; argc -= i;
  43. 4641 if (node->nd_opt) { /* has option parameters */
  44. 4642 NODE *opt = node->nd_opt;
  45. 4643
  46. 4644 while (opt && argc) {
  47. 4645 assign(recv, opt->nd_head, *argv, 1);
  48. 4646 argv++; argc--;
  49. 4647 opt = opt->nd_next;
  50. 4648 }
  51. 4649 if (opt) {
  52. 4650 rb_eval(recv, opt);
  53. 4651 }
  54. 4652 }
  55. 4653 local_vars = ruby_scope->local_vars;
  56. 4654 if (node->nd_rest >= 0) { /* has rest parameter */
  57. 4655 VALUE v;
  58. 4656
  59. /* make an array of the remainning parameters and assign it to a variable */
  60. 4657 if (argc > 0)
  61. 4658 v = rb_ary_new4(argc,argv);
  62. 4659 else
  63. 4660 v = rb_ary_new2(0);
  64. 4661 ruby_scope->local_vars[node->nd_rest] = v;
  65. 4662 }
  66. 4663 }
  67. 4664 }
  68.  
  69. (eval.c)

Since comments are added more than before, you might be able to understand what it is doing by following step-by-step.

One thing I’d like to mention is about argc and argv of ruby_frame. It seems to be updated only when any rest-parameter does not exist, why is it only when any rest-parameter does not exist?

This point can be understood by thinking about the purpose of argc and argv. These members actually exist for super without arguments. It means the following form:

  1. super

This super has a behavior to directly pass the parameters of the currently executing method. To enable to pass at the moment, the arguments are saved in ruby_frame->argv.

Going back to the previous story here, if there’s a rest-parameter, passing the original parameters list somehow seems more convenient. If there’s not, the one after option parameters are assigned seems better.

  1. def m(a, b, *rest)
  2. super # probably 5, 6, 7, 8 should be passed
  3. end
  4. m(5, 6, 7, 8)
  5.  
  6. def m(a, b = 6)
  7. super # probably 5, 6 should be passed
  8. end
  9. m(5)

This is a question of which is better as a specification rather than “it must be”. If a method has a rest-parameter, it supposed to also have a rest-parameter at superclass. Thus, if the value after processed is passed, there’s the high possibility of being inconvenient.

Now, I’ve said various things, but the story of method invocation is all done. The rest is, as the ending of this chapter, looking at the implementation of super which is just discussed.

h3. super

What corresponds to super are NODE_SUPER and NODE_ZSUPER. NODE_SUPER is ordinary super, and NODE_ZSUPER is super without arguments.

rb_eval()NODE_SUPER

  1. 2780 case NODE_SUPER:
  2. 2781 case NODE_ZSUPER:
  3. 2782 {
  4. 2783 int argc; VALUE *argv; /* used in SETUP_ARGS */
  5. 2784 TMP_PROTECT;
  6. 2785
  7. /*(A)case when super is forbidden */
  8. 2786 if (ruby_frame->last_class == 0) {
  9. 2787 if (ruby_frame->orig_func) {
  10. 2788 rb_name_error(ruby_frame->last_func,
  11. 2789 "superclass method `%s' disabled",
  12. 2790 rb_id2name(ruby_frame->orig_func));
  13. 2791 }
  14. 2792 else {
  15. 2793 rb_raise(rb_eNoMethodError,
  16. "super called outside of method");
  17. 2794 }
  18. 2795 }
  19. /*(B)setup or evaluate parameters */
  20. 2796 if (nd_type(node) == NODE_ZSUPER) {
  21. 2797 argc = ruby_frame->argc;
  22. 2798 argv = ruby_frame->argv;
  23. 2799 }
  24. 2800 else {
  25. 2801 BEGIN_CALLARGS;
  26. 2802 SETUP_ARGS(node->nd_args);
  27. 2803 END_CALLARGS;
  28. 2804 }
  29. 2805
  30. /*(C)yet mysterious PUSH_ITER() */
  31. 2806 PUSH_ITER(ruby_iter->iter?ITER_PRE:ITER_NOT);
  32. 2807 SET_CURRENT_SOURCE();
  33. 2808 result = rb_call(RCLASS(ruby_frame->last_class)->super,
  34. 2809 ruby_frame->self, ruby_frame->orig_func,
  35. 2810 argc, argv, 3);
  36. 2811 POP_ITER();
  37. 2812 }
  38. 2813 break;
  39.  
  40. (eval.c)

For super without arguments, I said that ruby_frame->argv is directly used as arguments, this is directly shown at (B).

==(C)== just before calling rb_call(), doing PUSH_ITER(). This is also what cannot be explained in detail, but in this way the block passed to the current method can be handed over to the next method (meaning, the method of superclass that is going to be called).

And finally, (A) when ruby_frame->last_class is 0, calling super seems forbidden. Since the error message says “must be enabled by rb_enable_super()“, it seems it becomes callable by calling rb_enable_super().
((errata: The error message “must be enabled by rb_enable_super()“ exists not in this list but in rb_call_super().))
Why?

First, If we investigate in what kind of situation last_class becomes 0, it seems that it is while executing the method whose substance is defined in C (NODE_CFUNC). Moreover, it is the same when doing alias or replacing such method.

I’ve understood until there, but even though reading source codes, I couldn’t understand the subsequents of them. Because I couldn’t, I searched “rb_enable_super“ over the ruby‘s mailing list archives and found it. According to that mail, the situation looks like as follows:

For example, there’s a method named String.new. Of course, this is a method to create a string. String.new creates a struct of T_STRING. Therefore, you can expect that the receiver is always of T_STRING when writing an instance methods of String.

Then, super of String.new is Object.new. Object.new create a struct of T_OBJECT. What happens if String.new is replaced by new definition and super is called?

  1. def String.new
  2. super
  3. end

As a consequence, an object whose struct is of T_OBJECT but whose class is String is created. However, a method of String is written with expectation of a struct of T_STRING, so naturally it downs.

How can we avoid this? The answer is to forbid to call any method expecting a struct of a different struct type. But the information of “expecting struct type” is not attached to method, and also not to class. For example, if there’s a way to obtain T_STRING from String class, it can be checked before calling, but currently we can’t do such thing. Therefore, as the second-best plan, “super from methods defined in C is forbidden” is defined. In this way, if the layer of methods at C level is precisely created, it cannot be got down at least. And, when the case is “It’s absolutely safe, so allow super“, super can be enabled by calling rb_enable_super().

In short, the heart of the problem is miss match of struct types. This is the same as the problem that occurs at the allocation framework.

Then, how to solve this is to solve the root of the problem that “the class does not know the struct-type of the instance”. But, in order to resolve this, at least new API is necessary, and if doing more deeply, compatibility will be lost. Therefore, for the time being, the final solution has not decided yet.