Friday, March 15, 2013

探索Lua5.2內部實現:虛擬機指令(8) LOOP


Lua5.2種除了for循環之外,其他的各種循環都使用關係和邏輯指令,配合JMP指令來完成。
  1. local a = 0;  
  2. while(a < 10) do  
  3.     a = a + 1;  
  4. end  
  1. 1 [1] LOADK 0 -1 ; 0  
  2. 2 [2] LT 0 0 -2 ; - 10  
  3. 3 [2] JMP 0 2 ; to 6  
  4. 4 [3] ADD 0 0 -3 ; - 1  
  5. 5 [3] JMP 0 -4 ; to 2  
  6. 6 [4] RETURN 0 1  
第二行使用LT對寄存器0和敞亮10進行比較,如果小於成立,跳過第三行的JMP,運行第四行的ADD指令,將a加1,然後運行第五行的JMP,跳轉回第二行,重新判斷條件。如果小於不成立,則直接運行下一個JMP指令,跳轉到第六行結束。
對於for循環,Lua5.2使用了兩套專門的指令,分別對應numeric for loop和generic for loop。
nameargsdesc
OP_FORLOOPA sBxR(A)+=R(A+2); 
if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }
OP_FORPREPA sBxR(A)-=R(A+2); pc+=sBx
  1. local a;  
  2. for i = 1, 10 do  
  3.     a = i;  
  4. end  
  1. main <test.lua:0,0> (8 instructions at 0x80048eb0)  
  2. 0+ params, 5 slots, 1 upvalue, 5 locals, 2 constants, 0 functions  
  3.         1 [1] LOADNIL 0 0  
  4.         2 [2] LOADK 1 -1 ; 1  
  5.         3 [2] LOADK 2 -2 ; 10  
  6.         4 [2] LOADK 3 -1 ; 1  
  7.         5 [2] FORPREP 1 1 ; to 7  
  8.         6 [3] MOVE 0 4  
  9.         7 [2] FORLOOP 1 -2 ; to 6  
  10.         8 [4] RETURN 0 1  
  11. constants (2) for 0x80048eb0:  
  12.         1 1  
  13.         2 10  
  14. locals (5) for 0x80048eb0:  
  15.         0 a 2 9  
  16.         1 (for index) 5 8  
  17.         2 (for limit) 5 8  
  18.         3 (for step) 5 8  
  19.         4 i 6 7  
  20. upvalues​​ (1) for 0x80048eb0:  
  21.         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不會影響循環計數。
nameargsdesc
OP_TFORCALLACR(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));
OP_TFORLOOPA sBxif R(A+1) ~= nil then { R(A)=R(A+1); pc += sBx }
  1. for i,v in 1,2,3 do  
  2.     a = 1;  
  3. end  
  1. main <test.lua:0,0> (8 instructions at 0x80048eb0)  
  2. 0+ params, 6 slots, 1 upvalue, 5 locals, 4 constants, 0 functions  
  3.         1 [1] LOADK 0 -1 ; 1  
  4.         2 [1] LOADK 1 -2 ; 2  
  5.         3 [1] LOADK 2 -3 ; 3  
  6.         4 [1] JMP 0 1 ; to 6  
  7.         5 [2] SETTABUP 0 -4 -1 ; _ENV "a" 1  
  8.         6 [1] TFORCALL 0 2  
  9.         7 [1] TFORLOOP 2 -3 ; to 5  
  10.         8 [3] RETURN 0 1  
  11. constants (4) for 0x80048eb0:  
  12.         1 1  
  13.         2 2  
  14.         3 3  
  15.         4 "a"  
  16. locals (5) for 0x80048eb0:  
  17.         0 (for generator) 4 8  
  18.         1 (for state) 4 8  
  19.         2 (for control) 4 8  
  20.         3 i 5 6  
  21.         4 v 5 6  
  22. upvalues​​ (1) for 0x80048eb0:  
  23.         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,然後跳轉到第五行,進行循環。

No comments: