lua解析賦值類型代碼的過程

咱們來看看lua vm在解析下面源碼並生成bytecode時的整個過程:數組

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

首先咱們先使用ChunkySpy這個工具來看看vm最終會具體生成什麼樣的vm instructions函數

在這裏,開頭爲[數字]的行是vm真正生成的字節碼,咱們看到一共生成了六行字節碼。首先loadk將常量表中下標爲1的常量即"bar"賦給寄存器0;而後setglobal將寄存器0的內容賦給全局變量表中下標爲0的全局變量即foo;loadk再將"a"和"b"分別賦值給了寄存器0、1,在這裏寄存器0和1分別表示當前函數的local變量即變量a和b;最後setglobal將變量a的值賦給了全局變量foo;最後一個return01是vm在每個chunk最後都會生成了,並無什麼用。如今應該比較清除的瞭解了lua vm生成的字節碼的含義了,接下來咱們看看vm是怎樣且爲何生成這些個字節碼的。工具

當咱們用luaL_dofile函數執行這個lua腳本源碼時會有兩個階段,第一個是將腳本加載進內存,分詞解析並生成字節碼並將其整個包裹爲main chunk放於lua stack棧頂,第二是調用lua_pcall執行這個chunk,這裏咱們只會分析第一個過程。this

前面幾篇文章說了,當dofile時會跑到一個叫作luaY_parser的函數中,lua

 1 Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name) {
 2   struct LexState lexstate;
 3   struct FuncState funcstate;
 4   -- ... ...
 5   funcstate.f->is_vararg = VARARG_ISVARARG;  /* main func. is always vararg */
 6   luaX_next(&lexstate);  /* read first token */
 7   chunk(&lexstate);
 8   -- ... ...
 9   return funcstate.f;
10 }

函數luaY_parser前面兩行定義了LexState和FuncState結構體變量,其中LexState不只用於保存當前的詞法分析狀態信息,並且也保存了整個編譯系統的全局狀態,FuncState結構體來保存當前函數編譯的狀態數據。在lua源碼中都會有一個全局的函數執行體,即爲main func,在開始解析的時候當前的函數必然是main func函數,此時第三行的funcstate表示了這個函數的狀態,因爲lua規定這個函數必然會接收不定參數所以第五行將is_vararg標識設爲VARARG_ISVARARG。接着第六行luaX_next解析文件流分離出第一個token,將其保存在lexstate的t成員中,此時t爲「foo」全局變量。接着調用了chunk函數,這裏開始了遞歸降低解析的所有過程:spa

 1 static void chunk (LexState *ls) {
 2   /* chunk -> { stat [`;'] } */
 3   int islast = 0;
 4   enterlevel(ls);
 5   while (!islast && !block_follow(ls->t.token)) {
 6     islast = statement(ls);//遞歸降低點
 7     testnext(ls, ';');
 8     lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg &&
 9                ls->fs->freereg >= ls->fs->nactvar);
10     ls->fs->freereg = ls->fs->nactvar;  /* free registers */
11   }
12   leavelevel(ls);
13 }

lua是有做用域層次概念的,所以當進入一個層次時會調用enterlevel函數,離開當前層次則會調用leavelevel函數。首先進入while循環,當前token爲「foo」,這既不是終結標誌也不是一個block開始的詞素,所以會進入statement函數,statement函數主體是一個長長的switch...case...代碼結構,根據第一個token進入不一樣的調用解析分支。在咱們這個例子中會進入default分支:3d

 1 static int statement (LexState *ls) {
 2   -- ... ...
 3   switch (ls->t.token) {
 4     case TK_IF: {  /* stat -> ifstat */
 5       ifstat(ls, line);
 6       return 0;
 7     }
 8     case TK_WHILE: {  /* stat -> whilestat */
 9       whilestat(ls, line);
10       return 0;
11     }
12     -- ... ...
13     default: {
14       exprstat(ls);
15       return 0;  /* to avoid warnings */
16     }
17   }
18 }

進入exprstate函數:code

 1 static void exprstat (LexState *ls) {
 2   /* stat -> func | assignment */
 3   FuncState *fs = ls->fs;
 4   struct LHS_assign v;
 5   primaryexp(ls, &v.v);
 6   if (v.v.k == VCALL)  /* stat -> func */
 7     SETARG_C(getcode(fs, &v.v), 1);  /* call statement uses no results */
 8   else {  /* stat -> assignment */
 9     v.prev = NULL;
10     assignment(ls, &v, 1);
11   }
12 }

第四行的LHS_assign結構體是爲了處理多變量賦值的狀況的,例如a,b,c = ...。在LHS_assign中成員v類型爲expdesc描述了等號左邊的變量,詳情可見上篇文章裏對expdesc的介紹。接下來進入primaryexp,來獲取並填充「foo」變量的expdesc信息,這會接着進入prefixexp函數中blog

 1 static void prefixexp (LexState *ls, expdesc *v) {
 2   /* prefixexp -> NAME | '(' expr ')' */
 3   switch (ls->t.token) {
 4     case '(': {
 5       int line = ls->linenumber;
 6       luaX_next(ls);
 7       expr(ls, v);
 8       check_match(ls, ')', '(', line);
 9       luaK_dischargevars(ls->fs, v);
10       return;
11     }
12     case TK_NAME: {
13       singlevar(ls, v);
14       return;
15     }
16     default: {
17       luaX_syntaxerror(ls, "unexpected symbol");
18       return;
19     }
20   }
21 }

因爲當前token是「foo」,所以進入TK_NAME分支,調用singlevar。遞歸

 1 static void singlevar (LexState *ls, expdesc *var) {
 2   TString *varname = str_checkname(ls);
 3   FuncState *fs = ls->fs;
 4   if (singlevaraux(fs, varname, var, 1) == VGLOBAL)
 5     var->u.s.info = luaK_stringK(fs, varname);  /* info points to global name */
 6 }
 7 static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
 8   if (fs == NULL) {  /* no more levels? */
 9     init_exp(var, VGLOBAL, NO_REG);  /* default is global variable */
10     return VGLOBAL;
11   }
12   else {
13     int v = searchvar(fs, n);  /* look up at current level */
14     if (v >= 0) {
15       init_exp(var, VLOCAL, v);
16       if (!base)
17         markupval(fs, v);  /* local will be used as an upval */
18       return VLOCAL;
19     }
20     else {  /* not found at current level; try upper one */
21       if (singlevaraux(fs->prev, n, var, 0) == VGLOBAL)
22         return VGLOBAL;
23       var->u.s.info = indexupvalue(fs, n, var);  /* else was LOCAL or UPVAL */
24       var->k = VUPVAL;  /* upvalue in this level */
25       return VUPVAL;
26     }
27   }

在singlevaraux函數中會判斷變量是local、upvalue仍是global的。若是fs爲null了則說明變量爲全局的,不然進入searchvar在當前的函數局部變量數組中查找,不然根據fs的prev成員取得其父函數的FuncState並傳入singlevaraux中遞歸查找,若是前面的都沒知足則變量爲upvlaue。此例中進入第21行中,因爲fs已經指向了main func所以其prev爲null,「foo」斷定爲global並返回到exprstate函數中。在取得了「foo」的信息後,由於「foo」不是函數調用,所以接着進入assignment函數中

1 primaryexp(ls, &v.v);
2   if (v.v.k == VCALL)  /* stat -> func */
3     SETARG_C(getcode(fs, &v.v), 1);  /* call statement uses no results */
4   else {  /* stat -> assignment */
5     v.prev = NULL;
6     assignment(ls, &v, 1);
7   }

在assignment函數中首先判斷下一個token是否爲「,",此例中不是則說明是單變量的賦值,接着check下一個token爲」=「,成立,接着調用explist1判斷等號右邊有幾個值,此例爲1個,而後會判斷左邊的變量數是否等於右邊的值數,不等於則進入adjust_assign函數進行調整,此例是相等的所以依次進入luaK_setoneret和luaK_storevar函數。在luaK_storevar中首先進入int e = luaK_exp2anyreg(fs, ex);函數luaK_exp2anyreg的K表明了此函數是字節碼相關的函數,ex爲值」bar「,這個函數又調用了discharge2reg,根據ex的類型來生成不一樣的字節碼:

 1 static void discharge2reg (FuncState *fs, expdesc *e, int reg) {
 2   luaK_dischargevars(fs, e);
 3   switch (e->k) {
 4     case VNIL: {
 5       luaK_nil(fs, reg, 1);
 6       break;
 7     }
 8     case VFALSE:  case VTRUE: {
 9       luaK_codeABC(fs, OP_LOADBOOL, reg, e->k == VTRUE, 0);
10       break;
11     }
12     case VK: {
13       luaK_codeABx(fs, OP_LOADK, reg, e->u.s.info);
14       break;
15     }
16 //... ...
17 }

因爲」bar「是常量所以調用luaK_codeABx函數生成loadk字節碼。reg爲保存載入的常量值的寄存器號,e->u.s.info根據不一樣類型值表明不一樣含義,根據註釋咱們知道此時info爲常量數組的下標。

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後返回到上面的函數中接着進入luaK_codeABx(fs, OP_SETGLOBAL, e, var->u.s.info);其中e爲luaK_exp2anyreg的返回值表示常量保存在的寄存器標號,info根據註釋當爲global類型時表示global table的相應下標,所以luaK_codeABx函數將生成setglobal字節碼,將剛剛用loadk將常量加載到寄存器中的值保存到global table相應的位置上。所以foo = "bar"語句就完整的生成了相應的字節碼了。

接下來將生成local a,b = "a","b"語句的字節碼了。過程大體相同,不一樣的是a,b是local變量且這個賦值語句是多變量賦值語句,所以前面的函數會用LHS_assign鏈表將a,b變量鏈接起來。如圖所示:

相關文章
相關標籤/搜索