您当前的位置:首页 > lua

Lua执行字节码的过程介绍

前面一篇文章中介绍了lua给下面代码生成最终的字节码的整个过程,这次我们来看看lua vm执行这些字节码的过程。Z2Qlinux系统宝典

1 foo = "bar"2 local a, b = "a", "b"3 foo = a

生成的字节码如下所示:Z2Qlinux系统宝典

Z2Qlinux系统宝典

之前lua是在luaY_parser函数(入口)中完成了lua脚本的解析生成字节码的整个过程的,在生成了main func(过程见““)后luaY_parser会返回一个Proto结构体指针tf,Proto结构将描述整个main func的所有信息。Z2Qlinux系统宝典

 1 //如果此字符是LUA_SIGNATURE中的第一个字符说明文件内容是预编译好的文件内容,因此利用函数luaU_undump来加载一个预编译后的代码块 2   //否则是未编译的脚本源码,利用luaY_parser来对源码进行parse 3   tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z, 4                                                              &p->buff, p->name); 5   cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L))); 6   cl->l.p = tf; 7   for (i = 0; i < tf->nups; i++)  /* initialize eventual upvalues */ 8     cl->l.upvals[i] = luaF_newupval(L); 9   setclvalue(L, L->top, cl);10   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加载整个生成的字节码并加以解释。Z2Qlinux系统宝典

Z2Qlinux系统宝典

 
 1 LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) { 2   struct CallS c; 3   //... ... 4   c.func = L->top - (nargs+1);  /* function to be called */ 5   c.nresults = nresults; 6   status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func); 7   //... ... 8 } 9 /*10 ** Execute a protected call.11 */12 struct CallS {  /* data to `f_call' */13   StkId func;14   int nresults;15 };

首先第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作为其参数传入。如下所示:Z2Qlinux系统宝典

1 static void f_call (lua_State *L, void *ud) {2   struct CallS *c = cast(struct CallS *, ud);3   luaD_call(L, c->func, c->nresults);4 }

在luaD_call中首先判断lua此时是否到达了函数调用层次的最大值,超过这报错否则判断要执行的函数是不是lua function,是的话就调用luaV_execute函数来运行vm执行字节码。Z2Qlinux系统宝典

 1 void luaD_call (lua_State *L, StkId func, int nResults) { 2   if (++L->nCcalls >= LUAI_MAXCCALLS) { 3     if (L->nCcalls == LUAI_MAXCCALLS) 4       luaG_runerror(L, "C stack overflow"); 5     else if (L->nCcalls >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3))) 6       luaD_throw(L, LUA_ERRERR);  /* error while handing stack error */ 7   } 8   if (luaD_precall(L, func, nResults) == PCRLUA)  /* is a Lua function? */ 9     luaV_execute(L, 1);  /* call it */10   L->nCcalls--;11   luaC_checkGC(L);12 }

luaV_execute函数是vm执行字节码的核心过程,整个函数约有400行代码,由于整个过程分支太多,我们只讲解示例中的字节码解析过程。Z2Qlinux系统宝典

 1 void luaV_execute (lua_State *L, int nexeccalls) { 2   LClosure *cl; 3   StkId base; 4   TValue *k; 5   const Instruction *pc; 6  reentry:  /* entry point */ 7   lua_assert(isLua(L->ci)); 8   pc = L->savedpc; 9   cl = &clvalue(L->ci->func)->l;10   base = L->base;11   k = cl->p->k;12 //... ...

L->savedpc为字节码数组的指针,因此pc保存了当前要执行字节码的下标,clvalue萃取出当前要执行的lua function对应的closure,k指向了当前function的常量数组。Z2Qlinux系统宝典

下面先来看看vm解释loadk01字节码的过程。Z2Qlinux系统宝典

 1 /* main loop of interpreter */ 2   for (;;) { 3     const Instruction i = *pc++; 4     StkId ra; 5     //... ... 6     ra = RA(i); 7     //... ... 8     switch (GET_OPCODE(i)) { 9       //... ...10       case OP_LOADK: {11         setobj2s(L, ra, KBx(i));12         continue;13       }14 //... ...

第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”。Z2Qlinux系统宝典

1 /* from stack to (same) stack */2 #define setobjs2s    setobj3 /* to stack (not from same stack) */4 #define setobj2s    setobj5 6 #define setobj(L,obj1,obj2) /7   { const TValue *o2=(obj2); TValue *o1=(obj1); /8     o1->value = o2->value; o1->tt=o2->tt; /9     checkliveness(G(L),o1); }

obj1为ra,obj2为KBx结果。可以看到第7行将这两个值转换为了TValue,并将o2的value设为o1的value,o2的值的类型设为o1的类型,效果上完成了将“bar”的值存放在了function stack上。接着又返回到上面的主循环处读取下一个字节码并执行,下一个要执行的字节码为setglobal00.Z2Qlinux系统宝典

 1 switch (GET_OPCODE(i)) { 2       //... ... 3       case OP_SETGLOBAL: { 4         TValue g; 5         sethvalue(L, &g, cl->env); 6         lua_assert(ttisstring(KBx(i))); 7         Protect(luaV_settable(L, &g, KBx(i), ra)); 8         continue; 9       }10 //... ...

首先第5行中,cl->env为当前function的环境,函数sethvalue将其传给了g,KBx(i)指向了function常量数组中的值,ra为stack中的值,这里为前一条字节码loadk保存在stack中的“bar”。Z2Qlinux系统宝典

 1 void luaV_settable (lua_State *L, const TValue *t, TValue *key, StkId val) { 2   int loop; 3   TValue temp; 4   for (loop = 0; loop < MAXTAGLOOP; loop++) { 5     const TValue *tm; 6     if (ttistable(t)) {  /* `t' is a table? */ 7       Table *h = hvalue(t); 8       TValue *oldval = luaH_set(L, h, key); /* do a primitive set */ 9       if (!ttisnil(oldval) ||  /* result is no nil? */10           (tm = fasttm(L, h->metatable, TM_NEWINDEX)) == NULL) { /* or no TM? */11         setobj2t(L, oldval, val);12         h->flags = 0;13         luaC_barriert(L, h, val);14         return;15       }16  //... ...17 }

这里首先判断g是不是table,然后第7行取得g的hash部分,通过第8行luaH_set里的luaH_get得到table中key对应的old value。最后第11行,函数setobj2t将val("bar")存放在了全局变量foo的位置处,即foo = “bar”。Z2Qlinux系统宝典

1 #define setobj(L,obj1,obj2) /2   { const TValue *o2=(obj2); TValue *o1=(obj1); /3     o1->value = o2->value; o1->tt=o2->tt; /4     checkliveness(G(L),o1); }

好了到了这里语句foo = “bar”对应的两条字节码的解释过程已经全部介绍完了,下面的三条字节码就不再详细解释了,大家可以按照上面的路线自己过一遍~Z2Qlinux系统宝典

Lua 语言 15 分钟快速入门 Z2Qlinux系统宝典

Lua程序设计(第2版)中文 PDF Z2Qlinux系统宝典

Lua程序设计(第二版)阅读笔记 Z2Qlinux系统宝典

NetBSD 将支持用 Lua 脚本开发内核组件 Z2Qlinux系统宝典

编译安装 Lua LuaSocket Z2Qlinux系统宝典

Lua 的详细介绍Z2Qlinux系统宝典
Lua 的下载地址Z2Qlinux系统宝典



沪ICP备10206494号-4