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; }