lua仮想マシン関数呼び出し命令OP_CALL、OP_RETURN

10698 ワード

lua仮想マシンには3つの関数があり、lua閉包、c関数、c閉包
#define LUA_TLCL (LUA_TFUNCTION | (0 << 4)) /* Lua closure */
#define LUA_TLCF (LUA_TFUNCTION | (1 << 4)) /* light C function */
#define LUA_TCCL (LUA_TFUNCTION | (2 << 4)) /* C closure */

c関数とc閉包の実現とlua閉包の実現はやや異なる
    vmcase(OP_CALL) {
        int b = GETARG_B(i);
        int nresults = GETARG_C(i) - 1;
        if (b != 0) L->top = ra+b;  /* else previous instruction set top */
        if (luaD_precall(L, ra, nresults)) {  /* C function? */
          if (nresults >= 0)
            L->top = ci->top;  /* adjust results */
          Protect((void)0);  /* update 'base' */
        }
        else {  /* Lua function */
          ci = L->ci;
          goto newframe;  /* restart luaV_execute over new Lua function */
        }
        vmbreak;
      }

luaクローズドパッケージの実装にはreturn命令、命令OP_が必要であるRETURNは後半部の実現を完了した.c関数とc閉パケットの実装を見てみましょう.luaの関数には複数の戻り値があります.ここでは戻り値の個数を計算してluaD_を呼び出します.precallは関数呼び出しを実現し、呼び出しが終了した後にL->topの値を調整する必要がある.ci->topはc関数呼び出しのために保持されたスタック空間タグビットであるため、ここでL->topはci->topに調整され、c関数のために予約された占有空間を占領しない.
int luaD_precall (lua_State *L, StkId func, int nresults) {
  lua_CFunction f;
  CallInfo *ci;
  switch (ttype(func)) {
    case LUA_TCCL:  /* C closure */
      f = clCvalue(func)->f;
      goto Cfunc;
    case LUA_TLCF:  /* light C function */
      f = fvalue(func);
     Cfunc: {
      int n;  /* number of returns */
      checkstackp(L, LUA_MINSTACK, func);  /* ensure minimum stack size */
      ci = next_ci(L);  /* now 'enter' new function */
      ci->nresults = nresults;
      ci->func = func;
      ci->top = L->top + LUA_MINSTACK;
      lua_assert(ci->top <= L->stack_last);
      ci->callstatus = 0;
      if (L->hookmask & LUA_MASKCALL)
        luaD_hook(L, LUA_HOOKCALL, -1);
      lua_unlock(L);
      n = (*f)(L);  /* do the actual call */
      lua_lock(L);
      api_checknelems(L, n);
      luaD_poscall(L, ci, L->top - n, n);
      return 1;
    }
    。。。
}

c関数は、c閉パケットの大部分の処理と同様であるが、関数のソースオブジェクトが異なる(c閉パケットは、より多くのデータを格納する必要がある).まず、呼び出しスタックに十分なスタック空間LUA_MINSTACKを予約し、その後、関数呼び出しスタックci=next_ci(L)を記録する.次に、必要な値を格納してc関数n=(*f)(L)を呼び出す.呼び出し終了後にluaD_を使用poscallは戻り値と関数呼び出しスタックを調整します.
lua閉パケットの呼び出しを2ステップに分けて実現する第1ステップはOP_CALL
      vmcase(OP_CALL) {
        int b = GETARG_B(i);
        int nresults = GETARG_C(i) - 1;
        if (b != 0) L->top = ra+b;  /* else previous instruction set top */
        if (luaD_precall(L, ra, nresults)) {  /* C function? */
          。。。
        }
        else {  /* Lua function */
          ci = L->ci;
          goto newframe;  /* restart luaV_execute over new Lua function */
        }
        vmbreak;

c関数呼び出し実装と同様にluaD_を呼び出すprecall
int luaD_precall (lua_State *L, StkId func, int nresults) {
  lua_CFunction f;
  CallInfo *ci;
  switch (ttype(func)) {
    。。。
    case LUA_TLCL: {  /* Lua function: prepare its call */
      StkId base;
      Proto *p = clLvalue(func)->p;
      int n = cast_int(L->top - func) - 1;  /* number of real arguments */
      int fsize = p->maxstacksize;  /* frame size */
      checkstackp(L, fsize, func);
      if (p->is_vararg != 1) {  /* do not use vararg? */
        for (; n < p->numparams; n++)
          setnilvalue(L->top++);  /* complete missing arguments */
        base = func + 1;
      }
      else
        base = adjust_varargs(L, p, n);
      ci = next_ci(L);  /* now 'enter' new function */
      ci->nresults = nresults;
      ci->func = func;
      ci->u.l.base = base;
      L->top = ci->top = base + fsize;
      lua_assert(ci->top <= L->stack_last);
      ci->u.l.savedpc = p->code;  /* starting point */
      ci->callstatus = CIST_LUA;
      if (L->hookmask & LUA_MASKCALL)
        callhook(L, ci);
      return 0;
    }
    。。。
}

いくつかの基本データと関数呼び出しスタックを記録する以外に、ここで最も重要なのはlua仮想マシンのプログラム命令ポインタの値を変更することです:ci->u.l.savedpc=p->code;/*starting point*/この関数が戻ると、次はci->u.l.savedpcから実行されます.OP_まで実行RETURN、この関数は次のように返されます.
vmcase(OP_RETURN) {
        int b = GETARG_B(i);
        if (cl->p->sizep > 0) luaF_close(L, base);
        b = luaD_poscall(L, ci, ra, (b != 0 ? b - 1 : cast_int(L->top - ra)));
        if (ci->callstatus & CIST_FRESH)  /* local 'ci' still from callee */
          return;  /* external invocation: return */
        else {  /* invocation via reentry: continue execution */
          ci = L->ci;
          if (b) L->top = ci->top;
          lua_assert(isLua(ci));
          lua_assert(GET_OPCODE(*((ci)->u.l.savedpc - 1)) == OP_CALL);
          goto newframe;  /* restart luaV_execute over new Lua function */
        }
      }

ここで主な操作時に戻り値を調整し,回復関数呼び出しスタック,回復命令ポインタカウンタ,これでlua閉パケット実行が終了する.