源码赏析LUA VM的运行过程!
Posted 西山居质量
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码赏析LUA VM的运行过程!相关的知识,希望对你有一定的参考价值。
LUA VM
Lua是一门解释行语言,但是在运行时并不是直接解释运行Lua的代码,而是先翻译成一种字节码。这些字节码运行在Lua的虚拟机上。从Lua第五版开始,Lua将之前的堆栈式虚拟机程序设计后,改为了基于寄存器的虚拟机。
指令结构
Lua的每条指令都用一个无符号的32位整数表示。这个Instruction表示的就是字节码。
typedef unsigned int Instruction;
表示指令种类的部分叫做opcode,也叫操作码。操作的种类只有40种,所以只需要6位编码。每条指令大都是对一个对象作出影响,这个受影响的对象称为A,它由一个8位整数编码。A通常是一个寄存器的索引值。而作用于A的一般有两个参数,每个参数都由9位表示,称作B和C。
该图是指令结构的布局,有部分指令不需要两个参数,这时可以把B和C合并为一个18位的整数Bx看待,这样可以表示的范围就更大了。
当操作符涉及指令跳转时,这个参数表示跳转的偏移量。这类指令需要的参数可能带有符号信息,此时记为sBx。 如果不需要参数的话,A将用26位整数编码,记为Ax。 这个图里有几个宏,表示的是几个不同类型的对象所占用的大小和偏移量。
#define SIZE_C9
#define SIZE_B9
#define SIZE_Bx(SIZE_C + SIZE_B)
#define SIZE_A8
#define SIZE_Ax(SIZE_C + SIZE_B + SIZE_A)
#define SIZE_OP6
#define POS_OP0
#define POS_A(POS_OP + SIZE_OP)
#define POS_C(POS_A + SIZE_A)
#define POS_B(POS_C + SIZE_C)
#define POS_BxPOS_C
#define POS_AxPOS_A
操作码分类
根据参数个数和种类不同,Lua把opcode分为以下4类(iABC,iABx,iAsBx,iAx)。在opcpde的源文件里,定义了一个叫luap_opmodes的数组,描述了每条操作码的类型,以及涉及参数的具体用法。包括参数是否有使用,或用于寄存器还是常量索引等。
enum OpMode {iABC, iABx, iAsBx, iAx};
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 */
};
LUAI_DDEF 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 */
,opmode(0, 1, OpArgN, OpArgN, iABx)/* OP_LOADKX */
,opmode(0, 1, OpArgU, OpArgU, iABC)/* OP_LOADBOOL */
,opmode(0, 1, OpArgU, OpArgN, iABC)/* OP_LOADNIL */
,opmode(0, 1, OpArgU, OpArgN, iABC)/* OP_GETUPVAL */
,opmode(0, 1, OpArgU, OpArgK, iABC)/* OP_GETTABUP */
,opmode(0, 1, OpArgR, OpArgK, iABC)/* OP_GETTABLE */
,opmode(0, 0, OpArgK, OpArgK, iABC)/* OP_SETTABUP */
,opmode(0, 0, OpArgU, OpArgN, iABC)/* OP_SETUPVAL */
,opmode(0, 0, OpArgK, OpArgK, iABC)/* OP_SETTABLE */
,opmode(0, 1, OpArgU, OpArgU, iABC)/* OP_NEWTABLE */
,opmode(0, 1, OpArgR, OpArgK, iABC)/* OP_SELF */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_ADD */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_SUB */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_MUL */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_MOD */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_POW */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_DIV */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_IDIV */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_BAND */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_BOR */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_BXOR */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_SHL */
,opmode(0, 1, OpArgK, OpArgK, iABC)/* OP_SHR */
,opmode(0, 1, OpArgR, OpArgN, iABC)/* OP_UNM */
,opmode(0, 1, OpArgR, OpArgN, iABC)/* OP_BNOT */
,opmode(0, 1, OpArgR, OpArgN, iABC)/* OP_NOT */
,opmode(0, 1, OpArgR, OpArgN, iABC)/* OP_LEN */
,opmode(0, 1, OpArgR, OpArgR, iABC)/* OP_CONCAT */
,opmode(0, 0, OpArgR, OpArgN, iAsBx)/* OP_JMP */
,opmode(1, 0, OpArgK, OpArgK, iABC)/* OP_EQ */
,opmode(1, 0, OpArgK, OpArgK, iABC)/* OP_LT */
,opmode(1, 0, OpArgK, OpArgK, iABC)/* OP_LE */
,opmode(1, 0, OpArgN, OpArgU, iABC)/* OP_TEST */
,opmode(1, 1, OpArgR, OpArgU, iABC)/* OP_TESTSET */
,opmode(0, 1, OpArgU, OpArgU, iABC)/* OP_CALL */
,opmode(0, 1, OpArgU, OpArgU, iABC)/* OP_TAILCALL */
,opmode(0, 0, OpArgU, OpArgN, iABC)/* OP_RETURN */
,opmode(0, 1, OpArgR, OpArgN, iAsBx)/* OP_FORLOOP */
,opmode(0, 1, OpArgR, OpArgN, iAsBx)/* OP_FORPREP */
,opmode(0, 0, OpArgN, OpArgU, iABC)/* OP_TFORCALL */
,opmode(0, 1, OpArgR, OpArgN, iAsBx)/* OP_TFORLOOP */
,opmode(0, 0, OpArgU, OpArgU, iABC)/* OP_SETLIST */
,opmode(0, 1, OpArgU, OpArgN, iABx)/* OP_CLOSURE */
,opmode(0, 1, OpArgU, OpArgN, iABC)/* OP_VARARG */
,opmode(0, 0, OpArgU, OpArgU, iAx)/* OP_EXTRAARG */
};
操作码列表
在opcode的头文件里,定义了40个操作码的枚举值,并且每个操作码都详细注释了含义。这些信息对于理解虚拟机来说非常重要。R()表示这是一个寄存器索引;RK()表示这可能是一个寄存器索引,也可能是一个常量索引,由参数的高位决定是哪种。
typedef enum
{
/*---------------------------------------------------------------------- nameargsdescription
-------------------------------------------------------------------------*/
OP_MOVE,/*A BR(A) := R(B)*/
OP_LOADK,/*A BxR(A) := Kst(Bx)*/
OP_LOADKX,/*A R(A) := Kst(extra arg)*/
OP_LOADBOOL,/*A B CR(A) := (Bool)B; if (C) pc++*/
OP_LOADNIL,/*A BR(A), R(A+1), ..., R(A+B) := nil*/
OP_GETUPVAL,/*A BR(A) := UpValue[B]*/
OP_GETTABUP,/*A B CR(A) := UpValue[B][RK(C)]*/
OP_GETTABLE,/*A B CR(A) := R(B)[RK(C)]*/
OP_SETTABUP,/*A B CUpValue[A][RK(B)] := RK(C)*/
OP_SETUPVAL,/*A BUpValue[B] := R(A)*/
OP_SETTABLE,/*A B CR(A)[RK(B)] := RK(C)*/
OP_NEWTABLE,/*A B CR(A) := {} (size = B,C)*/
OP_SELF,/*A B CR(A+1) := R(B); R(A) := R(B)[RK(C)]*/
OP_ADD,/*A B CR(A) := RK(B) + RK(C)*/
OP_SUB,/*A B CR(A) := RK(B) - RK(C)*/
OP_MUL,/*A B CR(A) := RK(B) * RK(C)*/
OP_MOD,/*A B CR(A) := RK(B) % RK(C)*/
OP_POW,/*A B CR(A) := RK(B) ^ RK(C)*/
OP_DIV,/*A B CR(A) := RK(B) / RK(C)*/
OP_IDIV,/*A B CR(A) := RK(B) // RK(C)*/
OP_BAND,/*A B CR(A) := RK(B) & RK(C)*/
OP_BOR,/*A B CR(A) := RK(B) | RK(C)*/
OP_BXOR,/*A B CR(A) := RK(B) ~ RK(C)*/
OP_SHL,/*A B CR(A) := RK(B) << RK(C)*/
OP_SHR,/*A B CR(A) := RK(B) >> RK(C)*/
OP_UNM,/*A BR(A) := -R(B)*/
OP_BNOT,/*A BR(A) := ~R(B)*/
OP_NOT,/*A BR(A) := not R(B)*/
OP_LEN,/*A BR(A) := length of R(B)*/
OP_CONCAT,/*A B CR(A) := R(B).. ... ..R(C)*/
OP_JMP,/*A sBxpc+=sBx; if (A) close all upvalues >= R(A - 1)*/
OP_EQ,/*A B Cif ((RK(B) == RK(C)) ~= A) then pc++*/
OP_LT,/*A B Cif ((RK(B) < RK(C)) ~= A) then pc++*/
OP_LE,/*A B Cif ((RK(B) <= RK(C)) ~= A) then pc++*/
OP_TEST,/*A Cif not (R(A) <=> C) then pc++*/
OP_TESTSET,/*A B Cif (R(B) <=> C) then R(A) := R(B) else pc++*/
OP_CALL,/*A B CR(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
OP_TAILCALL,/*A B Creturn R(A)(R(A+1), ... ,R(A+B-1))*/
OP_RETURN,/*A Breturn R(A), ... ,R(A+B-2)(see note)*/
OP_FORLOOP,/*A sBxR(A)+=R(A+2);
if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/
OP_FORPREP,/*A sBxR(A)-=R(A+2); pc+=sBx*/
OP_TFORCALL,/*A CR(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));*/
OP_TFORLOOP,/*A sBxif R(A+1) ~= nil then { R(A)=R(A+1); pc += sBx }*/
OP_SETLIST,/*A B CR(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B*/
OP_CLOSURE,/*A BxR(A) := closure(KPROTO[Bx])*/
OP_VARARG,/*A BR(A), R(A+1), ..., R(A+B-2) = vararg*/
OP_EXTRAARG/*Axextra (larger) argument for previous opcode*/
}
OpCode;
字节码的运行
LuaV_execute是Lua虚拟机执行一段字节码的入口,它用了一个死循环,依次解析字节码指令。其中,当前指令i从savedpc中取出。然后根据不同指令进行不同的操作。
void luaV_execute (lua_State *L)
{
CallInfo *ci = L->ci;
LClosure *cl;
TValue *k;
StkId base;
ci->callstatus |= CIST_FRESH; /* fresh invocation of 'luaV_execute" */
newframe: /* reentry point when frame changes (call/return) */
lua_assert(ci == L->ci);
cl = clLvalue(ci->func); /* local reference to function's closure */
k = cl->p->k; /* local reference to function's constant table */
base = ci->u.l.base; /* local copy of function's base */
/* main loop of interpreter */
for (;;)
{
Instruction i;
StkId ra;
i = *(ci->u.l.savedpc++);
//每次执行字节码都会检查是否需要进入Lua的钩子函
if (L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT))
Protect(luaG_traceexec(L));
ra = RA(i); /* WARNING: any stack reallocation invalidates 'ra' */
lua_assert(base == ci->u.l.base);
lua_assert(base <= L->top && L->top < L->stack + L->stacksize);
……
本期关于Lua虚拟机的讲解就到这里了,下一期我们将带您了解Lua的钩子函数的实现和应用。
精彩回顾 :
西山居质量Test+团队是金山西山居工作室的专业质量团队,对于游戏领域有超过18年的测试经验积累,专注于为手游、端游提供各类自动化测试等全方位质量保障。
以上是关于源码赏析LUA VM的运行过程!的主要内容,如果未能解决你的问题,请参考以下文章