0%

Lua源码笔记(6)--函数定义以及使用

要理解Lua虚拟机的运行原理最重要的重点就是要理解源码解析函数定义成指令集的原理,以及在运行时调用函数的过程,本文从源码解析和运行时代码两个角度分析函数的整个过程,其中指令集是这两个步骤的桥梁。

1. 解析函数定义

statement函数解析
1
2
3
4
5
6
7
8
9
10
11
12
// 在statement中解析函数定义的函数是funcstat
static int statement (LexState *ls) {
int line = ls->linenumber;
switch (ls->t.token) {
// ...
case TK_FUNCTION: {
funcstat(ls, line);
return 0;
}
// ...
}
}
funcstat是解析函数定义的主要地方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
static void funcstat (LexState *ls, int line) {
// 是否需要self
int needself;
expdesc v, b;
// 跳过function关键字
luaX_next(ls);
// 解析函数名字
// 有local function Name和function
// 参见后一节的注释
needself = funcname(ls, &v);
// 解析函数体的代码
body(ls, &b, needself, line);
// 将函数名字和函数体代码关联
luaK_storevar(ls->fs, &v, &b);
luaK_fixline(ls->fs, line);
}

// 解析函数名称
static int funcname (LexState *ls, expdesc *v) {
/* funcname -> NAME {field} [`:' NAME] */
int needself = 0;
// 将函数的名字解析到expdesc *v中.
singlevar(ls, v);
while (ls->t.token == '.')
field(ls, v);
if (ls->t.token == ':') {
needself = 1;
field(ls, v);
}
return needself;
}

// 解析函数体代码
static void body (LexState *ls, expdesc *e, int needself, int line) {
/* body -> `(' parlist `)' chunk END */
FuncState new_fs;
// FuncState局部变量new_fs
// open_func是对new_fs进行初始化
// 在new_fs中有Proto结构这个结构保存了函数体的代码
open_func(ls, &new_fs);
new_fs.f->linedefined = line;
// 解析函数()里的形参数
checknext(ls, '(');
if (needself) {
new_localvarliteral(ls, "self", 0);
adjustlocalvars(ls, 1);
}
parlist(ls);
// 形参解析结束
checknext(ls, ')');
// 解析函数主题代码
chunk(ls);
new_fs.f->lastlinedefined = ls->linenumber;
// 检查function和end是否是成对出现的
check_match(ls, TK_END, TK_FUNCTION, line);
// close_func重新计算代码大小等
close_func(ls);
// 主要将函数定义放到上层函数的Proto数组里
pushclosure(ls, &new_fs, e);
}

// 这个函数是将解析的函数放入到当前函数的上层函数定义中
static void pushclosure (LexState *ls, FuncState *func, expdesc *v) {
// 这里的fs是func的上一层函数定义
FuncState *fs = ls->fs;
Proto *f = fs->f;
int oldsize = f->sizep;
int i;
luaM_growvector(ls->L, f->p, fs->np, f->sizep, Proto *,
MAXARG_Bx, "constant table overflow");
while (oldsize < f->sizep) f->p[oldsize++] = NULL;
f->p[fs->np++] = func->f;
luaC_objbarrier(ls->L, f, func->f);
// func中涉及到的upvalue做局部定义
// 然后使用OP_GETUPVAL将引用的值赋值到upvalue中
// 注意一点是这个upvalue是func是外部变量和内部局部变量的桥梁
// OP_CLOSURE是定义函数的指令集中的一个指令
// 后面一定跟了函数用到的若干外部变量(全局变量或者upvalue变量)
init_exp(v, VRELOCABLE, luaK_codeABx(fs, OP_CLOSURE, 0, fs->np-1));
for (i=0; i<func->f->nups; i++) {
OpCode o = (func->upvalues[i].k == VLOCAL) ? OP_MOVE : OP_GETUPVAL;
luaK_codeABC(fs, o, 0, func->upvalues[i].info, 0);
}
}
Lua虚拟机中定义函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void luaV_execute (lua_State *L, int nexeccalls) {
// ...
// 读取OP_CLOSURE指令时定义Closure
// 也就是定义函数和upvalue
case OP_CLOSURE: {
Proto *p;
Closure *ncl;
int nup, j;
p = cl->p->p[GETARG_Bx(i)];
nup = p->nups;
// luaF_newLclosure创建Closure
ncl = luaF_newLclosure(L, nup, cl->env);
ncl->l.p = p;
// 根据upvalue的数量执行OP_GETUPVAL或者OP_MOVE
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);
// luaF_findupval是向上寻找变量的值
ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc));
}
}
// 将Closure* ncl设置给RA
setclvalue(L, ra, ncl);
Protect(luaC_checkGC(L));
continue;
}
//...
}

总结:解析的路径理下来,就是通过pushclosure函数,生成OP_CLOSURE指令,这个指令的意思是定义一个函数;OP_CLOSURE指令后面跟了若干OP_GETUPVAL指令或者OP_MOVE指令,是为了保持函数执行环境的独立性

2. 函数调用

从lua_State开始讲起

函数自身运行的时候就丢掉了函数的上下文环境信息,这个时候lua_State就负责保存函数的栈帧的信息,这里顺带延伸一下lua_State就类似操作系统的pcb,进程列表信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 两个部分重要内容
// 一个是ci数组,CallInfo保存的是每个函数调用的栈信息
// 第二个是stack,模拟的是一个可用的栈,函数中可以用的栈实际是在这里分配给函数的
// 上面两个讲的是运行时的情况
struct lua_State {
// ...
CommonHeader;
// 虚拟机当前状态
lu_byte status;
// 当前函数的栈顶
StkId top; /* first free slot in the stack */
// 当前函数的栈底
StkId base; /* base of current function */
global_State *l_G;
// 当前运行的函数帧
CallInfo *ci; /* call info for current function */
// 当前运行的函数指令地址
const Instruction *savedpc; /* `savedpc' of current function */
StkId stack_last; /* last free slot in the stack */
// 栈数组
StkId stack; /* stack base */
CallInfo *end_ci; /* points after end of ci array*/
// 函数栈帧数组
CallInfo *base_ci; /* array of CallInfo's */
// 栈数组的长度
int stacksize;
// 函数帧的长度
int size_ci; /* size of array `base_ci' */
// ...
};

虚拟机执行调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
void luaV_execute (lua_State *L, int nexeccalls) {
LClosure *cl;
StkId base;
TValue *k;
const Instruction *pc;
reentry: /* entry point */
lua_assert(isLua(L->ci));
pc = L->savedpc;
cl = &clvalue(L->ci->func)->l;
base = L->base;
k = cl->p->k;

// ...
case OP_CALL: {
int b = GETARG_B(i);
int nresults = GETARG_C(i) - 1;
if (b != 0) L->top = ra+b;
// 为luaD_precall准备函数环境时恢复用的函数返回的地址
L->savedpc = pc;
// 执行ra地址位置的函数前的准备工作
// nresults为返回结果数量
switch (luaD_precall(L, ra, nresults)) {
case PCRLUA: {
nexeccalls++;
// 跳到luaV_execute的reentry标记出运行函数指令
goto reentry;
}
case PCRC: {
if (nresults >= 0) L->top = L->ci->top;
base = L->base;
continue;
}
default: {
return; /* yield */
}
}
}
// ...
函数执行前的准备工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 注意一下++L->ci在得到一个ci位置时是自增的
#define inc_ci(L) \
((L->ci == L->end_ci) ? growCI(L) : \
(condhardstacktests(luaD_reallocCI(L, L->size_ci)), ++L->ci))


int luaD_precall (lua_State *L, StkId func, int nresults) {
LClosure *cl;
ptrdiff_t funcr;
if (!ttisfunction(func)) /* `func' is not a function? */
func = tryfuncTM(L, func); /* check the `function' tag method */
// 计算func离栈底的位置
funcr = savestack(L, func);
// 得到这个函数的Clouser
cl = &clvalue(func)->l;
// 将上一个函数的执行地址保存到当前ci的savedpc中,结束函数调用时要返回
L->ci->savedpc = L->savedpc;
if (!cl->isC) { /* Lua function? prepare its call */
CallInfo *ci;
StkId st, base;
Proto *p = cl->p;
// 检查当前函数需要的栈长度
luaD_checkstack(L, p->maxstacksize);
func = restorestack(L, funcr);
// 修正函数的参数
// 就是在栈中留出函数参数值的位置
// 调用函数后的几个指令要么是处理upvalue
if (!p->is_vararg) { /* no varargs? */
base = func + 1;
if (L->top > base + p->numparams)
L->top = base + p->numparams;
}
else { /* vararg function */
int nargs = cast_int(L->top - func) - 1;
base = adjust_varargs(L, p, nargs);
func = restorestack(L, funcr);
}
// 正如上面的宏描述的,运行时产生一个ci记录当前函数帧调用
ci = inc_ci(L);
ci->func = func;
L->base = ci->base = base;
ci->top = L->base + p->maxstacksize;
lua_assert(ci->top <= L->stack_last);
// 将执行的位置设置为Proto的code数组开始处
L->savedpc = p->code; /* starting point */
ci->tailcalls = 0;
ci->nresults = nresults;
for (st = L->top; st < ci->top; st++)
setnilvalue(st);
L->top = ci->top;
// ...
return PCRLUA;
}
// ...
}

函数执行完毕后的返回

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
case OP_RETURN: {
int b = GETARG_B(i);
if (b != 0) L->top = ra+b-1;
if (L->openupval) luaF_close(L, base);
L->savedpc = pc;
b = luaD_poscall(L, ra);
if (--nexeccalls == 0) /* was previous function running `here'? */
return; /* no: return */
else { /* yes: continue its execution */
if (b) L->top = L->ci->top;
lua_assert(isLua(L->ci));
lua_assert(GET_OPCODE(*((L->ci)->savedpc - 1)) == OP_CALL);
goto reentry;
}
}