Lua5.3 虚拟机指令分析(一)概述

来源:互联网 发布:数据结构c语言课程设计 编辑:程序博客网 时间:2024/06/07 09:26

Lua5.3 虚拟机指令分析(一)概述

概述

Lua VM 使用的是 Register based VM。 指令都是在已经分配好的寄存器中存取操作数。
add a b c 将 寄存器 b 与 寄存器 c 中的值相加,结果存在 寄存器 a 中。 标准的三地址指令,每条指令的表达能力很强,并有效的减少了内存赋值操作。

#if LUAI_BITSINT >= 32typedef unsigned int Instruction;#elsetypedef unsigned long Instruction;#endif

OpCode

Lua 的指令使用一个 32bit 的无符号整数表示,其中低 6 位 表示操作码,所以最多支持 2 ^ 6 = 64 条指令。所有的指令定义如下:

typedef enum {/*----------------------------------------------------------------------name        args    description------------------------------------------------------------------------*/OP_MOVE,/*  A B R(A) := R(B)                    */OP_LOADK,/* A Bx    R(A) := Kst(Bx)                 */OP_LOADKX,/*    A   R(A) := Kst(extra arg)              */OP_LOADBOOL,/*  A B C   R(A) := (Bool)B; if (C) pc++            */OP_LOADNIL,/*   A B R(A), R(A+1), ..., R(A+B) := nil        */OP_GETUPVAL,/*  A B R(A) := UpValue[B]              */OP_GETTABUP,/*  A B C   R(A) := UpValue[B][RK(C)]           */OP_GETTABLE,/*  A B C   R(A) := R(B)[RK(C)]             */OP_SETTABUP,/*  A B C   UpValue[A][RK(B)] := RK(C)          */OP_SETUPVAL,/*  A B UpValue[B] := R(A)              */OP_SETTABLE,/*  A B C   R(A)[RK(B)] := RK(C)                */OP_NEWTABLE,/*  A B C   R(A) := {} (size = B,C)             */OP_SELF,/*  A B C   R(A+1) := R(B); R(A) := R(B)[RK(C)]     */OP_ADD,/*   A B C   R(A) := RK(B) + RK(C)               */OP_SUB,/*   A B C   R(A) := RK(B) - RK(C)               */OP_MUL,/*   A B C   R(A) := RK(B) * RK(C)               */OP_MOD,/*   A B C   R(A) := RK(B) % RK(C)               */OP_POW,/*   A B C   R(A) := RK(B) ^ RK(C)               */OP_DIV,/*   A B C   R(A) := RK(B) / RK(C)               */OP_IDIV,/*  A B C   R(A) := RK(B) // RK(C)              */OP_BAND,/*  A B C   R(A) := RK(B) & RK(C)               */OP_BOR,/*   A B C   R(A) := RK(B) | RK(C)               */OP_BXOR,/*  A B C   R(A) := RK(B) ~ RK(C)               */OP_SHL,/*   A B C   R(A) := RK(B) << RK(C)              */OP_SHR,/*   A B C   R(A) := RK(B) >> RK(C)              */OP_UNM,/*   A B R(A) := -R(B)                   */OP_BNOT,/*  A B R(A) := ~R(B)                   */OP_NOT,/*   A B R(A) := not R(B)                */OP_LEN,/*   A B R(A) := length of R(B)              */OP_CONCAT,/*    A B C   R(A) := R(B).. ... ..R(C)           */OP_JMP,/*   A sBx   pc+=sBx; if (A) close all upvalues >= R(A - 1)  */OP_EQ,/*    A B C   if ((RK(B) == RK(C)) ~= A) then pc++        */OP_LT,/*    A B C   if ((RK(B) <  RK(C)) ~= A) then pc++        */OP_LE,/*    A B C   if ((RK(B) <= RK(C)) ~= A) then pc++        */OP_TEST,/*  A C if not (R(A) <=> C) then pc++           */OP_TESTSET,/*   A B C   if (R(B) <=> C) then R(A) := R(B) else pc++ */OP_CALL,/*  A B C   R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */OP_TAILCALL,/*  A B C   return R(A)(R(A+1), ... ,R(A+B-1))      */OP_RETURN,/*    A B return R(A), ... ,R(A+B-2)  (see note)  */OP_FORLOOP,/*   A sBx   R(A)+=R(A+2);            if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/OP_FORPREP,/*   A sBx   R(A)-=R(A+2); pc+=sBx               */OP_TFORCALL,/*  A C R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));  */OP_TFORLOOP,/*  A sBx   if R(A+1) ~= nil then { R(A)=R(A+1); pc += sBx }*/OP_SETLIST,/*   A B C   R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B    */OP_CLOSURE,/*   A Bx    R(A) := closure(KPROTO[Bx])         */OP_VARARG,/*    A B R(A), R(A+1), ..., R(A+B-2) = vararg        */OP_EXTRAARG/*   Ax  extra (larger) argument for previous opcode */} OpCode;

注释说明了该指令操作方式

/*** R(x) - register** Kst(x) - constant (in constant table)** RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x)*/

R()表示这一定是 寄存器索引(一定要操作Lua 栈)
RK()表示这有可能是 一个寄存器索引 或 是一个常量索引,RK 只能用 参数B 与 参数 C (SIZE_B = SIZE_C = 9 ),其中参数的最高位区分 寄存器索引与常量索引。

下面这组宏提供一些操作:

/*** Macros to operate RK indices*//* this bit 1 means constant (0 means register) */#define BITRK       (1 << (SIZE_B - 1))/* test whether value is a constant */#define ISK(x)      ((x) & BITRK)/* gets the index of the constant */#define INDEXK(r)   ((int)(r) & ~BITRK)#define MAXINDEXRK  (BITRK - 1)/* code a constant index as a RK value */#define RKASK(x)    ((x) | BITRK)

Opcode Type

下表整理了指令的描述信息:
这里写图片描述

  1. 每条指令都会对一个对象做出影响,受影响的对象被称为 A。它由 8 bits 来表示。 A 通常是一个寄存器的索引,也可能是对 Upvalue 的操作。
  2. 作用到 A 的参数一般有两个,每个参数 由 9 bits 表示,分别称为 B 和 C。
  3. 一部分指令不需要两个操作参数,这时候可以把 B 和 C 合并为一个 18 bits 的整数 Bx 来适应更大的范围。
  4. 当操作码涉及到跳转指令时,这个参数表示跳转偏移量。向前跳转需要设置偏移量为一个负数。这类指令需要带符号信息来区别,记作 sBx。 其中0被表示为 2^17 ; 1 则表示为 2^17 + 1 ; -1 表示为 2^17 - 1 。
  5. Lua VM 在运行期,会将需要的常量加载到 寄存器中(Lua 栈),然后利用这些寄存器做相应的工作。 加载常量的操作码 为LOADK,它由两个参数 A ,Bx ,这个操作把Bx 所指的常量加载到 A 所指的寄存器中。 Bx 有 18 bit 长,所以 LOADK 这个操作只能索引到 2^18 个常量。 为了扩大索引常量的上限,提供了LOADKX,它将常量索引号放在了接下来的一条EXTRAARG 指令中。 OP_EXTRAARG 指令 把 opcode所占的 8bit 以外的26 bit 都用于参数表示, 称之为* Ax*
    参数 A、B、C 所占的位数大小以及偏移量 ,在Lua 中由以下一组宏定义:

    /*
    ** 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_Ax (SIZE_C + SIZE_B + SIZE_A)

    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

    define POS_Ax POS_A

参数 A、B、C 一般用来存放指令操作数据的地址(索引),而地址(索引)有以下三种:
1. 寄存器 idx
2. 常量表 idx
3. upvalue idx

个人理解:
- Lua VM 是基于寄存器结构实现的,也就是说:每一段Lua chunk 代码都可以看成 被翻译成的一组对 256 (Opcode 只有 8 bit 2^8=256)个寄存器的操作指令。
- 我们在用C 语言给Lua 编写扩展的时候,C 函数通常是从lua-State 中取出参数逐个记录在 C 的局部变量中, 然后利用 C 代码直接对这些值进行操作。
- 在Lua 中 值都存储在三个地方:\<1>存在 Lua 寄存器中(也就是 Lua 的数据栈)局部变量。 \<2> 常量表中 ,一般存储存储常量。\<3> 一些既不是常量也不在寄存器的数据 , 存储在 upvalue 表中 或者 Table 表中。 可以看出来 OpCode 参数 A、B、C 存储的操作数据的索引根据不同的指令在三种不同的存储位置(寄存器、常量表、upvalue表)来获取值。
- Lua 使用当前函数的 栈来作为寄存器使用(Lua 寄存器 = Lua 栈),寄存器的idx 从 0 开始。当前函数的栈等同于寄存器数组,即 stack(n) = register(n)。
- 每一个函数的原型 Proto 都有一个属于本函数的常量表,用于存储编译过程中函数所使用到的常量。常量表可以存放 nil、boolean、number、string类型的数据,常量的idx 从 1 开始
- 每一个函数的原型Proto 中都有一个upvalue 表,用于存储在编译过程中该函数使用的upvalue 。在运行期,通过OP-CLOSURE 指令创建一个 closure时,会根据 Proto 中的描述为这个 closure 初始化upvalue 表。upvalue 也是根据id来索引的。 upvalue 的idx 从 0开始

       TTcs-Mac-mini:OpCode ttc$ cat tOP_VALUE.lua     local a = 100    function foo()        local b = 200        a = 300        c = b    end    TTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_VALUE.lua    main <tOP_VALUE.lua:0,0> (4 instructions at 0x7f92e44039b0)    0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 1 function        1   [1] LOADK       (iABx) [A]0 [K]-1   ; 100        2   [7] CLOSURE     (iABx) [A]1 [U]0    ; 0x7f92e4403b70        3   [3] SETTABUP    (iABC) [A]0 [ISK]256[B]-2[ISK]0[C]1 ; _ENV "foo"        4   [7] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0    constants (2) for 0x7f92e44039b0:        1(idx)  100        2(idx)  "foo"    locals (1) for 0x7f92e44039b0:        0   a(name)      2(startpc)     5(endpc)    upvalues (1) for 0x7f92e44039b0:        0    _ENV(name)      1(instack)      0(idx)    function <tOP_VALUE.lua:3,7> (5 instructions at 0x7f92e4403b70)    0 params, 2 slots, 2 upvalues, 1 local, 3 constants, 0 functions        1   [4] LOADK       (iABx) [A]0 [K]-1   ; 200        2   [5] LOADK       (iABx) [A]1 [K]-2   ; 300        3   [5] SETUPVAL    (iABC) [A]1 [ISK]0[B]0[ISK]0    ; a        4   [6] SETTABUP    (iABC) [A]1 [ISK]256[B]-3[ISK]0[C]0 ; _ENV "c"        5   [7] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0    constants (3) for 0x7f92e4403b70:        1(idx)  200        2(idx)  300        3(idx)  "c"    locals (1) for 0x7f92e4403b70:        0   b(name)      2(startpc)     6(endpc)    upvalues (2) for 0x7f92e4403b70:        0    a(name)     1(instack)      0(idx)        1    _ENV(name)      0(instack)      0(idx)    TTcs-Mac-mini:OpCode ttc$ '' 

Lua 代码 a 是局部变量 , 在 foo 函数中,a 是闭包变量(upvalue),c 是全局变量(_ENV 表),可以看出三个不同地方存储值的索引起始编号。

目前Lua 5.3 使用了47个OpCode ,分为了 4种不同的 mode类型:iABC , iABx , iAsBx , iAx。 Lua 用一个数组定义所有OpCode的一些说明信息:

#define opmode(t,a,b,c,m) (((t)<<7) | ((a)<<6) | ((b)<<4) | ((c)<<2) | (m))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 */};

这里用一个宏opmode封装了每个OpCode的具体格式,其中:
1. T: (第 7 bit) 表示这是不是一条逻辑测试相关的指令,这种指令可能会涉及一次条件跳转,将PC指针自增1。(之所以需要这个标记,是因为Lua 中所有涉及条件分支的地方,实际上都在分支指令后紧随着一条 JMP 指令。Lua 没有 为布尔运算单独设计opcode,它让所有的布尔运算都以分支执行流的形式出现。Lua 的 And 与 Or 关键字 支持短路求值,所以在VM 中以分支跳转的形式实现)。分支指令和之后的 JMP 跳转指令是一体的,是因为32bit 的 Instruction 无法全部描述才分拆为两条指令。这个指令可以用来检测是不是分支指令。 当遇到 JMP 指令时,可以回溯到前面的一条指令来分辨是否是一次条件跳转。 这对 生成Lua 的bytecode 模块有帮助。
2. A: (第 6 bit)表示这个指令是否会修改 register A,这个标记在 debug模块被用于跟踪最后改变register 内容的指令位置,帮助生成debug info。
3. B : (第 4-5 bit) B arg mode。
4. C : (第 2-3 bit) C arg mode。
4. mode:(第 0-1 bit)OpCode的格式,这些分类信息,用于luac 反编译字节码时的输出,对于Lua 的运行时没有实际意义。

Lua代码中提供下面一组宏, 用来根据 Opcode 参数 A、B、C 中的值(索引)来获取相应 Lua 值 (Lua VM 执行再详细分析)

#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)  \  (k + (GETARG_Bx(i) != 0 ? GETARG_Bx(i) - 1 : GETARG_Ax(*ci->u.l.savedpc++)))
原创粉丝点击