ステート

次はメモリ管理に進もうかと思ったが、読みすすめていくうちに、どうも lua_State の使い方を見ないと正しく理解できないのではないかと思った。そこで先に lstate を見て lua_State に簡単に触れる。次に ldo を見て lua のコールスタックの使い方を見る。それから lgc に進むことにする。

lstate.h にはいろんなデータ形式が保存されている。 lua_State は VM ではなく、実際には実行スレッドになっている。したがってコルーチンも lua_State である。グローバルな状態は global_State で抽象化しているので、ここを使って共有しているのだろう。

lua_State の初期化は lua_open を使う。

LUA_API lua_State *lua_open (void) {
  lua_State *L = mallocstate(NULL);
  if (L) {  /* allocation OK? */
    L->tt = LUA_TTHREAD;
    L->marked = 0;
    L->next = L->gclist = NULL;
    preinit_state(L);
    L->l_G = NULL;
    if (luaD_rawrunprotected(L, f_luaopen, NULL) != 0) {
      /* memory allocation error: free partial state */
      close_state(L);
      L = NULL;
    }
  }
  lua_userstateopen(L);
  return L;
}

luaD_rawrunprotected がちょっと奇妙に見えるかもしれない。これは setjmp を使ってエラーが起きたときの対処をしているものなので、ようするに f_luaopen を呼んでいるのだと考えればよい。実際のメンバの初期化は preinit_state と f_luaopen で行われていて、基本的には 0 や NULL で初期化したり、グローバル変数用のテーブルを作ったり、といったことが行われている。

一方、 lua_State はコルーチンでもあった。そちらには専用の関数がある。

lua_State *luaE_newthread (lua_State *L) {
  lua_State *L1 = mallocstate(L);
  luaC_link(L, valtogco(L1), LUA_TTHREAD);
  preinit_state(L1);
  L1->l_G = L->l_G;
  stack_init(L1, L);  /* init stack */
  setobj2n(gt(L1), gt(L));  /* share table of globals */
  return L1;
}

こちらは f_luaopen を使っていない。 stack_init で L を親としてスタックの初期化を行っている(f_luaopen 内でも自分自身を親にして stack_init は呼ばれている)。それからグローバルな状態を L1 にコピーして完了。 f_luaopen では実際にはグローバルな状態を初期化しているので、こちらはその処理が不要なぶん、短い。

lua_State を終了するときの関数は lua_close だ。

LUA_API void lua_close (lua_State *L) {
  lua_lock(L);
  L = G(L)->mainthread;  /* only the main thread can be closed */
  luaF_close(L, L->stack);  /* close all upvalues for this thread */
  luaC_separateudata(L);  /* separate udata that have GC metamethods */
  L->errfunc = 0;  /* no error function during GC metamethods */
  do {  /* repeat until no more errors */
    L->ci = L->base_ci;
    L->base = L->top = L->ci->base;
    L->nCcalls = 0;
  } while (luaD_rawrunprotected(L, callallgcTM, NULL) != 0);
  lua_assert(G(L)->tmudata == NULL);
  close_state(L);
}

基本的には、保有する様々なデータのメモリ領域を開放していくだけなのだが、ユーザデータには問題がある。というのは、ユーザデータは GC にタグつきメソッドを用意することができるからだ。つまりファイナライザである。 luaF_separateudata では所定のタグのあるユーザデータを検出し、 callallgcTM で実行している。終わったら開放しておしまいだ。

luaF_separateudata は lgc で定義されている。また callallgcTM も実体は lgc にあるから、そちらで読むことにする。