Lua5.3 VM 分析(一)字节码运行

来源:互联网 发布:网络春晚2016完整版 编辑:程序博客网 时间:2024/05/29 07:38

Lua5.3 VM 分析(一)字节码运行

luaV_execute 是Lua VM 执行一段字节码的入口。Lua VM 就是一个状态机,从当前调用栈上次运行点开始解释字节码指令,直到下一个 C 边界跳出点(可以是函数执行完毕,也可以是一次协程 yield 操作)。

Lua 函数部分

typedef struct CallInfo {  StkId func;  /* function index in the stack */  StkId top;  /* top for this function */  struct CallInfo *previous, *next;  /* dynamic call link */  union {    struct {  /* only for Lua functions */      StkId base;  /* base for this function */      const Instruction *savedpc;    } l;    struct {  /* only for C functions */      lua_KFunction k;  /* continuation in case of yields */      ptrdiff_t old_errfunc;      lua_KContext ctx;  /* context info. in case of yields */    } c;  } u;  ptrdiff_t extra;  short nresults;  /* expected number of results from this function */  lu_byte callstatus;} CallInfo;

savepc域 保存着指向当前指令的指针;base 域保存着当前函数的数据栈栈底指针。
每一次进入或退出一层Lua 函数,luaV_execute 并不会产生一次 C 层面的函数调用。也就是说,从Lua 函数中调用另一个Lua 函数,并不会产生一次独立的luaV_execute 调用。

luaV_execute

函数实现如下图
这里写图片描述

Lua 自己维护数据栈和调用栈,在解析字节码的时候,用 goto 语句来更新栈信息。 在luaV_execute 中定义了 newframe 这个跳转标签,Lua 函数执行 OP_CALL 、OP_TAILCALL 、OP_RETURN 指令都会跳转到这个标签,更新栈帧继续运行。
1. ci 变量从 当前 L 从获取当前正在调用的函数指针。
2. cl 变量是一个Lua 闭包类型,放置调用栈中当前函数对象,从ci->func中获取。
3. k 变量是 TValue 类型,放置当前函数的常量表, 从cl->p->k中获取。
4. base 变量是 StkId类型,放置当前数据栈 栈底的位置,从cl->u.l.base获取。
5. luaV_execute 解释字节码的过程也就是利用一个死循环,依次解析字节码指令,当前 指令 i 从 ci->u.l.savepc 中获取。

所有的指令都会操作寄存器 A ,从 Lua VM 的角度看,寄存器就是数据栈上的变量,所以可以将寄存器 A 所指变量预先取出放到局部变量ra 中。

ra = RA(i)。某些指令操作在 vm 运行过程中会改变数据栈的大小(伸缩),而 ra 是一个指向数据栈的指针,而不是一个索引。这种情况下,一旦数据栈发生变化,就需要重新获取ra 的值。

同理, base 变量 是一个指向数据栈栈底的指针,也会因为某些指令操作发生变化。这个时候就需要重新对 base 重新赋值。 栈底 base 作为基址是一个参考量,一直需要使用,所以重置 base 的值很频繁, Lua 提供了一个 Protect 宏,将重置base的操作包裹起来。

define Protect(x)   { {x;}; base = ci->u.l.base; }

在死循环中,利用 C 语言的 switch /case 语句 针对每种OpCode 提供不同的操作。
define vmdispatch(o) switch(o)
define vmcase(l) case l:
define vmbreak break

原创粉丝点击