《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");
}
原文地址:https://www.cnblogs.com/yyqng/p/14727237.html