lua虚拟机之字节码篇

 

大部分脚本语言的虚拟机实现方式都采用了基于栈的模式,比如java等。栈是一种常见的抽象结构,描述一种先进后出...



大部分脚本语言的虚拟机实现方式都采用了基于栈的模式,比如java等。栈是一种常见的抽象结构,描述一种先进后出的数据组织方式,基于栈的模式,即意味任何时刻,指令总是围绕着栈顶做运算,MOVE或者GET实际的的处理只是把局部变量压入栈顶,比较操作,也只是比较栈顶与栈顶的下一个元素,并且把比较的结果又重新压入栈顶等等。到了指令系统这一层(也就相当于汇编语言层),无法再像各种高级语言一样,有各种丰富的特性,比如局部变量、全局变量、各种各样的数据结构以及对应的操作,到了这一层,不再有所谓的变量,有的只是栈的一个索引,好在大部分情况下,我们都不用关心变量到底是在栈的哪个位置,语言解析器早帮我们做好了一切。

按照PUC-Rio的说法,在Lua4.0的时候,虚拟机实现也采用了基于栈的模式,如下图所示



而到了5.0时,PUC-Rio已经不再使用这种方式,而采用另一种基于寄存器的模式实现,他认为这种基于栈的模式的指令,层次过低,功能太过简单,解析时,将产生大量的指令,而基于寄存器模式的指令系统,可以支持更加强大的基础指令,复杂的功能,只需较少的指令就能完成。

可能会产生一些误解,这里的寄存器,并不是指cpu寄存器,而是lua虚拟机所使用的一组寄存器(register),这一组寄存器并没有固定的地址,只有在函数被执行时,在栈上指定寄存器,比如通常lua函数的第一个参数,就被命名为R0,依此类推R1...Rn,最多允许256个寄存器,这也意味着单个函数内,变量的个数无法超过这个值。

lua虚拟机指令,采用固定4字节长度,5.1的版本支持三种类型的指令,分别是iABC, iABx, iAsBx(5.2之后的版本加入了iAx格式),几种格式的字节码分别对应如下:





其中A一定是指代寄存器id(lua5.2以后稍有不同),占1个字节,这也是为什么最多只有256个寄存器,而B或C既可以是寄存器,也可以是常量,取决于op,有这样一段代码:

a = a+1

a = 1/a

a = t.x

t.x = a

假设a在寄存器0上,t在寄存器1上,常数1在常量表中的索引为3(索引从0开始),而字符x在常量表中的索引为4,那么其对应的lua汇编代码可能如下图所示:



不难看出,第一个常量为256,刚好比最大寄存器id大,B或C都有9个位,可以存的最大值为512,因此,对于单个函数而言,常量表里的常量个数超过256的部分,是无法用iABC这种指令模式访问的到的。

当然,稍大一点的代码量,全局变量很轻易就会超过这些上限,没关系,还有iABx以及iAsBx这两种格式,Bx与sBx代表无符号和有符号,全局变量或常量,其索引都不可能会是负数,所以都使用iABx格式,只有跳转指令才会需要sBx,既可能向前跳转,也可能向后跳转。由于只剩下了18个位来保存负数,c/c++自带的int32、int16均不适合表示18位的有符号整数,在虚拟机层又实现一遍补码方式有些得不偿失,于是有符号数采用了另一种更为简单的处理方式,把0到2^18之前的数分成相等的两部分,每个部分的值为max,此时当sBx=0时,其实际所代表的值为-max


    关注 高级技术宅


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册