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

上一篇记录了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);  //解析参数列表,直到遇到右括号')';这里稍微展开一下
    ---------------------------------------------------------------
     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->fs
                luaX_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_ADD
            while(op != OPR_NONE) 
            {
                。。。。
                luaX_next(ls); //读入下一个token, “b”
                luaK_infix(fs, op, v);  
                ------------------------------------------------------
                 luaK_infix展开:
                    。。。
                    case OPR_ADD: 
                        luaK_exp2RK(fs, e);  //终于要生成第一条字节码指令了
                        --------------------------------------------
                            篇幅不够,简单说来,就是生成了OP_ADD,a,b指令
                            然后将指令保存在fs->f->code[pc]中,pc++,同时保存该指
                            令对应的源文件行号 f->lineinfo[pc] = line
                            后面的大概就是生成一条OP_RETURN指令。。。
                            再开一篇介绍后面的步骤吧
                        --------------------------------------------
                ------------------------------------------------------

            }
        }
    }


}
发布了23 篇原创文章 · 获赞 0 · 访问量 1855

猜你喜欢

转载自blog.csdn.net/u014750316/article/details/104002524