# 探索Lua5.2內部實現:虛擬機指令(1) 概述
Lua一直把虛擬機執行代碼的效率作為一個非常重要的設計目標。而采用什么樣的指令系統的對于虛擬機的執行效率來說至關重要。
## Stack based vs Register based VM
根據指令獲取操作數方式的不同,我們可以把虛擬機的實現分為stack based和register based。
### Stack based vm
對于大多數的虛擬機,比如JVM,Python,都采用傳統的stack based vm。
Stack based vm的指令一般都是在當前stack中獲取和保存操作數的。比如一個簡單的加法賦值運算:a=b+c,對于stack based vm,一般會被轉化成如下的指令:
~~~
push?b;?//?將變量b的值壓入stack??
push?c;?//?將變量c的值壓入stack??
add;????//?將stack頂部的兩個值彈出后相加,將結果壓入stack??
mov?a;??//?將stack頂部結果放到a中??
~~~
由于Stack based vm的指令都是基于當前stack來查找操作數的,這就相當于所有操作數的存儲位置都是運行期決定的,在編譯器的代碼生成階段不需要額外為在哪里存儲操作數費心,所以stack based的編譯器實現起來相對比較簡單直接。也正因為這個原因,每條指令占用的存儲空間也比較小。
但是,對于一個簡單的運算,stack based vm會使用過多的指令組合來完成,這樣就增加了整體指令集合的長度。vm會使用同樣多的迭代次數來執行這些指令,這對于效率來說會有很大的影響。并且,由于操作數都要放到stack上面,使得移動這些操作數的內存復制大大增加,這也會影響到效率。
### Register based vm
Lua 采用的是register based vm。
Register based vm的指令都是在已經分配好的寄存器中存取操作數。對于上面的運算,register based vm一般會使用如下的指令:
` add?a?b?c;?//?將b與c對應的寄存器的值相加,將結果保存在a對應的寄存器中??`
Register based vm的指令可以直接對應標準的3地址指令,用一條指令完成了上面多條指令的計算工作,并且有效地減少了內存復制操作。這樣的指令系統對于效率有很大的幫助。
不過,在編譯器設計上,就要在代碼生成階段對寄存器進行分配,增加了實現的復雜度。并且每條指令所占用的存儲空間也相應的增加了。
## Lua虛擬機指令簡介
Lua的指令使用一個32bit的unsigned integer表示。所有指令的定義都在lopcodes.h文件中,使用一個enum OpCode代表指令類型。在lua5.2中,總共有40種指令(id從0到39)。根據指令參數的不同,可以將所有指令分為4類:

除了sBx之外,所有的指令參數都是unsigned integer類型。sBx可以表示負數,但表示方法比較特殊。sBx的18bit可表示的最大整數為262143,這個數的一半131071用來表示0,所以-1可以表示為-1+131071,也就是131070,而+1可以表示為+1+131071,也就是131072。
ABC一般用來存放指令操作數據的地址,而地址可以分成3種:
* 寄存器id
* 常量表id
* upvalue id
Lua使用當前函數的stack作為寄存器使用,寄存器id從0開始。當前函數的stack與寄存器數組是相同的概念。stack(n)其實就是register(n)。
每一個函數prototype都有一個屬于本函數的常量表,用于存放編譯過程中函數所用到的常量。常量表可以存放nil,boolean,number和string類型的數據,id從1開始。
每一個函數prototype中都有一個upvalue描述表,用于存放在編譯過程中確定的本函數所使用的upvalue的描述。在運行期,通過OP_CLOSURE指令創建一個closure時,會根據prototype中的描述,為這個closure初始化upvalue表。upvalue本身不需要使用名稱,而是通過id進行訪問。
A被大多數指令用來指定計算結果的目標寄存器地址。很多指令使用B或C同時存放寄存器地址和常量地址,并通過最左面的一個bit來區分。在指令生成階段,如果B或C需要引用的常量地址超出了表示范圍,則首先會生成指令將常量裝載到臨時寄存器,然后再將B或C改為使用該寄存器地址。
在lopcodes.h中,對于每個指令,在源碼注釋中都有簡單的操作描述。本文接下來將針對每一個指令做更詳細的描述,并給出關于這個指令的示例代碼。示例代碼可以幫助我們構建出一個指令使用的具體上下文,有助于進一步理解指令的作用。對指令上下文的理解還可以作為進一步研究lua的編譯和代碼生成系統的基礎。
在分析過程中,我們使用luac來顯示示例代碼所生成的指令。luac的具體使用方式為:
` luac?-l?-l?test.lua??`
- 前言
- 探索Lua5.2內部實現:TString
- 探索Lua5.2內部實現:虛擬機指令(1) 概述
- 探索Lua5.2內部實現:虛擬機指令(2) MOVE & LOAD
- 探索Lua5.2內部實現:虛擬機指令(3) Upvalues & Globals
- 探索Lua5.2內部實現:虛擬機指令(4) Table
- 探索Lua5.2內部實現:虛擬機指令(7) 關系和邏輯指令
- 探索Lua5.2內部實現:虛擬機指令(8) LOOP
- 探索Lua5.2內部實現:編譯系統(1) 概述
- 探索Lua5.2內部實現:編譯系統(2) 跳轉的處理
- 探索Lua5.2內部實現:編譯系統(3) 表達式
- 探索Lua5.2內部實現:編譯系統(4) 表達式分類
- 探索Lua5.2內部實現:Garbage Collection(1) 原理
- 探索Lua5.2內部實現:Garbage Collection(2)