Lua5.3 VM 分析(四)分支和跳转

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

Lua5.3 VM 分析(四)分支和跳转

Lua VM 定义了 OP_EQ、OP_LT、OP_LE、OP_TEST、OP_TESTSET 五种分支操作。

这五个分支指令必须与 之后的 跳转指令 JMP 看做一个整体解释。也就是说:当条件成立时,继续运行;条件不成立时,跳转到指定位置。

如果条件成立跳转到L1, 否则跳转到L2: L1:     success()     jmp exit L2:     fail()     jmp exit exit:     exit()

OP_JMP

OP_JMP 可以单独使用, 做无条件跳转指令。跳转地址使用的是相对量,负数表示向前跳转。0 表示下一条指令。

vmcase(OP_JMP) {        dojump(ci, i, 0);        vmbreak;      }define dojump(ci,i,e) \  { int a = GETARG_A(i); \if (a > 0) luaF_close(L, ci->u.l.base + a - 1); \ci->u.l.savedpc += GETARG_sBx(i) + e; }define donextjump(ci)   { i = *ci->u.l.savedpc; dojump(ci, i, 1); }

无条件跳转和条件跳转分别用 dojump 宏 与 donextjump 两个宏实现。
donextjump 读出下一条指令,必定是JMP。

dojump 宏负责 指令的偏移 ( ci->u.l.savedpc += GETARG_sBx(i) + e; )。
当 a (Instruction 的 A 部分)大于 0 时,调用luaF_close 函数关闭 A 层次的 upvalue 。

OP_EQ

  vmcase(OP_EQ) {TValue *rb = RKB(i);TValue *rc = RKC(i);Protect(  if (cast_int(luaV_equalobj(L, rb, rc)) != GETARG_A(i))ci->u.l.savedpc++;  elsedonextjump(ci);)vmbreak;  }

利用 luaV_equalobj 函数判断条件是否成立,当不成立时,执行 donextjump 。反则根据 类型不同比较算法不同。

int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) {  const TValue *tm;  if (ttype(t1) != ttype(t2)) {  /* not the same variant? */if (ttnov(t1) != ttnov(t2) || ttnov(t1) != LUA_TNUMBER)  return 0;  /* only numbers can be equal with different variants */else {  /* two numbers with different variants */  lua_Number n1, n2;  /* compare them as floats */  lua_assert(ttisnumber(t1) && ttisnumber(t2));  cast_void(tofloat(t1, &n1)); cast_void(tofloat(t2, &n2));  return luai_numeq(n1, n2);}  }  /* values have same type and same variant */  switch (ttype(t1)) {case LUA_TNIL: return 1;case LUA_TNUMINT: return (ivalue(t1) == ivalue(t2));case LUA_TNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2));case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2);  /* true must be 1 !! */case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2);case LUA_TLCF: return fvalue(t1) == fvalue(t2);case LUA_TSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2));case LUA_TLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2));case LUA_TUSERDATA: {  if (uvalue(t1) == uvalue(t2)) return 1;  else if (L == NULL) return 0;  tm = fasttm(L, uvalue(t1)->metatable, TM_EQ);  if (tm == NULL)tm = fasttm(L, uvalue(t2)->metatable, TM_EQ);  break;  /* will try TM */}case LUA_TTABLE: {  if (hvalue(t1) == hvalue(t2)) return 1;  else if (L == NULL) return 0;  tm = fasttm(L, hvalue(t1)->metatable, TM_EQ);  if (tm == NULL)tm = fasttm(L, hvalue(t2)->metatable, TM_EQ);  break;  /* will try TM */}default:  return gcvalue(t1) == gcvalue(t2);  }  if (tm == NULL)  /* no TM? */return 0;  /* objects are different */  luaT_callTM(L, tm, t1, t2, L->top, 1);  /* call TM */  return !l_isfalse(L->top);}

Lua 的不同类型比较操作分开处理。
其中 LUA_TNIL、LUA_TNUMINT 、LUA_TNUMFLT、LUA_TBOOLEAN、LUA_TLIGHTUSERDATA、LUA_TLCF、LUA_TUSERDATA 都是值类型,只需要值比较。O(1) 操作。

LUA_TSHRSTR 短字符串会做字符串内部化,所以只需要直接比较指针。

define eqshrstr(a,b)    check_exp((a)->tt == LUA_TSHRSTR, (a) == (b))

LUA_TLNGSTR 长字符串可能会触发完整的比较:

int luaS_eqlngstr (TString *a, TString *b) {  size_t len = a->len;  lua_assert(a->tt == LUA_TLNGSTR && b->tt == LUA_TLNGSTR);  return (a == b) ||  /* same instance or... */((len == b->len) &&  /* equal length and ... */ (memcmp(getstr(a), getstr(b), len) == 0));  /* equal contents */}

OP_LT OP_LE

OP_LT 操作由 luaV_lessthan 函数完成,OP_LE 操作由luaV_lessequal 函数完成。大于操作可以由小于等于 取反得到。

luaV_lessthan 函数逻辑:
1. 如果两个参数都是 integer 则利用ivalue 转换类型 直接比较。
2. 如果两个参数都是 float 则利用luai_numlt 比较。
3. 如果两个参数都是 字符串,则调用l_strcmp 辅助函数比较。
4. 触发LT 元方法
luaV_ lessequal 函数逻辑:
1. 如果两个参数都是 integer 则利用ivalue 转换类型 直接比较。
2. 如果两个参数都是 float 则利用luai_numlt 比较。
3. 如果两个参数都是 字符串,则调用l_strcmp 辅助函数比较。
4. 尝试触发LE 元方法
5. 尝试触发LT 元方法

    vmcase(OP\_LT) {    Protect(      if (luaV\_lessthan(L, RKB(i), RKC(i)) != GETARG\_A(i))    ci->u.l.savedpc++;      else    donextjump(ci);    )    vmbreak;      }      vmcase(OP\_LE) {    Protect(      if (luaV\_lessequal(L, RKB(i), RKC(i)) != GETARG\_A(i))    ci->u.l.savedpc++;      else    donextjump(ci);    )    vmbreak;      }    int luaV\_lessthan (lua\_State *L, const TValue *l, const TValue \*r) {      int res;      lua\_Number nl, nr;      if (ttisinteger(l) && ttisinteger(r))  /\* both operands are integers? \*/    return (ivalue(l) < ivalue(r));      else if (tofloat(l, &nl) && tofloat(r, &nr))  /\* both are numbers? \*/    return luai\_numlt(nl, nr);      else if (ttisstring(l) && ttisstring(r))  /\* both are strings? \*/    return l\_strcmp(tsvalue(l), tsvalue(r)) < 0;      else if ((res = luaT\_callorderTM(L, l, r, TM\_LT)) < 0)  /\* no metamethod? \*/    luaG\_ordererror(L, l, r);  /\* error \*/      return res;    }    int luaV\_lessequal (lua\_State *L, const TValue *l, const TValue \*r) {      int res;      lua\_Number nl, nr;      if (ttisinteger(l) && ttisinteger(r))  /\* both operands are integers? \*/    return (ivalue(l) <= ivalue(r));      else if (tofloat(l, &nl) && tofloat(r, &nr))  /\* both are numbers? \*/    return luai\_numle(nl, nr);      else if (ttisstring(l) && ttisstring(r))  /\* both are strings? \*/    return l\_strcmp(tsvalue(l), tsvalue(r)) <= 0;      else if ((res = luaT\_callorderTM(L, l, r, TM\_LE)) >= 0)  /\* first try 'le' \*/    return res;      else if ((res = luaT\_callorderTM(L, r, l, TM\_LT)) < 0)  /\* else try 'lt' \*/    luaG\_ordererror(L, l, r);      return !res;    }

字符串比较

Lua 字符串比较并不是简单的调用 C 标准库函函数 memcmp 比较的,而是考虑了系统locale 定义,利用 C 标准库函数 strcoll 来实现。

Lua 的字符串与 C字符串不同,不以’\0’做结束标志,也就是说 在Lua TString
中可能包含 ‘\0’。一次strcoll 调用并不一定得到结果。每次strcoll 比较完,如果字符串相等则要跳过’\0’继续比较,直到 达到TString 中记录的字符串的长度为止。

static int l_strcmp (const TString *ls, const TString *rs) {  const char *l = getstr(ls);  size_t ll = ls->len;  const char *r = getstr(rs);  size_t lr = rs->len;  for (;;) {  /* for each segment */int temp = strcoll(l, r);if (temp != 0)  /* not equal? */  return temp;  /* done */else {  /* strings are equal up to a '\0' */  size_t len = strlen(l);  /* index of first '\0' in both strings */  if (len == lr)  /* 'rs' is finished? */return (len == ll) ? 0 : 1;  /* check 'ls' */  else if (len == ll)  /* 'ls' is finished? */return -1;  /* 'ls' is smaller than 'rs' ('rs' is not finished) */  /* both strings longer than 'len'; go on comparing after the '\0' */  len++;  l += len; ll -= len; r += len; lr -= len;}  }}