1.opcode
#define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m))
const lu_byte luaP_opmodes[NUM_OPCODES] = {
/* T A B C mode opcode */
opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */
,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */
...
/*
** masks for instruction properties. The format is:
** bits 0-1: op mode
** bits 2-3: C arg mode
** bits 4-5: B arg mode
** bit 6: instruction set register A
** bit 7: operator is a test
*/
enum OpArgMask {
OpArgN, /* argument is not used */
OpArgU, /* argument is used */
OpArgR, /* argument is a register or a jump offset */
OpArgK /* argument is a constant or register/constant */
};
...
#define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 3))
#define getBMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 4) & 3))
#define getCMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 2) & 3))
#define testAMode(m) (luaP_opmodes[m] & (1 << 6))
#define testTMode(m) (luaP_opmodes[m] & (1 << 7))
#define RA(i) (base+GETARG_A(i))
/* to be used after possible stack reallocation */
#define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i))
#define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i))
#define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \
ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i))
#define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \
ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i))
#define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i))
)
pc++;
continue;
lua代码执行过程是:源代码先被编译成为字节码,然后虚拟机解释执行。相关模块是lopcode.c,lvm.c。lua的字节码类似汇编,也有pc寄存器这个概念,指向下一条要执行的指令,lua里面每一条指令由无符号数表示,低六位代表opcode,指示这是条什么指令(例如OP_MOVE, OP_ADD,...)。剩下的位主要是给操作数用的,像汇编中一样,lua指令的操作数也可以有不同的寻址方式,但是lua简单很多,主要是根据不同的操作指令变化,分三种模式:
| 31 ~ 23 bit | 22 ~ 14 bit | 13 ~ 6 bit | 5 ~ 0 bit |
| C | B | A | OPCODE |
| Bx | A | OPCODE |
| sBx | A | OPCODE |
三种模式分别叫 iABC, iABx, iAsBx。
lopcode.c中只有两个数组定义,数组luaP_opnames是opcode数值对应opcode名字的一个映射表,luaP_opmodes是opcode对应相应操作模式的一个映射表,虚拟机肯定会先取出当前指令的opcode,再在luaP_opmodes找到对应操作模式,根据模式去不同地方取操作数。
看下每一个模式是怎么定义的:
#define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m))
const lu_byte luaP_opmodes[NUM_OPCODES] = {
/* T A B C mode opcode */
opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */
,opmode(0, 1, OpArgK, OpArgN, iABx) /* OP_LOADK */
...
}
每一个操作模式都是一个字节表示,各位表示的意思lopcodes.h有说明:
/*
** masks for instruction properties. The format is:
** bits 0-1: op mode
** bits 2-3: C arg mode
** bits 4-5: B arg mode
** bit 6: instruction set register A
** bit 7: operator is a test
*/
enum OpArgMask {
OpArgN, /* argument is not used */
OpArgU, /* argument is used */
OpArgR, /* argument is a register or a jump offset */
OpArgK /* argument is a constant or register/constant */
};
简单解释如下:
0~1:这个操作本身对应模式的枚举值,上面说了只有三个枚举值;
2~3:操作数C的模式,主要有四种模式,OpArgN,OpArgU,OpArgR,OpArgK
4~5: 操作数B的模式
6: 操作数A是否被使用,1被使用,0没有
7:这条指令是否是判定指令,为什么要这一位,因为跳转时,跳转地址已经无法在一条指令里装下,所以lua里面就用两条指令来表示了,第一条判断指令,第二条无条件跳转指令,比如OP_EQ,如果判断成功,PC加1,否则跳转。
lopcodes.h里面定义了一系列的宏操作,方便取指令中的opcode,操作数,代码如下:
...
/*
** size and position of opcode arguments.
*/
#define SIZE_C 9
#define SIZE_B 9
#define SIZE_Bx (SIZE_C + SIZE_B)
#define SIZE_A 8
#define SIZE_OP 6
#define POS_OP 0
#define POS_A (POS_OP + SIZE_OP)
#define POS_C (POS_A + SIZE_A)
#define POS_B (POS_C + SIZE_C)
#define POS_Bx POS_C
/*
** the following macros help to manipulate instructions
*/
/*
** size and position of opcode arguments.
*/
#define SIZE_C 9
#define SIZE_B 9
#define SIZE_Bx (SIZE_C + SIZE_B)
#define SIZE_A 8
#define SIZE_OP 6
#define POS_OP 0
#define POS_A (POS_OP + SIZE_OP)
#define POS_C (POS_A + SIZE_A)
#define POS_B (POS_C + SIZE_C)
#define POS_Bx POS_C
...
...
/* creates a mask with `n' 1 bits at position `p' */
#define MASK1(n,p) ((~((~(Instruction)0)<<n))<<p)
/* creates a mask with `n' 0 bits at position `p' */
#define MASK0(n,p) (~MASK1(n,p))
...
#define MASK1(n,p) ((~((~(Instruction)0)<<n))<<p)
/* creates a mask with `n' 0 bits at position `p' */
#define MASK0(n,p) (~MASK1(n,p))
...
...
/*
** the following macros help to manipulate instructions
*/
#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))
#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \
((cast(Instruction, o)<<POS_OP)&MASK1(SIZE_OP,POS_OP))))
#define GETARG_A(i) (cast(int, ((i)>>POS_A) & MASK1(SIZE_A,0)))
#define SETARG_A(i,u) ((i) = (((i)&MASK0(SIZE_A,POS_A)) | \
((cast(Instruction, u)<<POS_A)&MASK1(SIZE_A,POS_A))))
#define GETARG_B(i) (cast(int, ((i)>>POS_B) & MASK1(SIZE_B,0)))
#define SETARG_B(i,b) ((i) = (((i)&MASK0(SIZE_B,POS_B)) | \
((cast(Instruction, b)<<POS_B)&MASK1(SIZE_B,POS_B))))
#define GETARG_C(i) (cast(int, ((i)>>POS_C) & MASK1(SIZE_C,0)))
#define SETARG_C(i,b) ((i) = (((i)&MASK0(SIZE_C,POS_C)) | \
((cast(Instruction, b)<<POS_C)&MASK1(SIZE_C,POS_C))))
#define GETARG_Bx(i) (cast(int, ((i)>>POS_Bx) & MASK1(SIZE_Bx,0)))
#define SETARG_Bx(i,b) ((i) = (((i)&MASK0(SIZE_Bx,POS_Bx)) | \
((cast(Instruction, b)<<POS_Bx)&MASK1(SIZE_Bx,POS_Bx))))
#define GETARG_sBx(i) (GETARG_Bx(i)-MAXARG_sBx)
#define SETARG_sBx(i,b) SETARG_Bx((i),cast(unsigned int, (b)+MAXARG_sBx))
#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) | \
((cast(Instruction, o)<<POS_OP)&MASK1(SIZE_OP,POS_OP))))
#define GETARG_A(i) (cast(int, ((i)>>POS_A) & MASK1(SIZE_A,0)))
#define SETARG_A(i,u) ((i) = (((i)&MASK0(SIZE_A,POS_A)) | \
((cast(Instruction, u)<<POS_A)&MASK1(SIZE_A,POS_A))))
#define GETARG_B(i) (cast(int, ((i)>>POS_B) & MASK1(SIZE_B,0)))
#define SETARG_B(i,b) ((i) = (((i)&MASK0(SIZE_B,POS_B)) | \
((cast(Instruction, b)<<POS_B)&MASK1(SIZE_B,POS_B))))
#define GETARG_C(i) (cast(int, ((i)>>POS_C) & MASK1(SIZE_C,0)))
#define SETARG_C(i,b) ((i) = (((i)&MASK0(SIZE_C,POS_C)) | \
((cast(Instruction, b)<<POS_C)&MASK1(SIZE_C,POS_C))))
#define GETARG_Bx(i) (cast(int, ((i)>>POS_Bx) & MASK1(SIZE_Bx,0)))
#define SETARG_Bx(i,b) ((i) = (((i)&MASK0(SIZE_Bx,POS_Bx)) | \
((cast(Instruction, b)<<POS_Bx)&MASK1(SIZE_Bx,POS_Bx))))
#define GETARG_sBx(i) (GETARG_Bx(i)-MAXARG_sBx)
#define SETARG_sBx(i,b) SETARG_Bx((i),cast(unsigned int, (b)+MAXARG_sBx))
...
...
这里说下为什么 GETARG_sBx(i) (GETARG_Bx(i)-MAXARG_sBx)?其实lopcodes.h里面有说
/*===========================================================================
We assume that instructions are unsigned numbers.
All instructions have an opcode in the first 6 bits.
Instructions can have the following fields:
`A' : 8 bits
`B' : 9 bits
`C' : 9 bits
`Bx' : 18 bits (`B' and `C' together)
`sBx' : signed Bx
A signed argument is represented in excess K; that is, the number
value is the unsigned value minus K. K is exactly the maximum value
for that argument (so that -max is represented by 0, and +max is
represented by 2*max), which is half the maximum for the corresponding
unsigned argument.
===========================================================================*/
We assume that instructions are unsigned numbers.
All instructions have an opcode in the first 6 bits.
Instructions can have the following fields:
`A' : 8 bits
`B' : 9 bits
`C' : 9 bits
`Bx' : 18 bits (`B' and `C' together)
`sBx' : signed Bx
A signed argument is represented in excess K; that is, the number
value is the unsigned value minus K. K is exactly the maximum value
for that argument (so that -max is represented by 0, and +max is
represented by 2*max), which is half the maximum for the corresponding
unsigned argument.
===========================================================================*/
MAXARG_sBx正好是18bits的Bx能表示的最大数的一半,可以去看代码。
还有取对应opcode的mode的宏定义:
#define getOpMode(m) (cast(enum OpMode, luaP_opmodes[m] & 3))
#define getBMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 4) & 3))
#define getCMode(m) (cast(enum OpArgMask, (luaP_opmodes[m] >> 2) & 3))
#define testAMode(m) (luaP_opmodes[m] & (1 << 6))
#define testTMode(m) (luaP_opmodes[m] & (1 << 7))
这些宏定义在lvm.c里面用了很多,看下在lvm.c里面定义的取操作数的值:
#define RA(i) (base+GETARG_A(i))
/* to be used after possible stack reallocation */
#define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i))
#define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i))
#define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \
ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i))
#define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \
ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i))
#define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i))
RA,RB,RC,是在寄存器取相应操作数,但是这里的寄存器其实就是lua的栈,指令保存的其实操作数相对函数栈base上的偏移,所以取出来都是这样 base + GETARG_X;
RKB,RKC, 是操作数或者在寄存器中,或者就是一个常数。例如对于操作数B,当B的最高位是1,代表其是一个常数,否则代表其是寄存器变量;
KBx,操作数Bx就是常数;
总的来说,一条指令包含操作数(A,B,C)和操作符(opcode), 每一个opcode预定义了mode,mode包含操作数的取值fan
2.虚拟机是如何解释指令的
*例如简单的OP_ADD:
虚拟机解释指令的过程主要在lvm.c函数luaC_execute中,先略去次要的内容
void luaV_execute(lua_State *L, int nexeccalls) {
...
...
for (;;) {
const Instruction i = *pc++; // 取下一条指令到i中
...
swith (GET_OPCODE(i)) {
...
case OP_ADD: { arith_op(luai_numadd, TM_ADD);
continue;
...
}
}
}
}
#define arith_op(op,tm) { \
TValue *rb = RKB(i); \ \\ 取操作数B
TValue *rc = RKC(i); \ \\ 取操作数C
if (ttisnumber(rb) && ttisnumber(rc)) { \
lua_Number nb = nvalue(rb), nc = nvalue(rc); \
setnvalue(ra, op(nb, nc)); \ \\相加赋给操作数A
} \
else \
Protect(Arith(L, ra, rb, rc, tm)); \
}
TValue *rb = RKB(i); \ \\ 取操作数B
TValue *rc = RKC(i); \ \\ 取操作数C
if (ttisnumber(rb) && ttisnumber(rc)) { \
lua_Number nb = nvalue(rb), nc = nvalue(rc); \
setnvalue(ra, op(nb, nc)); \ \\相加赋给操作数A
} \
else \
Protect(Arith(L, ra, rb, rc, tm)); \
}
*稍微复杂点的是跳转指令,例如OP_EQ
case OP_EQ: {
TValue *rb = RKB(i);
TValue *rc = RKC(i);
Protect(
if (equalobj(L, rb, rc) == GETARG_A(i))
dojump(L, pc, GETARG_sBx(*pc)); // pc += GETARG_sBx(*pc)
TValue *rb = RKB(i);
TValue *rc = RKC(i);
Protect(
if (equalobj(L, rb, rc) == GETARG_A(i))
dojump(L, pc, GETARG_sBx(*pc)); // pc += GETARG_sBx(*pc)
)
pc++;
continue;
正如之前所说,跳转指令涉及到两条指令,下一条指令代表跳转的偏移值(GETARG_sBx(*pc) 可正可负), 如果满足判定条件,则跳转,不满足则继续执行(只是跳过下一条指令)。
3.栈结构
....
|
|
|
top
|
...
|
base
|
func
|
....
|
lua_State这个结构中保存了一些函数调用栈的信息,每次一个函数被调用执行前,都会去初始化lua_State的一些成员,比如C调用lua函数的API : lua_call,
整个流程是lua_call -----> luaD_call --------> luaV_execute, 就是在luaD_call的luaD_precall初始化lua_State的.
void luaD_call (lua_State *L, StkId func, int nResults) {
if (++L->nCcalls >= LUAI_MAXCCALLS) {
if (L->nCcalls == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
}
if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */
luaV_execute(L, 1); /* call it */
L->nCcalls--;
luaC_checkGC(L);
}
if (++L->nCcalls >= LUAI_MAXCCALLS) {
if (L->nCcalls == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
}
if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */
luaV_execute(L, 1); /* call it */
L->nCcalls--;
luaC_checkGC(L);
}
总的来说指令都是在操作该函数栈上的操作数。
4.指令数据从何而来
指令当然是编译出来的,看下luaL_dofile的调用栈就能了解这个流程了:
luaL_dofile ---->luaL_loadfile ----> lua_load -----> luaY_parser
编译的过程是先读源码文件, 然后解析创建一个函数原型(Proto),函数原型就是包含了所有指令,编译工作主要在
luaY_parser中:
Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
struct LexState lexstate;
struct FuncState funcstate;
lexstate.buff = buff;
luaX_setinput(L, &lexstate, z, luaS_new(L, name));
open_func(&lexstate, &funcstate); // 创建Proto,并作初始化工作
funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */
luaX_next(&lexstate); /* read first token */
chunk(&lexstate); // 词法,语法分析,如果有词法语法错误,这里会报错
check(&lexstate, TK_EOS);
close_func(&lexstate);
lua_assert(funcstate.prev == NULL);
lua_assert(funcstate.f->nups == 0);
lua_assert(lexstate.fs == NULL);
return funcstate.f;
}
struct LexState lexstate;
struct FuncState funcstate;
lexstate.buff = buff;
luaX_setinput(L, &lexstate, z, luaS_new(L, name));
open_func(&lexstate, &funcstate); // 创建Proto,并作初始化工作
funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */
luaX_next(&lexstate); /* read first token */
chunk(&lexstate); // 词法,语法分析,如果有词法语法错误,这里会报错
check(&lexstate, TK_EOS);
close_func(&lexstate);
lua_assert(funcstate.prev == NULL);
lua_assert(funcstate.f->nups == 0);
lua_assert(lexstate.fs == NULL);
return funcstate.f;
}