Lua5.3 VM 分析(三)表达式运算

二元运算

+、-、*、%、^、/、//、&、 |、\~、\<\<、>> 这 12 种二元运算
OP_ADD、OP_SUB、OP_MUL、OP_DIV、OP_POW、OP_MOD、OP_IDIV、OP_BAND、OP_BOR、OP_BXOR、OP_SHL、OP_SHR
###

vmcase(OP_ADD) {
                TValue *rb = RKB(i);
                TValue *rc = RKC(i);
                lua_Number nb; lua_Number nc;
                if (ttisinteger(rb) && ttisinteger(rc)) {
                    lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
                    setivalue(ra, intop(+, ib, ic));
                }
                else if (tonumber(rb, &nb) && tonumber(rc, &nc)) {
                    setfltvalue(ra, luai_numadd(L, nb, nc));
                }
                else {
                    Protect(luaT_trybinTM(L, rb, rc, ra, TM_ADD));
                }
                vmbreak;
            }

这些操作类似,都是以两个对象作为操作对象,经过运算后,将结果放入寄存器 A 中。
对于这些数值类型之间的运算,做了优化,不会判断和出发元方法。这样可以提高效率。

1、首先取出 两个参数的值 rb 与 rc ,利用 ttisinteger 宏判断是不是 lua_Integer 小类型,如果是则利用 ivalue 宏 转换成 lua_Integer 类型的值。
TValue *io=ra; val_(ra).i=(ib+ic); settt_(io, LUA_TNUMINT);

将 ib 与 ic 做数值运算后的结果赋值给 ra.i (lua_Integer 类型 Value 结构中定义)
最后对ra->tt_ 设置类型标志。

2、如果不是 LUA_TNUMINT 子类型 则可能是 LUA_TNUMFLT 子类型
(float),利用tonumber 宏将 rb 与 rc 转换后存储在 lua_Number nb;
lua_Number nc; 最后
TValue *io=(ra); val_(io).n=(nb+nc); settt_(io, LUA_TNUMFLT);

3、其他情况 触发 元方法,有点复杂。用Call_binTM 函数做了一些封装。
函数逻辑: 先判断第一个对象是否有需要的元方法,如果找不到则去第二个函数
上面查找元方法。 如果两个对象上都没有找打元方法则返回 到 luaT_trybinTM
函数中,除了TM_CONCAT、TM_BAND、TM_BOR、TM_BXOR、TM_SHL、
TM_SHR、TM_BNOT 特殊处理外 ,利用 luaG_opinterror 抛出异常。

vmcase(OP_ADD) { 
        TValue *rb = RKB(i);
        TValue *rc = RKC(i);
        lua_Number nb; lua_Number nc;
        if (ttisinteger(rb) && ttisinteger(rc)) {
          lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
          setivalue(ra, intop(+, ib, ic));
        }
        else if (tonumber(rb, &nb) && tonumber(rc, &nc)) {
          setfltvalue(ra, luai_numadd(L, nb, nc));
        }
        else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_ADD)); }
        vmbreak;
      }


int luaT_callbinTM (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;
  luaT_callTM(L, tm, p1, p2, res, 1);
  return 1;
}

void luaT_trybinTM (lua_State *L, const TValue *p1, const TValue *p2,
                    StkId res, TMS event) {
  if (!luaT_callbinTM(L, p1, p2, res, event)) {
    switch (event) {
      case TM_CONCAT:
        luaG_concaterror(L, p1, p2);
      case TM_BAND: case TM_BOR: case TM_BXOR:
      case TM_SHL: case TM_SHR: case TM_BNOT: {
        lua_Number dummy;
        if (tonumber(p1, &dummy) && tonumber(p2, &dummy))
          luaG_tointerror(L, p1, p2);
        else
          luaG_opinterror(L, p1, p2, "perform bitwise operation on");
        /* else go through */
      }
      default:
        luaG_opinterror(L, p1, p2, "perform arithmetic on");
    }
  }
}

其中 OP_ADD、OP_SUB、OP_MUL、OP_MOD、OP_IDIV 总是处理 int 、float、元方法 三种情况。
OP_DIV 、OP_POW 总是处理 float 类型 与 元方法 两种情况。
OP_BAND、OP_BOR、OP_BXOR、OP_SHL、OP_SHR 这几种opcode 只处理 int 与 元方法 两种情况。

一元运算

-、\~、not、length of R(B) 这 4 种一元运算
OP_UNM、OP_BNOT、OP_NOT、OP_LEN

OP_UNM

总是处理 int 、float、元方法 三种情况。

vmcase(OP_UNM) {
        TValue *rb = RB(i);
        lua_Number nb;
        if (ttisinteger(rb)) {
          lua_Integer ib = ivalue(rb);
          setivalue(ra, intop(-, 0, ib));
        }
        else if (tonumber(rb, &nb)) {
          setfltvalue(ra, luai_numunm(L, nb));
        }
        else {
          Protect(luaT_trybinTM(L, rb, rb, ra, TM_UNM));
        }
        vmbreak;
      }

OP_BNOT

总是处理 int 、元方法 两种情况。

vmcase(OP_BNOT) {
TValue *rb = RB(i);
lua_Integer ib;
if (tointeger(rb, &ib)) {
  setivalue(ra, intop(^, ~l_castS2U(0), ib));
}
else {
  Protect(luaT_trybinTM(L, rb, rb, ra, TM_BNOT));
}
vmbreak;
  }

OP_NOT

对 rb 取负

vmcase(OP_NOT) {
TValue *rb = RB(i);
int res = l_isfalse(rb);  /* next assignment may change this value */
setbvalue(ra, res);
vmbreak;
  }

OP_LEN

Len 操作用于取对象长度, 根据 Lua 的定义:
1. 对于字符串取长度;
2. 对于表则取数组部分长度;
3. 其他情况调用 元方法 len。

void luaV\_objlen (lua\_State *L, StkId ra, const TValue *rb) {
      const TValue \*tm;
      switch (ttnov(rb)) {
    case LUA_TTABLE: {
      Table *h = hvalue(rb);
      tm = fasttm(L, h->metatable, TM_LEN);
      if (tm) break;  /* metamethod? break switch to call it */
      setivalue(ra, luaH_getn(h));  /* else primitive len */
      return;
    }
    case LUA_TSTRING: {
      setivalue(ra, tsvalue(rb)->len);
      return;
    }
    default: {  /* try metamethod */
      tm = luaT_gettmbyobj(L, rb, TM_LEN);
      if (ttisnil(tm))  /* no metamethod? */
        luaG_typeerror(L, rb, "get length of");
      break;
    }
      }
      luaT\_callTM(L, tm, rb, rb, ra, 1);
    }

OP_CONCAT 字符串链接

将R(B) 到 R(C) 之间的所有值,都以字符串方式连接起来,把结果放到R(A) 中。这个连接过程是通过 luaV_concat 函数完成的。

函数逻辑:
1. 通过临时修改栈顶地址为 C ,然后连接 R(B) 到 R(C) 的值,将结果临时存在 R(B) 中。
2. 再将R(B) 复制到 R(A),最后将栈顶位置调整回去。
3. R(B)到R(C) 以及之后的寄存器,不能被后续指令读取。也就是说 R(B) 与 R(C) 寄存器必须在栈顶工作。

vmcase(OP\_CONCAT) {
    int b = GETARG\_B(i);
    int c = GETARG\_C(i);
    StkId rb;
    L->top = base + c + 1;  /\* mark the end of concat operands \*/
    Protect(luaV\_concat(L, c - b + 1));
    ra = RA(i);  /\* 'luav\_concat' may invoke TMs and move the stack \*/
    rb = b + base;
    setobjs2s(L, ra, rb);
    checkGC(L, (ra >= rb ? ra + 1 : rb));
    L->top = ci->top;  /\* restore top \*/
    vmbreak;
      }

由于字符串连接操作,可能会触发元方法,导致数据栈空间扩展。所以必须在luaV_concat 函数调用完后 重新获取 ra = RA(i) (因为ra 不再指向原来的位置)。
在OP_CONCAT 操作的最后,重置了数据栈的栈顶。

Lua字节码以寄存器的方法来理解数据栈空间,在大多数情况下,用到多少寄存器是在编译期生成字节码的时候决定的。所以在函数原型Proto 结构里有 maxstacksize 这个信息,同时在运行时,会把这段空间的top 记录在 CallInfo->top 中。 Lua VM 在运行时 会以堆栈的方法利用这个数据栈,这种栈形式利用数据堆栈都是临时行为,使用完毕后应该重置数据栈栈顶。

void luaV_concat (lua_State *L, int total) {
  lua_assert(total >= 2);
  do {
    StkId top = L->top;
    int n = 2;  /* number of elements handled in this pass (at least 2) */
    if (!(ttisstring(top-2) || cvt2str(top-2)) || !tostring(L, top-1))
      luaT_trybinTM(L, top-2, top-1, top-2, TM_CONCAT);
    else if (tsvalue(top-1)->len == 0)  /* second operand is empty? */
      cast_void(tostring(L, top - 2));  /* result is first operand */
    else if (ttisstring(top-2) && tsvalue(top-2)->len == 0) {
      setobjs2s(L, top - 2, top - 1);  /* result is second op. */
    }
    else {
      /* at least two non-empty string values; get as many as possible */
      size_t tl = tsvalue(top-1)->len;
      char *buffer;
      int i;
      /* collect total length */
      for (i = 1; i < total && tostring(L, top-i-1); i++) {
        size_t l = tsvalue(top-i-1)->len;
        if (l >= (MAX_SIZE/sizeof(char)) - tl)
          luaG_runerror(L, "string length overflow");
        tl += l;
      }
      buffer = luaZ_openspace(L, &G(L)->buff, tl);
      tl = 0;
      n = i;
      do {  /* copy all strings to buffer */
        size_t l = tsvalue(top-i)->len;
        memcpy(buffer+tl, svalue(top-i), l * sizeof(char));
        tl += l;
      } while (--i > 0);
      setsvalue2s(L, top-n, luaS_newlstr(L, buffer, tl));  /* create result */
    }
    total -= n-1;  /* got 'n' strings to create 1 new */
    L->top -= n-1;  /* popped 'n' strings and pushed one */
  } while (total > 1);  /* repeat until only 1 result left */
}