ひとり勉強会

ひとり楽しく勉強会

命令フォーマット

スタックの構造の次は、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関数をライブラリとして提供して、その関数呼び出しで実装という形になっているようです。