全部博文(512)
分类:
2009-02-25 14:15:48
第3章 lua中函数调用的方法
前面,以及分析了lua中定义一个函数的方法,现在总结如下:
1、 将函数名作为局部变量存在局部变量表里,并在栈上开辟一个寄存器空间,在运行期,将新建一个closure,并存在已保留的寄存器里;
2、 将在语法解析阶段新建的FuncState结构体保存在其父函数的局部函数定义数组里。
现在,就要分析,当lua进行函数调用的时候,是怎么调用的呢?
当分析到这里,对于lua生成中间码的过程就比较熟悉了。关键是生成的中间码必须要和lua虚拟机的执行联系在一起。所以,对于这里分析的函数调用,要结合lua虚拟机的执行一起来分析。
上篇文章对生成局部函数中间码做了简单的介绍。这里知道,当lua发现一个新定义的函数的时候,会生成OP_CLOSURE指令。那么,lua虚拟机执行到OP_CLOSURE后怎么执行呢?
在此之前,先说在lua解析代码完了以后,会做那些善后之事呢?
前面说过,lua会把一个代码文件当作是一个函数解析执行。在解析期间,它会率先生成一个FuncState的结构,作为最外面的函数。但这是解析时做的事情,运行期间,是不会有FuncState这个东西出现的。在运行期间,是由一个个叫CallInfo的数据结果的,它指的是当前运行的函数。
那么,在解析代码以后,是怎么转入运行的呢?
秘密就在lua做的一些善后工作。在f_parser()函数里,解析完了后,最外层的那个FuncState是有的,其实用的是FuncState里的函数头Proto。然后,lua会新建一个闭包(Closure),通过函数luaF_newLclosure(),并把刚才解析代码生成的Proto保存在这个闭包里,cl->l.p=tf,新建完了这个Closure,lua会把它压入栈。然后就转入运行期进行运行。也就是进入lua_pcall()函数执行。而lua_pcall()这个函数就是假设在当前栈顶有一个Closure,然后执行这个Closure。具体说来如下:
lua_pcall()经过一系列调用,最后通过调用luaD_precall()来执行。
这个luaD_precall()做什么呢?如果这个函数是C函数,那么就直接执行,如果是lua函数,这个函数便字如其人了,就是做执行前的准备。
首先,这个函数就会在L的ci数组里申请一项空槽。也就是说,L里有一个CallInfo数组,代表每个执行的函数。然后用现在的Closure初始化这个CallInfo。并且将这个函数的栈底标志在栈顶,也就是那个Closure压入栈的地方。
接着,就是转入lua的虚拟机的运行了。也就是函数luaV_execute()。
这时,如果遇到OP_CALL的时候,也就是在lua代码中有函数调用的时候,就会直接用luaD_precall(),将要执行的函数加载到L->ci中去,然后重新开始luaV_execute()虚拟机执行。
与调用函数相对应的,怎么从一个函数里返回呢?
也就是,当lua虚拟机遇到OP_RETUREN的时候,该如何操作呢?
lua会通过一个函数来实现从子函数到调用它的函数里的一个转变:luaD_poscall()。在这个函数里,lua主要做的事情就是,因为L->ci是个链,代表当前运行函数的嵌套调用链,调用过的函数会被直接丢弃。于是,只要L->ci--,也就是让当前ci指针退一格,指向前面调用它的函数,即可返回。另外,要将栈底位置和pc值恢复,这些恢复都很简单。
但是,最主要的一件事,就是如果这个函数里含有upvalue怎么办?这个留在明天再研究一下。