5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

mrubyAdvent Calendar 2017

Day 6

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

Last updated at Posted at 2017-12-05

はじめに

Array#size

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

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

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

  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の中身を見に行きます。

/**
 * 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にありそうです。

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でしょう。見に行ってみます。

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にありそうですね。見に行きましょう。

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?