ldo
lua の関数呼出しを受け持つのが luaD_call である。しかし、細かいエラー処理を除くと、実際には luaD_precall を実行し、 luaD_poscall しているにすぎない。そのあいだに、 luaV_execute で実際の関数のコードを呼び出しているが、これは lvm のときまで待つ。
というわけで、 luaD_precall を見てみよう。この返り値から C 関数か lua 関数かを識別しているはずである。
StkId luaD_precall (lua_State *L, StkId func) { LClosure *cl; ptrdiff_t funcr = savestack(L, func); if (!ttisfunction(func)) /* `func' is not a function? */ func = tryfuncTM(L, func); /* check the `function' tag method */ if (L->ci + 1 == L->end_ci) luaD_growCI(L); else condhardstacktests(luaD_reallocCI(L, L->size_ci)); cl = &clvalue(func)->l; if (!cl->isC) { /* Lua function? prepare its call */ /* lua 関数の処理 */ } else { /* if is a C function, call it */ /* C 関数の処理 */ } }
全体の概形はこんなふうになっている。まず、関数呼出しのデータが関数型の値かどうかをチェックしている。そうでないときには tryfuncTM を呼び出している。メタテーブルで「関数呼出し」に相当するエントリがあるためである。
luaD_growCI は、 CallInfo のスタックが足りなかったときに realloc で伸ばしている操作。 condhardstacktests は気にしなくてよい。で、ようするに func のデータを取り出して、 isC かどうかを調べていると。 C 関数であれば適当な処理をして値を返す。 lua 関数なら NULL を返すはずである。
先に C 関数の処理を見てみる。
CallInfo *ci; int n; luaD_checkstack(L, LUA_MINSTACK); /* ensure minimum stack size */ ci = ++L->ci; /* now `enter' new function */ L->base = L->ci->base = restorestack(L, funcr) + 1; ci->top = L->top + LUA_MINSTACK; ci->state = CI_C; /* a C function */ if (L->hookmask & LUA_MASKCALL) luaD_callhook(L, LUA_HOOKCALL, -1); lua_unlock(L); #ifdef LUA_COMPATUPVALUES lua_pushupvalues(L); #endif n = (*clvalue(L->base - 1)->c.f)(L); /* do the actual call */ lua_lock(L); return L->top - n;
ci をひとつ進めて、新しい ci を取り出す(足りなければさっき grow_CI していたはずなのでここで足りないことはない)。で、 ci の値を初期化している。 funcr は restorestack は precall の冒頭で定義されている値で、 stack の始点と func の差を計算したもの。 restorestack はそれを再度足しているので、ようするに base を func の次のスタックとしている。で、 lua_State を引数に関数呼出しを実行しておしまい。 L->top - n というのはどういう意味かというのも後で詳しく見るが、この位置からスタックに積まれているものを、関数呼出しの返り値とするということである。つまり、 n 個の引数を持っている。
では、 lua の関数呼出しを見る。
CallInfo *ci; Proto *p = cl->p; if (p->is_vararg) /* varargs? */ adjust_varargs(L, p->numparams, func+1); luaD_checkstack(L, p->maxstacksize); ci = ++L->ci; /* now `enter' new function */ L->base = L->ci->base = restorestack(L, funcr) + 1; ci->top = L->base + p->maxstacksize; ci->u.l.savedpc = p->code; /* starting point */ ci->u.l.tailcalls = 0; ci->state = CI_SAVEDPC; while (L->top < ci->top) setnilvalue(L->top++); L->top = ci->top; return NULL;
Proto というのは、その lua 関数のプロトタイプ情報で、引数の数であるとか、可変長引数関数かとか、最大スタックサイズとか、そういった情報を保存している。 adjust_varargs は可変長引数のための関数である。で、
maxstacksize だけスタックを伸ばしても問題ないかチェックし、CallInfo
を取り出す。こちらは maxstacksize だけ top を加えている。で、 CallInfo には lua 関数の呼出しのための機構がいくつかある。 savedpc の pc は program counter だろうから、実行するプログラムカウンタを使う。で、 NULL を返している。
adjust_varargs も見てみる。
static void adjust_varargs (lua_State *L, int nfixargs, StkId base) { int i; Table *htab; TObject nname; int actual = L->top - base; /* actual number of arguments */ if (actual < nfixargs) { luaD_checkstack(L, nfixargs - actual); for (; actual < nfixargs; ++actual) setnilvalue(L->top++); } actual -= nfixargs; /* number of extra arguments */ htab = luaH_new(L, actual, 1); /* create `arg' table */ for (i=0; i<actual; i++) /* put extra arguments into `arg' table */ setobj2n(luaH_setnum(L, htab, i+1), L->top - actual + i); /* store counter in field `n' */ setsvalue(&nname, luaS_newliteral(L, "n")); setnvalue(luaH_set(L, htab, &nname), cast(lua_Number, actual)); L->top -= actual; /* remove extra elements from the stack */ sethvalue(L->top, htab); incr_top(L); }
nfixargs には「ふつうの引数の数」が入る。 top と base の差が実際の引数。つまり関数を呼ぶときには、まず関数オブジェクトを積み、それから引数をぜんぶ積んでから luaD_call するということになる。
ここからは、 lua のマニュアルにある次の挙動をもとに考える。
function f(a, b) end function g(a, b, ...) end function r() return 1,2,3 end g(3) a=3, b=nil, ... --> (nothing) g(3, 4) a=3, b=4, ... --> (nothing) g(3, 4, 5, 8) a=3, b=4, ... --> 5 8 g(5, r()) a=5, b=1, ... --> 2 3
まず actual が nfixargs より小さい場合。これは g(3) に対応する。このときには、通常の引数である b にも nil が入るように不足ぶんだけ top を伸ばす必要がある。
そうでない場合は actual から nfixargs を引くと、可変長引数の個数になる。が、可変長引数はテーブルとして扱う必要があるからこのままではマズい。そこで、まずテーブルを作成し、順にデータを格納していく。見直してほしいが、 luaH_new では第二引数が数値(配列)部の長さ、第三引数がハッシュの大きさなので、ほぼ配列として使っている。で、順にデータを入れていく。最後に n というキーで varargs の数を入れていた。ちょっと試す。
> function f (...) return arg.n; end > return f() 0 > return f(1) 1 > return f(1, 2, 3) 3 > return f(1, 2, 3, 4, 5) 5
なるほどそのようになっている。
テーブルができたら、可変長引数のぶんだけ top を巻戻して、作成したテーブルをスタックに積む。