Bug 1466000 - Part 7: Add PropOpEmitter. r=efaust
authorTooru Fujisawa <arai_a@mac.com>
Tue, 09 Oct 2018 21:23:12 +0900
changeset 496204 e29ced1ead38118a28a170f99975936caafae178
parent 496203 cad2c1babaf228066ced4fd7c94017026a1d482c
child 496205 61f8a1e5fa5d4ecb83ff2c82e24831550117c5e3
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 7: Add PropOpEmitter. r=efaust
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/PropOpEmitter.cpp
js/src/frontend/PropOpEmitter.h
js/src/moz.build
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -29,16 +29,17 @@
 #include "frontend/DoWhileEmitter.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"
 #include "frontend/SwitchEmitter.h"
 #include "frontend/TDZCheckCache.h"
 #include "frontend/TryEmitter.h"
 #include "frontend/WhileEmitter.h"
 #include "js/CompileOptions.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Debugger.h"
 #include "vm/GeneratorObject.h"
@@ -875,17 +876,16 @@ BytecodeEmitter::emitIndexOp(JSOp op, ui
     updateDepth(offset);
     return true;
 }
 
 bool
 BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
 {
     MOZ_ASSERT(atom);
-    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
 
     // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
     // JSOP_GETNAME etc, to bypass |with| objects on the scope chain.
     // It's safe to emit .this lookups though because |with| objects skip
     // those.
     MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME,
                   atom != cx->names().dotGenerator);
 
@@ -894,17 +894,25 @@ BytecodeEmitter::emitAtomOp(JSAtom* atom
         op = JSOP_LENGTH;
     }
 
     uint32_t index;
     if (!makeAtomIndex(atom, &index)) {
         return false;
     }
 
-    return emitIndexOp(op, index);
+    return emitAtomOp(index, op);
+}
+
+bool
+BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op)
+{
+    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
+
+    return emitIndexOp(op, atomIndex);
 }
 
 bool
 BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op)
 {
     MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
     MOZ_ASSERT(index < scopeList.length());
     return emitIndex32(op, index);
@@ -2037,152 +2045,44 @@ BytecodeEmitter::emitPropLHS(PropertyAcc
             break;
         }
         pndot = &pnup->as<PropertyAccess>();
     }
     return true;
 }
 
 bool
-BytecodeEmitter::emitSuperPropLHS(UnaryNode* superBase, bool isCall)
-{
-    if (!emitGetThisForSuperBase(superBase)) {            // THIS
-        return false;
-    }
-    if (isCall && !emit1(JSOP_DUP)) {                     // THIS? THIS
-        return false;
-    }
-    if (!emit1(JSOP_SUPERBASE)) {                         // THIS? THIS SUPERBASE
-        return false;
-    }
-    return true;
-}
-
-bool
-BytecodeEmitter::emitPropOp(PropertyAccess* prop, JSOp op)
-{
-    if (!emitPropLHS(prop)) {
-        return false;
-    }
-
-    if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) {
-        return false;
-    }
-
-    JSAtom* atom = prop->key().atom();
-    if (!emitAtomOp(atom, op)) {
-        return false;
-    }
-
-    if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) {
-        return false;
-    }
-
-    return true;
-}
-
-bool
-BytecodeEmitter::emitSuperGetProp(PropertyAccess* prop, bool isCall)
-{
-    UnaryNode* base = &prop->expression().as<UnaryNode>();
-    if (!emitSuperPropLHS(base, isCall)) {                // THIS? THIS SUPERBASE
-        return false;
-    }
-
-    JSAtom* atom = prop->key().atom();
-    if (!emitAtomOp(atom, JSOP_GETPROP_SUPER)) {          // THIS? PROP
-        return false;
-    }
-
-    if (isCall) {
-        if (!emit1(JSOP_SWAP))                            // PROP THIS
-            return false;
-    }
-
-    return true;
-}
-
-bool
 BytecodeEmitter::emitPropIncDec(UnaryNode* incDec)
 {
     PropertyAccess* prop = &incDec->kid()->as<PropertyAccess>();
-
-    bool post;
     bool isSuper = prop->isSuper();
-    JSOp binop = GetIncDecInfo(incDec->getKind(), &post);
-
+    ParseNodeKind kind = incDec->getKind();
+    PropOpEmitter poe(this,
+                      kind == ParseNodeKind::PostIncrement ? PropOpEmitter::Kind::PostIncrement
+                      : kind == ParseNodeKind::PreIncrement ? PropOpEmitter::Kind::PreIncrement
+                      : kind == ParseNodeKind::PostDecrement ? PropOpEmitter::Kind::PostDecrement
+                      : PropOpEmitter::Kind::PreDecrement,
+                      isSuper
+                      ? PropOpEmitter::ObjKind::Super
+                      : PropOpEmitter::ObjKind::Other);
+    if (!poe.prepareForObj()) {
+        return false;
+    }
     if (isSuper) {
         UnaryNode* base = &prop->expression().as<UnaryNode>();
-        if (!emitSuperPropLHS(base)) {              // THIS OBJ
-            return false;
-        }
-        if (!emit1(JSOP_DUP2)) {                    // THIS OBJ THIS OBJ
+        if (!emitGetThisForSuperBase(base)) {       // THIS
             return false;
         }
     } else {
         if (!emitPropLHS(prop)) {
             return false;                           // OBJ
         }
-        if (!emit1(JSOP_DUP)) {                     // OBJ OBJ
-            return false;
-        }
-    }
-    JSAtom* atom = prop->key().atom();
-    if (!emitAtomOp(atom, isSuper ? JSOP_GETPROP_SUPER : JSOP_GETPROP)) {
-        return false;                               // THIS? OBJ V
-    }
-    if (!emit1(JSOP_POS)) {                         // ... N
-        return false;
-    }
-    if (post) {
-        if (!emit1(JSOP_DUP)) {                     // ... N N
-            return false;
-        }
-    }
-    if (!emit1(JSOP_ONE)) {                         // ... N? N 1
-        return false;
-    }
-    if (!emit1(binop)) {                            // ... N? N+1
-        return false;
-    }
-
-    if (post) {
-        if (isSuper) {                              // THIS OBJ N N+1
-            if (!emit2(JSOP_PICK, 3)) {             // OBJ N N+1 THIS
-                return false;
-            }
-            if (!emit1(JSOP_SWAP)) {                // OBJ N THIS N+1
-                return false;
-            }
-            if (!emit2(JSOP_PICK, 3)) {             // N THIS N+1 OBJ
-                return false;
-            }
-            if (!emit1(JSOP_SWAP)) {                // N THIS OBJ N+1
-                return false;
-            }
-        } else {                                    // OBJ N N+1
-            if (!emit2(JSOP_PICK, 2)) {             // N N+1 OBJ
-                return false;
-            }
-            if (!emit1(JSOP_SWAP)) {                // N OBJ N+1
-                return false;
-
-            }
-        }
-    }
-
-    JSOp setOp = isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
-                         : sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
-    if (!emitAtomOp(atom, setOp)) {                 // N? N+1
-        return false;
-    }
-    if (post) {
-        if (!emit1(JSOP_POP)) {                     // N
-            return false;
-        }
+    }
+    if (!poe.emitIncDec(prop->key().atom())) {      // RESULT
+        return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitGetNameAtLocationForCompoundAssignment(JSAtom* name, const NameLocation& loc)
 {
@@ -2967,27 +2867,44 @@ BytecodeEmitter::emitDestructuringLHSRef
 
 #ifdef DEBUG
     int depth = stackDepth;
 #endif
 
     switch (target->getKind()) {
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &target->as<PropertyAccess>();
-        if (prop->isSuper()) {
-            if (!emitSuperPropLHS(&prop->expression().as<UnaryNode>())) {
-                return false;                             // THIS SUPERBASE
-            }
+        bool isSuper = prop->isSuper();
+        PropOpEmitter poe(this,
+                          PropOpEmitter::Kind::SimpleAssignment,
+                          isSuper
+                          ? PropOpEmitter::ObjKind::Super
+                          : PropOpEmitter::ObjKind::Other);
+        if (!poe.prepareForObj()) {
+            return false;
+        }
+        if (isSuper) {
+            UnaryNode* base = &prop->expression().as<UnaryNode>();
+            if (!emitGetThisForSuperBase(base)) {         // THIS SUPERBASE
+                return false;
+            }
+            // SUPERBASE is pushed onto THIS in poe.prepareForRhs below.
             *emitted = 2;
         } else {
             if (!emitTree(&prop->expression())) {         // OBJ
                 return false;
             }
             *emitted = 1;
         }
+        if (!poe.prepareForRhs()) {                       // [Super]
+            //                                            // THIS SUPERBASE
+            //                                            // [Other]
+            //                                            // 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
@@ -3096,25 +3013,32 @@ BytecodeEmitter::emitSetOrInitializeDest
                 break;
             }
 
             break;
           }
 
           case ParseNodeKind::Dot: {
             // The reference is already pushed by emitDestructuringLHSRef.
+            //                                            // [Super]
+            //                                            // THIS SUPERBASE VAL
+            //                                            // [Other]
+            //                                            // OBJ VAL
             PropertyAccess* prop = &target->as<PropertyAccess>();
-            JSOp setOp;
-            if (prop->isSuper()) {                        // THIS OBJ VAL
-                setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER;
-            } else {                                      // OBJ VAL
-                setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
-            }
-            if (!emitAtomOp(prop->key().atom(), setOp)) { // VAL
-                return false;
+            bool isSuper = prop->isSuper();
+            PropOpEmitter poe(this,
+                              PropOpEmitter::Kind::SimpleAssignment,
+                              isSuper
+                              ? PropOpEmitter::ObjKind::Super
+                              : PropOpEmitter::ObjKind::Other);
+            if (!poe.skipObjAndRhs()) {
+                return false;
+            }
+            if (!poe.emitAssignment(prop->key().atom())) {
+                return false;                             // VAL
             }
             break;
           }
 
           case ParseNodeKind::Elem: {
             // The reference is already pushed by emitDestructuringLHSRef.
             PropertyByValue* elem = &target->as<PropertyByValue>();
             if (elem->isSuper()) {                        // THIS KEY OBJ VAL
@@ -3139,17 +3063,17 @@ BytecodeEmitter::emitSetOrInitializeDest
                                    "targets in destructuring assignments");
             break;
 
           default:
             MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind");
         }
 
         // Pop the assigned value.
-        if (!emit1(JSOP_POP)) {
+        if (!emit1(JSOP_POP)) {                           // !STACK EMPTY!
             return false;
         }
     }
 
     return true;
 }
 
 bool
@@ -4368,37 +4292,48 @@ BytecodeEmitter::emitAssignment(ParseNod
             }
 
             return true;
         };
 
         return emitSetName(lhs, emitRhs);
     }
 
+    Maybe<PropOpEmitter> poe;
+
     // Deal with non-name assignments.
-    uint32_t atomIndex = (uint32_t) -1;
     uint8_t offset = 1;
 
     switch (lhs->getKind()) {
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &lhs->as<PropertyAccess>();
-        if (prop->isSuper()) {
-            if (!emitSuperPropLHS(&prop->expression().as<UnaryNode>())) {
-                return false;                             // THIS SUPERBASE
-            }
+        bool isSuper = prop->isSuper();
+        poe.emplace(this,
+                    isCompound
+                    ? PropOpEmitter::Kind::CompoundAssignment
+                    : PropOpEmitter::Kind::SimpleAssignment,
+                    isSuper
+                    ? PropOpEmitter::ObjKind::Super
+                    : PropOpEmitter::ObjKind::Other);
+        if (!poe->prepareForObj()) {
+            return false;
+        }
+        if (isSuper) {
+            UnaryNode* base = &prop->expression().as<UnaryNode>();
+            if (!emitGetThisForSuperBase(base)) {         // THIS SUPERBASE
+                return false;
+            }
+            // SUPERBASE is pushed onto THIS later in poe->emitGet below.
             offset += 2;
         } else {
             if (!emitTree(&prop->expression())) {         // OBJ
                 return false;
             }
             offset += 1;
         }
-        if (!makeAtomIndex(prop->key().atom(), &atomIndex)) {
-            return false;
-        }
         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;
@@ -4434,31 +4369,21 @@ BytecodeEmitter::emitAssignment(ParseNod
       default:
         MOZ_ASSERT(0);
     }
 
     if (isCompound) {
         MOZ_ASSERT(rhs);
         switch (lhs->getKind()) {
           case ParseNodeKind::Dot: {
-            JSOp getOp;
             PropertyAccess* prop = &lhs->as<PropertyAccess>();
-            if (prop->isSuper()) {
-                if (!emit1(JSOP_DUP2)) {                  // THIS OBJ THIS OBJ
-                    return false;
-                }
-                getOp = JSOP_GETPROP_SUPER;
-            } else {
-                if (!emit1(JSOP_DUP)) {                   // OBJ OBJ
-                    return false;
-                }
-                bool isLength = prop->key().atom() == cx->names().length;
-                getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP;
-            }
-            if (!emitIndex32(getOp, atomIndex)) {         // THIS? OBJ VAL
+            if (!poe->emitGet(prop->key().atom())) {      // [Super]
+                //                                        // THIS SUPERBASE PROP
+                //                                        // [Other]
+                //                                        // OBJ PROP
                 return false;
             }
             break;
           }
           case ParseNodeKind::Elem: {
             JSOp elemOp;
             PropertyByValue* elem = &lhs->as<PropertyByValue>();
             if (elem->isSuper()) {
@@ -4493,16 +4418,33 @@ BytecodeEmitter::emitAssignment(ParseNod
             if (!emit1(JSOP_NULL)) {
                 return false;
             }
             break;
           default:;
         }
     }
 
+    switch (lhs->getKind()) {
+      case ParseNodeKind::Dot:
+        if (!poe->prepareForRhs()) {                      // [Simple,Super]
+            //                                            // THIS SUPERBASE
+            //                                            // [Simple,Other]
+            //                                            // OBJ
+            //                                            // [Compound,Super]
+            //                                            // THIS SUPERBASE PROP
+            //                                            // [Compound,Other]
+            //                                            // OBJ PROP
+            return false;
+        }
+        break;
+      default:
+        break;
+    }
+
     if (!EmitAssignmentRhs(this, rhs, offset)) {          // ... VAL? RHS
         return false;
     }
 
     /* If += etc., emit the binary operator with a source note. */
     if (isCompound) {
         if (!newSrcNote(SRC_ASSIGNOP)) {
             return false;
@@ -4510,22 +4452,22 @@ BytecodeEmitter::emitAssignment(ParseNod
         if (!emit1(compoundOp)) {                         // ... VAL
             return false;
         }
     }
 
     /* Finally, emit the specialized assignment bytecode. */
     switch (lhs->getKind()) {
       case ParseNodeKind::Dot: {
-        JSOp setOp = lhs->as<PropertyAccess>().isSuper() ?
-                       (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER) :
-                       (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP);
-        if (!emitIndexOp(setOp, atomIndex)) {             // VAL
-            return false;
-        }
+        PropertyAccess* prop = &lhs->as<PropertyAccess>();
+        if (!poe->emitAssignment(prop->key().atom())) {   // VAL
+            return false;
+        }
+
+        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 :
@@ -6824,42 +6766,47 @@ BytecodeEmitter::emitDeleteName(UnaryNod
 }
 
 bool
 BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteProp));
 
     PropertyAccess* propExpr = &deleteNode->kid()->as<PropertyAccess>();
-
+    PropOpEmitter poe(this,
+                      PropOpEmitter::Kind::Delete,
+                      propExpr->as<PropertyAccess>().isSuper()
+                      ? PropOpEmitter::ObjKind::Super
+                      : PropOpEmitter::ObjKind::Other);
     if (propExpr->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 the super-base is not an object, before throwing a
         // ReferenceError for attempting to delete a super-reference.
-        if (!emitGetThisForSuperBase(&propExpr->expression().as<UnaryNode>())) {
-            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 emit1(JSOP_POP);
-    }
-
-    JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
-    return emitPropOp(propExpr, delOp);
+        UnaryNode* base = &propExpr->expression().as<UnaryNode>();
+        if (!emitGetThisForSuperBase(base)) {             // THIS
+            return false;
+        }
+    } else {
+        if (!poe.prepareForObj()) {
+            return false;
+        }
+        if (!emitPropLHS(propExpr)) {                         // OBJ
+            return false;
+        }
+    }
+
+    if (!poe.emitDelete(propExpr->key().atom())) {        // [Super]
+        //                                                // THIS
+        //                                                // [Other]
+        //                                                // SUCCEEDED
+        return false;
+    }
+
+    return true;
 }
 
 bool
 BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteElem));
 
     PropertyByValue* elemExpr = &deleteNode->kid()->as<PropertyByValue>();
@@ -7251,26 +7198,44 @@ BytecodeEmitter::emitCalleeAndThis(Parse
               case NameLocation::Kind::DynamicAnnexBVar:
                 MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
             }
         }
 
         break;
       }
       case ParseNodeKind::Dot: {
+        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyAccess* prop = &callee->as<PropertyAccess>();
-        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
-        if (prop->isSuper()) {
-            if (!emitSuperGetProp(prop, isCall)) {        // CALLEE THIS?
+        bool isSuper = prop->isSuper();
+        PropOpEmitter poe(this,
+                          isCall
+                          ? PropOpEmitter::Kind::Call
+                          : PropOpEmitter::Kind::Get,
+                          isSuper
+                          ? PropOpEmitter::ObjKind::Super
+                          : PropOpEmitter::ObjKind::Other);
+        if (!poe.prepareForObj()) {
+            return false;
+        }
+        if (isSuper) {
+            UnaryNode* base = &prop->expression().as<UnaryNode>();
+            if (!emitGetThisForSuperBase(base)) {        // THIS
                 return false;
             }
         } else {
-            if (!emitPropOp(prop, isCall ? JSOP_CALLPROP : JSOP_GETPROP)) {
-                return false;                             // CALLEE THIS?
-            }
+            if (!emitPropLHS(prop)) {                    // OBJ
+                return false;
+            }
+        }
+        if (!poe.emitGet(prop->key().atom())) {           // [needsThis]
+            //                                            // CALLEE
+            //                                            // [!needsThis]
+            //                                            // CALLEE THIS
+            return false;
         }
 
         break;
       }
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &callee->as<PropertyByValue>();
         MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         if (elem->isSuper()) {
@@ -9260,24 +9225,37 @@ BytecodeEmitter::emitTree(ParseNode* pn,
       case ParseNodeKind::DeleteExpr:
         if (!emitDeleteExpression(&pn->as<UnaryNode>())) {
             return false;
         }
         break;
 
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &pn->as<PropertyAccess>();
-        if (prop->isSuper()) {
-            if (!emitSuperGetProp(prop)) {
+        bool isSuper = prop->isSuper();
+        PropOpEmitter poe(this,
+                          PropOpEmitter::Kind::Get,
+                          isSuper
+                          ? PropOpEmitter::ObjKind::Super
+                          : PropOpEmitter::ObjKind::Other);
+        if (!poe.prepareForObj()) {
+            return false;
+        }
+        if (isSuper) {
+            UnaryNode* base = &prop->expression().as<UnaryNode>();
+            if (!emitGetThisForSuperBase(base)) {         // THIS
                 return false;
             }
         } else {
-            if (!emitPropOp(prop, JSOP_GETPROP)) {
-                return false;
-            }
+            if (!emitPropLHS(prop)) {                     // OBJ
+                return false;
+            }
+        }
+        if (!poe.emitGet(prop->key().atom())) {           // PROP
+            return false;
         }
         break;
       }
 
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &pn->as<PropertyByValue>();
         if (elem->isSuper()) {
             if (!emitSuperGetElem(elem)) {
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -543,16 +543,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     MOZ_MUST_USE bool emitGoto(NestableControl* target, JumpList* jumplist,
                                SrcNoteType noteType = SRC_NULL);
 
     MOZ_MUST_USE bool emitIndex32(JSOp op, uint32_t index);
     MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index);
 
     MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op);
+    MOZ_MUST_USE bool emitAtomOp(uint32_t atomIndex, JSOp op);
 
     MOZ_MUST_USE bool emitArrayLiteral(ListNode* array);
     MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count);
 
     MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op);
     MOZ_MUST_USE bool emitInternedObjectOp(uint32_t index, JSOp op);
     MOZ_MUST_USE bool emitObjectOp(ObjectBox* objbox, JSOp op);
     MOZ_MUST_USE bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op);
@@ -640,17 +641,16 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitYieldStar(ParseNode* iter);
     MOZ_MUST_USE bool emitAwaitInInnermostScope() {
         return emitAwaitInScope(*innermostEmitterScope());
     }
     MOZ_MUST_USE bool emitAwaitInInnermostScope(UnaryNode* awaitNode);
     MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope);
 
     MOZ_MUST_USE bool emitPropLHS(PropertyAccess* prop);
-    MOZ_MUST_USE bool emitPropOp(PropertyAccess* prop, JSOp op);
     MOZ_MUST_USE bool emitPropIncDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow);
     MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow,
                                        bool isGenerator);
 
     MOZ_MUST_USE bool emitComputedPropertyName(UnaryNode* computedPropName);
 
@@ -827,18 +827,16 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     // and the iterator to be on the stack in that order (iterator on the bottom).
     // It will pop the iterator and I, then iterate over the iterator by calling
     // |.next()| and put the results into the I-th element of array with
     // incrementing I, then push the result I (it will be original I +
     // iteration count). The stack after iteration will look like |ARRAY INDEX|.
     MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false);
 
     MOZ_MUST_USE bool emitClass(ClassNode* classNode);
-    MOZ_MUST_USE bool emitSuperPropLHS(UnaryNode* superBase, bool isCall = false);
-    MOZ_MUST_USE bool emitSuperGetProp(PropertyAccess* prop, bool isCall = false);
     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);
 
     MOZ_MUST_USE bool emitPipeline(ListNode* node);
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/PropOpEmitter.cpp
@@ -0,0 +1,275 @@
+/* -*- 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/PropOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+#include "vm/StringType.h"
+
+using namespace js;
+using namespace js::frontend;
+
+PropOpEmitter::PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
+  : bce_(bce),
+    kind_(kind),
+    objKind_(objKind)
+{}
+
+bool
+PropOpEmitter::prepareAtomIndex(JSAtom* prop)
+{
+    if (!bce_->makeAtomIndex(prop, &propAtomIndex_)) {
+        return false;
+    }
+    isLength_ = prop == bce_->cx->names().length;
+
+    return true;
+}
+
+bool
+PropOpEmitter::prepareForObj()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+#ifdef DEBUG
+    state_ = State::Obj;
+#endif
+    return true;
+}
+
+bool
+PropOpEmitter::emitGet(JSAtom* prop)
+{
+    MOZ_ASSERT(state_ == State::Obj);
+
+    if (!prepareAtomIndex(prop)) {
+        return false;
+    }
+    if (isCall()) {
+        if (!bce_->emit1(JSOP_DUP)) {                 // [Super]
+            //                                        // THIS THIS
+            //                                        // [Other]
+            //                                        // OBJ OBJ
+            return false;
+        }
+    }
+    if (isSuper()) {
+        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS? THIS SUPERBASE
+            return false;
+        }
+    }
+    if (isIncDec() || isCompoundAssignment()) {
+        if (isSuper()) {
+            if (!bce_->emit1(JSOP_DUP2)) {            // THIS SUPERBASE THIS SUPERBASE
+                return false;
+            }
+        } else {
+            if (!bce_->emit1(JSOP_DUP)) {             // OBJ OBJ
+                return false;
+            }
+        }
+    }
+
+    JSOp op;
+    if (isSuper()) {
+        op = JSOP_GETPROP_SUPER;
+    } else if (isCall()) {
+        op = JSOP_CALLPROP;
+    } else {
+        op = isLength_ ? JSOP_LENGTH : JSOP_GETPROP;
+    }
+    if (!bce_->emitAtomOp(propAtomIndex_, op)) {      // [Get]
+        //                                            // PROP
+        //                                            // [Call]
+        //                                            // THIS PROP
+        //                                            // [Inc/Dec/Compound,
+        //                                            //  Super]
+        //                                            // THIS SUPERBASE PROP
+        //                                            // [Inc/Dec/Compound,
+        //                                            //  Other]
+        //                                            // OBJ PROP
+        return false;
+    }
+    if (isCall()) {
+        if (!bce_->emit1(JSOP_SWAP)) {                // PROP THIS
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Get;
+#endif
+    return true;
+}
+
+bool
+PropOpEmitter::prepareForRhs()
+{
+    MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+    MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Obj);
+    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 SUPERBASE
+                return false;
+            }
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Rhs;
+#endif
+    return true;
+}
+
+bool
+PropOpEmitter::skipObjAndRhs()
+{
+    MOZ_ASSERT(state_ == State::Start);
+    MOZ_ASSERT(isSimpleAssignment());
+
+#ifdef DEBUG
+    state_ = State::Rhs;
+#endif
+    return true;
+}
+
+bool
+PropOpEmitter::emitDelete(JSAtom* prop)
+{
+    MOZ_ASSERT_IF(!isSuper(), state_ == State::Obj);
+    MOZ_ASSERT_IF(isSuper(), state_ == State::Start);
+    MOZ_ASSERT(isDelete());
+
+    if (!prepareAtomIndex(prop)) {
+        return false;
+    }
+    if (isSuper()) {
+        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS SUPERBASE
+            return false;
+        }
+
+        // Unconditionally throw when attempting to delete a super-reference.
+        if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
+            return false;                             // THIS 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_->emit1(JSOP_POP)) {                 // THIS
+            return false;
+        }
+    } else {
+        JSOp op = bce_->sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
+        if (!bce_->emitAtomOp(propAtomIndex_, op)) {  // SUCCEEDED
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Delete;
+#endif
+    return true;
+}
+
+bool
+PropOpEmitter::emitAssignment(JSAtom* prop)
+{
+    MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+    MOZ_ASSERT(state_ == State::Rhs);
+
+    if (isSimpleAssignment()) {
+        if (!prepareAtomIndex(prop)) {
+            return false;
+        }
+    }
+
+    JSOp setOp = isSuper()
+                 ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
+                 : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
+    if (!bce_->emitAtomOp(propAtomIndex_, setOp)) {   // VAL
+        return false;
+    }
+
+#ifdef DEBUG
+    state_ = State::Assignment;
+#endif
+    return true;
+}
+
+bool
+PropOpEmitter::emitIncDec(JSAtom* prop)
+{
+    MOZ_ASSERT(state_ == State::Obj);
+    MOZ_ASSERT(isIncDec());
+
+    if (!emitGet(prop)) {
+        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 OBJ N N+1
+            if (!bce_->emit2(JSOP_PICK, 3)) {         // OBJ N N+1 THIS
+                return false;
+            }
+            if (!bce_->emit1(JSOP_SWAP)) {            // OBJ N THIS N+1
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 3)) {         // N THIS N+1 OBJ
+                return false;
+            }
+            if (!bce_->emit1(JSOP_SWAP)) {            // N THIS OBJ N+1
+                return false;
+            }
+        } else {                                      // OBJ N N+1
+            if (!bce_->emit2(JSOP_PICK, 2)) {         // N N+1 OBJ
+                return false;
+            }
+            if (!bce_->emit1(JSOP_SWAP)) {            // N OBJ N+1
+                return false;
+            }
+        }
+    }
+
+    JSOp setOp = isSuper()
+                 ? bce_->sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
+                 : bce_->sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
+    if (!bce_->emitAtomOp(propAtomIndex_, 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/PropOpEmitter.h
@@ -0,0 +1,267 @@
+/* -*- 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_PropOpEmitter_h
+#define frontend_PropOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include <stdint.h>
+
+#include "js/TypeDecls.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for property operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `obj.prop;`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::Get,
+//                       PropOpEmitter::ObjKind::Other);
+//     poe.prepareForObj();
+//     emit(obj);
+//     poe.emitGet(atom_of_prop);
+//
+//   `super.prop;`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::Get,
+//                       PropOpEmitter::ObjKind::Super);
+//     poe.prepareForObj();
+//     emit(obj);
+//     poe.emitGet(atom_of_prop);
+//
+//   `obj.prop();`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::Call,
+//                       PropOpEmitter::ObjKind::Other);
+//     poe.prepareForObj();
+//     emit(obj);
+//     poe.emitGet(atom_of_prop);
+//     emit_call_here();
+//
+//   `new obj.prop();`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::Call,
+//                       PropOpEmitter::ObjKind::Other);
+//     poe.prepareForObj();
+//     emit(obj);
+//     poe.emitGet(atom_of_prop);
+//     emit_call_here();
+//
+//   `delete obj.prop;`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::Delete,
+//                       PropOpEmitter::ObjKind::Other);
+//     poe.prepareForObj();
+//     emit(obj);
+//     poe.emitDelete(atom_of_prop);
+//
+//   `delete super.prop;`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::Delete,
+//                       PropOpEmitter::ObjKind::Other);
+//     poe.emitDelete(atom_of_prop);
+//
+//   `obj.prop++;`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::PostIncrement,
+//                       PropOpEmitter::ObjKind::Other);
+//     poe.prepareForObj();
+//     emit(obj);
+//     poe.emitIncDec(atom_of_prop);
+//
+//   `obj.prop = value;`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::SimpleAssignment,
+//                       PropOpEmitter::ObjKind::Other);
+//     poe.prepareForObj();
+//     emit(obj);
+//     poe.prepareForRhs();
+//     emit(value);
+//     poe.emitAssignment(atom_of_prop);
+//
+//   `obj.prop += value;`
+//     PropOpEmitter poe(this,
+//                       PropOpEmitter::Kind::CompoundAssignment,
+//                       PropOpEmitter::ObjKind::Other);
+//     poe.prepareForObj();
+//     emit(obj);
+//     poe.emitGet(atom_of_prop);
+//     poe.prepareForRhs();
+//     emit(value);
+//     emit_add_op_here();
+//     poe.emitAssignment(nullptr); // nullptr for CompoundAssignment
+//
+class MOZ_STACK_CLASS PropOpEmitter
+{
+  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_;
+
+    // The index for the property name's atom.
+    uint32_t propAtomIndex_ = 0;
+
+    // Whether the property name is `length` or not.
+    bool isLength_ = false;
+
+#ifdef DEBUG
+    // The state of this emitter.
+    //
+    //             skipObjAndRhs
+    //           +----------------------------+
+    //           |                            |
+    // +-------+ | prepareForObj +-----+      |
+    // | Start |-+-------------->| Obj |-+    |
+    // +-------+                 +-----+ |    |
+    //                                   |    |
+    // +---------------------------------+    |
+    // |                                      |
+    // |                                      |
+    // | [Get]                                |
+    // | [Call]                               |
+    // |   emitGet +-----+                    |
+    // +---------->| Get |                    |
+    // |           +-----+                    |
+    // |                                      |
+    // | [Delete]                             |
+    // |   emitDelete +--------+              |
+    // +------------->| Delete |              |
+    // |              +--------+              |
+    // |                                      |
+    // | [PostIncrement]                      |
+    // | [PreIncrement]                       |
+    // | [PostDecrement]                      |
+    // | [PreDecrement]                       |
+    // |   emitIncDec +--------+              |
+    // +------------->| IncDec |              |
+    // |              +--------+              |
+    // |                                      |
+    // | [SimpleAssignment]                   |
+    // |                        prepareForRhs |  +-----+
+    // +--------------------->+-------------->+->| Rhs |-+
+    // |                      ^                  +-----+ |
+    // |                      |                          |
+    // |                      |                +---------+
+    // | [CompoundAssignment] |                |
+    // |   emitGet +-----+    |                | emitAssignment +------------+
+    // +---------->| Get |----+                + -------------->| Assignment |
+    //             +-----+                                      +------------+
+    enum class State {
+        // The initial state.
+        Start,
+
+        // After calling prepareForObj.
+        Obj,
+
+        // After calling emitGet.
+        Get,
+
+        // After calling emitDelete.
+        Delete,
+
+        // After calling emitIncDec.
+        IncDec,
+
+        // After calling prepareForRhs or skipObjAndRhs.
+        Rhs,
+
+        // After calling emitAssignment.
+        Assignment,
+    };
+    State state_ = State::Start;
+#endif
+
+  public:
+    PropOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
+
+  private:
+    MOZ_MUST_USE bool isCall() const {
+        return kind_ == Kind::Call;
+    }
+
+    MOZ_MUST_USE bool isSuper() const {
+        return objKind_ == ObjKind::Super;
+    }
+
+    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
+    prepareAtomIndex(JSAtom* prop);
+
+  public:
+    MOZ_MUST_USE bool prepareForObj();
+
+    MOZ_MUST_USE bool emitGet(JSAtom* prop);
+
+    MOZ_MUST_USE bool prepareForRhs();
+    MOZ_MUST_USE bool skipObjAndRhs();
+
+    MOZ_MUST_USE bool emitDelete(JSAtom* prop);
+
+    // `prop` can be nullptr for CompoundAssignment.
+    MOZ_MUST_USE bool emitAssignment(JSAtom* prop);
+
+    MOZ_MUST_USE bool emitIncDec(JSAtom* prop);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_PropOpEmitter_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -233,16 +233,17 @@ UNIFIED_SOURCES += [
     'frontend/FoldConstants.cpp',
     'frontend/ForInEmitter.cpp',
     'frontend/ForOfEmitter.cpp',
     'frontend/ForOfLoopControl.cpp',
     'frontend/IfEmitter.cpp',
     'frontend/JumpList.cpp',
     'frontend/NameFunctions.cpp',
     'frontend/ParseNode.cpp',
+    'frontend/PropOpEmitter.cpp',
     'frontend/SwitchEmitter.cpp',
     'frontend/TDZCheckCache.cpp',
     'frontend/TokenStream.cpp',
     'frontend/TryEmitter.cpp',
     'frontend/WhileEmitter.cpp',
     'gc/Allocator.cpp',
     'gc/AtomMarking.cpp',
     'gc/Barrier.cpp',