mrubyのソースコード(https://github.com/mruby/mruby/)をざっと眺めてみて、気づいたことをメモ。
たどり方としては、tools/mruby/mruby.c から開き、ぱっと見で気になった所から掘り下げてみていく感じで。
typedef struct mrb_value { union { mrb_float f; void *p; mrb_int i; mrb_sym sym; } value; enum mrb_vtype tt:8; } mrb_value;
なんてこった!VALUEやめちゃったのか。Fixnumを«1して保存するやり方、上手いなあと思っていたけどmrubyでは採用しなかったみたい。 とすると、よりRiteVMのHW化が生きてきそう。
mrbc_contextにfilenameとlineno(=1)をセットするだけ。
allaca_headerのサイズを足してmrb_mallocしたブロック。alloca_headerは一方方向のリンクリストでnextポインタを持つだけ。 mrb→memsが最終要素のポインタ(ブロック先頭ではなくデータ領域先頭)を保持している。
NULLからのmrb_reallocするだけ。
mrb→allocfに任せる。ただし、失敗した場合はmrb_garbage_collectしてから一度だけ再試行する。
これはそのまんま。reallocしてmemset。
これもmrb→allocfに任せる。サイズ0へのreallocとして実行される。
# test.rb 10.times {|n| puts n }
RITEファイル(*.mrb)は、ほとんどテキストファイルである。数値等は16進数表記されている。つまり整数値をそのまま16進表記にしたもので、プレフィックス(0x)やサフィックス(h)は付けない。 使える文字は [0-9A-Fa-f] である。それ以外の数値は 0 として扱われる(hex2bin @ load.c)。 ファイル中の改行コード(\r,\n)は無視される。また、#から\r,\n,\0まではコメントとして無視される(ignorecomment != 0の場合)。 なお、コメント終端の\0は非コメントのデータ扱い。コメント終端の\rや\nは前述どおり無視(rite_fgetc @ load.c)。
バイナリヘッダ(rite_binary_header)とは微妙に異なるので混同注意!
メンバ名 | サイズ | データ例 | 説明 | |
---|---|---|---|---|
rbfi | Rite Binary File Identify | 4 | “RITE” | 固定 |
rbfv | Rite Binary File Format Version | 8 | “00090000” | 固定 |
risv | Rite Instruction Specification Version | 8 | “00090000” | |
rct | Rite Compiler Type | 8 | “MATZ□□□□” 1) | |
rcv | Rite Compiler Version | 8 | “00090000” | |
rbds | Rite Binary Data Size | 8 | “000000C5” | |
nirep | Number of ireps | 4 | “0002” | |
sirep | Start index | 4 | “0000” | |
rsv | Reserved | 8 | “□□□□□□□□” | |
hcrc | HCRC | 4 | “2BBD” | |
Total Size | 64 |
名前 | サイズ | データ例 | 説明 | |
---|---|---|---|---|
record len | 8 | “0000005F” | ||
IREP HEADER BLOCK | ||||
record identifier | 1 | “S” | 固定 | |
class or module | 1 | “C” | ||
num of local variables | 4 | “0001” | ||
num of register variables | 4 | “0003” | ||
offset of isec block | 4 | “0004” | ||
header CRC | 4 | “469F” | ||
class or module name | offset-4 | 長さ0 | ||
ISEQ BLOCK | ||||
iseq length (blocklen) | 8 | “00000004” | ||
iseq | 8×blocklen | “00C00483” “01000340” “00800021” “0000004A” | LOADI; R(1) := 10 LAMBDA; R(2) := lambda(SEQ[0+1], OP_L_CAPTURE) SENDB; R(1) := call(R(1), mSym(0), &R(2)) STOP; |
|
iseq CRC | 4 | “61F6” | ||
POOL BLOCK | ||||
pool length (blocklen) | 8 | “00000000” | ||
TT | (2) | |||
pool data length (pdl) | (4) | |||
pool | (pdl) | |||
×blocklen | ||||
pool CRC | 4 | “0000” | ||
SYMS BLOCK | ||||
syms length (blocklen) | 8 | “00000001” | ||
0 | symbol name length (snl) | (4) | “0005” | |
symbol name | (snl) | “times” | ||
×blocklen | ||||
syms CRC | 4 | “94E7” |
名前 | サイズ | データ例 | 説明 | |
---|---|---|---|---|
record len | 8 | “00000066” | ||
IREP HEADER BLOCK | ||||
record identifier | 1 | “S” | 固定 | |
class or module | 1 | “C” | ||
num of local variables | 4 | “0003” | ||
num of register variables | 4 | “0005” | ||
offset of isec block | 4 | “0004” | ||
header CRC | 4 | “4839” | ||
class or module name | offset-4 | 長さ0 | ||
ISEQ BLOCK | ||||
iseq length (blocklen) | 8 | “00000005” | ||
iseq | 8×blocklen | “02000026” “01800006” “02004001” “018000A0” “01800029” | ENTER(1:0:0:0:0:0:0); LOADSELF; R(3) := self MOVE; R(4) := R(1) SEND; R(3) := call(R(3), mSym(0), R(4)) RETURN; return R(3) [R_NORMAL] |
|
iseq CRC | 4 | “FA4E” | ||
POOL BLOCK | ||||
pool length (blocklen) | 8 | “00000000” | ||
TT | (2) | |||
pool data length (pdl) | (4) | |||
pool | (pdl) | |||
×blocklen | ||||
pool CRC | 4 | “0000” | ||
SYMS BLOCK | ||||
syms length (blocklen) | 8 | “00000001” | ||
0 | symbol name length (snl) | (4) | “0004” | |
symbol name | (snl) | “puts” | ||
×blocklen | ||||
syms CRC | 4 | “2489” |
名前 | サイズ | データ例 | 説明 | |
---|---|---|---|---|
record len | 8 | “00000000” | ||
EOF |
上記RITEファイルをバイナリに変換したもの。コメントを除去したり、16進表記の部分を変換したりするだけで、mrbファイルと大差はない。 なお、16進数表記はそのままの並びでバイナリに変換される。つまり、ビッグエンディアン配置となる。
ファイルヘッダ(rite_file_header)とは微妙に異なるので混同注意!
メンバ名 | サイズ | データ例 | 説明 | |
---|---|---|---|---|
rbfi | Rite Binary File Identify | 4 | “RITE” | 固定 |
rbfv | Rite Binary File Format Version | 8 | “00090000” | |
risv | Rite Instruction Specification Version | 8 | “00090000” | 固定 |
rct | Rite Compiler Type | 8 | “MATZ□□□□” 2) | |
rcv | Rite Compiler Version | 8 | “00090000” | |
rbds | Rite Binary Data Size | 4 | 0x000000C5 | バイナリに変換 |
nirep | Number of ireps | 2 | 0x0002 | バイナリに変換 |
sirep | Start index | 2 | 0x0000 | バイナリに変換 |
rsv | Reserved | 8 | “□□□□□□□□” | |
Total Size | 52 |
なお、file_header.hcrcはこの構造体全体のCRC16(CCITT)である。
TBW
まだ頭の中でまとめられていない。ここはメモ的なもの。まとまったら書き直す。
現在居るメソッドやブロックの情報を保持する構造体。スタックフレーム情報みたいなもの。 この構造体自体もスタック構造で管理されている。(cipush関数、cipop関数参照)
今居るブロックの情報と、戻り先の情報がまぜこぜになっていて非常にややこしいので整理する。
変更されるタイミングで整理すると、以下の通りになる。
operation | ci | ci→ | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
mid | proc | nregs | argc | target_class | ridx | eidx | stackidx | pc | acc | env | stack | ||
OP_ONERR sBx | ++ | ||||||||||||
OP_POPERR A | -= A | ||||||||||||
OP_RAISE A | |||||||||||||
OP_EPUSH Bx | ++ | Proc(SEQ[Bx])→env3) | |||||||||||
OP_EPOP A (右記をA回繰り返す) | ++ci | 不変 | p=ensure[i] | p→body.irep→nregs | 0 | p→target_class | 不変 | -- | stack-stbase | 無効4) | -1 | NULL | += ci[-1].nregs |
OP_SEND A,B,C OP_SENDB A,B,C | ++ci | Sym(B) | Sym(B)のproc | C+25)(CFUNCの場合) irep→nregs(非CFUNCの場合) | C6) | R(A)→c | 不変7) | 不変 | stack-stbase | pc+1 | A | NULL | += A |
OP_SUPER A,C | ci[-1]→mid | ci[-1]→midのproc | 28)(CFUNCの場合) irep→nregs(非CFUNCの場合) | C9) | m→target_class | ||||||||
OP_CALL | m→env→mid | m | m→target_class |
前述のcallinfoはメソッドやブロックの呼び出しをそのままコールツリーとして記録するものだが、envは「ソースコード上の見た目のツリー」を記録するためのもの。 主な使い道はUPVARである。要するに、ブロックから上位のブロック/メソッドにある変数へ直接アクセスする際のアクセス経路を提供するもの。例を使って説明する。
class Array def each(&block) n = 0 while (n < self.size) block.call(self[n]) end end end def hoge total = 0 # R2 [1,2,3].each {|v| # R3=[R3,R4,R5], R4=blockA # blockA # R4=v, R3=total total += v # R3=R3+R4, total=R3, return R3 } total end
この例の場合、callinfoは以下のようになる(Proc#callが0段として)。
level | type | name |
---|---|---|
0 | method | Object.hoge |
1 | method | [1,2,3].each |
2 | block | blockA |
一方のenvは、クロージャの作成(closure_setup)のタイミングで生成され、ツリーが形成される。 クロージャの生成は、OP_LAMBDA(OP_L_CAPTUREが付いている場合)またはOP_EPUSHにて行われる。 ci→envが未作成の場合、クロージャ生成で新しいenvが作られる。現在のenv(ci→proc→env)を親とし10)(NULLの場合もある)、flagsに現在のローカル変数数、mid、ciの数、stack位置を記録する。ci→envは新envで更新される。 ci→envが作成済みの場合は、proc→envにci→envがセットされるだけである。要するに、UPVARで戻って参照される場合のみ、envを作るようになっている。(パフォーマンスのためかな?)
つまりは、ブロックを渡す側で自分のstackにアクセスできるようproc→envを設定してやるようになっている。