美文网首页
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)

    先来看看lua闭包的定义这个部分代码注释都在我github上 可以观察lua闭包,不仅包含proto还包含 Up...

  • lua 5.3.4 GC的变化-UpVal变为引用计数管理

    Lua 5.1.4 Lua 5.3.4 UpVal 要注意的是: UpVal 结构中去掉了 GCObject 的通...

  • lua解释器(变量以及表)

    本篇主要来结合着lua的代码,配合lua源码来阅读lua的汇编代码,具体反汇编工具可以用ChunkSpy这个工具,...

  • Lua中的CAPI概述

    头文件lua.h: Lua提供的基础函数,包括创建Lua环境,调用Lua函数,读写Lua环境中的全局变量,以及注册...

  • lua入门笔记 目录

    lua的中文API lua入门笔记1 类型 表达式 语句 函数lua入门笔记2 深入函数 深入函数 迭代器与泛型f...

  • Lua语言学习教程

    lua闭包 函数尾调用 迭代器

  • tolua调用c#函数及变量

    比如说你要调用一个lua函数 但你想把自身传给lua函数让这个函数调用c# 类的其他函数以及变量 首先是调用lua...

  • Redis 脚本

    Redis 脚本 Redis 脚本使用 Lua 解释器来执行脚本。 Reids 2.6 版本通过内嵌支持 Lua ...

  • 基础: Lua引擎组成

    Lua引擎组成 1.Lua核心模块:虚拟机、编译器/解释器、GC、标准库、内嵌辅助库、C Api。 1).虚拟机核...

  • Redis学习之路(8)命令 -Redis 脚本

    Redis 脚本使用 Lua 解释器来执行脚本。 Redis 2.6 版本通过内嵌支持 Lua 环境。执行脚本的常...

网友评论

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

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