ひとり勉強会

ひとり楽しく勉強会

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は特別に対応する命令はなくて、条件分岐の組み合わせにコンパイルされるので、説明しません。)