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

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

1
2
3
4
local a = 0;
while(a < 10) do
a = a + 1;
end

1
2
3
4
5
6
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。

1
2
3
4
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

1
2
3
4
local a;
for i = 1, 10 do
a = i;
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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不会影响循环计数。

1
2
3
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 }

1
2
3
for i,v in 1,2,3 do
a = 1;
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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,然后跳转到第五行,进行循环。

-------------本文结束 感谢阅读-------------