以下内容是脚本文件: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);
}