zoukankan      html  css  js  c++  java
  • 《lua设计与实现》第6章 指令的解析与执行--6.2赋值类指令

    6.1 Lua词法

        语言的解析一般是两遍遍历的过程,第一遍生成AST,第二遍将AST翻译为字节码。

        Lua使用一遍扫描代码文件的方式生成字节码,以加快解释执行的速度。但缺点是代码比较难以理解。如

        dostat ->    DO      block    END

        函数    -> 终结符  语法块  终结符

    6.2 赋值类指令

    --filename中的lua代码
    local a = 10

         使用myloadfile(个人测试代码)加载上述 filename 文件中的 lua 代码进行加载和解析:

    int myloadfile(const char *filename) {
        lua_State *L = luaL_newstate();
        luaL_openlibs(L); // open all the libs above
        if (luaL_loadfile(L, filename)) {
            error(L, "luaL_loadfile %s", lua_tostring(L, -1));
        }
        lua_close(L);
        return 0;
    }

         解释器会依次走过以下函数(箭头左方),右边是其对应的EBNF表示

    chunk     -> { stat [';'] }
    statement -> localstat
    localstat -> LOCAL NAME {',' NAME} [ '=' explist1]
    explist1  -> expr {',' expr}
    expr      -> subexpr
    subexpr   -> simpleexp
    simpleexp -> NUMBER

         详细函数调用顺序如下

    int myloadfile(const char *filename);
    LUALIB_API int luaL_loadfile (lua_State *L, const char *filename);
    LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char *chunkname);
    int luaD_protectedparser (lua_State *L, ZIO *z, const char *name);
    int luaD_pcall (lua_State *L, Pfunc func, void *u, ptrdiff_t old_top, ptrdiff_t ef);
    int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);
    static void f_parser (lua_State *L, void *ud);
    Proto *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff, const char *name);
    // EBNF 对应函数
    static void chunk (LexState *ls);
    static int statement (LexState *ls);
    static void localstat (LexState *ls);
    static int explist1 (LexState *ls, expdesc *v);
    static void expr (LexState *ls, expdesc *v);
    static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit);
    static void simpleexp (LexState *ls, expdesc *v);

         EBNF 对应函数中,有一个贯穿始终的用于词法分析的中间临时变量ls(llex.h),结构体为:

    typedef struct LexState {
      int current;  /* current character (charint) */
      int linenumber;  /* input line counter */
      int lastline;  /* line of last token `consumed' */
      Token t;  /* current token */
      Token lookahead;  /* look ahead token */
      struct FuncState *fs;  /* `FuncState' is private to the parser */
      struct lua_State *L;
      ZIO *z;  /* input stream */
      Mbuffer *buff;  /* buffer for tokens */
      TString *source;  /* current source name */
      char decpoint;  /* locale decimal point */
    } LexState;

          ls->fs->freereg (int型) 存放的就是当前函数栈的下一个可用位置,在每个 chunk 函数中,都会根据当前函数栈存放的变量数量(包括函数的局部变量、函数的参数等)进行调整:

    static void chunk (LexState *ls) {
      /* chunk -> { stat [`;'] } */
      int islast = 0;
      enterlevel(ls);
      while (!islast && !block_follow(ls->t.token)) {
        islast = statement(ls); //此函数中将选择进入localstat
        testnext(ls, ';');
        lua_assert(ls->fs->f->maxstacksize >= ls->fs->freereg);
        lua_assert(ls->fs->freereg >= ls->fs->nactvar);
        ls->fs->freereg = ls->fs->nactvar;  /* free registers */
      }
      leavelevel(ls);
    }

        在函数 localstat(lparser.c) 中,循环调用 new_localvar,将赋值表达式中 = 左边的所有变量都生成一个相应的局部变量,对应的结构体是 LocVar,存放于ls->fs->f->locvars(LocVar *)

    typedef struct LocVar {
      TString *varname; /* 变量名 */
      int startpc;  /* first point where variable is active */
      int endpc;    /* first point where variable is dead */
    } LocVar;

         localstat用于生成局部变量

    static void localstat (LexState *ls) {
      /* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */
      int nvars = 0;
      int nexps;
      expdesc e;
      do {
        // 循环调用 new_localvar,将赋值表达式中 = 左边的所有变量都生成一个相应的局部变量。
        new_localvar(ls, str_checkname(ls), nvars++);
      } while (testnext(ls, ','));
      if (testnext(ls, '='))
        nexps = explist1(ls, &e);
      else {
        e.k = VVOID;
        nexps = 0;
      }
      adjust_assign(ls, nvars, nexps, &e);
      adjustlocalvars(ls, nvars); //用于根据nvars调整ls->fs->nactvar
    }

         adjustlocalvars用于调整ls->fs->nactvar

    static void adjustlocalvars (LexState *ls, int nvars) {
      FuncState *fs = ls->fs;
      fs->nactvar = cast_byte(fs->nactvar + nvars);
      for (; nvars; nvars--) {
        getlocvar(fs, fs->nactvar - nvars).startpc = fs->pc;
      }
    }
    #define getlocvar(fs, i)    ((fs)->f->locvars[(fs)->actvar[i]])

         下面介绍右边表达式结果的存储

        解析表达式的结果会存储在一个临时数据结构 expdesc (lparser.h)中:

    /*
    ** Expression descriptor
    */
    
    typedef enum {
      VVOID,    /* no value */
      VNIL,
      VTRUE,
      VFALSE,
      VK,        /* info = index of constant in `k' */
      VKNUM,    /* nval = numerical value */
      VLOCAL,    /* info = local register */
      VUPVAL,       /* info = index of upvalue in `upvalues' */
      VGLOBAL,    /* info = index of table; aux = index of global name in `k' */
      VINDEXED,    /* info = table register; aux = index register (or `k') */
      VJMP,        /* info = instruction pc */
      VRELOCABLE,    /* info = instruction pc */
      VNONRELOC,    /* info = result register */
      VCALL,    /* info = instruction pc */
      VVARARG    /* info = instruction pc */
    } expkind;
    
    typedef struct expdesc {
      expkind k; /* 具体类型 */
      union {
        struct { int info, aux; } s;
        lua_Number nval;
      } u;
      int t;  /* patch list of `exit when true' */
      int f;  /* patch list of `exit when false' */
    } expdesc;

         localstat会调用explist1(lparser.c),将结果缓存在 expdesc 中

    static int explist1 (LexState *ls, expdesc *v) {
      /* explist1 -> expr { `,' expr } */
      int n = 1;  /* at least one expression */
      expr(ls, v); //解析表达式
      while (testnext(ls, ',')) {
        //将表达式存入当前函数的下一个可用寄存器中
        luaK_exp2nextreg(ls->fs, v);
        expr(ls, v); //解析表达式
        n++;
      }
      return n;
    }

        expr->subexpr ->simpleexp,赋值表达式右边为10,最终走入函数simpleexp(lparser.c)

    static void simpleexp (LexState *ls, expdesc *v) {
      /* simpleexp -> NUMBER | STRING | NIL | true | false | ... |
                      constructor | FUNCTION body | primaryexp */
      switch (ls->t.token) {
        case TK_NUMBER: {
          init_exp(v, VKNUM, 0); //VKNUM表示数字常量
          //union u 的数据根据不同的类型会存储不同的信息,这里将10赋值给nval
          v->u.nval = ls->t.seminfo.r; 
          break;
        }
        现在这个表达式的信息已经存放在 expdesc 结构体中,接下来根据它生成对应的字节码。
        localstat -> adjust_assign -> luaK_exp2nextreg(lcode.c)
    void luaK_exp2nextreg (FuncState *fs, expdesc *e) {
      // 根据变量所在的不同作用域( local, global, upvalue )来决定这个变量是否需要重定向。
      luaK_dischargevars(fs, e);
      freeexp(fs, e);
      // 分配可用的函数寄存器空间,得到这个空间对应的寄存器索引。有了空间,才能存储变量。
      luaK_reserveregs(fs, 1);
      // 把表达式的数据放入寄存器空间。最终又会调用 discharge2reg 函数,
      // 这个函数式根据不同的表达式类型( NIL ,布尔表达式,数字等) 来生成存取表达式的值到寄存器的字节码。
      exp2reg(fs, e, fs->freereg - 1); 
    }

        luaK_exp2nextreg -> discharge2reg(lcode.c)

    static void discharge2reg (FuncState *fs, expdesc *e, int reg) {
      luaK_dischargevars(fs, e);
      switch (e->k) {
        // ... ...
        case VKNUM: {
          luaK_codeABx(fs, OP_LOADK, reg, luaK_numberK(fs, e->u.nval));
          break;
        }

        设置 fs->f->code[fs->pc] = i;  // i 为 Instruction

    int luaK_codeABx (FuncState *fs, OpCode o, int a, unsigned int bc) {
      lua_assert(getOpMode(o) == iABx || getOpMode(o) == iAsBx);
      lua_assert(getCMode(o) == OpArgN);
      return luaK_code(fs, CREATE_ABx(o, a, bc), fs->ls->lastline);
    }

        如果左边的变量变成下面这样 

    --filename中的lua代码
    local a = 10

         回头看看前面的 localstat 函数最后两行代码

    static void localstat (LexState *ls) {
      // ......
      adjust_assign(ls, nvars, nexps, &e); // 根据等号两边变量和表达式的数量来调整赋值,b会赋值为nil
      adjustlocalvars(ls, nvars); //用于根据nvars调整ls->fs->nactvar,并记录这些局部变量的 startpc值
    }

         再次修改示例

    --filename中的lua代码
    local a = 10
    local b = a

         Chunk:Spy反编译的指令如下:

    ; source chunk: c_call_lua/a.lua
    ; x86 standard (32-bit, little endian, doubles)
    
    ; function [0] definition (level 1)
    ; 0 upvalues, 0 params, 2 stacks
    .function  0 0 2 2
    .local  "a"  ; 0
    .local  "b"  ; 1
    .const  1  ; 0
    [1] loadk      0   0        ; 1
    [2] move       1   0      
    [3] return     0   1      
    ; end of function

         对于loadk,前面已经介绍过了。对于move指令,解释器会依次走过以下函数(箭头左方),右边是其对应的EBNF表示

    chunk     -> { stat [';'] }
    statement -> localstat
    localstat -> LOCAL NAME {',' NAME} [ '=' explist1]
    explist1  -> expr {',' expr}
    expr      -> subexpr
    subexpr   -> simpleexp
    simpleexp -> primaryexp
    primaryexp-> prefixexp
    prefixexp -> NAME

        与前面的区别是,这里走入了 primaryexp,然后在 prefixexp中判断这是一个变量时,会调用 singlevar(lparser.c) 进行变量查找

    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 */
    }

         singlevar会调用singlevaraux进行递归查找

    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;
        }
      }
    }

         localstat -> adjust_assign -> luaK_exp2nextreg->luaK_dischargevars(lcode.c)根据三种不同的类型,有三种不同的操作 

    void luaK_dischargevars (FuncState *fs, expdesc *e) {
      switch (e->k) {
        case VLOCAL: { //局部变量不需要重定向,也无需额外的语句把这个值加载进来
          e->k = VNONRELOC;
          break;
        }

         localstat -> adjust_assign -> luaK_exp2nextreg -> exp2reg -> discharge2reg(lcode.c)

    static void discharge2reg (FuncState *fs, expdesc *e, int reg) {
      luaK_dischargevars(fs, e);
      switch (e->k) {
        // ......
        case VNONRELOC: {// 不需要重定位,那么直接生成MOVE 指令来完成变量的赋值
          if (reg != e->u.s.info)
            luaK_codeABC(fs, OP_MOVE, reg, e->u.s.info, 0);
          break;
        }
        default: {
          lua_assert(e->k == VVOID || e->k == VJMP);
          return;  /* nothing to do... */
        }
      }
      e->u.s.info = reg;
      e->k = VNONRELOC;
    }

        赋值元源数据是局部变量,使用MOVE指令完成赋值。

  • 相关阅读:
    内部类&匿名内部类
    Object:
    多 态★★★★★(面向对象特征之三)
    接 口:
    继 承(面向对象特征之二)
    封 装(面向对象特征之一)
    三:面向对象:★★★★★
    Codeforces 719 E. Sasha and Array (线段树+矩阵运算)
    uestc oj 1218 Pick The Sticks (01背包变形)
    uestc oj 1217 The Battle of Chibi (dp + 离散化 + 树状数组)
  • 原文地址:https://www.cnblogs.com/yyqng/p/14660241.html
Copyright © 2011-2022 走看看