当前位置: 代码迷 >> 综合 >> lua源码解析与笔记整理二,函数的解析和调用过程
  详细解决方案

lua源码解析与笔记整理二,函数的解析和调用过程

热度:26   发布时间:2024-01-25 13:59:13.0

上一篇记录了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[])对应的下标indexint pc;       //指向当前需要执行的字节码,类似于指令寄存器 ip,每执行一条+1int freereg;  //当前空闲的栈序号,从0开始,每入栈一个常量变量等,freereg+1,出栈-1short 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_NAMEif(testnext(ls,TK_FUNCTION)localfunc(ls)elselocalstat(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已经是刚才open_func新建里的FuncState,不是一开始用的那个了//ls->fs是链表结构,链表头已经换新了checknext(ls,'(');  //左括号则读入下一个token,这里是a,否则报错parlist(ls);  //解析参数列表,直到遇到右括号')';这里稍微展开一下---------------------------------------------------------------parlist展开{if(token != ')')do{cast TK_NAME:{new_locvar(ls, str_checkname(ls), nparams++)//熟悉的味道,只不过ls-fs换成了新的FuncState了//表示给ls->fs 创建变量,占用当前函数的freereg(每个函数单独的栈)break;}    }while(token !=')');//解析完 a,b两个参数后, fs->nlocvars = 2, freereg = 0(还未调整),//fs->f->locvars[] = { "a", "b"}, fs->h[] = { {'a',1},{'b',1}}//当然,fs->h中的a,b需要哈希成对应的key保存,这里仅用于好理解//且value仅赋值为bool(1)类型,以防垃圾回收,如果是常量,则需存对应fs->k[]下标//这个下标是常量组成字节码的一部分。。。。luaK_reserveregs(fs.fs->nactvar);  // //里面仅是简单的 freereg += 2 这样//子,表示a,b占用了两个栈层}----------------------------------------------------------------checknext(ls,')'); //确保是又括号,然后读入下一个token,这里是“return”chunk(ls);  //进入函数内部,然后又进入刚开始的statment(ls),用于解析内部元素----------------------------------------------------------------------chunk(ls)展开:。。。。。   while(!islast){islast = statement(ls); //每次解析一行 }----------------------------------------------------------------------}

4、接下来我们回到了statement(ls)中,追踪下一步

static int statment(LexState * ls)
{int line = ls->linenumber;  //当前所解析的行,在源码文件中的第几行switch(ls->t.token)   //读入的是return a+b中的return{。。。case TK_RETURN:retstat(ls);--------------------------------------------------------------------retstat展开:{fs = ls->fsluaX_next(ls); //读取下一个token, "a+b"中的 a...nret = explist1(ls,&e);//好像我们来过?详见前面的 第二步}--------------------------------------------------------------------    return 1;  //表示已经结束......}}

 

5、经过跟第二步一样的过程,我们又来到了simpleexp(ls)这里;这里就有点不一样了

static void simpleexp(LexState* ls, expdesc * v)
{switch(ls->t.token)  //每个token都是解析到的一个单独的元素,如TK_NAME, TK_LOCAL等{.....default:{primaryexp(ls,v);//这次没有进入TK_FUNCITON,而是TK_NAME ,进入default了,return; //解析完 a+b直接返回}.......}}

 

6、 进入primaryexp:

static void primaryexp( LexState* ls, expdesc* v)
{fs = ls->fs;prefixexp(ls,v); //解析当前token,并读入下一个token,是“+”号----------------------------------------------------展开{switch(token){case TK_NAME: singlevar(ls,v);-----------------------------------------展开:{singevaraaux(fs, varname, v ,1);//varname就是刚读入的'a'//这个函数会调用searchvar搜索a,搜到后将变量在fs->f->locvars[]中//的下标保存在v结构中,并返回VLOCAL,表示这是局部变量,在第几个//栈位置上,在这是0}-----------------------------------------return;}}-----------------------------------------------------//经过上面的函数后v已经是( k= VLOCAL, info= 0 )的状态了for(;;){Switch(token){。。。进入default: return; :}}

 

7、从primaryexp(ls,v);返回后,返回到第4的explist1->expr->subexpr中

explist1(ls,e)
{.....expr(ls, e){subexpr(ls, e){....simpleexp(ls, e);  //经过层层调用,做种进入这个函数//经过一系列追踪,刚从simpleexp中返回op = getbinopr(token); //这里是“+”号,返回 OPR_ADDwhile(op != OPR_NONE) {。。。。luaX_next(ls); //读入下一个token, “b”luaK_infix(fs, op, v);  ------------------------------------------------------展开:。。。case OPR_ADD: luaK_exp2RK(fs, e);  //终于要生成第一条字节码指令了--------------------------------------------篇幅不够,简单说来,就是生成了OP_ADD,a,b指令然后将指令保存在fs->f->code[pc]中,pc++,同时保存该指令对应的源文件行号 f->lineinfo[pc] = line后面的大概就是生成一条OP_RETURN指令。。。再开一篇介绍后面的步骤吧--------------------------------------------------------------------------------------------------}}}}
  相关解决方案