Lua2.4 執行以前 opcode.c

上節說到了 lua_dofile 執行腳本文件,或者編譯過的腳本二進制文件。
這節看下,Lua 是如何區別這兩種文件的,以及虛擬機在開始執行字節碼以前,程序裏面都發生了什麼?

lua.c 裏面的調用了 lua_dofile 來執行文件,看下 lua_dofile
less

/*
** 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);

這兩句是讀取文件的第一個字符,而後再把該字符放回到文件輸入流中去,使輸入流的仍是保持在文件頭的位置(也就是還沒有讀取的狀態)。
接下來的這一句
lua

  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。
rest

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 異常處理, 雖然它們之間是有區別的。
後面代碼是作一些異常發生的善後處理,設置狀態位,釋放相關的資源。

code

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.
get

/*
** 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 怎麼恢復場景的
----------------------------------------
虛擬機

相關文章
相關標籤/搜索