+、-、*、%、^、/、//、&、 |、\~、\<\<、>> 这 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
总是处理 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;
}
总是处理 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;
}
对 rb 取负
vmcase(OP_NOT) {
TValue *rb = RB(i);
int res = l_isfalse(rb); /* next assignment may change this value */
setbvalue(ra, res);
vmbreak;
}
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);
}
将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 */
}