VM命令:演算子
Luaの演算子に一対一に対応した命令がそれぞれ用意されています。OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_MOD, OP_POW, OP_UNM, OP_NOT, OP_LEN, OP_CONCAT, OP_GETTABLE, OP_SETTABLE。例として、OP_ADDの実装はこんなんです。
case OP_ADD: { arith_op(luai_numadd, TM_ADD); continue; }
arith_opマクロはこう。
#define arith_op(op,tm) { \ TValue *rb = RKB(i); \ TValue *rc = RKC(i); \ if (ttisnumber(rb) && ttisnumber(rc)) { \ lua_Number nb = nvalue(rb), nc = nvalue(rc); \ setnvalue(ra, op(nb, nc)); \ } \ else \ Protect(Arith(L, ra, rb, rc, tm)); \ }
オペランドBとCの指すレジスタ(または定数)を足し算してAにセットします。両方ともnumberなら普通の足し算を行い、すでない場合はメタテーブルに足し算が定義されているかどうかを見に行きます。
static void Arith (lua_State *L, StkId ra, const TValue *rb, const TValue *rc, TMS op) { ... else if (!call_binTM(L, rb, rc, ra, op)) luaG_aritherror(L, rb, rc); }
call_binTMでメタテーブルをトライ。
static int call_binTM (lua_State *L, const TValue *p1, const TValue *p2, StkId res, TMS event) { const TValue *tm = luaT_gettmbyobj(L, p1, event); /* try first operand */ if (ttisnil(tm)) tm = luaT_gettmbyobj(L, p2, event); /* try second operand */ if (ttisnil(tm)) return 0; callTMres(L, res, tm, p1, p2); return 1; }
こんなんです。ほぼマニュアル(http://www.lua.org/manual/5.1/manual.html#2.8)に出てくる擬似コードをそのままCで実装した感じですね。
先程あげた命令の中で文字列結合演算 OP_CONCAT だけは命令フォーマットが変わっていて、「R[B]からR[C]までの範囲のレジスタの値を全部繋げてR[A]に格納」となっています。a..b..c..d のように複数一気に繋ぐ場合を最適化するためでしょうか。
case OP_CONCAT: { int b = GETARG_B(i); int c = GETARG_C(i); Protect(luaV_concat(L, c-b+1, c); luaC_checkGC(L)); setobjs2s(L, RA(i), base+b); continue; }
luaV_concatの中身については省略。
他の演算子としては、比較演算とand/orがありますが、これは次に説明します。(あ、いや、正確には、and/orは特別に対応する命令はなくて、条件分岐の組み合わせにコンパイルされるので、説明しません。)