以下内容是脚本文件:package.lua
#1 -- Note: This file is loaded automatically by the engine. #2 #3 local mt = { #4 __index = function(self, k) #5 if k ~= "__tostring" then #6 error("Tried to access an empty package", 2) #7 end #8 end, #9 __newindex = function() #10 error("Tried to access an empty package", 2) #11 end, #12 __metatable = "empty package", #13 __tostring = function() return "{empty package}" end, #14 } #15 local empty_pkg = setmetatable({}, mt) #16 #17 local function global() #18 function funcA(a, b) #19 return a + b + 10 #20 end #21 local c = funcA(30, 40) #22 end #23 #24 -- TODO: Currently if you require a file by different (relative) paths, each will be a different copy. #25 function wesnoth.require(pkg_name) #26 global() #27 end #28 #29 return empty_pkg
一、package.lua对应的Proto
从package.lua解析出的函数原型。以下的Proto内容在执行完lua_load(L, , , "@lua/package.lua", "t")后就会生成,存储在L,不必等lua_pcall去执行@lua/package.lua的顶层lua脚本函数。
---source:@lua/package.lua--- linedefined: 0 lastlinedefined: 0 numparams: 0 is_vararg: 1 maxstacksize: 5 sizecode: 21 0 L#1 VARARGPREP(81) (iABC) [A]0 [k]0 [B]0 [C]0 1 L#3 NEWTABLE(19) (iABC) [A]0 [k]0 [B]3 [C]0 2 L#3 EXTRAARG(82) (iAx) [Ax]0 3 L#8 CLOSURE(79) (iABx) [A]1 [Bx]0 4 L#8 SETFIELD(18) (iABC) [A]0 [k]0 [B]0 [C]1 5 L#11 CLOSURE(79) (iABx) [A]1 [Bx]1 6 L#11 SETFIELD(18) (iABC) [A]0 [k]0 [B]1 [C]1 7 L#12 SETFIELD(18) (iABC) [A]0 [k]1 [B]2 [C]3 8 L#13 CLOSURE(79) (iABx) [A]1 [Bx]2 9 L#13 SETFIELD(18) (iABC) [A]0 [k]0 [B]4 [C]1 10 L#15 GETTABUP(11) (iABC) [A]1 [k]0 [B]0 [C]5 11 L#15 NEWTABLE(19) (iABC) [A]2 [k]0 [B]0 [C]0 12 L#15 EXTRAARG(82) (iAx) [Ax]0 13 L#15 MOVE(0) (iABC) [A]3 [k]0 [B]0 [C]0 14 L#15 CALL(68) (iABC) [A]1 [k]0 [B]3 [C]2 15 L#22 CLOSURE(79) (iABx) [A]2 [Bx]3 16 L#25 GETTABUP(11) (iABC) [A]3 [k]0 [B]0 [C]6 17 L#27 CLOSURE(79) (iABx) [A]4 [Bx]4 18 L#25 SETFIELD(18) (iABC) [A]3 [k]0 [B]7 [C]4 19 L#29 RETURN(70) (iABC) [A]1 [k]1 [B]2 [C]1 20 L#29 RETURN(70) (iABC) [A]3 [k]1 [B]1 [C]1 sizek: 8 0 [VSHRSTR]__index 1 [VSHRSTR]__newindex 2 [VSHRSTR]__metatable 3 [VSHRSTR]empty package 4 [VSHRSTR]__tostring 5 [VSHRSTR]setmetatable 6 [VSHRSTR]wesnoth 7 [VSHRSTR]require sizeupvalues: 1 0 _ENV [instack]true [idx]0 [kind]0 sizep: 5 [1/5]---source:@lua/package.lua--- linedefined: 4 lastlinedefined: 8 numparams: 2 is_vararg: 0 maxstacksize: 5 sizecode: 7 0 L#5 EQK(60) (iABC) [A]1 [k]1 [B]0 [C]0 1 L#5 JMP(56) (isJ) [sJ]4 2 L#6 GETTABUP(11) (iABC) [A]2 [k]0 [B]0 [C]1 3 L#6 LOADK(3) (iABx) [A]3 [Bx]2 4 L#6 LOADI(1) (iAsBx) [A]4 [sBx]2 5 L#6 CALL(68) (iABC) [A]2 [k]0 [B]3 [C]1 6 L#8 RETURN0(71) (iABC) [A]2 [k]0 [B]1 [C]0 sizek: 3 0 [VSHRSTR]__tostring 1 [VSHRSTR]error 2 [VSHRSTR]Tried to access an empty package sizeupvalues: 1 0 _ENV [instack]false [idx]0 [kind]0 sizep: 0 sizeabslineinfo: 0 sizelocvars: 2 0 self [startpc]0 [endpc]7 1 k [startpc]0 [endpc]7 ============ [2/5]---source:@lua/package.lua--- linedefined: 9 lastlinedefined: 11 numparams: 0 is_vararg: 0 maxstacksize: 3 sizecode: 5 0 L#10 GETTABUP(11) (iABC) [A]0 [k]0 [B]0 [C]0 1 L#10 LOADK(3) (iABx) [A]1 [Bx]1 2 L#10 LOADI(1) (iAsBx) [A]2 [sBx]2 3 L#10 CALL(68) (iABC) [A]0 [k]0 [B]3 [C]1 4 L#11 RETURN0(71) (iABC) [A]0 [k]0 [B]1 [C]0 sizek: 2 0 [VSHRSTR]error 1 [VSHRSTR]Tried to access an empty package sizeupvalues: 1 0 _ENV [instack]false [idx]0 [kind]0 sizep: 0 sizeabslineinfo: 0 sizelocvars: 0 ============ [3/5]---source:@lua/package.lua--- linedefined: 13 lastlinedefined: 13 numparams: 0 is_vararg: 0 maxstacksize: 2 sizecode: 3 0 L#13 LOADK(3) (iABx) [A]0 [Bx]0 1 L#13 RETURN1(72) (iABC) [A]0 [k]0 [B]2 [C]0 2 L#13 RETURN0(71) (iABC) [A]0 [k]0 [B]1 [C]0 sizek: 1 0 [VSHRSTR]{empty package} sizeupvalues: 0 sizep: 0 sizeabslineinfo: 0 sizelocvars: 0 ============ [4/5]---source:@lua/package.lua--- linedefined: 17 lastlinedefined: 22 numparams: 0 is_vararg: 0 maxstacksize: 3 sizecode: 7 0 L#20 CLOSURE(79) (iABx) [A]0 [Bx]0 1 L#18 SETTABUP(15) (iABC) [A]0 [k]0 [B]0 [C]0 2 L#21 GETTABUP(11) (iABC) [A]0 [k]0 [B]0 [C]0 3 L#21 LOADI(1) (iAsBx) [A]1 [sBx]30 4 L#21 LOADI(1) (iAsBx) [A]2 [sBx]40 5 L#21 CALL(68) (iABC) [A]0 [k]0 [B]3 [C]2 6 L#22 RETURN0(71) (iABC) [A]1 [k]0 [B]1 [C]0 sizek: 1 0 [VSHRSTR]funcA sizeupvalues: 1 0 _ENV [instack]false [idx]0 [kind]0 sizep: 1 [1/1]---source:@lua/package.lua--- linedefined: 18 lastlinedefined: 20 numparams: 2 is_vararg: 0 maxstacksize: 3 sizecode: 6 0 L#19 ADD(34) (iABC) [A]2 [k]0 [B]0 [C]1 1 L#19 MMBIN(46) (iABC) [A]0 [k]0 [B]1 [C]6 2 L#19 ADDI(21) (iABC) [A]2 [k]0 [B]2 [C]137 3 L#19 MMBINI(47) (iABC) [A]2 [k]0 [B]137 [C]6 4 L#19 RETURN1(72) (iABC) [A]2 [k]0 [B]2 [C]0 5 L#20 RETURN0(71) (iABC) [A]2 [k]0 [B]1 [C]0 sizek: 0 sizeupvalues: 0 sizep: 0 sizeabslineinfo: 0 sizelocvars: 2 0 a [startpc]0 [endpc]6 1 b [startpc]0 [endpc]6 ============ sizeabslineinfo: 0 sizelocvars: 1 0 c [startpc]6 [endpc]7 ============ [5/5]---source:@lua/package.lua--- linedefined: 25 lastlinedefined: 27 numparams: 1 is_vararg: 0 maxstacksize: 2 sizecode: 3 0 L#26 GETUPVAL(9) (iABC) [A]1 [k]0 [B]0 [C]0 1 L#26 CALL(68) (iABC) [A]1 [k]0 [B]1 [C]1 2 L#27 RETURN0(71) (iABC) [A]1 [k]0 [B]1 [C]0 sizek: 0 sizeupvalues: 1 0 global [instack]true [idx]2 [kind]0 sizep: 0 sizeabslineinfo: 0 sizelocvars: 1 0 pkg_name [startpc]0 [endpc]3 ============ sizeabslineinfo: 0 sizelocvars: 3 0 mt [startpc]10 [endpc]21 1 empty_pkg [startpc]15 [endpc]21 2 global [startpc]16 [endpc]21 ============
二、主Proto的虚拟机指令
解释package.lua中第一个Proto中的虚拟机指令
- A、k、B、C、Ax、Bx,这些参数不是在生成时就肯定正确的,有些参数需要后面补上正确值。举个例子,NEWTABLE,生成时A、k、B、C都是0,在语法分析到表结束后,luaK_settablesize(...)会补上正确值
================================================
- 0 L#1 VARARGPREP(81) (iABC) [A]0 [k]0 [B]0 [C]0
- 1 L#3 NEWTABLE(19) (iABC) [A]0 [k]0 [B]3 [C]0
新加入时,B是0,那3应该是后面补。那什么时候补呢?luaK_settablesize。B:构造时“ceillog2(记录式单元数)+1”,否则0。C:构造时“数组式单元数%(MAXARG_C+1)”。k:数组式单元数/MAXARG_C+1。NEWTABLE后总会有一个EXTRAARG。 - 2 L#3 EXTRAARG(82) (iAx) [Ax]0
- 3 L#8 CLOSURE(79) (iABx) [A]1 [Bx]0
lex遇到function的end,生成一个闭包。R[1] := closure(KPROTO[0]) - 4 L#8 SETFIELD(18) (iABC) [A]0 [k]0 [B]0 [C]1
设置表的key=value。值从栈中取(k=0)。
R[0]["__index"] := R[1]。一直到这里,R[1]只是lua闭包,并没有调用lua闭包中函数。当访问不存在的单元时,才会调用元方法__index。 - 5 L#11 CLOSURE(79) (iABx) [A]1 [Bx]1
lex遇到function的end,生成一个闭包。R[1] := closure(KPROTO[1]) - 6 L#11 SETFIELD(18) (iABC) [A]0 [k]0 [B]1 [C]1
设置表的key=value。值从栈中取(k=0)。
R[0]["__newindex"] := R[1] - 7 L#12 SETFIELD(18) (iABC) [A]0 [k]1 [B]2 [C]3
设置表的key=value。值从常量中取(k=1)。 - 8 L#13 CLOSURE(79) (iABx) [A]1 [Bx]2
lex遇到function的end,生成一个闭包。R[1] := closure(KPROTO[2]) - 9 L#13 SETFIELD(18) (iABC) [A]0 [k]0 [B]4 [C]1
设置表的key=value。值从栈中取(k=0)。注:遇到},是表定义结束符,会个修正和该表相关的指令的,即更新pc=1、pc=2中的指令参数。 - 10 L#15 GETTABUP(11) (iABC) [A]1 [k]0 [B]0 [C]5
R[A] := UpValue[B][K[C]:string] => R[1] = UpValue[0][K[5]] = UpValue[0]["setmetatable"]
从上值中取,该值放入R[1],是如何由"setmetatable"得到C=5的,这涉及到如何在全局变量区搜索变量,参考后文“三、GETTABUP(11) (iABC) [A]1 [k]0 [B]0 [C]5”)。 - 11 L#15 NEWTABLE(19) (iABC) [A]2 [k]0 [B]0 [C]0
- 12 L#15 EXTRAARG(82) (iAx) [Ax]0
- 13 L#15 MOVE(0) (iABC) [A]3 [k]0 [B]0 [C]0
- 14 L#15 CALL(68) (iABC) [A]1 [k]0 [B]3 [C]2
- 15 L#22 CLOSURE(79) (iABx) [A]2 [Bx]3
- 16 L#25 GETTABUP(11) (iABC) [A]3 [k]0 [B]0 [C]6
- 17 L#27 CLOSURE(79) (iABx) [A]4 [Bx]4
- 18 L#25 SETFIELD(18) (iABC) [A]3 [k]0 [B]7 [C]4
- 19 L#29 RETURN(70) (iABC) [A]1 [k]1 [B]2 [C]1
- 20 L#29 RETURN(70) (iABC) [A]3 [k]1 [B]1 [C]1
三、GETTABUP(11) (iABC) [A]1 [k]0 [B]0 [C]5
<lua>/lparser.cpp ------ static void localstat (LexState *ls) { ...... // 过程对应的lua脚本:local empty_pkg = setmetatable({}, mt) if (testnext(ls, '=')) nexps = explist(ls, &e); else { e.k = VVOID; nexps = 0; } ...... } <lua>/lparser.cpp ------ static int testnext (LexState *ls, int c) { if (ls->t.token == c) { luaX_next(ls); return 1; } else return 0; }
满足条件“ls->t.token == '='”,认为是表达式,调用luaX_next,取出表达式右侧内容。
<lua>/llex.cpp ------ void luaX_next (LexState *ls) { ls->lastline = ls->linenumber; if (ls->lookahead.token != TK_EOS) { /* is there a look-ahead token? */ ls->t = ls->lookahead; /* use this one */ ls->lookahead.token = TK_EOS; /* and discharge it */ } else { ls->t.token = llex(ls, &ls->t.seminfo); /* read next token */ } }
luaX_next调用llex。解析出“setmetatable”这个词后,调用llex(..),后者再调用luaX_newstring(...)。
<lua>/llex.cpp ------ TString *luaX_newstring (LexState *ls, const char *str, size_t l) { lua_State *L = ls->L; // 以“setmetatable”这个字符串生成ts。 TString *ts = luaS_newlstr(L, str, l); /* create new string */ // 以key="setmetatable",搜索ls->h这个扫描表。 const TValue *o = luaH_getstr(ls->h, ts); if (!ttisnil(o)) /* string already present? */ ts = keystrval(nodefromval(o)); /* get saved copy */ else { /* not in use yet */ // ls->h不存在key="setmetatable",进入这个入口。 TValue *stv = s2v(L->top++); /* reserve stack space for string */ setsvalue(L, stv, ts); /* temporarily anchor the string */ // 下面这个luaH_finishset会向ls->h添加记录式单元:ls->h["setmetatable"] = "setmetatable" luaH_finishset(L, ls->h, stv, o, stv); /* t[string] = string */ /* table is not a metatable, so it does not need to invalidate cache */ luaC_checkGC(L); L->top--; /* remove string from stack */ } return ts; }
localstae检测到有表达式,调用explist(...)处理这个表达式。第一步的testnext已解析出“setmetatable”,会调用singlevar(...)。singlevar的功能是要确定这变量在哪里。
<lua>/lparser.cpp ------ static void singlevar (LexState *ls, expdesc *var) { // var->k是VNONRELOC TString *varname = str_checkname(ls); // varname: setmetatable const std::string str = TSTRING_2_str(varname); FuncState *fs = ls->fs; singlevaraux(fs, varname, var, 1); // setmetatable不是local变量,singlevaraux会把var->k改为VVOID if (var->k == VVOID) { /* global name? */ expdesc key; // singlevaraux用的TString是ls->envn,而不是varname。 singlevaraux(fs, ls->envn, var, 1); /* get environment variable */ // 经过singlevaraux,var->k改为了KUPVAL。 lua_assert(var->k != VVOID); /* this one must exist */ // key是另一个expdesc,用于表示后绪用于搜索的表索引“key”。 codestring(&key, varname); /* key is variable name */ // key->k = VKSTR,e->u.strval = varname。luaK_indexed执行获取env[varname] luaK_indexed(fs, var, &key); /* env[varname] */ } } <lua>/lcode.cpp ------ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) { if (k->k == VKSTR) { // k是字符串类型,会入这里。 // str2K是主要操作,它会修改k->u.info,指示setmetatable在全局表中的整数值索引。 str2K(fs, k); } lua_assert(!hasjumps(t) && (t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL)); if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non 'Kstr'? */ luaK_exp2anyreg(fs, t); /* put it in a register */ if (t->k == VUPVAL) { // setmetatable时会进这入口。 // 在上值数组的索引。t->u->info: 0 t->u.ind.t = t->u.info; /* upvalue index */ // 在常量数组的索引。k->u.info: 5 t->u.ind.idx = k->u.info; /* literal string */ t->k = VINDEXUP; } else { /* register index of the table */ t->u.ind.t = (t->k == VLOCAL) ? t->u.var.ridx: t->u.info; if (isKstr(fs, k)) { t->u.ind.idx = k->u.info; /* literal string */ t->k = VINDEXSTR; } else if (isCint(k)) { t->u.ind.idx = cast_int(k->u.ival); /* int. constant in proper range */ t->k = VINDEXI; } else { t->u.ind.idx = luaK_exp2anyreg(fs, k); /* register */ t->k = VINDEXED; } } } <lua>/lcode.cpp ------ static void str2K (FuncState *fs, expdesc *e) { lua_assert(e->k == VKSTR); e->u.info = stringK(fs, e->u.strval); e->k = VK; } <lua>/lcode.cpp ------ static int addk (FuncState *fs, TValue *key, TValue *v) { TValue val; lua_State *L = fs->ls->L; Proto *f = fs->f; // 第一步已经让ls->h存在key="setmetatable"的记录。 const TValue *idx = luaH_get(fs->ls->h, key); /* query scanner table */ int k, oldsize; if (ttisinteger(idx)) { /* is there an index there? */ // 虽然存在key="setmetatable"的记录,但存的值是TSTRING的值"setmetatable",因而不会进入这里。 k = cast_int(ivalue(idx)); /* correct value? (warning: must distinguish floats from integers!) */ if (k < fs->nk && ttypetag(&f->k[k]) == ttypetag(v) && luaV_rawequalobj(&f->k[k], v)) return k; /* reuse index */ } /* constant not found; create a new entry */ // f->sizek是已经分配的用于存储常量的数目,总是2的次方,值:8。fs->nk是有效的常量数目,值:5。 oldsize = f->sizek; k = fs->nk; // k就是要用于之后访问“setmetatable”的常量值了。 /* numerical value does not need GC barrier; table has no metatable, so it does not need to invalidate cache */ setivalue(&val, k); luaH_finishset(L, fs->ls->h, key, idx, &val); // 以k=5生成val,并调用luaH_finishset,把新值设置到ls->h,至此key="setmetatable"的记录值会是5。 luaM_growvector(L, f->k, k, f->sizek, TValue, MAXARG_Ax, "constants"); while (oldsize < f->sizek) setnilvalue(&f->k[oldsize++]); setobj(L, &f->k[k], v); fs->nk++; luaC_barrier(L, f, v); return k; }
addk返回值5,于是e->u.info=5。这个5是在常量区的索引。回到luaK_indexed(...),执行完str2K(fs, k)后,会把e->u.info赋值给t->u.ind.idx。
以上分析可看出,整个过程没有判断_ENV中是否真存在key="setmetatable"的记录。只要解析到“setmetatable”这个词了,就会以它为key加到ls->h。至于5,只是到那时刻,此个lua脚本函数对应的Proto的常量数组中,恰好索引5是每个空闲的位置而已。
得出v后,解析器检测到下一个token是“(”,于是调用luaK_exp2nextreg生成访问这个表达式的指令。由于v.k==VINDEXUP,生成的指令是OP_GETTABUP。
e->k | 使用的字段 | 说明 |
VLOCAL | e->u.info | 表示值在栈中,R(e->u.info) |
VUPVAL | e->u.info | 表示值在upvalues中,UpValue[e->u.info] |
VINDEXED | e->u.ind.vt = VUPVAL e->u.ind.t e->u.ind.idx | 表示值在UpValue[e->u.ind.t][RK(e->u.ind.idx)] |
更多查找变量细节参考:构建Lua解释器Part7:构建完整的语法分析器(上)
四、LexState.h
LexState.h类型是“Table*”,即代码中经常出现的“ls->h”。luaY_parser在把主函数闭包压栈后,便会创建它,并压栈。执行mainfunc后,把反它弹出栈,从而确保经过luaY_Parser后,栈只多了一个主函数闭包。
ls->h作用是提供过程中扫描到的变量。让看下执行mainfunc后、弹出栈之前,它当中内容。
INFO: ======table(e279880)'s sizenode:32======main_lexstate_h(e279880) INFO: [0/32]empty package --> (49438b0)3 INFO: [1/32]Tried to access an empty package --> (49438c8)1 INFO: [2/32]__tostring --> (49438e0)4 INFO: [3/32]LUA_TNIL --> (49438f8)LUA_TNIL INFO: [4/32]if --> (4943910)if INFO: [5/32]LUA_TNIL --> (4943928)LUA_TNIL INFO: [6/32]require --> (4943940)7 INFO: [7/32]LUA_TNIL --> (4943958)LUA_TNIL INFO: [8/32]a --> (4943970)a INFO: [9/32]{empty package} --> (4943988)0 INFO: [10/32]c --> (49439a0)c INFO: [11/32]error --> (49439b8)0 INFO: [12/32]LUA_TNIL --> (49439d0)LUA_TNIL INFO: [13/32]self --> (49439e8)self INFO: [14/32]return --> (4943a00)return INFO: [15/32]function --> (4943a18)function INFO: [16/32]LUA_TNIL --> (4943a30)LUA_TNIL INFO: [17/32]wesnoth --> (4943a48)6 INFO: [18/32]__metatable --> (4943a60)2 INFO: [19/32]mt --> (4943a78)mt INFO: [20/32]pkg_name --> (4943a90)pkg_name INFO: [21/32]funcA --> (4943aa8)0 INFO: [22/32]end --> (4943ac0)end INFO: [23/32]b --> (4943ad8)b INFO: [24/32]__newindex --> (4943af0)1 INFO: [25/32]local --> (4943b08)local INFO: [26/32]then --> (4943b20)then INFO: [27/32]setmetatable --> (4943b38)5 INFO: [28/32]empty_pkg --> (4943b50)empty_pkg INFO: [29/32]__index --> (4943b68)0 INFO: [30/32]global --> (4943b80)global INFO: [31/32]k --> (4943b98)k INFO: =============================
五、删除table,记录类型是lua脚本函数,它们的删除情况
删除table指的是把table置为nil。假设lua/terminal.lua这个文件定义了一个table:aplt_leagor_iaccess__terminal,该表中存在类型是lua脚本函数的记录。
- 加载lua/terminal.lua后,terminal.lua主Proto会很快被删掉(主Proto的特征是linedefined、lastlinedefined值都是0)。
- 若在下一次lua_gc(L, LUA_GCCOLLECT)前删除table,那table中记录对应Proto都会被删除。
- 若没有删除table时,又加载了lua/terminal.lua,那上一次加载时生成的记录对应Proto会被删除。即lua会确保最多保留一份aplt_leagor_iaccess__terminal中的Proto。
- 小程序编辑时,如果要重加载小程序的lua,在加载前要删除terminal表格。为提高加载效率,可使用二进制格式的lua文件。
如何调试以上情况?——删除Proto时必会调用luaF_freeproto,可在该函数内设置断点。以下是想查看lua何时删除lua/terminal.lua中的Proto。
<lua>/lfunc.cpp ------ void luaF_freeproto (lua_State *L, Proto *f) { const char *str = getstr(f->source); if (memcmp(str, "@lua/terminal.lua", strlen(str)) == 0) { int ii = 0; } luaM_freearray(L, f->code, f->sizecode); luaM_freearray(L, f->p, f->sizep); luaM_freearray(L, f->k, f->sizek); luaM_freearray(L, f->lineinfo, f->sizelineinfo); luaM_freearray(L, f->abslineinfo, f->sizeabslineinfo); luaM_freearray(L, f->locvars, f->sizelocvars); luaM_freearray(L, f->upvalues, f->sizeupvalues); luaM_free(L, f); }