diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index ab58bc1651..f799b8d039 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -7105,6 +7105,7 @@ frontend::EmitTree(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) case PNK_MULASSIGN: case PNK_DIVASSIGN: case PNK_MODASSIGN: + case PNK_POWASSIGN: if (!EmitAssignment(cx, bce, pn->pn_left, pn->getOp(), pn->pn_right)) return false; break; @@ -7154,6 +7155,20 @@ frontend::EmitTree(ExclusiveContext* cx, BytecodeEmitter* bce, ParseNode* pn) break; } + case PNK_POW: { + MOZ_ASSERT(pn->isArity(PN_LIST)); + /* Right-associative operator chain. */ + for (ParseNode* subexpr = pn->pn_head; subexpr; subexpr = subexpr->pn_next) { + if (!EmitTree(cx, bce, subexpr)) + return false; + } + for (int i = 0; i < pn->pn_count - 1; i++) { + if (!Emit1(cx, bce, JSOP_POW)) + return false; + } + break; + } + case PNK_THROW: case PNK_TYPEOF: case PNK_VOID: diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 956a55a816..2eaf119e8d 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -363,6 +363,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_STAR: case PNK_DIV: case PNK_MOD: + case PNK_POW: case PNK_ASSIGN: case PNK_ADDASSIGN: case PNK_SUBASSIGN: @@ -375,6 +376,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_MULASSIGN: case PNK_DIVASSIGN: case PNK_MODASSIGN: + case PNK_POWASSIGN: case PNK_COMMA: case PNK_ARRAY: case PNK_OBJECT: @@ -523,6 +525,10 @@ FoldBinaryNumeric(ExclusiveContext* cx, JSOp op, ParseNode* pn1, ParseNode* pn2, d = js_fmod(d, d2); } break; + + case JSOP_POW: + d = ecmaPow(d, d2); + break; default:; } @@ -1001,6 +1007,7 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, case PNK_URSH: case PNK_DIV: case PNK_MOD: + case PNK_POW: MOZ_ASSERT(pn->getArity() == PN_LIST); MOZ_ASSERT(pn->pn_count >= 2); for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { @@ -1012,6 +1019,13 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, if (!pn2->isKind(PNK_NUMBER)) break; } + + // No constant-folding for (a**b**c[**d][...]), because + // (**) is right-associative and we would have to reverse + // the list first. It's not worth doing that. + if (pn->getKind() == PNK_POW && pn->pn_count > 2) + break; + if (!pn2) { JSOp op = pn->getOp(); diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 679788de96..fb2ee74d99 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -257,6 +257,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_MULASSIGN: case PNK_DIVASSIGN: case PNK_MODASSIGN: + case PNK_POWASSIGN: // ...and a few others. case PNK_ELEM: case PNK_IMPORT_SPEC: @@ -442,6 +443,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_STAR: case PNK_DIV: case PNK_MOD: + case PNK_POW: case PNK_COMMA: case PNK_NEW: case PNK_CALL: @@ -575,7 +577,18 @@ ParseNode::appendOrCreateList(ParseNodeKind kind, JSOp op, ParseNode* left, Pars // processing such a tree, exactly implemented that way, would blow the // the stack. We use a list node that uses O(1) stack to represent // such operations: (+ a b c). - if (left->isKind(kind) && left->isOp(op) && (js_CodeSpec[op].format & JOF_LEFTASSOC)) { + // + // (**) is right-associative; per spec |a ** b ** c| parses as + // (** a (** b c)). But we treat this the same way, creating a list + // node: (** a b c). All consumers must understand that this must be + // processed with a right fold, whereas the list (+ a b c) must be + // processed with a left fold because (+) is left-associative. + // + if (left->isKind(kind) && + left->isOp(op) && + (js_CodeSpec[op].format & JOF_LEFTASSOC || + (kind == PNK_POW && !left->pn_parens))) + { ListNode* list = &left->as(); list->append(right); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index a856945e5b..df682e342b 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -183,6 +183,7 @@ class UpvarCookie F(STAR) \ F(DIV) \ F(MOD) \ + F(POW) \ \ /* Assignment operators (= += -= etc.). */ \ /* ParseNode::isAssignment assumes all these are consecutive. */ \ @@ -197,7 +198,8 @@ class UpvarCookie F(URSHASSIGN) \ F(MULASSIGN) \ F(DIVASSIGN) \ - F(MODASSIGN) + F(MODASSIGN) \ + F(POWASSIGN) /* * Parsing builds a tree of nodes that directs code generation. This tree is @@ -216,9 +218,9 @@ enum ParseNodeKind #undef EMIT_ENUM PNK_LIMIT, /* domain size */ PNK_BINOP_FIRST = PNK_OR, - PNK_BINOP_LAST = PNK_MOD, + PNK_BINOP_LAST = PNK_POW, PNK_ASSIGNMENT_START = PNK_ASSIGN, - PNK_ASSIGNMENT_LAST = PNK_MODASSIGN + PNK_ASSIGNMENT_LAST = PNK_POWASSIGN }; /* diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 370a58ea56..6753da52c7 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -5871,7 +5871,8 @@ static const JSOp ParseNodeKindToJSOp[] = { JSOP_SUB, JSOP_MUL, JSOP_DIV, - JSOP_MOD + JSOP_MOD, + JSOP_POW }; static inline JSOp @@ -5918,7 +5919,8 @@ static const int PrecedenceTable[] = { 9, /* PNK_SUB */ 10, /* PNK_STAR */ 10, /* PNK_DIV */ - 10 /* PNK_MOD */ + 10, /* PNK_MOD */ + 11 /* PNK_POW */ }; static const int PRECEDENCE_CLASSES = 10; @@ -5940,8 +5942,8 @@ template MOZ_ALWAYS_INLINE typename ParseHandler::Node Parser::orExpr1(InvokedPrediction invoked) { - // Shift-reduce parser for the left-associative binary operator part of - // the JS syntax. + // Shift-reduce parser for the binary operator part of the JS expression + // syntax. // Conceptually there's just one stack, a stack of pairs (lhs, op). // It's implemented using two separate arrays, though. @@ -5975,10 +5977,9 @@ Parser::orExpr1(InvokedPrediction invoked) // stack, reduce. This combines nodes on the stack until we form the // actual lhs of pnk. // - // The >= in this condition works because all the operators in question - // are left-associative; if any were not, the case where two operators - // have equal precedence would need to be handled specially, and the - // stack would need to be a Vector. + // The >= in this condition works because it is appendOrCreateList's + // job to decide if the operator in question is left- or + // right-associative, and build the corresponding tree. while (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(pnk)) { depth--; ParseNodeKind combiningPnk = kindStack[depth]; @@ -6176,6 +6177,7 @@ Parser::assignExpr(InvokedPrediction invoked) case TOK_MULASSIGN: kind = PNK_MULASSIGN; op = JSOP_MUL; break; case TOK_DIVASSIGN: kind = PNK_DIVASSIGN; op = JSOP_DIV; break; case TOK_MODASSIGN: kind = PNK_MODASSIGN; op = JSOP_MOD; break; + case TOK_POWASSIGN: kind = PNK_POWASSIGN; op = JSOP_POW; break; case TOK_ARROW: { tokenStream.seek(start); diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index cf5d67a2e5..d4137567c7 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -119,7 +119,7 @@ * range-testing. \ */ \ /* \ - * Binary operators tokens, TOK_OR thru TOK_MOD. These must be in the same \ + * Binary operators tokens, TOK_OR thru TOK_POW. These must be in the same \ * order as F(OR) and friends in FOR_EACH_PARSE_NODE_KIND in ParseNode.h. \ */ \ macro(OR, "'||'") /* logical or */ \ @@ -160,7 +160,8 @@ macro(MUL, "'*'") \ macro(DIV, "'/'") \ macro(MOD, "'%'") \ - range(BINOP_LAST, MOD) \ + macro(POW, "'**'") \ + range(BINOP_LAST, POW) \ \ /* Unary operation tokens. */ \ macro(TYPEOF, "keyword 'typeof'") \ @@ -184,7 +185,8 @@ macro(MULASSIGN, "'*='") \ macro(DIVASSIGN, "'/='") \ macro(MODASSIGN, "'%='") \ - range(ASSIGNMENT_LAST, MODASSIGN) + macro(POWASSIGN, "'*='") \ + range(ASSIGNMENT_LAST, POWASSIGN) #define TOKEN_KIND_RANGE_EMIT_NONE(name, value) #define FOR_EACH_TOKEN_KIND(macro) \ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index a2360f51c2..1e73d16a49 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1499,7 +1499,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) goto out; case '*': - tp->type = matchChar('=') ? TOK_MULASSIGN : TOK_MUL; + if (matchChar('*')) + tp->type = matchChar('=') ? TOK_POWASSIGN : TOK_POW; + else + tp->type = matchChar('=') ? TOK_MULASSIGN : TOK_MUL; goto out; case '/': diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index cd17fdb0eb..f5f06ead3d 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1577,6 +1577,12 @@ BaselineCompiler::emit_JSOP_MOD() return emitBinaryArith(); } +bool +BaselineCompiler::emit_JSOP_POW() +{ + return emitBinaryArith(); +} + bool BaselineCompiler::emitBinaryArith() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 2dba2ef86e..a4b73fa440 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -78,6 +78,7 @@ namespace jit { _(JSOP_MUL) \ _(JSOP_DIV) \ _(JSOP_MOD) \ + _(JSOP_POW) \ _(JSOP_LT) \ _(JSOP_LE) \ _(JSOP_GT) \ diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 280cea3864..fc82c963e3 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -2459,6 +2459,10 @@ DoBinaryArithFallback(JSContext* cx, BaselineFrame* frame, ICBinaryArith_Fallbac if (!ModValues(cx, &lhsCopy, &rhsCopy, ret)) return false; break; + case JSOP_POW: + if (!math_pow_handle(cx, lhsCopy, rhsCopy, ret)) + return false; + break; case JSOP_BITOR: { int32_t result; if (!BitOr(cx, lhs, rhs, &result)) @@ -2559,7 +2563,7 @@ DoBinaryArithFallback(JSContext* cx, BaselineFrame* frame, ICBinaryArith_Fallbac } // Handle only int32 or double. - if (!lhs.isNumber() || !rhs.isNumber()) { + if (!lhs.isNumber() || !rhs.isNumber() && op != JSOP_POW) { stub->noteUnoptimizableOperands(); return true; } @@ -2812,6 +2816,7 @@ ICBinaryArith_Double::Compiler::generateStubCode(MacroAssembler& masm) masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, NumberMod), MoveOp::DOUBLE); MOZ_ASSERT(ReturnDoubleReg == FloatReg0); break; + // ??? default: MOZ_CRASH("Unexpected op"); } diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp index c100e443ed..1968597bf2 100644 --- a/js/src/jsreflect.cpp +++ b/js/src/jsreflect.cpp @@ -41,6 +41,7 @@ char const * const js::aopNames[] = { "*=", /* AOP_STAR */ "/=", /* AOP_DIV */ "%=", /* AOP_MOD */ + "**=", /* AOP_POW */ "<<=", /* AOP_LSH */ ">>=", /* AOP_RSH */ ">>>=", /* AOP_URSH */ @@ -66,6 +67,7 @@ char const * const js::binopNames[] = { "*", /* BINOP_STAR */ "/", /* BINOP_DIV */ "%", /* BINOP_MOD */ + "**", /* BINOP_POW */ "|", /* BINOP_BITOR */ "^", /* BINOP_BITXOR */ "&", /* BINOP_BITAND */ @@ -1833,6 +1835,8 @@ ASTSerializer::aop(JSOp op) return AOP_DIV; case JSOP_MOD: return AOP_MOD; + case JSOP_POW: + return AOP_POW; case JSOP_LSH: return AOP_LSH; case JSOP_RSH: @@ -1911,6 +1915,8 @@ ASTSerializer::binop(ParseNodeKind kind, JSOp op) return BINOP_DIV; case PNK_MOD: return BINOP_MOD; + case PNK_POW: + return BINOP_POW; case PNK_BITOR: return BINOP_BITOR; case PNK_BITXOR: @@ -2820,6 +2826,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_MULASSIGN: case PNK_DIVASSIGN: case PNK_MODASSIGN: + case PNK_POWASSIGN: { MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos)); MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos)); @@ -2856,6 +2863,9 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_INSTANCEOF: return leftAssociate(pn, dst); + case PNK_POW: + return rightAssociate(pn, dst); + case PNK_DELETE: case PNK_TYPEOF: case PNK_VOID: diff --git a/js/src/jsreflect.h b/js/src/jsreflect.h index a592bfe0cb..e87001da18 100644 --- a/js/src/jsreflect.h +++ b/js/src/jsreflect.h @@ -26,7 +26,7 @@ enum AssignmentOperator { /* assign */ AOP_ASSIGN = 0, /* operator-assign */ - AOP_PLUS, AOP_MINUS, AOP_STAR, AOP_DIV, AOP_MOD, + AOP_PLUS, AOP_MINUS, AOP_STAR, AOP_DIV, AOP_MOD, AOP_POW, /* shift-assign */ AOP_LSH, AOP_RSH, AOP_URSH, /* binary */ @@ -45,7 +45,7 @@ enum BinaryOperator { /* shift */ BINOP_LSH, BINOP_RSH, BINOP_URSH, /* arithmetic */ - BINOP_ADD, BINOP_SUB, BINOP_STAR, BINOP_DIV, BINOP_MOD, + BINOP_ADD, BINOP_SUB, BINOP_STAR, BINOP_DIV, BINOP_MOD, BINOP_POW, /* binary */ BINOP_BITOR, BINOP_BITXOR, BINOP_BITAND, /* misc */ diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 35a3d171d4..bf52d3db7a 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1665,7 +1665,6 @@ CASE(JSOP_UNUSED146) CASE(JSOP_UNUSED147) CASE(JSOP_UNUSED148) CASE(JSOP_BACKPATCH) -CASE(JSOP_UNUSED150) CASE(JSOP_UNUSED157) CASE(JSOP_UNUSED158) CASE(JSOP_UNUSED159) @@ -2276,6 +2275,18 @@ CASE(JSOP_MOD) } END_CASE(JSOP_MOD) +CASE(JSOP_POW) +{ + RootedValue& lval = rootValue0, &rval = rootValue1; + lval = REGS.sp[-2]; + rval = REGS.sp[-1]; + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!math_pow_handle(cx, lval, rval, res)) + goto error; + REGS.sp--; +} +END_CASE(JSOP_POW) + CASE(JSOP_NOT) { bool cond = ToBoolean(REGS.stackHandleAt(-1)); diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index f5a95a337b..528f4e7794 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1421,7 +1421,15 @@ * Stack: => */ \ macro(JSOP_BACKPATCH, 149,"backpatch", NULL, 5, 0, 0, JOF_JUMP) \ - macro(JSOP_UNUSED150, 150,"unused150", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Pops the top two values 'lval' and 'rval' from the stack, then pushes + * the result of 'Math.pow(lval, rval)'. + * Category: Operators + * Type: Arithmetic Operators + * Operands: + * Stack: lval, rval => (lval ** rval) + */ \ + macro(JSOP_POW, 150, "pow", "**", 1, 2, 1, JOF_BYTE|JOF_ARITH) \ \ /* * Pops the top of stack value as 'v', sets pending exception as 'v', diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index d0dd54f40c..13c3431b28 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,7 +29,7 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 249; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 250; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND);