Lua虚拟机实现
来点铺垫
Lua会先将脚本编译成字节码,然后由虚拟机执行。Lua closure的底层实现中,Proto.code指向的就是存放字节码的数组。因此虚拟机的效率对Lua的运行至关重要。
栈和寄存器
在Lua 5.0版本之前,Lua采用的是基于栈的虚拟机架构;但从Lua 5.0开始,Lua虚拟机已重构为更高效的基于寄存器的虚拟机实现。在深入介绍Lua虚拟机的底层实现之前,要先理解这两种虚拟机的区别:
- 在基于栈的虚拟机中,所有操作都通过一个栈来进行。操作数从栈中弹出,计算完成后结果再压入栈中。这种设计简单直观,但在执行效率方面存在一些劣势。
- 基于寄存器的虚拟机使用一组虚拟寄存器来存储操作数和中间结果。每条指令可以直接引用这些寄存器,避免了频繁的栈操作,显著提升了执行效率。
例如,对于同一段代码:
lua
local a, t, i
a = a + i
a = a + 1
a = t[i]在基于栈的虚拟机中,可能会按照以下指令执行:
text
PUSHNIL 3 ; local a, t, i
GETLOCAL 0 ; a
GETLOCAL 2 ; i
ADD ; a = a + i
SETLOCAL 0 ; a
GETLOCAL 0 ; a
ADDI 1 ; a = a + 1
SETLOCAL 0 ; a
GETLOCAL 1 ; t
GETINDEXED 2 ; t[i]
SETLOCAL 0 ; a = t[i]而在基于寄存器的虚拟机中,指令锐减:
text
LOADNIL 0 2 0 ; local a, t, i
ADD 0 0 2 ; a = a + i
ADD 0 0 250 ; a = a + 1
GETTABLE 0 1 2 ; a = t[i]NOTE
以上示例来自于Lua官方paper《The implementation of Lua 5.0》,这里俺就偷懒了。
字节码参考
| 操作码 | 名称 | 类型 | 参数格式 | 功能描述 |
|---|---|---|---|---|
| OP_MOVE | MOVE | iABC | A B | 将寄存器B的值移动到寄存器A,如果B是临时变量则可能被重用 |
| OP_LOADI | LOADI | iABx | A sBx | 将立即数sBx(有符号整数)加载到寄存器A,sBx范围为-131071到131071 |
| OP_LOADF | LOADF | iABx | A sBx | 将浮点常量sBx(有符号整数转换为浮点)加载到寄存器A |
| OP_LOADK | LOADK | iABx | A Bx | 从常量表中加载索引为Bx的常量到寄存器A,Bx最大值为262143 |
| OP_LOADKX | LOADKX | iABx | A | 使用EXTRAARG指令的Ax作为常量索引,从常量表加载到寄存器A |
| OP_LOADFALSE | LOADFALSE | iABC | A | 将false值加载到寄存器A,通常用于布尔表达式或条件判断初始化 |
| OP_LFALSESKIP | LFALSESKIP | iABC | A | 加载false到寄存器A并跳过下一条指令,用于优化短路逻辑表达式 |
| OP_LOADTRUE | LOADTRUE | iABC | A | 将true值加载到寄存器A,通常用于布尔表达式或条件判断初始化 |
| OP_LOADNIL | LOADNIL | iABC | A B | 将nil值加载到从寄存器A到B的所有寄存器,用于变量初始化或清除状态 |
| OP_GETUPVAL | GETUPVAL | iABC | A B | 从当前闭包的上值表中获取索引为B的上值到寄存器A,用于访问外层局部变量 |
| OP_SETUPVAL | SETUPVAL | iABC | A B | 将寄存器A的值设置到当前闭包的上值表中索引为B的位置,用于修改外层局部变量 |
| OP_GETTABUP | GETTABUP | iABC | A B C | 从当前闭包的上值表中获取索引为B的上值(必须为表),然后获取该表中键为C的值到寄存器A |
| OP_GETTABLE | GETTABLE | iABC | A B C | 从寄存器B(必须为表)中获取键为C的值到寄存器A,如果键不存在则返回nil |
| OP_GETI | GETI | iABC | A B C | 从寄存器B(必须为表)中获取整数键C的值到寄存器A,优化了整数键访问性能 |
| OP_GETFIELD | GETFIELD | iABC | A B C | 从寄存器B(必须为表)中获取字符串键C的值到寄存器A,优化了字符串键访问性能 |
| OP_SETTABUP | SETTABUP | iABC | A B C | 在当前闭包的上值表中设置索引为A的上值(必须为表)的键B的值为C,用于修改外层表字段 |
| OP_SETTABLE | SETTABLE | iABC | A B C | 设置寄存器A(必须为表)的键B的值为C,如果表不存在会触发错误 |
| OP_SETI | SETI | iABC | A B C | 设置寄存器A(必须为表)的整数键B的值为C,优化了整数键设置性能 |
| OP_SETFIELD | SETFIELD | iABC | A B C | 设置寄存器A(必须为表)的字符串键B的值为C,优化了字符串键设置性能 |
| OP_NEWTABLE | NEWTABLE | iABC | A B C | 创建新表并存储在寄存器A中,B指定数组部分初始大小,C指定哈希部分初始大小 |
| OP_SELF | SELF | iABC | A B C | 准备方法调用,将寄存器B的键C方法放入寄存器A和A+1 |
| OP_ADDI | ADDI | iABC | A B sC | 寄存器B加立即数sC结果存入A |
| OP_ADDK | ADDK | iABC | A B C | 寄存器B加常量C结果存入A |
| OP_SUBK | SUBK | iABC | A B C | 寄存器B减常量C结果存入A |
| OP_MULK | MULK | iABC | A B C | 寄存器B乘常量C结果存入A |
| OP_MODK | MODK | iABC | A B C | 寄存器B模常量C结果存入A |
| OP_POWK | POWK | iABC | A B C | 寄存器B的常量C次方结果存入A |
| OP_DIVK | DIVK | iABC | A B C | 寄存器B除常量C结果存入A |
| OP_IDIVK | IDIVK | iABC | A B C | 寄存器B整除常量C结果存入A |
| OP_BANDK | BANDK | iABC | A B C | 寄存器B与常量C按位与结果存入A |
| OP_BORK | BORK | iABC | A B C | 寄存器B或常量C按位或结果存入A |
| OP_BXORK | BXORK | iABC | A B C | 寄存器B异或常量C按位异或结果存入A |
| OP_SHRI | SHRI | iABC | A B sC | 寄存器B右移立即数sC结果存入A |
| OP_SHLI | SHLI | iABC | A B sC | 寄存器B左移立即数sC结果存入A |
| OP_ADD | ADD | iABC | A B C | 寄存器B加寄存器C结果存入A |
| OP_SUB | SUB | iABC | A B C | 寄存器B减寄存器C结果存入A |
| OP_MUL | MUL | iABC | A B C | 寄存器B乘寄存器C结果存入A |
| OP_MOD | MOD | iABC | A B C | 寄存器B模寄存器C结果存入A |
| OP_POW | POW | iABC | A B C | 寄存器B的寄存器C次方结果存入A |
| OP_DIV | DIV | iABC | A B C | 寄存器B除寄存器C结果存入A |
| OP_IDIV | IDIV | iABC | A B C | 寄存器B整除寄存器C结果存入A |
| OP_BAND | BAND | iABC | A B C | 寄存器B与寄存器C按位与结果存入A |
| OP_BOR | BOR | iABC | A B C | 寄存器B或寄存器C按位或结果存入A |
| OP_BXOR | BXOR | iABC | A B C | 寄存器B异或寄存器C按位异或结果存入A |
| OP_SHL | SHL | iABC | A B C | 寄存器B左移寄存器C结果存入A |
| OP_SHR | SHR | iABC | A B C | 寄存器B右移寄存器C结果存入A |
| OP_MMBIN | MMBIN | iABC | A B C | 调用寄存器B的元方法处理二元操作C |
| OP_MMBINI | MMBINI | iABC | A sB C k | 调用寄存器A的元方法处理立即数二元操作 |
| OP_MMBINK | MMBINK | iABC | A B C k | 调用寄存器A的元方法处理常量二元操作 |
| OP_UNM | UNM | iABC | A B | 寄存器B取负结果存入A |
| OP_BNOT | BNOT | iABC | A B | 寄存器B按位取反结果存入A |
| OP_NOT | NOT | iABC | A B | 寄存器B逻辑取反结果存入A |
| OP_LEN | LEN | iABC | A B | 获取寄存器B的长度存入A |
| OP_CONCAT | CONCAT | iABC | A B C | 连接寄存器B到C的字符串存入A |
| OP_CLOSE | CLOSE | iABC | A | 关闭寄存器A到A的局部变量 |
| OP_TBC | TBC | iABC | A | 标记寄存器A为待关闭 |
| OP_JMP | JMP | isJ | sJ | 无条件跳转sJ |
| OP_EQ | EQ | iABC | A B k | 比较寄存器B和C是否相等 |
| OP_LT | LT | iABC | A B k | 比较寄存器B是否小于C |
| OP_LE | LE | iABC | A B k | 比较寄存器B是否小于等于C |
| OP_EQK | EQK | iABC | A B k | 比较寄存器B和常量C是否相等 |
| OP_EQI | EQI | iABC | A sB k | 比较寄存器A和立即数sB是否相等 |
| OP_LTI | LTI | iABC | A sB k | 比较寄存器A是否小于立即数sB |
| OP_LEI | LEI | iABC | A sB k | 比较寄存器A是否小于等于立即数sB |
| OP_GTI | GTI | iABC | A sB k | 比较寄存器A是否大于立即数sB |
| OP_GEI | GEI | iABC | A sB k | 比较寄存器A是否大于等于立即数sB |
| OP_TEST | TEST | iABC | A k | 测试寄存器A是否为真 |
| OP_TESTSET | TESTSET | iABC | A B k | 测试寄存器B是否为真并存入A |
| OP_CALL | CALL | iABC | A B C | 调用寄存器A的函数,参数B-1个,结果C-1个 |
| OP_TAILCALL | TAILCALL | iABC | A B C k | 尾调用寄存器A的函数 |
| OP_RETURN | RETURN | iABC | A B | 从函数返回寄存器A到B的值 |
| OP_RETURN0 | RETURN0 | iABC | 返回0个值 | |
| OP_RETURN1 | RETURN1 | iABC | A | 返回寄存器A的1个值 |
| OP_FORLOOP | FORLOOP | iAsBx | A sBx | 数字for循环 |
| OP_FORPREP | FORPREP | iAsBx | A sBx | 数字for循环准备 |
| OP_TFORPREP | TFORPREP | iAsBx | A sBx | 通用for循环准备 |
| OP_TFORCALL | TFORCALL | iABC | A B C | 通用for循环调用 |
| OP_TFORLOOP | TFORLOOP | iAsBx | A sBx | 通用for循环 |
| OP_SETLIST | SETLIST | iABC | A B C | 设置列表元素 |
| OP_CLOSURE | CLOSURE | iABx | A Bx | 创建闭包 |
| OP_VARARG | VARARG | iABC | A B | 处理可变参数 |
| OP_VARARGPREP | VARARGPREP | iABC | A | 准备可变参数 |
| OP_EXTRAARG | EXTRAARG | iAx | Ax | 额外参数 |