Lua5.3 VM 分析(七)生成闭包

来源:互联网 发布:已备案域名已删除 编辑:程序博客网 时间:2024/05/29 08:42

Lua5.3 VM 分析(七)生成闭包

在Lua 中,函数是一等公民。一切代码都是函数,准确的说是闭包。当我们执行一段程序时,其实就是调用一个函数。加载一个库,也是调用一个函数。加载一个Lua 源文件,里面即使定义了很多 Lua 函数,但是 它整体依旧是单个函数。

所以,每段完整的字节码都是一个Lua 函数。而每个函数里可以附有很多个函数原型 Proto。函数原型 Proto 没有放在常量表中,而是单独成表。这是因为,它们不可以被Lua 代码直接使用。只有 Proto 和 upvalue 绑定在一起时,形成闭包,才是 Lua VM 可以处理的 执行对象。

函数原型在生成包含它们的函数的代码被加载时,在内存中生成 Proto 对象。单个函数原型可以被多次绑定,生成多个闭包对象。这个过程由 CLOSURE 操作完成。

vmcase(OP_CLOSURE) {                printf("OP_CLOSURE  \n");                Proto *p = cl->p->p[GETARG_Bx(i)];                LClosure *ncl = getcached(p, cl->upvals, base);  /* cached closure */                if (ncl == NULL)  /* no match? */                    pushclosure(L, p, cl->upvals, base, ra);  /* create a new one */                else                    setclLvalue(L, ra, ncl);  /* push cashed closure */                checkGC(L, ra + 1);                vmbreak;            }

在生成闭包的过程中,首先调用 getcached 函数,从缓存中取上次生成的闭包,如果可能,就重复利用。这对函数式编程特别有效,因为当你返回一个没有任何 upvalue 的纯函数,或是只绑定有全局变量的函数时,不会生成新的闭包 实例。

getcached 函数 用来检查是否有缓存过的闭包,以及这次需要绑定的 upvalue 是否和缓存中的 proto 一致。

static LClosure *getcached (Proto *p, UpVal **encup, StkId base) {    LClosure *c = p->cache;    if (c != NULL) {  /* is there a cached closure? */        int nup = p->sizeupvalues;        Upvaldesc *uv = p->upvalues;        int i;        for (i = 0; i < nup; i++) {  /* check whether it has right upvalues */            TValue *v = uv[i].instack ? base + uv[i].idx : encup[uv[i].idx]->v;            if (c->upvals[i]->v != v)                return NULL;  /* wrong upvalue; cannot reuse closure */        }    }    return c;  /* return cached closure (or NULL if no cached closure) */}

函数原型 Proto 中记录了 upvalue的描述信息 Upvaldesc 结构 用来让 upvalue 可以比较。

typedef struct Upvaldesc {    TString *name;  /* upvalue name (for debug information) */    lu_byte instack;  /* whether it is in stack */    lu_byte idx;  /* index of upvalue (in stack or in outer function's list) */} Upvaldesc;

比较引用的 upvalue是否相同,按instack 标记分开处理。
相同的 upvalue 地址也一定相同。全部 upvalue 都一致的情况下,说明缓存的闭包就可以复用。此时,只需要调用 setclLvalue 把他赋给 ra 即可。

反之,缓存闭包不可复用,需要调用 pushclosure 函数生成一个新的闭包,并更新缓存。

static void pushclosure (lua_State *L, Proto *p, UpVal **encup, StkId base,                         StkId ra) {    int nup = p->sizeupvalues;    Upvaldesc *uv = p->upvalues;    int i;    LClosure *ncl = luaF_newLclosure(L, nup);    ncl->p = p;    setclLvalue(L, ra, ncl);  /* anchor new closure in stack */    for (i = 0; i < nup; i++) {  /* fill in its upvalues */        if (uv[i].instack)  /* upvalue refers to local variable? */            ncl->upvals[i] = luaF_findupval(L, base + uv[i].idx);        else  /* get upvalue from enclosing function */            ncl->upvals[i] = encup[uv[i].idx];        ncl->upvals[i]->refcount++;        /* new closure is white, so we do not need a barrier here */    }    if (!isblack(p))  /* cache will not break GC invariant? */        p->cache = ncl;  /* save it on cache for reuse */}

绑定 upvalue 生成闭包过程 之后,在函数最后,在更新 cache 指针之前,需要调用判断 Proto 是否需要 GC。

原创粉丝点击