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 部分的初始大小。
- Lua5.3 虚拟机指令分析(二)赋值指令
- Lua5.3 虚拟机指令分析(十)表相关指令
- Lua5.3 虚拟机指令分析(一)概述
- Lua5.3 虚拟机指令分析(三)表达式运算
- Lua5.3 虚拟机指令分析(四)分支与跳转
- Lua5.3 虚拟机指令分析(五)函数调用
- Lua5.3 虚拟机指令分析(六)不定参数
- Lua5.3 虚拟机指令分析(八)循环
- 探索Lua5.2内部实现:虚拟机指令(3) Upvalues & Globals
- 探索Lua5.2内部实现:虚拟机指令(5)Arithmetic
- 探索Lua5.2内部实现:虚拟机指令(6)FUNCTION
- 探索Lua5.2内部实现:虚拟机指令(1) 概述
- 探索Lua5.2内部实现:虚拟机指令(2) MOVE & LOAD
- 探索Lua5.2内部实现:虚拟机指令(4) Table
- 探索Lua5.2内部实现:虚拟机指令(8) LOOP
- Java虚拟机常用指令(二十二)
- 探索Lua5.2内部实现:虚拟机指令(7) 关系和逻辑指令
- JSP指令(指令二)
- MAVEN配置pom.xml名词解释
- 429A. Generous Kefa
- uva 11582 快速幂 Fibonacci循环节
- CentOS 6使用openssl搭建根CA
- 01背包问题之二 (动态规划(DP))
- Lua5.3 虚拟机指令分析(二)赋值指令
- 第二章:2.3 卷积定义(卷积积分与卷积和)
- HTML---第一天内容
- 不信...如果...
- 动态规划、递归:word-break II
- 算法学习之枚举--称硬币
- Java大整数 大浮点数 处理
- xlistview用法
- Laravel使用Form Request使你的Controller更整洁