Files
tubestation/js/src/frontend/FoldConstants.cpp
2015-06-03 12:42:45 +02:00

1147 lines
37 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "frontend/FoldConstants.h"
#include "mozilla/FloatingPoint.h"
#include "jslibmath.h"
#include "frontend/ParseNode.h"
#include "frontend/Parser.h"
#include "js/Conversions.h"
#include "jscntxtinlines.h"
#include "jsobjinlines.h"
using namespace js;
using namespace js::frontend;
using mozilla::IsNaN;
using mozilla::IsNegative;
using mozilla::NegativeInfinity;
using mozilla::PositiveInfinity;
using JS::GenericNaN;
using JS::ToInt32;
using JS::ToUint32;
static bool
ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result);
static bool
ListContainsHoistedDeclaration(ExclusiveContext* cx, ListNode* list, bool* result)
{
for (ParseNode* node = list->pn_head; node; node = node->pn_next) {
if (!ContainsHoistedDeclaration(cx, node, result))
return false;
if (*result)
return true;
}
*result = false;
return true;
}
// Determines whether the given ParseNode contains any declarations whose
// visibility will extend outside the node itself -- that is, whether the
// ParseNode contains any var statements.
//
// THIS IS NOT A GENERAL-PURPOSE FUNCTION. It is only written to work in the
// specific context of deciding that |node|, as one arm of a PNK_IF controlled
// by a constant condition, contains a declaration that forbids |node| being
// completely eliminated as dead.
static bool
ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result)
{
JS_CHECK_RECURSION(cx, return false);
// With a better-typed AST, we would have distinct parse node classes for
// expressions and for statements and would characterize expressions with
// ExpressionKind and statements with StatementKind. Perhaps someday. In
// the meantime we must characterize every ParseNodeKind, even the
// expression/sub-expression ones that, if we handle all statement kinds
// correctly, we'll never see.
switch (node->getKind()) {
// Base case.
case PNK_VAR:
*result = true;
return true;
// Non-global lexical declarations are block-scoped (ergo not hoistable).
// (Global lexical declarations, in addition to being irrelevant here as
// ContainsHoistedDeclaration is only used on the arms of an |if|
// statement, are handled by PNK_GLOBALCONST and PNK_VAR.)
case PNK_LET:
case PNK_CONST:
MOZ_ASSERT(node->isArity(PN_LIST));
*result = false;
return true;
// Similarly to the lexical declarations above, classes cannot add hoisted
// declarations
case PNK_CLASS:
MOZ_ASSERT(node->isArity(PN_TERNARY));
*result = false;
return true;
// ContainsHoistedDeclaration is only called on nested nodes, so any
// instance of this can't be function statements at body level. In
// SpiderMonkey, a binding induced by a function statement is added when
// the function statement is evaluated. Thus any declaration introduced
// by a function statement, as observed by this function, isn't a hoisted
// declaration.
case PNK_FUNCTION:
MOZ_ASSERT(node->isArity(PN_CODE));
*result = false;
return true;
// Statements with no sub-components at all.
case PNK_NOP: // induced by function f() {} function f() {}
case PNK_DEBUGGER:
MOZ_ASSERT(node->isArity(PN_NULLARY));
*result = false;
return true;
// Statements containing only an expression have no declarations.
case PNK_SEMI:
case PNK_THROW:
MOZ_ASSERT(node->isArity(PN_UNARY));
*result = false;
return true;
case PNK_RETURN:
// These two aren't statements in the spec, but we sometimes insert them
// in statement lists anyway.
case PNK_YIELD_STAR:
case PNK_YIELD:
MOZ_ASSERT(node->isArity(PN_BINARY));
*result = false;
return true;
// Other statements with no sub-statement components.
case PNK_BREAK:
case PNK_CONTINUE:
case PNK_IMPORT:
case PNK_IMPORT_SPEC_LIST:
case PNK_IMPORT_SPEC:
case PNK_EXPORT_FROM:
case PNK_EXPORT_DEFAULT:
case PNK_EXPORT_SPEC_LIST:
case PNK_EXPORT_SPEC:
case PNK_EXPORT:
case PNK_EXPORT_BATCH_SPEC:
*result = false;
return true;
// Statements possibly containing hoistable declarations only in the left
// half, in ParseNode terms -- the loop body in AST terms.
case PNK_DOWHILE:
return ContainsHoistedDeclaration(cx, node->pn_left, result);
// Statements possibly containing hoistable declarations only in the
// right half, in ParseNode terms -- the loop body or nested statement
// (usually a block statement), in AST terms.
case PNK_WHILE:
case PNK_WITH:
return ContainsHoistedDeclaration(cx, node->pn_right, result);
case PNK_LABEL:
return ContainsHoistedDeclaration(cx, node->pn_expr, result);
// Statements with more complicated structures.
// if-statement nodes may have hoisted declarations in their consequent
// and alternative components.
case PNK_IF: {
MOZ_ASSERT(node->isArity(PN_TERNARY));
ParseNode* consequent = node->pn_kid2;
if (!ContainsHoistedDeclaration(cx, consequent, result))
return false;
if (*result)
return true;
if (ParseNode* alternative = node->pn_kid3)
return ContainsHoistedDeclaration(cx, alternative, result);
*result = false;
return true;
}
// Legacy array and generator comprehensions use PNK_IF to represent
// conditions specified in the comprehension tail: for example,
// [x for (x in obj) if (x)]. The consequent of such PNK_IF nodes is
// either PNK_YIELD in a PNK_SEMI statement (generator comprehensions) or
// PNK_ARRAYPUSH (array comprehensions) . The first case is consistent
// with normal if-statement structure with consequent/alternative as
// statements. The second case is abnormal and requires that we not
// banish PNK_ARRAYPUSH to the unreachable list, handling it explicitly.
//
// We could require that this one weird PNK_ARRAYPUSH case be packaged in
// a PNK_SEMI, for consistency. That requires careful bytecode emitter
// adjustment that seems unwarranted for a deprecated feature.
case PNK_ARRAYPUSH:
*result = false;
return true;
// try-statements have statements to execute, and one or both of a
// catch-list and a finally-block.
case PNK_TRY: {
MOZ_ASSERT(node->isArity(PN_TERNARY));
MOZ_ASSERT(node->pn_kid2 || node->pn_kid3,
"must have either catch(es) or finally");
ParseNode* tryBlock = node->pn_kid1;
if (!ContainsHoistedDeclaration(cx, tryBlock, result))
return false;
if (*result)
return true;
if (ParseNode* catchList = node->pn_kid2) {
for (ParseNode* lexicalScope = catchList->pn_head;
lexicalScope;
lexicalScope = lexicalScope->pn_next)
{
MOZ_ASSERT(lexicalScope->isKind(PNK_LEXICALSCOPE));
ParseNode* catchNode = lexicalScope->pn_expr;
MOZ_ASSERT(catchNode->isKind(PNK_CATCH));
ParseNode* catchStatements = catchNode->pn_kid3;
if (!ContainsHoistedDeclaration(cx, catchStatements, result))
return false;
if (*result)
return true;
}
}
if (ParseNode* finallyBlock = node->pn_kid3)
return ContainsHoistedDeclaration(cx, finallyBlock, result);
*result = false;
return true;
}
// A switch node's left half is an expression; only its right half (a
// list of cases/defaults, or a block node) could contain hoisted
// declarations.
case PNK_SWITCH:
MOZ_ASSERT(node->isArity(PN_BINARY));
return ContainsHoistedDeclaration(cx, node->pn_right, result);
// A case/default node's right half is its statements. A default node's
// left half is null; a case node's left half is its expression.
case PNK_DEFAULT:
MOZ_ASSERT(!node->pn_left);
case PNK_CASE:
MOZ_ASSERT(node->isArity(PN_BINARY));
return ContainsHoistedDeclaration(cx, node->pn_right, result);
case PNK_FOR: {
MOZ_ASSERT(node->isArity(PN_BINARY));
ParseNode* loopHead = node->pn_left;
MOZ_ASSERT(loopHead->isKind(PNK_FORHEAD) ||
loopHead->isKind(PNK_FORIN) ||
loopHead->isKind(PNK_FOROF));
if (loopHead->isKind(PNK_FORHEAD)) {
// for (init?; cond?; update?), with only init possibly containing
// a hoisted declaration. (Note: a lexical-declaration |init| is
// (at present) hoisted in SpiderMonkey parlance -- but such
// hoisting doesn't extend outside of this statement, so it is not
// hoisting in the sense meant by ContainsHoistedDeclaration.)
MOZ_ASSERT(loopHead->isArity(PN_TERNARY));
ParseNode* init = loopHead->pn_kid1;
if (init && init->isKind(PNK_VAR)) {
*result = true;
return true;
}
} else {
MOZ_ASSERT(loopHead->isKind(PNK_FORIN) || loopHead->isKind(PNK_FOROF));
// for each? (target in ...), where only target may introduce
// hoisted declarations.
//
// -- or --
//
// for (target of ...), where only target may introduce hoisted
// declarations.
//
// Either way, if |target| contains a declaration, it's |loopHead|'s
// first kid.
MOZ_ASSERT(loopHead->isArity(PN_TERNARY));
ParseNode* decl = loopHead->pn_kid1;
if (decl && decl->isKind(PNK_VAR)) {
*result = true;
return true;
}
}
ParseNode* loopBody = node->pn_right;
return ContainsHoistedDeclaration(cx, loopBody, result);
}
case PNK_LETBLOCK: {
MOZ_ASSERT(node->isArity(PN_BINARY));
MOZ_ASSERT(node->pn_right->isKind(PNK_LEXICALSCOPE));
MOZ_ASSERT(node->pn_left->isKind(PNK_LET) ||
(node->pn_left->isKind(PNK_CONST) && node->pn_right->pn_expr->isKind(PNK_FOR)),
"a let-block's left half is its declarations: ordinarily a PNK_LET node but "
"PNK_CONST in the weird case of |for (const x ...)|");
return ContainsHoistedDeclaration(cx, node->pn_right, result);
}
case PNK_LEXICALSCOPE: {
MOZ_ASSERT(node->isArity(PN_NAME));
ParseNode* expr = node->pn_expr;
if (expr->isKind(PNK_FOR))
return ContainsHoistedDeclaration(cx, expr, result);
MOZ_ASSERT(expr->isKind(PNK_STATEMENTLIST));
return ListContainsHoistedDeclaration(cx, &node->pn_expr->as<ListNode>(), result);
}
// List nodes with all non-null children.
case PNK_STATEMENTLIST:
return ListContainsHoistedDeclaration(cx, &node->as<ListNode>(), result);
// Grammar sub-components that should never be reached directly by this
// method, because some parent component should have asserted itself.
case PNK_OBJECT_PROPERTY_NAME:
case PNK_COMPUTED_NAME:
case PNK_SPREAD:
case PNK_MUTATEPROTO:
case PNK_COLON:
case PNK_SHORTHAND:
case PNK_CONDITIONAL:
case PNK_TYPEOFNAME:
case PNK_TYPEOFEXPR:
case PNK_VOID:
case PNK_NOT:
case PNK_BITNOT:
case PNK_DELETENAME:
case PNK_DELETEPROP:
case PNK_DELETESUPERPROP:
case PNK_DELETEELEM:
case PNK_DELETESUPERELEM:
case PNK_DELETEEXPR:
case PNK_POS:
case PNK_NEG:
case PNK_PREINCREMENT:
case PNK_POSTINCREMENT:
case PNK_PREDECREMENT:
case PNK_POSTDECREMENT:
case PNK_OR:
case PNK_AND:
case PNK_BITOR:
case PNK_BITXOR:
case PNK_BITAND:
case PNK_STRICTEQ:
case PNK_EQ:
case PNK_STRICTNE:
case PNK_NE:
case PNK_LT:
case PNK_LE:
case PNK_GT:
case PNK_GE:
case PNK_INSTANCEOF:
case PNK_IN:
case PNK_LSH:
case PNK_RSH:
case PNK_URSH:
case PNK_ADD:
case PNK_SUB:
case PNK_STAR:
case PNK_DIV:
case PNK_MOD:
case PNK_ASSIGN:
case PNK_ADDASSIGN:
case PNK_SUBASSIGN:
case PNK_BITORASSIGN:
case PNK_BITXORASSIGN:
case PNK_BITANDASSIGN:
case PNK_LSHASSIGN:
case PNK_RSHASSIGN:
case PNK_URSHASSIGN:
case PNK_MULASSIGN:
case PNK_DIVASSIGN:
case PNK_MODASSIGN:
case PNK_COMMA:
case PNK_ARRAY:
case PNK_OBJECT:
case PNK_DOT:
case PNK_ELEM:
case PNK_CALL:
case PNK_NAME:
case PNK_TEMPLATE_STRING:
case PNK_TEMPLATE_STRING_LIST:
case PNK_TAGGED_TEMPLATE:
case PNK_CALLSITEOBJ:
case PNK_STRING:
case PNK_REGEXP:
case PNK_TRUE:
case PNK_FALSE:
case PNK_NULL:
case PNK_THIS:
case PNK_ELISION:
case PNK_NUMBER:
case PNK_NEW:
case PNK_GENERATOR:
case PNK_GENEXP:
case PNK_ARRAYCOMP:
case PNK_ARGSBODY:
case PNK_CATCHLIST:
case PNK_CATCH:
case PNK_FORIN:
case PNK_FOROF:
case PNK_FORHEAD:
case PNK_FRESHENBLOCK:
case PNK_CLASSMETHOD:
case PNK_CLASSMETHODLIST:
case PNK_CLASSNAMES:
case PNK_SUPERPROP:
case PNK_SUPERELEM:
MOZ_CRASH("ContainsHoistedDeclaration should have indicated false on "
"some parent node without recurring to test this node");
case PNK_GLOBALCONST:
MOZ_CRASH("ContainsHoistedDeclaration is only called on nested nodes where "
"globalconst nodes should never have been generated");
case PNK_LIMIT: // invalid sentinel value
MOZ_CRASH("unexpected PNK_LIMIT in node");
}
MOZ_CRASH("invalid node kind");
}
/*
* Fold from one constant type to another.
* XXX handles only strings and numbers for now
*/
static bool
FoldType(ExclusiveContext* cx, ParseNode* pn, ParseNodeKind kind)
{
if (!pn->isKind(kind)) {
switch (kind) {
case PNK_NUMBER:
if (pn->isKind(PNK_STRING)) {
double d;
if (!StringToNumber(cx, pn->pn_atom, &d))
return false;
pn->pn_dval = d;
pn->setKind(PNK_NUMBER);
pn->setOp(JSOP_DOUBLE);
}
break;
case PNK_STRING:
if (pn->isKind(PNK_NUMBER)) {
pn->pn_atom = NumberToAtom(cx, pn->pn_dval);
if (!pn->pn_atom)
return false;
pn->setKind(PNK_STRING);
pn->setOp(JSOP_STRING);
}
break;
default:;
}
}
return true;
}
/*
* Fold two numeric constants. Beware that pn1 and pn2 are recycled, unless
* one of them aliases pn, so you can't safely fetch pn2->pn_next, e.g., after
* a successful call to this function.
*/
static bool
FoldBinaryNumeric(ExclusiveContext* cx, JSOp op, ParseNode* pn1, ParseNode* pn2,
ParseNode* pn)
{
double d, d2;
int32_t i, j;
MOZ_ASSERT(pn1->isKind(PNK_NUMBER) && pn2->isKind(PNK_NUMBER));
d = pn1->pn_dval;
d2 = pn2->pn_dval;
switch (op) {
case JSOP_LSH:
case JSOP_RSH:
i = ToInt32(d);
j = ToInt32(d2);
j &= 31;
d = int32_t((op == JSOP_LSH) ? uint32_t(i) << j : i >> j);
break;
case JSOP_URSH:
j = ToInt32(d2);
j &= 31;
d = ToUint32(d) >> j;
break;
case JSOP_ADD:
d += d2;
break;
case JSOP_SUB:
d -= d2;
break;
case JSOP_MUL:
d *= d2;
break;
case JSOP_DIV:
if (d2 == 0) {
#if defined(XP_WIN)
/* XXX MSVC miscompiles such that (NaN == 0) */
if (IsNaN(d2))
d = GenericNaN();
else
#endif
if (d == 0 || IsNaN(d))
d = GenericNaN();
else if (IsNegative(d) != IsNegative(d2))
d = NegativeInfinity<double>();
else
d = PositiveInfinity<double>();
} else {
d /= d2;
}
break;
case JSOP_MOD:
if (d2 == 0) {
d = GenericNaN();
} else {
d = js_fmod(d, d2);
}
break;
default:;
}
/* Take care to allow pn1 or pn2 to alias pn. */
pn->setKind(PNK_NUMBER);
pn->setOp(JSOP_DOUBLE);
pn->setArity(PN_NULLARY);
pn->pn_dval = d;
return true;
}
// Remove a ParseNode, **pnp, from a parse tree, putting another ParseNode,
// *pn, in its place.
//
// pnp points to a ParseNode pointer. This must be the only pointer that points
// to the parse node being replaced. The replacement, *pn, is unchanged except
// for its pn_next pointer; updating that is necessary if *pn's new parent is a
// list node.
static void
ReplaceNode(ParseNode** pnp, ParseNode* pn)
{
pn->pn_next = (*pnp)->pn_next;
*pnp = pn;
}
enum Truthiness { Truthy, Falsy, Unknown };
static Truthiness
Boolish(ParseNode* pn)
{
switch (pn->getKind()) {
case PNK_NUMBER:
return (pn->pn_dval != 0 && !IsNaN(pn->pn_dval)) ? Truthy : Falsy;
case PNK_STRING:
return (pn->pn_atom->length() > 0) ? Truthy : Falsy;
case PNK_TRUE:
case PNK_FUNCTION:
case PNK_GENEXP:
return Truthy;
case PNK_FALSE:
case PNK_NULL:
return Falsy;
default:
return Unknown;
}
}
// Expressions that appear in a few specific places are treated specially
// during constant folding. This enum tells where a parse node appears.
enum class SyntacticContext : int {
// pn is an expression, and it appears in a context where only its side
// effects and truthiness matter: the condition of an if statement,
// conditional expression, while loop, or for(;;) loop; or an operand of &&
// or || in such a context.
Condition,
// pn is the operand of the 'delete' keyword.
Delete,
// Any other syntactic context.
Other
};
static SyntacticContext
condIf(const ParseNode* pn, ParseNodeKind kind)
{
return pn->isKind(kind) ? SyntacticContext::Condition : SyntacticContext::Other;
}
static bool
Fold(ExclusiveContext* cx, ParseNode** pnp,
FullParseHandler& handler, const ReadOnlyCompileOptions& options,
bool inGenexpLambda, SyntacticContext sc)
{
ParseNode* pn = *pnp;
ParseNode* pn1 = nullptr;
ParseNode* pn2 = nullptr;
ParseNode* pn3 = nullptr;
JS_CHECK_RECURSION(cx, return false);
// First, recursively fold constants on the children of this node.
switch (pn->getArity()) {
case PN_CODE:
if (pn->isKind(PNK_FUNCTION) && pn->pn_funbox->useAsmOrInsideUseAsm())
return true;
// Note: pn_body is nullptr for functions which are being lazily parsed.
MOZ_ASSERT(pn->getKind() == PNK_FUNCTION);
if (pn->pn_body) {
if (!Fold(cx, &pn->pn_body, handler, options, pn->pn_funbox->inGenexpLambda,
SyntacticContext::Other))
return false;
}
break;
case PN_LIST:
{
// Propagate Condition context through logical connectives.
SyntacticContext kidsc = SyntacticContext::Other;
if (pn->isKind(PNK_OR) || pn->isKind(PNK_AND))
kidsc = sc;
// Don't fold a parenthesized call expression. See bug 537673.
ParseNode** listp = &pn->pn_head;
if ((pn->isKind(PNK_CALL) || pn->isKind(PNK_NEW)) && (*listp)->isInParens())
listp = &(*listp)->pn_next;
for (; *listp; listp = &(*listp)->pn_next) {
if (!Fold(cx, listp, handler, options, inGenexpLambda, kidsc))
return false;
}
// If the last node in the list was replaced, pn_tail points into the wrong node.
pn->pn_tail = listp;
// Save the list head in pn1 for later use.
pn1 = pn->pn_head;
pn2 = nullptr;
break;
}
case PN_TERNARY:
/* Any kid may be null (e.g. for (;;)). */
if (pn->pn_kid1) {
if (!Fold(cx, &pn->pn_kid1, handler, options, inGenexpLambda, condIf(pn, PNK_IF)))
return false;
}
pn1 = pn->pn_kid1;
if (pn->pn_kid2) {
if (!Fold(cx, &pn->pn_kid2, handler, options, inGenexpLambda, condIf(pn, PNK_FORHEAD)))
return false;
if (pn->isKind(PNK_FORHEAD) && pn->pn_kid2->isKind(PNK_TRUE)) {
handler.freeTree(pn->pn_kid2);
pn->pn_kid2 = nullptr;
}
}
pn2 = pn->pn_kid2;
if (pn->pn_kid3) {
if (!Fold(cx, &pn->pn_kid3, handler, options, inGenexpLambda, SyntacticContext::Other))
return false;
}
pn3 = pn->pn_kid3;
break;
case PN_BINARY:
case PN_BINARY_OBJ:
if (pn->isKind(PNK_OR) || pn->isKind(PNK_AND)) {
// Propagate Condition context through logical connectives.
SyntacticContext kidsc = SyntacticContext::Other;
if (sc == SyntacticContext::Condition)
kidsc = sc;
if (!Fold(cx, &pn->pn_left, handler, options, inGenexpLambda, kidsc))
return false;
if (!Fold(cx, &pn->pn_right, handler, options, inGenexpLambda, kidsc))
return false;
} else {
/* First kid may be null (for default case in switch). */
if (pn->pn_left) {
if (!Fold(cx, &pn->pn_left, handler, options, inGenexpLambda, condIf(pn, PNK_WHILE)))
return false;
}
/* Second kid may be null (for return in non-generator). */
if (pn->pn_right) {
if (!Fold(cx, &pn->pn_right, handler, options, inGenexpLambda, condIf(pn, PNK_DOWHILE)))
return false;
}
}
pn1 = pn->pn_left;
pn2 = pn->pn_right;
break;
case PN_UNARY:
if (pn->pn_kid) {
SyntacticContext kidsc =
pn->isKind(PNK_NOT)
? SyntacticContext::Condition
: IsDeleteKind(pn->getKind())
? SyntacticContext::Delete
: SyntacticContext::Other;
if (!Fold(cx, &pn->pn_kid, handler, options, inGenexpLambda, kidsc))
return false;
}
pn1 = pn->pn_kid;
break;
case PN_NAME:
/*
* Skip pn1 down along a chain of dotted member expressions to avoid
* excessive recursion. Our only goal here is to fold constants (if
* any) in the primary expression operand to the left of the first
* dot in the chain.
*/
if (!pn->isUsed()) {
ParseNode** lhsp = &pn->pn_expr;
while (*lhsp && (*lhsp)->isArity(PN_NAME) && !(*lhsp)->isUsed())
lhsp = &(*lhsp)->pn_expr;
if (*lhsp && !Fold(cx, lhsp, handler, options, inGenexpLambda, SyntacticContext::Other))
return false;
pn1 = *lhsp;
}
break;
case PN_NULLARY:
break;
}
// The immediate child of a PNK_DELETE* node should not be replaced
// with node indicating a different syntactic form; |delete x| is not
// the same as |delete (true && x)|. See bug 888002.
//
// pn is the immediate child in question. Its descendants were already
// constant-folded above, so we're done.
if (sc == SyntacticContext::Delete)
return true;
switch (pn->getKind()) {
case PNK_IF:
{
bool result;
if (ParseNode* consequent = pn2) {
if (!ContainsHoistedDeclaration(cx, consequent, &result))
return false;
if (result)
break;
}
if (ParseNode* alternative = pn3) {
if (!ContainsHoistedDeclaration(cx, alternative, &result))
return false;
if (result)
break;
}
}
/* FALL THROUGH */
case PNK_CONDITIONAL:
/* Reduce 'if (C) T; else E' into T for true C, E for false. */
switch (pn1->getKind()) {
case PNK_NUMBER:
if (pn1->pn_dval == 0 || IsNaN(pn1->pn_dval))
pn2 = pn3;
break;
case PNK_STRING:
if (pn1->pn_atom->length() == 0)
pn2 = pn3;
break;
case PNK_TRUE:
break;
case PNK_FALSE:
case PNK_NULL:
pn2 = pn3;
break;
default:
/* Early return to dodge common code that copies pn2 to pn. */
return true;
}
#if JS_HAS_GENERATOR_EXPRS
/* Don't fold a trailing |if (0)| in a generator expression. */
if (!pn2 && inGenexpLambda)
break;
#endif
if (pn2 && !pn2->isDefn()) {
ReplaceNode(pnp, pn2);
pn = pn2;
}
if (!pn2 || (pn->isKind(PNK_SEMI) && !pn->pn_kid)) {
/*
* False condition and no else, or an empty then-statement was
* moved up over pn. Either way, make pn an empty block (not an
* empty statement, which does not decompile, even when labeled).
* NB: pn must be a PNK_IF as PNK_CONDITIONAL can never have a null
* kid or an empty statement for a child.
*/
handler.prepareNodeForMutation(pn);
pn->setKind(PNK_STATEMENTLIST);
pn->setArity(PN_LIST);
pn->makeEmpty();
}
if (pn3 && pn3 != pn2)
handler.freeTree(pn3);
break;
case PNK_OR:
case PNK_AND:
if (sc == SyntacticContext::Condition) {
ParseNode** listp = &pn->pn_head;
MOZ_ASSERT(*listp == pn1);
uint32_t orig = pn->pn_count;
do {
Truthiness t = Boolish(pn1);
if (t == Unknown) {
listp = &pn1->pn_next;
continue;
}
if ((t == Truthy) == pn->isKind(PNK_OR)) {
for (pn2 = pn1->pn_next; pn2; pn2 = pn3) {
pn3 = pn2->pn_next;
handler.freeTree(pn2);
--pn->pn_count;
}
pn1->pn_next = nullptr;
break;
}
MOZ_ASSERT((t == Truthy) == pn->isKind(PNK_AND));
if (pn->pn_count == 1)
break;
*listp = pn1->pn_next;
handler.freeTree(pn1);
--pn->pn_count;
} while ((pn1 = *listp) != nullptr);
// We may have to replace a one-element list with its element.
pn1 = pn->pn_head;
if (pn->pn_count == 1) {
ReplaceNode(pnp, pn1);
pn = pn1;
} else if (orig != pn->pn_count) {
// Adjust list tail.
pn2 = pn1->pn_next;
for (; pn1; pn2 = pn1, pn1 = pn1->pn_next)
continue;
pn->pn_tail = &pn2->pn_next;
}
}
break;
case PNK_ADD: {
MOZ_ASSERT(pn->isArity(PN_LIST));
bool folded = false;
pn2 = pn1->pn_next;
if (pn1->isKind(PNK_NUMBER)) {
// Fold addition of numeric literals: (1 + 2 + x === 3 + x).
// Note that we can only do this the front of the list:
// (x + 1 + 2 !== x + 3) when x is a string.
while (pn2 && pn2->isKind(PNK_NUMBER)) {
pn1->pn_dval += pn2->pn_dval;
pn1->pn_next = pn2->pn_next;
handler.freeTree(pn2);
pn2 = pn1->pn_next;
pn->pn_count--;
folded = true;
}
}
// Now search for adjacent pairs of literals to fold for string
// concatenation.
//
// isStringConcat is true if we know the operation we're looking at
// will be string concatenation at runtime. As soon as we see a
// string, we know that every addition to the right of it will be
// string concatenation, even if both operands are numbers:
// ("s" + x + 1 + 2 === "s" + x + "12").
//
bool isStringConcat = false;
RootedString foldedStr(cx);
// (number + string) is definitely concatenation, but only at the
// front of the list: (x + 1 + "2" !== x + "12") when x is a
// number.
if (pn1->isKind(PNK_NUMBER) && pn2 && pn2->isKind(PNK_STRING))
isStringConcat = true;
while (pn2) {
isStringConcat = isStringConcat || pn1->isKind(PNK_STRING);
if (isStringConcat &&
(pn1->isKind(PNK_STRING) || pn1->isKind(PNK_NUMBER)) &&
(pn2->isKind(PNK_STRING) || pn2->isKind(PNK_NUMBER)))
{
// Fold string concatenation of literals.
if (pn1->isKind(PNK_NUMBER) && !FoldType(cx, pn1, PNK_STRING))
return false;
if (pn2->isKind(PNK_NUMBER) && !FoldType(cx, pn2, PNK_STRING))
return false;
if (!foldedStr)
foldedStr = pn1->pn_atom;
RootedString right(cx, pn2->pn_atom);
foldedStr = ConcatStrings<CanGC>(cx, foldedStr, right);
if (!foldedStr)
return false;
pn1->pn_next = pn2->pn_next;
handler.freeTree(pn2);
pn2 = pn1->pn_next;
pn->pn_count--;
folded = true;
} else {
if (foldedStr) {
// Convert the rope of folded strings into an Atom.
pn1->pn_atom = AtomizeString(cx, foldedStr);
if (!pn1->pn_atom)
return false;
foldedStr = nullptr;
}
pn1 = pn2;
pn2 = pn2->pn_next;
}
}
if (foldedStr) {
// Convert the rope of folded strings into an Atom.
pn1->pn_atom = AtomizeString(cx, foldedStr);
if (!pn1->pn_atom)
return false;
}
if (folded) {
if (pn->pn_count == 1) {
// We reduced the list to one constant. There is no
// addition anymore. Replace the PNK_ADD node with the
// single PNK_STRING or PNK_NUMBER node.
ReplaceNode(pnp, pn1);
pn = pn1;
} else if (!pn2) {
pn->pn_tail = &pn1->pn_next;
}
}
break;
}
case PNK_SUB:
case PNK_STAR:
case PNK_LSH:
case PNK_RSH:
case PNK_URSH:
case PNK_DIV:
case PNK_MOD:
MOZ_ASSERT(pn->getArity() == PN_LIST);
MOZ_ASSERT(pn->pn_count >= 2);
for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
if (!FoldType(cx, pn2, PNK_NUMBER))
return false;
}
for (pn2 = pn1; pn2; pn2 = pn2->pn_next) {
/* XXX fold only if all operands convert to number */
if (!pn2->isKind(PNK_NUMBER))
break;
}
if (!pn2) {
JSOp op = pn->getOp();
pn2 = pn1->pn_next;
pn3 = pn2->pn_next;
if (!FoldBinaryNumeric(cx, op, pn1, pn2, pn))
return false;
while ((pn2 = pn3) != nullptr) {
pn3 = pn2->pn_next;
if (!FoldBinaryNumeric(cx, op, pn, pn2, pn))
return false;
}
}
break;
case PNK_TYPEOFNAME:
case PNK_TYPEOFEXPR:
case PNK_VOID:
case PNK_NOT:
case PNK_BITNOT:
case PNK_POS:
case PNK_NEG:
if (pn1->isKind(PNK_NUMBER)) {
double d;
/* Operate on one numeric constant. */
d = pn1->pn_dval;
switch (pn->getKind()) {
case PNK_BITNOT:
d = ~ToInt32(d);
break;
case PNK_NEG:
d = -d;
break;
case PNK_POS:
break;
case PNK_NOT:
if (d == 0 || IsNaN(d)) {
pn->setKind(PNK_TRUE);
pn->setOp(JSOP_TRUE);
} else {
pn->setKind(PNK_FALSE);
pn->setOp(JSOP_FALSE);
}
pn->setArity(PN_NULLARY);
/* FALL THROUGH */
default:
/* Return early to dodge the common PNK_NUMBER code. */
return true;
}
pn->setKind(PNK_NUMBER);
pn->setOp(JSOP_DOUBLE);
pn->setArity(PN_NULLARY);
pn->pn_dval = d;
handler.freeTree(pn1);
} else if (pn1->isKind(PNK_TRUE) || pn1->isKind(PNK_FALSE)) {
if (pn->isKind(PNK_NOT)) {
ReplaceNode(pnp, pn1);
pn = pn1;
if (pn->isKind(PNK_TRUE)) {
pn->setKind(PNK_FALSE);
pn->setOp(JSOP_FALSE);
} else {
pn->setKind(PNK_TRUE);
pn->setOp(JSOP_TRUE);
}
}
}
break;
case PNK_ELEM: {
// An indexed expression, pn1[pn2]. A few cases can be improved.
PropertyName* name = nullptr;
if (pn2->isKind(PNK_STRING)) {
JSAtom* atom = pn2->pn_atom;
uint32_t index;
if (atom->isIndex(&index)) {
// Optimization 1: We have something like pn1["100"]. This is
// equivalent to pn1[100] which is faster.
pn2->setKind(PNK_NUMBER);
pn2->setOp(JSOP_DOUBLE);
pn2->pn_dval = index;
} else {
name = atom->asPropertyName();
}
} else if (pn2->isKind(PNK_NUMBER)) {
double number = pn2->pn_dval;
if (number != ToUint32(number)) {
// Optimization 2: We have something like pn1[3.14]. The number
// is not an array index. This is equivalent to pn1["3.14"]
// which enables optimization 3 below.
JSAtom* atom = ToAtom<NoGC>(cx, DoubleValue(number));
if (!atom)
return false;
name = atom->asPropertyName();
}
}
if (name && NameToId(name) == IdToTypeId(NameToId(name))) {
// Optimization 3: We have pn1["foo"] where foo is not an index.
// Convert to a property access (like pn1.foo) which we optimize
// better downstream. Don't bother with this for names which TI
// considers to be indexes, to simplify downstream analysis.
ParseNode* expr = handler.newPropertyAccess(pn->pn_left, name, pn->pn_pos.end);
if (!expr)
return false;
ReplaceNode(pnp, expr);
// Supposing we're replacing |obj["prop"]| with |obj.prop|, we now
// can free the |"prop"| node and |obj["prop"]| nodes -- but not
// the |obj| node now a sub-node of |expr|. Mutate |pn| into a
// node with |"prop"| as its child so that our |pn| doesn't have a
// necessarily-weird structure (say, by nulling out |pn->pn_left|
// only) that would fail AST sanity assertions performed by
// |handler.freeTree(pn)|.
pn->setKind(PNK_TYPEOFEXPR);
pn->setArity(PN_UNARY);
pn->pn_kid = pn2;
handler.freeTree(pn);
pn = expr;
}
break;
}
default:;
}
if (sc == SyntacticContext::Condition) {
Truthiness t = Boolish(pn);
if (t != Unknown) {
/*
* We can turn function nodes into constant nodes here, but mutating function
* nodes is tricky --- in particular, mutating a function node that appears on
* a method list corrupts the method list. However, methods are M's in
* statements of the form 'this.foo = M;', which we never fold, so we're okay.
*/
handler.prepareNodeForMutation(pn);
if (t == Truthy) {
pn->setKind(PNK_TRUE);
pn->setOp(JSOP_TRUE);
} else {
pn->setKind(PNK_FALSE);
pn->setOp(JSOP_FALSE);
}
pn->setArity(PN_NULLARY);
}
}
return true;
}
bool
frontend::FoldConstants(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>* parser)
{
// Don't fold constants if the code has requested "use asm" as
// constant-folding will misrepresent the source text for the purpose
// of type checking. (Also guard against entering a function containing
// "use asm", see PN_FUNC case below.)
if (parser->pc->useAsmOrInsideUseAsm())
return true;
return Fold(cx, pnp, parser->handler, parser->options(), false, SyntacticContext::Other);
}