Friday, March 15, 2013

探索Lua5.2內部實現:虛擬機指令(7) 關係和邏輯指令


nameargsdesc
OP_JMPA sBxpc+=sBx; if (A) close all upvalues​​ >= R(A) + 1
JMP執行一個跳轉,sBx表示跳轉的偏移位置,被加到當前指向下一指令的指令指針上。如果sBx為0,表示沒有任何跳轉;1表示跳過下一個指令;-1表示重新執行當前指令。如果A>0,表示需要關閉所有從寄存器A+1開始的所有local變量。實際執行的關閉操作只對upvalue有效。
JMP最直接的使用就是對應lua5.2新加入的goto語句:
  1. ::l::  
  2. goto l;  
  1. 1 [1] JMP 0 -1 ; to 1  
  2. 2 [2] RETURN 0 1  
這是一個無限循環。第一行JMP的sBx為-1,表示重新執行JMP。
  1. do  
  2.     local a;  
  3.     function f() ​​a = 1 end  
  4. end  
  1. main <test.lua:0,0> (5 instructions at 0x80048eb0)  
  2. 0+ params, 2 slots, 1 upvalue, 1 local, 1 constant, 1 function  
  3.         1 [2] LOADNIL 0 0  
  4.         2 [3] CLOSURE 1 0 ; 0x80049128  
  5.         3 [3] SETTABUP 0 -1 1 ; _ENV "f"  
  6.         4 [3] JMP 1 0 ; to 5  
  7.         5 [4] RETURN 0 1  
  8. constants (1) for 0x80048eb0:  
  9.         1 "f"  
  10. locals (1) for 0x80048eb0:  
  11.         0 a 2 5  
  12. upvalues​​ (1) for 0x80048eb0:  
  13.         0 _ENV 1 0  
  14.   
  15. function <test.lua:3,3> (3 instructions at 0x80049128)  
  16. 0 params, 2 slots, 1 upvalue, 0 locals, 1 constant, 0 functions  
  17.         1 [3] LOADK 0 -1 ; 1  
  18.         2 [3] SETUPVAL 0 0 ; a  
  19.         3 [3] RETURN 0 1  
  20. constants (1) for 0x80049128:  
  21.         1 1  
  22. locals (0) for 0x80049128:  
  23. upvalues​​ (1) for 0x80049128:  
  24.         0 a 1 0  
上面的代碼在do block中創建了一個局部變量a,並且a作為upvalue在函數f中被引用到。到退出do block是,a會退出他的有效域,並且關閉他對應的upvalue。Lua5.2中去除了以前專門處理關閉upvalue的指令CLOSE,而把這個功能加入到了JMP中。所以,生成的指令第四行的JMP在這裡沒有執行跳轉,而只是為了關閉a的upvalue。
JMP其他的功能就是配合邏輯和關係指令(統稱為test指令),實現程序的條件跳轉。每個test輯指令與JMP搭配,都會將接下來生成的指令分為兩個集合,滿足條件的為true集合,否則為false集合。當test條件滿足時,指令指針回+1,跳過後面緊跟的JMP指令,然後繼續執行。當test條件不滿足時,則繼續執行,也就到了JMP,然後跳轉到分支代碼。

nameargsdesc
OP_EQABCif ((RK(B) == RK(C)) ~= A) then pc++
OP_LTABCif ((RK(B) < RK(C)) ~= A) then pc++
OP_LEABCif ((RK(B) <= RK(C)) ~= A) then pc++
關係指令對RK(B)和RK(C)進行比較,然後將比較結果與A指定的boolean值進行比較,來決定最終的boolean值。A在這里為每個關係指令提供了兩種比較目標,滿足和不滿足。比如OP_LT何以用來實現“<”和“>”。
  1. local a,b,c;  
  2. a = b < c;  
  1. 1 [1] LOADNIL 0 2  
  2. 2 [2] LT 1 1 2  
  3. 3 [2] JMP 0 1 ; to 5  
  4. 4 [2] LOADBOOL 0 0 1  
  5. 5 [2] LOADBOOL 0 1 0  
  6. 6 [2] RETURN 0 1  
第二行的LT對寄存器1和2進行LT比較,如果結果為true,則繼續執行後面的JMP,跳轉到第五行的LOADBOOL,將寄存器0賦值為true;如果結果為false,則跳過後面的JMP,執行第四行的LOADBOOL,將寄存器0賦值為false。我們前面講過關於LOADBOOL,第四行執行後會跳過第五行的賦值。

nameargsdesc
OP_TESTACif not (R(A) <=> C) then pc++
OP_TESTSETABCif (R(B) <=> C) then R(A) := R(B) else pc++
邏輯指令用於實現and和or邏輯運算符,或者在條件語句中判斷一個寄存器。TESTSET將寄存器B轉化成一個​​boolean值,然後與C進行比較。如果不相等,跳過後面的JMP指令。否則將寄存器B的值賦給寄存器A,然後繼續執行。TEST是TESTSET的簡化版,不需要賦值操作。
  1. local a,b,c;  
  2. a = b and c;  
  1. 1 [1] LOADNIL 0 2  
  2. 2 [2] TESTSET 0 1 0  
  3. 3 [2] JMP 0 1 ; to 5  
  4. 4 [2] MOVE 0 2  
  5. 5 [2] RETURN 0 1  
第二行的TESTSET將寄存器1的值與false比較。如果不成立,跳過JMP,執行第四行的MOVE,將寄存器2的值賦給寄存器0。否則,將寄存器1的值賦給寄存器0;然後執行後面的JMP。
上面的代碼等價於
  1. if b then  
  2.  a = c  
  3. else  
  4.  a = b  
  5. end  

No comments: