先来看看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比较难正确性热更新的原因。
网友评论