2 Lua指令基础
The Lua virtual machine instruction set we will look at is a particular implementation of the Lua language. It is by no means the only way to skin the chicken. The instruction set just happens to be the way the authors of Lua chose to implement version 5 of Lua. The following sections are based on the instruction set used in Lua 5.1. The instruction set might change in the future – do not expect it to be set in stone. This is because the implementation details of virtual machines are not a concern to most users of scripting languages. For most applications, there is no need to specify how bytecode is generated or how the virtual machine runs, as long as the language works as advertised. So remember that there is no official specification of the Lua virtual machine instruction set, there is no need for one; the only official specification is of the Lua language.
我们将看到的Lua虚拟机指令集,是Lua语言的一个特别的实现。它并不是实现的唯一方式。Lua的作者碰巧选择指令集作为Lua版本5的实现。下面的章节是基于Lua5.1的指令集。指令集将来有可能改变——不能期望它一成不变。这是因为虚拟机的实现细节不是脚本编写者关心的。在大部分的应用中,没有必要说明字节码是如何产生的,或者虚拟机是如何运行的,只要Lua语言按它说明的工作就行。因此记住:没有官方的Lua虚拟机指令集的说明,这也没有必要。仅有的官方说明是关于Lua语言的。
In the course of studying disassemblies of Lua binary chunks, you will notice that many generated instruction sequences aren’t as perfect as you would like them to be. This is perfectly normal from an engineering standpoint. The canonical Lua implementation is not meant to be an optimizing bytecode compiler or a JIT compiler. Instead it is supposed to load, parse and run Lua source code efficiently. It is the totality of the implementation that counts. If you really need the performance, you are supposed to drop down into native C functions anyway.
在学习Lua字节码块的反汇编的过程中,你会发现许多产生的字节码流没有你想像的那么好。从工程的角度而言,这也是很正常的。规范的Lua实现并不意味着优化字节码编译器或者JIT(Just-In-Time)编译器。实际上,它只是有效的加载,解析和运行Lua源代码。它就是这些实现的全部。如果你真的要考虑性能,无论如何你都应该深入到本地的C函数里。
Lua instructions have a fixed size, using a 32 bit unsigned integer data type by default. In binary chunks, endianness is significant, but while in memory, an instruction can be portably decoded or encoded in C using the usual integer shift and mask operations. The details can be found in lopcodes.h, while the Instructiontype definition is defined in llimits.h.
Lua指令拥有固定大小,默认使用32位无符号整型。在代码块中,字节顺序是重要的,但是在内存中,在C语言中使用整型移动和掩码操作,指令可以方便的解码或编码。详细的可以参考文件lopcodes.h,而指令类型定义在文件llimits.h中。
There are three instruction types and 38 opcodes (numbered 0 through 37) are currently in use as of Lua 5.1. The instruction types are enumerated as iABC, iABx, iAsBx, and may be visually represented as follows:
Lua5.1中,当前使用三种指令类型和38个编码(0到37)。指令类型可以枚举为:iABC, iABx, iAsBx,同时按下图方式组织:
(略)
Instruction fields are encoded as simple unsigned integer values, except for sBx. Field sBx can represent negative numbers, but it doesn’t use 2s complement. Instead, it has a bias equal to half the maximum integer that can be represented by its unsigned counterpart, Bx. For a field size of 18 bits, Bx can hold a maximum unsigned integer value of 262143, and so the bias is 131071 (calculated as 262143 >> 1). A value of -1 will be encoded as (-1 + 131071) or 131070 or 1FFFE in hexadecimal.
除了sBx,指令字段按简单的无符号整型值编码。sBx段可以代表负数,但是不能使用2s补充。取而代之的是,它有最大整数一半的偏差,这可以用它的对应部分Bx表示。字段的大小有18位,Bx能存储最大的无符号整型值262143,同时偏差为131071(通过262143 >> 1计算)。值为-1的数可以编码为(-1 + 131071)或131070或十六进制的1FFFE。
Fields A, B and C usually refers to register numbers (I’ll use the term “register” because of its similarity to processor registers). Although field A is the target operand in arithmetic operations, this rule isn’t always true for other instructions. A register is really an index into the current stack frame, register 0 being the bottom-of-stack position.
字段A、B和C通常只寄存器的数字(我使用词“寄存器”是因为它和处理器的寄存器相似)。虽然字段A在算数操作中是目标操作数,但并未适用于其它指令。寄存器实际上是当前栈帧的索引值,寄存器0是栈底位置。
Unlike the Lua C API, negative indices (counting from the top of stack) are not supported. For some instructions, where the top of stack may be required, it is encoded as a special operand value, usually 0. Local variables are equivalent to certain registers in the current stack frame, while dedicated opcodes allow read/write of globals and upvalues. For some instructions, a value in fields B or C may be a register or an encoding of the number of a constant in the constant pool. This will be described further in the section on instruction notation.
和Lua的C API不同,负索引(从栈顶计算)并不支持。对一些指令而言,栈顶值是必须的,它被编码成特别的操作数值,通常是0。局部变量相当于当前栈帧确定的寄存器,然而专用的编码允许读/写全局变量和上值。对某些指令而言,字段B或C的值也许是寄存器或常量池中常量的数字编码。这将在指令说明的章节中进一步描述。
By default, Lua has a maximum stack frame size of 250. This is encoded as MAXSTACK in llimits.h. The maximum stack frame size in turn limits the maximum number of locals per function, which is set at 200, encoded as LUAI_MAXVARS in luaconf.h. Other limits found in the same file include the maximum number of upvalues per function (60), encoded as LUAI_MAXUPVALUES, call depths, the minimum C stack size, etc. Also, with an sBx field of 18 bits, jumps and control structures cannot exceed a jump distance of about 131071.
默认情况下,Lua拥有最大的栈帧大小为250。这个值定义为文件llimits.h的MAXSTACK。最大的栈帧大小反过来限制了函数的最大的局部变量,这被设为200,位于luaconf.h中的LUAI_MAXVARS。此文件中可以发现其他的限制,这包括:函数的最大上值的个数(60)LUAI_MAXUPVALUES、调用深度、C代码栈的最小值等等。同样,sBx字段是18位,跳转和控制结构不能超过131071。
A summary of the Lua 5.1 virtual machine instruction set is as follows:
Lua5.1虚拟机指令集的简单描述如下表所示:
Opcode | Name | Description |
0 | MOVE | Copy a value between registers |
1 | LOADK | Load a constant into a register |
2 | LOADBOOL | Load a boolean into a register |
3 | LOADNIL | Load nil values into a range of registers |
4 | GETUPVAL | Read an upvalue into a register |
5 | GETGLOBAL | Read a global variable into a register |
6 | GETTABLE | Read a table element into a register |
7 | SETGLOBAL | Write a register value into a global variable |
8 | SETUPVAL | Write a register value into an upvalue |
9 | SETTABLE | Write a register value into a table element |
10 | NEWTABLE | Create a new table |
11 | SELF | Prepare an object method for calling |
12 | ADD | Addition operator |
13 | SUB | Subtraction operator |
14 | MUL | Multiplication operator |
15 | DIV | Division operator |
16 | MOD | Modulus (remainder) operator |
17 | POW | Exponentiation operator |
18 | UNM | Unary minus operator |
19 | NOT | Logical NOT operator |
20 | LEN | Length operator |
21 | CONCAT | Concatenate a range of registers |
22 | JMP | Unconditional jump |
23 | EQ | Equality test |
24 | LT | Less than test |
25 | LE | Less than or equal to test |
26 | TEST | Boolean test, with conditional jump |
27 | TESTSET | Boolean test, with conditional jump and assignment |
28 | CALL | Call a closure |
29 | TAILCALL | Perform a tail call |
30 | RETURN | Return from function call |
31 | FORLOOP | Iterate a numeric for loop |
32 | FORPREP | Initialization for a numeric for loop |
33 | TFORLOOP | Iterate a generic for loop |
34 | SETLIST | Set a range of array elements for a table |
35 | CLOSE | Close a range of locals being used as upvalues |
36 | CLOSURE | Create a closure of a function prototype |
37 | VARARG | Assign vararg function arguments to registers |
Opcode | Name | Description |
0 | MOVE | 寄存器间拷贝值 |
1 | LOADK | 加载一个常量到寄存器中 |
2 | LOADBOOL | 加载一个布尔值到寄存器中 |
3 | LOADNIL | 加载nil值到一定范围内的寄存器中 |
4 | GETUPVAL | 读取上值到寄存器中 |
5 | GETGLOBAL | 读取全局变量到寄存器中 |
6 | GETTABLE | 读取表成员到寄存器中 |
7 | SETGLOBAL | 将寄存器中的值写到全局变量中 |
8 | SETUPVAL | 将寄存器中的值写到上值中 |
9 | SETTABLE | 将寄存器中的值写到表成员中 |
10 | NEWTABLE | 创建一个新的表 |
11 | SELF | 准备对象的方法供调用 |
12 | ADD | 加法操作符 |
13 | SUB | 减法操作符 |
14 | MUL | 乘法操作符 |
15 | DIV | 除法操作符 |
16 | MOD | 取模(求余)操作符 |
17 | POW | 求幂操作符 |
18 | UNM | 取负操作符 |
19 | NOT | 逻辑非操作符 |
20 | LEN | 取长度操作符 |
21 | CONCAT | 连接一定范围的寄存器 |
22 | JMP | 无条件跳转 |
23 | EQ | 相等测试 |
24 | LT | 小于测试 |
25 | LE | 小于等于测试 |
26 | TEST | 布尔测试,附加条件跳转 |
27 | TESTSET | 布尔测试,附加条件跳转和赋值 |
28 | CALL | 调用闭合函数 |
29 | TAILCALL | 执行尾调用 |
30 | RETURN | 从函数调用中返回 |
31 | FORLOOP | 迭代数字的for循环 |
32 | FORPREP | 初始化数字的for循环 |
33 | TFORLOOP | 迭代泛化的for循环 |
34 | SETLIST | 设置数组中一定范围内的元素到表中 |
35 | CLOSE | 关闭一定范围用作上值的局部变量 |
36 | CLOSURE | 创建函数原型的闭包 |
37 | VARARG | 分配变参函数的参数到寄存器中 |
参考资料
1. skin the chicken
http://www.iciba.com/skin%20the%20chicken/
set in stone