Lua中的函数(或Function),其实应该是闭包(Closure),闭包能够认为是函数+外部变量,这里为了简单没有做区分,函数原型(或Proto)能够认为是函数的静态表示。数组
函数与函数原型的关系有点相似系统中进程与程序,一个程序被屡次启动会建立多个进程。一个Proto能够被此加载建立多个函数。Proto是静态的,Function是动态的,在Lua中调用一个函数,老是先依照Proto建立一个函数对象,再执行这个函数对象。闭包
函数原型表明了一个函数定义,是解释器编译lua代码获得的,包括一系列lua指令、常量等的集合。函数
GCProto结构体表明了一个函数的原型,结构体的组成以下所示。布局
typedef struct GCproto { GCHeader; uint8_t numparams; /* Number of parameters. */ uint8_t framesize; /* Fixed frame size. */ MSize sizebc; /* Number of bytecode instructions. */ #if LJ_GC64 uint32_t unused_gc64; #endif GCRef gclist; MRef k; /* Split constant array (points to the middle). */ MRef uv; /* Upvalue list. local slot|0x8000 or parent uv idx. */ MSize sizekgc; /* Number of collectable constants. */ MSize sizekn; /* Number of lua_Number constants. */ MSize sizept; /* Total size including colocated arrays. */ uint8_t sizeuv; /* Number of upvalues. */ uint8_t flags; /* Miscellaneous flags (see below). */ uint16_t trace; /* Anchor for chain of root traces. */ /* ------ The following fields are for debugging/tracebacks only ------ */ GCRef chunkname; /* Name of the chunk this function was defined in. */ BCLine firstline; /* First line of the function definition. */ BCLine numline; /* Number of lines for the function definition. */ MRef lineinfo; /* Compressed map from bytecode ins. to source line. */ MRef uvinfo; /* Upvalue names. */ MRef varinfo; /* Names and compressed extents of local variables. */ } GCproto;
GCProto结构体主要成员ui
函数原型的内存布局以下图所示this
LuaJIT中函数(Function)的定义以下lua
/* -- Function object (closures) ------------------------------------------ */ /* Common header for functions. env should be at same offset in GCudata. */ #define GCfuncHeader \ GCHeader; uint8_t ffid; uint8_t nupvalues; \ GCRef env; GCRef gclist; MRef pc typedef struct GCfuncC { GCfuncHeader; lua_CFunction f; /* C function to be called. */ TValue upvalue[1]; /* Array of upvalues (TValue). */ } GCfuncC; typedef struct GCfuncL { GCfuncHeader; GCRef uvptr[1]; /* Array of _pointers_ to upvalue objects (GCupval). */ } GCfuncL; typedef union GCfunc { GCfuncC c; GCfuncL l; } GCfunc;
GCfunc是一个union,封装了Lua函数和C函数,这里只关注Lua函数GCfuncL,GCfuncHeader是通用的函数头部,其中.net
GCfuncL还有另外一个成员,GCfunc upptr[1]
指向upvalue对象的数组。debug
函数中操做的数据由三种指针
如下面的代码为例
local code = 2 local function output() local a = “code:" .. tostring(code) print(a) end
对于函数output来讲,
code
是upvalue,由于是在函数外层定义的"a"
是局部变量2
和"code:"
是常量,会保存在Proto中print
是全局变量(若是引用了一个变量,而这两变量不是局部变量,如a,也不是在函数外层定义的,如code,编译器便会把他看成全局变量),依照Proto建立一个函数对象有两种状况
加载一个Lua脚本时,首先会调用lj_parse解析脚本生成一个Proto结构体,而后调用lj_func_newL_empty建立一个Lua函数而后执行,经过解析Lua脚本生成的函数是非嵌套函数,是没有upvalue的。
/* Create a new Lua function with empty upvalues. */ GCfunc *lj_func_newL_empty(lua_State *L, GCproto *pt, GCtab *env) { GCfunc *fn = func_newL(L, pt, env); MSize i, nuv = pt->sizeuv; /* NOBARRIER: The GCfunc is new (marked white). */ for (i = 0; i < nuv; i++) { GCupval *uv = func_emptyuv(L); int32_t v = proto_uv(pt)[i]; uv->immutable = ((v / PROTO_UV_IMMUTABLE) & 1); uv->dhash = (uint32_t)(uintptr_t)pt ^ (v << 24); setgcref(fn->l.uvptr[i], obj2gco(uv)); } fn->l.nupvalues = (uint8_t)nuv; return fn; }
FNEW
字节码指令建立对于在Lua脚本中定义的函数,加载时只会生成相应的Proto,建立函数对象的操做隐藏在字节码指令中,Lua解释器会在函数调用以前插入一个FNEW
的指令,这条指令会依照Proto建立相应的函数对象。
相应的逻辑在lj_func_new_gc中,与前一种方式最大的不一样在于须要加载Upvalue,也须要继承父函数的环境。
/* Do a GC check and create a new Lua function with inherited upvalues. */ GCfunc *lj_func_newL_gc(lua_State *L, GCproto *pt, GCfuncL *parent) { GCfunc *fn; GCRef *puv; MSize i, nuv; TValue *base; lj_gc_check_fixtop(L); fn = func_newL(L, pt, tabref(parent->env)); /* NOBARRIER: The GCfunc is new (marked white). */ puv = parent->uvptr; nuv = pt->sizeuv; base = L->base; for (i = 0; i < nuv; i++) { uint32_t v = proto_uv(pt)[i]; GCupval *uv; if ((v & PROTO_UV_LOCAL)) { uv = func_finduv(L, base + (v & 0xff)); uv->immutable = ((v / PROTO_UV_IMMUTABLE) & 1); uv->dhash = (uint32_t)(uintptr_t)mref(parent->pc, char) ^ (v << 24); } else { uv = &gcref(puv[v])->uv; } setgcref(fn->l.uvptr[i], obj2gco(uv)); } fn->l.nupvalues = (uint8_t)nuv; return fn; }