mruby
mrubyDay 6

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

More than 1 year has passed since last update.

はじめに

Array#size

1. それっぽいファイルを読む

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

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

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

  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しても良さそうな気がします。

$ 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

/**
 * 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);

なるほど、mrbRClassnamefuncaspecを受けとっていますね……なんのこっちゃ。

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

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

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

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

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

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

3. mrb_ary_sizeとは?

$ 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

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 *mrbmrb_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

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_ptrmrb_ptrの結果をRArrayのポインタに変換するマクロだ、ということがわかります。

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

5. mrb_fixnum_valueの正体

$ 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

/*
 * 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の正体
  • その他いろいろ