• 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,这里我们只会分析第一个过程。

    前面几篇文章说了,当dofile时会跑到一个叫做luaY_parser的函数中,

     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函数,这里开始了递归下降解析的全部过程:

     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分支:

     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函数:

     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函数中

     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变量连接起来。如图所示:

  • 相关阅读:
    什么是MIME
    bit/byte/英文字符/汉字之间的换算及java八大基本数据类型的占字节数
    js 上传文件大小检查
    java.toString() ,(String),String.valueOf的区别
    java 下载文件的样例
    回调函数分析
    IO流详析
    各个秒之间的换算率
    内边距:
    Less-6【报错+BOOL类型】
  • 原文地址:https://www.cnblogs.com/zxh1210603696/p/4476332.html
Copyright © 2020-2023  润新知