Lua 虚拟机指令

Lua 虚拟机指令

Lua运行代码时,首先把代码编译成虚拟机的指令("opcode"),而后执行它们。 Lua编译器为每一个函数建立一个原型(prototype),这个原型包含函数执行的一组指令和函数所用到的数据表1c++

从Lua5.0开始,Lua使用基于寄存器的虚拟机(虚拟机主要分为基于寄存器的和基于栈的)。 为了分配寄存器使用时的activation record,这个虚拟机也使用到了栈。 当Lua进入函数时,它从栈中预分配了足够容纳全部函数寄存器的activation record。 全部的局部变量在寄存器中分配。所以提升了访问局部变量的效率。数组

基于寄存器的指令避免了“push”和“pop”操做,而这正式基于栈的虚拟机须要的。 这些操做在Lua中十分昂贵,由于它们涉及了对值的拷贝。 因此寄存器结构可以避免昂贵的值拷本,以及减小每一个函数指令个数。bash

但基于寄存器的虚拟机仍有两个问题:指令的长度和译码的代价。 一条基于寄存器的指令须要指明它的操做对象,因此它比通常基于栈的指令要长(如Lua的指令为4个字节,而基于栈的指令只需1~2个字节)。 另外一方面,基于寄存器虚拟机通常都比基于栈的虚拟机产生更少的操做,所以整体长度不会很长。ide

大多数基于栈的指令都有隐式操做对象。 而基于寄存器的指令是从指令中取出操做对象,这增长了解释器的开销。 针对这个开销,如下有几点分析。 第一,基于栈的虚拟机仍须要寻找隐藏的操做对象。 第二,有时基于寄存器的操做对象有更低的运算代价,如逻辑操做。 而基于栈的虚拟机一个指令有时须要屡次操做。函数

Lua虚拟机有35条指令,大部分指令与语言结构直接交互,如算术、table建立和索引、函数和方法的调用、写入和读取变量的值。 固然还有一组常规的跳转指令来实现过程控制。 下段代码是Lua中指令名称的定义。 测试

---------------------------------------------------------------------------
// R(x) - register
// Kst(x) - constant 常量 (in constant table)
// RK(x) == if ISK(x) then Kst(INDEXK(x)) else R(x)

typedef enum {
/*----------------------------------------------------------------------
name		args	description
------------------------------------------------------------------------*/
OP_MOVE,/*	A B	R(A) := R(B)					*/
OP_LOADK,/*	A Bx	R(A) := Kst(Bx)					*/
OP_LOADBOOL,/*	A B C	R(A) := (Bool)B; if (C) pc++			*/
OP_LOADNIL,/*	A B	R(A) := ... := R(B) := nil			*/
OP_GETUPVAL,/*	A B	R(A) := UpValue[B]				*/

OP_GETGLOBAL,/*	A Bx	R(A) := Gbl[Kst(Bx)]				*/
OP_GETTABLE,/*	A B C	R(A) := R(B)[RK(C)]				*/

OP_SETGLOBAL,/*	A Bx	Gbl[Kst(Bx)] := R(A)				*/
OP_SETUPVAL,/*	A B	UpValue[B] := R(A)				*/
OP_SETTABLE,/*	A B C	R(A)[RK(B)] := RK(C)				*/

OP_NEWTABLE,/*	A B C	R(A) := {} (size = B,C)				*/

OP_SELF,/*	A B C	R(A+1) := R(B); R(A) := R(B)[RK(C)]		*/

OP_ADD,/*	A B C	R(A) := RK(B) + RK(C)				*/
OP_SUB,/*	A B C	R(A) := RK(B) - RK(C)				*/
OP_MUL,/*	A B C	R(A) := RK(B) * RK(C)				*/
OP_DIV,/*	A B C	R(A) := RK(B) / RK(C)				*/
OP_MOD,/*	A B C	R(A) := RK(B) % RK(C)				*/
OP_POW,/*	A B C	R(A) := RK(B) ^ RK(C)				*/
OP_UNM,/*	A B	R(A) := -R(B)					*/
OP_NOT,/*	A B	R(A) := not R(B)				*/
OP_LEN,/*	A B	R(A) := length of R(B)				*/

OP_CONCAT,/*	A B C	R(A) := R(B).. ... ..R(C)			*/

OP_JMP,/*	sBx	pc+=sBx					*/

OP_EQ,/*	A B C	if ((RK(B) == RK(C)) ~= A) then pc++		*/
OP_LT,/*	A B C	if ((RK(B) <  RK(C)) ~= A) then pc++  		*/
OP_LE,/*	A B C	if ((RK(B) <= RK(C)) ~= A) then pc++  		*/

OP_TEST,/*	A C	if not (R(A) <=> C) then pc++			*/
OP_TESTSET,/*	A B C	if (R(B) <=> C) then R(A) := R(B) else pc++	*/

OP_CALL,/*	A B C	R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
OP_TAILCALL,/*	A B C	return R(A)(R(A+1), ... ,R(A+B-1))		*/
OP_RETURN,/*	A B	return R(A), ... ,R(A+B-2)	(see note)	*/

OP_FORLOOP,/*	A sBx	R(A)+=R(A+2);
			if R(A) =) R(A)*/
OP_CLOSURE,/*	A Bx	R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))	*/

OP_VARARG/*	A B	R(A), R(A+1), ..., R(A+B-1) = vararg		*/
} OpCode;

---------------------------------------------------------------------------

寄存器保存在运行时栈中,且这个栈也是一个能随机访问的数组,这样能快速访问寄存器。 常量和上值(upvalue)2也保存在数组中,因此访问它们也很快。 全局表是一个传统的Lua table,它经过strings来快速访问,而且strings已经预计算出了它们的hash值。spa

Lua虚拟机中指令的长度为32位,划分为3~4个域。其中prototype

    1. 指令的OP域占6位,即最多有64条指令。
    2. A域占8位。
    3. B和C域能够分别占9位,或组合成18位的Bx域(unsigned)或sBx域(signed)。

大部分指令使用三地址格式,A指向保存结果的寄存器,B和C指向操做操做目标,即寄存器或常量。 使用这种形式,一些Lua中典型的操做可以译成一条指令。 如a = a + 1能够被译成ADD x x y,其中x表明寄存器保存的变量,y表明常量1。 a = b.f能够被译成GETTABLE x y z,其中x表明a的寄存器,y表明b的寄存器,z表明常量f的索引。code

转移指令使用4地址格式时会有一些困难,由于这将限制偏移的长度在256内(9-bit field)。 Lua的解决方法是,当测试指令(test instruction)判断为否时,跳过下一条转移指令;不然下一条指令是一个正常跳转指令,使用18位的偏移。 这也就是说测试指令后总跟着一条转移指令,那么解释器能够把这两条指令一块儿执行。对象

对于函数调用,Lua使用一种寄存器窗口(register window)的方式。它使用第一个没有使用的寄存器来连续存储参数。 当执行函数调用时,这些寄存器称为调用的函数的activation recode的一部分,使得函数像访问局部变量同样访问这些参数。 当函数返回时,这些寄存器加入到调用函数的上下文的activation recode里。

Lua为函数调用使用两个平行的栈。 一个栈保存着每一个激活函数的信息项。这条信息保存着调用函数、返回地址和指向函数activation record的索引。 另外一条栈是一条保存了activation records的数组。每一条activation record保存了函数全部的临时变量。 实际上,咱们能够把第二条栈中的每一个项当作第一个栈中的交互项的变量大小部分。


1. R. Ierusalimschy, L. H. de Figueiredo, W. Celes, The implementation of Lua 5.0, Journal of Universal Computer Science 11 #7 (2005) 1159–1176. [jucs · slides]

2. 内部嵌套函数使用的外部函数的局部变量在函数内部称之为上值(upvalue),或者外局部变量(external local variable)。

相关文章
相关标签/搜索