企业模板网站建设优势分析,房地产网站建设分析,网站建设的价值是什么,网站建设补救方法前言这一节我们来深入解析与调用相关的指令#xff0c;这些指令是#xff1a;OP_CALL 调用OP_TAILCALL 尾调用OP_VARARG 可变参数OP_RETURN 返回解析这些指令的过程中#xff0c;最重要的是时刻跟踪栈的变化情况。简单调用OP_CALL 的语法是#xff1a;R(A), ... ,R(AC-2) :…前言这一节我们来深入解析与调用相关的指令这些指令是OP_CALL 调用OP_TAILCALL 尾调用OP_VARARG 可变参数OP_RETURN 返回解析这些指令的过程中最重要的是时刻跟踪栈的变化情况。简单调用 OP_CALL 的语法是R(A), ... ,R(AC-2) : R(A)(R(A1), ... ,R(AB-1))。R(A)为要调用的函数本身如果B1表示没有参数如果B1表示有B-1个参数这些参数从寄存器R(A1)开始。函数调用完之后如果C1表示没有返回值如果C1表示有C-1个返回值这些返回值会存到寄存器R(A)和它后面。从这里可以看出本来存函数的R(A)最后被替换为返回值。 OP_RETURN 函数返回指令语法是return R(A), ... ,R(AB-2) 如果B1表示没有返回值如果B1表示有B-1个返因值这些返回值就存在寄存器R(A)和它后面。这和OP_CALL是相呼应的。有了上面两个指令就可以进行函数调用先看下面的Lua代码1
2 local function add(a, b)
3 return a b
4 end
5
6 local function div(a, b)
7 return a // b, a % b
8 end
9
10 local function main()
11 local s add(10, 20)
12 local d, v div(s, 8)
13 print(d, v)
14 end
15
16 main()通过luac查看上面几个函数的操作码在每行操作码的最后我加上了栈的内容用括起来栈从函数对象开始函数对象的后面为base, 操作码中的数字大多相对于base比如0表示base自己1表示base1。下面是main函数1 [11] GETUPVAL 0 0 ; add -- main|add
2 [11] LOADK 1 -1 ; 10 -- main|add|10
3 [11] LOADK 2 -2 ; 20 -- main|add|10|20
4 [11] CALL 0 3 2 -- main|30
5 [12] GETUPVAL 1 1 ; div -- main|30|div
6 [12] MOVE 2 0 -- main|30|div|30
7 [12] LOADK 3 -3 ; 8 -- main|30|div|30|8
8 [12] CALL 1 3 3 -- main|30|3|6
9 [13] GETTABUP 3 2 -4 ; -- main|30|3|6|print
10 [13] MOVE 4 1 -- main|30|3|6|print|3
11 [13] MOVE 5 2 -- main|30|3|6|print|3|6
12 [13] CALL 3 3 1 -- main|30|3|6
13 [14] RETURN 0 1 -- 下面是add函数1 [3] ADD 2 0 1 -- add|10|20|30
2 [3] RETURN 2 2 -- 30下面是div函数1 [7] IDIV 2 0 1 -- div|30|8|3
2 [7] MOD 3 0 1 -- div|30|8|3|6
3 [7] RETURN 2 3 -- 3|6一开始main函数的调用信息和栈是这样的执行了add函数之后调用信息和栈变成这样add函数返回再调用div之后调用信息和栈变成这样函数返回结果作为函数参数把上面的Lua代码修改一下变成下面这样local function add(a, b)return a b
endlocal function div(a, b)return a // b, a % b
endlocal function main()local r add(div(10, 4))local s sum .. rprint(s)
endmain()变化之处是div函数的返回结果直接作为add的参数。这一改变使得VM不知道add会得到多少参数只能借助于div返回的栈顶。OP_CALL的B和C有另一种情况B0时参数从R(A1)一直到栈顶C0时返回值从R(A)一直到栈顶。借助这两种情况就能实现上面的逻辑main函数如下1 [11] GETUPVAL 0 0 ; add -- main|add
2 [11] GETUPVAL 1 1 ; div -- main|add|div
3 [11] LOADK 2 -1 ; 10 -- main|add|div|10
4 [11] LOADK 3 -2 ; 4 -- main|add|div|10|4
5 [11] CALL 1 3 0 -- main|add|2|2
6 [11] CALL 0 0 2 -- main|4
7 [12] LOADK 1 -3 ; -- main|4|sum
8 [12] MOVE 2 0 -- main|4|sum|4
9 [12] CONCAT 1 1 2 -- main|4|sum4
10 [13] GETTABUP 2 2 -4 ; -- main|4|sum4|print
11 [13] MOVE 3 1 -- main|4|sum4|print|sum4
12 [13] CALL 2 2 1 -- main|4|sum4
13 [14] RETURN 0 1 -- 第5行CALL 1 3 0C0表示返回结果从R(1)一直到栈顶第6行CALL 0 0 2B0表示add的参数从R(1)一直栈顶。从VM代码看调用div时新的CallInfo的nresults等于-1这表示函数的返回值为LUA_MULTRET在div返回时moveresults判断如果CallInfo的nresults等于-1就返回函数的实际返回值并且将L-top调整到n个返回值之后。紧接着下一条指令是对add的调用就能根据L-top得到实际的参数。可变参数可变参数的指令是OP_VARARGOP_VARARG 语法是R(A), R(A1), ..., R(AB-1) vararg如果B0表示从可变参数接受B-1个参数如果可变参数不满B-1个则后面自动填nil。如果B0则将传进函数的所有可变参数赋值给R(A)...。看下面代码local function main(...)print(...)return ...
endmain(10, ok, false)main函数的操作码如下1 [16] GETTABUP 0 0 -1 ; -- main|10|ok|false|print
2 [16] VARARG 1 0 -- main|10|ok|false|print|10|ok|false
3 [16] CALL 0 0 1 -- main|10|ok|false
4 [17] VARARG 0 0 -- main|10|ok|false|10|ok|false
5 [17] RETURN 0 0 -- 10|ok|false这里要注意一点是main函数的栈base是从可变参数之后开始的即false后面的寄存器为0。第2行VARARG 1 0 B0表示将所有可变参数保存到R(1)和后面的寄存器然后设置好L-top。第3行调用printB0所以参数就是从R(1)一直到L-top。第4行返回可变参数B0表示将所有可变参数保存到R(0)和后面的寄存器然后设置好L-top第5行函数返回B0表示将R(0)到L-top作为返回值。尾调用尾调用使用OP_TAILCALL指令它和OP_CALL的不同之处是这个指令不会生成新的CallInfo它会重用调用者的CallInfo因为尾调用只能在最后一条返回语句产生在那一刻调用者的CallInfo已经使用完毕所以可以重用这个CallInfo。尾调用只能是Lua函数具体可看lvm.c的OP_TAILCALL指令处理。其他方面和OP_CALL的含义基本一致下面是一个例子local function div(a, b)return a // b, a % b
endlocal function calc(a, b)return div(a, b)
endcalc(10, 3)calc里面的div调用就是一个尾调用calc的指令如下1 [7] GETUPVAL 2 0 ; div -- calc|10|3|div
2 [7] MOVE 3 0 -- calc|10|3|div|10
3 [7] MOVE 4 1 -- calc|10|3|div|10|3
4 [7] TAILCALL 2 3 0 -- calc|10|3|3|1
5 [7] RETURN 2 0 -- 3|1第4行的C0表示返回值为从R(2)一直到栈顶。第5行的B0表示从R(2)一直到栈顶作为返回值。结合上面的例子能得到这种情况一般都是将上一个函数的返回值作为当前函数的返回值。