Friday, March 15, 2013

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


nameargsdesc
OP_CALLABCABC R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
CALL執行一個函數調用。寄存器A中存放函數對象,所有參數按順序放置在A後面的寄存器中。B-1表示參數個數。如果參數列表的最後一個表達式是變長的,則B會設置為0,表示使用A+1到當前棧頂作為參數。函數調用的返回值會按順序存放在從寄存器A開始的C-1個寄存器中。如果C為0,表示返回值的個數由函數決定。
  1. font font f(); </ font </ font >  
  1. font font     1 [1] GETTABUP 0 0 -1 ; _ENV "f" </ font </ font font ></ font font font >  
  2.     2 [1] CALL 0 1 1 </ font </ font font </ font font font >  
  3.     3 [1] RETURN 0 1 </ font </ font font </ font >  
第一行取得全局變量f的值,保存到寄存器0。第二行CALL調用寄存器0中的函數,參數和返回值都是0。
  1. <font><font>local t = {f(...)};</font></font>  
  1. <font><font> 1 [1] NEWTABLE 0 0 0</font></font><font></font><font><font>  
  2.     2 [1] GETTABUP 1 0 -1 ; _ENV "f"</font></font><font></font><font><font>  
  3.     3 [1] VARARG 2 0</font></font><font></font><font><font>  
  4.     4 [1] CALL 1 0 0</font></font><font></font><font><font>  
  5.     5 [1] SETLIST 0 0 1 ; 1</font></font><font></font><font><font>  
  6.     6 [1] RETURN 0 1</font></font><font></font>  
第一行NETTABLE創建一個表放到寄存器0中。第二行獲取全局變量f放到寄存器1中。第三行VARARG表示使用當前函數的變長參數列表。第四行的CALL調用寄存器1中的函數,B為0,代表參數是變長的​​。前面講過,如果表的構造的最後一項是多返回值的表達式,則這個表會接受所有的返回值。這裡就是這種情況,表的構造會接受函數所有的返回值,所以C也為0。

nameargsdesc
OP_TAILCALLABCABC return R(A)(R(A+1), ... ,R(A+B-1))
如果一個return statement只有一個函數調用表達式,這個函數調用指令CALL會被改為TAILCALL指令。TAILCALL不會為要調用的函數增加調用堆棧的深度,而是直接使用當前調用信息。ABC操作數與CALL的意思一樣,不過C永遠都是0。TAILCALL在執行過程中,只對lua closure進行tail call處理,對於c closure,其實與CALL沒什麼區別。
  1. <font><font>return f();</font></font>  
  1. <font><font> 1 [1] GETTABUP 0 0 -1 ; _ENV "f"</font></font><font></font><font><font>  
  2.     2 [1] TAILCALL 0 1 0</font></font><font></font><font><font>  
  3.     3 [1] RETURN 0 0</font></font><font></font><font><font>  
  4.     4 [1] RETURN 0 1</font></font>  
上面如果f是一個lua closure,那麼執行到第二行後,此函數就會返回了,不會執行到後面第三行的RETURN。如果f是一個c closure,那就和CALL一樣調用這個函數,然後依賴第三行的RETURN返回。這就是為什麼TAILCALL後面還會己跟著生成一個RETURN的原因。

nameargsdesc
OP_RETURNABreturn R(A), ... ,R(A+B-2)
RETURE將返回結果存放到寄存器A到寄存器A+B-2中。如果返回的為變長表達式,則B會被設置為0,表示將寄存器A到當前棧頂的所有值返回。
  1. <font><font>return 1;</font></font>  
  1. <font><font> 1 [1] LOADK 0 -1 ; 1</font></font><font></font><font><font>  
  2.     2 [1] RETURN 0 2</font></font><font></font><font><font>  
  3.     3 [1] RETURN 0 1</font></font><font></font>  
RETURN只能從寄存器返回數據,所以第一行LOADK先將常量1裝載道寄存器0,然後返回。
  1. <font><font>return ...;  
  2. </font></font>  
  1. <font><font> 1 [1] VARARG 0 0</font></font><font></font><font><font>  
  2.     2 [1] RETURN 0 0</font></font><font></font><font><font>  
  3.     3 [1] RETURN 0 1</font></font><font></font>  
因為返回的為變長表達式,B為0。

nameargsdesc
OP_CLOSUREA BxR(A) := closure(KPROTO[Bx])
CLOSURE為指定的函數prototype創建一個closure,並將這個closure保存到寄存器A中。Bx用來指定函數prototype的id。
  1. <font><font>local function f()</font></font><font></font><font><font>  
  2. end</font></font>  
  1. <font><font>main <test.lua:0,0> (2 instructions at 0x102a016f0)</font></font><font></font><font><font>  
  2. 0+ params, 2 slots, 1 upvalue, 1 local, 0 constants, 1 function</font></font><font></font><font><font>  
  3.     1 [2] CLOSURE 0 0 ; 0x102a019b0</font></font><font></font><font><font>  
  4.     2 [2] RETURN 0 1</font></font><font></font><font><font>  
  5. constants (0) for 0x102a016f0:</font></font><font></font><font><font>  
  6. locals (1) for 0x102a016f0:</font></font><font></font><font><font>  
  7.     0 f 2 3</font></font><font></font><font><font>  
  8. upvalues​​​​ (1) for 0x102a016f0:</font></font><font></font><font><font>  
  9.     0 _ENV 1 0</font></font><font></font>  
  10. <font></font><font><font>  
  11. function <test.lua:1,2> (1 instruction at 0x102a019b0)</font></font><font></font><font><font>  
  12. 0 params, 2 slots, 0 upvalues​​​​, 0 locals, 0 constants, 0 functions</font></font><font></font><font><font>  
  13.     1 [2] RETURN 0 1</font></font><font></font><font><font>  
  14. constants (0) for 0x102a019b0:</font></font><font></font><font><font>  
  15. locals (0) for 0x102a019b0:</font></font><font></font><font><font>  
  16. upvalues​​​​ (0) for 0x102a019b0:</font></font><font></font>  
上面生成了一個主函數和一個子函數,CLOSURE將為這個索引為0的子函數生成一個closure,並保存到寄存器0中。

nameargsdesc
OP_VARARGABR(A), R(A+1), ..., R(A+B-2) = vararg 
VARARG直接對應'...'運算符。VARARG拷貝B-1個參數到從A開始的寄存器中,如果不足,使用nil補充。如果B為0,表示拷貝實際的參數數量。
  1. <font><font>local a = ...;</font></font>  
  1. <font><font> 1 [1] VARARG 0 2</font></font><font></font><font><font>  
  2.     2 [1] RETURN 0 1</font></font><font></font>  
上面第一行表示拷貝B-1個,也就是1個變長參數到寄存器0,也就是local a中。
  1. <font><font>f(...);  
  2. </font></font>  
  1. <font><font> 1 [1] GETTABUP 0 0 -1 ; _ENV "f"</font></font><font></font><font><font>  
  2.     2 [1] VARARG 1 0</font></font><font></font><font><font>  
  3.     3 [1] CALL 0 0 1</font></font><font></font><font><font>  
  4.     4 [1] RETURN 0 1</font></font><font></font>  
由於函數調用最後一個參數可以接受不定數量的參數,所以第二行生成的VARARG的B參數為0。

nameargsdesc
OP_SELFABC
R(A+1) := R(B); R(A) := R(B)[RK(C)]
SELF是專門為“:”運算符準備的指令。從寄存器B表示的table中,獲取出C作為key的closure,存入寄存器A中,然後將table本身存入到寄存器A+1中,為接下來調用這個closure做準備。
  1. <font><font>a:b();  
  2. </font></font>  
  1. <font><font> 1 [1] GETTABUP 0 0 -1 ; _ENV "a"</font></font><font></font><font><font>  
  2.     2 [1] SELF 0 0 -2 ; "b"</font></font><font></font><font><font>  
  3.     3 [1] CALL 0 2 1</font></font><font></font><font><font>  
  4.     4 [1] RETURN 0 1</font></font><font></font>  
看一下與上面語法等價的表示方法生成的指令:
  1. <font><font>ab(a);</font></font>  
  1. <font><font> 1 [1] GETTABUP 0 0 -1 ; _ENV "a"</font></font><font></font><font><font>  
  2.     2 [1] GETTABLE 0 0 -2 ; "b"</font></font><font></font><font><font>  
  3.     3 [1] GETTABUP 1 0 -1 ; _ENV "a"</font></font><font></font><font><font>  
  4.     4 [1] CALL 0 2 1</font></font><font></font><font><font>  
  5.     5 [1] RETURN 0 1</font></font><font></font>  
比使用“:"操作符多使用了一個指令。所以,如果需要使用這種面向對象調用的語義時,應該盡量使用”:"。

No comments: