ひとり勉強会

ひとり楽しく勉強会

起動の流れ

〜mainからluaV_executeまで〜

ということで、次に、luaインタプリタの起動から、VMのメインループにたどりつくまでの流れを読みます。Luaはアプリケーションへの組み込みを考えてしっかり内部もAPI化されているので、始めて読む身には逆に中身が入り組んでいてちょっと大変でした(><)。それはともかく、mainです。mainのお仕事は、2つ関数を呼ぶことです。

// lua.c
int main (int argc, char **argv) {
  ...略...
  lua_State *L = lua_open();  /* create state */
  ...略...
  status = lua_cpcall(L, &pmain, &s);
  ...略...
}

lua_open は luaL_newstate への #define で、ここで、global_State と lua_State を新規に作って返します。新しいVMです。以下このVMで作業します。
lua_cpcallというのは(たぶん)Luaの枠組みでCの関数を呼び出す時の汎用の仕組みで、コルーチンを使ってる時などに重要になってくるのだと思いますが、ひとまずその辺りは置いておきます。あとで読みます。やることは要するに、pmain という関数の呼び出しです。

pmainも、おおざっぱに見ると、2つの関数を呼ぶだけの簡単なお仕事です。

// lua.c
static int pmain (lua_State *L) {
  ...略...
  luaL_openlibs(L);  /* open libraries */
  ...略...
  if (script)
    s->status = handle_script(L, argv, script);
}

luaL_openlibsは標準ライブラリの関数をグローバルテーブルに登録します。詳細はスキップ。引数の解析(これも詳細はスキップ)で、スクリプトファイルが指定されていると変数scriptに名前が入って、handle_script関数に渡されます。スクリプトファイルが指定されてない場合は対話シェルが始まりますが、そっちもとりあえずスキップ。一つのファイルを実行する場合に絞っていきましょう。

static int handle_script (lua_State *L, char **argv, int n) {
  ...略...
  status = luaL_loadfile(L, fname);
  lua_insert(L, -(narg+1));
  if (status == 0)
    status = docall(L, narg, 0);
  ...略...
}

handle_scriptは、luaL_loadfileでファイルをロードして、トップレベルのスクリプトコンパイルした関数オブジェクトをスタックに積みます。それを、docallで呼び出すと、いよいよ実行スタート!

そのまえに、luaL_loadfileの方も見ておかないとですね。怒濤のforwardingがはじまります。

// lauxlib.c
LUALIB_API int luaL_loadfile (lua_State *L, const char *filename) {
  ...略...
  status = lua_load(L, getF, &lf, lua_tostring(L, -1));
  ...略...
}

lua_loadへ

// lapi.c
LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
                      const char *chunkname) {
  ...略...
  status = luaD_protectedparser(L, &z, chunkname);
  ...略...
}

luaD_protectedparserへ

int luaD_protectedparser (lua_State *L, ZIO *z, const char *name) {
  ...略...
  status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc);
  ...略...
}

f_parserへ

// ldo.c
static void f_parser (lua_State *L, void *ud) {
  ...略...
  tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z,
                                                             &p->buff, p->name);
  cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));
  cl->l.p = tf;
  for (i = 0; i < tf->nups; i++)  /* initialize eventual upvalues */
    cl->l.upvals[i] = luaF_newupval(L);
  setclvalue(L, L->top, cl);
  incr_top(L);
}

ここです。スクリプトがprecompile済みのバイナリだった場合、luaU_undumpでロードします。そうでなかった場合は、luaY_parserで構文解析VM機械語へとコンパイルします。どちらの関数もProtoオブジェクトを返すので、それをClosureオブジェクトにして、スタック先頭 L->top の TValue に setclvalue でセットします。incr_top(L) でスタックを一つ進めて、実行準備完了です。

一方、docall側は・・・

static int docall (lua_State *L, int narg, int clear) {
  ...略...
  status = lua_pcall(L, narg, (clear ? 0 : LUA_MULTRET), base);
  ...略...
}

lua_pcallへ。スタックに積まれた引数と関数オブジェクトを適用する処理です。

// lapi.c
LUA_API int lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) {
  ...略...
  status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
  ...略...
}

f_callへ

// lapi.c
static void f_call (lua_State *L, void *ud) {
  struct CallS *c = cast(struct CallS *, ud);
  luaD_call(L, c->func, c->nresults);
}

luaD_callへ

// ldo.h
void luaD_call (lua_State *L, StkId func, int nResults) {
  ...略...
  if (luaD_precall(L, func, nResults) == PCRLUA)  /* is a Lua function? */
    luaV_execute(L, 1);  /* call it */
  ...略...
}

luaD_precallはCの関数だった場合はそれを呼び出し、Luaの関数だった場合は何もせずPCRLUAを返すようです。今回は必ずトップレベルのスクリプトコンパイルしたコードが入っているはずで、絶対Luaの関数なので、luaV_executeに行きます。

// lvm.c
void luaV_execute (lua_State *L, int nexeccalls) {
  ...略...
  /* main loop of interpreter */
  for (;;) {
    ...略...
  }
  ...略...
}

到着!!!

到着しました。次回は、このluaV_execute関数を調べていきます。よろしくお願いします。