日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

本文從一個簡單示例入手,詳細講解 Lua 字節碼文件的存儲結構及各字段含義,進而引出 Lua 虛擬機指令集和運行時的核心數據結構 Lua State,最后解釋 Lua 虛擬機的 47 條指令如何在 Lua State 上運作的。

為了達到較高的執行效率,lua 代碼并不是直接被 Lua 解釋器解釋執行,而是會先編譯為字節碼,然后再交給 lua 虛擬機去執行。lua 代碼稱為 chunk,編譯成的字節碼則稱為二進制 chunk(Binary chunk)。lua.exe、wlua.exe 解釋器可直接執行 lua 代碼(解釋器內部會先將其編譯成字節碼),也可執行使用 luac.exe 將 lua 代碼預編譯(Precompiled)為字節碼。使用預編譯的字節碼并不會加快腳本執行的速度,但可以加快腳本加載的速度,并在一定程度上保護源代碼。luac.exe 可作為編譯器,把 lua 代碼編譯成字節碼,同時可作為反編譯器,分析字節碼的內容。

luac.exe -v  // 顯示luac的版本號
luac.exe Hello.lua  //
在當前目錄下,編譯得到Hello.lua的二進制chunk文件luac.out(默認含調試符號)

luac.exe -o Hello.out Hello1.lua Hello2.lua //
在當前目錄下,編譯得到Hello1.lua和Hello2.lua的二進制chunk文件Hello.out(默認含調試符號)

luac.exe -s -o d:\Hello.out Hello.lua  //
編譯得到Hello.lua的二進制chunk文件d:\Hello.out(去掉調試符號)

luac.exe -p Hello1.lua Hello2.lua  //
對Hello1.lua和Hello2.lua只進行語法檢測(注:只會檢查語法規則,不會檢查變量、函數等是否定義和實現,函數參數返回值是否合法)

lua 編譯器以函數為單位對源代碼進行編譯,每個函數會被編譯成一個稱之為原型(Prototype)的結構,原型主要包含 6 部分內容:函數基本信息(basic info,含參數數量、局部變量數量等信息)、字節碼(bytecodes)、常量(constants)表、upvalue(閉包捕獲的非局部變量)表、調試信息(debug info)、子函數原型列表(sub functions)。

原型結構使用這種嵌套遞歸結構,來描述函數中定義的子函數:

萬字詳文:深入理解 Lua 虛擬機

 

注:lua 允許開發者可將語句寫到文件的全局范圍中,這是因為 lua 在編譯時會將整個文件放到一個稱之為 main 函數中,并以它為起點進行編譯。

Hello.lua 源代碼如下:

print ("hello")
function add(a, b)
    return a+b
end

編譯得到的 Hello.out 的二進制為:

萬字詳文:深入理解 Lua 虛擬機

 

二進制 chunk(Binary chunk)的格式并沒有標準化,也沒有任何官方文檔對其進行說明,一切以 lua 官方實現的源代碼為準。其設計并沒有考慮跨平臺,對于需要超過一個字節表示的數據,必須要考慮大小端(Endianness)問題。

lua 官方實現的做法比較簡單:編譯 lua 腳本時,直接按照本機的大小端方式生成二進制 chunk 文件,當加載二進制 chunk 文件時,會探測被加載文件的大小端方式,如果和本機不匹配,就拒絕加載。二進制 chunk 格式設計也沒有考慮不同 lua 版本之間的兼容問題,當加載二進制 chunk 文件時,會檢測其版本號,如果和當前 lua 版本不匹配,就拒絕加載。另外,二進制 chunk 格式設計也沒有被刻意設計得很緊湊。在某些情況下,一段 lua 代碼編譯成二進制 chunk 后,甚至會被文本形式的源代碼還要大。預編譯成二進制 chunk 主要是為了提升加載速度,因此這也不是很大的問題。

頭部字段

萬字詳文:深入理解 Lua 虛擬機

 

嵌套的函數原型

萬字詳文:深入理解 Lua 虛擬機

 

注 1:二進制 chunk 中的字符串分為三種情況:

①NULL 字符串用 0x00 表示;

② 長度小于等于 253(0xFD)的字符串,先用 1 個 byte 存儲字符串長度+1 的數值,然后是字節數組;

③ 長度大于等于 254(0xFE)的字符串,第一個字節是 0xFF,后面跟一個 8 字節 size_t 類型存儲字符串長度+1 的數值,然后是字節數組。

注 2:常量 tag 對應表

萬字詳文:深入理解 Lua 虛擬機

 

查看二進制 chunk 中的所有函數(精簡模式):

luac.exe -l Hello.lua

luac.exe -l Hello.out

萬字詳文:深入理解 Lua 虛擬機

 

注 1:每個函數信息包括兩個部分:前面兩行是函數的基本信息,后面是函數的指令列表。

注 2:函數的基本信息包括:函數名稱、函數的起始行列號、函數包含的指令數量、函數地址。函數的參數 params 個數(0+表示函數為不固定參數)、寄存器 slots 數量、upvalue 數量、局部變量 locals 數量、常量 constants 數量、子函數 functions 數量。

注 3:指令列表里的每一條指令包含指令序號、對應代碼行號、操作碼和操作數。分號后為 luac 生成的注釋,以便于我們理解指令。

注 4:整個文件內容被放置到了 main 函數中,并以它作為嵌套起點。

查看二進制 chunk 中的所有函數(詳細模式):

luac.exe -l -l Hello.lua 注:參數為 2 個-l

luac.exe -l -l Hello.out 注:詳細模式下,luac 會把常量表、局部變量表和 upvalue 表的信息也打印出來

main <Test2.lua:0,0> (6 instructions at 0046e528)
0+ params, 2 slots, 1 upvalue, 0 locals, 3 constants, 1 function
        序號    代碼行    指令
        1       [1]     GETTABUP        0 0 -1  ; _ENV "print"   //GETTABUP A B C  //將upvalues表索引為B:0的upvalue(即:_ENV)中key為常量表索引為C:-1的(即print),放到寄存器索引為A:0的地方
        2       [1]     LOADK           1 -2    ; "hello"  //LOADK A Bx  //將常量表索引為Bx:-2的hello加載到寄存器索引為A:1的地方
        3       [1]     CALL            0 2 1    ; //CALL A B C  //調用寄存器索引為A:0的函數,參數個數為B:2減1(即1個),C:1表示無返回值
        4       [5]     CLOSURE         0 0     ; 0046e728      //CLOSURE A Bx  //將子函數原型列表索引為Bx:0的函數地址,放到寄存器索引為A:0的地方
        5       [3]     SETTABUP        0 -3 0  ; _ENV "add"   //SETTABUP A B C  //將upvalues表索引為A:0的upvalue(即:_ENV)中key為常量表索引為B:-3(即add),設置為寄存器索引為C:0指向的值
        6       [5]     RETURN          0 1        ; //RETURN A B   //B:1表示無返回值
constants (3) for 0046e528:
        序號    常量名
        1       "print"
        2       "hello"
        3       "add"
locals (0) for 0046e528:
upvalues (1) for 0046e528:
        序號    upvalue名    是否為直接外圍函數的局部變量    在外圍函數調用幀的索引
        0       _ENV        1                               0

function <Test2.lua:3,5> (3 instructions at 0046e728)
2 params, 3 slots, 0 upvalues, 2 locals, 0 constants, 0 functions
        序號    代碼行    指令
        1       [4]     ADD             2 0 1    ; //ADD A B C  //將寄存器索引為0、1的兩個數相加得到的結果放到寄存器索引為2的地方
        2       [4]     RETURN          2 2        ; //RETURN A B //B:2表示有一個返回值  A:2表示返回值在寄存器索引為2的地方
        3       [5]     RETURN          0 1        ; //RETURN A B //B:1表示無返回值
constants (0) for 0046e728:
locals (2) for 0046e728:
    寄存器索引    起始指令序號  終止指令序號  -1得到實際指令序號
        0       a       1       4        ; a變量的指令范圍為[0, 3],起始為0表示為傳入的參數變量
        1       b       1       4        ; b變量的指令范圍為[0, 3]
upvalues (0) for 0046e728:

luac.exe -l - // 從標準設備讀入腳本,輸完后按回車,然后按 Ctrl+Z 并回車,會打印出輸入內容對應的二進制 chunk 內容 注:進入輸入模式后可按 Ctrl+C 強制退出

luac.exe -l -- // 使用上次輸入,打印出二進制 chunk 內容

luac.exe -l -l -- // 使用上次輸入,詳細模式下打印出二進制 chunk 內容(參數為 2 個-l)

Stack Based VM vs Rigister Based VM

高級編程語言的虛擬機是利用軟件技術對硬件進行的模擬和抽象。按照實現方式,可分為兩類:基于棧(Stack Based)和基于寄存器(Rigister Based)。JAVA、.NET CLR、Python、Ruby、Lua5.0 之前的版本的虛擬機都是基于棧的虛擬機;從 5.0 版本開始,Lua 的虛擬機改成了基于寄存器的虛擬機。

一個簡單的加法賦值運算:a=b+c

基于棧的虛擬機,會轉化成如下指令:

push b; // 將變量b的值壓入stack

push c; // 將變量c的值壓入stack

add; // 將stack頂部的兩個值彈出后相加,然后將結果壓入stack頂

mov a; // 將stack頂部結果放到a中

所有的指令執行,都是基于一個操作數棧的。你想要執行任何指令時,對不起,得先入棧,然后算完了再給我出棧??偟膩碚f,就是抽象出了一個高度可移植的操作數棧,所有代碼都會被編譯成字節碼,然后字節碼就是在玩這個棧。好處是實現簡單,移植性強。壞處是指令條數比較多,數據轉移次數比較多,因為每一次入棧出棧都牽涉數據的轉移。

基于寄存器的虛擬機,會轉化成如下指令:

add a b c; // 將b與c對應的寄存器的值相加,將結果保存在a對應的寄存器中

沒有操作數棧這一概念,但是會有許多的虛擬寄存器。這類虛擬寄存器有別于 CPU 的寄存器,因為 CPU 寄存器往往是定址的(比如 DX 本身就是能存東西),而寄存器式的虛擬機中的寄存器通常有兩層含義:

(1)寄存器別名(比如 lua 里的 RA、RB、RC、RBx 等),它們往往只是起到一個地址映射的功能,它會根據指令中跟操作數相關的字段計算出操作數實際的內存地址,從而取出操作數進行計算;

(2)實際寄存器,有點類似操作數棧,也是一個全局的運行時棧,只不過這個棧是跟函數走的,一個函數對應一個棧幀,棧幀里每個 slot 就是一個寄存器,第 1 步中通過別名映射后的地址就是每個 slot 的地址。

好處是指令條數少,數據轉移次數少。壞處是單挑指令長度較長。具體來看,lua 里的實際寄存器數組是用 TValue 結構的棧來模擬的,這個棧也是 lua 和 C 進行交互的虛擬棧。

lua 指令集

Lua 虛擬機的指令集為定長(Fixed-width)指令集,每條指令占 4 個字節(32bits),其中操作碼(OpCode)占 6bits,操作數(Operand)使用剩余的 26bits。Lua5.3 版本共有 47 條指令,按功能可分為 6 大類:常量加載指令、運算符相關指令、循環和跳轉指令、函數調用相關指令、表操作指令和 Upvalue 操作指令。

按編碼模式分為 4 類:iABC(39)、iABx(3)、iAsBx(4)、iAx(1)

萬字詳文:深入理解 Lua 虛擬機

 

4 種模式中,只有 iAsBx 下的 sBx 操作數會被解釋成有符號整數,其他情況下操作數均被解釋為無符號整數。操作數 A 主要用來表示目標寄存器索引,其他操作數按表示信息可分為 4 種類型:OpArgN、OpArgU、OpArgR、OpArgK:

萬字詳文:深入理解 Lua 虛擬機

 

Lua 棧索引

萬字詳文:深入理解 Lua 虛擬機

 

注 1:絕對索引是從 1 開始由棧底到棧頂依次增長的;

注 2:相對索引是從-1 開始由棧頂到棧底依次遞減的(在 lua API 函數內部會將相對索引轉換為絕對索引);

注 3:上圖棧的容量為 7,棧頂絕對索引為 5,有效索引范圍為:[1,5],可接受索引范圍為:[1, 7];

注 4:Lua 虛擬機指令里寄存器索引是從 0 開始的,而 Lua API 里的棧索引是從 1 開始的,因此當需要把寄存器索引當成棧索引使用時,要進行+1。

Lua State

萬字詳文:深入理解 Lua 虛擬機

 

指令表

下面是 Lua 的 47 條指令詳細說明:

萬字詳文:深入理解 Lua 虛擬機

 

B:1 C A:3 MOVE

把源寄存器(索引由 B 指定)里的值移動到目標寄存器(索引有 A 指定),常用于局部變量賦值和參數傳遞。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := R(B)

萬字詳文:深入理解 Lua 虛擬機

 

Bx:2 A:4 LOADK

給單個寄存器(索引由 A 指定)設置成常量(其在常量表的索引由 Bx 指定),將常量表里的某個常量加載到指定寄存器。

在 lua 中,數值型、字符串型等局部變量賦初始值 (數字和字符串會放到常量表中):

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := Kst(Bx)

萬字詳文:深入理解 Lua 虛擬機

 

Bx A:4 LOADKX

Ax:585028 EXTRAARG

LOADK 使用 Bx(18bits,最大無符號整數為 262143)表示常量表索引。當將 lua 作數據描述語言使用時,常量表可能會超過這個限制,為了應對這種情況,lua 提供了 LOADKX 指令。LOADKX 指令需要和 EXTRAAG 指令搭配使用,用后者的 Ax(26bits)操作數來指定常量索引。

公式:R(A) := Kst(Ax)

指令名稱類型操作碼BCA
LOADBOOLiABC0x03OpArgUOpArgU目標寄存器 idx

B:0 C:1 A:2 LOADBOOL

給單個寄存器(索引由 A 指定)設置布爾值(布爾值由 B 指定),如果寄存器 C 為非 0 則跳過下一條指令。

萬字詳文:深入理解 Lua 虛擬機

 

公式:

R(A) := (bool)B

if(C) pc++

指令名稱類型操作碼BCA
LOADNILiABC0x04OpArgUOpArgN目標寄存器 idx

B:4 C A:0 LOADNIL

將序號[A,A+B]連續 B+1 個寄存器設置成 nil 值,用于給連續 n 個寄存器放置 nil 值。在 lua 中,局部變量的默認初始值為 nil,LOADNIL 指令常用于給連續 n 個局部變量設置初始值。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A), R(A+1), ... ,R(A+B) := nil

指令名稱類型操作碼BCA
GETUPVALiABC0x05OpArgUOpArgN目標寄存器 idx

B:1 C A:3 GETUPVAL

把當前閉包的某個 Upvalue 值(索引由 B 指定)拷貝到目標寄存器(索引由 A 指定)中 。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := Upvalue[B]

指令名稱類型操作碼BCA
GETTABUPiABC0x06OpArgUOpArgK目標寄存器 idx

B:0 C:0x002 A:3 GETTABUP

把當前閉包的某個 Upvalue 值(索引由 B 指定)拷貝到目標寄存器(索引由 A 指定)中,與 GETUPVAL 不同的是,Upvalue 從表里取值(鍵由 C 指定,為寄存器或常量表索引)。

萬字詳文:深入理解 Lua 虛擬機

 

R(A) := Upvalue[B][rk(c)]

指令名稱類型操作碼BCA
GETTABLEiABC0x07OpArgROpArgK目標寄存器 idx

B:0 C:0x002 A:3 GETTABLE

把表中某個值拷貝到目標寄存器(索引由 A 指定)中,表所在寄存器索引由 B 指定,鍵由 C(為寄存器或常量表索引)指定。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := R[B][rk(c)]

指令名稱類型操作碼BCA
SETTABUPiABC0x08OpArgKOpArgK目標寄存器 idx

B:0x002 C:0x003 A:0 SETTABUP

設置當前閉包的某個 Upvalue 值(索引由 A 指定)為寄存器或常量表的某個值(索引由 C 指定),與 SETUPVAL 不同的是,Upvalue 從表里取值(鍵由 B 指定,為寄存器或常量表索引)。

萬字詳文:深入理解 Lua 虛擬機

 

Upvalue[A][rk(b)] := RK(C)

指令名稱類型操作碼BCA
SETUPVALiABC0x09OpArgUOpArgN目標寄存器 idx

B:0 C A:3 SETUPVAL

設置當前閉包的某個 Upvalue 值(索引由 B 指定)為寄存器的某個值(索引由 A 指定)。

萬字詳文:深入理解 Lua 虛擬機

 

公式:Upvalue[B] := R(A)

指令名稱類型操作碼BCA
SETTABLEiABC0x0AOpArgKOpArgK目標寄存器 idx

B:0x002 C:0x003 A:1 SETTABLE

給寄存器中的表(索引由 A 指定)的某個鍵進行賦值,鍵和值分別由 B 和 C 指定(為寄存器或常量表索引)。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A)[RK(B)] := RK(C)

指令名稱類型操作碼BCA
NEWTABLEiABC0x0BOpArgUOpArgU目標寄存器 idx

B:0 C:2 A:4 NEWTABLE

創建空表,并將其放入指定寄存器(索引有 A 指定),表的初始數組容量和哈希表容量分別有 B 和 C 指定。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := {} (size = B, C)

指令名稱類型操作碼BCASELFiABC0x0COpArgROpArgK目標寄存器 idx

B:1 C:0x100 A:2 SELF

把寄存器中對象(索引由 B 指定)和常量表中方法(索引由 C 指定)拷貝到相鄰的兩個目標寄存器中,起始目標寄存器的索引由 A 指定。

萬字詳文:深入理解 Lua 虛擬機

 

公式:

R(A+1) := R(B)

R(A) := R(B)[RK(C)]

指令名稱類型操作碼BCAADDiABC0x0DOpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 ADD

對兩個寄存器或常量值(索引由 B 和 C 指定)進行相加,并將結果放入另一個寄存器中(索引由 A 指定)。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := RK(B) + RK(C)

指令名稱類型操作碼BCASUBiABC0x0EOpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 SUB

對兩個寄存器或常量值(索引由 B 和 C 指定)進行相減,并將結果放入另一個寄存器中(索引由 A 指定)

公式:

R(A) := RK(B) - RK(C)

指令名稱類型操作碼BCAMULiABC0x0FOpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 MUL

對兩個寄存器或常量值(索引由 B 和 C 指定)進行相乘,并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) * RK(C)

指令名稱類型操作碼BCAMODiABC0x10OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 MOD

對兩個寄存器或常量值(索引由 B 和 C 指定)進行求摸運算,并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) % RK(C)

指令名稱類型操作碼BCAPOWiABC0x11OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 POW

對兩個寄存器或常量值(索引由 B 和 C 指定)進行求冪運算,并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) ^ RK(C)

指令名稱類型操作碼BCADIViABC0x12OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 DIV

對兩個寄存器或常量值(索引由 B 和 C 指定)進行相除,并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) / RK(C)

指令名稱類型操作碼BCAIDIViABC0x13OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 IDIV

對兩個寄存器或常量值(索引由 B 和 C 指定)進行相整除,并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) // RK(C)

指令名稱類型操作碼BCABANDiABC0x14OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 BAND

對兩個寄存器或常量值(索引由 B 和 C 指定)進行求與操作,并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) & RK(C)

指令名稱類型操作碼BCABORiABC0x15OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 BOR

對兩個寄存器或常量值(索引由 B 和 C 指定)進行求或操作,并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) | RK(C)

指令名稱類型操作碼BCABXORiABC0x16OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 BXOR

對兩個寄存器或常量值(索引由 B 和 C 指定)進行求異或操作,并將結果放入另一個寄存器中(索引由 A 指定)

公式:R(A) := RK(B) ~ RK(C)

指令名稱類型操作碼BCASHLiABC0x17OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 SHL

索引由 B 指定的寄存器或常量值進行左移位操作(移動位數的索引由 C 指定的寄存器或常量值),并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) << RK(C)

指令名稱類型操作碼BCASHRiABC0x18OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:4 SHR

索引由 B 指定的寄存器或常量值進行右移位操作(移動位數的索引由 C 指定的寄存器或常量值),并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := RK(B) >> RK(C)

指令名稱類型操作碼BCAUNMiABC0x19OpArgROpArgN目標寄存器 idx

B:1 C A:3 UNM

對寄存器(索引由 B 指定)進行取負數操作,并將結果放入另一個寄存器中(索引由 A 指定)。

公式:R(A) := - R(B)

指令名稱類型操作碼BCABNOTiABC0x1AOpArgROpArgN目標寄存器 idx

B:1 C A:3 BNOT

對寄存器(索引由 B 指定)進行取反操作,并將結果放入另一個寄存器中(索引由 A 指定)。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := ~ R(B)

指令名稱類型操作碼BCANOTiABC0x1BOpArgROpArgN目標寄存器 idx

B:1 C A:3 NOT

對寄存器(索引由 B 指定)進行求非操作,并將結果放入另一個寄存器中(索引由 A 指定)。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := not R(B)

指令名稱類型操作碼BCALENiABC0x1COpArgROpArgN目標寄存器 idx

B:1 C A:3 LEN

對寄存器(索引由 B 指定)進行求長度操作,并將結果放入另一個寄存器中(索引由 A 指定)。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := length of R(B)

指令名稱類型操作碼BCA
CONCATiABC0x1DOpArgROpArgR目標寄存器 idx

B:2 C:4 A:1 CONCAT

將連續 n 個寄存器(起始索引和終止索引由 B 和 C 指定)里的值進行拼接,并將結果放入另一個寄存器中(索引由 A 指定)。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := R(B) .. ... .. R(C)

指令名稱類型操作碼sBxAJMPiAsBx0x1EOpArgR目標寄存器 idx

sBx:-1 A JMP

當 sBx 不為 0 時,進行無條件跳轉,執行 pc = pc + sBx(sBx 為-1,表示將當前指令再執行一次 注:這將是一個死循環)

sBx:0 A:0x001 JMP;

當 sBx 為 0 時(繼續執行后面指令,不跳轉),用于閉合處于開啟狀態的 Upvalue(即:把即將銷毀的局部變量的值復制出來,并更新到某個 Upvalue 中)。

當前閉包的某個 Upvalue 值的索引由 A 指定:

指令名稱類型操作碼BCAEQiABC0x1FOpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:1 EQ

寄存器或常量表(索引由 B 指定)是否等于寄存器或常量表(索引由 C 指定),若結果等于操作數 A,則跳過下一條指令。

萬字詳文:深入理解 Lua 虛擬機

 

公式:if ((RK(B) == RK(C)) pc++

指令名稱類型操作碼BCALTiABC0x20OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:1 LT

寄存器或常量表(索引由 B 指定)是否小于寄存器或常量表(索引由 C 指定),若結果等于操作數 A,則跳過下一條指令。

公式:if ((RK(B) < RK(C)) pc++

指令名稱類型操作碼BCALEiABC0x21OpArgKOpArgK目標寄存器 idx

B:0x001 C:0x100 A:1 LE

寄存器或常量表(索引由 B 指定)是否小于等于寄存器或常量表(索引由 C 指定),若結果等于操作數 A,則跳過下一條指令。

公式:if ((RK(B) <= RK(C)) pc++

指令名稱類型操作碼BCATESTiABC0x22OpArgNOpArgU目標寄存器 idx

B C:0 A:1 TEST

判斷寄存器(索引由 A 指定)中的值轉換為 bool 值后,是否和操作數 C 表示的 bool 值一致,若結果不一致,則跳過下一條指令。

萬字詳文:深入理解 Lua 虛擬機

 

公式:

if not (R(A) <=> C) pc++

注:<=>表示按 bool 值比較

指令名稱類型操作碼BCA
TESTSETiABC0x23OpArgROpArgU目標寄存器 idx

B:3 C:0 A:1 TESTSET

判斷寄存器(索引由 B 指定)中的值轉換為 bool 值后,是否和操作數 C 表示的 bool 值一致,若結果一致,將寄存器(索引由 B 指定)中的值復制到寄存器中(索引由 A 指定),否則跳過下一條指令。

萬字詳文:深入理解 Lua 虛擬機

 

公式:

if (R(B) <=> C)
   R(A) := R(B)
else
   pc++ 

注:<=>表示按 bool 值比較

指令名稱類型操作碼BCACALLiABC0x24OpArgUOpArgU目標寄存器 idx

B:5 C:4 A:0 CALL

被調用函數位于寄存器中(索引由 A 指定),傳遞給被調用函數的參數值也在寄存器中,緊挨著被調用函數,參數個數為操作數 B 指定。

① B==0,接受其他函數全部返回來的參數

② B>0,參數個數為 B-1

函數調用結束后,原先存放函數和參數值的寄存器會被返回值占據,具體多少個返回值由操作數 C 指定。

① C==0,將返回值全部返回給接收者

② C==1,無返回值

③ C>1,返回值的數量為 C-1

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A), ... ,

指令名稱類型操作碼BCA
TAILCALLiABC0x25OpArgUOpArgU目標寄存器 idx

函數調用一般通過調用棧來實現。用這種方法,每調用一個函數都會產生一個調用幀。

如果調用層次太深(如遞歸),容易導致棧溢出。尾遞歸優化則可以讓我們發揮遞歸函數調用威力的同時,避免調用棧溢出。利用這種優化,被調函數可以重用主調函數的調用幀,因此可有效緩解調用棧溢出癥狀。不過該優化只適合某些特定情況。

如:return f(args) 會被編譯器優化成 TAILCALL 指令,公式:return R(A)(R(A+1), ... , R(A+B-1))

指令名稱類型操作碼BCA
RETURNiABC0x26OpArgUOpArgN目標寄存器 idx

B:4 C A:2 RETURN

把存放在連續多個寄存器里的值返回給父函數,其中第一個寄存器的索引由操作數 A 指定,寄存器數量由操作數 B 指定,操作數 C 沒有使用,需要將返回值推入棧頂:

① B==1,不需要返回任何值

② B > 1,需要返回 B-1 個值;這些值已經在寄存器中了,只用再將它們復制到棧頂即可

③ B==0,一部分返回值已經在棧頂了,只需將另一部分也推入棧頂即可

萬字詳文:深入理解 Lua 虛擬機

 

公式:return R(A),...,R(A+B-2)

指令名稱類型操作碼sBxAFORLOOPiAsBx0x27OpArgR目標寄存器 idx

數值 for 循環:用于按一定步長遍歷某個范圍內的數值 如:for i=1,100,2 do f() end // 初始值為 1,步長為 2,上限為 100

該指令先給 i 加上步長,然后判斷 i 是否在范圍之內。若已經超出范圍,則循環結束;若為超出范圍,則將數值拷貝給用戶定義的局部變量,然后跳轉到循環體內部開始執行具體的代碼塊。

萬字詳文:深入理解 Lua 虛擬機

 

公式:

R(A) += R(A+2)
if R(A) <?= R(A+1)
    pc+=sBx
    R(A+3)=R(A)

注:當步長為正數時<?=為<=

當步長為負數時<?=為>=

指令名稱類型操作碼sBxAFORPREPiAsBx0x28OpArgR目標寄存器 idx

數值 for 循環:用于按一定步長遍歷某個范圍內的數值 如:for i=1,100,2 do f() end // 初始值為 1,步長為 2,上限為 100。

該指令的目的是在循環之前預先將 i 減去步長(得到-1),然后跳轉到 FORLOOP 指令正式開始循環:

萬字詳文:深入理解 Lua 虛擬機

 

公式:

R(A)-=R(A+2)

pc+=sBx

指令名稱類型操作碼BCA
TFORCALLiABC0x29OpArgNOpArgU目標寄存器 idx

通用 for 循環:for k,v in pairs(t) do print(k,v) end

編譯器使用的第一個特殊變量(generator):f 存放的是迭代器,其他兩個特殊變量(state):s、(control):var 來調用迭代器,把結果保存在用戶定義的變量 k、v 中。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A+3),...,R(A+2+C) := R(A)(R(A+1),R(A+2))

指令名稱類型操作碼sBxATFORLOOPiAsBx0x2AOpArgR目標寄存器 idx

通用 for 循環:for k,v in pairs(t) do print(k,v) end

若迭代器返回的第一個值(變量 k)不是 nil,則把該值拷貝到(control):var,然后跳轉到循環體;若為 nil,則循環結束。

萬字詳文:深入理解 Lua 虛擬機

 

公式:

if R(A+1) ~= nil

R(A)=R(A+1)

pc+=sBx

指令名稱類型操作碼BCA
SETLISTiABC0x2BOpArgUOpArgU目標寄存器 idx

SETTABLE 是通用指令,每次只處理一個鍵值對,具體操作交給表去處理,并不關心實際寫入的是表的 hash 部分還是數組部分。SETLIST 則是專門給數組準備的,用于按索引批量設置數組元素。其中數組位于寄存器中,索引由操作數 A 指定;需要寫入數組的一系列值也在寄存器中,緊挨著數組,數量由操作數 B 指定;數組起始索引則由操作數 C 指定。

因為 C 操作數只有 9bits,所以直接用它表示數組索引顯然不夠用。這里解決辦法是讓 C 操作數保存批次數,然后用批次數乘上批大?。‵PF,默認為 50)就可以算出數組的起始索引。因此,C 操作數能表示的最大索引為 25600(50*512),當數組長度大于 25600 時,SETLIST 指令后會跟一條 EXTRAARG 指令,用其 Ax 操作數來保存批次數。

綜上,C>0,表示的是批次數+1,否則,真正批次數存放在后續的 EXTRAARG 指令中。

操作數 B 為 0 時,當表構造器的最后一個元素是函數調用或者 vararg 表達式時,Lua 會把它們產生的所有值都收集起來供 SETLIST 使用。

萬字詳文:深入理解 Lua 虛擬機

 

公式:

R(A)[(C-1)*FPF+i] := R(A+i)

1 <= i <= B

指令名稱類型操作碼BxACLOSUREiABx0x2COpArgU目標寄存器 idx

把當前 Lua 函數的子函數原型實例化為閉包,放入由操作數 A 指定的寄存器中子函數原型來自于當前函數原型的子函數原型表,索引由操作數 Bx 指定。

下圖為將 prototypes 表中索引為 1 的 g 子函數,放入索引為 4 的寄存器中:

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A) := closure(KPROTO[Bx])

指令名稱類型操作碼BCA
VARARGiABC0x2DOpArgUOpArgN目標寄存器 idx

把傳遞給當前函數的變長參數加載到連續多個寄存器中。

其中第一個寄存器的索引由操作數 A 指定,寄存器數量由操作數 B 指定,操作數 C 沒有使用,操作數 B 若大于 1,表示把 B-1 個 vararg 參數復制到寄存器中,否則只能等于 0。

萬字詳文:深入理解 Lua 虛擬機

 

公式:R(A),R(A+1),...R(A+B-2)=vararg

指令名稱類型操作碼AxEXTRAARGiAx0x2EOpArgU

Ax:67108864 EXTRAARG

Ax 有 26bits,用來指定常量索引,可存放最大無符號整數為 67108864,可滿足大部分情況的需要了。

 

分享到:
標簽:虛擬機 Lua
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定