6 Loading Constants
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
装载和移动几乎是所有的处理器和虚拟机指令集的起点,所以我们将以原始的装载和移动开始:
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
MOVE A B R(A) := R(B)
将寄存器R(B)中的内容拷贝到寄存器R(A)中。如果R(B)保存着表,函数或用户数据,那么对象的引用将会被。MOVE经常被用来向下一条指令要使用的空间中转移数据。
对MOVE进行编码还有第二个目的 – 它也被用在创建闭包当中,通常出现在CLOSURE指令后面;查看CLOSURE获得更多信息。 |
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
MOVE最直接的使用是将一个局部变量赋值给另一个局部变量。
>local a,b = 10; b = a
; function [0] definition (level 1)
; 0 upvalues, 0 params, 2 stacks
.function 0 0 2 2
.local "a" ; 0
.local "b" ; 1
.const 10 ; 0
[1] loadk 0 0 ; 10
[2] loadnil 1 1
[3] move 1 0
[4] return 0 1
; end of function
行[3]将局部变量a (寄存器0)中的值赋值(拷贝)给局部变量b (寄存器1)。你不会看到MOVE用在算术表达式中,因为算术操作不需要它。所有的算术操作都具有2个或3个操作数的形式;对于操作数R(A),,R(B)和R(C)来说全部的局部堆栈都是可以访问的,所以不需要额外的MOVE指令。
你会看到MOVE指令的其他地方:
• 为了函数调用将参数放入特定空间时
• 为了某些指令将值放入特定的空间时,堆栈的顺序对于这些指令来说很重要,例如GETTABLE,SETTABLE和CONCAT。
• 当函数调用结束后把返回结果拷贝到局部变量中。
• CLOSURE指令后面(在第14章讨论)
3个基本的指令用于将常量装载到局部变量中。其他的用于读写管局变量,upvalues和表的指令在接下来的章节讨论。第一个常量装载指令是LOADNIL:
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
LOADNIL A B R(A) := ... := R(B) := nil
将从R(A)到R(B)这个范围内的寄存器设置成nil。如果要设置一个寄存器,使R(A) = R(B)。当两个或更多的寄存器需要设置成nil值,仅需要一条 LOADNIL指令。 |
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
LOADNIL使用操作数A和B作为寄存器位置的范围。上一页中的MOVE的例子中展示了LOADNIL被用来将一个寄存器设置成nil。
>local a,b,c,d,e = nil,nil,0
; function [0] definition (level 1)
; 0 upvalues, 0 params, 5 stacks
.function 0 0 2 5
.local "a" ; 0
.local "b" ; 1
.local "c" ; 2
.local "d" ; 3
.local "e" ; 4
.const 0 ; 0
[1] loadk 2 0 ; 0
[2] loadnil 3 4
[3] return 0 1
; end of function
行[2] nils位于d和e。局部变量a和b不需要LOADNIL,因为指令早已被优化过了。局部变量c被显示的初始化为0。对于局部变量a和b的LOADNIL总是被优化,因为在执行函数之前Lua虚拟机总是设置将所有的局部变量设置成nil。优化规则是很简单的;如果没有其他指令产生,作为第一条指令的LOADNIL总是被优化掉。
在例子中虽然行[2]的LOADNIL是多余的,但是它仍然作为不在行[1]的LOADNIL指令生成了。理想的情况下,在做任何事情之前,应该将所有的要初始化为nil的局部变量都放在函数的顶端。像上面所述的情形,我们应该重新排列局部变量以获得优化规则带来的好处:
>local a,b,d,e local c=0
; function [0] definition (level 1)
; 0 upvalues, 0 params, 5 stacks
.function 0 0 2 5
.local "a" ; 0
.local "b" ; 1
.local "d" ; 2
.local "e" ; 3
.local "c" ; 4
.const 0 ; 0
[1] loadk 4 0 ; 0
[2] return 0 1
; end of function
现在,我们节省了一条LOADNIL指令。在函数的其他部分,一个将nil给局部变量的赋值将理所当然的要求一条LOADNIL指令。
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
LOADK A Bx R(A) := Kst(Bx)
将计数为Bx的常量装载到寄存器R(A)。常量通常是数字或字符串。每一个函数都有自己的常量列表或池。 |
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
LOADK将常量列表中的常量装载到寄存器或局部变量中。常量从0开始索引。许多指令,如算术指令,可以不需要LOADK就能使用常量列表。常量集中在列表中,重复的被删除。这个列表可以保存nils,布尔值,数值或字符串。
>local a,b,c,d = 3,"foo",3,"foo"
; function [0] definition (level 1)
; 0 upvalues, 0 params, 4 stacks
.function 0 0 2 4
.local "a" ; 0
.local "b" ; 1
.local "c" ; 2
.local "d" ; 3
.const 3 ; 0
.const "foo" ; 1
[1] loadk 0 0 ; 3
[2] loadk 1 1 ; "foo"
[3] loadk 2 0 ; 3
[4] loadk 3 1 ; "foo"
[5] return 0 1
; end of function
常量3和常量字符串“foo”在源代码片段中都出现了两次,但是在常量列表中,每一个常量只有单独一个位置。常量列表也保存着全局变量的名字,GETGLOBAL和SETGLOBAL隐含的执行了LOADK操作,为了在全局表中查寻全局变量之前得到全局变量的名字。
最后一个常量装载指令是LOADBOOL,则个指令是为了设置布尔值,同时它还有一些其他的功能。
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
LOADBOOL A B C R(A) := (Bool)B; if (C) PC++
将布尔值(true or false)装载到寄存器R(A)。true通常被编码成整数1,false通常是0。如果C是非0值,那么下一条指令将会被跳过(当一条赋值语句中的表达式是关系操作的时候就会用到它。例如M = K>5.) 在域B中你可以使用任何非0值作为布尔值true,但是在Lua中你不能把布尔值当作数字来用,最好将1和true联系在一起。 |
rel="File-List" href="file:///C:%5CDOCUME%7E1%5CWangBo%5CLOCALS%7E1%5CTemp%5Cmsohtml1%5C01%5Cclip_filelist.xml">
LOADBOOL被用来向寄存器中装载一个布尔值。也被用在希望产生布尔结果的地方,因为条件测试指令不产生布尔结果 – 它可以代替条件跳转。为了实现跳转代码操作数C被用来跳过下条指令(通过将PC加1)。在简单的布尔赋值中,C总是0。
>local a,b = true,false
; function [0] definition (level 1)
; 0 upvalues, 0 params, 2 stacks
.function 0 0 2 2
.local "a" ; 0
.local "b" ; 1
[1] loadbool 0 1 0 ; true
[2] loadbool 1 0 0 ; false
[3] return 0 1
; end of function
这个例子很明显:行[1]把true赋值给局部变量a (寄存器0)行[2]把false赋值给局部变量b (寄存器1)。在上面的两种情况中,C都是0,所以PC没有增加,下一条指令没有被跳过。
>local a = 5 > 2
; function [0] definition (level 1)
; 0 upvalues, 0 params, 2 stacks
.function 0 0 2 2
.local "a" ; 0
.const 5 ; 0
.const 2 ; 1
[1] lt 1 257 256 ; 2 5, to [3] if false
[2] jmp 1 ; to [4]
[3] loadbool 0 0 1 ; false, to [5]
[4] loadbool 0 1 0 ; true
[5] return 0 1
; end of function
在这个例子中的表达式给出了一个布尔结果并把它赋值给了变量。注意Lua并没有把表达式优化成true值;Lua 5.1并没有实现编译时对关系运算的常量求值,但是它实现了简单的算术运算常量求值。
关系操作符LT(后面会更详细的介绍)不能给出一个布尔值但是实现了一个条件跳转,LOADBOOL使用它的C操作数在行[3]处实现了一个无条件跳转 – 节省了一条指令并且把事情做的更加整洁了。上述这些情况的原因时对于if...then块的指令集做了简单的优化。本质上来说,local a = 5 > 2是一下面的方式执行的:
local a
if 2 < 5 then
a = true
else
a = false
end
在反汇编列表中,LT测试2 < 5,求值的结果为true,并且不执行条件跳转。行[2]跳过的错误结果的路径,在行[4]局部变量a(寄存器0)通过LOADBOOL指令,被赋值为布尔值true。如果2和5颠倒一下,那么接下来的应该是行[3],设置false,然后结果为true的路径(行[4])会被跳过,因为LOADBOOL有一个非0的域C。
true结果路径会是这样(括号中为附加的注释):
[1] lt 1 257 256 ; 2 5, to [3] if false (if 2 < 5)
[2] jmp 1 ; to [4]
[4] loadbool 0 1 0 ; true (a = true)
[5] return 0 1
false结果路径(在本例中它永远不会被执行)会是这样:
[1] lt 1 257 256 ; 2 5, to [3] if false (if 2 < 5)
[3] loadbool 0 0 1 ; false, to [5] (a = false)
[5] return 0 1
true结果的路径看起来更长,其实不是这样的,这取决于虚拟机现实的方法。在关系运算和逻辑运算指令的章节将会更深入的讨论。