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

来源:互联网 发布:已备案域名已删除 编辑:程序博客网 时间:2024/06/09 15:19

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 */}