1. Proto函数原型
任何一个函数,一个 lua 文件, 经过编译之后都会对应变成Lua的一个LClosure(闭包)对象,一个闭包包含一个函数原型(Proto)和一个upvalues列表。
typedef struct LClosure {
ClosureHeader;
struct Proto *p;
UpVal *upvals[1]; /* list of upvalues */
} LClosure;
比较重要的就是生成的 Proto 这个数据结构, 这个结构的定义在 lobject.h 中。
/*
** Function Prototypes
*/
typedef struct Proto {
CommonHeader;
lu_byte numparams; /* 固定参数个数 */
lu_byte is_vararg; /* 是否是可变参数列表 */
lu_byte maxstacksize; /* 这个函数最多需要寄存器的数量(就是函数栈) */
int sizeupvalues; /* upvalues的个数 */
int sizek; /* 常量的个数 */
int sizecode; /* 指令的个数 */
int sizelineinfo; /* 字段表示当前函数所有的行总数目 */
int sizep; /* 定义的函数(proto)的个数 */
int sizelocvars; /* 局部变量的个数 */
int linedefined; /* 定义的首行(Debug信息) */
int lastlinedefined; /* 定义的尾行(Debug信息) */
TValue *k; /* 常量表 */
Instruction *code; /* 指令表 */
struct Proto **p; /* 定义的内部函数 */
int *lineinfo; /* 每个指令对应的行数 (Debug信息) */
LocVar *locvars; /* 局部变量的信息 (Debug信息) */
Upvaldesc *upvalues; /* Upvalue的信息 */
struct LClosure *cache; /* last-created closure with this prototype */
TString *source; /* lua完整文件路径名(Debug信息) */
GCObject *gclist;
} Proto;
2. Upvalue
完整的函数是包括Proto和Upvalues,在介绍Proto之前,先介绍Upvalue的概念。在Lua中,函数视为一种对象,那么我们可以在函数内部定义新的函数,如下:
function fun1()
local a = 100
local test_fun = function(num)
if num > a then
print("true")
else
print("false")
end
end
test_fun(50) -- output : false
test_fun(200) -- output : true
end
如上,内部函数test_fun函数在执行过程中可以访问外部函数的局部变量a,那么a变量对于函数test_fun而言就是一个Upvalue。
在Proto的结构中放了Upvalue 的名称(用于调试),是否在父函数栈上,索引位置等信息:
/* Description of an upvalue for function prototypes */
typedef struct Upvaldesc {
TString *name; /* 名字(Debug信息) */
lu_byte instack; /* 是否在栈中 */
lu_byte idx; /* 索引(可能是外部函寄存器索引,也可能是外部函数的upvalue索引) */
} Upvaldesc;
Lua编译器在编译阶段是如何认定一个变量是Upvalue呢?
- 会先在当前函数中找,找到则标记为 Local 变量,否则递归的查找父函数,找到则为 Upvalue, 否则为 Global
- 对当前函数的 Upvalue来说,有两种情况:是父函数的 Upvalue;是父函数的 Local;
当是父函数的 Upvalue时,将 instack 标记置为 0
否则是父函数的 Local,将 instack 标记置为 1
对于在父函数栈中的Upvalue,值为L->base + idx,否则就是父函数的Upvalue,值为Upvalue[idx]
3. 指令的生成
这篇文章不重点讲述指令的生成流程,只是简单介绍指令产生的地方。
#define CREATE_ABC(o,a,b,c) ((cast(Instruction, o)<<POS_OP) \
| (cast(Instruction, a)<<POS_A) \
| (cast(Instruction, b)<<POS_B) \
| (cast(Instruction, c)<<POS_C))
#define CREATE_ABx(o,a,bc) ((cast(Instruction, o)<<POS_OP) \
| (cast(Instruction, a)<<POS_A) \
| (cast(Instruction, bc)<<POS_Bx))
#define CREATE_Ax(o,a) ((cast(Instruction, o)<<POS_OP) \
| (cast(Instruction, a)<<POS_Ax))
Lua是通过上面三个宏来生成对应的指令,并通过luaK_code添加到对应的FuncState中。(在 LexState 中,成员 struct FuncState *fs, 记录了当前正在解析 FuncState,它相当于一系列被编译的函数栈的栈顶。 每当新解析一个函数时,就会新建一个 FuncState 来记录当前函数编译信息。)
4. 虚拟机的执行
虚拟机相关的代码是在lvm.c和lvm.h两个文件中。
最核心的函数就是void luaV_execute (lua_State L) ,参数lua_State实际上是每一个LUA线程创建独立的函数栈和线程栈,以及线程执行过程中需要用到的内存管理、字符串管理、gc等信息。
/*
** 'per thread' state
** Lua 主线程 栈 数据结构
** 作用:管理整个栈和当前函数使用的栈的情况,最主要的功能就是函数调用以及和c的通信
*/
struct lua_State {
CommonHeader;
lu_byte status; /* 解析容器的用于记录中间状态*/
/* 全局状态机 */
global_State *l_G;
/* 调用栈:调用栈信息管理(CallInfo 为双向链表结构) */
unsigned short nci; /* number of items in 'ci' list - 存储一共多少个CallInfo */
CallInfo base_ci; /* CallInfo for first level (C calling Lua) - 调用栈的头部指针 */
CallInfo *ci; /* call info for current function - 当前运行函数信息 */
/* 数据栈:栈指针地址管理 StkId = TValue 的数组 */
StkId top; /* first free slot in the stack - 线程栈的栈顶指针 */
StkId stack_last; /* last free slot in the stack - 线程栈的最后一个位置 */
StkId stack; /* stack base - 栈的指针,当前执行的位置*/
const Instruction *oldpc; /* last pc traced 在当前thread 的解释执行指令的过程中,指向最后一次执行的指令的指针 */
UpVal *openupval; /* list of open upvalues in this stack */
GCObject *gclist; /* GC列表 */
struct lua_State *twups; /* list of threads with open upvalues */
struct lua_longjmp *errorJmp; /* current error recover point */
/* Hook 相关管理 - 服务于debug模块 */
volatile lua_Hook hook;
ptrdiff_t errfunc; /* current error handling function (stack index) */
int stacksize;
int basehookcount;
int hookcount;
l_signalT hookmask;
lu_byte allowhook;
/* 跟C语言通信 管理*/
unsigned short nCcalls; /* number of nested C calls */
unsigned short nny; /* number of non-yieldable calls in stack */
};
可以看出lua_State里面存储了当前运行的函数信息,以及函数栈、数据栈等等;而luaV_execute也是从lua_State里面取数据和指令进行运行。
而在luaV_execute函数中,可以看到有一个无限循环,从ci中不断的取出指令,然后通过switch (GET_OPCODE(i))来根据不同的opcode进行不同的过程:
void luaV_execute (lua_State *L) {
CallInfo *ci = L->ci;
LClosure *cl;
TValue *k;
StkId base;
ci->callstatus |= CIST_FRESH; /* fresh invocation of 'luaV_execute" */
newframe: /* reentry point when frame changes (call/return) */
lua_assert(ci == L->ci);
cl = clLvalue(ci->func); /* local reference to function's closure */
k = cl->p->k; /* local reference to function's constant table */
base = ci->u.l.base; /* local copy of function's base */
/* main loop of interpreter */
for (;;) {
Instruction i;
StkId ra;
vmfetch(); //获取指令,并且准备其执行的相关数据初始化
vmdispatch (GET_OPCODE(i)) {
vmcase(...) { //对于不同的指令处理
...
vmbreak;
}
}
}
}