首页 > 代码库 > Lua2.4 执行之前 opcode.c

Lua2.4 执行之前 opcode.c

上节说到了 lua_dofile 执行脚本文件,或者编译过的脚本二进制文件。
这节看下,Lua 是如何区别这两种文件的,以及虚拟机在开始执行字节码之前,程序里面都发生了什么?

lua.c 里面的调用了 lua_dofile 来执行文件,看下 lua_dofile

/*
** Open file, generate opcode and execute global statement. Return 0 on
** success or 1 on error.
*/
int lua_dofile (char *filename)
{
  int status;
  int c;
  FILE *f = lua_openfile(filename);
  if (f == NULL)
    return 1;
  c = fgetc(f);
  ungetc(c, f);
  status = (c == ID_CHUNK) ? luaI_undump(f) : do_protectedmain();
  lua_closefile();
  return status;
}

注释里写得很清楚,这个函数是用来打开文件,生成字节码,执行全局的语句。
成功返回 0 ,失败返回 1 。
lua_openfile 打开文件部分已经在编译器分析的时候说过了,这里就不再重复了(后面再遇到这样的已经分析过的可能就不再说明,直接略过了。)。
看下面的

  c = fgetc(f);
  ungetc(c, f);

这两句是读取文件的第一个字符,然后再把该字符放回到文件输入流中去,使输入流的还是保持在文件头的位置(也就是尚未读取的状态)。
接下来的这一句

  status = (c == ID_CHUNK) ? luaI_undump(f) : do_protectedmain();

就是检查刚才读到那个字符。在编译器分析时,我们知道编译器生成的 *.out 二进制文件的开头第一个字符就是 ID_CHUNK ,ASCII 码为 27 。而在正常的脚本文件中,这个字符是不会出现的。所以可以根据这个标签来判定文件是个 *.out 的二进制文件或者是个脚本文件。
如果是编译过的二进制文件,调用 luaI_undump 恢复场景并执行。

/*
** load and run all chunks in a file
*/
int luaI_undump(FILE* D)
{
 TFunc* m;
 while ((m=luaI_undump1(D)))
 {
  int status=luaI_dorun(m);
  luaI_freefunc(m);
  if (status!=0) return status;
 }
 return 0;
}

这里可以看到,luaI_undump 先是调用 luaI_undump1 恢复场景,至于怎么恢复场景的,下节再说。
恢复之后,调用 luaI_dorun 。

回到 lua_dofile,如果是一个脚本文件的话,调用 do_protectedmain。

static int do_protectedmain (void)
{
  TFunc tf;
  int status;
  jmp_buf myErrorJmp;
  jmp_buf *oldErr = errorJmp;
  errorJmp = &myErrorJmp;
  luaI_initTFunc(&tf);
  tf.fileName = lua_parsedfile;
  if (setjmp(myErrorJmp) == 0)
  {
    lua_parse(&tf);
    status = luaI_dorun(&tf);
  }
  else
  {
    status = 1;
    adjustC(0); /* erase extra slot */
  }
  errorJmp = oldErr;
  luaI_free(tf.code);
  return status;
}

这里我们看到,在做一些初始化,和设置异常恢复断点之后,语法分析 lua_parse 之后,它也是调用了 luaI_dorun ,调到这一步的时候,编译过二进制文件和脚本文件就没有差别了。setjmp 可以先简单的认为是 C 语言版的 try...catch 异常处理, 虽然它们之间是有区别的。
后面代码是做一些异常发生的善后处理,设置状态位,释放相关的资源。

int luaI_dorun (TFunc *tf)
{
  int status;
  adjustC(1); /* one slot for the pseudo-function */
  stack[CBase].tag = LUA_T_FUNCTION;
  stack[CBase].value.tf = tf;
  status = do_protectedrun(0);
  adjustC(0);
  return status;
}

把编译好的 TFunc 设置到栈上,调用 do_protectedrun。

/*
** Execute a protected call. Assumes that function is at CBase and
** parameters are on top of it. Leave nResults on the stack. 
*/
static int do_protectedrun (int nResults)
{
  jmp_buf myErrorJmp;
  int status;
  StkId oldCBase = CBase;
  jmp_buf *oldErr = errorJmp;
  errorJmp = &myErrorJmp;
  if (setjmp(myErrorJmp) == 0)
  {
    do_call(CBase+1, nResults);
    CnResults = (top-stack) - CBase; /* number of results */
    CBase += CnResults; /* incorporate results on the stack */
    status = 0;
  }
  else
  { /* an error occurred: restore CBase and top */
    CBase = oldCBase;
    top = stack+CBase;
    status = 1;
  }
  errorJmp = oldErr;
  return status;
}

注释比较清楚,不再细说。函数名字中有 protected 字样的就是受保护的调用,内部使用 setjmp 来设置异常恢复点。函数调用 do_call 执行刚才压栈的 TFunc.

/*
** Call a function (C or Lua). The parameters must be on the stack,
** between [stack+base,top). The function to be called is at stack+base-1.
** When returns, the results are on the stack, between [stack+base-1,top).
** The number of results is nResults, unless nResults=MULT_RET.
*/
static void do_call (StkId base, int nResults)
{
  StkId firstResult;
  Object *func = stack+base-1;
  int i;
  if (tag(func) == LUA_T_CFUNCTION)
  {
    tag(func) = LUA_T_CMARK;
    firstResult = callC(fvalue(func), base);
  }
  else if (tag(func) == LUA_T_FUNCTION)
  {
    tag(func) = LUA_T_MARK;
    firstResult = lua_execute(func->value.tf->code, base);
  }
  else
  { /* func is not a function */
    /* Call the fallback for invalid functions */
    open_stack((top-stack)-(base-1));
    stack[base-1] = luaI_fallBacks[FB_FUNCTION].function;
    do_call(base, nResults);
    return;
  }
  /* adjust the number of results */
  if (nResults != MULT_RET && top - (stack+firstResult) != nResults)
    adjust_top(firstResult+nResults);
  /* move results to base-1 (to erase parameters and function) */
  base--;
  nResults = top - (stack+firstResult); /* actual number of results */
  for (i=0; i<nResults; i++)
    *(stack+base+i) = *(stack+firstResult+i);
  top -= firstResult-base;
}

判断栈上的是 C 函数还是,Lua 的函数。
如果是 C 的调用 callC。
如果是 Lua 的函数,则调用 lua_execute 由虚拟机执行。
如果不是一个函数,则调用 "function" 的回退函数。
之后,是调整返回值和栈。
调整栈有一个简单的规则就是:函数调用后栈的状态要和函数调用前保持一样,除了可能在栈上剩下几个返回值。这句话可能不容易理解,举个小例子。比如在 C 语言里,我们有一个函数:

int add(int a, int b)
{
  return a + b;
}

如果我们有一个调用 int result = add(3, 5),在这句执行完后,它所产生的效果和 int result = 8 是完全一样的。套到这里就是,在该调用 add(3, 5) 的地方,你直接放个 8 到栈上去效果是一样的,对于后续程序的执行完全没有任何影响。
回忆一下汇编里的函数调用返回的栈桢结构,是不是觉得这里的函数调用返回有点眼熟了。

callC 就是调用一个 C 的函数指针。
lua_execute 是一个很大的 switch...case 来执行指令。这个在之前的版本里有详细介绍,在这个版本里可能就略过了。
fallback 对程序的主流程基本没什么影响。

----------------------------------------
到目前为止的问题:
>  luaI_undump1 怎么恢复场景的
----------------------------------------

Lua2.4 执行之前 opcode.c