探索Lua5.2内部实现:虚拟机指令(8) LOOP

原文

Lua5.2种除了for循环之外,其他的各种循环都使用关系和逻辑指令,配合JMP指令来完成。

local a = 0;
while(a < 10) do
    a = a + 1;
end
        1       [1]     LOADK           0 -1    ; 0
        2       [2]     LT              0 0 -2  ; - 10
        3       [2]     JMP             0 2     ; to 6
        4       [3]     ADD             0 0 -3  ; - 1
        5       [3]     JMP             0 -4    ; to 2
        6       [4]     RETURN          0 1
第二行使用LT对寄存器0和敞亮10进行比较,如果小于成立,跳过第三行的JMP,运行第四行的ADD指令,将a加1,然后运行第五行的JMP,跳转回第二行,重新判断条件。如果小于不成立,则直接运行下一个JMP指令,跳转到第六行结束。

对于for循环,Lua5.2使用了两套专门的指令,分别对应numeric for loop和generic for loop。

name args desc
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

 local a;
 for i = 1, 10 do
     a = i;
 end
main <test.lua:0,0> (8 instructions at 0x80048eb0)
0+ params, 5 slots, 1 upvalue, 5 locals, 2 constants, 0 functions
        1       [1]     LOADNIL         0 0
        2       [2]     LOADK           1 -1    ; 1
        3       [2]     LOADK           2 -2    ; 10
        4       [2]     LOADK           3 -1    ; 1
        5       [2]     FORPREP         1 1     ; to 7
        6       [3]     MOVE            0 4
        7       [2]     FORLOOP         1 -2    ; to 6
        8       [4]     RETURN          0 1
constants (2) for 0x80048eb0:
        1       1
        2       10
locals (5) for 0x80048eb0:
        0       a       2       9
        1       (for index)     5       8
        2       (for limit)     5       8
        3       (for step)      5       8
        4       i       6       7
upvalues (1) for 0x80048eb0:
        0       _ENV    1       0
Numeric for loop内部使用了3个局部变量来控制循环,他们分别是"for index",“for limit”和“for step”。“for index”用作存放初始值和循环计数器,“for limit”用作存放循环上限,“for step”用作存放循环步长。对于上面的程序,三个值分别是1,10和1。这三个局部变量对于使用者是不可见得,我们可以在生成代码的locals表中看到这3个局部变量,他们的有效范围为第五行道第八行,也就是整个for循环。还有一个使用到的局部变量,就是使用者自己指定的计数器,上例中为"i"。我们可以看到,这个局部变量的有效范围为6~7行,也就是循环的内部。这个变量在每次循环时都被设置成"for index"变量来使用。

上例中2~4行初始化循环使用的3个内部局部变量。第五行FORPREP用于准备这个循环,将for index减去一个for step,然后跳转到第七行。第七行的FORLOOP将for index加上一个for step,然后与for limit进行比较。如果小于等于for limit,则将i设置成for index,然后跳回第六行。否则就退出循环。我们可以看到,i并不用于真正的循环计数,而只是在每次循环时被赋予真正的计数器for index的值而已,所以在循环中修改i不会影响循环计数。

name args desc
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 }

for i,v in 1,2,3 do
    a = 1;
end
main <test.lua:0,0> (8 instructions at 0x80048eb0)
0+ params, 6 slots, 1 upvalue, 5 locals, 4 constants, 0 functions
        1       [1]     LOADK           0 -1    ; 1
        2       [1]     LOADK           1 -2    ; 2
        3       [1]     LOADK           2 -3    ; 3
        4       [1]     JMP             0 1     ; to 6
        5       [2]     SETTABUP        0 -4 -1 ; _ENV "a" 1
        6       [1]     TFORCALL        0 2
        7       [1]     TFORLOOP        2 -3    ; to 5
        8       [3]     RETURN          0 1
constants (4) for 0x80048eb0:
        1       1
        2       2
        3       3
        4       "a"
locals (5) for 0x80048eb0:
        0       (for generator) 4       8
        1       (for state)     4       8
        2       (for control)   4       8
        3       i       5       6
        4       v       5       6
upvalues (1) for 0x80048eb0:
        0       _ENV    1       0

Generic for loop内部也使用了3个局部变量来控制循环,分别是"for generator”,“for state”和“for control”。for generator用来存放迭代使用的closure,每次迭代都会调用这个closure。for state和for control用于存放传给for generator的两个参数。Generic for loop还使用自定义的局部变量i,v,用来存储for generator的返回值。

上例中1~3行使用in后面的表达式列表(1,2,3)初始化3个内部使用的局部变量。第四行JMP调转到第六行。TFORCALL教用寄存器0(for generator)中的closure,传入for state和for control,并将结果返回给自定义局部变量列表i和v。第七行调用TFORLOOP进行循环条件判断,判断i是否为空。如果不为空,将i的值赋给for control,然后跳转到第五行,进行循环。



你可能感兴趣的:(虚拟机,lua,lua,指令)