Bug 1183400 - Fold ?: expressions by kind. r=efaust
authorJeff Walden <jwalden@mit.edu>
Wed, 08 Jul 2015 13:54:14 -0700
changeset 287213 58129dccd5d8393be998eada7621dfdcdbae27bc
parent 287212 436b509ee2bc60b1bae87386b8464e9a1043e59e
child 287214 7dbbc282b2e75110b67d7e33903a6855c7440075
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust
bugs1183400
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1183400 - Fold ?: expressions by kind. r=efaust
js/src/frontend/FoldConstants.cpp
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -844,16 +844,108 @@ FoldIncrementDecrement(ExclusiveContext*
         return false;
 
     MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser<FullParseHandler>::PermitAssignmentToFunctionCalls));
 
     return true;
 }
 
 
+static bool
+FoldConditional(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+                bool inGenexpLambda)
+{
+    ParseNode** nextNode = nodePtr;
+
+    do {
+        // |nextNode| on entry points to the C?T:F expression to be folded.
+        // Reset it to exit the loop in the common case where F isn't another
+        // ?: expression.
+        nodePtr = nextNode;
+        nextNode = nullptr;
+
+        ParseNode* node = *nodePtr;
+        MOZ_ASSERT(node->isKind(PNK_CONDITIONAL));
+        MOZ_ASSERT(node->isArity(PN_TERNARY));
+
+        ParseNode*& expr = node->pn_kid1;
+        if (!Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Condition))
+            return false;
+
+        ParseNode*& ifTruthy = node->pn_kid2;
+        if (!Fold(cx, &ifTruthy, parser, inGenexpLambda, SyntacticContext::Other))
+            return false;
+
+        ParseNode*& ifFalsy = node->pn_kid3;
+
+        // If our C?T:F node has F as another ?: node, *iteratively* constant-
+        // fold F *after* folding C and T (and possibly eliminating C and one
+        // of T/F entirely); otherwise fold F normally.  Making |nextNode| non-
+        // null causes this loop to run again to fold F.
+        //
+        // Conceivably we could instead/also iteratively constant-fold T, if T
+        // were more complex than F.  Such an optimization is unimplemented.
+        if (ifFalsy->isKind(PNK_CONDITIONAL)) {
+            nextNode = &ifFalsy;
+        } else {
+            if (!Fold(cx, &ifFalsy, parser, inGenexpLambda, SyntacticContext::Other))
+                return false;
+        }
+
+        // Try to constant-fold based on the condition expression.
+        Truthiness t = Boolish(expr);
+        if (t == Unknown)
+            continue;
+
+        // Otherwise reduce 'C ? T : F' to T or F as directed by C.
+        ParseNode* replacement;
+        ParseNode* discarded;
+        if (t == Truthy) {
+            replacement = ifTruthy;
+            discarded = ifFalsy;
+        } else {
+            replacement = ifFalsy;
+            discarded = ifTruthy;
+        }
+
+        // Don't decay the overall expression if the replacement node is a
+        // a definition.
+        //
+        // The rationale for this pre-existing restriction is unclear; if you
+        // discover it, please document it!  Speculation is that it has
+        // something to do with constant-folding something like:
+        //
+        //   true ? function f() {} : false;
+        //
+        // into
+        //
+        //   function f() {}
+        //
+        // and worrying this might convert a function *expression* into a
+        // function *statement* that defined its name early.  But function
+        // expressions aren't isDefn(), so this can't be it.
+        //
+        // This lack of explanation is tolerated only because failing to
+        // optimize *should* always be okay.
+        if (replacement->isDefn())
+            continue;
+
+        // Otherwise perform a replacement.  This invalidates |nextNode|, so
+        // reset it (if the replacement requires folding) or clear it (if
+        // |ifFalsy| is dead code) as needed.
+        if (nextNode)
+            nextNode = (*nextNode == replacement) ? nodePtr : nullptr;
+        ReplaceNode(nodePtr, replacement);
+
+        parser.freeTree(discarded);
+    } while (nextNode);
+
+    return true;
+}
+
 bool
 Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bool inGenexpLambda,
      SyntacticContext sc)
 {
     JS_CHECK_RECURSION(cx, return false);
 
     ParseNode** restartNode = nullptr;
     SyntacticContext restartContext;
@@ -921,16 +1013,19 @@ Fold(ExclusiveContext* cx, ParseNode** p
       case PNK_DELETEELEM:
       case PNK_DELETESUPERELEM:
         return FoldDeleteElement(cx, pn, parser, inGenexpLambda);
 
       case PNK_DELETEPROP:
       case PNK_DELETESUPERPROP:
         return FoldDeleteProperty(cx, pn, parser, inGenexpLambda);
 
+      case PNK_CONDITIONAL:
+        return FoldConditional(cx, pnp, parser, inGenexpLambda);
+
       case PNK_NOT:
         return FoldNot(cx, pn, parser, inGenexpLambda);
 
       case PNK_BITNOT:
       case PNK_POS:
       case PNK_NEG:
         return FoldUnaryArithmetic(cx, pn, parser, inGenexpLambda);
 
@@ -975,17 +1070,16 @@ Fold(ExclusiveContext* cx, ParseNode** p
       case PNK_CLASSNAMES:
       case PNK_DEFAULT:
       case PNK_YIELD_STAR:
       case PNK_YIELD:
       case PNK_RETURN:
       case PNK_IMPORT:
       case PNK_EXPORT_FROM:
       case PNK_EXPORT_DEFAULT:
-      case PNK_CONDITIONAL:
       case PNK_FORIN:
       case PNK_FOROF:
       case PNK_FORHEAD:
       case PNK_CLASS:
       case PNK_IF:
       case PNK_TRY:
       case PNK_OR:
       case PNK_AND:
@@ -1102,17 +1196,19 @@ Fold(ExclusiveContext* cx, ParseNode** p
             if (pn->isKind(PNK_FORHEAD) && pn->pn_kid2->isKind(PNK_TRUE)) {
                 parser.freeTree(pn->pn_kid2);
                 pn->pn_kid2 = nullptr;
             }
         }
         pn2 = pn->pn_kid2;
 
         if (pn->pn_kid3) {
-            if (pn->isKind(PNK_IF) || pn->isKind(PNK_CONDITIONAL)) {
+            MOZ_ASSERT(!pn->isKind(PNK_CONDITIONAL),
+                       "should be skipping this above");
+            if (pn->isKind(PNK_IF)) {
                 restartNode = &pn->pn_kid3;
                 restartContext = SyntacticContext::Other;
             } else {
                 if (!Fold(cx, &pn->pn_kid3, parser, inGenexpLambda, SyntacticContext::Other))
                     return false;
             }
         }
         pn3 = pn->pn_kid3;
@@ -1198,20 +1294,18 @@ Fold(ExclusiveContext* cx, ParseNode** p
             if (ParseNode* alternative = pn3) {
                 if (!ContainsHoistedDeclaration(cx, alternative, &result))
                     return false;
                 if (result)
                     break;
             }
         }
         mightHaveHoistedDeclarations = false;
-        /* FALL THROUGH */
 
-      case PNK_CONDITIONAL:
-        /* Reduce 'if (C) T; else E' into T for true C, E for false. */
+        /* Reduce 'if (C)  T; else F' into T for true C, F 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;
@@ -1441,16 +1535,17 @@ Fold(ExclusiveContext* cx, ParseNode** p
 
       case PNK_TYPEOFNAME:
       case PNK_TYPEOFEXPR:
       case PNK_VOID:
       case PNK_NOT:
       case PNK_BITNOT:
       case PNK_POS:
       case PNK_NEG:
+      case PNK_CONDITIONAL:
         MOZ_CRASH("should have been fully handled above");
 
       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;