lua执行字节码的过程介绍
前面一篇文章中介绍了lua给下面代码生成最终的字节码的整个过程,这次我们来看看lua vm执行这些字节码的过程。
foo = "bar"
local a, b = "a", "b"
foo = a
生成的字节码如下所示:

之前lua是在luaY_parser函数(入口)中完成了lua脚本的解析生成字节码的整个过程的,在生成了main func(过程见“lua解析赋值类型代码的过程“)后luaY_parser会返回一个Proto结构体指针tf,Proto结构将描述整个main func的所有信息。
//如果此字符是LUA_SIGNATURE中的第一个字符说明文件内容是预编译好的文件内容,因此利用函数luaU_undump来加载一个预编译后的代码块
//否则是未编译的脚本源码,利用luaY_parser来对源码进行parse
tf = ((c == LUA_SIGNATURE[]) ? luaU_undump : luaY_parser)(L, p->z,
&p->buff, p->name);
cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
cl->l.p = tf;
for (i = ; i < tf->nups; i++) /* initialize eventual upvalues */
cl->l.upvals[i] = luaF_newupval(L);
setclvalue(L, L->top, cl);
incr_top(L);
接下来第5行,函数luaF_newLclosure生成了一个Closure结构体来表示lua的closure,然后下一行将Proto结构体地址传给cl保存,接下来的循环里cl的upvalue数组记录下main func中的upvalue,然后setclvalue函数将cl放入到lua stack的栈顶上,incr_top将栈顶L->top加一。此时lua stack的顶部存放了包裹了main func的closure结构体,下面lua将会调用lua_pcall函数来执行这个closure了,也即vm加载整个生成的字节码并加以解释。

LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) {
struct CallS c;
//... ...
c.func = L->top - (nargs+); /* function to be called */
c.nresults = nresults;
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
//... ...
}
/*
** Execute a protected call.
*/
struct CallS { /* data to `f_call' */
StkId func;
int nresults;
};
首先第4行根据要执行的函数参数数量和L->top的值来算出function在lua stack中的位置并将其保存到CallS结构体中,其中CallS结构体中的StkId类型为stack下标类型。接着第6行将c和f_call函数一起传入luaD_pcall函数中,luaD_pcall函数执行一些标志的设置后调用函数luaD_rawrunprotected,函数luaD_rawrunprotected内部调用f_call并将c作为其参数传入。如下所示:
static void f_call (lua_State *L, void *ud) {
struct CallS *c = cast(struct CallS *, ud);
luaD_call(L, c->func, c->nresults);
}
在luaD_call中首先判断lua此时是否到达了函数调用层次的最大值,超过这报错否则判断要执行的函数是不是lua function,是的话就调用luaV_execute函数来运行vm执行字节码。
void luaD_call (lua_State *L, StkId func, int nResults) {
if (++L->nCcalls >= LUAI_MAXCCALLS) {
if (L->nCcalls == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>)))
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
}
if (luaD_precall(L, func, nResults) == PCRLUA) /* is a Lua function? */
luaV_execute(L, ); /* call it */
L->nCcalls--;
luaC_checkGC(L);
}
luaV_execute函数是vm执行字节码的核心过程,整个函数约有400行代码,由于整个过程分支太多,我们只讲解示例中的字节码解析过程。
void luaV_execute (lua_State *L, int nexeccalls) {
LClosure *cl;
StkId base;
TValue *k;
const Instruction *pc;
reentry: /* entry point */
lua_assert(isLua(L->ci));
pc = L->savedpc;
cl = &clvalue(L->ci->func)->l;
base = L->base;
k = cl->p->k;
//... ...
L->savedpc为字节码数组的指针,因此pc保存了当前要执行字节码的下标,clvalue萃取出当前要执行的lua function对应的closure,k指向了当前function的常量数组。
下面先来看看vm解释loadk01字节码的过程。
/* main loop of interpreter */
for (;;) {
const Instruction i = *pc++;
StkId ra;
//... ...
ra = RA(i);
//... ...
switch (GET_OPCODE(i)) {
//... ...
case OP_LOADK: {
setobj2s(L, ra, KBx(i));
continue;
}
//... ...
第3行i保存了当前要执行的字节码,同时pc指向下一条字节码,第6行ra保存了通过宏RA萃取出的字节码中的a部分并与function stack的base相加得出的stack中的值;第8行Get_OPCODE宏萃取出字节码i的类型,结果是OP_LOADK,因此调用了setobj2s函数,其中KBx宏萃取出字节码i的bx部分并与function的常量数组地址相加得出的常量值,这里ra指向了function stack中相应的位置,KBx(i)部分指向了当前function中常量数组中存放的常量“bar”。
/* from stack to (same) stack */
#define setobjs2s setobj
/* to stack (not from same stack) */
#define setobj2s setobj #define setobj(L,obj1,obj2) \
{ const TValue *o2=(obj2); TValue *o1=(obj1); \
o1->value = o2->value; o1->tt=o2->tt; \
checkliveness(G(L),o1); }
obj1为ra,obj2为KBx结果。可以看到第7行将这两个值转换为了TValue,并将o2的value设为o1的value,o2的值的类型设为o1的类型,效果上完成了将“bar”的值存放在了function stack上。接着又返回到上面的主循环处读取下一个字节码并执行,下一个要执行的字节码为setglobal00.
switch (GET_OPCODE(i)) {
//... ...
case OP_SETGLOBAL: {
TValue g;
sethvalue(L, &g, cl->env);
lua_assert(ttisstring(KBx(i)));
Protect(luaV_settable(L, &g, KBx(i), ra));
continue;
}
//... ...
首先第5行中,cl->env为当前function的环境,函数sethvalue将其传给了g,KBx(i)指向了function常量数组中的值,ra为stack中的值,这里为前一条字节码loadk保存在stack中的“bar”。
void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) {
int loop;
TValue temp;
for (loop = ; loop < MAXTAGLOOP; loop++) {
const TValue *tm;
if (ttistable(t)) { /* `t' is a table? */
Table *h = hvalue(t);
TValue *oldval = luaH_set(L, h, key); /* do a primitive set */
if (!ttisnil(oldval) || /* result is no nil? */
(tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */
setobj2t(L, oldval, val);
h->flags = ;
luaC_barriert(L, h, val);
return;
}
//... ...
}
这里首先判断g是不是table,然后第7行取得g的hash部分,通过第8行luaH_set里的luaH_get得到table中key对应的old value。最后第11行,函数setobj2t将val("bar")存放在了全局变量foo的位置处,即foo = “bar”。
#define setobj(L,obj1,obj2) \
{ const TValue *o2=(obj2); TValue *o1=(obj1); \
o1->value = o2->value; o1->tt=o2->tt; \
checkliveness(G(L),o1); }
好了到了这里语句foo = “bar”对应的两条字节码的解释过程已经全部介绍完了,下面的三条字节码就不再详细解释了,大家可以按照上面的路线自己过一遍~
lua执行字节码的过程介绍的更多相关文章
- JVM总结-虚拟机怎么执行字节码
1. JRE,JDK JRE : 包含运行 Java 程序的必需组件,Java 虚拟机+ Java 核心类库等. JDK : JRE + 一系列开发.诊断工具. 2. java字节码 编译器将 Ja ...
- 【JDK命令行 一】手动编译Java源码与执行字节码命令合集(含外部依赖引用)
写作目标 记录常见的使用javac手动编译Java源码和java手动执行字节码的命令,一方面用于应对 Maven 和 Gradle 暂时无法使用的情况,临时生成class文件(使用自己的jar包):另 ...
- 重读《深入理解Java虚拟机》五、虚拟机如何执行字节码?程序方法如何被执行?虚拟机执行引擎的工作机制
Class文件二进制字符流通过类加载器和虚拟机加载到内存(方法区)完成在内存上的布局和初始化后,虚拟机字节码执行引擎就可以执行相关代码实现程序所定义的功能.虚拟机执行引擎执行的对象是方法(均特指非本地 ...
- JVM插庄之一:JVM字节码增强技术介绍及入门示例
字节码增强技术:AOP技术其实就是字节码增强技术,JVM提供的动态代理追根究底也是字节码增强技术. 目的:在Java字节码生成之后,对其进行修改,增强其功能,这种方式相当于对应用程序的二进制文件进行修 ...
- Java之字节码(3) - 简单介绍
转载来自 首先了解一下理论知识: 字节码: Class文件是8位字节流,按字节对齐.之所以称为字节码,是因为每条指令都只占据一个字节,所有的操作码和操作数都是按字节对齐的.如:0×03表示iconst ...
- JVM-绘图展现字节码执行引擎执行过程
在我的上一篇博客JVM-String比较-字节码分析中介绍了String字符串比较的原因,借着分析字节码的机会,我这篇博客将会绘图展现方法内部字节码执行过程. 话不多说,贴上我们将要分析的Java方法 ...
- JVM总结(五):JVM字节码执行引擎
JVM字节码执行引擎 运行时栈帧结构 局部变量表 操作数栈 动态连接 方法返回地址 附加信息 方法调用 解析 分派 –“重载”和“重写”的实现 静态分派 动态分派 单分派和多分派 JVM动态分派的实现 ...
- java虚拟机字节码执行引擎
定义 java虚拟机字节码执行引擎是jvm最核心的组成部分之一,它做的事情很简单:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果.在不同的虚拟机实现里,执行引擎在执行java代码 ...
- JVM(6) 字节码执行引擎
编译器(javac)将Java源文件(.java文件)编译成Java字节码(.class文件). 类加载器负责加载编译后的字节码,并加载到运行时数据区(Runtime Data Area) 通过类加载 ...
随机推荐
- Kali对wifi的破解记录
好记性不如烂笔头,记录一下. 我是在淘宝买的拓实N87,Kali可以识别,还行. 操作系统:Kali 开始吧. 查看一下网卡的接口.命令如下 airmon-ng 可以看出接口名称是wlan0mon. ...
- Java 线程
线程:线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程.线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不能拥有系统资源.它与父进程的其他线程共享该进程的所有资 ...
- iOS二维码生成、识别、扫描等
二维码扫描 前言: 最近的项目中使用到了二维码,二维码这个模块功能也完成:觉得还是有必要总结一下用来做记录.好长时间没有写二维码了都忘记在差不多了,重新拾起来还是挻快的. 二维码使用场景: 生活中有很 ...
- Dapper where Id in的解决方案
简单记一下,一会出去有点事情~ 我们一般写sql都是==>update NoteInfo set NDataStatus=@NDataStatus where NId in (@NIds) Da ...
- SQL Server 2016白皮书
随着SQL Server 2016正式版发布日临近,相关主要特性通过以下预览学习: Introducing Microsoft SQL Server 2016 e-bookSQL Server 201 ...
- .NET面试题集锦②(Part 二)
一.前言部分 文中的问题及答案多收集整理自网络,不保证100%准确,还望斟酌采纳. 1.实现产生一个int数组,长度为100,并向其中随机插入1-100,并且不能重复. ]; ArrayList my ...
- c++ pair 使用
1. 包含头文件: #include <utility> 2. pair 的操作: pair<T1,T2> p; pair<T1,T2> p(v1,v2); pai ...
- yaf的简单入门
1.目录结构: 2.入口文件 入口文件是所有请求的入口,一般都借助于rewrite规则,把所有的请求都重定向到这个入口文件. 一个经典的入口文件 public/index.php 3.重写规则 需要 ...
- 封装集合(Encapsulate Collection)
封装就是将相关的方法或者属性抽象成为一个对象. 封装的意义: 对外隐藏内部实现,接口不变,内部实现自由修改. 只返回需要的数据和方法. 提供一种方式防止数据被修改. 更好的代码复用. 当一个类的属性类 ...
- 用Swagger生成接口文档
Swagger简介 在系统设计的时候,各个应用之间往往是通过接口进行交互的.因此接口的定义在整个团队中就变得尤为重要.我们可以把接口的规范用接口描述语言进行描述,然后Swagger可以根据我们定义的接 ...