lua函数执行和虚拟机指令

news/2024/10/21 9:55:08/

Stack based vs Register based VM

可直接参考 Stack based vs Register based VM

lua_2">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。

关系指令

lua">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

另一种情况:

lua">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。例如:

lua">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.ppmy.cn/news/1464894.html

相关文章

Linux基础指令用户管理002

继Linux基础指令用户管理001我们讲述了创建用户和删除用户&#xff0c;我们讲一下如何设置用户密码以及修改用户信息。 操作系统 CentOS Stream 9 设置用户密码 我们使用passwd指令passwd name [rootlocalhost ~]# passwd wg 更改用户 wg 的密码 。 新的密码&#xff1a; …

反编译 Trino Dockerfile

文章目录 反编译 Trino Dockerfile反编译Dockerfile命令反编译后Dockerfile内容获取 Trino 启动脚本卸载 反编译 Trino Dockerfile 反编译Dockerfile命令 alias dfimage"docker run -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/laniksj/dfimage" d…

AWS安全性身份和合规性之Amazon Macie

Amazon Macie是一项数据安全和数据隐私服务&#xff0c;它利用机器学习&#xff08;ML&#xff09;和模式匹配来发现和保护敏感数据。可帮助客户发现、分类和保护其敏感数据&#xff0c;以及监控其数据存储库的安全性。 应用场景&#xff1a; 敏感数据发现 一家金融服务公司…

B/S架构+java语言+Mysqladr数 据 库ADR药物不良反应监测系统源码 ADR药物不良反应监测系统有哪些作用?

B/S架构&#xff0b;java语言&#xff0b;Mysqladr数 据 库ADR药物不良反应监测系统源码 ADR药物不良反应监测系统有哪些作用&#xff1f; 药物不良反应(ADR)是指在合格药物以正常用量和用法用于预防、诊断、治疗疾病或调节生理功能时所发生的意外的、与防治目的无关的、不利或…

42、Flink 关于窗口状态大小的考量

关于状态大小的考量 窗口可以被定义在很长的时间段上&#xff08;比如几天、几周或几个月&#xff09;并且积累下很大的状态&#xff0c;当估算窗口计算的储存需求时&#xff0c;注意如下&#xff1a; Flink 会为一个元素在它所属的每一个窗口中都创建一个副本。 因此&#x…

C 语言实例 - 循环输出26个字母

循环输出 26 个字母。 以下例子我们用变量 letter 来存储当前要输出的字母&#xff0c;然后&#xff0c;使用 for 循环来重复 26 次输出字母&#xff0c;并在每个字母后面加一个空格。 循环内部使用 printf 函数来输出 letter 变量的值&#xff0c;%c 是 printf 的格式控制符…

IT行业现状及未来发展趋势

IT行业现状及未来发展趋势 引言IT行业现状1. 云计算2. 大数据3. 人工智能4. 物联网5. 5G通信6. 区块链 IT行业未来发展趋势1. 边缘计算2. 人工智能与机器学习3. 量子计算4. 人工智能伦理与法规5. 可持续技术6. 数字孪生技术 结论 引言 随着技术的不断进步&#xff0c;IT行业已…

Facebook开户 | 如何检查公共主页的状态

想要了解你的Facebook公共主页的状态吗&#xff1f; Facebook公共主页是让广告主与粉丝互动、传播信息的绝佳平台&#xff0c;但是大家知道如何检查并维护自己的主页状态吗&#xff1f;别担心&#xff0c;Facebook提供了一系列简单易用的工具来帮助大家实现这一目标。 *Page Q…