我們來看看lua vm在解析下面源碼并生成bytecode時的整個過程:
foo = "bar" local a, b = "a", "b" foo = a
首先我們先使用ChunkySpy這個工具來看看vm最終會具體生成什么樣的vm instructions
在這里,開頭為[數(shù)字]的行是vm真正生成的字節(jié)碼,我們看到一共生成了六行字節(jié)碼。首先loadk將常量表中下標為1的常量即"bar"賦給寄存器0;然后setglobal將寄存器0的內(nèi)容賦給全局變量表中下標為0的全局變量即foo;loadk再將"a"和"b"分別賦值給了寄存器0、1,在這里寄存器0和1分別表示當(dāng)前函數(shù)的local變量即變量a和b;最后setglobal將變量a的值賦給了全局變量foo;最后一個return01是vm在每一個chunk最后都會生成了,并沒有什么用。現(xiàn)在應(yīng)該比較清除的了解了lua vm生成的字節(jié)碼的含義了,接下來我們看看vm是怎樣且為什么生成這些個字節(jié)碼的。
當(dāng)我們用luaL_dofile函數(shù)執(zhí)行這個lua腳本源碼時會有兩個階段,第一個是將腳本加載進內(nèi)存,分詞解析并生成字節(jié)碼并將其整個包裹為main chunk放于lua stack棧頂,第二是調(diào)用lua_pcall執(zhí)行這個chunk,這里我們只會分析第一個過程。
前面幾篇文章說了,當(dāng)dofile時會跑到一個叫做luaY_parser的函數(shù)中,
Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) { struct LexState lexstate; struct FuncState funcstate; -- ... ... funcstate.f->is_vararg = VARARG_ISVARARG; /* main func. is always vararg */ luaX_next(lexstate); /* read first token */ chunk(lexstate); -- ... ... return funcstate.f; }
函數(shù)luaY_parser前面兩行定義了LexState和FuncState結(jié)構(gòu)體變量,其中LexState不僅用于保存當(dāng)前的詞法分析狀態(tài)信息,而且也保存了整個編譯系統(tǒng)的全局狀態(tài),F(xiàn)uncState結(jié)構(gòu)體來保存當(dāng)前函數(shù)編譯的狀態(tài)數(shù)據(jù)。在lua源碼中都會有一個全局的函數(shù)執(zhí)行體,即為main func,在開始解析的時候當(dāng)前的函數(shù)必然是main func函數(shù),此時第三行的funcstate表示了這個函數(shù)的狀態(tài),由于lua規(guī)定這個函數(shù)必然會接收不定參數(shù)因此第五行將is_vararg標識設(shè)為VARARG_ISVARARG。接著第六行l(wèi)uaX_next解析文件流分離出第一個token,將其保存在lexstate的t成員中,此時t為“foo”全局變量。接著調(diào)用了chunk函數(shù),這里開始了遞歸下降解析的全部過程:
static void chunk (LexState *ls) { /* chunk -> { stat [`;'] } */ int islast = 0; enterlevel(ls); while (!islast !block_follow(ls->t.token)) { islast = statement(ls);//遞歸下降點 testnext(ls, ';'); lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg ls->fs->freereg >= ls->fs->nactvar); ls->fs->freereg = ls->fs->nactvar; /* free registers */ } leavelevel(ls); }
lua是有作用域?qū)哟胃拍畹?,因此?dāng)進入一個層次時會調(diào)用enterlevel函數(shù),離開當(dāng)前層次則會調(diào)用leavelevel函數(shù)。首先進入while循環(huán),當(dāng)前token為“foo”,這既不是終結(jié)標志也不是一個block開始的詞素,因此會進入statement函數(shù),statement函數(shù)主體是一個長長的switch...case...代碼結(jié)構(gòu),根據(jù)第一個token進入不同的調(diào)用解析分支。在我們這個例子中會進入default分支:
static int statement (LexState *ls) { -- ... ... switch (ls->t.token) { case TK_IF: { /* stat -> ifstat */ ifstat(ls, line); return 0; } case TK_WHILE: { /* stat -> whilestat */ whilestat(ls, line); return 0; } -- ... ... default: { exprstat(ls); return 0; /* to avoid warnings */ } } }
進入exprstate函數(shù):
static void exprstat (LexState *ls) { /* stat -> func | assignment */ FuncState *fs = ls->fs; struct LHS_assign v; primaryexp(ls, v.v); if (v.v.k == VCALL) /* stat -> func */ SETARG_C(getcode(fs, v.v), 1); /* call statement uses no results */ else { /* stat -> assignment */ v.prev = NULL; assignment(ls, v, 1); } }
第四行的LHS_assign結(jié)構(gòu)體是為了處理多變量賦值的情況的,例如a,b,c = ...。在LHS_assign中成員v類型為expdesc描述了等號左邊的變量,詳情可見上篇文章里對expdesc的介紹。接下來進入primaryexp,來獲取并填充“foo”變量的expdesc信息,這會接著進入prefixexp函數(shù)中
static void prefixexp (LexState *ls, expdesc *v) { /* prefixexp -> NAME | '(' expr ')' */ switch (ls->t.token) { case '(': { int line = ls->linenumber; luaX_next(ls); expr(ls, v); check_match(ls, ')', '(', line); luaK_dischargevars(ls->fs, v); return; } case TK_NAME: { singlevar(ls, v); return; } default: { luaX_syntaxerror(ls, "unexpected symbol"); return; } } }
由于當(dāng)前token是“foo”,因此進入TK_NAME分支,調(diào)用singlevar。
static void singlevar (LexState *ls, expdesc *var) { TString *varname = str_checkname(ls); FuncState *fs = ls->fs; if (singlevaraux(fs, varname, var, 1) == VGLOBAL) var->u.s.info = luaK_stringK(fs, varname); /* info points to global name */ } static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) { if (fs == NULL) { /* no more levels? */ init_exp(var, VGLOBAL, NO_REG); /* default is global variable */ return VGLOBAL; } else { int v = searchvar(fs, n); /* look up at current level */ if (v >= 0) { init_exp(var, VLOCAL, v); if (!base) markupval(fs, v); /* local will be used as an upval */ return VLOCAL; } else { /* not found at current level; try upper one */ if (singlevaraux(fs->prev, n, var, 0) == VGLOBAL) return VGLOBAL; var->u.s.info = indexupvalue(fs, n, var); /* else was LOCAL or UPVAL */ var->k = VUPVAL; /* upvalue in this level */ return VUPVAL; } }
在singlevaraux函數(shù)中會判斷變量是local、upvalue還是global的。如果fs為null了則說明變量為全局的,否則進入searchvar在當(dāng)前的函數(shù)局部變量數(shù)組中查找,否則根據(jù)fs的prev成員取得其父函數(shù)的FuncState并傳入singlevaraux中遞歸查找,如果前面的都沒滿足則變量為upvlaue。此例中進入第21行中,由于fs已經(jīng)指向了main func因此其prev為null,“foo”判定為global并返回到exprstate函數(shù)中。在取得了“foo”的信息后,因為“foo”不是函數(shù)調(diào)用,因此接著進入assignment函數(shù)中
primaryexp(ls, v.v); if (v.v.k == VCALL) /* stat -> func */ SETARG_C(getcode(fs, v.v), 1); /* call statement uses no results */ else { /* stat -> assignment */ v.prev = NULL; assignment(ls, v, 1); }
在assignment函數(shù)中首先判斷下一個token是否為“,",此例中不是則說明是單變量的賦值,接著check下一個token為”=“,成立,接著調(diào)用explist1判斷等號右邊有幾個值,此例為1個,然后會判斷左邊的變量數(shù)是否等于右邊的值數(shù),不等于則進入adjust_assign函數(shù)進行調(diào)整,此例是相等的因此依次進入luaK_setoneret和luaK_storevar函數(shù)。在luaK_storevar中首先進入int e = luaK_exp2anyreg(fs, ex);函數(shù)luaK_exp2anyreg的K代表了此函數(shù)是字節(jié)碼相關(guān)的函數(shù),ex為值”bar“,這個函數(shù)又調(diào)用了discharge2reg,根據(jù)ex的類型來生成不同的字節(jié)碼:
static void discharge2reg (FuncState *fs, expdesc *e, int reg) { luaK_dischargevars(fs, e); switch (e->k) { case VNIL: { luaK_nil(fs, reg, 1); break; } case VFALSE: case VTRUE: { luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0); break; } case VK: { luaK_codeABx(fs, OP_LOADK, reg, e->u.s.info); break; } //... ... }
由于”bar“是常量因此調(diào)用luaK_codeABx函數(shù)生成loadk字節(jié)碼。reg為保存載入的常量值的寄存器號,e->u.s.info根據(jù)不同類型值代表不同含義,根據(jù)注釋我們知道此時info為常量數(shù)組的下標。
typedef enum { //... ... VK, /* info = index of constant in `k' */ VKNUM, /* nval = numerical value */ VLOCAL, /* info = local register */ VGLOBAL, /* info = index of table; aux = index of global name in `k' */ //... ... } expkind;
生成了loadk后返回到上面的函數(shù)中接著進入luaK_codeABx(fs, OP_SETGLOBAL, e, var->u.s.info);其中e為luaK_exp2anyreg的返回值表示常量保存在的寄存器標號,info根據(jù)注釋當(dāng)為global類型時表示global table的相應(yīng)下標,因此luaK_codeABx函數(shù)將生成setglobal字節(jié)碼,將剛剛用loadk將常量加載到寄存器中的值保存到global table相應(yīng)的位置上。因此foo = "bar"語句就完整的生成了相應(yīng)的字節(jié)碼了。
接下來將生成local a,b = "a","b"語句的字節(jié)碼了。過程大致相同,不同的是a,b是local變量且這個賦值語句是多變量賦值語句,因此前面的函數(shù)會用LHS_assign鏈表將a,b變量連接起來。如圖所示:
以上所述就是本文都全部內(nèi)容了,希望大家能夠喜歡。
標簽:臨滄 昌都 ???/a> 西寧 宿遷 南京 泰安 營口
巨人網(wǎng)絡(luò)通訊聲明:本文標題《lua中賦值類型代碼詳解》,本文關(guān)鍵詞 lua,中,賦值,類型,代碼,詳解,;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請?zhí)峁┫嚓P(guān)信息告之我們,我們將及時溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。