[toc]

# 入口

入口 : luaL_dofile () doFile 中会对读入的代码进行检查,但是最后会走到 f_parser 函数,其原型如下:

# f_parser

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void f_parser(lua_State* L, void* ud) {
int i;
Proto* tf;
//最外层一个闭包
Closure* cl;
struct SParser* p = cast(struct SParser*, ud);
int c = luaZ_lookahead(p->z);
luaC_checkGC(L);
tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
&p->buff, p->name);
cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
//设置闭包的Proto为分析后的tf
cl->l.p = tf;
//然后初始化tf中的upvalue
for (i = 0; i < tf->nups; i++) /* initialize eventual upvalues */
cl->l.upvals[i] = luaF_newupval(L);
//将最外层的闭包压入栈中
setclvalue(L, L->top, cl);
incr_top(L);
}

f_parser 中,通过 luaY_parser 函数指针进行实际的语法分析,并在最后输出词法 / 语义分析后的结果 Proto ,并将这个结果保存到比包中,然后申请闭包中的 Upvalue ,最后将闭包压栈,同时栈顶指针自增 在 doFile 后 如果成功会返回0 会进入 lua_pcall

# lua_pcall

其中 lua_pcall 的函数原型如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
LUA_API int lua_pcall(lua_State* L, int nargs, int nresults, int errfunc) {
printf("进入lua_pcalln");
struct CallS c;
int status;
ptrdiff_t func;
lua_lock(L);
api_checknelems(L, nargs + 1);
checkresults(L, nargs, nresults);
if (errfunc == 0)
func = 0;
else {
StkId o = index2adr(L, errfunc);
api_checkvalidindex(L, o);
func = savestack(L, o);
}
//栈顶减去参数数量就是函数入口,这个入口就是函数指针
//如果是lua_dofile的话,传入的nargs和errfunc都是0,换句话说,这里取到的就是Lua栈中的Closure指针
c.func = L->top - (nargs + 1); /* function to be called */
c.nresults = nresults;
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
adjustresults(L, nresults);
lua_unlock(L);
return status;
}

# 函数调用

lua_pcall 中,先检查当前执行到的闭包是否是 Lua函数 ,入口是 luaD_precall

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
//进行lua闭包函数执行前准备
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 */
funcr = savestack(L, func);
cl = &clvalue(func)->l;
L->ci->savedpc = L->savedpc;
//判定是否是一个闭包
if (!cl->isC) { /* Lua function? prepare its call */
//每个函数是一个CallInfo
CallInfo* ci;
StkId st, base;
//获得闭包的Proto,就是dofile里分析时生成的Proto
Proto* p = cl->p;
luaD_checkstack(L, p->maxstacksize);
func = restorestack(L, funcr);
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); /* previous call may change the stack */
}
//从lua_State(lua虚拟状态机)中获取下一个callInfo,这个内部实现实际上是通过函数指针数组实现的
//每次调用inc_ci时函数指针自增,并且按断是否是最后一个了
ci = inc_ci(L); /* now `enter' new function */
//设置闭包指针
ci->func = func;
//起始地址以及下面的top指针 这俩是标定参数地址用的
L->base = ci->base = base;
//设置参数在栈中的位置
ci->top = L->base + p->maxstacksize;
lua_assert(ci->top <= L->stack_last);
//将proto字节内的code部分赋值给状态机的savedpc,其中Code就是字节码
L->savedpc = p->code; /* starting point */
ci->tailcalls = 0;
ci->nresults = nresults;
//函数所需要的参数如果不足的话,填充nil
for (st = L->top; st < ci->top; st++)
setnilvalue(st);
L->top = ci->top;
if (L->hookmask & LUA_MASKCALL) {
L->savedpc++; /* hooks assume 'pc' is already incremented */
luaD_callhook(L, LUA_HOOKCALL, -1);
L->savedpc--; /* correct 'pc' */
}
return PCRLUA;
}
else { /* if is a C function, call it */
//如果是cFunction,直接调用
-----
}
}

大体的流程是:
1. 判断是否是一个 C 函数,是的话直接调用 C 函数,否则走 Lua 闭包逻辑
2. 获取 Lua 状态机中的一个 CallInfo 2.1. 设置闭包指针 2.2. 设置调用函数参数起始与终点地址
2.3. 设置生成的 Proto 中的字节码到状态机上
2.4. 如果函数的参数不足声明的部分,不足的部分填充 nil 2.5. 返回 PCRLUA ,表示是一个 Lua 闭包
3. 进入 luaV_execute <lvm.c> 函数,这个函数内部会执行词法分析后得到的字节码,然后塞入一个巨大的状态机中进行执行,每个 Op 就是一个状态节点,case 内部的内容就是 Action
4. 进入尾状态 OP_RETURN <lvm.c> ,这是状态图的尾状态,在这个状态内会调用 luaD_poscall <ldo.c> ,这个函数是状态机的尾 Action,主要负责回复上一个调用栈状态,以及将结果填充到上一个调用栈中。 简单的外流程如图

# 函数调用栈流程

第一个函数调用栈的示意图如上,始终遵循 函数 -> 参数表的结构进行 第二个函数就是在第一个函数调用中插入命令,实际上想要恢复调用前状态,只需要恢复 base_ci 指向的对象和栈顶指针即可 这里比较感兴趣的是返回值存放到栈的那部分 (应该是调用前的栈顶指针后压栈)

# OpCode 解析流程

OpCode 格式 其中所有 OpCode 的指令封装在 lopcode.c 文件下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//封装了每个OpCode的格式
// T : 是否是逻辑测试相关指令
// A : 这个指令是否会赋值给R(A)
// B/C : B、C参数的格式
// mode: 这个OpCode的格式

//OpArgN : 参数未被使用,只是没有作为R()或RK()的参数使用
//OpArgU : 已使用参数
//OpArgR : 表示该参数是寄存器或跳转偏移
//OpArgK : 表示该参数是常量还是寄存器,K表示常量
const lu_byte luaP_opmodes[NUM_OPCODES] = {
/* T A B C mode opcode */
opmode(0, 1, OpArgR, OpArgN, iABC) /* OP_MOVE */
...
}

#define opmode(t,a,b,c,m) (((t)<<7) ((a)<<6) ((b)<<4) ((c)<<2) (m))

可以发现 opmode 直接将指令封装到了一个 lu_byte 变量中
其中最后一个 m 就是 opCode 解 Op 指令的函数丢在了两个文件里, lvm.clopcodes.h 里。 在上面的 函数调用部分 ,我们知道 Lua 的函数解析最后会走到 luaV_execute 函数中,而所有的指令也是在这个函数下执行的, 同时最外层的指令解析也是在这个函数中定义和进行的

1
2
3
4
5
6
7
8
9
10
11
12
//以参数i为偏移量,在函数栈内取数据,GETARG_A(i)是从opCode中取出A参数
#define RA(i) (base+GETARG_A(i))
/* to be used after possible stack reallocation */
//同上,取出B参数,但要限定op种类
#define RB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i))
//同上,取出C参数,但要限定op种类
#define RC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i))
#define RKB(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \
ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i))
#define RKC(i) check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \
ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i))
#define KBx(i) check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i))

比如其中的 RA(i) ,传入的 i 就是指令封装的 lua_byte ,这个解指令的函数以是根据指令中定位到 A 参数的栈中位置,其他的就暂时不说了,大概就是 最外层 针对指令的操作 基本就是用来 确定参数在栈中或者常量区的位置 另外,上面的 K 通常代表 常量 另外一层的解指令是在 lopcodes.h 文件中,这里是实际的解引用, lvm.h 里的是最外层的接口,实际的解引用实现放在了 lopcodes.h
比如:
定义每个参数在指令中的 bit 位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
** size and position of opcode arguments.
*/
#define SIZE_C 9
#define SIZE_B 9
#define SIZE_Bx (SIZE_C + SIZE_B)
#define SIZE_A 8

#define SIZE_OP 6

#define POS_OP 0
#define POS_A (POS_OP + SIZE_OP)
#define POS_C (POS_A + SIZE_A)
#define POS_B (POS_C + SIZE_C)
#define POS_Bx POS_C

快速引用到某个参数的 BitMask

1
2
3
4
5
6
7
8
9
10
11
12
#if SIZE_Bx < LUAI_BITSINT-1
#define MAXARG_Bx ((1<<SIZE_Bx)-1)
#define MAXARG_sBx (MAXARG_Bx>>1) /* `sBx' is signed */
#else
#define MAXARG_Bx MAX_INT
#define MAXARG_sBx MAX_INT
#endif


#define MAXARG_A ((1<<SIZE_A)-1)
#define MAXARG_B ((1<<SIZE_B)-1)
#define MAXARG_C ((1<<SIZE_C)-1)

获取参数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define GET_OPCODE(i)  (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))
#define SET_OPCODE(i,o) ((i) = (((i)&MASK0(SIZE_OP,POS_OP)) \
((cast(Instruction, o)<<POS_OP)&MASK1(SIZE_OP,POS_OP))))

#define GETARG_A(i) (cast(int, ((i)>>POS_A) & MASK1(SIZE_A,0)))
#define SETARG_A(i,u) ((i) = (((i)&MASK0(SIZE_A,POS_A)) \
((cast(Instruction, u)<<POS_A)&MASK1(SIZE_A,POS_A))))

#define GETARG_B(i) (cast(int, ((i)>>POS_B) & MASK1(SIZE_B,0)))
#define SETARG_B(i,b) ((i) = (((i)&MASK0(SIZE_B,POS_B)) \
((cast(Instruction, b)<<POS_B)&MASK1(SIZE_B,POS_B))))

#define GETARG_C(i) (cast(int, ((i)>>POS_C) & MASK1(SIZE_C,0)))
#define SETARG_C(i,b) ((i) = (((i)&MASK0(SIZE_C,POS_C)) \
((cast(Instruction, b)<<POS_C)&MASK1(SIZE_C,POS_C))))

#define GETARG_Bx(i) (cast(int, ((i)>>POS_Bx) & MASK1(SIZE_Bx,0)))
#define SETARG_Bx(i,b) ((i) = (((i)&MASK0(SIZE_Bx,POS_Bx)) \
((cast(Instruction, b)<<POS_Bx)&MASK1(SIZE_Bx,POS_Bx))))

#define GETARG_sBx(i) (GETARG_Bx(i)-MAXARG_sBx)
#define SETARG_sBx(i,b) SETARG_Bx((i),cast(unsigned int, (b)+MAXARG_sBx))

比如 GETARG_A(i) 就是获取指令中的 A 参数

# opCode 执行流程

  1. 先保存当前 pc 的执行位置
  2. cl: 当前的函数环境
  3. base: 当前函数环境的 base 地址
  4. k: 当前函数环境的常量数组