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
下表整理了指令的描述信息:
- 每条指令都会对一个对象做出影响,受影响的对象被称为 A。它由 8 bits 来表示。 A 通常是一个寄存器的索引,也可能是对 Upvalue 的操作。
- 作用到 A 的参数一般有两个,每个参数 由 9 bits 表示,分别称为 B 和 C。
- 一部分指令不需要两个操作参数,这时候可以把 B 和 C 合并为一个 18 bits 的整数 Bx 来适应更大的范围。
- 当操作码涉及到跳转指令时,这个参数表示跳转偏移量。向前跳转需要设置偏移量为一个负数。这类指令需要带符号信息来区别,记作 sBx。 其中0被表示为 2^17 ; 1 则表示为 2^17 + 1 ; -1 表示为 2^17 - 1 。
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++)))
- Lua5.3 虚拟机指令分析(一)概述
- Lua5.3 虚拟机指令分析(二)赋值指令
- Lua5.3 虚拟机指令分析(十)表相关指令
- Lua5.3 虚拟机指令分析(三)表达式运算
- Lua5.3 虚拟机指令分析(四)分支与跳转
- Lua5.3 虚拟机指令分析(五)函数调用
- Lua5.3 虚拟机指令分析(六)不定参数
- Lua5.3 虚拟机指令分析(八)循环
- 探索Lua5.2内部实现:虚拟机指令(1) 概述
- 探索Lua5.2内部实现:虚拟机指令(3) Upvalues & Globals
- 探索Lua5.2内部实现:虚拟机指令(5)Arithmetic
- 探索Lua5.2内部实现:虚拟机指令(6)FUNCTION
- Lua5.3 VM 分析(一)字节码运行
- 探索Lua5.2内部实现:虚拟机指令(2) MOVE & LOAD
- 探索Lua5.2内部实现:虚拟机指令(4) Table
- 探索Lua5.2内部实现:虚拟机指令(8) LOOP
- Lua5.3 VM 分析(二)表处理
- Lua5.3 VM 分析(三)表达式运算
- C# 向MySQL数据库存储及读取图片、音乐等文件
- myeclipse中web项目出现红色叹号解决方案
- malloc/free和new/delete
- 【离散化+并查集】POJ_1733_Parity game
- CSS3基础学习 02-应用方式:内联、内部、外部
- Lua5.3 虚拟机指令分析(一)概述
- 涉及AbstractQueuedSynchronizer使用-CountDownLatch等
- B. Godsend
- Java中转换字符串编码
- 传感器项目框架1
- Android Studio中的神操作
- MAVEN配置pom.xml名词解释
- 429A. Generous Kefa
- uva 11582 快速幂 Fibonacci循环节