上一篇记录了local变量的解析过程,今天整理下函数funciton在虚拟机的解析和调用过程;
主要目标:函数怎么存放,参数怎么存放,怎么执行函数和传递参数,返回值怎么传递。
来个简单的例子 ,在lua命令行输入如下代码
local myfun = function(a,b)
return a + b
end
一般来说虚拟机都是阻塞在读取输入的地方,当输入上面代码后,接下来看下函数在虚拟机里面会变成什么样子。
先介绍function在虚拟机的存储结构:FuncState 这里用fs表示,挑几个重要的讲下,这东西跟luaState概念类似(状态机???)
typedef struct FuncState{
Proto * f ; //类似c++代码段+函数栈,真正存储字节码和栈变量的地方
Table* h ; //哈希表,用于快速查找常量所在数组(f->k[])对应的下标index
int pc; //指向当前需要执行的字节码,类似于指令寄存器 ip,每执行一条+1
int freereg; //当前空闲的栈序号,从0开始,每入栈一个常量变量等,freereg+1,出栈-1
short nlocalvars; //指示在f->locvars[]数组中有多少个元素(这个数组存局部变量)
......其他的用到再说
}FuncState;
Proto* f在这里也介绍下:
typedef struct Proto{
TValue *k ; //保存常量用的数组(向量)
int sizek;
Instruction *code; //字节码真正保存处,fs->pc指向要执行的指令处
struct LocVar *locvars; //LocVar仅保存变量名和生命周期起始点和终点,用数组下标对应栈位置
int sizelocars; //仅表示locvars的分配的空间,变量个数在fs->nlocvars中保存
int numparams; //参数个数 function(a,b)中的a,b,会优先入栈占领1,2两个位置
。。。。。//其他留待后续研究
}Proto;
1、解析准备过程略过,直接进入statement函数,local对应的是 TK_LOCAL(词法单元还是语法单元?不必深究,管他呢)
case TK_LOCAL:{
luaX_next(ls); //解析local后面的一个元素,这里是myfun,对应TK_NAME
if(testnext(ls,TK_FUNCTION)
localfunc(ls)
else
localstat(ls);//局部变量,上一篇介绍过了
}
这里进入localstat(ls), 局部变量上一篇介绍过,这里略过,大致过程如下:用new_localvar,生成一个变量,并将freereg+1,表示占用一个栈位置,其实里面什么都没有,等到解析完(执行完?这里是模拟执行过程) "="后面的才是真正赋值。
static void localstat(LexState *ls) //LexState今天研究重点不在这,大致看成输入源和解析缓存
{
.......
do
{
new_localvar(ls,str_checkname(ls),nvars++);
}while(testnext(ls,",)); //local变量定义可一次定义多个,用“,”隔开,如local a,b,c = 1,2,3
//注意完成上面过程后 fs -> f->locvars[0].name = "myfun", fs->nlocvars = 1 了
//其实是TString类型的,这里直接用字符串"myfun"方便理解了
if (testnext(ls,"=")) //赋值表达式
nexps = explist1(ls,&e); //读取"="右边的数值列表,本次示例仅一个function(a,b)的定义
else
{
....
//仅占位,不赋值,如 local a,b,c
}
adjuxt_assign(ls,nvars,nexps,&e); //占用栈空间,调整freereg的数值
....
}
2、接着深入explist1(ls, &e),看看function是怎解解析的
explist1(ls,e)
{
.....
expr(ls, e)
{
subexpr(ls, e)
{
....
simpleexp(ls, e); //经过层层调用,做种进入这个函数
}
}
}
进过一系列的追踪后,发现进入了simpleexp(ls,e)函数,一般来说赋值,函数都是进入这个解析函数中
static void simpleexp(LexState* ls, expdesc * v)
{
switch(ls->t.token) //每个token都是解析到的一个单独的元素,如TK_NAME, TK_LOCAL等
{
.....
case TK_FUNCTION:
{
luaX_next(ls); //读取后面的“(”,返回
body(ls, v, 0 , ls->linenumber); //终于进入我们想要去的地方了,前面都是一直在函数外面打转转
}
.......
}
}
3、正式进入函数解析 body
static void body(LexState* ls, expdesc *e, int needSelf, int line)
{
FuncState fs; //圈起来,要考, 这就是我们一开始介绍的函数保存的形式了
open_func(ls, &fs); //给fs->f指针new一个Proto,各种变量赋初值如,fs->freereg = 0,
//fs->pc = 0; fs->nlocvars = 0; fa->h = newTable等,最后将
//fs->h压入栈,将fs->f压入栈(解析完会不会出栈我们拭目以待)
//其中fs->f->souce保存了源代码文件名,可用于调试使用
//注意,这里ls->fs已经是刚才圈起来的FuncState,不是一开始用的那个了
//ls->fs是链表结构,链表头已经换新了
checknext(ls,'('); //左括号则读入下一个token(这里是a),否则报错
parlist(ls); //解析参数列表,直到