Skip to content

Latest commit

 

History

History
96 lines (62 loc) · 4.1 KB

ch03-Lua指令执行过程.md

File metadata and controls

96 lines (62 loc) · 4.1 KB

Lua指令执行过程

有了上一节Lua栈的基础,这节讲解Lua指令执行过程中涉及到的关键数据结构,流程以及相应的函数.

存放分析Lua代码之后生成相关的opcode,是存放在Proto结构体中,一个Lua文件有一个总的Proto结构体,如果它内部还有定义函数,那么每个函数也有一个对应的Proto结构体.这里仅列列举出与指令执行相关的几个成员:

  • TValue *k:存放常量的数组.
  • Instruction *code:存放指令的数组.
  • struct Proto **p:该Proto内定义的函数相关的Proto数据.

Proto结构体是分析完一个Lua文件之后的产物,所以需要在分析阶段需要有中间使用到的临时数据用来保存它,向它里面写入数据,待分析完毕之后,这个在分析过程中临时使用的数据就不再使用了,这个结构体就是FuncState,这里也仅是列举出它里面与指令相关的成员:

  • Proto *f:绑定的Proto结构体.
  • int pc:当前的pc索引,类似于CPU执行指令时的计数器,在这里这个变量用于保存当前已经向Proto的code数组写入了多少数据.
  • int freereg:当前的可用寄存器索引.

有了以上的准备,来看看何时向Proto中写入指令.Lua代码中对不同的指令格式提供了几个函数,luaK_codeABx/luaK_codeABC等,这几个函数最终都会调用函数luaK_code:

(lcode.c)
797 static int luaK_code (FuncState *fs, Instruction i, int line) {
798   Proto *f = fs->f;
803   f->code[fs->pc] = i;
808   return fs->pc++;
809 }

如前面分析的那样,luaK_code中将指令写入Proto结构体的code数组中,返回code数组中下一个可写的位置,这个用FuncState的pc成员保存.

接下来看看Lua虚拟机真正执行每一条指令的函数luaV_execute:

(lvm.c)
343 #define RA(i)   (base+GETARG_A(i))
344 /* to be used after possible stack reallocation */
345 #define RB(i)   check_exp(getBMode(GET_OPCODE(i)) == OpArgR, base+GETARG_B(i))
346 #define RC(i)   check_exp(getCMode(GET_OPCODE(i)) == OpArgR, base+GETARG_C(i))
347 #define RKB(i)  check_exp(getBMode(GET_OPCODE(i)) == OpArgK, \
348     ISK(GETARG_B(i)) ? k+INDEXK(GETARG_B(i)) : base+GETARG_B(i))
349 #define RKC(i)  check_exp(getCMode(GET_OPCODE(i)) == OpArgK, \
350     ISK(GETARG_C(i)) ? k+INDEXK(GETARG_C(i)) : base+GETARG_C(i))
351 #define KBx(i)  check_exp(getBMode(GET_OPCODE(i)) == OpArgK, k+GETARG_Bx(i))
	
373 void luaV_execute (lua_State *L, int nexeccalls) {
374   LClosure *cl;
375   StkId base;
376   TValue *k;
377   const Instruction *pc;
378  reentry:  /* entry point */
380   pc = L->savedpc;
381   cl = &clvalue(L->ci->func)->l;
382   base = L->base;
383   k = cl->p->k;
384   /* main loop of interpreter */
385   for (;;) {
386     const Instruction i = *pc++;
387     StkId ra;
398     ra = RA(i);
402     switch (GET_OPCODE(i)) {
760     }
761   }
762 }

首先注意到,在函数luaV_execute中,变量pc来自于lua_State的savedpc,它是在前面出现过的luaD_precall函数中进行赋值的,只不过在那个时候仅关注函数栈的相关变量,这里把这行代码列举出来:

(ldo.c)
264 int luaD_precall (lua_State *L, StkId func, int nresults) {
293     L->savedpc = p->code;  /* starting point */

其次看到,这个函数的主体是一个大循环,依次取出pc指针指向的指令拿出来做解析执行,而需要注意到,RA/RB/RC等几个宏,其取数据都是以base也就是函数栈的起始地址来获取的,于是如果把上一节函数栈的内容和这一节的内容结合起来,那么可以得到一个显然的结论:

函数栈空间,也就是函数的执行环境是在函数开始执行之前创建好的,而在解析指令时并不知道将来指令执行时所在的具体位置,当时只能知道的相对于函数栈的相对位置.因此Lua指令中存放的地址都是相对于函数栈的偏移量,而执行时的函数栈环境,存放在base/top成员中.

虽然指令中存放的是相对偏移量,但是也需要保存一个值,知道当前函数栈中有多少值被分配出去了,存放这个数据的变量,就是FuncState结构体的freereg.

于是图3.1中最右边表示R[0]...R[n]的意义也就明白了.