mrubyを読む
はじめに †
初期化の際に呼び出されるのはmrb_open()関数のようです。
mrb_open(src/state.c) †
それでは早速mrb_open()を見てみましょう。
1
2
3
4
5
6
7
|
-
|
|
|
!
| mrb_state*
mrb_open()
{
mrb_state *mrb = mrb_open_allocf(allocf);
return mrb;
}
|
というわけでmrb_open_allocf()に委譲です。
allocf(src/state.c) †
mrb_open_allocf()に進む前にallocfとは何者なのか見てみましょう。allocfはmrb_open()のすぐ上に書かれています。
1
2
3
4
5
6
7
8
9
10
11
|
-
-
|
|
!
-
|
!
!
| static void*
allocf(mrb_state *mrb, void *p, size_t size)
{
if (size == 0) {
free(p);
return NULL;
}
else {
return realloc(p, size);
}
}
|
というわけでallocfとはメモリ確保・解放関数のようです。mrubyは組み込み向け*1なので独自のメモリアロケーターを使いやすいようになっているのでしょう。
allocfに書いてあることをそのまま書くとallocf、というかmrb_open_allocf()に渡すmrb_allocfの仕様は以下のようになっているようです。
- 第2引数で指定されているメモリ領域を第3引数で指定した量に拡張する(第2引数がNULLの場合は単純に第3引数分メモリ確保する)
- 第3引数が0の場合は第2引数が指すメモリ領域を開放する
2つ目の仕様がなんか気にくわないですがallocfの呼び出しをwrapした馴染みの関数が定義されているのでメモリ解放時に0指定で呼び出すという気持ち悪いことはしないでもOKです*2。
src/gc.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
-
|
!
-
|
!
-
|
|
|
|
!
-
|
!
| void*
mrb_realloc(mrb_state *mrb, void *p, size_t len)
{
return (mrb->allocf)(mrb, p, len);
}
void*
mrb_malloc(mrb_state *mrb, size_t len)
{
return (mrb->allocf)(mrb, 0, len);
}
void*
mrb_calloc(mrb_state *mrb, size_t nelem, size_t len)
{
void *p = (mrb->allocf)(mrb, 0, nelem*len);
memset(p, 0, nelem*len);
return p;
}
void*
mrb_free(mrb_state *mrb, void *p)
{
return (mrb->allocf)(mrb, p, 0);
}
|
ちなみに、freeが別関数になっていないのは気持ち悪いとか0サイズ指定だとfreeなのは気にくわないとか書いてますが、少しでもmrb_state構造体のサイズを減らす努力かもと思います。う〜ん、でもそんなにメモリが厳しい環境って、allocf中のif文の方が時間的・空間的につらくなるような気も。
mrb_open_allocf(src/state.c) †
さて、話が長くなりましたがmrb_open_allocf()です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
|
|
|
|
|
|
|
|
|
|
!
| mrb_state*
mrb_open_allocf(mrb_allocf f)
{
mrb_state *mrb = (f)(NULL, NULL, sizeof(mrb_state));
memset(mrb, 0, sizeof(mrb_state));
mrb->allocf = f;
mrb->current_white_part = MRB_GC_WHITE_A;
mrb_init_heap(mrb);
mrb_init_core(mrb);
mrb_init_ext(mrb);
return mrb;
}
|
見ての通り、mrb_state構造体を確保した後、ヒープやコアライブラリと思えるものを初期化しています。なお、current_white_partというのはGC関連の変数です。
mrb_init_heap()は普通にヒープを初期化しているだけなので飛ばしてmrb_init_core()に進みます。
mrb_init_core(src/init.c) †
mrb_init_core()ですが予想通りにRubyオブジェクトスペースの構築を行っているようです。それらに入っていく前に一つ面白い点があったので取り上げます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
| void
mrb_init_core(mrb_state *mrb)
{
mrb_init_symtbl(mrb);
mrb_init_class(mrb);
mrb_init_object(mrb);
mrb_init_kernel(mrb);
mrb_init_comparable(mrb);
mrb_init_enumerable(mrb);
mrb_init_symbols(mrb);
mrb_init_proc(mrb);
mrb_init_string(mrb);
mrb_init_array(mrb);
mrb_init_hash(mrb);
mrb_init_numeric(mrb);
mrb_init_range(mrb);
mrb_init_struct(mrb);
mrb_init_gc(mrb);
#ifdef INCLUDE_REGEXP
mrb_init_regexp(mrb);
mrb_init_encoding(mrb);
#endif
mrb_init_exception(mrb);
mrb_init_print(mrb);
mrb_init_time(mrb);
mrb_init_math(mrb);
mrb_init_mrblib(mrb);
mrb_gc_arena_restore(mrb, 0);
}
|
RegexpとEncodingの構築は条件コンパイルになっています。正規表現や文字エンコーディングの処理は大きめなので容易にカットできるようにしているのでしょう*3。その場合、リテラルで書いてある正規表現とかがどうなるかは興味深いところですがそれはまたパーサを読むときに見ることにします。
さて、各initに進みます。まず初めのmrb_init_symtbl()は予想通りに名前からシンボルID、シンボルIDから名前のハッシュテーブルを作成しています。
mrb_init_class(src/class.c) †
次にmrb_init_class()を見てみます。BasicObject, Object, Module, Classを構築しています。普通のクラスを定義するための関数はmrb_define_class()ですが、BasicObject, Object, Module, Classの構築ではmrb_define_class()中のクラス構築ステップをばらして呼ばれています。mrb_define_class()と照らし合わせて見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
|
bob = boot_defclass(mrb, 0);
obj = boot_defclass(mrb, bob); mrb->object_class = obj;
mod = boot_defclass(mrb, obj); mrb->module_class = mod;
cls = boot_defclass(mrb, mod); mrb->class_class = cls;
bob->c = obj->c = mod->c = cls->c = cls;
make_metaclass(mrb, bob);
make_metaclass(mrb, obj);
make_metaclass(mrb, mod);
make_metaclass(mrb, cls);
mrb_define_const(mrb, obj, "BasicObject", mrb_obj_value(bob));
mrb_define_const(mrb, obj, "Object", mrb_obj_value(obj));
mrb_define_const(mrb, obj, "Module", mrb_obj_value(mod));
mrb_define_const(mrb, obj, "Class", mrb_obj_value(cls));
mrb_name_class(mrb, bob, mrb_intern(mrb, "BasicObject"));
mrb_name_class(mrb, obj, mrb_intern(mrb, "Object"));
mrb_name_class(mrb, mod, mrb_intern(mrb, "Module"));
mrb_name_class(mrb, cls, mrb_intern(mrb, "Class"));
|
mrb_define_class()とmrb_define_class()から呼ばれる関数は以下のようになります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
-
|
|
|
!
-
|
|
|
|
|
|
|
!
-
|
|
-
|
!
|
|
|
|
!
-
|
|
-
|
!
|
|
-
|
!
-
|
!
|
|
|
!
| struct RClass*
mrb_define_class(mrb_state *mrb, const char *name, struct RClass *super)
{
struct RClass *c;
c = mrb_define_class_id(mrb, mrb_intern(mrb, name), super);
return c;
}
struct RClass*
mrb_define_class_id(mrb_state *mrb, mrb_sym name, struct RClass *super)
{
struct RClass *c = mrb_class_new(mrb, super);
mrb_obj_iv_set(mrb, (struct RObject*)mrb->object_class,
name, mrb_obj_value(c));
mrb_name_class(mrb, c, name);
return c;
}
struct RClass *
mrb_class_new(mrb_state *mrb, struct RClass *super)
{
struct RClass *c;
if (super) {
mrb_check_inheritable(mrb, super);
}
c = boot_defclass(mrb, super);
make_metaclass(mrb, c);
return c;
}
static void
make_metaclass(mrb_state *mrb, struct RClass *c)
{
struct RClass *sc;
if (c->c->tt == MRB_TT_SCLASS) {
return;
}
sc = (struct RClass*)mrb_obj_alloc(mrb, MRB_TT_SCLASS, mrb->class_class);
sc->mt = 0;
if (!c->super) {
sc->super = mrb->class_class;
}
else {
sc->super = c->super->c;
}
c->c = sc;
mrb_field_write_barrier(mrb, (struct RBasic*)c, (struct RBasic*)sc);
mrb_field_write_barrier(mrb, (struct RBasic*)sc, (struct RBasic*)sc->super);
}
|
さて、というわけでBasicObject, Object, Module, Classがmrb_define_class()で作れない理由がおわかりいただけたかと思います。つまり、
- クラス名をobject_classに登録しようにもobject_classがまだ生成されていない(mrb_define_const(mrb, obj, "BasicObject", mrb_obj_value(bob))とmrb_obj_iv_set(mrb, (struct RObject*)mrb->object_class, name, mrb_obj_value(c))は等価です)
- メタクラスを設定しようにもclass_classがまだ生成されていない
というわけです。
mrb_define_method(src/class.c) †
さて、クラスを定義したので次はメソッドです。メソッドを定義する関数はmrb_define_method()です。いくつか定義例を見てみましょう。
1
2
3
4
5
6
|
| mrb_define_method(mrb, bob, "initialize", mrb_bob_init, ARGS_NONE());
mrb_define_method(mrb, bob, "!", mrb_bob_not, ARGS_NONE());
mrb_define_method(mrb, bob, "method_missing", mrb_bob_missing, ARGS_ANY());
mrb_define_method(mrb, cls, "new", mrb_instance_new, ARGS_ANY());
mrb_define_method(mrb, cls, "inherited", mrb_bob_init, ARGS_REQ(1));
mrb_define_method(mrb, mod, "include", mrb_mod_include, ARGS_REQ(1));
|
大体、CRubyと同じです。最後の引数だけやや違う。ARGS_*の定義はinclude/mruby.hに書かれています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
|
#define ARGS_REQ(n) (((n)&0x1f) << 19)
#define ARGS_OPT(n) (((n)&0x1f) << 14)
#define ARGS_REST() (1 << 13)
#define ARGS_POST(n) (((n)&0x1f) << 8)
#define ARGS_KEY(n1,n2) ((((n1)&0x1f) << 3) | ((n2)?(1<<2):0))
#define ARGS_BLOCK() (1 << 1)
#define ARGS_ANY() ARGS_REST()
#define ARGS_NONE() 0
|
というわけで引数の種類を示すビット情報になっているようです。
では、mrb_define_method()に進みます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
-
|
!
-
|
|
|
|
|
!
| void
mrb_define_method(mrb_state *mrb, struct RClass *c, const char *name, mrb_func_t func, int aspec)
{
mrb_define_method_id(mrb, c, mrb_intern(mrb, name), func, aspec);
}
void
mrb_define_method_id(mrb_state *mrb, struct RClass *c, mrb_sym mid, mrb_func_t func, int aspec)
{
struct RProc *p;
p = mrb_proc_new_cfunc(mrb, func);
p->target_class = c;
mrb_define_method_raw(mrb, c, mid, p);
}
|
というわけで、mrubyではメソッドはProcにラップされるようです。
最後に、登録されている関数をいくつか見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
-
|
!
-
|
!
-
|
!
| static mrb_value
mrb_bob_not(mrb_state *mrb, mrb_value cv)
{
...
}
static mrb_value
mrb_mod_include(mrb_state *mrb, mrb_value klass)
{
...
}
mrb_value
mrb_instance_new(mrb_state *mrb, mrb_value cv)
{
...
}
|
わかることとしては、引数の数によらず登録する関数は
mrb_stateとmrb_valueを受け取り、mrb_valueを返す
という形式であるということです。関数の呼び出される仕組みや関数側での引数の取り出しなどは後ほど実行系を読む際に見ていきたいと思います。
mrb_define_class_method(src/class.c) †
クラスメソッドの定義についても説明しておきましょう。
1
2
3
4
5
|
-
|
!
| void
mrb_define_class_method(mrb_state *mrb, struct RClass *c, const char *name, mrb_func_t func, int aspec)
{
mrb_define_method_id(mrb, c->c, mrb_intern(mrb, name), func, aspec);
}
|
c->cとはなんでしょうか?答えは上の方にありますがメタクラスです。Ruby業界ではクラスメソッドとはクラスオブジェクトの特異メソッドであるということは常識だと思いますがそれを知らないと「なんでこれでクラスメソッドを定義したことになるんだぁ!?」と悩むことになると思うので解説しておきます。
mrb_init_mrblib(mrblib/init_mrblib.c) †
後はまったり読み流せばいいやと思いきやまた面白いコードがありました。
src/compar.c
1
2
3
4
5
|
-
|
!
| void
mrb_init_comparable(mrb_state *mrb)
{
mrb_define_module(mrb, "Comparable");
}
|
なんとComparableはモジュールだけ定義されていてメソッドが定義されていません。大バグです。まつもとさんにバグレポを送りましょう。ではなくて、Comparableの各メソッドは別のところに定義されています。mrblibディレクトリを見るとComparableの各関数がRubyコードで定義されています。
どういうカラクリかというと実はmrubyのビルドプロセスは2つに分かれていて、
- ステップ1
- コア部分のビルド
- ステップ2
- Rubyで書かれたコードをコンパイルしてライブラリに追加
ということをしています。ステップ2のカラクリを担当しているのがmrb_init_mrblib()で以下のようなコードになってます。
1
2
3
4
5
6
7
8
9
10
|
-
|
|
|
|
!
| extern const char mrblib_irep[];
void
mrb_init_mrblib(mrb_state *mrb)
{
int n = mrb_read_irep(mrb, mrblib_irep);
extern mrb_value mrb_top_self(mrb_state *mrb);
mrb_run(mrb, mrb_proc_new(mrb, mrb->irep[n]), mrb_top_self(mrb));
}
|
実行系はまだ読んでいませんがなんとなく、「コンパイルしたRubyコードを実行してんだろーなー」ということはわかると思います。Rubiniusもこんな風にRubyコードでライブラリを定義していると聞いた覚えがあります。見たことはありません。
mrb_init_ext(src/init_ext.c) †
さて初期化の最後にmrb_init_ext()を見てみましょう。
1
2
3
4
5
6
7
8
|
-
|
|
|
|
!
| void
mrb_init_ext(mrb_state *mrb)
{
#ifdef INCLUDE_SOCKET
extern void mrb_init_socket(mrb_state *mrb);
mrb_init_socket(mrb);
#endif
}
|
CRubyでは拡張ライブラリを動的ロードする仕組みがありますがmrubyは組み込み向けなので動的ロードはサポートせず拡張ライブラリも常に読み込まれた状態にするという思想なのでしょう、多分。
mrb_state(include/mruby.h) †
今まで特に取り上げずに進めてきましたが、mrubyのオブジェクトスペース、メモリ管理、実行時情報などはmrb_stateに収められているようです。まあ、さわだの解説スタンスは処理の流れメインで構造サブなので折に触れて構造体内の各メンバーについて触れることにしたいと思います。
おわりに †
というわけでmrubyの初期化、mrb_open()から始まる世界を見てきました。感想としては、
- CRubyの知識はmruby読解の役に立つ
- いろいろと組み込み向けと思われるケアがある
といったところでしょうか。