Friday, March 15, 2013

索Lua5.2內部實現:虛擬機指令(3) Upvalues​​ & Globals


在編譯期,如果要訪問變量a時,會依照以下的順序決定變量a的類型:
  1. a是當前函數的local變量
  2. a是外層函數的local變量,那麼a是當前函數的upvalue
  3. a是全局變量
local變量本身就存在於當前的register中,所有的指令都可以直接使用它的id來訪問。而對於upvalue,lua則有專門的指令負責獲取和設置。
全局變量在lua5.1中也是使用專門的指令,而5.2對這一點做了改變。Lua5.2種沒有專門針對全局變量的指令,而是把全局表放到最外層函數的名字為"_ENV"的upvalue中。對於全局變量a,相當於編譯期幫你改成了_ENV.a來進行訪問。
nameargsdesc
OP_GETUPVALABCR(A) := UpValue[B]
OP_SETUPVALABUpValue[B] := R(A)
OP_GETTABUPABCR(A) := UpValue[B][RK(C)]
OP_SETTABUPABCUpValue[A][RK(B)] := RK(C)
GETUPVAL將B為索引的upvalue的值裝載到A寄存器中。SETUPVAL將A寄存器的值保存到B為索引的upvalue中。
GETTABUP將B為索引的upvalue當作一個table,並將C做為索引的寄存器或者常量當作key獲取的值放入寄存器A。SETTABUP將A為索引的upvalue當作一個table,將C寄存器或者常量的值以B寄存器或常量為key,存入table。
  1. local u = 0;  
  2. function f()   
  3.     local l;  
  4.     u = 1;   
  5.     l = u;  
  6.     g = 1;  
  7.     l = g;  
  8. end  
  1. main <test.lua:0,0> (4 instructions at 0x80048eb0)  
  2. 0+ params, 2 slots, 1 upvalue, 1 local, 2 constants, 1 function  
  3.     1 [1] LOADK 0 -1 ; 0  
  4.     2 [8] CLOSURE 1 0 ; 0x80049140  
  5.     3 [2] SETTABUP 0 -2 1 ; _ENV "f"  
  6.     4 [8] RETURN 0 1  
  7. constants (2) for 0x80048eb0:  
  8.     1 0  
  9.     2 "f"  
  10. locals (1) for 0x80048eb0:  
  11.     0 u 2 5  
  12. upvalues​​ (1) for 0x80048eb0:  
  13.     0 _ENV 1 0  
  14.   
  15. function <test.lua:2,8> (7 instructions at 0x80049140)  
  16. 0 params, 2 slots, 2 upvalues​​, 1 local, 2 constants, 0 functions  
  17.     1 [3] LOADNIL 0 0  
  18.     2 [4] LOADK 1 -1 ; 1  
  19.     3 [4] SETUPVAL 1 0 ; u  
  20.     4 [5] GETUPVAL 0 0 ; u  
  21.     5 [6] SETTABUP 1 -2 -1 ; _ENV "g" 1  
  22.     6 [7] GETTABUP 0 1 -2 ; _ENV "g"  
  23.     7 [8] RETURN 0 1  
  24. constants (2) for 0x80049140:  
  25.     1 1  
  26.     2 "g"  
  27. locals (1) for 0x80049140:  
  28.     0 l 2 8  
  29. upvalues​​ (2) for 0x80049140:  
  30.     0 u 1 0  
  31.     1 _ENV 0 0    

上面的代碼片段生成一個主函數和一個內嵌函數。根據前面說到的變量規則,在內嵌函數中,l是local變量,u是upvalue,g由於既不是local變量,也不是upvalue,當作全局變量處理。我們先來看內嵌函數,生成的指令從17行開始。
第17行的LOADNIL前面已經講過,為local變量賦值。下面的LOADK和SETUPVAL組合,完成了u = 1。因為1是一個常量,存在於常量表中,而lua沒有常量與upvalue的直接操作指令,所以需要先把常量1裝在到臨時寄存器1種,然後將寄存器1的值賦給upvalue 0,也就是u。第20行的GETUPVAL將upvalue u賦給local變量l。第21行開始的SETTABUP和GETTABUP就是前面提到的對全局變量的處理了。g=1被轉化為_ENV.g=1。_ENV是系統預先設置在主函數中的upvalue,所以對於全局變量g的訪問被轉化成對upvalue[_ENV][g]的訪問。SETTABUP將upvalue 1(_ENV代表的upvalue)作為一個table,將常量表2(常量"g")作為key的值設置為常量表1(常量1);GETTABUP則是將upvalue 1作為table,將常量表2為key的值賦給寄存器0(local l)。

No comments: