Top > Shiba > Shiba VM

Shiba VM Edit

シンプルでもいける気がしてきた。

レジスタの概要 Edit

  • 1つのレジスタのサイズは8バイト。
  • 4バイトでもいいかなと思ったが,64bit環境でポインタが1レジスタで表現できなくなるため,8バイトにした。
  • ただし,メモリがきつきつの環境もあるので32bitモード用意する。その場合,64bitの整数・浮動小数,64bitのアドレス空間は扱えなくなる。
  • 1つのレジスタはUNIONで表現。
    すべてを展開すべてを収束
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
    
     
    -
    |
    -
    |
    |
    |
    |
    |
    |
    |
    |
    !
    !
     
    
    struct Register
    {
        union
        {
            bool boolVal; 
            u32 data32;
            u64 data64;
            s32 i32val;
            s64 i64val;
            f32 f32val;
            f64 f64val;
            void*  ptr; // 64bit環境だとこれは8バイト。
        };
    };
    

レジスタ構成 Edit

Full NameShort NameComment
Stack Register PointerSRPスタックレジスタポインタ。
スタックレジスタの先頭アドレス。
言い換えるとSR00の場所。
Stack PointerSPスタックポインタ。現在のスタック位置。
Program CounterPCプログラムカウンタ。現在実行しているバイトコードのポインタ。
Program FunctionPFプログラムの関数オブジェクトのポインタ。デバッグ用。
Program LinePLプログラムのソースコードの行数。デバッグ用。
Link RegisterLRリンクレジスタ。戻り先のバイトコードのポインタ。
Function RegisterFR関数レジスタ。全部で16個。FR00 - FR16。
すべてを展開すべてを収束
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 
-
|
|
-
|
|
|
|
!
|
|
|
|
!
struct SEVMContext
{
   // 関数開始時に保存されるレジスタ達。
   struct SaveReg
   {
       void*        srp;
       void*        lr;
       SEFunction*  pf;
       uint32       pl;
   };
   SaveReg      saveReg;
   // 保存されないレジスタ達。
   void*        sp;
   void*        pc;
};

スタックレジスタ Edit

  • 計算の一時領域として使うレジスタはStackRegister(スタックレジスタ)呼び,SRと略す。
  • スタックレジスタ数は動的に変更し,最大で256。それを越える必要がある関数はコンパイルエラーとする。
  • SRP(Stack Register Pointer)からのオフセットがスタックレジスタの番号に相当する。
  • SR01ならSRP + 0x08のアドレスとなる。(32bitモードなら+0x04)
  • 通常,関数の先頭でFUNCET命令を使い関数で使用するスタックレジスタの数 x レジスタのサイズ分,スタックから確保し,その先頭アドレスをSRPに設定する。

変数とレジスタ Edit

  • ローカル変数は全てスタックレジスタに割り当てられる。
  • スコープを考慮してスタックレジスタの使用数はなるべく少なくて済むように最適化される。
  • 一時変数もスタックレジスタに割り当てられる。
    すべてを展開すべてを収束
      1
      2
      3
      4
      5
      6
      7
      8
      9
     10
     11
     12
     13
     14
     15
     16
     17
     18
    
    -
    |
    -
    |
    |
    -
    |
    !
    |
    -
    |
    !
    |
    |
    |
    |
    !
    !
    
    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;
    }
    };

戻り値とレジスタ Edit

  • 戻り値はFR00に格納される。
  • FUNCRT命令が実行されるとSR00の値はFR00にコピーされる。
    すべてを展開すべてを収束
      1
      2
      3
      4
      5
      6
      7
      8
    
     
    -
    |
    -
    |
    !
    !
     
    
    utility Hoge
    {
        static int GetOne()
        {
            return 1; // FR00 = 1
        }
    };
    

引数とレジスタ Edit

  • 引数は関数レジスタのFR01から順番にレジスタに割り当てられる。
  • 非staticなメンバ関数はFR01にthisポインタが割り当てられる。
  • 関数レジスタは16個しか用意されていないので,引数の最大数は下記の表のようになる。
戻り値あり非staticなメンバ関数最大引数
xx16
xo15
ox15
oo14
すべてを展開すべてを収束
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 
-
|
|
|
|
|
-
|
|
!
!
 
pod Vector3
{
    float x;
    float y;
    float z;
 
    void func(float addValue)
    {
        // FR01 : this
        // FR02 : addValue
    }
}

boolとレジスタ Edit

  • boolのサイズは環境によって1バイトだったり4バイトだったりするのでそこは考慮する。
  • falseはだいたいの環境で0なので,falseの代入には0の代入を使う。
  • trueの代入についてはreg.boolVal = trueで実現する。

アセンブラのイメージ Edit

  • 簡単なコード
    すべてを展開すべてを収束
      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
    
     
    -
    |
    !
     
    -
    |
    |
    !
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
    
    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()
    

命令コード Edit

  • どの命令コードも32bitで統一。
  • SRegおよびFRegはu8。
命令書式コメント
ロード
LDSRZRLDSRZR SReg指定したスタックレジスタをゼロクリアする。
LDSRBTLDSRBT SReg指定したスタックレジスタにtrueを代入する。
LDSRC1LDSRC4 SReg ConstantTableIndex(u16)ConstantTableの値を指定したスタックレジスタにロード
LDSRC2
LDSRC4
LDSRC8
LDSRSRLDSRSR SReg1 SReg2SReg2の値をSReg1にロード
LDSRSPLDSRSP SReg1 StackPointerOffset(u16)StackPointerから相対アドレス分移動したアドレスをReg1にロード
LDSRP1LDSRP1 SReg1 SReg2SReg2の値をアドレスとみなし,SReg2が指す値を取得する
LDSRP2
LDSRP4
LDSRP8
LDSROJLDSROJ SReg1 SReg2SReg2の値をobject型とみなし,SReg2が指すobjectの実体のアドレスのアドレスを取得する
LDSRFZLDSRFZ SRegFR00の値をSRegにロード
LDFRSRLDFRSR FReg SRegSRegの値をFRegにロード
算術演算
ADDI32ADDI32 SReg1 SReg2 SReg3SReg1 = SReg2 + SReg3
SUBI32SUBI32 SReg1 SReg2 SReg3SReg1 = SReg2 - SReg3
MULS32MULS32 SReg1 SReg2 SReg3SReg1 = SReg2 * SReg3
DIVS32MULS32 SReg1 SReg2 SReg3SReg1 = SReg2 / SReg3
MODS32MODS32 SReg1 SReg2 SReg3SReg1 = SReg2 % SReg3
INCI32INCI32 SReg1SReg1++
DECI32DECI32 SReg1SReg1--
NEGS32NEGS32 SReg1 SReg2SReg1 = -SReg2
比較演算
LTS32LTS32 SReg1 SReg2 SReg3SReg1 = SReg2 < SReg3
LES32LES32 SReg1 SReg2 SReg3SReg1 = SReg2 <= SReg3
EQI32EQI32 SReg1 SReg2 SReg3SReg1 = SReg2 == SReg3
EQBOOLEQBOOL SReg1 SReg2 SReg3
NEI32NEI32 SReg1 SReg2 SReg3SReg1 = SReg2 != SReg3
NEBOOLNEBOOL SReg1 SReg2 SReg
ビット演算
ANDI32ANDI32 SReg1 SReg2 SReg3SReg1 = SReg2 & SReg3
ORI32ORI32 SReg1 SReg2 SReg3SReg1 = SReg2 | SReg3
XORI32XORI32 SReg1 SReg2 SReg3SReg1 = SReg2 ^ SReg3
NTI32NOTI32 SReg1 SReg2SReg1 = ~SReg2
NTBOOLNTBOOL SReg1 SReg2SReg1 = !SReg2
シフト演算
SLLI32SLLI32 SReg1 SReg2 SReg3SReg1 = SReg2 << SReg3
SLRI32SLRI32 SReg1 SReg2 SReg3SReg1 = SReg2 >> SReg3
分岐命令
JMPJMP RelativePCPos(s16)RelativePCPosの値をpcに足す。
1ならpcを1つ進める。(1バイト進める,ではない)
JMPPOSJMPPOS SReg1 RelativePCPos(s16)SReg1がtrueならJMPする。
JMPNEGJMPNEG SReg1 RelativePCPos(s16)SReg1がfalseならJMPする。
関数あれこれ
FENTERFENTER AllocSRegNum CopyFRegNumFunction Enter.
まずスタックに現在のSPR,LR,PL,PFをつみ,SPを変更する。
AllocSRegNumで指定された数,スタックレジスタを確保し,SPRに設定する。SPを変更する。
CopyFRegNumで指定された数,スタックレジスタにFRに内容をコピーする。
FLEAVEFLEAVE FreeSRegNumFunction Leave.
FreeSRegNumで指定された数,スタックレジスタを解放しSPを変更する。
PF,PL,LR,SPR,をスタックから取り出し設定する。SPも変更する。
PCにLRを代入する。
CALLCALL SymbolTableIndex(u16)LRにPCを代入する。
SymbolTableIndexが示す関数の先頭バイトコードをPCに設定する。
CALLVFCALLVF SReg1 ConstantTableIndex(u16)Call Virtual Function.
SReg1が指すオブジェクトの仮想関数の先頭バイトコードを求め,PCに代入する。
ConstantTableIndexが示す先にはSymbolTableIndex(u16)が2つ連続して存在する。
1つめは仮想関数の親となるSSObjectTypeが格納されているSymbolTableのIndex。
2つめは仮想関数自体のSSFunctionが格納されているSymbolTableのIndex。
スタック
PUSHPUSH SReg ConstantTableIndex(u16)ConstantTableIndexが指す定数サイズ(u32)減算し,指定のSRegに演算後のSPを設定する。
POPPOP ConstantTableIndex(u16)ConstantTableIndexが指す定数サイズ(u32)分,SPを加算する。
オブジェクト
OBJNEWOBJNEW SReg SymbolIndexObject New.
指定のSymbolIndexが指すObjectTypeのインスタンスを作成し,ハンドルをSReg1に代入する。
OBJDELOBJDEL SRegObject Delete.
指定のSRegが指すオブジェクトに対してdeleteする。
OBJRFIOBJRFI SRegObject Reference Increment.
指定のSRegが指すオブジェクトのリファレンスカウンタをインクリメントする。
OBJRFDOBJRFD SRegObject Reference Decrement.
指定のSRegが指すオブジェクトのリファレンスカウンタをデクリメントし,SRegに0を代入する。
デバッグ
DLINEDLINE ConstantValue(u24)Debug Line.
デバッグ情報のプログラム行数を代入するための命令。
PLにConstantValueを代入する。
DFUNCDFUNC SymbolTableIndex(u16)Debug Function.
デバッグ情報の関数オブジェクトを代入するための命令。
PFにSymbolTableIndexが示すアドレスを代入する。

クラス構成 Edit

SEVMCore Edit

  • 仮想マシン本体。
  • コンパイル済みの命令コード,ユーザー定義型の初期化データ(.init)を持つ。
  • staticなデータはこいつが持ち,複数のContextで共有することになる。

SEVMContext Edit

  • コンテキスト。スレッド。
  • スタック,レジスタは各ScriptContextがそれぞれ持つ。

SEVMCore Edit

手順 Edit

  • セットアップ前準備(ユーザーがやる)
    • コンパイル済みのオブジェクトファイルの追加。
    • 関数,クラス類,変数類のC++とのバインド。
  • セットアップ
    • 1) リンク&未解決シンボルのチェック。
    • 2) staticデータに.initデータをコピー。
    • 3) (ver2.0)各クラスのstaticコンストラクタ&static invariant()を実行。
  • ファイナライズ
    • 1) 全Contextが終了していることを確認。
    • 2) ガベージコレクトを実行。
    • 3) (ver2.0)各クラスのstatic invariant()&staticデストラクタを実行。
    • 4) ガベージコレクトを実行。
    • 5) ゴミが残っていないかチェック。

SEVMContext Edit

実行開始手順 Edit

  • (ユーザーがやる)
  • エントリーポイントとなる関数とその引数を設定。

その他 Edit

./メモ


リロード   新規 下位ページ作成 編集 凍結 差分 添付 コピー 名前変更   ホーム 一覧 検索 最終更新 バックアップ リンク元   ヘルプ   最終更新のRSS
Last-modified: Tue, 10 Aug 2010 15:04:37 JST (5007d)