為了達到較高的執行效率,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在編譯時會將整個文件放到一個稱之為main函數中,並以它為起點進行編譯
Hello.lua源代碼如下:
1 print ("hello") 2 3 function add(a, b) 4 return a+b 5 end
編譯得到的Hello.out的二進制為:
二進制chunk(Binary chunk)的格式並沒有標准化,也沒有任何官方文檔對其進行說明,一切以lua官方實現的源代碼為准。
其設計並沒有考慮跨平台,對於需要超過一個字節表示的數據,必須要考慮大小端(Endianness)問題。
lua官方實現的做法比較簡單:編譯lua腳本時,直接按照本機的大小端方式生成二進制chunk文件,當加載二進制chunk文件時,會探測被加載文件的大小端方式,如果和本機不匹配,就拒絕加載.
二進制chunk格式設計也沒有考慮不同lua版本之間的兼容問題,當加載二進制chunk文件時,會檢測其版本號,如果和當前lua版本不匹配,就拒絕加載
另外,二進制chunk格式設計也沒有被刻意設計得很緊湊。在某些情況下,一段lua代碼編譯成二進制chunk后,甚至會被文本形式的源代碼還要大。
預編譯成二進制chunk主要是為了提升加載速度,因此這也不是很大的問題.
頭部字段:
字段 | 字節數 | 值 | 解釋 |
簽名(signature) | byte[4] | 0x1B4C7561 | 二進制chunk文件的魔數,分別是ESC、L、u、a的ASCII碼 |
版本號(version) | byte | 0x53 | lua語言的版本號由3部分組成:大版本號(Major Version)、小版本號(Minor Version)和發布號(Release Version) 比如:當前lua的版本號為5.3.5,那么大版本號為5,小版本號為3、發布號為5 由於發布號的增加只是為了修復bug,不會對二進制chunk文件格式進行調整,因此版本號(version)只存儲了大版本號和小版本號 |
格式號(format) | byte | 0x00 | 二進制chunk文件的格式號 lua虛擬機在加載二進制chunk時,會檢查其格式號,如果和虛擬機本身的格式號不匹配,就會拒絕加載該文件 |
luacData | byte[6] | 0x19930D0A1A0A | 其中前兩個字節0x1993,是lua1.0發布的年份;后四個字節依次是回車符(0x0D)、換行符(0x0A)、替換符(0x1A)和另一個換行符 |
cintSize | byte | 0x04 | cint數據類型size |
sizeSize | byte | 0x04 | size_t數據類型size |
instructionSize | byte | 0x04 | lua虛擬機指令size |
luaIntegerSize | byte | 0x08 | lua整型size |
luaNumberSize | byte | 0x08 | lua浮點數size |
lua整數值(luacInt) | int64 | 0x7856000000000000 | 加載二進制chunk文件時,會用該字段檢測其大小端和本機是否匹配 |
格式號(luacNum) | float64 | 0x0000000000287740 | 浮點數值為370.5 加載二進制chunk文件時,會用該字段檢測其使用的浮點數格式(目前主流平台一般都采用IEEE754浮點數格式) |
嵌套的函數原型:
0B4048656C6C6F2E6C7561 | 源文件名Hello.lua;-s去掉調試信息后,該項數值為:00 | ||
main | 00000000 00000000 | main函數起始行列號 | |
00 | 函數參數個數 | ||
01 | 函數為不定參數 | ||
02 | 寄存器數量 | ||
06000000 | 函數指令數目 | ||
06004000 | 第1條指令 | ||
41400000 | 第2條指令 | ||
24400001 | 第3條指令 | ||
2C000000 | 第4條指令 | ||
08000081 | 第5條指令 | ||
26008000 | 第6條指令 | ||
03000000 | 常量數目 | ||
04 | 第1個常量tag,04表示字符串 | ||
067072696E74 | 第1個常量內容 | ||
04 | 第2個常量tag,04表示字符串 | ||
0668656C6C6F | 第2個常量內容 | ||
04 | 第3個常量tag,04表示字符串 | ||
04616464 | 第3個常量內容 | ||
01000000 | Upvalue數目 | ||
0100 | 第1個Upvalue | ||
01000000 | 子函數原型數目 | ||
add | 03000000 05000000 | add函數起始行列號 | |
02 | 函數參數個數 | ||
00 | 函數為不定參數 | ||
03 | 寄存器數量 | ||
03000000 | 函數指令數目 | ||
8D400000 | 第1條指令 | ||
A6000001 | 第2條指令 | ||
26008000 | 第3條指令 | ||
00000000 | 常量數目 | ||
00000000 | Upvalue數目 | ||
00000000 | 子函數原型數目 | ||
03000000 | 行號數目;-s去掉調試信息后,該項數值為:00000000;為0時下面的信息也都沒有 | ||
04000000 | 第1條指令行號 | ||
04000000 | 第2條指令行號 | ||
05000000 | 第3條指令行號 | ||
02000000 | 局部變量數目;-s去掉調試信息后,該項數值為:00000000;為0時下面的信息也都沒有 | ||
0261 | 第1個局部變量名稱 | ||
00000000 | 第1個局部變量起始指令索引 | ||
03000000 | 第1個局部變量終止指令索引 | ||
0262 | 第2個局部變量名稱 | ||
00000000 | 第2個局部變量起始指令索引 | ||
03000000 | 第2個局部變量終止指令索引 | ||
00000000 | Upvalue名稱數目;-s去掉調試信息后,該項數值為:00000000;為0時下面的信息也都沒有 | ||
06000000 | 行號數目;-s去掉調試信息后,該項數值為:00000000;為0時下面的信息也都沒有 | ||
01000000 | 第1條指令行號 | ||
01000000 | 第2條指令行號 | ||
01000000 | 第3條指令行號 | ||
05000000 | 第4條指令行號 | ||
03000000 | 第5條指令行號 | ||
05000000 | 第6條指令行號 | ||
00000000 | 局部變量數目;-s去掉調試信息后,該項數值為:00000000;為0時下面的信息也都沒有 | ||
01000000 | Upvalue名稱數目;-s去掉調試信息后,該項數值為:00000000;為0時下面的信息也都沒有 | ||
055F454E56 | 第1個Upvalue名稱 |
注1:二進制chunk中的字符串分為三種情況:
①NULL字符串用0x00表示
②長度小於等於253(0xFD)的字符串,先用1個byte存儲字符串長度+1的數值,然后是字節數組
③長度大於等於254(0xFE)的字符串,第一個字節是0xFF,后面跟一個8字節size_t類型存儲字符串長度+1的數值,然后是字節數組
注2:常量tag對應表
tag | lua字面量類型 | 存儲類型 |
0x00 | nil | 不存儲 |
0x01 | boolean | 字節(0、1) |
0x03 | number | lua浮點數 |
0x13 | integer | lua整數 |
0x04 | string | 短字符串 |
0x14 | string | 長字符串 |
查看二進制chunk中的所有函數(精簡模式):
luac.exe -l Hello.lua
luac.exe -l Hello.out
注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中
所有的指令執行,都是基於一個操作數棧的。你想要執行任何指令時,對不起,得先入棧,然后算完了再給我出棧。
總的來說,就是抽象出了一個高度可移植的操作數棧,所有代碼都會被編譯成字節碼,然后字節碼就是在玩這個棧。 好處是實現簡單,移植性強。
壞處是指令條數比較多,數據轉移次數比較多,因為每一次入棧出棧都牽涉數據的轉移。
基於寄存器的虛擬機,會轉化成如下指令
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)
4種模式中,只有iAsBx下的sBx操作數會被解釋成有符號整數,其他情況下操作數均被解釋為無符號整數
操作數A主要用來表示目標寄存器索引,其他操作數按表示信息可分為4種類型:OpArgN、OpArgU、OpArgR、OpArgK
類型 | 示例 | 說明 |
OpArgN | B:1 C A:3 MOVE |
不表示任何信息,不會被使用 MOVE指令的C操作數為OpArgN類型 |
OpArgR | B:1 C A:3 MOVE B:2 C:4 A:1 CONCAT |
iABC指令中的B、C操作數,表示寄存器索引 |
sBx:-3 A:0 FORLOOP | iAsBx指令中的sBx操作數,表示跳轉偏移 | |
OpArgK | Bx:2 A:4 LOADK |
LOADK指令中的Bx操作數,表示常量表索引 |
B:0x001 C:0x100 A:4 ADD | iABC指令中的B、C操作數 B、C操作數只能使用9bits中的低8位 最高位為1,表示常量表索引 最高位為0,表示寄存器索引 |
|
OpArgU | B:0 C:1 A:2 LOADBOOL |
表示布爾值、整數值、Upvalue索引、子函數索引等 |
Lua棧索引
注1:絕對索引是從1開始由棧底到棧頂依次增長的
注2:相對索引是從-1開始由棧頂到棧底依次遞減的(在lua API函數內部會將相對索引轉換為絕對索引)
注3:上圖棧的容量為7,棧頂絕對索引為5,有效索引范圍為:[1, 5],可接受索引范圍為:[1, 7]
注4:Lua虛擬機指令里寄存器索引是從0開始的,而Lua API里的棧索引是從1開始的,因此當需要把寄存器索引當成棧索引使用時,要進行+1
Lua State
指令表
指令名稱 | 類型 | 操作碼 | 操作數類型 (B|C|A、Bx|A、sBx|A、Ax) |
示例說明 | 公式 | ||
MOVE | iABC | 0x00 | OpArgR | OpArgN | 目標寄存器idx | B:1 C A:3 MOVE 把源寄存器(索引由B指定)里的值移動到目標寄存器(索引有A指定) 常用於局部變量賦值和參數傳遞 |
R(A) := R(B) |
LOADK | iABx | 0x01 | OpArgK | 目標寄存器idx | Bx:2 A:4 LOADK 給單個寄存器(索引由A指定)設置成常量(其在常量表的索引由Bx指定) 將常量表里的某個常量加載到指定寄存器 在lua中,數值型、字符串型等局部變量賦初始值 (數字和字符串會放到常量表中) |
R(A) := Kst(Bx) | |
LOADKX | iABx | 0x02 | OpArgN | 目標寄存器idx | Bx A:4 LOADKX Ax:585028 EXTRAARG LOADK使用Bx(18bits,最大無符號整數為262143)表示常量表索引 當將lua作數據描述語言使用時,常量表可能會超過這個限制, 為了應對這種情況,lua提供了LOADKX指令 LOADKX指令需要和EXTRAAG指令搭配使用,用后者的Ax(26bits)操作數來指定常量索引 |
R(A) := Kst(Ax) | |
LOADBOOL | iABC | 0x03 | OpArgU | OpArgU | 目標寄存器idx | B:0 C:1 A:2 LOADBOOL 給單個寄存器(索引由A指定)設置布爾值(布爾值由B指定) 如果寄存器C為非0則跳過下一條指令 ![]() |
R(A) := (bool)B if(C) pc++ |
LOADNIL | iABC | 0x04 | OpArgU | OpArgN | 目標寄存器idx | B:4 C A:0 LOADNIL 將序號[A,A+B]連續B+1個寄存器設置成nil值 用於給連續n個寄存器放置nil值 在lua中,局部變量的默認初始值為nil,LOADNIL指令常用於給連續n個局部變量設置初始值 |
R(A), R(A+1), ... ,R(A+B) := nil |
GETUPVAL | iABC | 0x05 | OpArgU | OpArgN | 目標寄存器idx | B:1 C A:3 GETUPVAL 把當前閉包的某個Upvalue值(索引由B指定)拷貝到目標寄存器(索引由A指定)中
|
R(A) := Upvalue[B] |
GETTABUP | iABC | 0x06 | OpArgU | OpArgK | 目標寄存器idx | B:0 C:0x002 A:3 GETTABUP 把當前閉包的某個Upvalue值(索引由B指定)拷貝到目標寄存器(索引由A指定)中 與GETUPVAL不同的是,Upvalue從表里取值(鍵由C指定,為寄存器或常量表索引)
|
R(A) := Upvalue[B][RK(C)] |
GETTABLE | iABC | 0x07 | OpArgR | OpArgK | 目標寄存器idx | B:0 C:0x002 A:3 GETTABLE 把表中某個值拷貝到目標寄存器(索引由A指定)中 表所在寄存器索引由B指定,鍵由C(為寄存器或常量表索引)指定 |
R(A) := R[B][RK(C)] |
SETTABUP | iABC | 0x08 | OpArgK | OpArgK | 目標寄存器idx | B:0x002 C:0x003 A:0 SETTABUP 設置當前閉包的某個Upvalue值(索引由A指定)為寄存器或常量表的某個值(索引由C指定) 與SETUPVAL不同的是,Upvalue從表里取值(鍵由B指定,為寄存器或常量表索引)
|
Upvalue[A][RK(B)] := RK(C) |
SETUPVAL | iABC | 0x09 | OpArgU | OpArgN | 目標寄存器idx | B:0 C A:3 SETUPVAL 設置當前閉包的某個Upvalue值(索引由B指定)為寄存器的某個值(索引由A指定) |
Upvalue[B] := R(A) |
SETTABLE | iABC | 0x0A | OpArgK | OpArgK | 目標寄存器idx | B:0x002 C:0x003 A:1 SETTABLE 給寄存器中的表(索引由A指定)的某個鍵進行賦值 鍵和值分別由B和C指定(為寄存器或常量表索引) |
R(A)[RK(B)] := RK(C) |
NEWTABLE | iABC | 0x0B | OpArgU | OpArgU | 目標寄存器idx | B:0 C:2 A:4 NEWTABLE 創建空表,並將其放入指定寄存器(索引有A指定) 表的初始數組容量和哈希表容量分別有B和C指定 |
R(A) := {} (size = B, C) |
SELF | iABC | 0x0C | OpArgR | OpArgK | 目標寄存器idx | B:1 C:0x100 A:2 SELF 把寄存器中對象(索引由B指定)和常量表中方法(索引由C指定)拷貝到相鄰的兩個目標寄存器中 起始目標寄存器的索引由A指定 |
R(A+1) := R(B) R(A) := R(B)[RK(C)] |
ADD(+,加) | iABC | 0x0D | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 ADD 對兩個寄存器或常量值(索引由B和C指定)進行相加,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) + RK(C) |
SUB (-,減) |
iABC | 0x0E | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 SUB 對兩個寄存器或常量值(索引由B和C指定)進行相減,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) - RK(C) |
MUL (*,乘) |
iABC | 0x0F | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 MUL 對兩個寄存器或常量值(索引由B和C指定)進行相乘,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) * RK(C) |
MOD (%,求模) |
iABC | 0x10 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 MOD 對兩個寄存器或常量值(索引由B和C指定)進行求摸運算,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) % RK(C) |
POW (^,求冪) |
iABC | 0x11 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 POW 對兩個寄存器或常量值(索引由B和C指定)進行求冪運算,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) ^ RK(C) |
DIV (/,除) |
iABC | 0x12 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 DIV 對兩個寄存器或常量值(索引由B和C指定)進行相除,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) / RK(C) |
IDIV (//,整除) |
iABC | 0x13 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 IDIV 對兩個寄存器或常量值(索引由B和C指定)進行相整除,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) // RK(C) |
BAND (&,與) |
iABC | 0x14 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 BAND 對兩個寄存器或常量值(索引由B和C指定)進行求與操作,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) & RK(C) |
BOR (|,或) |
iABC | 0x15 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 BOR 對兩個寄存器或常量值(索引由B和C指定)進行求或操作,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) | RK(C) |
BXOR (~,異或) |
iABC | 0x16 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 BXOR 對兩個寄存器或常量值(索引由B和C指定)進行求異或操作,並將結果放入另一個寄存器中(索引由A指定)
|
R(A) := RK(B) ~ RK(C) |
SHL (<<,左移) |
iABC | 0x17 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 SHL 索引由B指定的寄存器或常量值進行左移位操作(移動位數的索引由C指定的寄存器或常量值) 並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) << RK(C) |
SHR (>>,右移) |
iABC | 0x18 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:4 SHR 索引由B指定的寄存器或常量值進行右移位操作(移動位數的索引由C指定的寄存器或常量值) 並將結果放入另一個寄存器中(索引由A指定) |
R(A) := RK(B) >> RK(C) |
UNM (-,取負數) |
iABC | 0x19 | OpArgR | OpArgN | 目標寄存器idx | B:1 C A:3 UNM 對寄存器(索引由B指定)進行取負數操作,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := - R(B) |
BNOT (~,取反) |
iABC | 0x1A | OpArgR | OpArgN | 目標寄存器idx | B:1 C A:3 BNOT 對寄存器(索引由B指定)進行取反操作,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := ~ R(B) |
NOT | iABC | 0x1B | OpArgR | OpArgN | 目標寄存器idx | B:1 C A:3 NOT 對寄存器(索引由B指定)進行求非操作,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := not R(B) |
LEN | iABC | 0x1C | OpArgR | OpArgN | 目標寄存器idx | B:1 C A:3 LEN 對寄存器(索引由B指定)進行求長度操作,並將結果放入另一個寄存器中(索引由A指定) |
R(A) := length of R(B) |
CONCAT | iABC | 0x1D | OpArgR | OpArgR | 目標寄存器idx | B:2 C:4 A:1 CONCAT 將連續n個寄存器(起始索引和終止索引由B和C指定)里的值進行拼接 並將結果放入另一個寄存器中(索引由A指定) |
R(A) := R(B) .. ... .. R(C) |
JMP | iAsBx | 0x1E | OpArgR | 目標寄存器idx | sBx:-1 A JMP 當sBx不為0時,進行無條件跳轉 執行pc = pc + sBx(sBx為-1,表示將當前指令再執行一次 注:這將是一個死循環) sBx:0 A:0x001 JMP 當sBx為0時(繼續執行后面指令,不跳轉) 用於閉合處於開啟狀態的Upvalue(即:把即將銷毀的局部變量的值復制出來,並更新到某個Upvalue中) 當前閉包的某個Upvalue值的索引由A指定 |
||
EQ(==) | iABC | 0x1F | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:1 EQ 寄存器或常量表(索引由B指定)是否等於寄存器或常量表(索引由C指定) 若結果等於操作數A,則跳過下一條指令 |
if ((RK(B) == RK(C)) pc++ |
LT(<) | iABC | 0x20 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:1 LT 寄存器或常量表(索引由B指定)是否小於寄存器或常量表(索引由C指定) 若結果等於操作數A,則跳過下一條指令 |
if ((RK(B) < RK(C)) pc++ |
LE(<=) | iABC | 0x21 | OpArgK | OpArgK | 目標寄存器idx | B:0x001 C:0x100 A:1 LE 寄存器或常量表(索引由B指定)是否小於等於寄存器或常量表(索引由C指定) 若結果等於操作數A,則跳過下一條指令 |
if ((RK(B) <= RK(C)) pc++ |
TEST | iABC | 0x22 | OpArgN | OpArgU | 目標寄存器idx | B C:0 A:1 TEST 判斷寄存器(索引由A指定)中的值轉換為bool值后,是否和操作數C表示的bool值一致 若結果不一致,則跳過下一條指令 |
if not (R(A) <=> C) pc++ 注:<=>表示按bool值比較 |
TESTSET | iABC | 0x23 | OpArgR | OpArgU | 目標寄存器idx | B:3 C:0 A:1 TESTSET 判斷寄存器(索引由B指定)中的值轉換為bool值后,是否和操作數C表示的bool值一致 若結果一致,將寄存器(索引由B指定)中的值復制到寄存器中(索引由A指定),否則跳過下一條指令
|
if (R(B) <=> C) R(A) := R(B) else pc++ 注:<=>表示按bool值比較 |
CALL |
iABC | 0x24 | OpArgU | OpArgU | 目標寄存器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 |
R(A), ... , |
TAILCALL | iABC | 0x25 | OpArgU | OpArgU | 目標寄存器idx | 函數調用一般通過調用棧來實現。用這種方法,每調用一個函數都會產生一個調用幀。 如果調用層次太深(如遞歸),容易導致棧溢出。尾遞歸優化則可以讓我們發揮遞歸函數調用威力的同時,避免調用棧溢出。 利用這種優化,被調函數可以重用主調函數的調用幀,因此可有效緩解調用棧溢出症狀。不過該優化只適合某些特定情況。 如:return f(args) 會被編譯器優化成TAILCALL指令 |
return R(A)(R(A+1), ... , R(A+B-1)) |
RETURN | iABC | 0x26 | OpArgU | OpArgN | 目標寄存器idx | B:4 C A:2 RETURN 把存放在連續多個寄存器里的值返回給父函數 其中第一個寄存器的索引由操作數A指定,寄存器數量由操作數B指定,操作數C沒有使用
需要將返回值推入棧頂 ① B==1,不需要返回任何值 ② B > 1,需要返回B-1個值;這些值已經在寄存器中了,只用再將它們復制到棧頂即可 ③ B==0,一部分返回值已經在棧頂了,只需將另一部分也推入棧頂即可 |
return R(A),...,R(A+B-2) |
FORLOOP | iAsBx | 0x27 | OpArgR | 目標寄存器idx | 數值for循環:用於按一定步長遍歷某個范圍內的數值 如:for i=1,100,2 do f() end // 初始值為1,步長為2,上限為100
該指令先給i加上步長,然后判斷i是否在范圍之內。若已經超出范圍,則循環結束;若為超出范圍,則將數值拷貝給用戶定義的局部變量 然后跳轉到循環體內部開始執行具體的代碼塊 |
R(A) += R(A+2) if R(A) <?= R(A+1) pc+=sBx R(A+3)=R(A) 注:當步長為正數時<?=為<= 當步長為負數時<?=為>= |
|
FORPREP | iAsBx | 0x28 | OpArgR | 目標寄存器idx | 數值for循環:用於按一定步長遍歷某個范圍內的數值 如:for i=1,100,2 do f() end // 初始值為1,步長為2,上限為100
該指令的目的是在循環之前預先將i減去步長(得到-1),然后跳轉到FORLOOP指令正式開始循環 |
R(A)-=R(A+2) pc+=sBx |
|
TFORCALL | iABC | 0x29 | OpArgN | OpArgU | 目標寄存器idx | 通用for循環: for k,v in pairs(t) do print(k,v) end
編譯器使用的第一個特殊變量(generator):f存放的是迭代器,其他兩個特殊變量(state):s、(control):var來調用迭代器 把結果保存在用戶定義的變量k、v中 |
R(A+3),...,R(A+2+C) := R(A)(R(A+1),R(A+2)) |
TFORLOOP | iAsBx | 0x2A | OpArgR | 目標寄存器idx | 通用for循環: for k,v in pairs(t) do print(k,v) end
若迭代器返回的第一個值(變量k)不是nil,則把該值拷貝到(control):var,然后跳轉到循環體;若為nil,則循環結束 |
if R(A+1) ~= nil R(A)=R(A+1) pc+=sBx |
|
SETLIST | iABC | 0x2B | OpArgU | OpArgU | 目標寄存器idx | SETTABLE是通用指令,每次只處理一個鍵值對,具體操作交給表去處理,並不關心實際寫入的是表的hash部分還是數組部分。 SETLIST則是專門給數組准備的,用於按索引批量設置數組元素。其中數組位於寄存器中,索引由操作數A指定; 需要寫入數組的一系列值也在寄存器中,緊挨着數組,數量由操作數B指定;數組起始索引則由操作數C指定。
因為C操作數只有9bits,所以直接用它表示數組索引顯然不夠用。 這里解決辦法是讓C操作數保存批次數,然后用批次數乘上批大小(FPF,默認為50)就可以算出數組的起始索引。 因此,C操作數能表示的最大索引為25600(50*512) 當數組長度大於25600時,SETLIST指令后會跟一條EXTRAARG指令,用其Ax操作數來保存批次數。 綜上,C>0,表示的是批次數+1,否則,真正批次數存放在后續的EXTRAARG指令中。
操作數B為0時,當表構造器的最后一個元素是函數調用或者vararg表達式時,Lua會把它們產生的所有值都收集起來供SETLIST使用 |
R(A)[(C-1)*FPF+i] := R(A+i) 1 <= i <= B |
CLOSURE | iABx | 0x2C | OpArgU | OpArgN | 目標寄存器idx | 把當前Lua函數的子函數原型實例化為閉包,放入由操作數A指定的寄存器中 子函數原型來自於當前函數原型的子函數原型表,索引由操作數Bx指定
下圖為將prototypes表中索引為1的g子函數,放入索引為4的寄存器中 |
R(A) := closure(KPROTO[Bx]) |
VARARG | iABC | 0x2D | OpArgU | OpArgN | 目標寄存器idx | 把傳遞給當前函數的變長參數加載到連續多個寄存器中。 其中第一個寄存器的索引由操作數A指定,寄存器數量由操作數B指定,操作數C沒有使用 操作數B若大於1,表示把B-1個vararg參數復制到寄存器中,否則只能等於0 |
R(A),R(A+1),...R(A+B-2)=vararg |
EXTRAARG | iAx | 0x2E | OpArgU | Ax:67108864 EXTRAARG Ax有26bits,用來指定常量索引,可存放最大無符號整數為67108864,可滿足大部分情況的需要了 |
參考