源码赏析LUA VM的运行过程!

Posted 西山居质量

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了源码赏析LUA VM的运行过程!相关的知识,希望对你有一定的参考价值。

【源码赏析】LUA VM的运行过程!

 LUA VM 

Lua是一门解释行语言,但是在运行时并不是直接解释运行Lua的代码,而是先翻译成一种字节码。这些字节码运行在Lua的虚拟机上。从Lua第五版开始,Lua将之前的堆栈式虚拟机程序设计后,改为了基于寄存器的虚拟机。

【源码赏析】LUA VM的运行过程!

 指令结构 

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 VM的运行过程!

 操作码分类

根据参数个数和种类不同,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 */

 };

【源码赏析】LUA VM的运行过程!

 操作码列表

在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的运行过程!的主要内容,如果未能解决你的问题,请参考以下文章

源码赏析带你零基础掌握Lua!

Android 源码分析 Dalvik 虚拟机创建过程

让 new bing 使用 GPT-4 编写一个令人满意的程序全过程赏析

Cocos2dx源码赏析之渲染

[源码]Lua垃圾回收详解

表哥有话说 第62期记一次路由器lua反编译过程