michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "frontend/FoldConstants.h" michael@0: michael@0: #include "mozilla/FloatingPoint.h" michael@0: #include "mozilla/TypedEnum.h" michael@0: michael@0: #include "jslibmath.h" michael@0: michael@0: #include "frontend/ParseNode.h" michael@0: #include "frontend/Parser.h" michael@0: #include "vm/NumericConversions.h" michael@0: michael@0: #include "jscntxtinlines.h" michael@0: #include "jsinferinlines.h" michael@0: #include "jsobjinlines.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::frontend; michael@0: michael@0: using mozilla::IsNaN; michael@0: using mozilla::IsNegative; michael@0: using mozilla::NegativeInfinity; michael@0: using mozilla::PositiveInfinity; michael@0: using JS::GenericNaN; michael@0: michael@0: static bool michael@0: ContainsVarOrConst(ExclusiveContext *cx, ParseNode *pn, ParseNode **resultp) michael@0: { michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: if (!pn) { michael@0: *resultp = nullptr; michael@0: return true; michael@0: } michael@0: if (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST)) { michael@0: *resultp = pn; michael@0: return true; michael@0: } michael@0: switch (pn->getArity()) { michael@0: case PN_LIST: michael@0: for (ParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { michael@0: if (!ContainsVarOrConst(cx, pn2, resultp)) michael@0: return false; michael@0: if (*resultp) michael@0: return true; michael@0: } michael@0: break; michael@0: michael@0: case PN_TERNARY: michael@0: if (!ContainsVarOrConst(cx, pn->pn_kid1, resultp)) michael@0: return false; michael@0: if (*resultp) michael@0: return true; michael@0: if (!ContainsVarOrConst(cx, pn->pn_kid2, resultp)) michael@0: return false; michael@0: if (*resultp) michael@0: return true; michael@0: return ContainsVarOrConst(cx, pn->pn_kid3, resultp); michael@0: michael@0: case PN_BINARY: michael@0: case PN_BINARY_OBJ: michael@0: // Limit recursion if pn is a binary expression, which can't contain a michael@0: // var statement. michael@0: if (!pn->isOp(JSOP_NOP)) { michael@0: *resultp = nullptr; michael@0: return true; michael@0: } michael@0: if (!ContainsVarOrConst(cx, pn->pn_left, resultp)) michael@0: return false; michael@0: if (*resultp) michael@0: return true; michael@0: return ContainsVarOrConst(cx, pn->pn_right, resultp); michael@0: michael@0: case PN_UNARY: michael@0: if (!pn->isOp(JSOP_NOP)) { michael@0: *resultp = nullptr; michael@0: return true; michael@0: } michael@0: return ContainsVarOrConst(cx, pn->pn_kid, resultp); michael@0: michael@0: case PN_NAME: michael@0: return ContainsVarOrConst(cx, pn->maybeExpr(), resultp); michael@0: michael@0: default:; michael@0: } michael@0: *resultp = nullptr; michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Fold from one constant type to another. michael@0: * XXX handles only strings and numbers for now michael@0: */ michael@0: static bool michael@0: FoldType(ExclusiveContext *cx, ParseNode *pn, ParseNodeKind kind) michael@0: { michael@0: if (!pn->isKind(kind)) { michael@0: switch (kind) { michael@0: case PNK_NUMBER: michael@0: if (pn->isKind(PNK_STRING)) { michael@0: double d; michael@0: if (!StringToNumber(cx, pn->pn_atom, &d)) michael@0: return false; michael@0: pn->pn_dval = d; michael@0: pn->setKind(PNK_NUMBER); michael@0: pn->setOp(JSOP_DOUBLE); michael@0: } michael@0: break; michael@0: michael@0: case PNK_STRING: michael@0: if (pn->isKind(PNK_NUMBER)) { michael@0: pn->pn_atom = NumberToAtom(cx, pn->pn_dval); michael@0: if (!pn->pn_atom) michael@0: return false; michael@0: pn->setKind(PNK_STRING); michael@0: pn->setOp(JSOP_STRING); michael@0: } michael@0: break; michael@0: michael@0: default:; michael@0: } michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: /* michael@0: * Fold two numeric constants. Beware that pn1 and pn2 are recycled, unless michael@0: * one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after michael@0: * a successful call to this function. michael@0: */ michael@0: static bool michael@0: FoldBinaryNumeric(ExclusiveContext *cx, JSOp op, ParseNode *pn1, ParseNode *pn2, michael@0: ParseNode *pn) michael@0: { michael@0: double d, d2; michael@0: int32_t i, j; michael@0: michael@0: JS_ASSERT(pn1->isKind(PNK_NUMBER) && pn2->isKind(PNK_NUMBER)); michael@0: d = pn1->pn_dval; michael@0: d2 = pn2->pn_dval; michael@0: switch (op) { michael@0: case JSOP_LSH: michael@0: case JSOP_RSH: michael@0: i = ToInt32(d); michael@0: j = ToInt32(d2); michael@0: j &= 31; michael@0: d = int32_t((op == JSOP_LSH) ? uint32_t(i) << j : i >> j); michael@0: break; michael@0: michael@0: case JSOP_URSH: michael@0: j = ToInt32(d2); michael@0: j &= 31; michael@0: d = ToUint32(d) >> j; michael@0: break; michael@0: michael@0: case JSOP_ADD: michael@0: d += d2; michael@0: break; michael@0: michael@0: case JSOP_SUB: michael@0: d -= d2; michael@0: break; michael@0: michael@0: case JSOP_MUL: michael@0: d *= d2; michael@0: break; michael@0: michael@0: case JSOP_DIV: michael@0: if (d2 == 0) { michael@0: #if defined(XP_WIN) michael@0: /* XXX MSVC miscompiles such that (NaN == 0) */ michael@0: if (IsNaN(d2)) michael@0: d = GenericNaN(); michael@0: else michael@0: #endif michael@0: if (d == 0 || IsNaN(d)) michael@0: d = GenericNaN(); michael@0: else if (IsNegative(d) != IsNegative(d2)) michael@0: d = NegativeInfinity(); michael@0: else michael@0: d = PositiveInfinity(); michael@0: } else { michael@0: d /= d2; michael@0: } michael@0: break; michael@0: michael@0: case JSOP_MOD: michael@0: if (d2 == 0) { michael@0: d = GenericNaN(); michael@0: } else { michael@0: d = js_fmod(d, d2); michael@0: } michael@0: break; michael@0: michael@0: default:; michael@0: } michael@0: michael@0: /* Take care to allow pn1 or pn2 to alias pn. */ michael@0: pn->setKind(PNK_NUMBER); michael@0: pn->setOp(JSOP_DOUBLE); michael@0: pn->setArity(PN_NULLARY); michael@0: pn->pn_dval = d; michael@0: return true; michael@0: } michael@0: michael@0: // Remove a ParseNode, **pnp, from a parse tree, putting another ParseNode, michael@0: // *pn, in its place. michael@0: // michael@0: // pnp points to a ParseNode pointer. This must be the only pointer that points michael@0: // to the parse node being replaced. The replacement, *pn, is unchanged except michael@0: // for its pn_next pointer; updating that is necessary if *pn's new parent is a michael@0: // list node. michael@0: static void michael@0: ReplaceNode(ParseNode **pnp, ParseNode *pn) michael@0: { michael@0: pn->pn_next = (*pnp)->pn_next; michael@0: *pnp = pn; michael@0: } michael@0: michael@0: enum Truthiness { Truthy, Falsy, Unknown }; michael@0: michael@0: static Truthiness michael@0: Boolish(ParseNode *pn) michael@0: { michael@0: switch (pn->getKind()) { michael@0: case PNK_NUMBER: michael@0: return (pn->pn_dval != 0 && !IsNaN(pn->pn_dval)) ? Truthy : Falsy; michael@0: michael@0: case PNK_STRING: michael@0: return (pn->pn_atom->length() > 0) ? Truthy : Falsy; michael@0: michael@0: case PNK_TRUE: michael@0: case PNK_FUNCTION: michael@0: case PNK_GENEXP: michael@0: return Truthy; michael@0: michael@0: case PNK_FALSE: michael@0: case PNK_NULL: michael@0: return Falsy; michael@0: michael@0: default: michael@0: return Unknown; michael@0: } michael@0: } michael@0: michael@0: // Expressions that appear in a few specific places are treated specially michael@0: // during constant folding. This enum tells where a parse node appears. michael@0: MOZ_BEGIN_ENUM_CLASS(SyntacticContext, int) michael@0: // pn is an expression, and it appears in a context where only its side michael@0: // effects and truthiness matter: the condition of an if statement, michael@0: // conditional expression, while loop, or for(;;) loop; or an operand of && michael@0: // or || in such a context. michael@0: Condition, michael@0: michael@0: // pn is the operand of the 'delete' keyword. michael@0: Delete, michael@0: michael@0: // Any other syntactic context. michael@0: Other michael@0: MOZ_END_ENUM_CLASS(SyntacticContext) michael@0: michael@0: static SyntacticContext michael@0: condIf(const ParseNode *pn, ParseNodeKind kind) michael@0: { michael@0: return pn->isKind(kind) ? SyntacticContext::Condition : SyntacticContext::Other; michael@0: } michael@0: michael@0: static bool michael@0: Fold(ExclusiveContext *cx, ParseNode **pnp, michael@0: FullParseHandler &handler, const ReadOnlyCompileOptions &options, michael@0: bool inGenexpLambda, SyntacticContext sc) michael@0: { michael@0: ParseNode *pn = *pnp; michael@0: ParseNode *pn1 = nullptr, *pn2 = nullptr, *pn3 = nullptr; michael@0: michael@0: JS_CHECK_RECURSION(cx, return false); michael@0: michael@0: // First, recursively fold constants on the children of this node. michael@0: switch (pn->getArity()) { michael@0: case PN_CODE: michael@0: if (pn->isKind(PNK_FUNCTION) && michael@0: pn->pn_funbox->useAsmOrInsideUseAsm() && options.asmJSOption) michael@0: { michael@0: return true; michael@0: } else { michael@0: // Note: pn_body is nullptr for functions which are being lazily parsed. michael@0: JS_ASSERT(pn->getKind() == PNK_FUNCTION); michael@0: if (pn->pn_body) { michael@0: if (!Fold(cx, &pn->pn_body, handler, options, pn->pn_funbox->inGenexpLambda, michael@0: SyntacticContext::Other)) michael@0: return false; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case PN_LIST: michael@0: { michael@0: // Propagate Condition context through logical connectives. michael@0: SyntacticContext kidsc = SyntacticContext::Other; michael@0: if (pn->isKind(PNK_OR) || pn->isKind(PNK_AND)) michael@0: kidsc = sc; michael@0: michael@0: // Don't fold a parenthesized call expression. See bug 537673. michael@0: ParseNode **listp = &pn->pn_head; michael@0: if ((pn->isKind(PNK_CALL) || pn->isKind(PNK_NEW)) && (*listp)->isInParens()) michael@0: listp = &(*listp)->pn_next; michael@0: michael@0: for (; *listp; listp = &(*listp)->pn_next) { michael@0: if (!Fold(cx, listp, handler, options, inGenexpLambda, kidsc)) michael@0: return false; michael@0: } michael@0: michael@0: // If the last node in the list was replaced, pn_tail points into the wrong node. michael@0: pn->pn_tail = listp; michael@0: michael@0: // Save the list head in pn1 for later use. michael@0: pn1 = pn->pn_head; michael@0: pn2 = nullptr; michael@0: break; michael@0: } michael@0: michael@0: case PN_TERNARY: michael@0: /* Any kid may be null (e.g. for (;;)). */ michael@0: if (pn->pn_kid1) { michael@0: if (!Fold(cx, &pn->pn_kid1, handler, options, inGenexpLambda, condIf(pn, PNK_IF))) michael@0: return false; michael@0: } michael@0: pn1 = pn->pn_kid1; michael@0: michael@0: if (pn->pn_kid2) { michael@0: if (!Fold(cx, &pn->pn_kid2, handler, options, inGenexpLambda, condIf(pn, PNK_FORHEAD))) michael@0: return false; michael@0: if (pn->isKind(PNK_FORHEAD) && pn->pn_kid2->isKind(PNK_TRUE)) { michael@0: handler.freeTree(pn->pn_kid2); michael@0: pn->pn_kid2 = nullptr; michael@0: } michael@0: } michael@0: pn2 = pn->pn_kid2; michael@0: michael@0: if (pn->pn_kid3) { michael@0: if (!Fold(cx, &pn->pn_kid3, handler, options, inGenexpLambda, SyntacticContext::Other)) michael@0: return false; michael@0: } michael@0: pn3 = pn->pn_kid3; michael@0: break; michael@0: michael@0: case PN_BINARY: michael@0: case PN_BINARY_OBJ: michael@0: if (pn->isKind(PNK_OR) || pn->isKind(PNK_AND)) { michael@0: // Propagate Condition context through logical connectives. michael@0: SyntacticContext kidsc = SyntacticContext::Other; michael@0: if (sc == SyntacticContext::Condition) michael@0: kidsc = sc; michael@0: if (!Fold(cx, &pn->pn_left, handler, options, inGenexpLambda, kidsc)) michael@0: return false; michael@0: if (!Fold(cx, &pn->pn_right, handler, options, inGenexpLambda, kidsc)) michael@0: return false; michael@0: } else { michael@0: /* First kid may be null (for default case in switch). */ michael@0: if (pn->pn_left) { michael@0: if (!Fold(cx, &pn->pn_left, handler, options, inGenexpLambda, condIf(pn, PNK_WHILE))) michael@0: return false; michael@0: } michael@0: if (!Fold(cx, &pn->pn_right, handler, options, inGenexpLambda, condIf(pn, PNK_DOWHILE))) michael@0: return false; michael@0: } michael@0: pn1 = pn->pn_left; michael@0: pn2 = pn->pn_right; michael@0: break; michael@0: michael@0: case PN_UNARY: michael@0: /* michael@0: * Kludge to deal with typeof expressions: because constant folding michael@0: * can turn an expression into a name node, we have to check here, michael@0: * before folding, to see if we should throw undefined name errors. michael@0: * michael@0: * NB: We know that if pn->pn_op is JSOP_TYPEOF, pn1 will not be michael@0: * null. This assumption does not hold true for other unary michael@0: * expressions. michael@0: */ michael@0: if (pn->isKind(PNK_TYPEOF) && !pn->pn_kid->isKind(PNK_NAME)) michael@0: pn->setOp(JSOP_TYPEOFEXPR); michael@0: michael@0: if (pn->pn_kid) { michael@0: SyntacticContext kidsc = michael@0: pn->isKind(PNK_NOT) michael@0: ? SyntacticContext::Condition michael@0: : pn->isKind(PNK_DELETE) michael@0: ? SyntacticContext::Delete michael@0: : SyntacticContext::Other; michael@0: if (!Fold(cx, &pn->pn_kid, handler, options, inGenexpLambda, kidsc)) michael@0: return false; michael@0: } michael@0: pn1 = pn->pn_kid; michael@0: break; michael@0: michael@0: case PN_NAME: michael@0: /* michael@0: * Skip pn1 down along a chain of dotted member expressions to avoid michael@0: * excessive recursion. Our only goal here is to fold constants (if michael@0: * any) in the primary expression operand to the left of the first michael@0: * dot in the chain. michael@0: */ michael@0: if (!pn->isUsed()) { michael@0: ParseNode **lhsp = &pn->pn_expr; michael@0: while (*lhsp && (*lhsp)->isArity(PN_NAME) && !(*lhsp)->isUsed()) michael@0: lhsp = &(*lhsp)->pn_expr; michael@0: if (*lhsp && !Fold(cx, lhsp, handler, options, inGenexpLambda, SyntacticContext::Other)) michael@0: return false; michael@0: pn1 = *lhsp; michael@0: } michael@0: break; michael@0: michael@0: case PN_NULLARY: michael@0: break; michael@0: } michael@0: michael@0: // The immediate child of a PNK_DELETE node should not be replaced michael@0: // with node indicating a different syntactic form; |delete x| is not michael@0: // the same as |delete (true && x)|. See bug 888002. michael@0: // michael@0: // pn is the immediate child in question. Its descendents were already michael@0: // constant-folded above, so we're done. michael@0: if (sc == SyntacticContext::Delete) michael@0: return true; michael@0: michael@0: switch (pn->getKind()) { michael@0: case PNK_IF: michael@0: { michael@0: ParseNode *decl; michael@0: if (!ContainsVarOrConst(cx, pn2, &decl)) michael@0: return false; michael@0: if (decl) michael@0: break; michael@0: if (!ContainsVarOrConst(cx, pn3, &decl)) michael@0: return false; michael@0: if (decl) michael@0: break; michael@0: } michael@0: /* FALL THROUGH */ michael@0: michael@0: case PNK_CONDITIONAL: michael@0: /* Reduce 'if (C) T; else E' into T for true C, E for false. */ michael@0: switch (pn1->getKind()) { michael@0: case PNK_NUMBER: michael@0: if (pn1->pn_dval == 0 || IsNaN(pn1->pn_dval)) michael@0: pn2 = pn3; michael@0: break; michael@0: case PNK_STRING: michael@0: if (pn1->pn_atom->length() == 0) michael@0: pn2 = pn3; michael@0: break; michael@0: case PNK_TRUE: michael@0: break; michael@0: case PNK_FALSE: michael@0: case PNK_NULL: michael@0: pn2 = pn3; michael@0: break; michael@0: default: michael@0: /* Early return to dodge common code that copies pn2 to pn. */ michael@0: return true; michael@0: } michael@0: michael@0: #if JS_HAS_GENERATOR_EXPRS michael@0: /* Don't fold a trailing |if (0)| in a generator expression. */ michael@0: if (!pn2 && inGenexpLambda) michael@0: break; michael@0: #endif michael@0: michael@0: if (pn2 && !pn2->isDefn()) { michael@0: ReplaceNode(pnp, pn2); michael@0: pn = pn2; michael@0: } michael@0: if (!pn2 || (pn->isKind(PNK_SEMI) && !pn->pn_kid)) { michael@0: /* michael@0: * False condition and no else, or an empty then-statement was michael@0: * moved up over pn. Either way, make pn an empty block (not an michael@0: * empty statement, which does not decompile, even when labeled). michael@0: * NB: pn must be a PNK_IF as PNK_CONDITIONAL can never have a null michael@0: * kid or an empty statement for a child. michael@0: */ michael@0: pn->setKind(PNK_STATEMENTLIST); michael@0: pn->setArity(PN_LIST); michael@0: pn->makeEmpty(); michael@0: } michael@0: if (pn3 && pn3 != pn2) michael@0: handler.freeTree(pn3); michael@0: break; michael@0: michael@0: case PNK_OR: michael@0: case PNK_AND: michael@0: if (sc == SyntacticContext::Condition) { michael@0: if (pn->isArity(PN_LIST)) { michael@0: ParseNode **listp = &pn->pn_head; michael@0: JS_ASSERT(*listp == pn1); michael@0: uint32_t orig = pn->pn_count; michael@0: do { michael@0: Truthiness t = Boolish(pn1); michael@0: if (t == Unknown) { michael@0: listp = &pn1->pn_next; michael@0: continue; michael@0: } michael@0: if ((t == Truthy) == pn->isKind(PNK_OR)) { michael@0: for (pn2 = pn1->pn_next; pn2; pn2 = pn3) { michael@0: pn3 = pn2->pn_next; michael@0: handler.freeTree(pn2); michael@0: --pn->pn_count; michael@0: } michael@0: pn1->pn_next = nullptr; michael@0: break; michael@0: } michael@0: JS_ASSERT((t == Truthy) == pn->isKind(PNK_AND)); michael@0: if (pn->pn_count == 1) michael@0: break; michael@0: *listp = pn1->pn_next; michael@0: handler.freeTree(pn1); michael@0: --pn->pn_count; michael@0: } while ((pn1 = *listp) != nullptr); michael@0: michael@0: // We may have to change arity from LIST to BINARY. michael@0: pn1 = pn->pn_head; michael@0: if (pn->pn_count == 2) { michael@0: pn2 = pn1->pn_next; michael@0: pn1->pn_next = nullptr; michael@0: JS_ASSERT(!pn2->pn_next); michael@0: pn->setArity(PN_BINARY); michael@0: pn->pn_left = pn1; michael@0: pn->pn_right = pn2; michael@0: } else if (pn->pn_count == 1) { michael@0: ReplaceNode(pnp, pn1); michael@0: pn = pn1; michael@0: } else if (orig != pn->pn_count) { michael@0: // Adjust list tail. michael@0: pn2 = pn1->pn_next; michael@0: for (; pn1; pn2 = pn1, pn1 = pn1->pn_next) michael@0: ; michael@0: pn->pn_tail = &pn2->pn_next; michael@0: } michael@0: } else { michael@0: Truthiness t = Boolish(pn1); michael@0: if (t != Unknown) { michael@0: if ((t == Truthy) == pn->isKind(PNK_OR)) { michael@0: handler.freeTree(pn2); michael@0: ReplaceNode(pnp, pn1); michael@0: pn = pn1; michael@0: } else { michael@0: JS_ASSERT((t == Truthy) == pn->isKind(PNK_AND)); michael@0: handler.freeTree(pn1); michael@0: ReplaceNode(pnp, pn2); michael@0: pn = pn2; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case PNK_SUBASSIGN: michael@0: case PNK_BITORASSIGN: michael@0: case PNK_BITXORASSIGN: michael@0: case PNK_BITANDASSIGN: michael@0: case PNK_LSHASSIGN: michael@0: case PNK_RSHASSIGN: michael@0: case PNK_URSHASSIGN: michael@0: case PNK_MULASSIGN: michael@0: case PNK_DIVASSIGN: michael@0: case PNK_MODASSIGN: michael@0: /* michael@0: * Compound operators such as *= should be subject to folding, in case michael@0: * the left-hand side is constant, and so that the decompiler produces michael@0: * the same string that you get from decompiling a script or function michael@0: * compiled from that same string. += is special and so must be michael@0: * handled below. michael@0: */ michael@0: goto do_binary_op; michael@0: michael@0: case PNK_ADDASSIGN: michael@0: JS_ASSERT(pn->isOp(JSOP_ADD)); michael@0: /* FALL THROUGH */ michael@0: case PNK_ADD: michael@0: if (pn->isArity(PN_LIST)) { michael@0: bool folded = false; michael@0: michael@0: pn2 = pn1->pn_next; michael@0: if (pn1->isKind(PNK_NUMBER)) { michael@0: // Fold addition of numeric literals: (1 + 2 + x === 3 + x). michael@0: // Note that we can only do this the front of the list: michael@0: // (x + 1 + 2 !== x + 3) when x is a string. michael@0: while (pn2 && pn2->isKind(PNK_NUMBER)) { michael@0: pn1->pn_dval += pn2->pn_dval; michael@0: pn1->pn_next = pn2->pn_next; michael@0: handler.freeTree(pn2); michael@0: pn2 = pn1->pn_next; michael@0: pn->pn_count--; michael@0: folded = true; michael@0: } michael@0: } michael@0: michael@0: // Now search for adjacent pairs of literals to fold for string michael@0: // concatenation. michael@0: // michael@0: // isStringConcat is true if we know the operation we're looking at michael@0: // will be string concatenation at runtime. As soon as we see a michael@0: // string, we know that every addition to the right of it will be michael@0: // string concatenation, even if both operands are numbers: michael@0: // ("s" + x + 1 + 2 === "s" + x + "12"). michael@0: // michael@0: bool isStringConcat = false; michael@0: RootedString foldedStr(cx); michael@0: michael@0: // (number + string) is definitely concatenation, but only at the michael@0: // front of the list: (x + 1 + "2" !== x + "12") when x is a michael@0: // number. michael@0: if (pn1->isKind(PNK_NUMBER) && pn2 && pn2->isKind(PNK_STRING)) michael@0: isStringConcat = true; michael@0: michael@0: while (pn2) { michael@0: isStringConcat = isStringConcat || pn1->isKind(PNK_STRING); michael@0: michael@0: if (isStringConcat && michael@0: (pn1->isKind(PNK_STRING) || pn1->isKind(PNK_NUMBER)) && michael@0: (pn2->isKind(PNK_STRING) || pn2->isKind(PNK_NUMBER))) michael@0: { michael@0: // Fold string concatenation of literals. michael@0: if (pn1->isKind(PNK_NUMBER) && !FoldType(cx, pn1, PNK_STRING)) michael@0: return false; michael@0: if (pn2->isKind(PNK_NUMBER) && !FoldType(cx, pn2, PNK_STRING)) michael@0: return false; michael@0: if (!foldedStr) michael@0: foldedStr = pn1->pn_atom; michael@0: RootedString right(cx, pn2->pn_atom); michael@0: foldedStr = ConcatStrings(cx, foldedStr, right); michael@0: if (!foldedStr) michael@0: return false; michael@0: pn1->pn_next = pn2->pn_next; michael@0: handler.freeTree(pn2); michael@0: pn2 = pn1->pn_next; michael@0: pn->pn_count--; michael@0: folded = true; michael@0: } else { michael@0: if (foldedStr) { michael@0: // Convert the rope of folded strings into an Atom. michael@0: pn1->pn_atom = AtomizeString(cx, foldedStr); michael@0: if (!pn1->pn_atom) michael@0: return false; michael@0: foldedStr = nullptr; michael@0: } michael@0: pn1 = pn2; michael@0: pn2 = pn2->pn_next; michael@0: } michael@0: } michael@0: michael@0: if (foldedStr) { michael@0: // Convert the rope of folded strings into an Atom. michael@0: pn1->pn_atom = AtomizeString(cx, foldedStr); michael@0: if (!pn1->pn_atom) michael@0: return false; michael@0: } michael@0: michael@0: if (folded) { michael@0: if (pn->pn_count == 1) { michael@0: // We reduced the list to one constant. There is no michael@0: // addition anymore. Replace the PNK_ADD node with the michael@0: // single PNK_STRING or PNK_NUMBER node. michael@0: ReplaceNode(pnp, pn1); michael@0: pn = pn1; michael@0: } else if (!pn2) { michael@0: pn->pn_tail = &pn1->pn_next; michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: michael@0: /* Handle a binary string concatenation. */ michael@0: JS_ASSERT(pn->isArity(PN_BINARY)); michael@0: if (pn1->isKind(PNK_STRING) || pn2->isKind(PNK_STRING)) { michael@0: if (!FoldType(cx, !pn1->isKind(PNK_STRING) ? pn1 : pn2, PNK_STRING)) michael@0: return false; michael@0: if (!pn1->isKind(PNK_STRING) || !pn2->isKind(PNK_STRING)) michael@0: return true; michael@0: RootedString left(cx, pn1->pn_atom); michael@0: RootedString right(cx, pn2->pn_atom); michael@0: RootedString str(cx, ConcatStrings(cx, left, right)); michael@0: if (!str) michael@0: return false; michael@0: pn->pn_atom = AtomizeString(cx, str); michael@0: if (!pn->pn_atom) michael@0: return false; michael@0: pn->setKind(PNK_STRING); michael@0: pn->setOp(JSOP_STRING); michael@0: pn->setArity(PN_NULLARY); michael@0: handler.freeTree(pn1); michael@0: handler.freeTree(pn2); michael@0: break; michael@0: } michael@0: michael@0: /* Can't concatenate string literals, let's try numbers. */ michael@0: goto do_binary_op; michael@0: michael@0: case PNK_SUB: michael@0: case PNK_STAR: michael@0: case PNK_LSH: michael@0: case PNK_RSH: michael@0: case PNK_URSH: michael@0: case PNK_DIV: michael@0: case PNK_MOD: michael@0: do_binary_op: michael@0: if (pn->isArity(PN_LIST)) { michael@0: JS_ASSERT(pn->pn_count > 2); michael@0: for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { michael@0: if (!FoldType(cx, pn2, PNK_NUMBER)) michael@0: return false; michael@0: } michael@0: for (pn2 = pn1; pn2; pn2 = pn2->pn_next) { michael@0: /* XXX fold only if all operands convert to number */ michael@0: if (!pn2->isKind(PNK_NUMBER)) michael@0: break; michael@0: } michael@0: if (!pn2) { michael@0: JSOp op = pn->getOp(); michael@0: michael@0: pn2 = pn1->pn_next; michael@0: pn3 = pn2->pn_next; michael@0: if (!FoldBinaryNumeric(cx, op, pn1, pn2, pn)) michael@0: return false; michael@0: while ((pn2 = pn3) != nullptr) { michael@0: pn3 = pn2->pn_next; michael@0: if (!FoldBinaryNumeric(cx, op, pn, pn2, pn)) michael@0: return false; michael@0: } michael@0: } michael@0: } else { michael@0: JS_ASSERT(pn->isArity(PN_BINARY)); michael@0: if (!FoldType(cx, pn1, PNK_NUMBER) || michael@0: !FoldType(cx, pn2, PNK_NUMBER)) { michael@0: return false; michael@0: } michael@0: if (pn1->isKind(PNK_NUMBER) && pn2->isKind(PNK_NUMBER)) { michael@0: if (!FoldBinaryNumeric(cx, pn->getOp(), pn1, pn2, pn)) michael@0: return false; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case PNK_TYPEOF: michael@0: case PNK_VOID: michael@0: case PNK_NOT: michael@0: case PNK_BITNOT: michael@0: case PNK_POS: michael@0: case PNK_NEG: michael@0: if (pn1->isKind(PNK_NUMBER)) { michael@0: double d; michael@0: michael@0: /* Operate on one numeric constant. */ michael@0: d = pn1->pn_dval; michael@0: switch (pn->getKind()) { michael@0: case PNK_BITNOT: michael@0: d = ~ToInt32(d); michael@0: break; michael@0: michael@0: case PNK_NEG: michael@0: d = -d; michael@0: break; michael@0: michael@0: case PNK_POS: michael@0: break; michael@0: michael@0: case PNK_NOT: michael@0: if (d == 0 || IsNaN(d)) { michael@0: pn->setKind(PNK_TRUE); michael@0: pn->setOp(JSOP_TRUE); michael@0: } else { michael@0: pn->setKind(PNK_FALSE); michael@0: pn->setOp(JSOP_FALSE); michael@0: } michael@0: pn->setArity(PN_NULLARY); michael@0: /* FALL THROUGH */ michael@0: michael@0: default: michael@0: /* Return early to dodge the common PNK_NUMBER code. */ michael@0: return true; michael@0: } michael@0: pn->setKind(PNK_NUMBER); michael@0: pn->setOp(JSOP_DOUBLE); michael@0: pn->setArity(PN_NULLARY); michael@0: pn->pn_dval = d; michael@0: handler.freeTree(pn1); michael@0: } else if (pn1->isKind(PNK_TRUE) || pn1->isKind(PNK_FALSE)) { michael@0: if (pn->isKind(PNK_NOT)) { michael@0: ReplaceNode(pnp, pn1); michael@0: pn = pn1; michael@0: if (pn->isKind(PNK_TRUE)) { michael@0: pn->setKind(PNK_FALSE); michael@0: pn->setOp(JSOP_FALSE); michael@0: } else { michael@0: pn->setKind(PNK_TRUE); michael@0: pn->setOp(JSOP_TRUE); michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case PNK_ELEM: { michael@0: // An indexed expression, pn1[pn2]. A few cases can be improved. michael@0: PropertyName *name = nullptr; michael@0: if (pn2->isKind(PNK_STRING)) { michael@0: JSAtom *atom = pn2->pn_atom; michael@0: uint32_t index; michael@0: michael@0: if (atom->isIndex(&index)) { michael@0: // Optimization 1: We have something like pn1["100"]. This is michael@0: // equivalent to pn1[100] which is faster. michael@0: pn2->setKind(PNK_NUMBER); michael@0: pn2->setOp(JSOP_DOUBLE); michael@0: pn2->pn_dval = index; michael@0: } else { michael@0: name = atom->asPropertyName(); michael@0: } michael@0: } else if (pn2->isKind(PNK_NUMBER)) { michael@0: double number = pn2->pn_dval; michael@0: if (number != ToUint32(number)) { michael@0: // Optimization 2: We have something like pn1[3.14]. The number michael@0: // is not an array index. This is equivalent to pn1["3.14"] michael@0: // which enables optimization 3 below. michael@0: JSAtom *atom = ToAtom(cx, DoubleValue(number)); michael@0: if (!atom) michael@0: return false; michael@0: name = atom->asPropertyName(); michael@0: } michael@0: } michael@0: michael@0: if (name && NameToId(name) == types::IdToTypeId(NameToId(name))) { michael@0: // Optimization 3: We have pn1["foo"] where foo is not an index. michael@0: // Convert to a property access (like pn1.foo) which we optimize michael@0: // better downstream. Don't bother with this for names which TI michael@0: // considers to be indexes, to simplify downstream analysis. michael@0: ParseNode *expr = handler.newPropertyAccess(pn->pn_left, name, pn->pn_pos.end); michael@0: if (!expr) michael@0: return false; michael@0: ReplaceNode(pnp, expr); michael@0: michael@0: pn->pn_left = nullptr; michael@0: pn->pn_right = nullptr; michael@0: handler.freeTree(pn); michael@0: pn = expr; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: default:; michael@0: } michael@0: michael@0: if (sc == SyntacticContext::Condition) { michael@0: Truthiness t = Boolish(pn); michael@0: if (t != Unknown) { michael@0: /* michael@0: * We can turn function nodes into constant nodes here, but mutating function michael@0: * nodes is tricky --- in particular, mutating a function node that appears on michael@0: * a method list corrupts the method list. However, methods are M's in michael@0: * statements of the form 'this.foo = M;', which we never fold, so we're okay. michael@0: */ michael@0: handler.prepareNodeForMutation(pn); michael@0: if (t == Truthy) { michael@0: pn->setKind(PNK_TRUE); michael@0: pn->setOp(JSOP_TRUE); michael@0: } else { michael@0: pn->setKind(PNK_FALSE); michael@0: pn->setOp(JSOP_FALSE); michael@0: } michael@0: pn->setArity(PN_NULLARY); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: frontend::FoldConstants(ExclusiveContext *cx, ParseNode **pnp, Parser *parser) michael@0: { michael@0: // Don't fold constants if the code has requested "use asm" as michael@0: // constant-folding will misrepresent the source text for the purpose michael@0: // of type checking. (Also guard against entering a function containing michael@0: // "use asm", see PN_FUNC case below.) michael@0: if (parser->pc->useAsmOrInsideUseAsm() && parser->options().asmJSOption) michael@0: return true; michael@0: michael@0: return Fold(cx, pnp, parser->handler, parser->options(), false, SyntacticContext::Other); michael@0: }