• 《lua设计与实现》第6章 指令的解析与执行--6.3表相关的操作指令


    6.3.1 创建表

        创建一个空表,测试代码为:

    local p = {} --filename

        使用ChunkSpy反编译出来的结果:

    ; function [0] definition (level 1)
    ; 0 upvalues, 0 params, 2 stacks
    .function  0 0 2 2
    .local  "p"  ; 0
    [1] newtable   0   0   0    ; array=0, hash=0
    [2] return     0   1      
    ; end of function

          解释器会依次走过以下函数(箭头左方),右边是其对应的EBNF表示。与上一节的区别在于最后的 simpleexp最终调用的是 constructor 函数,这个函数就是专门负责构造表的 。

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

         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);
    static void constructor (LexState *ls, expdesc *t);

       对应的OPCODE

    typedef enum {
    /*name  args  description */
    // ......
    // 创建一个表,将结果存入寄存器: 
    // A:创建好的表存入寄存器的索引;B:数组部分大小;C:散列部分大小
    OP_NEWTABLE,/* A B C R(A) := {} (size = B,C) */
    // ......
    } OpCode;

          LFIELDS_PER_FLUSH

    //opcodes.h
    /* number of list items to accumulate before a OP_SETLIST instruction */
    #define LFIELDS_PER_FLUSH    50

         解析表的expdesc,会存放在 ConsControl 中

    //lparser.c
    struct ConsControl {
      expdesc v;  /* 表构造过程中最后一个表达式的信息 */
      expdesc *t;  /* 构造表相关的表达式信息,是由外部传入*/
      int nh;  /* 散列部分数据数量 */
      int na;  /* 数组部分数据数量 */
      int tostore;  /* 待存储的数组元素个数,当 > LFIELDS_PER_FLUSH时,调用OP_SETLIST */
    };

           核心函数constructor

    static void constructor (LexState *ls, expdesc *t) {
      /* constructor -> ?? */
      FuncState *fs = ls->fs;
      int line = ls->linenumber;
      // 生成一条 OP_NEWTABLE 指令,创建的表最终会根据指令中的参数A存储的寄存器地址,
      // 赋值给本函数楼内的寄存器,所以这条指令是需要重定向的,即VRELOCABLE
      int pc = luaK_codeABC(fs, OP_NEWTABLE, 0, 0, 0);
      // 初始化 cc
      struct ConsControl cc;
      cc.na = cc.nh = cc.tostore = 0;
      cc.t = t;
      init_exp(t, VRELOCABLE, pc);
      // cc.v 存储的是表构造过程中最后一个表达式的信息,初始化为VVOID
      init_exp(&cc.v, VVOID, 0);  /* no value (yet) */
      // 将寄存器地址修正为前面创建的 OP_NEWTABLE 指令的 参数A
      luaK_exp2nextreg(ls->fs, t);  /* fix it at stack top (for gc) */
      // ......
      SETARG_B(fs->f->code[pc], luaO_int2fb(cc.na)); /* set initial array size */
      SETARG_C(fs->f->code[pc], luaO_int2fb(cc.nh));  /* set initial table size */
    }

         测试代码修改为:

    local p = {1, 2} --filename

         ChunkSpy反编译出来的结果,与前面相比,在 newtable指令之后,还跟着两条 loadk 指令和一条 setlist 指令

    ; function [0] definition (level 1)
    ; 0 upvalues, 0 params, 3 stacks
    .function  0 0 2 3
    .local  "p"  ; 0
    .const  1  ; 0
    .const  2  ; 1
    [1] newtable   0   2   0    ; array=2, hash=0
    [2] loadk      1   0        ; 1
    [3] loadk      2   1        ; 2
    [4] setlist    0   2   1    ; index 1 to 2
    [5] return     0   1      
    ; end of function

         OP_SETLIST

    //opcodes.h
    typedef enum {
    // ......
    // A:OP_NEWTABLE指令中创建好的表所在的寄存器;B:数据;C:FPF(LFIELDS_PER_FLUSH)
    OP_SETLIST,/*    A B C    R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B    */
    // ......
    } OpCode;

         前面 constructor 函数省略的涉及散列、数组部分解析构造的内容如下:

    static void constructor (LexState *ls, expdesc *t) {
      // ......
      do {
        lua_assert(cc.v.k == VVOID || cc.tostore > 0);
        if (ls->t.token == '}') break; // 遇到},结束循环
        //生成上一个表达式的相关指令,它会调用 luaK_exp2nextreg 函数
        closelistfield(fs, &cc);
        // 
        switch(ls->t.token) {
          case TK_NAME: {  /* may be listfields or recfields */
            luaX_lookahead(ls);
            if (ls->lookahead.token != '=')  /* expression? */
              listfield(ls, &cc); //数组方式赋值
            else
              recfield(ls, &cc); // 散列方式赋值
            break;
          }
          case '[': {  /* constructor_item -> recfield */
            recfield(ls, &cc);  //散列方式赋值
            break;
          }
          default: {  /* constructor_part -> listfield */
            listfield(ls, &cc);
            break;
          }
        }
      // ......
    }

        本例为数组部分构造,会进入listfield(lparser.c)

    static void listfield (LexState *ls, struct ConsControl *cc) {
      expr(ls, &cc->v); // 解析表达式,得到cc->v
      // 检查当前表中数组部分的数据梳理是否超过限制
      luaY_checklimit(ls->fs, cc->na, MAX_INT, "items in a constructor");
      cc->na++;
      cc->tostore++;
    }

         closelistfield(lparser.c)从这个函数的命名可以看出,它做的工作是针对数组部分的

    static void closelistfield (FuncState *fs, struct ConsControl *cc) {
      if (cc->v.k == VVOID) return;  /* there is no list item */
      // 将 cc->v 的信息存入寄存器中
      luaK_exp2nextreg(fs, &cc->v);
      cc->v.k = VVOID;
      if (cc->tostore == LFIELDS_PER_FLUSH) {
        // 生成一个OP_SETLIST指令,用于将当前寄存器上的数据写入表的数组部分
        // 其位置是紧跟着 OP_NEWTABLE 指令中的参数A在栈上的位置,
        // A存放的是新创建的表在栈上的位置
        // OP SE T LIST指令中的数据量限制为 LFIELDS_PER_FLUSH,是为了避免占用过多的寄存器 
        luaK_setlist(fs, cc->t->u.s.info, cc->na, cc->tostore);  /* flush */
        cc->tostore = 0;  /* no more items pending */
      }
    }

         再次修改测试代码为:

    local p = {["a"] = 1}

         ChunkSpy看到的指令:

    ; function [0] definition (level 1)
    ; 0 upvalues, 0 params, 2 stacks
    .function  0 0 2 2
    .local  "p"  ; 0
    .const  "a"  ; 0
    .const  1  ; 1
    [1] newtable   0   0   1    ; array=0, hash=1
    [2] settable   0   256 257  ; "a" 1
    [3] return     0   1      
    ; end of function

        settable指令用来完成散列部分初始化,格式为:

    typedef enum {
    // ......
    //A:表所在寄存器;B:key存放的位置;C:value存放的位置
    OP_SETTABLE,/* A B C R(A)[RK(B)] := RK(C) */
    // ......
    } OpCode;

         settable会调用recfield

    static void recfield (LexState *ls, struct ConsControl *cc) {
      /* recfield -> (NAME | `['exp1`]') = exp1 */
      FuncState *fs = ls->fs;
      int reg = ls->fs->freereg;
      expdesc key, val;
      int rkkey;
      if (ls->t.token == TK_NAME) {
        luaY_checklimit(fs, cc->nh, MAX_INT, "items in a constructor");
        checkname(ls, &key);
      }
      else  /* ls->t.token == '[' */
        yindex(ls, &key);
      cc->nh++;
      checknext(ls, '=');
      rkkey = luaK_exp2RK(fs, &key); // 根据key常量的索引生成RK值
      expr(ls, &val);
     // 根据val常量的索引生成RK值,写入OP_SETTABLE 的参数
      luaK_codeABC(fs, OP_SETTABLE, cc->t->u.s.info, rkkey, luaK_exp2RK(fs, &val));
      fs->freereg = reg;  /* free registers */
    }

          再次修改测试代码为:

    local a = "a"
    local p = {[a] = 1}

          ChunkSpy看到的指令与上一个例子的区别于,多了一条 loadk 指令将常量"a"加载到局部变量 a 中,因为是从寄存器中而不是从常量数组中获取 key 的数据 。

    ; function [0] definition (level 1)
    ; 0 upvalues, 0 params, 2 stacks
    .function  0 0 2 2
    .local  "a"  ; 0
    .local  "p"  ; 1
    .const  "a"  ; 0
    .const  1  ; 1
    [1] loadk      0   0        ; "a"
    [2] newtable   1   0   1    ; array=0, hash=1
    [3] settable   1   0   257  ; 1
    [4] return     0   1      
    ; end of function

    6.3.2 查询表

    typedef enum {
    // ......
    // A:存放结果的寄存器;B:表所在的寄存器;C:key存放的位置
    OP_GETTABLE,/* A B C R(A) := R(B)[RK(C)] */
    // ......
    } OpCode;

         修改前面的 lua 代码

    local p = {["a"] = 1}
    local b = p["a"]

         ChunkSpy 看到的指令

    ; function [0] definition (level 1)
    ; 0 upvalues, 0 params, 2 stacks
    .function  0 0 2 2
    .local  "p"  ; 0
    .local  "b"  ; 1
    .const  "a"  ; 0
    .const  1  ; 1
    [1] newtable   0   0   1    ; array=0, hash=1
    [2] settable   0   256 257  ; "a" 1
    [3] gettable   1   0   256  ; "a"
    [4] return     0   1      
    ; end of function

     6.3.3 元表的实现原理

        初始化元方法对应的只读字符串

    void luaT_init (lua_State *L) {
      static const char *const luaT_eventname[] = {  /* ORDER TM */
        "__index", "__newindex",
        "__gc", "__mode", "__eq",
        "__add", "__sub", "__mul", "__div", "__mod",
        "__pow", "__unm", "__len", "__lt", "__le",
        "__concat", "__call"
      };
      int i;
      for (i=0; i<TM_N; i++) {
        G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);
        luaS_fix(G(L)->tmname[i]);  /* never collect these names */
      }
    }

        函数调用堆栈

    void luaT_init (lua_State *L);
    static void f_luaopen (lua_State *L, void *ud);
    int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud);
    LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud);
    LUALIB_API lua_State *luaL_newstate (void);
    int myloadfile(const char *filename);
    int main(void);

         Lua虚拟机从一个表中查询数据的过程

    void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) {
      int loop;
      for (loop = 0; loop < MAXTAGLOOP; loop++) {
        const TValue *tm;
        if (ttistable(t)) {  /* `t' is a table? */
          Table *h = hvalue(t);
          const TValue *res = luaH_get(h, key); /* do a primitive get */
          if (!ttisnil(res) ||  /* result is no nil? */
              (tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */
            setobj2s(L, val, res);
            return;
          }
          /* else will try the tag method */
        }
        else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
          luaG_typeerror(L, t, "index");
        if (ttisfunction(tm)) {
          callTMres(L, val, tm, t, key);
          return;
        }
        t = tm;  /* else repeat with `tm' */ 
      }
      luaG_runerror(L, "loop in gettable");
    }
  • 相关阅读:
    判断鼠标点击在div外时,更改背景图片
    CSS--border边框颜色渐变
    实验一:Java开发环境的熟悉
    实验楼第四次试验报告
    实验楼第三次试验报告
    实验楼第二次试验报告
    实验楼第一次试验报告
    c++程序设计中的函数重载
    C++中,new/delete和malloc/free的区别
    继承和多态二:虚析构函数
  • 原文地址:https://www.cnblogs.com/yyqng/p/14727237.html
Copyright © 2020-2023  润新知