Bug 1466000 - Part 8: Add ElemOpEmitter. r=efaust
authorTooru Fujisawa <arai_a@mac.com>
Tue, 09 Oct 2018 21:23:12 +0900
changeset 496205 61f8a1e5fa5d4ecb83ff2c82e24831550117c5e3
parent 496204 e29ced1ead38118a28a170f99975936caafae178
child 496206 1a5da918f6d7b1e650b56b96570648628ee92b1a
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [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 8: Add ElemOpEmitter. r=efaust
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/ElemOpEmitter.cpp
js/src/frontend/ElemOpEmitter.h
js/src/moz.build
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -22,16 +22,17 @@
 #include "jsnum.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/Nestable.h"
 #include "frontend/BytecodeControlStructures.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"
 #include "frontend/IfEmitter.h"
 #include "frontend/Parser.h"
 #include "frontend/PropOpEmitter.h"
@@ -2156,202 +2157,85 @@ BytecodeEmitter::emitNameIncDec(UnaryNod
     if (post && !emit1(JSOP_POP)) {
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitElemOperands(PropertyByValue* elem, EmitElemOption opts)
-{
-    if (!emitTree(&elem->expression())) {                 // OBJ
-        return false;
-    }
-
-    if (opts == EmitElemOption::IncDec) {
-        if (!emit1(JSOP_CHECKOBJCOERCIBLE)) {             // OBJ
-            return false;
-        }
-    } else if (opts == EmitElemOption::Call) {
-        if (!emit1(JSOP_DUP)) {                           // OBJ OBJ
-            return false;
-        }
-    }
-
-    if (!emitTree(&elem->key())) {                        // OBJ? OBJ KEY
-        return false;
-    }
-
-    if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
-        if (!emit1(JSOP_TOID)) {                          // OBJ KEY
-            return false;
-        }
-    }
-    return true;
-}
-
-bool
-BytecodeEmitter::emitSuperElemOperands(PropertyByValue* elem, EmitElemOption opts)
-{
-    MOZ_ASSERT(elem->isSuper());
-
-    if (!emitGetThisForSuperBase(&elem->expression().as<UnaryNode>())) {
-        return false;                               // THIS
-    }
-
-    if (opts == EmitElemOption::Call) {
-        // We need a second |this| that will be consumed during computation of
-        // the property value. (The original |this| is passed to the call.)
-        if (!emit1(JSOP_DUP)) {                     // THIS THIS
-            return false;
-        }
-    }
-
-    if (!emitTree(&elem->key())) {                  // THIS? THIS KEY
-        return false;
-    }
-
-    // We need to convert the key to an object id first, so that we do not do
-    // it inside both the GETELEM and the SETELEM.
-    if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
-        if (!emit1(JSOP_TOID)) {                    // THIS? THIS KEY
-            return false;
-        }
-    }
-
-    if (!emit1(JSOP_SUPERBASE)) {                   // THIS? THIS KEY SUPERBASE
-        return false;
-    }
-
-    return true;
-}
-
-bool
 BytecodeEmitter::emitElemOpBase(JSOp op)
 {
     if (!emit1(op)) {
         return false;
     }
 
     checkTypeSet(op);
     return true;
 }
 
 bool
-BytecodeEmitter::emitElemOp(PropertyByValue* elem, JSOp op)
-{
-    MOZ_ASSERT(op == JSOP_GETELEM ||
-               op == JSOP_CALLELEM ||
-               op == JSOP_DELELEM ||
-               op == JSOP_STRICTDELELEM);
-
-    EmitElemOption opts = op == JSOP_CALLELEM ? EmitElemOption::Call : EmitElemOption::Get;
-
-    if (!emitElemOperands(elem, opts)) {                  // OBJ? OBJ KEY
-        return false;
-    }
-    if (!emitElemOpBase(op)) {                            // OBJ? ELEM
-        return false;
-    }
-    return true;
-}
-
-bool
-BytecodeEmitter::emitSuperGetElem(PropertyByValue* elem, bool isCall)
-{
-    EmitElemOption opts = isCall ? EmitElemOption::Call : EmitElemOption::Get;
-
-    if (!emitSuperElemOperands(elem, opts)) {       // THIS? THIS KEY SUPERBASE
-        return false;
-    }
-    if (!emitElemOpBase(JSOP_GETELEM_SUPER)) {      // THIS? VALUE
-        return false;
-    }
-
-    if (isCall) {
-        if (!emit1(JSOP_SWAP)) {                    // VALUE THIS
-            return false;
-        }
+BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe)
+{
+    if (isSuper) {
+        if (!eoe.prepareForObj()) {                       //
+            return false;
+        }
+        UnaryNode* base = &elem->expression().as<UnaryNode>();
+        if (!emitGetThisForSuperBase(base)) {             // THIS
+            return false;
+        }
+        if (!eoe.prepareForKey()) {                       // THIS
+            return false;
+        }
+        if (!emitTree(&elem->key())) {                    // THIS KEY
+            return false;
+        }
+
+        return true;
+    }
+
+    if (!eoe.prepareForObj()) {                           //
+        return false;
+    }
+    if (!emitTree(&elem->expression())) {                 // OBJ
+        return false;
+    }
+    if (!eoe.prepareForKey()) {                           // OBJ? OBJ
+        return false;
+    }
+    if (!emitTree(&elem->key())) {                        // OBJ? OBJ KEY
+        return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitElemIncDec(UnaryNode* incDec)
 {
-    PropertyByValue* elem = &incDec->kid()->as<PropertyByValue>();
-    bool isSuper = elem->isSuper();
-
-    // We need to convert the key to an object id first, so that we do not do
-    // it inside both the GETELEM and the SETELEM. This is done by
-    // emit(Super)ElemOperands.
-    if (isSuper) {
-        if (!emitSuperElemOperands(elem, EmitElemOption::IncDec)) {
-            return false;                                 // THIS KEY OBJ
-        }
-    } else {
-        if (!emitElemOperands(elem, EmitElemOption::IncDec)) {
-            return false;                                 // OBJ KEY
-        }
-    }
-
-    bool post;
-    JSOp binop = GetIncDecInfo(incDec->getKind(), &post);
-
-    JSOp getOp;
-    if (isSuper) {
-        // There's no such thing as JSOP_DUP3, so we have to be creative.
-        // Note that pushing things again is no fewer JSOps.
-        if (!emitDupAt(2)) {                            // THIS KEY OBJ THIS
-            return false;
-        }
-        if (!emitDupAt(2)) {                            // THIS KEY OBJ THIS KEY
-            return false;
-        }
-        if (!emitDupAt(2)) {                            // THIS KEY OBJ THIS KEY OBJ
-            return false;
-        }
-        getOp = JSOP_GETELEM_SUPER;
-    } else {
-                                                        // OBJ KEY
-        if (!emit1(JSOP_DUP2)) {                        // OBJ KEY OBJ KEY
-            return false;
-        }
-        getOp = JSOP_GETELEM;
-    }
-    if (!emitElemOpBase(getOp)) {                       // OBJ KEY ELEM
-        return false;
-    }
-    if (!emit1(JSOP_POS)) {                             // OBJ KEY N
-        return false;
-    }
-    if (post) {
-        if (!emit1(JSOP_DUP)) {                         // OBJ KEY N N
-            return false;
-        }
-        if (!emit2(JSOP_UNPICK, 3 + isSuper)) {         // N OBJ KEY N
-            return false;
-        }
-    }
-    if (!emit1(JSOP_ONE)) {                             // N? OBJ KEY N 1
-        return false;
-    }
-    if (!emit1(binop)) {                                // N? OBJ KEY N+1
-        return false;
-    }
-
-    JSOp setOp = isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
-                         : (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
-    if (!emitElemOpBase(setOp)) {                       // N? N+1
-        return false;
-    }
-    if (post && !emit1(JSOP_POP)) {                     // RESULT
-        return false;
+    PropertyByValue* elemExpr = &incDec->kid()->as<PropertyByValue>();
+    bool isSuper = elemExpr->isSuper();
+    ParseNodeKind kind = incDec->getKind();
+    ElemOpEmitter eoe(this,
+                      kind == ParseNodeKind::PostIncrement ? ElemOpEmitter::Kind::PostIncrement
+                      : kind == ParseNodeKind::PreIncrement ? ElemOpEmitter::Kind::PreIncrement
+                      : kind == ParseNodeKind::PostDecrement ? ElemOpEmitter::Kind::PostDecrement
+                      : ElemOpEmitter::Kind::PreDecrement,
+                      isSuper
+                      ? ElemOpEmitter::ObjKind::Super
+                      : ElemOpEmitter::ObjKind::Other);
+    if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) {     // [Super]
+        //                                                // THIS KEY
+        //                                                // [Other]
+        //                                                // OBJ KEY
+        return false;
+    }
+    if (!eoe.emitIncDec()) {                              // RESULT
+         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitCallIncDec(UnaryNode* incDec)
 {
@@ -2900,27 +2784,40 @@ BytecodeEmitter::emitDestructuringLHSRef
             //                                            // OBJ
             return false;
         }
         break;
       }
 
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &target->as<PropertyByValue>();
-        if (elem->isSuper()) {
-            if (!emitSuperElemOperands(elem, EmitElemOption::Ref)) {
-                return false;                             // THIS KEY OBJ
-            }
+        bool isSuper = elem->isSuper();
+        ElemOpEmitter eoe(this,
+                          ElemOpEmitter::Kind::SimpleAssignment,
+                          isSuper
+                          ? ElemOpEmitter::ObjKind::Super
+                          : ElemOpEmitter::ObjKind::Other);
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
+            //                                            // THIS KEY
+            //                                            // [Other]
+            //                                            // OBJ KEY
+            return false;
+        }
+        if (isSuper) {
+            // SUPERBASE is pushed onto KEY in eoe.prepareForRhs below.
             *emitted = 3;
         } else {
-            if (!emitElemOperands(elem, EmitElemOption::Ref)) {
-                return false;                             // OBJ KEY
-            }
             *emitted = 2;
         }
+        if (!eoe.prepareForRhs()) {                       // [Super]
+            //                                            // THIS KEY SUPERBASE
+            //                                            // [Other]
+            //                                            // OBJ KEY
+            return false;
+        }
         break;
       }
 
       case ParseNodeKind::Call:
         MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget "
                                "rejects function calls as assignment "
                                "targets in destructuring assignments");
         break;
@@ -3035,29 +2932,32 @@ BytecodeEmitter::emitSetOrInitializeDest
             if (!poe.emitAssignment(prop->key().atom())) {
                 return false;                             // VAL
             }
             break;
           }
 
           case ParseNodeKind::Elem: {
             // The reference is already pushed by emitDestructuringLHSRef.
+            //                                            // [Super]
+            //                                            // THIS KEY SUPERBASE VAL
+            //                                            // [Other]
+            //                                            // OBJ KEY VAL
             PropertyByValue* elem = &target->as<PropertyByValue>();
-            if (elem->isSuper()) {                        // THIS KEY OBJ VAL
-                JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER;
-                // emitDestructuringLHSRef already did emitSuperElemOperands
-                // part of emitSuperElemOp.  Perform remaining part here.
-                if (!emitElemOpBase(setOp)) {             // VAL
-                    return false;
-                }
-            } else {                                      // OBJ KEY VAL
-                JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
-                if (!emitElemOpBase(setOp)) {             // VAL
-                    return false;
-                }
+            bool isSuper = elem->isSuper();
+            ElemOpEmitter eoe(this,
+                              ElemOpEmitter::Kind::SimpleAssignment,
+                              isSuper
+                              ? ElemOpEmitter::ObjKind::Super
+                              : ElemOpEmitter::ObjKind::Other);
+            if (!eoe.skipObjAndKeyAndRhs()) {
+                return false;
+            }
+            if (!eoe.emitAssignment()) {                  // VAL
+                return false;
             }
             break;
           }
 
           case ParseNodeKind::Call:
             MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget "
                                    "rejects function calls as assignment "
                                    "targets in destructuring assignments");
@@ -4293,16 +4193,17 @@ BytecodeEmitter::emitAssignment(ParseNod
 
             return true;
         };
 
         return emitSetName(lhs, emitRhs);
     }
 
     Maybe<PropOpEmitter> poe;
+    Maybe<ElemOpEmitter> eoe;
 
     // Deal with non-name assignments.
     uint8_t offset = 1;
 
     switch (lhs->getKind()) {
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &lhs->as<PropertyAccess>();
         bool isSuper = prop->isSuper();
@@ -4328,26 +4229,34 @@ BytecodeEmitter::emitAssignment(ParseNod
                 return false;
             }
             offset += 1;
         }
         break;
       }
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &lhs->as<PropertyByValue>();
-        EmitElemOption opt = isCompound ? EmitElemOption::CompoundAssign : EmitElemOption::Get;
-        if (elem->isSuper()) {
-            if (!emitSuperElemOperands(elem, opt)) {      // THIS KEY OBJ
-                return false;
-            }
+        bool isSuper = elem->isSuper();
+        eoe.emplace(this,
+                    isCompound
+                    ? ElemOpEmitter::Kind::CompoundAssignment
+                    : ElemOpEmitter::Kind::SimpleAssignment,
+                    isSuper
+                    ? ElemOpEmitter::ObjKind::Super
+                    : ElemOpEmitter::ObjKind::Other);
+        if (!emitElemObjAndKey(elem, isSuper, *eoe)) {    // [Super]
+            //                                            // THIS KEY
+            //                                            // [Other]
+            //                                            // OBJ KEY
+            return false;
+        }
+        if (isSuper) {
+            // SUPERBASE is pushed onto KEY in eoe->emitGet below.
             offset += 3;
         } else {
-            if (!emitElemOperands(elem, opt)) {           // OBJ KEY
-                return false;
-            }
             offset += 2;
         }
         break;
       }
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
         break;
       case ParseNodeKind::Call:
@@ -4379,39 +4288,17 @@ BytecodeEmitter::emitAssignment(ParseNod
                 //                                        // THIS SUPERBASE PROP
                 //                                        // [Other]
                 //                                        // OBJ PROP
                 return false;
             }
             break;
           }
           case ParseNodeKind::Elem: {
-            JSOp elemOp;
-            PropertyByValue* elem = &lhs->as<PropertyByValue>();
-            if (elem->isSuper()) {
-                if (!emitDupAt(2)) {                      // THIS KEY OBJ THIS
-                    return false;
-                }
-                if (!emitDupAt(2)) {                      // THIS KEY OBJ THIS KEY
-                    return false;
-                }
-                if (!emitDupAt(2)) {                      // THIS KEY OBJ THIS KEY OBJ
-                    return false;
-                }
-                elemOp = JSOP_GETELEM_SUPER;
-            } else {
-                if (!emit1(JSOP_DUP2)) {                  // OBJ KEY OBJ KEY
-                    return false;
-                }
-                elemOp = JSOP_GETELEM;
-            }
-            if (!emitElemOpBase(elemOp)) {                // [Super]
-                //                                        // THIS KEY OBJ ELEM
-                //                                        // [Other]
-                //                                        // OBJ KEY ELEM
+            if (!eoe->emitGet()) {                        // KEY THIS OBJ ELEM
                 return false;
             }
             break;
           }
           case ParseNodeKind::Call:
             // We just emitted a JSOP_THROWMSG and popped the call's return
             // value.  Push a random value to make sure the stack depth is
             // correct.
@@ -4431,16 +4318,28 @@ BytecodeEmitter::emitAssignment(ParseNod
             //                                            // OBJ
             //                                            // [Compound,Super]
             //                                            // THIS SUPERBASE PROP
             //                                            // [Compound,Other]
             //                                            // OBJ PROP
             return false;
         }
         break;
+      case ParseNodeKind::Elem:
+        if (!eoe->prepareForRhs()) {                      // [Simple,Super]
+            //                                            // THIS KEY SUPERBASE
+            //                                            // [Simple,Other]
+            //                                            // OBJ KEY
+            //                                            // [Compound,Super]
+            //                                            // THIS KEY SUPERBASE ELEM
+            //                                            // [Compound,Other]
+            //                                            // OBJ KEY ELEM
+            return false;
+        }
+        break;
       default:
         break;
     }
 
     if (!EmitAssignmentRhs(this, rhs, offset)) {          // ... VAL? RHS
         return false;
     }
 
@@ -4464,22 +4363,21 @@ BytecodeEmitter::emitAssignment(ParseNod
 
         poe.reset();
         break;
       }
       case ParseNodeKind::Call:
         // We threw above, so nothing to do here.
         break;
       case ParseNodeKind::Elem: {
-        JSOp setOp = lhs->as<PropertyByValue>().isSuper() ?
-                       sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER :
-                       sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
-        if (!emit1(setOp)) {                              // VAL
-            return false;
-        }
+        if (!eoe->emitAssignment()) {                     // VAL
+            return false;
+        }
+
+        eoe.reset();
         break;
       }
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
         if (!emitDestructuringOps(&lhs->as<ListNode>(), DestructuringAssignment)) {
             return false;
         }
         break;
@@ -6805,50 +6703,55 @@ BytecodeEmitter::emitDeleteProperty(Unar
 }
 
 bool
 BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteElem));
 
     PropertyByValue* elemExpr = &deleteNode->kid()->as<PropertyByValue>();
-
-    if (elemExpr->isSuper()) {
+    bool isSuper = elemExpr->isSuper();
+    ElemOpEmitter eoe(this,
+                      ElemOpEmitter::Kind::Delete,
+                      isSuper
+                      ? ElemOpEmitter::ObjKind::Super
+                      : ElemOpEmitter::ObjKind::Other);
+    if (isSuper) {
         // The expression |delete super[foo];| has to evaluate |super[foo]|,
         // which could throw if |this| hasn't yet been set by a |super(...)|
         // call, or trigger side-effects when evaluating ToPropertyKey(foo),
         // or also throw when the super-base is not an object, before throwing
         // a ReferenceError for attempting to delete a super-reference.
-        if (!emitGetThisForSuperBase(&elemExpr->expression().as<UnaryNode>())) {
-            return false;
-        }
-
-        if (!emitTree(&elemExpr->key())) {
-            return false;
-        }
-        if (!emit1(JSOP_TOID)) {
-            return false;
-        }
-
-        if (!emit1(JSOP_SUPERBASE)) {
-            return false;
-        }
-
-        // Unconditionally throw when attempting to delete a super-reference.
-        if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
-            return false;
-        }
-
-        // Another wrinkle: Balance the stack from the emitter's point of view.
-        // Execution will not reach here, as the last bytecode threw.
-        return emitPopN(2);
-    }
-
-    JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
-    return emitElemOp(elemExpr, delOp);
+        if (!eoe.prepareForObj()) {                       //
+            return false;
+        }
+
+        UnaryNode* base = &elemExpr->expression().as<UnaryNode>();
+        if (!emitGetThisForSuperBase(base)) {             // THIS
+            return false;
+        }
+        if (!eoe.prepareForKey()) {                       // THIS
+            return false;
+        }
+        if (!emitTree(&elemExpr->key())) {                // THIS KEY
+            return false;
+        }
+    } else {
+        if (!emitElemObjAndKey(elemExpr, false, eoe)) {   // OBJ KEY
+            return false;
+        }
+    }
+    if (!eoe.emitDelete()) {                              // [Super]
+        //                                                // THIS
+        //                                                // [Other]
+        //                                                // SUCCEEDED
+        return false;
+    }
+
+    return true;
 }
 
 bool
 BytecodeEmitter::emitDeleteExpression(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteExpr));
 
     ParseNode* expression = deleteNode->kid();
@@ -7231,35 +7134,41 @@ BytecodeEmitter::emitCalleeAndThis(Parse
             //                                            // [!needsThis]
             //                                            // CALLEE THIS
             return false;
         }
 
         break;
       }
       case ParseNodeKind::Elem: {
+        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyByValue* elem = &callee->as<PropertyByValue>();
-        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
-        if (elem->isSuper()) {
-            if (!emitSuperGetElem(elem, isCall)) {        // CALLEE THIS?
-                return false;
-            }
-        } else {
-            if (isCall) {
-                if (!emitElemOp(elem, JSOP_CALLELEM)) {   // THIS CALLEE
-                    return false;
-                }
-                if (!emit1(JSOP_SWAP)) {                  // CALLEE THIS
-                    return false;
-                }
-            } else {
-                if (!emitElemOp(elem, JSOP_GETELEM)) {    // CALLEE
-                    return false;
-                }
-            }
+        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
+            //                                            // [needsThis,Other]
+            //                                            // OBJ KEY
+            //                                            // [!needsThis,Super]
+            //                                            // THIS THIS KEY
+            //                                            // [!needsThis,Other]
+            //                                            // OBJ OBJ KEY
+            return false;
+        }
+        if (!eoe.emitGet()) {                             // [needsThis]
+            //                                            // CALLEE
+            //                                            // [!needsThis]
+            //                                            // 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
@@ -9252,25 +9161,32 @@ BytecodeEmitter::emitTree(ParseNode* pn,
         if (!poe.emitGet(prop->key().atom())) {           // PROP
             return false;
         }
         break;
       }
 
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &pn->as<PropertyByValue>();
-        if (elem->isSuper()) {
-            if (!emitSuperGetElem(elem)) {
-                return false;
-            }
-        } else {
-            if (!emitElemOp(elem, JSOP_GETELEM)) {
-                return false;
-            }
-        }
+        bool isSuper = elem->isSuper();
+        ElemOpEmitter eoe(this,
+                          ElemOpEmitter::Kind::Get,
+                          isSuper
+                          ? ElemOpEmitter::ObjKind::Super
+                          : ElemOpEmitter::ObjKind::Other);
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
+            //                                            // THIS KEY
+            //                                            // [Other]
+            //                                            // OBJ KEY
+            return false;
+        }
+        if (!eoe.emitGet()) {                             // ELEM
+            return false;
+        }
+
         break;
       }
 
       case ParseNodeKind::New:
       case ParseNodeKind::TaggedTemplate:
       case ParseNodeKind::Call:
       case ParseNodeKind::SuperCall:
         if (!emitCallOrNew(&pn->as<BinaryNode>(), valueUsage)) {
--- 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 ElemOpEmitter;
 class EmitterScope;
 class NestableControl;
 class TDZCheckCache;
 
 struct MOZ_STACK_CLASS BytecodeEmitter
 {
     SharedContext* const sc;      /* context shared between parsing and bytecode generation */
 
@@ -655,16 +656,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitComputedPropertyName(UnaryNode* computedPropName);
 
     // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM
     // opcode onto the stack in the right order. In the case of SETELEM, the
     // value to be assigned must already be pushed.
     enum class EmitElemOption { Get, Call, IncDec, CompoundAssign, Ref };
     MOZ_MUST_USE bool emitElemOperands(PropertyByValue* elem, EmitElemOption opts);
 
+    MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe);
     MOZ_MUST_USE bool emitElemOpBase(JSOp op);
     MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op);
     MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitCatch(BinaryNode* catchClause);
     MOZ_MUST_USE bool emitIf(TernaryNode* ifNode);
     MOZ_MUST_USE bool emitWith(BinaryNode* withNode);
 
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -0,0 +1,288 @@
+/* -*- 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/ElemOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
+  : bce_(bce),
+    kind_(kind),
+    objKind_(objKind)
+{}
+
+bool
+ElemOpEmitter::prepareForObj()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+#ifdef DEBUG
+    state_ = State::Obj;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::prepareForKey()
+{
+    MOZ_ASSERT(state_ == State::Obj);
+
+    if (!isSuper() && isIncDec()) {
+        if (!bce_->emit1(JSOP_CHECKOBJCOERCIBLE)) {   // OBJ
+            return false;
+        }
+    }
+    if (isCall()) {
+        if (!bce_->emit1(JSOP_DUP)) {                 // [Super]
+            //                                        // THIS THIS
+            //                                        // [Other]
+            //                                        // OBJ OBJ
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Key;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::emitGet()
+{
+    MOZ_ASSERT(state_ == State::Key);
+
+    if (isIncDec() || isCompoundAssignment()) {
+        if (!bce_->emit1(JSOP_TOID)) {                // [Super]
+            //                                        // THIS KEY
+            //                                        // [Other]
+            //                                        // OBJ KEY
+            return false;
+        }
+    }
+    if (isSuper()) {
+        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS? THIS KEY SUPERBASE
+            return false;
+        }
+    }
+    if (isIncDec() || isCompoundAssignment()) {
+        if (isSuper()) {
+            // There's no such thing as JSOP_DUP3, so we have to be creative.
+            // Note that pushing things again is no fewer JSOps.
+            if (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS
+                return false;
+            }
+            if (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS KEY
+                return false;
+            }
+            if (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS KEY SUPERBASE
+                return false;
+            }
+        } else {
+            if (!bce_->emit1(JSOP_DUP2)) {            // OBJ KEY OBJ KEY
+                return false;
+            }
+        }
+    }
+
+    JSOp op;
+    if (isSuper()) {
+        op = JSOP_GETELEM_SUPER;
+    } else if (isCall()) {
+        op = JSOP_CALLELEM;
+    } else {
+        op = JSOP_GETELEM;
+    }
+    if (!bce_->emitElemOpBase(op)) {                  // [Get]
+        //                                            // ELEM
+        //                                            // [Call]
+        //                                            // THIS ELEM
+        //                                            // [Inc/Dec/Assignment,
+        //                                            //  Super]
+        //                                            // THIS KEY SUPERBASE ELEM
+        //                                            // [Inc/Dec/Assignment,
+        //                                            //  Other]
+        //                                            // OBJ KEY ELEM
+        return false;
+    }
+    if (isCall()) {
+        if (!bce_->emit1(JSOP_SWAP)) {                // ELEM THIS
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Get;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::prepareForRhs()
+{
+    MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+    MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key);
+    MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
+
+    if (isSimpleAssignment()) {
+        // For CompoundAssignment, SUPERBASE is already emitted by emitGet.
+        if (isSuper()) {
+            if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS KEY SUPERBASE
+                return false;
+            }
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Rhs;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::skipObjAndKeyAndRhs()
+{
+    MOZ_ASSERT(state_ == State::Start);
+    MOZ_ASSERT(isSimpleAssignment());
+
+#ifdef DEBUG
+    state_ = State::Rhs;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::emitDelete()
+{
+    MOZ_ASSERT(state_ == State::Key);
+    MOZ_ASSERT(isDelete());
+
+    if (isSuper()) {
+        if (!bce_->emit1(JSOP_TOID)) {                // THIS KEY
+            return false;
+        }
+        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS KEY SUPERBASE
+            return false;
+        }
+
+        // Unconditionally throw when attempting to delete a super-reference.
+        if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
+            return false;                             // THIS KEY SUPERBASE
+        }
+
+        // Another wrinkle: Balance the stack from the emitter's point of view.
+        // Execution will not reach here, as the last bytecode threw.
+        if (!bce_->emitPopN(2)) {                     // THIS
+            return false;
+        }
+    } else {
+        JSOp op = bce_->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
+        if (!bce_->emitElemOpBase(op)){              // SUCCEEDED
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Delete;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::emitAssignment()
+{
+    MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+    MOZ_ASSERT(state_ == State::Rhs);
+
+    JSOp setOp = isSuper()
+                 ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER
+                 : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
+    if (!bce_->emitElemOpBase(setOp)) {               // ELEM
+        return false;
+    }
+
+#ifdef DEBUG
+    state_ = State::Assignment;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::emitIncDec()
+{
+    MOZ_ASSERT(state_ == State::Key);
+    MOZ_ASSERT(isIncDec());
+
+    if (!emitGet()) {                                 // ... ELEM
+        return false;
+    }
+
+    MOZ_ASSERT(state_ == State::Get);
+
+    JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+    if (!bce_->emit1(JSOP_POS)) {                     // ... N
+        return false;
+    }
+    if (isPostIncDec()) {
+        if (!bce_->emit1(JSOP_DUP)) {                 // ... N? N
+            return false;
+        }
+    }
+    if (!bce_->emit1(JSOP_ONE)) {                     // ... N? N 1
+        return false;
+    }
+    if (!bce_->emit1(binOp)) {                        // ... N? N+1
+        return false;
+    }
+    if (isPostIncDec()) {
+        if (isSuper()) {                              // THIS KEY OBJ N N+1
+            if (!bce_->emit2(JSOP_PICK, 4)) {         // KEY SUPERBASE N N+1 THIS
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 4)) {         // SUPERBASE N N+1 THIS KEY
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 4)) {         // N N+1 THIS KEY SUPERBASE
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 3)) {         // N THIS KEY SUPERBASE N+1
+                return false;
+            }
+        } else {                                      // OBJ KEY N N+1
+            if (!bce_->emit2(JSOP_PICK, 3)) {         // KEY N N+1 OBJ
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 3)) {         // N N+1 OBJ KEY
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 2)) {         // N OBJ KEY N+1
+                return false;
+            }
+        }
+    }
+
+    JSOp setOp = isSuper()
+                 ? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
+                 : (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
+    if (!bce_->emitElemOpBase(setOp)) {               // N? N+1
+        return false;
+    }
+    if (isPostIncDec()) {
+        if (!bce_->emit1(JSOP_POP)) {                 // N
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::IncDec;
+#endif
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ElemOpEmitter.h
@@ -0,0 +1,278 @@
+/* -*- 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_ElemOpEmitter_h
+#define frontend_ElemOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for element operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `obj[key];`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Get,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//
+//   `super[key];`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Get,
+//                       ElemOpEmitter::ObjKind::Super);
+//     eoe.prepareForObj();
+//     emit(this_for_super);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//
+//   `obj[key]();`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Call,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//     emit_call_here();
+//
+//   `new obj[key]();`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Call,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//     emit_call_here();
+//
+//   `delete obj[key];`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Delete,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitDelete();
+//
+//   `delete super[key];`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Delete,
+//                       ElemOpEmitter::ObjKind::Super);
+//     eoe.prepareForObj();
+//     emit(this_for_super);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitDelete();
+//
+//   `obj[key]++;`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::PostIncrement,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitIncDec();
+//
+//   `obj[key] = value;`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::SimpleAssignment,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.prepareForRhs();
+//     emit(value);
+//     eoe.emitAssignment();
+//
+//   `obj[key] += value;`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::CompoundAssignment,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//     eoe.prepareForRhs();
+//     emit(value);
+//     emit_add_op_here();
+//     eoe.emitAssignment();
+//
+class MOZ_STACK_CLASS ElemOpEmitter
+{
+  public:
+    enum class Kind {
+        Get,
+        Call,
+        Set,
+        Delete,
+        PostIncrement,
+        PreIncrement,
+        PostDecrement,
+        PreDecrement,
+        SimpleAssignment,
+        CompoundAssignment
+    };
+    enum class ObjKind {
+        Super,
+        Other
+    };
+
+  private:
+    BytecodeEmitter* bce_;
+
+    Kind kind_;
+    ObjKind objKind_;
+
+#ifdef DEBUG
+    // The state of this emitter.
+    //
+    //             skipObjAndKeyAndRhs
+    //           +------------------------------------------------+
+    //           |                                                |
+    // +-------+ | prepareForObj +-----+ prepareForKey +-----+    |
+    // | Start |-+-------------->| Obj |-------------->| Key |-+  |
+    // +-------+                 +-----+               +-----+ |  |
+    //                                                         |  |
+    // +-------------------------------------------------------+  |
+    // |                                                          |
+    // |                                                          |
+    // |                                                          |
+    // | [Get]                                                    |
+    // | [Call]                                                   |
+    // |   emitGet +-----+                                        |
+    // +---------->| Get |                                        |
+    // |           +-----+                                        |
+    // |                                                          |
+    // | [Delete]                                                 |
+    // |   emitDelete +--------+                                  |
+    // +------------->| Delete |                                  |
+    // |              +--------+                                  |
+    // |                                                          |
+    // | [PostIncrement]                                          |
+    // | [PreIncrement]                                           |
+    // | [PostDecrement]                                          |
+    // | [PreDecrement]                                           |
+    // |   emitIncDec +--------+                                  |
+    // +------------->| IncDec |                                  |
+    // |              +--------+                                  |
+    // |                                      +-------------------+
+    // | [SimpleAssignment]                   |
+    // |                        prepareForRhs v  +-----+
+    // +--------------------->+-------------->+->| Rhs |-+
+    // |                      ^                  +-----+ |
+    // |                      |                          |
+    // |                      |            +-------------+
+    // | [CompoundAssignment] |            |
+    // |   emitGet +-----+    |            | emitAssignment +------------+
+    // +---------->| Get |----+            +--------------->| Assignment |
+    //             +-----+                                  +------------+
+    enum class State {
+        // The initial state.
+        Start,
+
+        // After calling prepareForObj.
+        Obj,
+
+        // After calling emitKey.
+        Key,
+
+        // After calling emitGet.
+        Get,
+
+        // After calling emitDelete.
+        Delete,
+
+        // After calling emitIncDec.
+        IncDec,
+
+        // After calling prepareForRhs or skipObjAndKeyAndRhs.
+        Rhs,
+
+        // After calling emitAssignment.
+        Assignment,
+    };
+    State state_ = State::Start;
+#endif
+
+  public:
+    ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
+
+  private:
+    MOZ_MUST_USE bool isCall() const {
+        return kind_ == Kind::Call;
+    }
+
+    MOZ_MUST_USE bool isSimpleAssignment() const {
+        return kind_ == Kind::SimpleAssignment;
+    }
+
+    MOZ_MUST_USE bool isDelete() const {
+        return kind_ == Kind::Delete;
+    }
+
+    MOZ_MUST_USE bool isCompoundAssignment() const {
+        return kind_ == Kind::CompoundAssignment;
+    }
+
+    MOZ_MUST_USE bool isIncDec() const {
+        return isPostIncDec() || isPreIncDec();
+    }
+
+    MOZ_MUST_USE bool isPostIncDec() const {
+        return kind_ == Kind::PostIncrement ||
+               kind_ == Kind::PostDecrement;
+    }
+
+    MOZ_MUST_USE bool isPreIncDec() const {
+        return kind_ == Kind::PreIncrement ||
+               kind_ == Kind::PreDecrement;
+    }
+
+    MOZ_MUST_USE bool isInc() const {
+        return kind_ == Kind::PostIncrement ||
+               kind_ == Kind::PreIncrement;
+    }
+
+    MOZ_MUST_USE bool isSuper() const {
+        return objKind_ == ObjKind::Super;
+    }
+
+  public:
+    MOZ_MUST_USE bool prepareForObj();
+    MOZ_MUST_USE bool prepareForKey();
+
+    MOZ_MUST_USE bool emitGet();
+
+    MOZ_MUST_USE bool prepareForRhs();
+    MOZ_MUST_USE bool skipObjAndKeyAndRhs();
+
+    MOZ_MUST_USE bool emitDelete();
+
+    MOZ_MUST_USE bool emitAssignment();
+
+    MOZ_MUST_USE bool emitIncDec();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ElemOpEmitter_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -223,16 +223,17 @@ UNIFIED_SOURCES += [
     'ds/Bitmap.cpp',
     'ds/LifoAlloc.cpp',
     'ds/MemoryProtectionExceptionHandler.cpp',
     'frontend/BytecodeCompiler.cpp',
     'frontend/BytecodeControlStructures.cpp',
     'frontend/BytecodeEmitter.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',
     'frontend/ForOfLoopControl.cpp',
     'frontend/IfEmitter.cpp',
     'frontend/JumpList.cpp',