Lua 虛擬機指令

Lua 虛擬機指令

Lua運行代碼時,首先把代碼編譯成虛擬機的指令("opcode"),而後執行它們。 Lua編譯器爲每一個函數建立一個原型(prototype),這個原型包含函數執行的一組指令和函數所用到的數據表1c++

從Lua5.0開始,Lua使用基於寄存器的虛擬機(虛擬機主要分爲基於寄存器的和基於棧的)。 爲了分配寄存器使用時的activation record,這個虛擬機也使用到了棧。 當Lua進入函數時,它從棧中預分配了足夠容納全部函數寄存器的activation record。 全部的局部變量在寄存器中分配。所以提升了訪問局部變量的效率。數組

基於寄存器的指令避免了「push」和「pop」操做,而這正式基於棧的虛擬機須要的。 這些操做在Lua中十分昂貴,由於它們涉及了對值的拷貝。 因此寄存器結構可以避免昂貴的值拷本,以及減小每一個函數指令個數。bash

但基於寄存器的虛擬機仍有兩個問題:指令的長度和譯碼的代價。 一條基於寄存器的指令須要指明它的操做對象,因此它比通常基於棧的指令要長(如Lua的指令爲4個字節,而基於棧的指令只需1~2個字節)。 另外一方面,基於寄存器虛擬機通常都比基於棧的虛擬機產生更少的操做,所以整體長度不會很長。ide

大多數基於棧的指令都有隱式操做對象。 而基於寄存器的指令是從指令中取出操做對象,這增長了解釋器的開銷。 針對這個開銷,如下有幾點分析。 第一,基於棧的虛擬機仍須要尋找隱藏的操做對象。 第二,有時基於寄存器的操做對象有更低的運算代價,如邏輯操做。 而基於棧的虛擬機一個指令有時須要屢次操做。函數

Lua虛擬機有35條指令,大部分指令與語言結構直接交互,如算術、table建立和索引、函數和方法的調用、寫入和讀取變量的值。 固然還有一組常規的跳轉指令來實現過程控制。 下段代碼是Lua中指令名稱的定義。 測試

---------------------------------------------------------------------------
// R(x) - register
// Kst(x) - constant 常量 (in constant table)
// RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x)

typedef enum {
/*----------------------------------------------------------------------
name		args	description
------------------------------------------------------------------------*/
OP_MOVE,/*	A B	R(A) := R(B)					*/
OP_LOADK,/*	A Bx	R(A) := Kst(Bx)					*/
OP_LOADBOOL,/*	A B C	R(A) := (Bool)B; if (C) pc++			*/
OP_LOADNIL,/*	A B	R(A) := ... := R(B) := nil			*/
OP_GETUPVAL,/*	A B	R(A) := UpValue[B]				*/

OP_GETGLOBAL,/*	A Bx	R(A) := Gbl[Kst(Bx)]				*/
OP_GETTABLE,/*	A B C	R(A) := R(B)[RK(C)]				*/

OP_SETGLOBAL,/*	A Bx	Gbl[Kst(Bx)] := R(A)				*/
OP_SETUPVAL,/*	A B	UpValue[B] := R(A)				*/
OP_SETTABLE,/*	A B C	R(A)[RK(B)] := RK(C)				*/

OP_NEWTABLE,/*	A B C	R(A) := {} (size = B,C)				*/

OP_SELF,/*	A B C	R(A+1) := R(B); R(A) := R(B)[RK(C)]		*/

OP_ADD,/*	A B C	R(A) := RK(B) + RK(C)				*/
OP_SUB,/*	A B C	R(A) := RK(B) - RK(C)				*/
OP_MUL,/*	A B C	R(A) := RK(B) * RK(C)				*/
OP_DIV,/*	A B C	R(A) := RK(B) / RK(C)				*/
OP_MOD,/*	A B C	R(A) := RK(B) % RK(C)				*/
OP_POW,/*	A B C	R(A) := RK(B) ^ RK(C)				*/
OP_UNM,/*	A B	R(A) := -R(B)					*/
OP_NOT,/*	A B	R(A) := not R(B)				*/
OP_LEN,/*	A B	R(A) := length of R(B)				*/

OP_CONCAT,/*	A B C	R(A) := R(B).. ... ..R(C)			*/

OP_JMP,/*	sBx	pc+=sBx					*/

OP_EQ,/*	A B C	if ((RK(B) == RK(C)) ~= A) then pc++		*/
OP_LT,/*	A B C	if ((RK(B) <  RK(C)) ~= A) then pc++  		*/
OP_LE,/*	A B C	if ((RK(B) <= RK(C)) ~= A) then pc++  		*/

OP_TEST,/*	A C	if not (R(A) <=> C) then pc++			*/
OP_TESTSET,/*	A B C	if (R(B) <=> C) then R(A) := R(B) else pc++	*/

OP_CALL,/*	A B C	R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
OP_TAILCALL,/*	A B C	return R(A)(R(A+1), ... ,R(A+B-1))		*/
OP_RETURN,/*	A B	return R(A), ... ,R(A+B-2)	(see note)	*/

OP_FORLOOP,/*	A sBx	R(A)+=R(A+2);
			if R(A) =) R(A)*/
OP_CLOSURE,/*	A Bx	R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))	*/

OP_VARARG/*	A B	R(A), R(A+1), ..., R(A+B-1) = vararg		*/
} OpCode;

---------------------------------------------------------------------------

寄存器保存在運行時棧中,且這個棧也是一個能隨機訪問的數組,這樣能快速訪問寄存器。 常量和上值(upvalue)2也保存在數組中,因此訪問它們也很快。 全局表是一個傳統的Lua table,它經過strings來快速訪問,而且strings已經預計算出了它們的hash值。spa

Lua虛擬機中指令的長度爲32位,劃分爲3~4個域。其中prototype

    1. 指令的OP域佔6位,即最多有64條指令。
    2. A域佔8位。
    3. B和C域能夠分別佔9位,或組合成18位的Bx域(unsigned)或sBx域(signed)。

大部分指令使用三地址格式,A指向保存結果的寄存器,B和C指向操做操做目標,即寄存器或常量。 使用這種形式,一些Lua中典型的操做可以譯成一條指令。 如a = a + 1能夠被譯成ADD x x y,其中x表明寄存器保存的變量,y表明常量1。 a = b.f能夠被譯成GETTABLE x y z,其中x表明a的寄存器,y表明b的寄存器,z表明常量f的索引。code

轉移指令使用4地址格式時會有一些困難,由於這將限制偏移的長度在256內(9-bit field)。 Lua的解決方法是,當測試指令(test instruction)判斷爲否時,跳過下一條轉移指令;不然下一條指令是一個正常跳轉指令,使用18位的偏移。 這也就是說測試指令後總跟着一條轉移指令,那麼解釋器能夠把這兩條指令一塊兒執行。對象

對於函數調用,Lua使用一種寄存器窗口(register window)的方式。它使用第一個沒有使用的寄存器來連續存儲參數。 當執行函數調用時,這些寄存器稱爲調用的函數的activation recode的一部分,使得函數像訪問局部變量同樣訪問這些參數。 當函數返回時,這些寄存器加入到調用函數的上下文的activation recode裏。

Lua爲函數調用使用兩個平行的棧。 一個棧保存着每一個激活函數的信息項。這條信息保存着調用函數、返回地址和指向函數activation record的索引。 另外一條棧是一條保存了activation records的數組。每一條activation record保存了函數全部的臨時變量。 實際上,咱們能夠把第二條棧中的每一個項當作第一個棧中的交互項的變量大小部分。


1. R. Ierusalimschy, L. H. de Figueiredo, W. Celes, The implementation of Lua 5.0, Journal of Universal Computer Science 11 #7 (2005) 1159–1176. [jucs · slides]

2. 內部嵌套函數使用的外部函數的局部變量在函數內部稱之爲上值(upvalue),或者外局部變量(external local variable)。

相關文章
相關標籤/搜索