命令フォーマット
スタックの構造の次は、Luaの仮想マシン語の命令の構造を見ていきます。命令を表す型 Instruction
// llimits.h typedef lu_int32 Instruction;
は、32bit整数で、命令毎に、bitの使い方は3通りあります。
- ABC型 B:9bit, C:9bit, A:8bit, OpCode:6bit
- ABx型 Bx:18bit, A:8bit, OpCode:6bit
- AsBx型 sBx:18bit, A:8bit, OpCode:6bit
// lopcodes.h #define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0))) #define GETARG_A(i) (cast(int, ((i)>>POS_A) & MASK1(SIZE_A,0))) #define GETARG_B(i) (cast(int, ((i)>>POS_B) & MASK1(SIZE_B,0))) #define GETARG_C(i) (cast(int, ((i)>>POS_C) & MASK1(SIZE_C,0)))
Instruction i からそれぞれの部分を取り出すコードはこんな感じのビット演算で。
Aは基本的には、演算の結果を格納するレジスタを指すために使われます。つまり逆にいうと、レジスタは最大で8bit、256個までということになります。
BとCは、演算のオペランドの指定に使われます。最上位bitが1の時は定数表を指し、0の時はレジスタを指します。
#define BITRK (1 << (SIZE_B - 1)) #define ISK(x) ((x) & BITRK) #define INDEXK(r) ((int)(r) & ~BITRK) #define RKB(i) (... ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i))
こんな風に。kが定数表の先頭を指すポインタで、baseがスタック/レジスタの先頭を指すポインタです。例として、そうですね、1 番レジスタに定数 2 を足して 3 番レジスタに結果を格納する ADD 命令(OpCodeは12)は、
レジスタ1 定数2 レジスタ3 命令12 000000001 100000010 00000011 001100
というビット列になります。
Bxは符号無し整数で、主に定数表のインデックスとして使用されます。(他の演算のオペランドで8bitに収まるときはBやCを使いますが、一般にはBxで表を引きます)
sBxは符号付き整数で、ジャンプ命令のジャンプ距離の指定に使われます。正なら進む方向に飛んで、負なら戻る方向に飛ぶような指定になります。
現在のところ38個の命令が定義されていて、lopcodes.h で概要を見ることができます。
typedef enum { /*---------------------------------------------------------------------- name args description ------------------------------------------------------------------------*/ OP_MOVE,/* A B R(A) := R(B) */ OP_LOADK,/* A Bx R(A) := Kst(Bx) */ OP_LOADBOOL,/* A B C R(A) := (Bool)B; if (C) pc++ */ OP_LOADNIL,/* A B R(A) := ... := R(B) := nil */ OP_GETUPVAL,/* A B R(A) := UpValue[B] */ OP_GETGLOBAL,/* A Bx R(A) := Gbl[Kst(Bx)] */ OP_GETTABLE,/* A B C R(A) := R(B)[RK(C)] */ ... OP_VARARG/* A B R(A), R(A+1), ..., R(A+B-1) = vararg */ } OpCode;
見てみるとわかりますが、コルーチンやモジュール関係はVMの命令にはなっていないんですね。VMの構造を操作するようなC関数をライブラリとして提供して、その関数呼び出しで実装という形になっているようです。