- レジスタマシンのようだ
- レジスタは mrb_context で保持する stack に積んであるようだ
- mrb_vm_exec() では regs マクロでレジスタを取り出す
- TODO: どのくらいの種類のレジスタがあるのか確認
- mrb_code (uint32_t の typedef) で表現される
- 0 個以上 3 個以下とる
- オペランドについては A, B, C という名前になる
- オペランド修飾子
- なにも付かない場合: 9 ビットサイズになる
- オペランドの前にs: signed, 符号付き
- オペランドの後ろに x: (25 - 他のオペランドサイズ)の値になる
- オペランドの後ろに z: よーわからん
- めも: 9 ビットとかってなんだろう?どこか符号ビットにしているのか?
- 0 個以上 3 個以下のオペランドをとる
- mruby/opecode.h に enum で定義されている
- 各オペコードに対応する VM における処理は vm.c の mrb_vm_exec() を読むこと
- OP_NOP
- なにもしない
- OP_MOVE A B
- B を A にコピーする
- OP_LOADL A Bx
- Bx のプールの値を A にコピーする????
- OP_LOADI A sBx
- I は Int?
- sBx の値を Int 型とみなして A にコピーする???
- OP_LOADSYM A Bx
- SYM は symbol のはず
- Bx の値を Symbol 型とみなして A にコピーする???
- OP_LOADNIL A
- A に nil をコピーする
- OP_LOADSELF A
- A に self をコピーする
- self は regs[0] に相当する
- OP_LOADT A
- A に true をコピーする
- OP_LOADF A
- A に false をコピーする
- OP_JMP sBx
- sBx へ jump する
- pc を sBx の値に書き換えて break (JUMP マクロ)
- OP_JMPIF A sBx
- もし A の値が true なら sBx へ jump する
- A の評価には mrb_test マクロを使う
- OP_JMPNOT A sBx
- OP_JMPIF の逆バージョン
- A の値が false なら sBx へ jump する
...
- ローカル変数
- nlocals にローカル変数の数が格納される
- 16 ビットに収まる範囲であればローカル変数保持できるのか?
- レジスタ
- nregs にレジスタ数が格納される
- 16 ビットに(ry
- フラグ
- irep の解釈の仕方を制御するフラグ
- FLAG_BYTEORDER_BIG
- ビッグエンディアンフラグ
- bin_to_uint32() でビッグエンディアンとして iseq を解釈
- FLAG_BYTEORDER_LIL
- リトルエンディアンフラグ
- bin_to_uint32l() でリトルエンディアンとして iseq を解釈
- FLAG_BYTEORDER_NATIVE
- ネイティブのエンディアンに従うフラグ
- memcpy で iseq を一気にコピー
- FLAG_SRC_MALLOC
- バイトコード読み出しの際に mrb_malloc した領域に解釈した内容をコピーする
- FLAG_SRC_STATIC
- バイトコード読み出しの際にコピーせず、元のバイトコードへのポインタを保持する
- iseq
- コードセグメント(実体は mrb_code の配列)の先頭番地へのポインタ
- RiteVM に処理させたい際は、 iseq を pc に突っ込んで開始する
- pool
- syms
- mrb_sym の配列
- reps
- 続きの? mrb_irep のリスト
typedef struct mrb_irep {
uint16_t nlocals;
uint16_t nregs;
uint8_t flags;
mrb_code \*iseq;
mrb_value \*pool;
mrb_sym \*syms;
struct mrb_irep \*\*reps;
struct mrb_locals \*lv;
const char \*filename;
uint16_t \*lines;
struct mrb_irep_debug_info\* debug_info;
size_t ilen, plen, slen, rlen, refcnt;
} mrb_irep;
- mruby コマンドってやつ。コンパイル前のスクリプト .rb 、もしくはコンパイル済バイトコード .mrb を実行する
- mrbgems/mruby-bin-mruby/ にコードあり
- mrb_load_irep_file_cxt() などを読んでバイトコードを読み込み、実行
- 実行処理は mrb_top_run() を呼ぶことで行う
- RiteVM の実行状態や実行対象の手続きに関していくつものデータ構造が存在する
- http://qiita.com/miura1729/items/822a18051e8a97244dc3 が参考になりそう。
- C で mrbgem を実装しようとするとちらほら目にする構造体
- mruby の VM の状態を保持
- 各基本クラスへのポインタやGC情報、グローバル変数などを格納する
- RiteVM の実行コンテキストをもつ
struct mrb_context \*c;
struct mrb_context \*root_c;
- mruby 組み込みクラスの定義へのポインタを持つ
- これらはほぼ全て(全部じゃないよね?) init された後は書き換わることは無い
- string.c とか init 関数毎に代入箇所が散らばっているので注意!
struct RClass \*object_class;
struct RClass \*class_class;
struct RClass \*module_class;
struct RClass \*proc_class;
struct RClass \*string_class;
struct RClass \*array_class;
struct RClass \*hash_class;
struct RClass \*float_class;
struct RClass \*fixnum_class;
struct RClass \*true_class;
struct RClass \*false_class;
struct RClass \*nil_class;
struct RClass \*symbol_class;
struct RClass \*kernel_module;
- prev
- 以前のコンテキスト
- 例えば Fiber における親 fiber のコンテキスト
- 例えば ... ほかにある?
- ...
- status
- そのコンテキストの持ち主となる fiber の実行状態
- 詳しくは Fiber の項目を参考
- fib
- そのコンテキストの持ち主となる fiber へのポインタ
- そもそも fiber で実行していない場合...どうなる? NULL ?
struct mrb_context {
struct mrb_context \*prev;
mrb_value \*stack;
mrb_value \*stbase, \*stend;
mrb_callinfo \*ci;
mrb_callinfo \*cibase, \*ciend;
mrb_code \*\*rescue;
int rsize;
struct RProc \*\*ensure;
int esize;
enum mrb_fiber_state status;
mrb_bool vmexec;
struct RFiber \*fib;
};
- メソッド呼び出しに関する情報を保持?
- 与えられた引数の数など
- mruby の Proc オブジェクト型
- このオブジェクトを VM で実行して mruby で処理を行うイメージ
- バイトコードを読み出して、 mrb_irep の形式に当てはめる
- まず read_binary_header() でヘッダを読み出す
- 次の処理は各セクションを探して read_section_*() 関数で irep に変換していく
- irep セクションを読み出す
- mrb_irep のうち重要なメンバが読み出される
- irep セクションは必須のものになる。あとはオプションのはず
- lineno セクションを読み出す
- mrb_irep のうち filename, lines が読み出される
- debug セクションを読み出す
- mrb_irep のうち debug_info が読み出される
- lv セクションを読み出す
- mrb_irep のうち lv が読み出される
- irep として解釈するバイトコードは下記のようなフォーマットになってる?
+---------------------+ | binary header | +---------------------+ | irep section header | +---------------------+ | irep section body | +---------------------+ | any* section header | +---------------------+ | any* section body | +---------------------+ *: irep, lineno, debug or lv
- rite_binary_header 構造体で表現される
- フォーマットは下記の通り:
- 4 バイト: バイナリ ID。基本的に "RITE" という 4 文字が書かれる
- 4 バイト: バイナリバージョン。本文書の執筆段階では "0003"
- 2 バイト: バイナリの CRC
- 4 バイト: バイナリサイズ
- 4 バイト: コンパイラ名。 mrbc でビルドした場合は "MATZ"
- 4 バイト: コンパイラバージョン
- 渡された mrb_state 上で RProc を実行する
- 実際には mrb_vm_run() を呼んでいるだけ
- 渡された mrb_state 上で RProc を実行する
- mrb_run() より色々前準備をする場合がある
- が、いまいちよくわからん。あとでよむ
- stack_extend() でレジスタの置き場所をスタック上に(必要な領域になるように)作ってる?
- スタックに積んだレジスタ R0 (って呼ぶべきなのか?) に self を格納しておく
- mrb_vm_exec() を読んで VM のメインループ開始
- 最初の処理開始位置である引数の pc に irep->iseq を渡している TODO: irep について読む
- INIT_DISPATCH マクロ
- pc, プログラムカウンタから次に解釈すべき命令を fetch する
- CODE_FETCH_HOOK マクロを実行。なにこれ?
- switch 文でオペコード毎の処理に分岐
- バイトコードを読み、 irep に変換して実行する
- mrb_read_irep() でバイトコードから irep を生成する
- 生成した irep から RProc を生成する
- 実行処理は mrb_top_run() を呼び出すことで行う
- mrb_load_irep_cxt() を第三引数 NULL で呼び出す
- mrb_irep をバイトコードに dump する
- 第 2 引数に dump 対象の mrb_irep を指定する
- 第 3 引数は dump 結果のフォーマットを指定
- DUMP_DEBUG_INFO: デバッグ情報を出力する
- DUMP_ENDIAN_BIG: ビッグエンディアンで出力
- DUMP_ENDIAN_LIL: リトルエンディアンで出力
- DUMP_ENDIAN_NAT: ネイティブのエンディアンで出力
- DUMP_ENDIAN_MASK: 上記 3 フラグを取り出す際のビットマスク
- 第 4,5 引数に dump 結果のバイトコードがセットされる。これらは内部的に mrb_malloc() で領域確保がされる