lua函数执行和虚拟机指令

Stack based vs Register based VM

可直接参考 Stack based vs Register based VM

lua函数调用

先看一下lua函数的结构:

/*
** Function Prototypes
*/
typedef struct Proto {CommonHeader;TValue *k; /* constants used by the function */Instruction *code;struct Proto **p; /* functions defined inside the function */int *lineinfo; /* map from opcodes to source lines */struct LocVar *locvars; /* information about local variables */TString **upvalues; /* upvalue names */TString *source;int sizeupvalues;int sizek; /* size of `k' */int sizecode;int sizelineinfo;int sizep; /* size of `p' */int sizelocvars;int linedefined;int lastlinedefined;GCObject *gclist;lu_byte nups; /* number of upvalues */lu_byte numparams;lu_byte is_vararg;lu_byte maxstacksize;
} Proto;/*
** Upvalues
*/
typedef struct UpVal {CommonHeader;TValue *v; /* points to stack or to its own value */union {TValue value; /* the value (when closed) */struct { /* double linked list (when open) */struct UpVal *prev;struct UpVal *next;} l;} u;
} UpVal;#define ClosureHeader \CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \struct Table *envtypedef struct {ClosureHeader;Proto *p;UpVal *upvals[1];
} LClosure;

对于lua函数,luaD_precall已处理好一些前置操作,比如参数处理、增加调用栈等,然后调用luaV_execute去执行lua函数的每条指令。
特别的看一下数据栈的处理,在编译时已确定每个lua函数执行过程中数据栈的最大大小,将ci->top/L->top直接设为最大值,[L->base, L->top)就做为lua指令的“寄存器空间”使用,访问寄存器就是以下标访问base数组。

int luaD_precall (lua_State *L, StkId func, int nresults) {...ci->top = L->base + p->maxstacksize;lua_assert(ci->top <= L->stack_last);L->savedpc = p->code; /* starting point */ci->tailcalls = 0;ci->nresults = nresults;for (st = L->top; st < ci->top; st++)setnilvalue(st);L->top = ci->top;...

指令格式

/*
** type for virtual-machine instructions
** must be an unsigned with (at least) 4 bytes
*/
typedef lu_int32 Instruction;/*
We assume that instructions are unsigned numbers.
All instructions have an opcode in the first 6 bits.
Instructions can have the following fields:`A' : 8 bits`B' : 9 bits`C' : 9 bits`Bx' : 18 bits (`B' and `C' together)`sBx' : signed BxMSB  B C A op  LSB9 9 8 6   bits
*/

根据指令的不同,参数可以表示寄存器的索引,可以表示常量的索引(Proto的TValue *k;数组),可以根据最高位是否是1决定表示寄存器还是常量的索引,还可以是上值的索引(UpVal *upvals[1];),或是其他含义。
寄存器空间不会很大,但常量数组可能会很大,而B、C的大小有限,如果B或C需要引用的常量地址超出了表示范围,在指令生成阶段,则首先会生成指令将常量装载到寄存器,然后再将B或C改为使用该寄存器地址。

常量、上值可以是lua指令的数据来源,寄存器是临时变量,都算是内部数据,那如何与“外部”进行数据交互呢?比如简单的function test() b=a end,如何读取全局变量a,又赋值给全局变量b。
是通过struct Table *env,多数情况这个env表就是_G,可参考 lua源码学习:解释器和内嵌库 load库。
将上面的test函数放到test.lua中,用luac -l -p test.lua看一下test函数的字节码:

0 params, 2 slots, 0 upvalues, 0 locals, 2 constants, 0 functions1       [1]     GETGLOBAL       0 -2    ; a2       [1]     SETGLOBAL       0 -1    ; b3       [1]     RETURN          0 1
constants (2) for 0x563c4dbf3a40:1       "b"2       "a"

对比着源代码:

  // i是当前指令,ra是A表示的寄存器的位置case OP_GETGLOBAL: {TValue g;sethvalue(L, &g, cl->env);lua_assert(ttisstring(KBx(i)));Protect(luaV_gettable(L, &g, KBx(i), ra));continue;}case OP_SETGLOBAL: {TValue g;sethvalue(L, &g, cl->env);lua_assert(ttisstring(KBx(i)));Protect(luaV_settable(L, &g, KBx(i), ra));continue;}

GETGLOBAL,从指令中取出Bx来,将其做为常量索引,取得一个TValue,这个TValue就是TString “a”,然后从_G中取得名为a的变量的值,放到寄存器上。
SETGLOBAL,ra位置存放的就是getglobal中变量a的值,将其值赋给_G中名为b的变量。

全局变量名会放到常量表中,如果是局部变量,则只是对应寄存器位置,局部变量的名字除了提供debug信息外,没有其他作用。比如function test() local c=1; b=c end的字节码:

0 params, 2 slots, 0 upvalues, 2 locals, 2 constants, 0 functions1       [1]     LOADK           0 0    ; 12       [1]     MOVE            1 0 03       [1]     SETGLOBAL       0 1    ; d4       [1]     RETURN          0 1 0
constants (2) for 0x55726251aa40:1       12       "d"
locals (2) for 0x55726251aa40:1       c       2       42       b       3       4

LOADK将常量1加载到寄存器0上,MOVE将寄存器0拷贝到寄存器1上,SETGLOBAL将寄存器1的值赋给全局变量b。

关系指令

if a==b thenprint("==")
elseprint("!=")
end

字节码:

        1       [1]     GETGLOBAL       0 -1    ; a2       [1]     GETGLOBAL       1 -2    ; b3       [1]     EQ              0 0 14       [1]     JMP             4       ; to 95       [2]     GETGLOBAL       0 -3    ; print6       [2]     LOADK           1 -4    ; "=="7       [2]     CALL            0 2 18       [2]     JMP             3       ; to 129       [4]     GETGLOBAL       0 -3    ; print10      [4]     LOADK           1 -5    ; "!="11      [4]     CALL            0 2 1

源代码:

  // i是当前指令,*pc是下条指令case OP_EQ: {TValue *rb = RKB(i);TValue *rc = RKC(i);Protect(if (luaV_equalval(L, rb, rc) == GETARG_A(i)){dojump(L, pc, GETARG_sBx(*pc)); // if条件未满足,跳过true的代码段})pc++;continue;}

A的值是0,如果rb不等于rc,pc加上第4条指令中的4,再加1后跳转到第9条指令,就是lua中if不满足的代码;如果rb等于rc,pc加1跳过第4条指令,去执行if满足的代码。
流程大概是这样的:

         CMP-------|         |----TRUE CODE     |
|                  |
|                  |
|    FALSE CODE----
|        |
|        |----OTHER CODE

另一种情况:

return a==b

字节码:

        1       [1]     GETGLOBAL       0 -1    ; a2       [1]     GETGLOBAL       1 -2    ; b3       [1]     EQ              1 0 14       [1]     JMP             1       ; to 65       [1]     LOADBOOL        0 0 16       [1]     LOADBOOL        0 1 07       [1]     RETURN          0 2

源代码:

  case OP_LOADBOOL: {setbvalue(ra, GETARG_B(i));if (GETARG_C(i)) pc++; /* skip next instruction (if C) */continue;}

A的值是1,相等的情况,跳转到第6条指令,将ra设置为1;不等的情况,跳转到第5条指令,将ra设置为0,此时c为1,会跳过第6条指令。

OP_LT(小于),OP_LE(小于等于),OP_TEST也是类似。

创建和初始化表

创建表时用NEWTABLE创建,哈希部分用SETTABLE初始化,数组部分用SETLIST初始化,比如t={1,2,3}的字节码是:

0+ params, 4 slots, 0 upvalues, 0 locals, 4 constants, 0 functions1       [1]     NEWTABLE        0 3 02       [1]     LOADK           1 1    ; 13       [1]     LOADK           2 2    ; 24       [1]     LOADK           3 3    ; 35       [1]     SETLIST         0 3 1   ; 16       [1]     SETGLOBAL       0 0    ; t
constants (4) for 0x55987156f9c0:1       "t"2       13       24       3

现将3个常数放到寄存器,再用SETLIST设置到表中。如果要创建的表包含很多数组元素,将这些元素放到寄存器时,可能需要很大的寄存器范围。SETLIST实际是分批设置的,每次设置固定数量的元素。

case OP_SETLIST: {int n = GETARG_B(i);int c = GETARG_C(i);int last;Table *h;if (n == 0) {n = cast_int(L->top - ra) - 1;L->top = L->ci->top;}if (c == 0) c = cast_int(*pc++);if (!ttistable(ra)) break;h = hvalue(ra);last = ((c-1)*LFIELDS_PER_FLUSH) + n;if (last > h->sizearray) /* needs more space? */luaH_resizearray(L, h, last); /* pre-alloc it at once */for (; n > 0; n--) {TValue *val = ra+n;setobj2t(L, luaH_setnum(L, h, last--), val);luaC_barriert(L, h, val);}continue;
}

以上代码中c就是批次,n是当前批次元素数目。
如果数据量非常大,导致批次超出了C的表示范围,那么C会被设置成0,将SETLIST指令后的指令用来存储批次。
如果使用能产生多个返回值的表达式(… 和 函数调用)初始化表数组项,如果这个表达式不是表构造的最后一项,那么只有第一个值会被使用,其他都会被丢弃;如果是最后一项,那么SETLIST中的B会被设置为0。例如:

function getlist()return 1,2,3
end
a={getlist()}
function setlist(n,...)b={...}
end
setlist(4,5,6,7)

closure

参考 upval

参考

探索Lua5.2内部实现

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/14535.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络之应用层知识点总结

6.1 网络应用模型 &#xff08;1&#xff09;应用层概述 &#xff08;2&#xff09;网络应用模型的介绍 客户/服务器&#xff08;C/S&#xff09;模型 P2P模型 6.2 域名解析系统DNS &#xff08;1&#xff09;DNS系统介绍 &#xff08;2&#xff09;域名 &#xff08;3&#…

面试字节大模型算法实习岗,感觉有点崩溃。。。

节前&#xff0c;我们组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、今年参加社招和校招面试的同学。 针对大模型技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备面试攻略、面试常考点等热门话题进行了深入的讨论。 总结链接…

学习平台|基于Springboot+vue的学习平台系统的设计与实现(源码+数据库+文档)

学习平台系统 目录 基于Springboot&#xff0b;vue的学习平台系统的设计与实现 一、前言 二、系统设计 三、系统功能设计 1系统功能模块 2管理员功能模块 3学生功能模块 4教师功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八…

不小心丢失mfc140u.dll文件怎么办?mfc140u.dll丢失的解决办法

当您发现mfc140u.dll文件不见了或者受损&#xff0c;别担心&#xff0c;我们可以一起解决这个问题&#xff01;首先&#xff0c;您可能会注意到一个小提示&#xff0c;当您尝试打开某些程序时&#xff0c;屏幕上会跳出一个消息说“找不到mfc140u.dll”或者“mfc140u.dll文件缺失…

解决docker中container运行闪退终止的问题

在运行bindmount-test时&#xff0c;点击完运行按钮后闪退结束运行。 第一步查看log日志&#xff1a; 2024-05-18 23:46:18 Error: Cannot find module /app/nodemon 2024-05-18 23:46:18 at Function.Module._resolveFilename (internal/modules/cjs/loader.js:668:15) …

2024-5-24 石群电路-15

2024-5-24&#xff0c;星期五&#xff0c;22:15&#xff0c;天气&#xff1a;晴&#xff0c;心情&#xff1a;晴。今天最后一天上班&#xff0c;终于要放返校假啦&#xff0c;开心&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;不过放假也不能耽误…

rust_使用条件变量和使用通道有什么区别_各有什么优缺点

使用条件变量&#xff08;Condvar&#xff09;和使用通道&#xff08;mpsc::channel&#xff09;在 Rust 中都有各自的用途和特点&#xff0c;选择哪种方式取决于具体的应用场景和需求。以下是两者的主要区别和各自的优缺点&#xff1a; 使用通道 (mpsc::channel) 优点 简单…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(八)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 15 节&#xff09; P15《14.ArkUI组件-状态管理state装饰器》 回到最初的 Hello World 案例&#xff0c;首先验证 如果删掉 State…

Pycharm导入所有安装包到requirements.txt文件中

Pycharm导入所有安装包到requirements.txt文件中 在pycharm的终端命令行输入下边的命令,但是必须确保位于项目的根目录下 pip freeze > requirements.txt

物理服务器介绍

物理服务器介绍 概述分类按服务器应用分类按服务器结构分类塔式服务器机架式服务器刀片式服务器机架式服务器与刀片式服务器的对比按处理器个数分类按处理器架构分类 主板概述工作原理物理结构技术参数 CPU概述工作原理指令集相关技术技术参数主流产品 内存概述类型相关技术技术…

C语言-atoi()库函数的模拟实现

文章目录 前言一、atoi()库函数的介绍及使用1.1 atoi()库函数介绍1.2 atoi()库函数使用 二、atoi()库函数的模拟实现2.1 函数设计2.2 函数实现思路2.3 具体实现2.4 测试 总结 前言 本篇文章介绍c语言中库函数atoi()的使用&#xff0c;以及模拟实现库函数。 一、atoi()库函数的…

Threes 特效 炫酷传送门HTML5动画特效

基于Three.js的HTML5 3D动画&#xff0c;这个动画模拟了游戏中的一个炫酷的3D场景&#xff0c;支持360度视角查看&#xff0c;也支持鼠标滚轮进行缩放。画面中主要展现了一个游戏中传送门的效果&#xff0c;同时还有路两边的围栏、灯笼、石头&#xff0c;以及星光闪闪的萤火虫&…

Python机器人动力学和细胞酶常微分方程

&#x1f4dc;常微分方程-用例 &#x1f4dc;Python物理量和化学量数值计算 | &#x1f4dc;Julia和Python蛛网图轨道图庞加莱截面曲面确定性非线性系统 | &#x1f4dc;Python和C数学物理计算分形热力学静电学和波动方程 | &#x1f4dc;C计算资本市场收益及成本分配数学方程…

note-网络是怎样连接的3 集线器、交换机和路由器

助记提要 双绞线抑制噪声的原理集线器的工作交换机的工作过程交换机维护MAC地址表的方式交换机特殊情况下的操作 3种全双工模式的特点自动协商的原理路由器组成路由表内容路由器工作过程路由器和交换机的区别地址转换的基本原理 3章 从网线到网络设备 探索集线器、交换机和路…

IO端口编址

统一编址 特点 独立编址 特点 内存地址分配 区别 应用 IO端口地址译码 硬件上的实现 示例1&#xff1a; 示例2&#xff1a; IO指令 软件上的实现 示例

2024最佳画图软件合集,操作简单无需下载!

随着数字时代的到来&#xff0c;绘画软件已经成为创造性表达和艺术创作不可或缺的工具。无论你是设计师、艺术家&#xff0c;还是只是对创作充满热情&#xff0c;2024年的绘画软件集都为你提供了各种各样的选择&#xff0c;这样你就可以在数字画布上释放你的想法。本文将重点推…

XV4001系列陀螺仪传感器广泛用于车载导航系统

随着汽车电子化趋势的加速&#xff0c;越来越多的汽车配备一系列先进的车载导航系统&#xff0c;这些导航系统功能的实现都依赖于精确的传感器数据(位置、车速、转向角度、车轮转速等)。传感器作为这些系统的核心组件&#xff0c;其准确性和可靠性直接影响到整个导航系统的性能…

大模型时代,掌握Event Stream技术提升Web响应速度

大模型时代,每天搜索都可能会用到一种或多种大模型,在大文本输出的时候,页面是一字一字,一段一段的慢慢输出出来的,这背后是如何实现的呢?我们以KIMI为例 先抓个请求 我们发现界面展示是一句话,但是接口返回的时候是一个字一个字的。 普通请求 多了Event Stream的处理 …

头歌OpenGauss数据库-I.复杂查询第3关:统计总成绩

本关任务:计算每个班的语文总成绩和数学总成绩,要求科目中低于60分的成绩不记录总成绩。 tb_score结构数据: namechinesemathsA8998B9989C5566D8866E5566F8899tb_class表结构数据: stunameclassnameAC1BC2CC3DC2EC1FC3--#请在此添加实现代码 --# # # # # # # # # # Begin #…

DDoS攻击的最新动态及市场趋势分析

随着数字化转型的加速和网络连接设备的增加&#xff0c;分布式拒绝服务(Distributed Denial of Service, DDoS)攻击已经成为全球网络安全领域的一大威胁。根据最新的市场研究报告&#xff0c;预计到2028年&#xff0c;DDoS防护软件市场的复合年增长率将达到14%以上&#xff0c;…