* Shiba VM [#y3d91a37] #contents シンプルでもいける気がしてきた。 ** レジスタの概要 [#b2e56c51] - 1つのレジスタのサイズは8バイト。 - 4バイトでもいいかなと思ったが,64bit環境でポインタが1レジスタで表現できなくなるため,8バイトにした。 - ただし,メモリがきつきつの環境もあるので32bitモード用意する。その場合,64bitの整数・浮動小数,64bitのアドレス空間は扱えなくなる。 - 1つのレジスタはUNIONで表現。 #code(c,){{ struct Register { union { bool boolVal; u32 data32; u64 data64; s32 i32val; s64 i64val; f32 f32val; f64 f64val; void* ptr; // 64bit環境だとこれは8バイト。 }; }; }} ** レジスタ構成 [#ka053590] |Full Name|Short Name|Comment|h |Stack Register Pointer|SRP|スタックレジスタポインタ。&br; スタックレジスタの先頭アドレス。 &br;言い換えるとSR00の場所。| |Stack Pointer|SP|スタックポインタ。現在のスタック位置。| |Program Counter|PC|プログラムカウンタ。現在実行しているバイトコードのポインタ。| |Program Function|PF|プログラムの関数オブジェクトのポインタ。デバッグ用。| |Program Line|PL|プログラムのソースコードの行数。デバッグ用。| |Link Register|LR|リンクレジスタ。戻り先のバイトコードのポインタ。| |Function Register|FR|関数レジスタ。全部で16個。FR00 - FR16。| #code(c,){{ struct SEVMContext { // 関数開始時に保存されるレジスタ達。 struct SaveReg { void* srp; void* lr; SEFunction* pf; uint32 pl; }; SaveReg saveReg; // 保存されないレジスタ達。 void* sp; void* pc; }; }} ** スタックレジスタ [#bb1584f7] - 計算の一時領域として使うレジスタはStackRegister(スタックレジスタ)呼び,SRと略す。 - スタックレジスタ数は動的に変更し,最大で256。それを越える必要がある関数は''コンパイルエラー''とする。 - SRP(Stack Register Pointer)からのオフセットがスタックレジスタの番号に相当する。 - SR01ならSRP + 0x08のアドレスとなる。(32bitモードなら+0x04) - 通常,関数の先頭でFUNCET命令を使い関数で使用するスタックレジスタの数 x レジスタのサイズ分,スタックから確保し,その先頭アドレスをSRPに設定する。 ** 変数とレジスタ [#w8f36726] - ローカル変数は全てスタックレジスタに割り当てられる。 - スコープを考慮してスタックレジスタの使用数はなるべく少なくて済むように最適化される。 - 一時変数もスタックレジスタに割り当てられる。 #code(c,){{ utility Hoge { static void func() { int a; // SR01 int b; // SR02 { int c; // SR03 } int d; // SR02 { int e; // SR03 } d = (a + b) * d; // (a + b)が一時変数としてSR03に格納される // SR03 = a + b; // SR02 = R03 * R02; } }; }} ** 戻り値とレジスタ [#lb8a56e2] - 戻り値はFR00に格納される。 - FUNCRT命令が実行されるとSR00の値はFR00にコピーされる。 #code(c,){{ utility Hoge { static int GetOne() { return 1; // FR00 = 1 } }; }} ** 引数とレジスタ [#g01df345] - 引数は関数レジスタのFR01から順番にレジスタに割り当てられる。 - 非staticなメンバ関数はFR01にthisポインタが割り当てられる。 - 関数レジスタは16個しか用意されていないので,引数の最大数は下記の表のようになる。 |戻り値あり|非staticなメンバ関数|最大引数|h |x|x|16| |x|o|15| |o|x|15| |o|o|14| #code(c,){{ pod Vector3 { float x; float y; float z; void func(float addValue) { // FR01 : this // FR02 : addValue } } }} ** boolとレジスタ [#g2fb75db] - boolのサイズは環境によって1バイトだったり4バイトだったりするのでそこは考慮する。 - falseはだいたいの環境で0なので,falseの代入には0の代入を使う。 - trueの代入についてはreg.boolVal = trueで実現する。 ** アセンブラのイメージ [#sc9a365f] - 簡単なコード #code(c,){{ 0:int add(int a, int b) 1:{ 2: return a + b; 3:} 4:void func() 5:{ 6: int i = 3; 7: i = add(i , 4); 8:} # 命令コード Instruction: add: DFUNC 0x0000 DLINE 1 // '{'の位置 FUNCET 0x03 0x03 DLINE 2 ADDI32 R00 R01 R02 DLINE 3 // '}'の位置 FUNCLV 0x03 func: DFUNC 0x0001 DLINE 5 FUNCET 0x02 0x00 DLINE 6 LDSRC4 SR00 0x0000 DLINE 7 LDSRC4 SR01 0x0004 LDFRSR FR01 SR00 LDFRSR FR02 SR01 CALL 0x0000 LDRF SR00 FR00 FUNCLV 0x02 # コンパイル時に確定する定数のテーブル ConstantTable: 0x0000: 3 0x0004: 4 SymbolTable: 0x0000: int add(int,int) 0x0004: void func() }} ** 命令コード [#ac30d5cb] - どの命令コードも32bitで統一。 - SRegおよびFRegはu8。 |命令|書式|コメント|h |>|>|BGCOLOR(ORANGE):''ロード''| |LDSRZR|LDSRZR SReg|指定したスタックレジスタをゼロクリアする。| |LDSRBT|LDSRBT SReg|指定したスタックレジスタにtrueを代入する。| |LDSRC1|LDSRC4 SReg ConstantTableIndex(u16)|ConstantTableの値を指定したスタックレジスタにロード| |LDSRC2|~|~| |LDSRC4|~|~| |LDSRC8|~|~| |LDSRSR|LDSRSR SReg1 SReg2|SReg2の値をSReg1にロード| |LDSRSP|LDSRSP SReg1 StackPointerOffset(u16)|StackPointerから相対アドレス分移動したアドレスをReg1にロード| |LDSRP1|LDSRP1 SReg1 SReg2|SReg2の値をアドレスとみなし,SReg2が指す値を取得する| |LDSRP2|~|~| |LDSRP4|~|~| |LDSRP8|~|~| |LDSROJ|LDSROJ SReg1 SReg2|SReg2の値をobject型とみなし,SReg2が指すobjectの実体のアドレスのアドレスを取得する| |LDSRFR|LDSRFR SReg FReg|FRegの値をSRegにロード| |LDSRFZ|LDSRFZ SReg|FR00の値をSRegにロード| |LDFRSR|LDFRSR FReg SReg|SRegの値をFRegにロード| |>|>|BGCOLOR(ORANGE):''算術演算''| |ADDI32|ADDI32 SReg1 SReg2 SReg3|SReg1 = SReg2 + SReg3| |SUBI32|SUBI32 SReg1 SReg2 SReg3|SReg1 = SReg2 - SReg3| |MULS32|MULS32 SReg1 SReg2 SReg3|SReg1 = SReg2 * SReg3| |DIVS32|MULS32 SReg1 SReg2 SReg3|SReg1 = SReg2 / SReg3| |MODS32|MODS32 SReg1 SReg2 SReg3|SReg1 = SReg2 % SReg3| |INCI32|INCI32 SReg1|SReg1++| |DECI32|DECI32 SReg1|SReg1--| |NEGS32|NEGS32 SReg1 SReg2|SReg1 = -SReg2| |>|>|BGCOLOR(ORANGE):''比較演算''| |LTS32|LTS32 SReg1 SReg2 SReg3|SReg1 = SReg2 < SReg3| |LES32|LES32 SReg1 SReg2 SReg3|SReg1 = SReg2 <= SReg3| |EQI32|EQI32 SReg1 SReg2 SReg3|SReg1 = SReg2 == SReg3| |EQBOOL|EQBOOL SReg1 SReg2 SReg3|~| |NEI32|NEI32 SReg1 SReg2 SReg3|SReg1 = SReg2 != SReg3| |NEBOOL|NEBOOL SReg1 SReg2 SReg|~| |>|>|BGCOLOR(ORANGE):''ビット演算''| |ANDI32|ANDI32 SReg1 SReg2 SReg3|SReg1 = SReg2 & SReg3| |ORI32|ORI32 SReg1 SReg2 SReg3|SReg1 = SReg2 | SReg3| |XORI32|XORI32 SReg1 SReg2 SReg3|SReg1 = SReg2 ^ SReg3| |NTI32|NOTI32 SReg1 SReg2|SReg1 = ~SReg2| |NTBOOL|NTBOOL SReg1 SReg2|SReg1 = !SReg2| |>|>|BGCOLOR(ORANGE):''シフト演算''| |SLLI32|SLLI32 SReg1 SReg2 SReg3|SReg1 = SReg2 << SReg3| |SLRI32|SLRI32 SReg1 SReg2 SReg3|SReg1 = SReg2 >> SReg3| |>|>|BGCOLOR(ORANGE):''分岐命令''| |JMP|JMP RelativePCPos(s16)|RelativePCPosの値をpcに足す。&br;1ならpcを1つ進める。(1バイト進める,ではない)| |JMPPOS|JMPPOS SReg1 RelativePCPos(s16)|SReg1がtrueならJMPする。| |JMPNEG|JMPNEG SReg1 RelativePCPos(s16)|SReg1がfalseならJMPする。| |>|>|BGCOLOR(ORANGE):''関数あれこれ''| |FENTER|FENTER AllocSRegNum CopyFRegNum|Function Enter. &br;まずスタックに現在のSPR,LR,PL,PFをつみ,SPを変更する。&br;AllocSRegNumで指定された数,スタックレジスタを確保し,SPRに設定する。SPを変更する。&br;CopyFRegNumで指定された数,スタックレジスタにFRに内容をコピーする。| |FLEAVE|FLEAVE FreeSRegNum|Function Leave. &br;FreeSRegNumで指定された数,スタックレジスタを解放しSPを変更する。&br;PF,PL,LR,SPR,をスタックから取り出し設定する。SPも変更する。&br;PCにLRを代入する。| |CALL|CALL SymbolTableIndex(u16)|LRにPCを代入する。 &br;SymbolTableIndexが示す関数の先頭バイトコードをPCに設定する。| |CALLVF|CALLVF SReg1 ConstantTableIndex(u16)|Call Virtual Function.&br;SReg1が指すオブジェクトの仮想関数の先頭バイトコードを求め,PCに代入する。&br;ConstantTableIndexが示す先にはSymbolTableIndex(u16)が2つ連続して存在する。&br;1つめは仮想関数の親となるSSObjectTypeが格納されているSymbolTableのIndex。&br;2つめは仮想関数自体のSSFunctionが格納されているSymbolTableのIndex。| |>|>|BGCOLOR(ORANGE):''スタック''| |PUSH|PUSH SReg ConstantTableIndex(u16)|ConstantTableIndexが指す定数サイズ(u32)減算し,指定のSRegに演算後のSPを設定する。| |POP|POP ConstantTableIndex(u16)|ConstantTableIndexが指す定数サイズ(u32)分,SPを加算する。| |>|>|BGCOLOR(ORANGE):''オブジェクト''| |OBJNEW|OBJNEW SReg SymbolIndex|Object New.&br;指定のSymbolIndexが指すObjectTypeのインスタンスを作成し,ハンドルをSReg1に代入する。| |OBJDEL|OBJDEL SReg|Object Delete.&br;指定のSRegが指すオブジェクトに対してdeleteする。| |OBJRFI|OBJRFI SReg|Object Reference Increment.&br;指定のSRegが指すオブジェクトのリファレンスカウンタをインクリメントする。| |OBJRFD|OBJRFD SReg|Object Reference Decrement.&br;指定のSRegが指すオブジェクトのリファレンスカウンタをデクリメントし,SRegに0を代入する。| |>|>|BGCOLOR(ORANGE):''デバッグ''| |DLINE|DLINE ConstantValue(u24)|Debug Line.&br;デバッグ情報のプログラム行数を代入するための命令。 &br;PLにConstantValueを代入する。| |DFUNC|DFUNC SymbolTableIndex(u16)|Debug Function.&br;デバッグ情報の関数オブジェクトを代入するための命令。&br;PFにSymbolTableIndexが示すアドレスを代入する。| ** クラス構成 [#lfe85b6e] *** SEVMCore [#bb572835] - 仮想マシン本体。 - コンパイル済みの命令コード,ユーザー定義型の初期化データ(.init)を持つ。 - staticなデータはこいつが持ち,複数のContextで共有することになる。 *** SEVMContext [#uc0127e1] - コンテキスト。スレッド。 - スタック,レジスタは各ScriptContextがそれぞれ持つ。 ** SEVMCore [#hae3d592] *** 手順 [#i4dc4609] - セットアップ前準備(ユーザーがやる) -- コンパイル済みのオブジェクトファイルの追加。 -- 関数,クラス類,変数類のC++とのバインド。 - セットアップ -- 1) リンク&未解決シンボルのチェック。 -- 2) staticデータに.initデータをコピー。 -- 3) (ver2.0)各クラスのstaticコンストラクタ&static invariant()を実行。 - ファイナライズ -- 1) 全Contextが終了していることを確認。 -- 2) ガベージコレクトを実行。 -- 3) (ver2.0)各クラスのstatic invariant()&staticデストラクタを実行。 -- 4) ガベージコレクトを実行。 -- 5) ゴミが残っていないかチェック。 ** SEVMContext [#q24e9b98] *** 実行開始手順 [#xe3c6b37] - (ユーザーがやる) - エントリーポイントとなる関数とその引数を設定。 ** その他 [#n8572b2d] [[./メモ]] |