Lua5.3 虚拟机指令分析(二)赋值指令

来源:互联网 发布:unity3d汽车模型下载 编辑:程序博客网 时间:2024/06/10 15:28

Lua5.3 虚拟机指令分析(二)赋值指令

Lua VM 是基于寄存器结构实现的,也就是说,每段 Lua  chunk 代码都被翻译为一组对256 个寄存器的操作指令。这有点类似于我们为Lua编写 C 扩展。
C 函数通常是从 lua_State 中取出参数逐个记录在 C 的局部变量中,然后利用C 代码直接对这些值进行操作。

可以类比 把寄存器类比于 Lua 的寄存器。它们的确有相似之处, C 中的局部变量处于C 堆栈上,而Lua 的寄存器则处于 Lua 的数据栈中。

给Lua 局部变量赋值的过程,是由 OP_MOVE 、 OP_LOADK 、 OP_LOADKX  、 OP_LOADBOOL、  OP_LOADNIL、 OP_GETUPVAL、 OP_SETUPVAL 这组操作码完成的。

值得来源有三处:

第一:其它寄存器,即局部变量。OP_MOVE 可以完成这个工作

OP_MOVE A B R(A) := R(B)

OP_MOVE 用来将寄存器B中的值拷贝到寄存器A中。
由于Lua是register based vm,大部分的指令都是直接对寄存器进行操作,而不需要对数据进行压栈和弹栈,所以需要OP_MOVE 指令的地方并不多。
最直接的使用之处就是将一个local变量复制给另一个local变量时:

TTcs-Mac-mini:OpCode ttc$ cat tOP_MOVE.lua local alocal b = aTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_MOVE.luamain <tOP_MOVE.lua:0,0> (3 instructions at 0x7f905ac039b0)0+ params, 2 slots, 1 upvalue, 2 locals, 0 constants, 0 functions    1   [1] LOADNIL     (iABC) [A]0 [ISK]0[B]0[ISK]0    2   [2] MOVE        (iABC) [A]1 [ISK]0[B]0[ISK]0    3   [2] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (0) for 0x7f905ac039b0:locals (2) for 0x7f905ac039b0:    0   a(name)      2(startpc)     4(endpc)    1   b(name)      3(startpc)     4(endpc)upvalues (1) for 0x7f905ac039b0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$ 

在编译过程中,Lua会将每个local变量都分配到一个指定的寄存器中。在运行期,lua使用local变量所对应的寄存器id来操作local变量,而local变量的名字除了提供debug信息外,没有其他作用。

在这里a被分配给register 0,b被分配给register 1。MOVE表示将a(register 0)的值赋给b(register 1)。其他使用的地方基本都是对寄存器的位置有特殊要求的地方,比如函数参数的传递等等。

第二: 常量,nil 和 bool 类型的数据比较短,可以通过指令直接加载(不需要先加载常量表到寄存器)。

OP_LOADBOOL A B C R(A) := (Bool)B; if (C) pc++

LOADBOOL 将 B 所表示的 boolean 值装载到寄存器 A 中。B 使用 0 和 1 分别代表 false 和 true。C 也表示一个boolean值,如果C为1,就跳过下一个指令。

TTcs-Mac-mini:OpCode ttc$ cat tOP_LOADBOOL.lua local a = trueTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_LOADBOOL.luamain <tOP_LOADBOOL.lua:0,0> (2 instructions at 0x7ff1754039d0)0+ params, 2 slots, 1 upvalue, 1 local, 0 constants, 0 functions    1   [1] LOADBOOL    (iABC) [A]0 [ISK]0[B]1[ISK]0[C]0    2   [1] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (0) for 0x7ff1754039d0:locals (1) for 0x7ff1754039d0:    0   a(name)      2(startpc)     3(endpc)upvalues (1) for 0x7ff1754039d0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$ TTcs-Mac-mini:OpCode ttc$ cat tOP_LOADBOOL_2.lua local b = 1 < 2TTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_LOADBOOL_2.luamain <tOP_LOADBOOL_2.lua:0,0> (5 instructions at 0x7fda7d4039d0)0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 0 functions    1   [2] LT          (iABC) [A]1 [ISK]256[B]-1[ISK]256[C]-2  ; 1 2    2   [2] JMP         (iAsBx) [A]0 [sBx]1 ; to 4    3   [2] LOADBOOL    (iABC) [A]0 [ISK]0[B]0[ISK]0[C]1    4   [2] LOADBOOL    (iABC) [A]0 [ISK]0[B]1[ISK]0[C]0    5   [2] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (2) for 0x7fda7d4039d0:    1(idx)  1    2(idx)  2locals (1) for 0x7fda7d4039d0:    0   b(name)      5(startpc)     6(endpc)upvalues (1) for 0x7fda7d4039d0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$ 

可以看到,生成了 LT 与 JMP 指令,LT 指令本身不产生一个 boolean 值,而是配合后面紧跟着的 JMP 来实现 true 与 false 的跳转。 如果LT 为 true 就继续执行(执行到JMP 指令处),然后跳转到 JMP 所对应的 索引处;否则就跳过下一条指令 对 b 赋值 false,并且跳过下一个指令。
实际上述代码与下面代码等效。

 TTcs-Mac-mini:OpCode ttc$ cat tOP_LOADBOOL_3.lua local a;if 1 < 2 then    a = true;else    a = false;endTTcs-Mac-mini:OpCode ttc$ 

OP_LOADNIL A B  R(A), R(A+1), …, R(A+B) := nil

LOADNIL 将使用 A 到 B 所表示范围的寄存器赋值成nil。用范围表示寄存器主要为了对以下情况进行优化:

TTcs-Mac-mini:OpCode ttc$ cat tOP_LOADNIL_2.lua local a,b,c,d,e,fTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_LOADNIL_2.lua main <tOP_LOADNIL_2.lua:0,0> (2 instructions at 0x7ff816c039d0)0+ params, 6 slots, 1 upvalue, 6 locals, 0 constants, 0 functions    1   [1] LOADNIL     (iABC) [A]0 [ISK]0[B]5[ISK]0    2   [1] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (0) for 0x7ff816c039d0:locals (6) for 0x7ff816c039d0:    0   a(name)      2(startpc)     3(endpc)    1   b(name)      2(startpc)     3(endpc)    2   c(name)      2(startpc)     3(endpc)    3   d(name)      2(startpc)     3(endpc)    4   e(name)      2(startpc)     3(endpc)    5   f(name)      2(startpc)     3(endpc)upvalues (1) for 0x7ff816c039d0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$ 

对于连续的local 变量,使用一条LOADNIL 指令就可以完成。反之如下图

TTcs-Mac-mini:OpCode ttc$ cat tOP_LOADNIL.lua local a;local b = 10;local c;TTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_LOADNIL.lua main <tOP_LOADNIL.lua:0,0> (4 instructions at 0x7f8e3bc039c0)0+ params, 3 slots, 1 upvalue, 3 locals, 1 constant, 0 functions    1   [2] LOADNIL     (iABC) [A]0 [ISK]0[B]0[ISK]0    2   [3] LOADK       (iABx) [A]1 [K]-1   ; 10    3   [4] LOADNIL     (iABC) [A]2 [ISK]0[B]0[ISK]0    4   [4] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (1) for 0x7f8e3bc039c0:    1(idx)  10locals (3) for 0x7f8e3bc039c0:    0   a(name)      2(startpc)     5(endpc)    1   b(name)      3(startpc)     5(endpc)    2   c(name)      4(startpc)     5(endpc)upvalues (1) for 0x7f8e3bc039c0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$ 

OP_LOADK A Bx R(A) := Kst(Bx)

对于数字或者字符串这样的常量, 不可能直接把值编码到指令中。所以每个函数原型 Proto 都使用一个常量表来存储,只需要使用索引来引用这些常量。

TTcs-Mac-mini:OpCode ttc$ cat tOP_LOADK.lua local a = 1local b = "TTc"TTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_LOADK.luamain <tOP_LOADK.lua:0,0> (3 instructions at 0x7fce38c039b0)0+ params, 2 slots, 1 upvalue, 2 locals, 2 constants, 0 functions    1   [1] LOADK       (iABx) [A]0 [K]-1   ; 1    2   [2] LOADK       (iABx) [A]1 [K]-2   ; "TTc"    3   [2] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (2) for 0x7fce38c039b0:    1(idx)  1    2(idx)  "TTc"locals (2) for 0x7fce38c039b0:    0   a(name)      2(startpc)     4(endpc)    1   b(name)      3(startpc)     4(endpc)upvalues (1) for 0x7fce38c039b0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$

LOADK将Bx表示的常量表中的常量值装载到寄存器A中。如果常量表过大(2^18),超过了Bx表达的范围,那么就生成一个LOADKX 指令,取代LOADK指令,并且接下来立即生成一个 EXTRAARG 指令,并用其Ax来存放这个 idx

OP_LOADKX A  R(A) := Kst(extra arg)

第三:不是常量,又不再寄存器中的数据(这类数据指:upvalue 或 存在 table 中的值)

GETUPVAL 和 SETUPVAL 可以读写当前的upvalue。
GETTABUP 和 SETTABUP 可以读写 upvalue 所指的表的条目, 参数B 索引的是 upvalue 号。
GETTABLE 和 SETTABLE 可以操作的表不再 upvalue 中,而是在 寄存器中, 参数B 索引的是 寄存器号。
在编译期,如果要访问变量a时,会依照以下的顺序决定变量a的类型:
1. a是当前函数的local变量
2. a是外层函数的local变量,那么a是当前函数的upvalue
3. a是全局变量

local 变量本身就存在于当前的 register 中,所有的指令都可以直接使用它的id来访问。而对于upvalue,lua则有专门的指令负责获取和设置。

现在全局变量仅仅是对一个特殊的 upvalue _ENV 的引用,实际会有大量的Lua 代码会直接引用 _ENV 中的变量, 为这种访问方式设计一个独立的操作码有利于字节码的紧凑,提高性能。

OP_GETUPVAL A B R(A) := UpValue[B]

TTcs-Mac-mini:OpCode ttc$ cat tOP_GETUPVAL.lua local u = 100function f()    local l = uendTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_GETUPVAL.luamain <tOP_GETUPVAL.lua:0,0> (4 instructions at 0x7ffc0a4039d0)0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 1 function    1   [1] LOADK       (iABx) [A]0 [K]-1   ; 100    2   [4] CLOSURE     (iABx) [A]1 [U]0    ; 0x7ffc0a403b90    3   [2] SETTABUP    (iABC) [A]0 [ISK]256[B]-2[ISK]0[C]1 ; _ENV "f"    4   [4] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (2) for 0x7ffc0a4039d0:    1(idx)  100    2(idx)  "f"locals (1) for 0x7ffc0a4039d0:    0   u(name)      2(startpc)     5(endpc)upvalues (1) for 0x7ffc0a4039d0:    0    _ENV(name)      1(instack)      0(idx)function <tOP_GETUPVAL.lua:2,4> (2 instructions at 0x7ffc0a403b90)0 params, 2 slots, 1 upvalue, 1 local, 0 constants, 0 functions    1   [3] GETUPVAL    (iABC) [A]0 [ISK]0[B]0[ISK]0    ; u    2   [4] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (0) for 0x7ffc0a403b90:locals (1) for 0x7ffc0a403b90:    0   l(name)      2(startpc)     3(endpc)upvalues (1) for 0x7ffc0a403b90:    0    u(name)     1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$ 

GETUPVAL将B为索引的upvalue的值装载到A寄存器中。

B 代表的 upvalue 索引idx (upvalues -> 0) 指向的值 (u ) 赋值到寄存器 A 的 idx (register -> 0) 代表的局部变量 (l)。

OP_SETUPVAL A B UpValue[B] := R(A)

TTcs-Mac-mini:OpCode ttc$ cat tOP_SETUPVAL.lua local u = 19function f()    local l    u = 111endTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_SETUPVAL.luamain <tOP_SETUPVAL.lua:0,0> (4 instructions at 0x7fcfa04039d0)0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 1 function    1   [1] LOADK       (iABx) [A]0 [K]-1   ; 19    2   [5] CLOSURE     (iABx) [A]1 [U]0    ; 0x7fcfa0403b90    3   [2] SETTABUP    (iABC) [A]0 [ISK]256[B]-2[ISK]0[C]1 ; _ENV "f"    4   [5] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (2) for 0x7fcfa04039d0:    1(idx)  19    2(idx)  "f"locals (1) for 0x7fcfa04039d0:    0   u(name)      2(startpc)     5(endpc)upvalues (1) for 0x7fcfa04039d0:    0    _ENV(name)      1(instack)      0(idx)function <tOP_SETUPVAL.lua:2,5> (4 instructions at 0x7fcfa0403b90)0 params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions    1   [3] LOADNIL     (iABC) [A]0 [ISK]0[B]0[ISK]0    2   [4] LOADK       (iABx) [A]1 [K]-1   ; 111    3   [4] SETUPVAL    (iABC) [A]1 [ISK]0[B]0[ISK]0    ; u    4   [5] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (1) for 0x7fcfa0403b90:    1(idx)  111locals (1) for 0x7fcfa0403b90:    0   l(name)      2(startpc)     5(endpc)upvalues (1) for 0x7fcfa0403b90:    0    u(name)     1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$

111 是一个常量,存在于常量表中,Lua 没有常量 与 upvalue 互相直接操作指令,所以必须先使用 LOADK 指令将 常量 111 加载到 参数 A 代表的 临时寄存器 (register ->1)中,然后 SETUPVAL 将 register -> 1 的值赋给 参数B 代表的 upvalue 索引idx(upvalue-> 0 也就是 u 。

OP_GETTABUP A B C R(A) := UpValue[B][RK(C)]

GETTABUP 将 B 为索引的 upvalue 当作一个 table,并将 C 做为索引的寄存器(或者常量)当作key获取的值放入寄存器 A。

 TTcs-Mac-mini:OpCode ttc$ cat tOP_GETTABUP.lua g = 222function f()    local a = gendTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_GETTABUP.luamain <tOP_GETTABUP.lua:0,0> (4 instructions at 0x7f8cf0c039d0)0+ params, 2 slots, 1 upvalue, 0 locals, 3 constants, 1 function    1   [2] SETTABUP    (iABC) [A]0 [ISK]256[B]-1[ISK]256[C]-2  ; _ENV "g" 222    2   [5] CLOSURE     (iABx) [A]0 [U]0    ; 0x7f8cf0c03c50    3   [3] SETTABUP    (iABC) [A]0 [ISK]256[B]-3[ISK]0[C]0 ; _ENV "f"    4   [5] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (3) for 0x7f8cf0c039d0:    1(idx)  "g"    2(idx)  222    3(idx)  "f"locals (0) for 0x7f8cf0c039d0:upvalues (1) for 0x7f8cf0c039d0:    0    _ENV(name)      1(instack)      0(idx)function <tOP_GETTABUP.lua:3,5> (2 instructions at 0x7f8cf0c03c50)0 params, 2 slots, 1 upvalue, 1 local, 1 constant, 0 functions    1   [4] GETTABUP    (iABC) [A]0 [ISK]0[B]0[ISK]256[C]-1 ; _ENV "g"    2   [5] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (1) for 0x7f8cf0c03c50:    1(idx)  "g"locals (1) for 0x7f8cf0c03c50:    0   a(name)      2(startpc)     3(endpc)upvalues (1) for 0x7f8cf0c03c50:    0    _ENV(name)      0(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$

参数A 是寄存器 register->0 就是 局部变量 a。
ISK = 0 B 为寄存器 索引的 upvalue ->0 的值 是一个 table (_ENV);
ISK = 256 C为常量 constants -> 1 是常量 字符串 “g” ;

OP_SETTABUP A B C UpValue[A][RK(B)] := RK(C)

SETTABUP 将 A为索引的 upvalue 当作一个 table,将 C 代表的 寄存器 (或者常量)的值以B寄存器或常量为key,存入table。

TTcs-Mac-mini:OpCode ttc$ cat tOP_SETTABUP.lua function f()    g = 1111endTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_SETTABUP.luamain <tOP_SETTABUP.lua:0,0> (3 instructions at 0x7fa64fc039d0)0+ params, 2 slots, 1 upvalue, 0 locals, 1 constant, 1 function    1   [3] CLOSURE     (iABx) [A]0 [U]0    ; 0x7fa64fc03b80    2   [1] SETTABUP    (iABC) [A]0 [ISK]256[B]-1[ISK]0[C]0 ; _ENV "f"    3   [3] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (1) for 0x7fa64fc039d0:    1(idx)  "f"locals (0) for 0x7fa64fc039d0:upvalues (1) for 0x7fa64fc039d0:    0    _ENV(name)      1(instack)      0(idx)function <tOP_SETTABUP.lua:1,3> (2 instructions at 0x7fa64fc03b80)0 params, 2 slots, 1 upvalue, 0 locals, 2 constants, 0 functions    1   [2] SETTABUP    (iABC) [A]0 [ISK]256[B]-1[ISK]256[C]-2  ; _ENV "g" 1111    2   [3] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (2) for 0x7fa64fc03b80:    1(idx)  "g"    2(idx)  1111locals (0) for 0x7fa64fc03b80:upvalues (1) for 0x7fa64fc03b80:    0    _ENV(name)      0(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$

参数A 指 upvalues->0 的值 就是 _ENV 表
ISK = 256 参数B 为常量 constants->1 处的字符串常量 “g”
ISK = 256 参数C 为常量 constants->2 处的常量值 为数值 1111;

OP_GETTABLE A B C R(A) := R(B)[RK(C)]

GETTABLE 使用 C 表示的 key,将寄存器 B 中的表项值获取到 寄存器 A 中

TTcs-Mac-mini:OpCode ttc$ cat tOP_GETTABLE.lua local t = {}t.x=1local b = t.xTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_GETTABLE.luamain <tOP_GETTABLE.lua:0,0> (4 instructions at 0x7fb3a3c039d0)0+ params, 2 slots, 1 upvalue, 2 locals, 2 constants, 0 functions    1   [1] NEWTABLE    (iABC) [A]0 [ISK]0[B]0[ISK]0[C]0    2   [2] SETTABLE    (iABC) [A]0 [ISK]256[B]-1[ISK]256[C]-2  ; "x" 1    3   [3] GETTABLE    (iABC) [A]1 [ISK]0[B]0[ISK]256[C]-1 ; "x"    4   [3] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (2) for 0x7fb3a3c039d0:    1(idx)  "x"    2(idx)  1locals (2) for 0x7fb3a3c039d0:    0   t(name)      2(startpc)     5(endpc)    1   b(name)      4(startpc)     5(endpc)upvalues (1) for 0x7fb3a3c039d0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$

参数 A 为 寄存器 register -> 1 是局部变量 b ;
ISK = 0 参数 B 为 寄存器 register -> 0 是表 t ;
ISK = 256 参数 C 为常量 constants -> 1 是常量字符串 “x” 。

OP_SETTABLE A B C R(A)[RK(B)] := RK(C)

SETTABLE 设置寄存器 A 的表的 B项为 C 代表的值。

参数 A 为 寄存器 register -> 0 是表 t。
ISK = 256 参数B 为常量 constants -> 1 是常量字符串 “x”;
ISK = 256 说明 参数C 为常量 constants -> 2 是常量数值 1;

OP_NEWTABLE A B C R(A) := {} (size = B,C)

NEWTABLE 在寄存器 A 处创建一个table对象。B 和 C分别用来存储这个table数组部分和hash 部分的初始大小。