Golang编程语言知识介绍


  • 首页

  • todo

  • 思考

  • life

  • food

  • OS

  • lua

  • redis

  • Golang

  • C

  • TCP/IP

  • ebpf

  • p4

  • OpenVPN

  • IPSec

  • L2TP

  • DNS

  • distributed

  • web

  • OpenWRT

  • 运维

  • Git

  • 鸟哥的私房菜

  • IT杂谈

  • 投资

  • About Me

  • 友情链接

  • FTP

  • 搜索
close

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

时间: 2021-04-05   |   分类: lua     |   阅读: 1722 字 ~4分钟

原文链接

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

比使用“:“操作符多使用了一个指令。所以,如果需要使用这种面向对象调用的语义时,应该尽量使用”:"。

#lua#
探索Lua5.2内部实现:虚拟机指令(7) 关系和逻辑指令
探索Lua5.2内部实现:虚拟机指令(5)Arithmetic
shankusu2017@gmail.com

shankusu2017@gmail.com

日志
分类
标签
GitHub
© 2009 - 2025
粤ICP备2021068940号-1 粤公网安备44011302003059
0%