前面一篇文章中介紹了lua給下面代碼生成最終的字節碼的整個過程,此次咱們來看看lua vm執行這些字節碼的過程。html
1 foo = "bar" 2 local a, b = "a", "b" 3 foo = a
生成的字節碼以下所示:c++
以前lua是在luaY_parser函數(入口)中完成了lua腳本的解析生成字節碼的整個過程的,在生成了main func(過程見「lua解析賦值類型代碼的過程「)後luaY_parser會返回一個Proto結構體指針tf,Proto結構將描述整個main func的全部信息。數組
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加載整個生成的字節碼並加以解釋。函數
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做爲其參數傳入。以下所示:oop
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執行字節碼。lua
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行代碼,因爲整個過程分支太多,咱們只講解示例中的字節碼解析過程。spa
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的常量數組。指針
下面先來看看vm解釋loadk01字節碼的過程。code
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」。htm
1 /* from stack to (same) stack */ 2 #define setobjs2s setobj 3 /* to stack (not from same stack) */ 4 #define setobj2s setobj 5 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.
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」。
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」。
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」對應的兩條字節碼的解釋過程已經所有介紹完了,下面的三條字節碼就再也不詳細解釋了,你們能夠按照上面的路線本身過一遍~