探索Lua5.2内部实现:虚拟机指令(6)FUNCTION

以下内容转载自https://blog.csdn.net/yuanlin2008/article/details/8494636

name    args    desc
OP_CALL    A B C    A B C   R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))
CALL执行一个函数调用。寄存器A中存放函数对象,所有参数按顺序放置在A后面的寄存器中。B-1表示参数个数 。如果参数列表的最后一个表达式是变长的,则B会设置为0,表示使用A+1到当前栈顶作为参数。函数调用的返回值会按顺序存放在从寄存器A开始的C-1个寄存器中。如果C为0,表示返回值的个数由函数决定。

f();
    1    [1]    GETTABUP     0 0 -1    ; _ENV "f"
    2    [1]    CALL         0 1 1
    3    [1]    RETURN       0 1
第一行取得全局变量f的值,保存到寄存器0。第二行CALL调用寄存器0中的函数,参数和返回值都是0。

local t = {f(...)};
    1    [1]    NEWTABLE     0 0 0
    2    [1]    GETTABUP     1 0 -1    ; _ENV "f"
    3    [1]    VARARG       2 0
    4    [1]    CALL         1 0 0
    5    [1]    SETLIST      0 0 1    ; 1
    6    [1]    RETURN       0 1
第一行NETTABLE创建一个表放到寄存器0中。第二行获取全局变量f放到寄存器1中。第三行VARARG表示使用当前函数的变长参数列表。第四行的CALL调用寄存器1中的函数,B为0,代表参数是变长的。前面讲过,如果表的构造的最后一项是多返回值的表达式,则这个表会接受所有的返回值。这里就是这种情况,表的构造会接受函数所有的返回值,所以C也为0。

name    args    desc
OP_TAILCALL    A B C    A B C   return R(A)(R(A+1), ... ,R(A+B-1))
如果一个return statement只有一个函数调用表达式,这个函数调用指令CALL会被改为TAILCALL指令。TAILCALL不会为要调用的函数增加调用堆栈的深度,而是直接使用当前调用信息。ABC操作数与CALL的意思一样,不过C永远都是0。TAILCALL在执行过程中,只对lua closure进行tail call处理,对于c closure,其实与CALL没什么区别。

return f();
    1    [1]    GETTABUP     0 0 -1    ; _ENV "f"
    2    [1]    TAILCALL     0 1 0
    3    [1]    RETURN       0 0
    4    [1]    RETURN       0 1
上面如果f是一个lua closure,那么执行到第二行后,此函数就会返回了,不会执行到后面第三行的RETURN。如果f是一个c closure,那就和CALL一样调用这个函数,然后依赖第三行的RETURN返回。这就是为什么TAILCALL后面还会己跟着生成一个RETURN的原因。

name    args    desc
OP_RETURN    A B    return R(A), ... ,R(A+B-2)
RETURE将返回结果存放到寄存器A到寄存器A+B-2中。如果返回的为变长表达式,则B会被设置为0,表示将寄存器A到当前栈顶的所有值返回。

return 1;
    1    [1]    LOADK        0 -1    ; 1
    2    [1]    RETURN       0 2
    3    [1]    RETURN       0 1
RETURN只能从寄存器返回数据,所以第一行LOADK先将常量1装载道寄存器0,然后返回。

return ...;
    1    [1]    VARARG       0 0
    2    [1]    RETURN       0 0
    3    [1]    RETURN       0 1
因为返回的为变长表达式,B为0。

name    args    desc
OP_CLOSURE    A Bx    R(A) := closure(KPROTO[Bx])
CLOSURE为指定的函数prototype创建一个closure,并将这个closure保存到寄存器A中。Bx用来指定函数prototype的id。

local function f()
end
main <test.lua:0,0> (2 instructions at 0x102a016f0)
0+ params, 2 slots, 1 upvalue, 1 local, 0 constants, 1 function
    1    [2]    CLOSURE      0 0    ; 0x102a019b0
    2    [2]    RETURN       0 1
constants (0) for 0x102a016f0:
locals (1) for 0x102a016f0:
    0    f    2    3
upvalues (1) for 0x102a016f0:
    0    _ENV    1    0

function <test.lua:1,2> (1 instruction at 0x102a019b0)
0 params, 2 slots, 0 upvalues, 0 locals, 0 constants, 0 functions
    1    [2]    RETURN       0 1
constants (0) for 0x102a019b0:
locals (0) for 0x102a019b0:
upvalues (0) for 0x102a019b0:
上面生成了一个主函数和一个子函数,CLOSURE将为这个索引为0的子函数生成一个closure,并保存到寄存器0中。


name    args    desc
OP_VARARG    A B    R(A), R(A+1), ..., R(A+B-2) = vararg 
VARARG直接对应'...'运算符。VARARG拷贝B-1个参数到从A开始的寄存器中,如果不足,使用nil补充。如果B为0,表示拷贝实际的参数数量。

local a = ...;
    1    [1]    VARARG       0 2
    2    [1]    RETURN       0 1
上面第一行表示拷贝B-1个,也就是1个变长参数到寄存器0,也就是local a中。
f(...);
    1    [1]    GETTABUP     0 0 -1    ; _ENV "f"
    2    [1]    VARARG       1 0
    3    [1]    CALL         0 0 1
    4    [1]    RETURN       0 1
由于函数调用最后一个参数可以接受不定数量的参数,所以第二行生成的VARARG的B参数为0。


name    args    desc
OP_SELF    A B C    
R(A+1) := R(B); R(A) := R(B)[RK(C)]

SELF是专门为“:”运算符准备的指令。从寄存器B表示的table中,获取出C作为key的closure,存入寄存器A中,然后将table本身存入到寄存器A+1中,为接下来调用这个closure做准备。

a:b();
    1    [1]    GETTABUP     0 0 -1    ; _ENV "a"
    2    [1]    SELF         0 0 -2    ; "b"
    3    [1]    CALL         0 2 1
    4    [1]    RETURN       0 1
看一下与上面语法等价的表示方法生成的指令:
a.b(a);
    1    [1]    GETTABUP     0 0 -1    ; _ENV "a"
    2    [1]    GETTABLE     0 0 -2    ; "b"
    3    [1]    GETTABUP     1 0 -1    ; _ENV "a"
    4    [1]    CALL         0 2 1
    5    [1]    RETURN       0 1
比使用“:"操作符多使用了一个指令。所以,如果需要使用这种面向对象调用的语义时,应该尽量使用”:"。

--------------------- 
作者:yuanlin2008 
来源:CSDN 
原文:https://blog.csdn.net/yuanlin2008/article/details/8494636 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值