mrubyコードリーディング Array#size編

## はじめに
- 1.3.0の時点でのコードを対象としています (https://github.com/mruby/mruby/tree/1.3.0)
- 筆者のC言語の経験は高専の授業で数年触った程度です

## `Array#size`
### 1. それっぽいファイルを読む
`size`なんていう識別子をgrepすると死ぬのが見えているので、まずはそれっぽいファイルを探っていきます。

まずは、`src/array.c`といういかにもそれっぽいファイルがあるので、その中を`size`でgrepしてそれっぽい行を探します。

https://github.com/mruby/mruby/blob/1.3.0/src/array.c#L1174

```c
  mrb_define_method(mrb, a, "rindex",          mrb_ary_rindex_m,     MRB_ARGS_REQ(1)); /* 15.2.12.5.26 */
  mrb_define_method(mrb, a, "shift",           mrb_ary_shift,        MRB_ARGS_NONE()); /* 15.2.12.5.27 */
  mrb_define_method(mrb, a, "size",            mrb_ary_size,         MRB_ARGS_NONE()); /* 15.2.12.5.28 */
  mrb_define_method(mrb, a, "slice",           mrb_ary_aget,         MRB_ARGS_ANY());  /* 15.2.12.5.29 */
  mrb_define_method(mrb, a, "unshift",         mrb_ary_unshift_m,    MRB_ARGS_ANY());  /* 15.2.12.5.30 */
  mrb_define_method(mrb, a, "prepend",         mrb_ary_unshift_m,    MRB_ARGS_ANY());
```

いかにもそれっぽいですね。`mrb_define_method`という関数(？)で`size`を定義しているような雰囲気を感じます。

### 2. `mrb_define_method`とは？
さて、`mrb_define_method`とは一体何者でしょうか。これは普通にgrepしても良さそうな気がします。

```shell-session
$ git status
HEAD detached at 1.3.0
nothing to commit, working tree clean

$ git grep mruby_define_method | head
include/mruby.h: *           mrb_define_method(mrb, mrb->kernel_module, "example_method", example_method, MRB_ARGS_NONE());
include/mruby.h:MRB_API void mrb_define_method(mrb_state *mrb, struct RClass *cla, const char *name, mrb_func_t func, mrb_aspec aspec);
include/mruby.h: *       mrb_define_method(mrb, example_class_a, "example_method", mrb_example_method, MRB_ARGS_NONE());
include/mruby.h: *        mrb_define_method(mrb, example_class, "example_method", exampleMethod, MRB_ARGS_NONE());
include/mruby/class.h:MRB_API void mrb_define_method_raw(mrb_state*, struct RClass*, mrb_sym, struct RProc *);
include/mruby/class.h:MRB_API void mrb_define_method_id(mrb_state *mrb, struct RClass *c, mrb_sym mid, mrb_func_t func, mrb_aspec aspec);
mrbgems/mruby-array-ext/src/array.c:  mrb_define_method(mrb, a, "assoc",  mrb_ary_assoc,  MRB_ARGS_REQ(1));
mrbgems/mruby-array-ext/src/array.c:  mrb_define_method(mrb, a, "at",     mrb_ary_at,     MRB_ARGS_REQ(1));
mrbgems/mruby-array-ext/src/array.c:  mrb_define_method(mrb, a, "rassoc", mrb_ary_rassoc, MRB_ARGS_REQ(1));
mrbgems/mruby-array-ext/src/array.c:  mrb_define_method(mrb, a, "values_at", mrb_ary_values_at, MRB_ARGS_ANY());
```

よく見てみると、`include/mruby.h`に書かれている`mrb_define_method`には、先頭に型の定義(`void`)が付いていていかにもという感じがします。というわけで、`include/mruby.h`の中身を見に行きます。

https://github.com/mruby/mruby/blob/1.3.0/include/mruby.h#L312

```c
/**
 * Defines a global function in ruby.
 *
 * If you're creating a gem it may look something like this
 *
 * Example:
 *
 *     !!!c
 *     mrb_value example_method(mrb_state* mrb, mrb_value self)
 *     {
 *          puts("Executing example command!");
 *          return self;
 *     }
 *
 *     void mrb_example_gem_init(mrb_state* mrb)
 *     {
 *           mrb_define_method(mrb, mrb->kernel_module, "example_method", example_method, MRB_ARGS_NONE());
 *     }
 *
 * @param [mrb_state *] mrb The MRuby state reference.
 * @param [struct RClass *] cla The class pointer where the method will be defined.
 * @param [const char *] name The name of the method being defined.
 * @param [mrb_func_t] func The function pointer to the method definition.
 * @param [mrb_aspec] aspec The method parameters declaration.
 */
MRB_API void mrb_define_method(mrb_state *mrb, struct RClass *cla, const char *name, mrb_func_t func, mrb_aspec aspec);
```

なるほど、`mrb`と`RClass`と`name`と`func`と`aspec`を受けとっていますね……なんのこっちゃ。

落ち着いてYARDを読むと……

| 引数 | 正体 |
| ---: | --- |
| `mrb_state *mrb` | mrubyの状態への参照 |
| `struct RClass *cla` | そのメソッドが定義されるクラスへの参照 |
| `const char *name` | 定義されるメソッドの名前 |
| `mrb_func_t func` | メソッド定義への関数ポインタ |
| `mrb_aspec aspec` | メソッド引数の宣言 |

ということのようですね。

ということは、`Array#size`の本体は、先ほどのコードで第4引数に渡されている`mrb_ary_size`っぽいことがわかります。

```c
mrb_define_method(mrb, a, "size", mrb_ary_size, MRB_ARGS_NONE());
```

`mrb_define_method`の本体は一旦置いておいて、`mrb_ary_size`のほうを見に行きましょう。

### 3. `mrb_ary_size`とは？
```shell-session
$ git status
HEAD detached at 1.3.0
nothing to commit, working tree clean

$ git grep mrb_ary_size
src/array.c:mrb_ary_size(mrb_state *mrb, mrb_value self)
src/array.c:  mrb_define_method(mrb, a, "length",          mrb_ary_size,         MRB_ARGS_NONE()); /* 15.2.12.5.19 */
src/array.c:  mrb_define_method(mrb, a, "size",            mrb_ary_size,         MRB_ARGS_NONE()); /* 15.2.12.5.28 */
```

`mrb_ary_size`の本体は、`src/array.c`にありそうです。

https://github.com/mruby/mruby/blob/1.3.0/src/array.c#L975

```c
static mrb_value
mrb_ary_size(mrb_state *mrb, mrb_value self)
{
  struct RArray *a = mrb_ary_ptr(self);

  return mrb_fixnum_value(a->len);
}
```

はい、よくわかりません。

第1引数の`mrb_state *mrb`は`mrb_define_method`にも渡していたので、`mrb_define_method`経由で渡されるような気がします。第2引数は自分自身でしょうか。とりあえず今は深追いせずに、関数本体の処理を見てみます。

まず`struct RArray *a = mrb_ary_ptr(self)`ですが、どうやら`self`からarrayのポインタを取得して`RArray`型の構造体(のポインタ)に格納しているようです。

次に`return mrb_fixnum_value(a->len);`ですが、先程の構造体の`len`を取得してmrubyのfixnumに変換して返しているように見えます。

それぞれ見ていきましょう。

### 4. `struct RArray`の正体と`mrb_ary_ptr`
`struct RArray`というくらいですから、定義は`include/mruby/array.h`でしょう。見に行ってみます。

https://github.com/mruby/mruby/blob/1.3.0/include/mruby/array.h#L24

```c
struct RArray {
  MRB_OBJECT_HEADER;
  mrb_int len;
  union {
    mrb_int capa;
    mrb_shared_array *shared;
  } aux;
  mrb_value *ptr;
};

#define mrb_ary_ptr(v)    ((struct RArray*)(mrb_ptr(v)))
```

なんとその1行下に`mrb_ary_ptr`のマクロ定義もありました。(syntax highlightがおかしいような……)

`RArray`構造体には`len`フィールドがあり、`mrb_ary_ptr`は`mrb_ptr`の結果を`RArray`のポインタに変換するマクロだ、ということがわかります。

`len`フィールドにいつ値が格納されるのか、とかは一旦無視して、次は`mrb_fixnum_value`を見に行きます。

### 5. `mrb_fixnum_value`の正体
```shell-session
$ git status
HEAD detached at 1.3.0
nothing to commit, working tree clean

$ git grep mrb_fixnum_value | head
include/mruby.h: *            mrb_define_const(mrb, mrb->kernel_module, "AGE", mrb_fixnum_value(22));
include/mruby.h: *        mrb_funcall(mrb, obj, "method_name", 1, mrb_fixnum_value(i));
include/mruby/value.h:MRB_INLINE mrb_value mrb_fixnum_value(mrb_int i)
mrbgems/mruby-array-ext/src/array.c:        mrb_fixnum_value(i)
mrbgems/mruby-array-ext/src/array.c:        mrb_fixnum_value(i),
mrbgems/mruby-array-ext/src/array.c:        mrb_fixnum_value(RARRAY_LEN(v))
mrbgems/mruby-compiler/core/codegen.c:      str = mrb_format(mrb, "$%S", mrb_fixnum_value((mrb_int)(intptr_t)tree));
mrbgems/mruby-compiler/core/codegen.c:          int off = new_lit(s, mrb_fixnum_value(i));
mrbgems/mruby-compiler/core/codegen.c:              int off = new_lit(s, mrb_fixnum_value(i));
mrbgems/mruby-compiler/core/parse.y:    mrb_bug(p->mrb, "Internal error in backref_error() : n=>car == %S", mrb_fixnum_value(c));
```

定義は`include/mruby/value.h`にありそうですね。見に行きましょう。

https://github.com/mruby/mruby/blob/1.3.0/include/mruby/value.h#L194

```c
/*
 * Returns a fixnum in Ruby.
 */
MRB_INLINE mrb_value mrb_fixnum_value(mrb_int i)
{
  mrb_value v;
  SET_INT_VALUE(v, i);
  return v;
}
```

はい、そうですか……。ちなみに`SET_INT_VALUE`の定義を見に行こうとしてgrepしたのですが、深淵を見そうになったので一旦ここまでで止まります。

## まとめ
「mrubyにおける`Array#size`の実装は`src/array.c`にある`mrb_ary_size`である。`mrb_ary_size`では、`RArray`構造体の`len`フィールドをmrubyのfixnumに変換して返している」

## 今後の課題
* `mrb_define_method`の本体
* `SET_INT_VALUE`の正体
* その他いろいろ