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

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

概述

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

#if LUAI_BITSINT >= 32
typedef unsigned int Instruction;
#else
typedef 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(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

下表整理了指令的描述信息:
Lua5.3 虚拟机指令分析(一)概述_第1张图片

  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  (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  (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++)))

你可能感兴趣的:(C/Lua/C++)