Lua5.3 虚拟机指令分析(八)循环

来源:互联网 发布:剑网三捏脸数据免费 编辑:程序博客网 时间:2024/06/05 14:35

Lua5.3 虚拟机指令分析(八)循环

while

Lua 处了 for 循环之外的其它各种循环(while)都使用关系和逻辑指令,配合JMP 指令来完成。

TTcs-Mac-mini:OpCode ttc$ cat tOP_WHILE.lualocal a = 0;  while(a < 10) do      a = a + 1;  end print(a)TTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_WHILE.luamain <tOP_WHILE.lua:0,0> (9 instructions at 0x7ff365c039c0)0+ params, 3 slots, 1 upvalue, 1 local, 4 constants, 0 functions    1   [1] LOADK       (iABx) [A]0 [K]-1   ; 0    2   [2] LT          (iABC) [A]0 [ISK]0[B]0[ISK]256[C]-2 ; - 10    3   [2] JMP         (iAsBx) [A]0 [sBx]2 ; to 6    4   [3] ADD         (iABC) [A]0 [ISK]0[B]0[ISK]256[C]-3 ; - 1    5   [3] JMP         (iAsBx) [A]0 [sBx]-4    ; to 2    6   [6] GETTABUP    (iABC) [A]1 [ISK]0[B]0[ISK]256[C]-4 ; _ENV "print"    7   [6] MOVE        (iABC) [A]2 [ISK]0[B]0[ISK]0    8   [6] CALL        (iABC) [A]1 [ISK]0[B]2[ISK]0[C]1    9   [6] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (4) for 0x7ff365c039c0:    1(idx)  0    2(idx)  10    3(idx)  1    4(idx)  "print"locals (1) for 0x7ff365c039c0:    0   a(name)      2(startpc)     10(endpc)upvalues (1) for 0x7ff365c039c0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$

第二条指令 使用LT 对寄存器 0 和 常量 10 比较,如果结果 与 A 为 1 (true) 比较 不相等(false),则跳过第三条指令 JMP ,执行第四条指令 ADD ,将寄存器 0 的值 加一 后,执行第五条指令 JMP ,sBx = -4,说明向前跳转 4条指令,跳转到 第二条指令, 这里就是循环,直到第二条LT 指令的结果 与 A 为1(true)比较 相等(true),则执行 第三条指令 JMP,sBx = 2,向后跳转 2条指令,跳转到第6条指令。 循环结束。

for 相关指令

for 循环分为两类:
1. 一种是 numeric for loop,循环变量以某个长度来递增,直到某个阈值时 退出 for 循环。
2. 另一种 generic for loop,每次循环开始时调用一个函数,只有当返回值部位 nil 时才继续进行循环。

numeric for loop

'' '' 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               */TTcs-Mac-mini:OpCode ttc$ cat tOP_FORLOOP.lualocal afor i = 1,10 do    a=iendprint(a)TTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_FORLOOP.luamain <tOP_FORLOOP.lua:0,0> (11 instructions at 0x7fd3aa4039d0)0+ params, 5 slots, 1 upvalue, 5 locals, 3 constants, 0 functions    1   [1] LOADNIL     (iABC) [A]0 [ISK]0[B]0[ISK]0    2   [2] LOADK       (iABx) [A]1 [K]-1   ; 1    3   [2] LOADK       (iABx) [A]2 [K]-2   ; 10    4   [2] LOADK       (iABx) [A]3 [K]-1   ; 1    5   [2] FORPREP     (iAsBx) [A]1 [sBx]1 ; to 7    6   [3] MOVE        (iABC) [A]0 [ISK]0[B]4[ISK]0    7   [2] FORLOOP     (iAsBx) [A]1 [sBx]-2    ; to 6    8   [5] GETTABUP    (iABC) [A]1 [ISK]0[B]0[ISK]256[C]-3 ; _ENV "print"    9   [5] MOVE        (iABC) [A]2 [ISK]0[B]0[ISK]0    10  [5] CALL        (iABC) [A]1 [ISK]0[B]2[ISK]0[C]1    11  [5] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (3) for 0x7fd3aa4039d0:    1(idx)  1    2(idx)  10    3(idx)  "print"locals (5) for 0x7fd3aa4039d0:    0   a(name)      2(startpc)     12(endpc)    1   (for index)(name)    5(startpc)     8(endpc)    2   (for limit)(name)    5(startpc)     8(endpc)    3   (for step)(name)     5(startpc)     8(endpc)    4   i(name)      6(startpc)     7(endpc)upvalues (1) for 0x7fd3aa4039d0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$ 

Numeric for loop内部使用了3个局部变量来控制循环,他们分别是”for index”,“for limit”和“for step”。
1. “for index”用作存放初始值和循环计数器;
2. “for limit”用作存放循环上限;
3. “for step”用作存放循环改变长度。

对于上面的代码,三个值分别为 1,20,1,这三个局部变量对于使用者是不可见得,我们可以在生成代码的locals表中看到这3个局部变量,他们的有效作用范围为第五条指令 到 第八条指令,也就是整个for循环 子结构(block)。
还有一个使用到的局部变量,就是使用者自己指定的计数器(i)。可以看到,这个局部变量的有效作用范围 为第六条指令 到 第七条指令,也就是 循环内部。这个变量在每次循环时都被设置成”for index”变量来使用。

第二条指令到 第四条指令 初始化循环使用的3个 内部局部变量。

第五条FORPREP 指令将 寄存器 1 “for index” 减去一个 寄存器 3 “for step” 的值 赋给 寄存器 1 ,sBx= 1,PC +1 ,跳转到第七条指令 FORLOOP 执行,将 寄存器 1 “for index” 加上一个 寄存器 3 “for step” 的值 赋给 寄存器 1 ,然后与 “for limit” 比较,如果小于等于 “for limit”,则将 i 设置成 “for index”,sBx = -2 向前跳转两条指令, PC -2 = 6 ,然后跳回 第 六条指令。否则就退出循环。

可以看出来,i 并不用于真正的循环计数,而只是在每次循环时被赋予真正的计数器 “for index” 的值而已,所以在循环中修改 i 不会影响循环计数。

generic for loop

'' 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 }*/TTcs-Mac-mini:OpCode ttc$ cat  tOP_TFORCALL.lualocal t = {1,2,3,4}for i,v in ipairs(t) do    print(i,v)endTTcs-Mac-mini:OpCode ttc$ ./luac -l -l tOP_TFORCALL.luamain <tOP_TFORCALL.lua:0,0> (17 instructions at 0x7ff1504039e0)0+ params, 9 slots, 1 upvalue, 6 locals, 6 constants, 0 functions    1   [1] NEWTABLE    (iABC) [A]0 [ISK]0[B]4[ISK]0[C]0    2   [1] LOADK       (iABx) [A]1 [K]-1   ; 1    3   [1] LOADK       (iABx) [A]2 [K]-2   ; 2    4   [1] LOADK       (iABx) [A]3 [K]-3   ; 3    5   [1] LOADK       (iABx) [A]4 [K]-4   ; 4    6   [1] SETLIST     (iABC) [A]0 [ISK]0[B]4[ISK]0[C]1    ; 1    7   [2] GETTABUP    (iABC) [A]1 [ISK]0[B]0[ISK]256[C]-5 ; _ENV "ipairs"    8   [2] MOVE        (iABC) [A]2 [ISK]0[B]0[ISK]0    9   [2] CALL        (iABC) [A]1 [ISK]0[B]2[ISK]0[C]4    10  [2] JMP         (iAsBx) [A]0 [sBx]4 ; to 15    11  [3] GETTABUP    (iABC) [A]6 [ISK]0[B]0[ISK]256[C]-6 ; _ENV "print"    12  [3] MOVE        (iABC) [A]7 [ISK]0[B]4[ISK]0    13  [3] MOVE        (iABC) [A]8 [ISK]0[B]5[ISK]0    14  [3] CALL        (iABC) [A]6 [ISK]0[B]3[ISK]0[C]1    15  [2] TFORCALL    (iABC) [A]1 [ISK]0[ISK]0[C]2    16  [2] TFORLOOP    (iAsBx) [A]3 [sBx]-6    ; to 11    17  [4] RETURN      (iABC) [A]0 [ISK]0[B]1[ISK]0constants (6) for 0x7ff1504039e0:    1(idx)  1    2(idx)  2    3(idx)  3    4(idx)  4    5(idx)  "ipairs"    6(idx)  "print"locals (6) for 0x7ff1504039e0:    0   t(name)      7(startpc)     18(endpc)    1   (for generator)(name)    10(startpc)    17(endpc)    2   (for state)(name)    10(startpc)    17(endpc)    3   (for control)(name)      10(startpc)    17(endpc)    4   i(name)      11(startpc)    15(endpc)    5   v(name)      11(startpc)    15(endpc)upvalues (1) for 0x7ff1504039e0:    0    _ENV(name)      1(instack)      0(idx)TTcs-Mac-mini:OpCode ttc$ 

Generic for loop内部也使用了3个局部变量来控制循环,分别是”for generator”,“for state”和“for control”。

  1. “for generator”,用来存放迭代使用的closure,每次迭代都会调用这个closure;
  2. “for state”, “for control”,用于存放传给 “for generator”的两个参数

Generic for loop还使用自定义的局部变量i,v,用来存储”for generator”的返回值。
前8条指令 初始化 表 t 。
第10条指令 JMP sBx = 4, PC+4 = 15,跳转到第15条指令 TFORCALL 开始执行,使用寄存器 1 “for generator” 中的 closure,传入 A+1 = 2 寄存器 “for state”,和 A+2 = 3 寄存器 “for control” ,然后将结果返回给 A+3 = 4 寄存器 表示的局部变量 i 和 A+2+C = 5 寄存器表示的 局部变量 v。 然后执行第17条指令 TFORLOOP 进行循环条件判断,判断 A+1 = 4 寄存器 也就是局部变量 i 是否为 nil,如果不为空,则将寄存器 4 ,也就是局部变量 i 的值赋给 寄存器 3 “for control”, sBx = -6 , PC-6 = 11 ,然后跳转 第11条指令 开始进行循环。

原创粉丝点击