当前位置: 首页 > 工具软件 > Lua VM > 使用案例 >

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

华宏逸
2023-12-01

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++;
  else
donextjump(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;
}
  }
}
 类似资料: