Lua虚拟机栈结构及相关数据结构
这节重点来介绍Lua虚拟机的结构,Lua栈的结构,以及相关的数据结构,理解本节的内容是理解后面内容的基础,但是又是与后面的内容相辅相成,所以在看到内容的时候可能需要时不时回顾本节中的内容.
(3.1 Lua 栈)
图中,最左边的框中是lua_State中三个与Lua栈相关的成员;中间是一个函数栈的结构,其最底部是函数相关的数据,紧挨着它的依次是该函数的参数;在它右边是函数寄存器数组与函数栈数据的对应关系.接下来我们将依次对它们做解释.
lua_State结构体是使用Lua时接触最多的结构体,它的成员很多,但是就本节而言,我们仅关注到与Lua栈相关的几个成员,首先来看看这几个成员在哪里进行的初始化:
(lstate.c)
42 static void stack_init (lua_State *L1, lua_State *L) {
// ....
49 L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, TValue);
50 L1->stacksize = BASIC_STACK_SIZE + EXTRA_STACK;
51 L1->top = L1->stack;
52 L1->stack_last = L1->stack+(L1->stacksize - EXTRA_STACK)-1;
53 /* initialize first ci */
54 L1->ci->func = L1->top;
55 setnilvalue(L1->top++); /* `function' entry for this `ci' */
56 L1->base = L1->ci->base = L1->top;
57 L1->ci->top = L1->top + LUA_MINSTACK;
58 }
可以看到,最开始创建了一个TValue的数组,由stack保存它的首地址;接下来将一个nil值压入栈中,最后base和top成员都指向stack的下一个位置(因为第一个位置已经保存了一个nil值).
这就是整个Lua虚拟机环境在初始化的时候创建的Lua栈结构,在后面这里将是实际执行Lua代码的舞台.
接下来看看函数栈的环境在调用函数之前以及之后的变化情况.
前面介绍了lua_State中的stack/top/base这三个成员,lua_State中还有CallInfo类型的数组,这个成员中存放每次函数调用时的环境:
(lstate.h)
48 typedef struct CallInfo {
49 StkId base; /* base for this function */
50 StkId func; /* function index in the stack */
51 StkId top; /* top for this function */
52 const Instruction *savedpc;
53 int nresults; /* expected number of results from this function */
54 int tailcalls; /* number of tail calls lost under this entry */
55 } CallInfo;
其中的nresults,tailcalls这里暂且不做解释.
而lua_State中CallInfo类型数组的初始化,同样在前面stack_init函数中:
(lstate.c)
42 static void stack_init (lua_State *L1, lua_State *L) {
43 /* initialize CallInfo array */
44 L1->base_ci = luaM_newvector(L, BASIC_CI_SIZE, CallInfo);
45 L1->ci = L1->base_ci;
46 L1->size_ci = BASIC_CI_SIZE;
47 L1->end_ci = L1->base_ci + L1->size_ci - 1;
// ...
54 L1->ci->func = L1->top;
55 setnilvalue(L1->top++); /* `function' entry for this `ci' */
56 L1->base = L1->ci->base = L1->top;
57 L1->ci->top = L1->top + LUA_MINSTACK;
58 }
可以看到,在初始化之后:
最后,ci的top指针指向lua_State的top指针之后的LUA_MINSTACK位置.
有了这些准备,首先来看看在调用函数之前函数栈环境相关的操作,这里只列举出关键的代码:
(ldo.c) 264 int luaD_precall (lua_State L, StkId func, int nresults) { 272 if (!cl->isC) { / Lua function? prepare its call / 288 ci = inc_ci(L); / now `enter' new function */ 289 ci->func = func; 290 L->base = ci->base = base; 291 ci->top = L->base + p->maxstacksize; 292 lua_assert(ci->top <= L->stack_last); 296 for (st = L->top; st < ci->top; st++) 297 setnilvalue(st); 298 L->top = ci->top; 304 return PCRLUA; 305 }
把关键的代码挑拣出来之后,上面代码的含义就一目了然了:
调用函数完毕之后,又需要根据前一个函数调用时的环境进行恢复,代码如下:
(ldo.c)
342 int luaD_poscall (lua_State *L, StkId firstResult) {
343 StkId res;
344 int wanted, i;
345 CallInfo *ci;
348 ci = L->ci--;
349 res = ci->func; /* res == final position of 1st result */
351 L->base = (ci - 1)->base; /* restore base */
358 L->top = res;
360 }
代码同样很简单直白:
有了这两部分的讲解,相信回头再看图3.1就会清楚不少了.