lua解释器(变量以及表)

本篇主要来结合着lua的代码,配合lua源码来阅读lua的汇编代码,具体反汇编工具可以用ChunkSpy这个工具,在我github上有,这个主要是我自己用来阅读lua源码的,阅读的主要部分加上注释。下面列举部分常用的lua指令和lua代码。
具体下面c描述的部分的指令的信息都在lopcodes.h里。
这里有个前提的知识要提前说明下,lua虽然是基于“寄存器”的语言。但是寄存器本身的实现是用的c里的栈,这个是“栈式”语言两个概念,所以R(0)表示0号寄存器,但是在内存的栈指针base处。
比如实现一个加法 基于寄存器的语言指令只需要Add 1 2 一条指令就可以了,而基于栈式的语言需要pop 1 + pop 2 + add + push一共4条指令才可以。这个也是为什么lua性能比较高的一个重要原因。

  • 局部变量指令
local a = 1
local b = a

/////反汇编后 汇编如下////
0028  03000000           sizecode (3)
; (1)  local a = 1
002C  01000000           [1] loadk      0   0        ; 1
; (2)  local b = a
0030  40000000           [2] move       1   0      
0034  1E008000           [3] return     0   1   
////具体c代码中对应的指令/////
OP_LOADK,/* A Bx    R(A) := Kst(Bx)                 */
OP_MOVE,/*  A B R(A) := R(B)                    */
////在结合上面c指令来理解上面的反汇编代码////
loadk      0   0 等价与 R(0) = Kst(0),表示把常量表0索引处的内容移动到寄存器0中
move       1   0 等价与 R(1) = R(0),表示把寄存器0中的内容移动到寄存器1中
  • 全局变量指令
a = 2
local b = a
/////反汇编后 汇编如下////
; (1)  a = 2
002C  01400000           [1] loadk      0   1        ; 2
0030  07000000           [2] setglobal  0   0        ; a
; (2)  local b = a
0034  05000000           [3] getglobal  0   0        ; a
////具体c代码中对应的指令/////
OP_GETGLOBAL,/* A Bx    R(A) := Gbl[Kst(Bx)]                */
OP_SETGLOBAL,/* A Bx    Gbl[Kst(Bx)] := R(A)                */

////在结合上面c指令来理解上面的反汇编代码////

这里a = 2这个全局变量赋值,生产汇编就变成了两条指令了,先把常量表中的值加载到寄存器然后在设置到全局变量表中。
 local b = a 把全局变量赋值给局部变量,就有了一条OP_GETGLOBAL指令。
这个很像没有任何优化的c语言汇编生成的方式

  • 表操作
    1.创建表
local t0 = {}           --创建空表
local t1 = {1,2}        --创建只有数组部分的表
local t2 = {[2] = 3}    --创建只有哈希部分的表
/////反汇编后 汇编如下////
; (1)  local t0 = {}           --创建空表
002C  0A000000           [1] newtable   0   0   0    ; array=0, hash=0
; (2)  local t1 = {1,2}        --创建只有数组部分的表
0030  4A000001           [2] newtable   1   2   0    ; array=2, hash=0
0034  81000000           [3] loadk      2   0        ; 1
0038  C1400000           [4] loadk      3   1        ; 2
003C  62400001           [5] setlist    1   2   1    ; index 1 to 2
; (3)  local t2 = {[2] = 3}    --创建只有哈希部分的表
0040  8A400000           [6] newtable   2   0   1    ; array=0, hash=1
0044  8980C080           [7] settable   2   257 258  ; 2 3
////具体c代码中对应的指令/////
OP_NEWTABLE,/*  A B C   R(A) := {} (size = B,C)             */
OP_SETTABLE,/*  A B C   R(A)[RK(B)] := RK(C)                */
OP_SETLIST,/*   A B C   R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B    */

////在结合上面c指令来理解上面的反汇编代码////

创建t0表的指令只有一个newtable指令,参数B和C都为空,因为数组和哈希部分都是空的,
创建t1表的指令 newtable   1   2   0 ,这里B参数为2表示数组部分大小为2,
下面两行loadK分别从常量表中取出1和2数据放入寄存器2和寄存器3中,
最后从setlist指令可以看到参数A表示指向表的寄存器,B表示参数的个数,C先不用管,
我们可以推倒出栈的分布情况,栈底存储了表的索引,往上分布是两个参数。
接着看local t2 = {[2] = 3},这个首先newtable   2   0   1,表示哈希部分大小为1,接着通过settable 指令来设置key和value,RK这个宏可以自动判断是否常量表还是寄存器,这里毫无疑问是常量表中的,257这个值对应常量表中1的位置。

所以当我们使用lua的时候,数组部分初始化可以写在定义的地方,而不是下面在赋值,因为这样可以最后减少lua编译器生成的指令数量。

2.查询表

local t = {["b"] = 10}  --初始化表,哈希部分b->10
local v = t[b]          --取出哈希部分的b对应的值赋值给v
/////反汇编后 汇编如下////
; (1)  local t = {["b"] = 10}  --初始化表,哈希部分b->10
002C  0A400000           [1] newtable   0   0   1    ; array=0, hash=1
0030  09404080           [2] settable   0   256 257  ; "b" 10
; (2)  local v = t["b"]          --取出哈希部分的b对应的值赋值给v
0034  46004000           [3] gettable   1   0   256  ; "b"
0038  1E008000           [4] return     0   1     

////在结合上面c指令来理解上面的反汇编代码////
OP_GETTABLE,/*  A B C   R(A) := R(B)[RK(C)]             */

开始创建表并初始化的表就不解释了,说说gettable指令 A的值为1表示最后这个值用寄存器1接收,B表示表寄存器的位置,C的值为256表示,RK(256)得到产量表的中的值作为key获取值,最后赋值到寄存器1中。

3.元表
元表本身没有对应的指令,是通过lua api实现的。
先来看看元表的一些用法,参考我之前写的一篇lua-元表接着我来分析源码

//从表中查找数据
void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) {
  int loop;
  //这里支持多层查找,类似于多层继承 因为每次把查找到的tm重新赋值给t
  for (loop = 0; loop < MAXTAGLOOP; loop++) {
    const TValue *tm;
    //如果t是表,就先从哈希中查找,查找不到在通过
    if (ttistable(t)) {  /* `t' is a table? */
      Table *h = hvalue(t);
      //从哈希中查找 如果找不到通过fasttm查找,即是通过元表中index索引中查找
      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 */
    }
    //不是表也有元表,比如number或者string,所有的number或者string公用一个元表 userdata也拥有元表
    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");
}


#define fasttm(l,et,e)  gfasttm(G(l), et, e)

#define gfasttm(g,et,e) ((et) == NULL ? NULL : \
  ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e]))

//通过ename字符串的key来查找表中的元表 这里flags标记如果查找不到,
//下次查找时给上述的gfasttm宏,快速返回了
const TValue *luaT_gettm (Table *events, TMS event, TString *ename) {
  const TValue *tm = luaH_getstr(events, ename);
  lua_assert(event <= TM_EQ);
  if (ttisnil(tm)) {  /* no tag method? */
    events->flags |= cast_byte(1u<

有了上述代码的基础后,来对应下lua使用的代码

--继承关系 base->obj->test
local base = {basekey = "i am base key"}
local obj = {objkey = "i am obj key"}
setmetatable(obj,{__index = base,__newindex = base})
local test = {}
setmetatable(test,{__index = obj,__newindex = obj})

print(test.objkey)
print(test.basekey)

test.objkey = "i am new obj key"
test.basekey = "i am new base key"

print(test.objkey)
print(test.basekey)

--最后打印结果为
--i am obj key
--i am base key
--i am new obj key
--i am new base key

这样就容易理解,lua代码背后到底发生什么了。

你可能感兴趣的:(lua解释器(变量以及表))