美文网首页
lua解释器(函数以及upval)

lua解释器(函数以及upval)

作者: Teech | 来源:发表于2020-02-23 23:04 被阅读0次

    先来看看lua闭包的定义
    这个部分代码注释都在我github

    #define ClosureHeader \
        CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \
        struct Table *env
    
    //c闭包
    typedef struct CClosure {
      ClosureHeader;
      lua_CFunction f;    //这里是c函数指针
      TValue upvalue[1];  //这里为什么只有一个upvalue的值??
    } CClosure;
    
    //lua闭包
    typedef struct LClosure {
      ClosureHeader;
      struct Proto *p;  //编译的指令
      UpVal *upvals[1]; //保存的upvalue
    } LClosure;
    

    可以观察lua闭包,不仅包含proto还包含 UpVal 这个指向上一层的局部变量,下面通过例子来解释。

    local l0 = 3
    function fun1()
        local l1 = 10
        function fun2()
            l1 = l0
        end
    end
    /////反汇编后 汇编如下////
    ; (1)  local l0 = 3
    002C  01000000           [1] loadk      0   0        ; 3
    
    0030  64000000           [2] closure    1   0        ; 1 upvalues
    0034  00000000           [3] move       0   0      
    ; (2)  function fun1()
    0038  47400000           [4] setglobal  1   1        ; fun1
    ; (7)  end
    003C  1E008000           [5] return     0   1  
    
    ; (3)      local l1 = 10
    0077  01000000           [1] loadk      0   0        ; 10
    ; (6)      end
    
    007B  64000000           [2] closure    1   0        ; 2 upvalues
    007F  00000000           [3] move       0   0      
    0083  04000000           [4] getupval   0   0        ; l0
    ; (4)      function fun2()
    0087  47400000           [5] setglobal  1   1        ; fun2
    ; (7)  end
    008B  1E008000           [6] return     0   1     
    
    ; (5)          l1 = l0
    00C6  04008000           [1] getupval   0   1        ; l0
    00CA  08000000           [2] setupval   0   0        ; l1
    
    ////具体c代码中对应的指令/////
    OP_CLOSURE,/*   A Bx    R(A) := closure(KPROTO[Bx], R(A), ... ,R(A+n))  
    OP_SETUPVAL,/*  A B UpValue[B] := R(A)              */
    OP_GETUPVAL,/*  A B R(A) := UpValue[B]              */
    
    ////在结合上面c指令来理解上面的反汇编代码////
    每次产生一个闭包的时候产生了2条指令closure+move 或者closure+move+getupval,这里的move指令会进行特殊解释,其实看上述lua代码可以得出如果有引用到之前的upvalue会调用getupval,getupval指令就是把之前引用的upval对象复制到当前函数栈上,这里并没产生汇编指令设置upvalue。
    
    • 函数调用过程(准备) luaD_precall
      这里为了说明方便,短一点 ,只保留核心代码,代码的解释在注释上
    int luaD_precall (lua_State *L, StkId func, int nresults) {
      LClosure *cl;
      funcr = savestack(L, func);
      cl = &clvalue(func)->l;
      //上层的调用链节点保存pc指针
      L->ci->savedpc = L->savedpc;
        CallInfo *ci;
        StkId st, base;
        Proto *p = cl->p;
        func = restorestack(L, funcr);
        //不是可变参数 压入参数入栈
        ci = inc_ci(L);  /* now `enter' new function */
        ci->func = func;//设置调用函数
        L->base = ci->base = base;//设置调用栈的base指针
        ci->top = L->base + p->maxstacksize;//设置调用栈的top指针
        //保存pc指针 code就是字节码序列指针
        L->savedpc = p->code;  /* starting point */ //pc指针
        ci->tailcalls = 0;
        ci->nresults = nresults;
        //多余的参数设置为nil
        for (st = L->top; st < ci->top; st++)
          setnilvalue(st);
        L->top = ci->top;
        return PCRLUA;
    }
    
    • 函数调用过程(执行) luaV_execute 这个就是解析器主入口,执行L->savepc指针指向的一条条指令

    • 函数调用过程(执行完毕)进行恢复工作

    //恢复上一层调用信息,并设置好返回值以及处理返回值信息
    int luaD_poscall (lua_State *L, StkId firstResult) {
      StkId res;
      int wanted, i;
      CallInfo *ci;
      if (L->hookmask & LUA_MASKRET)
        firstResult = callrethooks(L, firstResult);
      //ci 减一,恢复上一层调用信息
      ci = L->ci--;
      res = ci->func;  /* res == final position of 1st result */
      //设置返回参数 恢复pc指针以及栈信息
      wanted = ci->nresults;
      L->base = (ci - 1)->base;  /* restore base */
      L->savedpc = (ci - 1)->savedpc;  /* restore savedpc */
      /* move results to correct place */
      for (i = wanted; i != 0 && firstResult < L->top; i--)
        setobjs2s(L, res++, firstResult++);
      while (i-- > 0)
        setnilvalue(res++);
      L->top = res;
      return (wanted - LUA_MULTRET);  /* 0 iff wanted == LUA_MULTRET */
    }
    
    • upval的处理
      先来看看lua虚拟机中对OP_CLOSURE指令的处理
          //闭包指令
          case OP_CLOSURE: {
            Proto *p;
            Closure *ncl;
            int nup, j;
            p = cl->p->p[GETARG_Bx(i)];
            nup = p->nups;
            ncl = luaF_newLclosure(L, nup, cl->env);
            ncl->l.p = p;
            //根据upvalue的数量 做特定的解释,可能跟着后面的mov指令也做特别的解释
            for (j=0; j<nup; j++, pc++) {
              if (GET_OPCODE(*pc) == OP_GETUPVAL)
                ncl->l.upvals[j] = cl->upvals[GETARG_B(*pc)]; //这里传递指针
              else {
                lua_assert(GET_OPCODE(*pc) == OP_MOVE);
                ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc));
              }
            }
            setclvalue(L, ra, ncl);
            Protect(luaC_checkGC(L));
            continue;
          }
    

    nup参数表示当前闭包引用upval的数量,for循环里紧跟着OP_GETUPVAL以及OP_MOVE指令,这里把OP_MOVE指令特殊特殊化解释了 通过luaF_findupval所需要的upval

    UpVal *luaF_findupval (lua_State *L, StkId level) {
      global_State *g = G(L);
      //pp指针指向当前虚拟机的openupval
      GCObject **pp = &L->openupval;
      UpVal *p;
      UpVal *uv;
      //遍历虚拟机的openupva链表,查找等于该level的节点
      //openupval链表是从大到小排序的链表 代表函数调用的不同层次
      while (*pp != NULL && (p = ngcotouv(*pp))->v >= level) {
        lua_assert(p->v != &p->u.value);
        if (p->v == level) {  /* found a corresponding upvalue? */
          if (isdead(g, obj2gco(p)))  /* is it dead? */
            changewhite(obj2gco(p));  /* ressurect it */
          return p;
        }
        pp = &p->next;
      }
      //找不到创建一个upval
      uv = luaM_new(L, UpVal);  /* not found: create a new one */
      uv->tt = LUA_TUPVAL;
      uv->marked = luaC_white(g);
      //设置当前调用的层级
      uv->v = level;  /* current value lives in the stack */
      //挂在入openupval链表
      uv->next = *pp;  /* chain it in the proper position */
      *pp = obj2gco(uv);
      uv->u.l.prev = &g->uvhead;  /* double link it in `uvhead' list */
      uv->u.l.next = g->uvhead.u.l.next;
      uv->u.l.next->u.l.prev = uv;
      g->uvhead.u.l.next = uv;
      lua_assert(uv->u.l.next->u.l.prev == uv && uv->u.l.prev->u.l.next == uv);
      return uv;
    }
    

    找不到时,创建一个upval挂载入虚拟机的openupval,最后当函数执行完毕会调用luaF_close

    //关闭函数
    void luaF_close (lua_State *L, StkId level) {
      UpVal *uv;
      global_State *g = G(L);
      while (L->openupval != NULL && (uv = ngcotouv(L->openupval))->v >= level) {
        GCObject *o = obj2gco(uv);
        lua_assert(!isblack(o) && uv->v != &uv->u.value);
        //把高于level的openupval中的upval都移除
        L->openupval = uv->next;  /* remove from `open' list */
        if (isdead(g, o))
          luaF_freeupval(L, uv);  /* free upvalue */
        else {
          //解除引用 从开状态切换到闭状态,这个时候
          unlinkupval(uv);
          //值保存到v中 这个时候闭状态了,并不指向虚拟机的openupval中
          //这里内存就多了一份拷贝了,在虚拟机执行过程中,未关闭函数时,都指向同一份内存也就是在openupval中的内存
          //关闭后就多一份拷贝,upval就处于关闭状态
          setobj(L, &uv->u.value, uv->v);
          uv->v = &uv->u.value;  /* now current value lives here */
          luaC_linkupval(L, uv);  /* link upvalue into `gcroot' list */
        }
      }
    }
    

    其实这里可以发现函数没有关闭时,引用的内存还是openupval上,关闭后就重新赋值了一份,这个时候upval就不是共享的了,每个闭包一份了。
    因为有upval的存在这个也是因为lua比较难正确性热更新的原因。

    相关文章

      网友评论

          本文标题:lua解释器(函数以及upval)

          本文链接:https://www.haomeiwen.com/subject/llmsqhtx.html