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 可以单独使用, 做无条件跳转指令。跳转地址使用的是相对量,负数表示向前跳转。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 。
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 操作由 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;
}
}
}