前言

这一节我们来深入解析与调用相关的指令,这些指令是:

  • OP_CALL 调用
  • OP_TAILCALL 尾调用
  • OP_VARARG 可变参数
  • OP_RETURN 返回

解析这些指令的过程中,最重要的是时刻跟踪栈的变化情况。

简单调用

  • OP_CALL 的语法是:R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1))

    • R(A)为要调用的函数本身
    • 如果B=1,表示没有参数,如果B>1,表示有B-1个参数,这些参数从寄存器R(A+1)开始。
    • 函数调用完之后,如果C=1,表示没有返回值,如果C>1,表示有C-1个返回值,这些返回值会存到寄存器R(A)和它后面。从这里可以看出,本来存函数的R(A)最后被替换为返回值。
  • OP_RETURN 函数返回指令,语法是:return R(A), ... ,R(A+B-2)
    • 如果B=1,表示没有返回值,如果B>1,表示有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表示base+1。

下面是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有另一种情况,B=0时,参数从R(A+1)一直到栈顶;C=0时,返回值从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|sum=4>
10      [13]    GETTABUP        2 2 -4  ;       -- <main|4|sum=4|print>
11      [13]    MOVE            3 1             -- <main|4|sum=4|print|sum=4>
12      [13]    CALL            2 2 1           -- <main|4|sum=4>
13      [14]    RETURN          0 1             -- <>

第5行CALL 1 3 0,C=0,表示返回结果从R(1)一直到栈顶;第6行CALL 0 0 2,B=0,表示add的参数从R(1)一直栈顶。

从VM代码看,调用div时,新的CallInfo的nresults等于-1,这表示函数的返回值为LUA_MULTRET;在div返回时,moveresults判断如果CallInfo的nresults等于-1,就返回函数的实际返回值,并且将L->top调整到n个返回值之后。紧接着下一条指令是对add的调用,就能根据L->top得到实际的参数。

可变参数

可变参数的指令是OP_VARARG:

  • OP_VARARG 语法是R(A), R(A+1), ..., R(A+B-1) = vararg,如果B>0,表示从可变参数接受B-1个参数,如果可变参数不满B-1个,则后面自动填nil。如果B=0,则将传进函数的所有可变参数赋值给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, B=0,表示将所有可变参数保存到R(1)和后面的寄存器,然后设置好L->top

第3行调用print,B=0,所以参数就是从R(1)一直到L->top

第4行返回可变参数,B=0,表示将所有可变参数保存到R(0)和后面的寄存器,然后设置好L->top

第5行函数返回,B=0,表示将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行的C=0,表示返回值为从R(2)一直到栈顶。第5行的B=0,表示从R(2)一直到栈顶作为返回值。结合上面的例子,能得到这种情况一般都是将上一个函数的返回值作为当前函数的返回值。

lua 调用文件中的函数调用_深入Lua:调用相关的指令相关推荐

  1. lua 调用文件中的函数调用_四、C++获得Lua的变量和Table的值

    上两篇文章都已经把Lua和C++函数的调用讲完了,这篇开始讲变量和Table的调用. 这篇文章主要是讲C++怎么调用获得Lua中的变量和Table的值,并且把lua中的值打印出来. 一.直接上代码: ...

  2. 教你如何用 Lua 操作文件中的数据

    了解 Lua 如何处理数据的读写. 有些数据是临时的,存储在 RAM 中,只有在应用运行时才有意义.但有些数据是要持久的,存储在硬盘上供以后使用.当你编程时,无论是简单的脚本还是复杂的工具套件,通常都 ...

  3. python中eval函数调用_如何从Python exec()/eval()调用中获取结果?

    我想用Python编写一个工具,通过为每个模拟运行创建一个文件夹和一个带有一些运行特定参数的配置文件来准备模拟研究.在study/ study.conf run1 run.conf run2 run. ...

  4. 模块导入---如何在一个文件中导入其它模块,来调用它的变量、函数等,以节省代码量...

    ***我们在开发过程中,经常会遇到一些公共的变量或者方法,如果在每个文件中声明定义,则会造成工作量重复.我们可以将我们会重复使用的方法和变量放在一个文件中,要用的时候去调用就可以了. #简例: 1.新 ...

  5. 此异常最初是在此调用堆栈中引发的:_【8】进大厂必须掌握的面试题Java面试异常和线程...

    点击上方"全栈程序员社区",星标公众号 重磅干货,第一时间送达 Q1.错误和异常有什么区别? 错误是在运行时发生的不可恢复的情况.如OutOfMemory错误.这些JVM错误无法在 ...

  6. python如何调用文件进行换位加密_文件加密---使用换位加密和解密方法加密文件...

    #Transposition Cipher Encrypt/Decrypt File#http://inventwithpython.com/hacking (BSD Licensed) #time模 ...

  7. matlab .m 返回值,MATLAB一个M文件的function返回值怎么在另一个M文件中的函数调用这个返回值?...

    答:1.首先要在笔记本电脑上安装好指定版本的matlab软件并双击打开. 2.然后双击matlab图标打开其主页面,可以看到这个软件的按钮都是全英文单词的. 3.那么创建m文件的方法就是鼠标移动到Ne ...

  8. python 如何批量提取文件中的字符_如何用Python批量提取PDF文本内容?

    本文为你展示,如何用Python把许多PDF文件的文本内容批量提取出来,并且整理存储到数据框中,以便于后续的数据分析. 问题 最近,读者们在后台的留言,愈发五花八门了. 写了几篇关于自然语言处理的文章 ...

  9. c++读取txt文件中的数字_在Python中读取包中的数据文件的三种方式

    我们知道,写Python代码的时候,如果一个包(package)里面的一个模块要导入另一个模块,那么我们可以使用相对导入: 假设当前代码结构如下图所示: 其中test_1是一个包,在util.py里面 ...

最新文章

  1. python自动开发之(算法)第二十七天
  2. 外服封号_外服大主播Diss原神:因为吐槽氪金体验差,米哈游把我号封了
  3. 2018.12.08 codeforces 946D. Timetable(背包)
  4. 特斯拉中国月销破5万台创纪录:每46秒就能卖出一辆车
  5. python 学习之路1-如何入门
  6. 如何快速批量新建文本文档(txt)?
  7. Android TTS实现简单阅读器(一)
  8. 适合matlab的编程字体“YAHEI CONSOLAS HYBRID”-下载+安装
  9. Python爬取58同城租房数据,完美解决字体加密
  10. VB.net 图片刷新闪烁 解决方案
  11. 2021年最全圣诞节攻略,外贸人看这一篇就够了
  12. Chrome浏览器视频网站全屏黑屏解决方法
  13. 区块链数字签名、验签,以及椭圆曲线算法JS库—elliptic的使用
  14. 查看Eclipse32位还是64位,查看JDK是32位还是64位
  15. python电影情感评论分析_python--电影评论文本情感分类
  16. 异常(父类对象ani instanceof是不是 子类Cat 的实例)
  17. 【刷题】华为笔试面试机考 [HJ17] - 坐标移动
  18. SQL Server集群
  19. 基于Vue+ElementUI实现的数据分析后台管理系统
  20. 中国大学MOOCPython语言程序设计(北京理工大学)第7-9周学习笔记和课后练习

热门文章

  1. POJ 1422 Air Raid
  2. .net宿舍管理系统 mysql_基于.NET CORE的精美后台管理系统-RuoYi C#版
  3. linux阻塞原语,Linux中的内存原语是什么?
  4. 重温Elasticsearch
  5. cesium进行模型高度测量的代码片段
  6. 跳出内层循环 使用 for of 代替 map
  7. 【引用】Json 定义与操作
  8. Android方法的概括,android中的Filter接口简介
  9. linux php cgi.sock,nginx中unix:/tmp/php-cgi.sock错误解决解决
  10. html按钮不可选中,如何使HTML文本不可选择