Bug 1466000 - Part 10: Add CallOrNewEmitter. r=efaust
authorTooru Fujisawa <arai_a@mac.com>
Tue, 09 Oct 2018 21:23:13 +0900
changeset 440376 4f55976a9e9115c9f41075843bc48955684364d9
parent 440375 1a5da918f6d7b1e650b56b96570648628ee92b1a
child 440377 7801a4fb37db335ce0f65a92668b3062d3ea2df0
push id108785
push userarai_a@mac.com
push dateWed, 10 Oct 2018 03:00:43 +0000
treeherdermozilla-inbound@4f55976a9e91 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust
bugs1466000
milestone64.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 1466000 - Part 10: Add CallOrNewEmitter. r=efaust
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/CallOrNewEmitter.cpp
js/src/frontend/CallOrNewEmitter.h
js/src/moz.build
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -20,16 +20,17 @@
 #include <string.h>
 
 #include "jsnum.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/Nestable.h"
 #include "frontend/BytecodeControlStructures.h"
+#include "frontend/CallOrNewEmitter.h"
 #include "frontend/CForEmitter.h"
 #include "frontend/DoWhileEmitter.h"
 #include "frontend/ElemOpEmitter.h"
 #include "frontend/EmitterScope.h"
 #include "frontend/ExpressionStatementEmitter.h"
 #include "frontend/ForInEmitter.h"
 #include "frontend/ForOfEmitter.h"
 #include "frontend/ForOfLoopControl.h"
@@ -6771,277 +6772,194 @@ BytecodeEmitter::isRestParameter(ParseNo
             return paramName && name == paramName;
         }
     }
 
     return false;
 }
 
 bool
-BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, bool isCall, bool isNew)
-{
-    bool needsThis = !isCall;
+BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNewEmitter& cone)
+{
     switch (callee->getKind()) {
-      case ParseNodeKind::Name: {
-        JSAtom* name = callee->name();
-        NameOpEmitter noe(this, name,
-                          isCall
-                          ? NameOpEmitter::Kind::Call
-                          : NameOpEmitter::Kind::Get);
-        if (!noe.emitGet()) {                             // CALLEE THIS
+      case ParseNodeKind::Name:
+        if (!cone.emitNameCallee(callee->name())) {       // CALLEE THIS
             return false;
         }
         break;
-      }
       case ParseNodeKind::Dot: {
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyAccess* prop = &callee->as<PropertyAccess>();
         bool isSuper = prop->isSuper();
-        PropOpEmitter poe(this,
-                          isCall
-                          ? PropOpEmitter::Kind::Call
-                          : PropOpEmitter::Kind::Get,
-                          isSuper
-                          ? PropOpEmitter::ObjKind::Super
-                          : PropOpEmitter::ObjKind::Other);
+
+        PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
         if (!poe.prepareForObj()) {
             return false;
         }
         if (isSuper) {
             UnaryNode* base = &prop->expression().as<UnaryNode>();
             if (!emitGetThisForSuperBase(base)) {        // THIS
                 return false;
             }
         } else {
             if (!emitPropLHS(prop)) {                    // OBJ
                 return false;
             }
         }
-        if (!poe.emitGet(prop->key().atom())) {           // [needsThis]
-            //                                            // CALLEE
-            //                                            // [!needsThis]
-            //                                            // CALLEE THIS
+        if (!poe.emitGet(prop->key().atom())) {           // CALLEE THIS?
             return false;
         }
 
         break;
       }
       case ParseNodeKind::Elem: {
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyByValue* elem = &callee->as<PropertyByValue>();
         bool isSuper = elem->isSuper();
-        ElemOpEmitter eoe(this,
-                          isCall
-                          ? ElemOpEmitter::Kind::Call
-                          : ElemOpEmitter::Kind::Get,
-                          isSuper
-                          ? ElemOpEmitter::ObjKind::Super
-                          : ElemOpEmitter::ObjKind::Other);
-        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [needsThis,Super]
-            //                                            // THIS KEY
+
+        ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
+            //                                            // THIS? THIS KEY
             //                                            // [needsThis,Other]
-            //                                            // OBJ KEY
-            //                                            // [!needsThis,Super]
-            //                                            // THIS THIS KEY
-            //                                            // [!needsThis,Other]
-            //                                            // OBJ OBJ KEY
-            return false;
-        }
-        if (!eoe.emitGet()) {                             // [needsThis]
-            //                                            // CALLEE
-            //                                            // [!needsThis]
-            //                                            // CALLEE THIS
+            //                                            // OBJ? OBJ KEY
+            return false;
+        }
+        if (!eoe.emitGet()) {                             // CALLEE? THIS
             return false;
         }
 
         break;
       }
       case ParseNodeKind::Function:
-        /*
-         * Top level lambdas which are immediately invoked should be
-         * treated as only running once. Every time they execute we will
-         * create new types and scripts for their contents, to increase
-         * the quality of type information within them and enable more
-         * backend optimizations. Note that this does not depend on the
-         * lambda being invoked at most once (it may be named or be
-         * accessed via foo.caller indirection), as multiple executions
-         * will just cause the inner scripts to be repeatedly cloned.
-         */
-        MOZ_ASSERT(!emittingRunOnceLambda);
-        if (checkRunOnceContext()) {
-            emittingRunOnceLambda = true;
-            if (!emitTree(callee)) {
-                return false;
-            }
-            emittingRunOnceLambda = false;
-        } else {
-            if (!emitTree(callee)) {
-                return false;
-            }
-        }
-        needsThis = true;
+        if (!cone.prepareForFunctionCallee()) {
+            return false;
+        }
+        if (!emitTree(callee)) {                          // CALLEE
+            return false;
+        }
         break;
       case ParseNodeKind::SuperBase:
         MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCall));
         MOZ_ASSERT(parser->astGenerator().isSuperBase(callee));
-        if (!emit1(JSOP_SUPERFUN)) {
+        if (!cone.emitSuperCallee()) {                    // CALLEE THIS
             return false;
         }
         break;
       default:
+        if (!cone.prepareForOtherCallee()) {
+            return false;
+        }
         if (!emitTree(callee)) {
             return false;
         }
-        needsThis = true;
         break;
     }
 
-    if (needsThis) {
-        if (isNew) {
-            if (!emit1(JSOP_IS_CONSTRUCTING)) {
-                return false;
-            }
-        } else {
-            if (!emit1(JSOP_UNDEFINED)) {
-                return false;
-            }
-        }
+    if (!cone.emitThis()) {                               // CALLEE THIS
+        return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitPipeline(ListNode* node)
 {
     MOZ_ASSERT(node->count() >= 2);
 
-    if (!emitTree(node->head())) {
+    if (!emitTree(node->head())) {                        // ARG
         return false;
     }
 
     ParseNode* callee = node->head()->pn_next;
-
+    CallOrNewEmitter cone(this, JSOP_CALL,
+                          CallOrNewEmitter::ArgumentsKind::Other,
+                          ValueUsage::WantValue);
     do {
-        if (!emitCalleeAndThis(callee, node, true, false)) {
-            return false;
-        }
-
-        if (!emit2(JSOP_PICK, 2)) {
-            return false;
-        }
-
-        if (!emitCall(JSOP_CALL, 1, node)) {
-            return false;
-        }
+        if (!emitCalleeAndThis(callee, node, cone)) {     // ARG CALLEE THIS
+            return false;
+        }
+        if (!emit2(JSOP_PICK, 2)) {                       // CALLEE THIS ARG
+            return false;
+        }
+        if (!cone.emitEnd(1, Some(node->pn_pos.begin))) { // RVAL
+            return false;
+        }
+
+        cone.reset();
 
         checkTypeSet(JSOP_CALL);
     } while ((callee = callee->pn_next));
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitArguments(ListNode* argsList, bool callop, bool spread)
+BytecodeEmitter::emitArguments(ListNode* argsList, bool isCall, bool isSpread,
+                               CallOrNewEmitter& cone)
 {
     uint32_t argc = argsList->count();
-
     if (argc >= ARGC_LIMIT) {
-        reportError(argsList, callop ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS);
-        return false;
-    }
-
-    if (!spread) {
+        reportError(argsList, isCall ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS);
+        return false;
+    }
+    if (!isSpread) {
+        if (!cone.prepareForNonSpreadArguments()) {       // CALLEE THIS
+            return false;
+        }
         for (ParseNode* arg : argsList->contents()) {
             if (!emitTree(arg)) {
                 return false;
             }
         }
     } else {
-        ParseNode* args = argsList->head();
-        MOZ_ASSERT_IF(argc == 1, args->isKind(ParseNodeKind::Spread));
-        bool emitOptCode = (argc == 1) && isRestParameter(args->as<UnaryNode>().kid());
-        InternalIfEmitter ifNotOptimizable(this);
-
-        if (emitOptCode) {
-            // Emit a preparation code to optimize the spread call with a rest
-            // parameter:
-            //
-            //   function f(...args) {
-            //     g(...args);
-            //   }
-            //
-            // If the spread operand is a rest parameter and it's optimizable
-            // array, skip spread operation and pass it directly to spread call
-            // operation.  See the comment in OptimizeSpreadCall in
-            // Interpreter.cpp for the optimizable conditons.
-
-            if (!emitTree(args->as<UnaryNode>().kid())) {
-                return false;
-            }
-
-            if (!emit1(JSOP_OPTIMIZE_SPREADCALL)) {
-                return false;
-            }
-
-            if (!emit1(JSOP_NOT)) {
-                return false;
-            }
-
-            if (!ifNotOptimizable.emitThen()) {
-                return false;
-            }
-
-            if (!emit1(JSOP_POP)) {
-                return false;
-            }
-        }
-
-        if (!emitArray(args, argc)) {
-            return false;
-        }
-
-        if (emitOptCode) {
-            if (!ifNotOptimizable.emitEnd()) {
-                return false;
-            }
+        if (cone.wantSpreadOperand()) {
+            UnaryNode* spreadNode = &argsList->head()->as<UnaryNode>();
+            if (!emitTree(spreadNode->kid())) {           // CALLEE THIS ARG0
+                return false;
+            }
+        }
+        if (!cone.emitSpreadArgumentsTest()) {            // CALLEE THIS
+            return false;
+        }
+        if (!emitArray(argsList->head(), argc)) {         // CALLEE THIS ARR
+            return false;
         }
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitCallOrNew(BinaryNode* callNode,
                                ValueUsage valueUsage /* = ValueUsage::WantValue */)
 {
-    bool callop =
-        callNode->isKind(ParseNodeKind::Call) || callNode->isKind(ParseNodeKind::TaggedTemplate);
-
     /*
      * Emit callable invocation or operator new (constructor call) code.
      * First, emit code for the left operand to evaluate the callable or
      * constructable object expression.
      *
      * For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc.
      * This is necessary to interpose the lambda-initialized method read
      * barrier -- see the code in jsinterp.cpp for JSOP_LAMBDA followed by
      * JSOP_{SET,INIT}PROP.
      *
      * Then (or in a call case that has no explicit reference-base
      * object) we emit JSOP_UNDEFINED to produce the undefined |this|
      * value required for calls (which non-strict mode functions
      * will box into the global object).
      */
+    bool isCall =
+        callNode->isKind(ParseNodeKind::Call) || callNode->isKind(ParseNodeKind::TaggedTemplate);
     ParseNode* calleeNode = callNode->left();
     ListNode* argsList = &callNode->right()->as<ListNode>();
-
-    bool spread = JOF_OPTYPE(callNode->getOp()) == JOF_BYTE;
-
-    if (calleeNode->isKind(ParseNodeKind::Name) && emitterMode == BytecodeEmitter::SelfHosting && !spread) {
+    bool isSpread = JOF_OPTYPE(callNode->getOp()) == JOF_BYTE;
+    if (calleeNode->isKind(ParseNodeKind::Name) && emitterMode == BytecodeEmitter::SelfHosting &&
+        !isSpread)
+    {
         // Calls to "forceInterpreter", "callFunction",
         // "callContentFunction", or "resumeGenerator" in self-hosted
         // code generate inline bytecode.
         PropertyName* calleeName = calleeNode->name();
         if (calleeName == cx->names().callFunction ||
             calleeName == cx->names().callContentFunction ||
             calleeName == cx->names().constructContentFunction)
         {
@@ -7064,50 +6982,28 @@ BytecodeEmitter::emitCallOrNew(BinaryNod
         }
         if (calleeName == cx->names().getPropertySuper) {
             return emitSelfHostedGetPropertySuper(callNode);
         }
         // Fall through
     }
 
     JSOp op = callNode->getOp();
-    bool isNewOp = op == JSOP_NEW || op == JSOP_SPREADNEW ||
-                   op == JSOP_SUPERCALL ||
-                   op == JSOP_SPREADSUPERCALL;
-
-    if (!emitCalleeAndThis(calleeNode, callNode, callop, isNewOp)) {
-        return false;
-    }
-
-    if (!emitArguments(argsList, callop, spread)) {
-        return false;
-    }
-
     uint32_t argc = argsList->count();
-
-    /*
-     * Emit code for each argument in order, then emit the JSOP_*CALL or
-     * JSOP_NEW bytecode with a two-byte immediate telling how many args
-     * were pushed on the operand stack.
-     */
-    if (isNewOp) {
-        if (callNode->isKind(ParseNodeKind::SuperCall)) {
-            if (!emit1(JSOP_NEWTARGET)) {
-                return false;
-            }
-        } else if (!spread) {
-            // Repush the callee as new.target
-            if (!emitDupAt(argc + 1)) {
-                return false;
-            }
-        } else {
-            if (!emitDupAt(2)) {
-                return false;
-            }
-        }
+    CallOrNewEmitter cone(this, op,
+                          isSpread && (argc == 1) &&
+                          isRestParameter(argsList->head()->as<UnaryNode>().kid())
+                          ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest
+                          : CallOrNewEmitter::ArgumentsKind::Other,
+                          valueUsage);
+    if (!emitCalleeAndThis(calleeNode, callNode, cone)) { // CALLEE THIS
+        return false;
+    }
+    if (!emitArguments(argsList, isCall, isSpread, cone)) {
+        return false;                                     // CALLEE THIS ARGS...
     }
 
     ParseNode* coordNode = callNode;
     if (op == JSOP_CALL || op == JSOP_SPREADCALL) {
         switch (calleeNode->getKind()) {
           case ParseNodeKind::Dot: {
 
             // Check if this member is a simple chain of simple chain of
@@ -7142,48 +7038,18 @@ BytecodeEmitter::emitCallOrNew(BinaryNod
             // obj[expr]() // expression
             //          ^  // column coord
             coordNode = argsList;
             break;
           default:
             break;
         }
     }
-
-    if (!spread) {
-        if (op == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
-            if (!emitCall(JSOP_CALL_IGNORES_RV, argc, coordNode)) {
-                return false;
-            }
-            checkTypeSet(JSOP_CALL_IGNORES_RV);
-        } else {
-            if (!emitCall(op, argc, coordNode)) {
-                return false;
-            }
-            checkTypeSet(op);
-        }
-    } else {
-        if (coordNode) {
-            if (!updateSourceCoordNotes(coordNode->pn_pos.begin)) {
-                return false;
-            }
-        }
-
-        if (!emit1(op)) {
-            return false;
-        }
-        checkTypeSet(op);
-    }
-    if (op == JSOP_EVAL || op == JSOP_STRICTEVAL ||
-        op == JSOP_SPREADEVAL || op == JSOP_STRICTSPREADEVAL)
-    {
-        uint32_t lineNum = parser->errorReporter().lineAt(callNode->pn_pos.begin);
-        if (!emitUint32Operand(JSOP_LINENO, lineNum)) {
-            return false;
-        }
+    if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) {
+        return false;                                     // RVAL
     }
 
     return true;
 }
 
 static const JSOp ParseNodeKindToJSOp[] = {
     // JSOP_NOP is for pipeline operator which does not emit its own JSOp
     // but has highest precedence in binary operators
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -105,16 +105,17 @@ struct CGYieldAndAwaitOffsetList {
     void finish(mozilla::Span<uint32_t> array, uint32_t prologueLength);
 };
 
 // Have a few inline elements, so as to avoid heap allocation for tiny
 // sequences.  See bug 1390526.
 typedef Vector<jsbytecode, 64> BytecodeVector;
 typedef Vector<jssrcnote, 64> SrcNotesVector;
 
+class CallOrNewEmitter;
 class ElemOpEmitter;
 class EmitterScope;
 class NestableControl;
 class TDZCheckCache;
 
 struct MOZ_STACK_CLASS BytecodeEmitter
 {
     SharedContext* const sc;      /* context shared between parsing and bytecode generation */
@@ -757,17 +758,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional,
                                                 ValueUsage valueUsage = ValueUsage::WantValue);
 
     bool isRestParameter(ParseNode* pn);
 
-    MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool callop, bool spread);
+    MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool isCall, bool isSpread,
+                                    CallOrNewEmitter& cone);
     MOZ_MUST_USE bool emitCallOrNew(BinaryNode* callNode,
                                     ValueUsage valueUsage = ValueUsage::WantValue);
     MOZ_MUST_USE bool emitSelfHostedCallFunction(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedResumeGenerator(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedForceInterpreter();
     MOZ_MUST_USE bool emitSelfHostedAllowContentIter(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedDefineDataProperty(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedGetPropertySuper(BinaryNode* callNode);
@@ -804,17 +806,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false);
 
     MOZ_MUST_USE bool emitClass(ClassNode* classNode);
     MOZ_MUST_USE bool emitSuperElemOperands(PropertyByValue* elem,
                                             EmitElemOption opts = EmitElemOption::Get);
     MOZ_MUST_USE bool emitSuperGetElem(PropertyByValue* elem, bool isCall = false);
 
     MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callee, ParseNode* call,
-                                        bool isCall, bool isNew);
+                                        CallOrNewEmitter& cone);
 
     MOZ_MUST_USE bool emitPipeline(ListNode* node);
 
     MOZ_MUST_USE bool emitExportDefault(BinaryNode* exportNode);
 };
 
 class MOZ_RAII AutoCheckUnstableEmitterScope {
 #ifdef DEBUG
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/CallOrNewEmitter.cpp
@@ -0,0 +1,319 @@
+/* -*- 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/CallOrNewEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/NameOpEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+#include "vm/StringType.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+AutoEmittingRunOnceLambda::AutoEmittingRunOnceLambda(BytecodeEmitter* bce)
+  : bce_(bce)
+{
+    MOZ_ASSERT(!bce_->emittingRunOnceLambda);
+    bce_->emittingRunOnceLambda = true;
+}
+
+AutoEmittingRunOnceLambda::~AutoEmittingRunOnceLambda()
+{
+    bce_->emittingRunOnceLambda = false;
+}
+
+CallOrNewEmitter::CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
+                                   ArgumentsKind argumentsKind,
+                                   ValueUsage valueUsage)
+  : bce_(bce),
+    op_(op),
+    argumentsKind_(argumentsKind)
+{
+    if (op_ == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
+        op_ = JSOP_CALL_IGNORES_RV;
+    }
+
+    MOZ_ASSERT(isCall() || isNew() || isSuperCall());
+}
+
+bool
+CallOrNewEmitter::emitNameCallee(JSAtom* name)
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    NameOpEmitter noe(bce_, name,
+                      isCall()
+                      ? NameOpEmitter::Kind::Call
+                      : NameOpEmitter::Kind::Get);
+    if (!noe.emitGet()) {                             // CALLEE THIS
+        return false;
+    }
+
+    state_ = State::NameCallee;
+    return true;
+}
+
+MOZ_MUST_USE PropOpEmitter&
+CallOrNewEmitter::prepareForPropCallee(bool isSuperProp)
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    poe_.emplace(bce_,
+                 isCall()
+                 ? PropOpEmitter::Kind::Call
+                 : PropOpEmitter::Kind::Get,
+                 isSuperProp
+                 ? PropOpEmitter::ObjKind::Super
+                 : PropOpEmitter::ObjKind::Other);
+
+    state_ = State::PropCallee;
+    return *poe_;
+}
+
+MOZ_MUST_USE ElemOpEmitter&
+CallOrNewEmitter::prepareForElemCallee(bool isSuperElem)
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    eoe_.emplace(bce_,
+                 isCall()
+                 ? ElemOpEmitter::Kind::Call
+                 : ElemOpEmitter::Kind::Get,
+                 isSuperElem
+                 ? ElemOpEmitter::ObjKind::Super
+                 : ElemOpEmitter::ObjKind::Other);
+
+    state_ = State::ElemCallee;
+    return *eoe_;
+}
+
+bool
+CallOrNewEmitter::prepareForFunctionCallee()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    // Top level lambdas which are immediately invoked should be treated as
+    // only running once. Every time they execute we will create new types and
+    // scripts for their contents, to increase the quality of type information
+    // within them and enable more backend optimizations. Note that this does
+    // not depend on the lambda being invoked at most once (it may be named or
+    // be accessed via foo.caller indirection), as multiple executions will
+    // just cause the inner scripts to be repeatedly cloned.
+    MOZ_ASSERT(!bce_->emittingRunOnceLambda);
+    if (bce_->checkRunOnceContext()) {
+        autoEmittingRunOnceLambda_.emplace(bce_);
+    }
+
+    state_ = State::FunctionCallee;
+    return true;
+}
+
+bool
+CallOrNewEmitter::emitSuperCallee()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    if (!bce_->emit1(JSOP_SUPERFUN)) {                // CALLEE
+        return false;
+    }
+    if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) {         // CALLEE THIS
+        return false;
+    }
+
+    state_ = State::SuperCallee;
+    return true;
+}
+
+bool
+CallOrNewEmitter::prepareForOtherCallee()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    state_ = State::OtherCallee;
+    return true;
+}
+
+bool
+CallOrNewEmitter::emitThis()
+{
+    MOZ_ASSERT(state_ == State::NameCallee ||
+               state_ == State::PropCallee ||
+               state_ == State::ElemCallee ||
+               state_ == State::FunctionCallee ||
+               state_ == State::SuperCallee ||
+               state_ == State::OtherCallee);
+
+    bool needsThis = false;
+    switch (state_) {
+      case State::NameCallee:
+        if (!isCall()) {
+            needsThis = true;
+        }
+        break;
+      case State::PropCallee:
+        poe_.reset();
+        if (!isCall()) {
+            needsThis = true;
+        }
+        break;
+      case State::ElemCallee:
+        eoe_.reset();
+        if (!isCall()) {
+            needsThis = true;
+        }
+        break;
+      case State::FunctionCallee:
+        autoEmittingRunOnceLambda_.reset();
+        needsThis = true;
+        break;
+      case State::SuperCallee:
+        break;
+      case State::OtherCallee:
+        needsThis = true;
+        break;
+      default:;
+    }
+    if (needsThis) {
+        if (isNew() || isSuperCall()) {
+            if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS
+                return false;
+            }
+        } else {
+            if (!bce_->emit1(JSOP_UNDEFINED)) {       // CALLEE THIS
+                return false;
+            }
+        }
+    }
+
+    state_ = State::This;
+    return true;
+}
+
+// Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance
+// across multiple chained calls.
+void
+CallOrNewEmitter::reset()
+{
+    MOZ_ASSERT(state_ == State::End);
+    state_ = State::Start;
+}
+
+bool
+CallOrNewEmitter::prepareForNonSpreadArguments()
+{
+    MOZ_ASSERT(state_ == State::This);
+    MOZ_ASSERT(!isSpread());
+
+    state_ = State::Arguments;
+    return true;
+}
+
+// See the usage in the comment at the top of the class.
+bool
+CallOrNewEmitter::wantSpreadOperand()
+{
+    MOZ_ASSERT(state_ == State::This);
+    MOZ_ASSERT(isSpread());
+
+    state_ = State::WantSpreadOperand;
+    return isSingleSpreadRest();
+}
+
+bool
+CallOrNewEmitter::emitSpreadArgumentsTest()
+{
+    // Caller should check wantSpreadOperand before this.
+    MOZ_ASSERT(state_ == State::WantSpreadOperand);
+    MOZ_ASSERT(isSpread());
+
+    if (isSingleSpreadRest()) {
+        // Emit a preparation code to optimize the spread call with a rest
+        // parameter:
+        //
+        //   function f(...args) {
+        //     g(...args);
+        //   }
+        //
+        // If the spread operand is a rest parameter and it's optimizable
+        // array, skip spread operation and pass it directly to spread call
+        // operation.  See the comment in OptimizeSpreadCall in
+        // Interpreter.cpp for the optimizable conditons.
+
+        ifNotOptimizable_.emplace(bce_);
+        //                                            // CALLEE THIS ARG0
+        if (!bce_->emit1(JSOP_OPTIMIZE_SPREADCALL)) { // CALLEE THIS ARG0 OPTIMIZED
+            return false;
+        }
+        if (!bce_->emit1(JSOP_NOT)) {                 // CALLEE THIS ARG0 !OPTIMIZED
+            return false;
+        }
+        if (!ifNotOptimizable_->emitThen()) {         // CALLEE THIS ARG0
+            return false;
+        }
+        if (!bce_->emit1(JSOP_POP)) {                 // CALLEE THIS
+            return false;
+        }
+    }
+
+    state_ = State::Arguments;
+    return true;
+}
+
+bool
+CallOrNewEmitter::emitEnd(uint32_t argc, const Maybe<uint32_t>& beginPos)
+{
+    MOZ_ASSERT(state_ == State::Arguments);
+
+    if (isSingleSpreadRest()) {
+        if (!ifNotOptimizable_->emitEnd()) {          // CALLEE THIS ARR
+            return false;
+        }
+
+        ifNotOptimizable_.reset();
+    }
+    if (isNew() || isSuperCall()) {
+        if (isSuperCall()) {
+            if (!bce_->emit1(JSOP_NEWTARGET)) {       // CALLEE THIS ARG.. NEW.TARGET
+                return false;
+            }
+        } else {
+            // Repush the callee as new.target
+            uint32_t effectiveArgc = isSpread() ? 1 : argc;
+            if (!bce_->emitDupAt(effectiveArgc + 1)) {
+                return false;                         // CALLEE THIS ARR CALLEE
+            }
+        }
+    }
+    if (!isSpread()) {
+        if (!bce_->emitCall(op_, argc, beginPos)) {   // RVAL
+            return false;
+        }
+    } else {
+        if (beginPos) {
+            if (!bce_->updateSourceCoordNotes(*beginPos)) {
+                return false;
+            }
+        }
+        if (!bce_->emit1(op_)) {                      // RVAL
+            return false;
+        }
+    }
+    bce_->checkTypeSet(op_);
+
+    if (isEval() && beginPos) {
+        uint32_t lineNum = bce_->parser->errorReporter().lineAt(*beginPos);
+        if (!bce_->emitUint32Operand(JSOP_LINENO, lineNum)) {
+            return false;
+        }
+    }
+
+    state_ = State::End;
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/CallOrNewEmitter.h
@@ -0,0 +1,338 @@
+/* -*- 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/. */
+
+#ifndef frontend_CallOrNewEmitter_h
+#define frontend_CallOrNewEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+
+#include "frontend/ElemOpEmitter.h"
+#include "frontend/IfEmitter.h"
+#include "frontend/PropOpEmitter.h"
+#include "frontend/ValueUsage.h"
+#include "js/TypeDecls.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/Opcodes.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+class MOZ_RAII AutoEmittingRunOnceLambda
+{
+    BytecodeEmitter* bce_;
+
+  public:
+    explicit AutoEmittingRunOnceLambda(BytecodeEmitter* bce);
+    ~AutoEmittingRunOnceLambda();
+};
+
+// Class for emitting bytecode for call or new expression.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `print(arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.emitNameCallee();
+//     emit(print);
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `callee.prop(arg1, arg2);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     PropOpEmitter& poe = cone.prepareForPropCallee(false);
+//     ... emit `callee.prop` with `poe` here...
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg1);
+//     emit(arg2);
+//     cone.emitEnd(2, Some(offset_of_callee));
+//
+//   `callee[key](arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     ElemOpEmitter& eoe = cone.prepareForElemCallee(false);
+//     ... emit `callee[key]` with `eoe` here...
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `(function() { ... })(arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.prepareForFunctionCallee();
+//     emit(function);
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `super(arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.emitSuperCallee();
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `(some_other_expression)(arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.prepareForOtherCallee();
+//     emit(some_other_expression);
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `print(...arg);`
+//     CallOrNewEmitter cone(this, JSOP_SPREADCALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.emitNameCallee();
+//     emit(print);
+//     cone.emitThis();
+//     if (cone.wantSpreadOperand())
+//       emit(arg)
+//     cone.emitSpreadArgumentsTest();
+//     emit([...arg]);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `print(...rest);`
+//   where `rest` is rest parameter
+//     CallOrNewEmitter cone(this, JSOP_SPREADCALL,
+//                           CallOrNewEmitter::ArgumentsKind::SingleSpreadRest,
+//                           ValueUsage::WantValue);
+//     cone.emitNameCallee();
+//     emit(print);
+//     cone.emitThis();
+//     if (cone.wantSpreadOperand())
+//       emit(arg)
+//     cone.emitSpreadArgumentsTest();
+//     emit([...arg]);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `new f(arg);`
+//     CallOrNewEmitter cone(this, JSOP_NEW,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.emitNameCallee();
+//     emit(f);
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+class MOZ_STACK_CLASS CallOrNewEmitter
+{
+  public:
+    enum class ArgumentsKind {
+        Other,
+
+        // Specify this for the following case:
+        //
+        //   function f(...rest) {
+        //     g(...rest);
+        //   }
+        //
+        // This enables optimization to avoid allocating an intermediate array
+        // for spread operation.
+        //
+        // wantSpreadOperand() returns true when this is specified.
+        SingleSpreadRest
+    };
+
+  private:
+    BytecodeEmitter* bce_;
+
+    // The opcode for the call or new.
+    JSOp op_;
+
+    // Whether the call is a spread call with single rest parameter or not.
+    // See the comment in emitSpreadArgumentsTest for more details.
+    ArgumentsKind argumentsKind_;
+
+    // The branch for spread call optimization.
+    mozilla::Maybe<InternalIfEmitter> ifNotOptimizable_;
+
+    mozilla::Maybe<AutoEmittingRunOnceLambda> autoEmittingRunOnceLambda_;
+
+    mozilla::Maybe<PropOpEmitter> poe_;
+    mozilla::Maybe<ElemOpEmitter> eoe_;
+
+    // The state of this emitter.
+    //
+    // +-------+   emitNameCallee           +------------+
+    // | Start |-+------------------------->| NameCallee |------+
+    // +-------+ |                          +------------+      |
+    //           |                                              |
+    //           | prepareForPropCallee     +------------+      v
+    //           +------------------------->| PropCallee |----->+
+    //           |                          +------------+      |
+    //           |                                              |
+    //           | prepareForElemCallee     +------------+      v
+    //           +------------------------->| ElemCallee |----->+
+    //           |                          +------------+      |
+    //           |                                              |
+    //           | prepareForFunctionCallee +----------------+  v
+    //           +------------------------->| FunctionCallee |->+
+    //           |                          +----------------+  |
+    //           |                                              |
+    //           | emitSuperCallee          +-------------+     v
+    //           +------------------------->| SuperCallee |---->+
+    //           |                          +-------------+     |
+    //           |                                              |
+    //           | prepareForOtherCallee    +-------------+     v
+    //           +------------------------->| OtherCallee |---->+
+    //                                      +-------------+     |
+    //                                                          |
+    // +--------------------------------------------------------+
+    // |
+    // | emitThis +------+
+    // +--------->| This |-+
+    //            +------+ |
+    //                     |
+    // +-------------------+
+    // |
+    // | [!isSpread]
+    // |   prepareForNonSpreadArguments    +-----------+ emitEnd +-----+
+    // +------------------------------->+->| Arguments |-------->| End |
+    // |                                ^  +-----------+         +-----+
+    // |                                |
+    // |                                +----------------------------------+
+    // |                                                                   |
+    // | [isSpread]                                                        |
+    // |   wantSpreadOperand +-------------------+ emitSpreadArgumentsTest |
+    // +-------------------->| WantSpreadOperand |-------------------------+
+    //                       +-------------------+
+    enum class State {
+        // The initial state.
+        Start,
+
+        // After calling emitNameCallee.
+        NameCallee,
+
+        // After calling prepareForPropCallee.
+        PropCallee,
+
+        // After calling prepareForElemCallee.
+        ElemCallee,
+
+        // After calling prepareForFunctionCallee.
+        FunctionCallee,
+
+        // After calling emitSuperCallee.
+        SuperCallee,
+
+        // After calling prepareForOtherCallee.
+        OtherCallee,
+
+        // After calling emitThis.
+        This,
+
+        // After calling wantSpreadOperand.
+        WantSpreadOperand,
+
+        // After calling prepareForNonSpreadArguments.
+        Arguments,
+
+        // After calling emitEnd.
+        End
+    };
+    State state_ = State::Start;
+
+  public:
+    CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
+                     ArgumentsKind argumentsKind,
+                     ValueUsage valueUsage);
+
+  private:
+    MOZ_MUST_USE bool isCall() const {
+        return op_ == JSOP_CALL || op_ == JSOP_CALL_IGNORES_RV ||
+               op_ == JSOP_SPREADCALL ||
+               isEval() || isFunApply() || isFunCall();
+    }
+
+    MOZ_MUST_USE bool isNew() const {
+        return op_ == JSOP_NEW || op_ == JSOP_SPREADNEW;
+    }
+
+    MOZ_MUST_USE bool isSuperCall() const {
+        return op_ == JSOP_SUPERCALL || op_ == JSOP_SPREADSUPERCALL;
+    }
+
+    MOZ_MUST_USE bool isEval() const {
+        return op_ == JSOP_EVAL || op_ == JSOP_STRICTEVAL ||
+               op_ == JSOP_SPREADEVAL || op_ == JSOP_STRICTSPREADEVAL;
+    }
+
+    MOZ_MUST_USE bool isFunApply() const {
+        return op_ == JSOP_FUNAPPLY;
+    }
+
+    MOZ_MUST_USE bool isFunCall() const {
+        return op_ == JSOP_FUNCALL;
+    }
+
+    MOZ_MUST_USE bool isSpread() const {
+        return JOF_OPTYPE(op_) == JOF_BYTE;
+    }
+
+    MOZ_MUST_USE bool isSingleSpreadRest() const {
+        return argumentsKind_ == ArgumentsKind::SingleSpreadRest;
+    }
+
+  public:
+    MOZ_MUST_USE bool emitNameCallee(JSAtom* name);
+    MOZ_MUST_USE PropOpEmitter& prepareForPropCallee(bool isSuperProp);
+    MOZ_MUST_USE ElemOpEmitter& prepareForElemCallee(bool isSuperElem);
+    MOZ_MUST_USE bool prepareForFunctionCallee();
+    MOZ_MUST_USE bool emitSuperCallee();
+    MOZ_MUST_USE bool prepareForOtherCallee();
+
+    MOZ_MUST_USE bool emitThis();
+
+    // Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance
+    // across multiple chained calls.
+    void reset();
+
+    MOZ_MUST_USE bool prepareForNonSpreadArguments();
+
+    // See the usage in the comment at the top of the class.
+    MOZ_MUST_USE bool wantSpreadOperand();
+    MOZ_MUST_USE bool emitSpreadArgumentsTest();
+
+    // Parameters are the offset in the source code for each character below:
+    //
+    //   callee(arg);
+    //   ^
+    //   |
+    //   beginPos
+    //
+    // Can be Nothing() if not available.
+    MOZ_MUST_USE bool emitEnd(uint32_t argc, const mozilla::Maybe<uint32_t>& beginPos);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_CallOrNewEmitter_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -221,16 +221,17 @@ UNIFIED_SOURCES += [
     'builtin/WeakSetObject.cpp',
     'devtools/sharkctl.cpp',
     'ds/Bitmap.cpp',
     'ds/LifoAlloc.cpp',
     'ds/MemoryProtectionExceptionHandler.cpp',
     'frontend/BytecodeCompiler.cpp',
     'frontend/BytecodeControlStructures.cpp',
     'frontend/BytecodeEmitter.cpp',
+    'frontend/CallOrNewEmitter.cpp',
     'frontend/CForEmitter.cpp',
     'frontend/DoWhileEmitter.cpp',
     'frontend/ElemOpEmitter.cpp',
     'frontend/EmitterScope.cpp',
     'frontend/ExpressionStatementEmitter.cpp',
     'frontend/FoldConstants.cpp',
     'frontend/ForInEmitter.cpp',
     'frontend/ForOfEmitter.cpp',