Skip to content

Latest commit

 

History

History
87 lines (73 loc) · 4.28 KB

ch04-计算类指令.md

File metadata and controls

87 lines (73 loc) · 4.28 KB

数值计算类指令

相关指令

OP_ADD,/*   A B C   R(A) := RK(B) + RK(C)               */
OP_SUB,/*   A B C   R(A) := RK(B) - RK(C)               */
OP_MUL,/*   A B C   R(A) := RK(B) * RK(C)               */
OP_DIV,/*   A B C   R(A) := RK(B) / RK(C)               */
OP_MOD,/*   A B C   R(A) := RK(B) % RK(C)               */
OP_POW,/*   A B C   R(A) := RK(B) ^ RK(C)               */
OP_UNM,/*   A B R(A) := -R(B)                   */
OP_NOT,/*   A B R(A) := not R(B)                */

本节的内容相对简单,这里不会一个一个指令都做说明,仅就一个指令做些解释,其他的指令的处理是类似的.

二元计算

从OP_ADD,OP_SUB,OP_MUL,OP_DIV,OP_MOD,OP_POW几个指令的格式可以看到,B/C参数既可能从寄存器中获取,也可能从K数组中获取,具体的判断标准前面讲解指令格式的时候已经说明.

这里重点说一下Lua在做二元计算时做的一些优化,比如Lua代码:

local a = 4 + 7

如果按照我们前面的解释,那么理论上应该有两条指令,首先一条加法指令将4与7相加,其次将这个和赋值给局部变量a中. 但是Lua在这里做了一些优化,判断加法操作的两个对象都是常量数字时,会进行常量合并(const folding)操作,首先在计算时将他们的计算结果计算好,再赋值给相应的变量:

(lcode.c)
635 static int constfolding (OpCode op, expdesc *e1, expdesc *e2) {
636   lua_Number v1, v2, r;
637   if (!isnumeral(e1) || !isnumeral(e2)) return 0;
638   v1 = e1->u.nval;
639   v2 = e2->u.nval;
640   switch (op) {
641     case OP_ADD: r = luai_numadd(v1, v2); break;
642     case OP_SUB: r = luai_numsub(v1, v2); break;
643     case OP_MUL: r = luai_nummul(v1, v2); break;
644     case OP_DIV:
645       if (v2 == 0) return 0;  /* do not attempt to divide by 0 */
646       r = luai_numdiv(v1, v2); break;
647     case OP_MOD:
648       if (v2 == 0) return 0;  /* do not attempt to divide by 0 */
649       r = luai_nummod(v1, v2); break;
650     case OP_POW: r = luai_numpow(v1, v2); break;
651     case OP_UNM: r = luai_numunm(v1); break;
652     case OP_LEN: return 0;  /* no constant folding for 'len' */
653     default: lua_assert(0); r = 0; break;
654   }
655   if (luai_numisnan(r)) return 0;  /* do not attempt to produce NaN */
656   e1->u.nval = r;
657   return 1;
658 }

但是并不是任何时候,只要操作的对象是常量都可以进行常量合并,还需要考虑到操作的优先级,来看另一种情况:

local a = b + 4 + 7

在这里,"="号右边的表达式,首先解析了"b",然后是操作符"+",因为此时前面没有别的操作,所以此时ADD操作优先级是最高的,继续往下执行,解析了"4"以及操作符"+",但是第二个ADD操作的优先级并没有比前一个高,所以仍然是优先执行前面的"b + 4"操作.在整个表达式解析完毕时,大致是如此的:首先计算b+4的结果存放一个临时寄存器中,然后将第一步的结果与7进行相加.可见在这里,"4+7"并没有进行常量合并,如果需要优化的话,那么需要显式的在"4+7"前后加括号才能让这个操作进行常量合并.这部分对应的代码在:

(lparser.c)
828 static BinOpr subexpr (LexState *ls, expdesc *v, unsigned int limit) {
829   BinOpr op;
830   UnOpr uop;
831   enterlevel(ls);
832   uop = getunopr(ls->t.token);
833   if (uop != OPR_NOUNOPR) {
834     luaX_next(ls);
835     subexpr(ls, v, UNARY_PRIORITY);
836     luaK_prefix(ls->fs, uop, v);
837   }
838   else simpleexp(ls, v);
839   /* expand while operators have priorities higher than `limit' */
840   op = getbinopr(ls->t.token);
841   while (op != OPR_NOBINOPR && priority[op].left > limit) {
842     expdesc v2;
843     BinOpr nextop;
844     luaX_next(ls);
845     luaK_infix(ls->fs, op, v);
846     /* read sub-expression with higher priority */
847     nextop = subexpr(ls, &v2, priority[op].right);
848     luaK_posfix(ls->fs, op, v, &v2);
849     op = nextop;
850   }
851   leavelevel(ls);
852   return op;  /* return first untreated operator */
853 }

这个函数的代码,可以分为三部分:832-838行首先解析第一个操作对象,840行解析操作符,841-850行解析第二个操作对象.需要注意的是,第三部分中也会调用这个subexpr函数,是一个递归的调用.