Bug 1466000 - Part 9: Add NameOpEmitter. r=efaust
authorTooru Fujisawa <arai_a@mac.com>
Tue, 09 Oct 2018 21:23:12 +0900
changeset 496206 1a5da918f6d7b1e650b56b96570648628ee92b1a
parent 496205 61f8a1e5fa5d4ecb83ff2c82e24831550117c5e3
child 496207 4f55976a9e9115c9f41075843bc48955684364d9
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 9: Add NameOpEmitter. r=efaust
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/NameOpEmitter.cpp
js/src/frontend/NameOpEmitter.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/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/NameOpEmitter.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"
@@ -992,29 +993,16 @@ BytecodeEmitter::emitEnvCoordOp(JSOp op,
     SET_ENVCOORD_HOPS(pc, ec.hops());
     pc += ENVCOORD_HOPS_LEN;
     SET_ENVCOORD_SLOT(pc, ec.slot());
     pc += ENVCOORD_SLOT_LEN;
     checkTypeSet(op);
     return true;
 }
 
-static JSOp
-GetIncDecInfo(ParseNodeKind kind, bool* post)
-{
-    MOZ_ASSERT(kind == ParseNodeKind::PostIncrement ||
-               kind == ParseNodeKind::PreIncrement ||
-               kind == ParseNodeKind::PostDecrement ||
-               kind == ParseNodeKind::PreDecrement);
-    *post = kind == ParseNodeKind::PostIncrement || kind == ParseNodeKind::PostDecrement;
-    return (kind == ParseNodeKind::PostIncrement || kind == ParseNodeKind::PreIncrement)
-           ? JSOP_ADD
-           : JSOP_SUB;
-}
-
 JSOp
 BytecodeEmitter::strictifySetNameOp(JSOp op)
 {
     switch (op) {
       case JSOP_SETNAME:
         if (sc->strict()) {
             op = JSOP_STRICTSETNAME;
         }
@@ -1713,265 +1701,30 @@ BytecodeEmitter::emitFinishIteratorResul
         return false;
     }
     return true;
 }
 
 bool
 BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc)
 {
-    switch (loc.kind()) {
-      case NameLocation::Kind::Dynamic:
-        if (!emitAtomOp(name, JSOP_GETNAME)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Global:
-        if (!emitAtomOp(name, JSOP_GETGNAME)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Intrinsic:
-        if (!emitAtomOp(name, JSOP_GETINTRINSIC)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::NamedLambdaCallee:
-        if (!emit1(JSOP_CALLEE)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Import:
-        if (!emitAtomOp(name, JSOP_GETIMPORT)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::ArgumentSlot:
-        if (!emitArgOp(JSOP_GETARG, loc.argumentSlot())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::FrameSlot:
-        if (loc.isLexical()) {
-            if (!emitTDZCheckIfNeeded(name, loc)) {
-                return false;
-            }
-        }
-        if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::EnvironmentCoordinate:
-        if (loc.isLexical()) {
-            if (!emitTDZCheckIfNeeded(name, loc)) {
-                return false;
-            }
-        }
-        if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::DynamicAnnexBVar:
-        MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+    NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
+    if (!noe.emitGet()) {
+        return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitGetName(ParseNode* pn)
 {
     return emitGetName(pn->name());
 }
 
-template <typename RHSEmitter>
-bool
-BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
-                                                   RHSEmitter emitRhs, bool initialize)
-{
-    bool emittedBindOp = false;
-
-    switch (loc.kind()) {
-      case NameLocation::Kind::Dynamic:
-      case NameLocation::Kind::Import:
-      case NameLocation::Kind::DynamicAnnexBVar: {
-        uint32_t atomIndex;
-        if (!makeAtomIndex(name, &atomIndex)) {
-            return false;
-        }
-        if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) {
-            // Annex B vars always go on the nearest variable environment,
-            // even if lexical environments in between contain same-named
-            // bindings.
-            if (!emit1(JSOP_BINDVAR)) {
-                return false;
-            }
-        } else {
-            if (!emitIndexOp(JSOP_BINDNAME, atomIndex)) {
-                return false;
-            }
-        }
-        emittedBindOp = true;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex)) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::Global: {
-        JSOp op;
-        uint32_t atomIndex;
-        if (!makeAtomIndex(name, &atomIndex)) {
-            return false;
-        }
-        if (loc.isLexical() && initialize) {
-            // INITGLEXICAL always gets the global lexical scope. It doesn't
-            // need a BINDGNAME.
-            MOZ_ASSERT(innermostScope()->is<GlobalScope>());
-            op = JSOP_INITGLEXICAL;
-        } else {
-            if (!emitIndexOp(JSOP_BINDGNAME, atomIndex)) {
-                return false;
-            }
-            emittedBindOp = true;
-            op = strictifySetNameOp(JSOP_SETGNAME);
-        }
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitIndexOp(op, atomIndex)) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::Intrinsic:
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitAtomOp(name, JSOP_SETINTRINSIC)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::NamedLambdaCallee:
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        // Assigning to the named lambda is a no-op in sloppy mode but
-        // throws in strict mode.
-        if (sc->strict() && !emit1(JSOP_THROWSETCALLEE)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::ArgumentSlot: {
-        // If we assign to a positional formal parameter and the arguments
-        // object is unmapped (strict mode or function with
-        // default/rest/destructing args), parameters do not alias
-        // arguments[i], and to make the arguments object reflect initial
-        // parameter values prior to any mutation we create it eagerly
-        // whenever parameters are (or might, in the case of calls to eval)
-        // assigned.
-        FunctionBox* funbox = sc->asFunctionBox();
-        if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) {
-            funbox->setDefinitelyNeedsArgsObj();
-        }
-
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitArgOp(JSOP_SETARG, loc.argumentSlot())) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::FrameSlot: {
-        JSOp op = JSOP_SETLOCAL;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (loc.isLexical()) {
-            if (initialize) {
-                op = JSOP_INITLEXICAL;
-            } else {
-                if (loc.isConst()) {
-                    op = JSOP_THROWSETCONST;
-                }
-
-                if (!emitTDZCheckIfNeeded(name, loc)) {
-                    return false;
-                }
-            }
-        }
-        if (!emitLocalOp(op, loc.frameSlot())) {
-            return false;
-        }
-        if (op == JSOP_INITLEXICAL) {
-            if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) {
-                return false;
-            }
-        }
-        break;
-      }
-
-      case NameLocation::Kind::EnvironmentCoordinate: {
-        JSOp op = JSOP_SETALIASEDVAR;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (loc.isLexical()) {
-            if (initialize) {
-                op = JSOP_INITALIASEDLEXICAL;
-            } else {
-                if (loc.isConst()) {
-                    op = JSOP_THROWSETALIASEDCONST;
-                }
-
-                if (!emitTDZCheckIfNeeded(name, loc)) {
-                    return false;
-                }
-            }
-        }
-        if (loc.bindingKind() == BindingKind::NamedLambdaCallee) {
-            // Assigning to the named lambda is a no-op in sloppy mode and throws
-            // in strict mode.
-            op = JSOP_THROWSETALIASEDCONST;
-            if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate())) {
-                return false;
-            }
-        } else {
-            if (!emitEnvCoordOp(op, loc.environmentCoordinate())) {
-                return false;
-            }
-        }
-        if (op == JSOP_INITALIASEDLEXICAL) {
-            if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) {
-                return false;
-            }
-        }
-        break;
-      }
-    }
-
-    return true;
-}
-
 bool
 BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc)
 {
     // Dynamic accesses have TDZ checks built into their VM code and should
     // never emit explicit TDZ checks.
     MOZ_ASSERT(loc.hasKnownSlot());
     MOZ_ASSERT(loc.isLexical());
 
@@ -2079,87 +1832,30 @@ BytecodeEmitter::emitPropIncDec(UnaryNod
     }
     if (!poe.emitIncDec(prop->key().atom())) {      // RESULT
         return false;
     }
 
     return true;
 }
 
-bool
-BytecodeEmitter::emitGetNameAtLocationForCompoundAssignment(JSAtom* name, const NameLocation& loc)
-{
-    if (loc.kind() == NameLocation::Kind::Dynamic) {
-        // For dynamic accesses we need to emit GETBOUNDNAME instead of
-        // GETNAME for correctness: looking up @@unscopables on the
-        // environment chain (due to 'with' environments) must only happen
-        // once.
-        //
-        // GETBOUNDNAME uses the environment already pushed on the stack from
-        // the earlier BINDNAME.
-        if (!emit1(JSOP_DUP)) {                            // ENV ENV
-            return false;
-        }
-        if (!emitAtomOp(name, JSOP_GETBOUNDNAME)) {        // ENV V
-            return false;
-        }
-    } else {
-        if (!emitGetNameAtLocation(name, loc)) {           // ENV? V
-            return false;
-        }
-    }
-
-    return true;
-}
 
 bool
 BytecodeEmitter::emitNameIncDec(UnaryNode* incDec)
 {
     MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name));
 
-    bool post;
-    JSOp binop = GetIncDecInfo(incDec->getKind(), &post);
-
-    auto emitRhs = [incDec, post, binop](BytecodeEmitter* bce, const NameLocation& loc,
-                                         bool emittedBindOp)
-    {
-        JSAtom* name = incDec->kid()->name();
-        if (!bce->emitGetNameAtLocationForCompoundAssignment(name, loc)) { // ENV? V
-            return false;
-        }
-        if (!bce->emit1(JSOP_POS)) {                       // ENV? N
-            return false;
-        }
-        if (post && !bce->emit1(JSOP_DUP)) {               // ENV? N? N
-            return false;
-        }
-        if (!bce->emit1(JSOP_ONE)) {                       // ENV? N? N 1
-            return false;
-        }
-        if (!bce->emit1(binop)) {                          // ENV? N? N+1
-            return false;
-        }
-
-        if (post && emittedBindOp) {
-            if (!bce->emit2(JSOP_PICK, 2)) {               // N? N+1 ENV?
-                return false;
-            }
-            if (!bce->emit1(JSOP_SWAP)) {                  // N? ENV? N+1
-                return false;
-            }
-        }
-
-        return true;
-    };
-
-    if (!emitSetName(incDec->kid(), emitRhs)) {
-        return false;
-    }
-
-    if (post && !emit1(JSOP_POP)) {
+    ParseNodeKind kind = incDec->getKind();
+    NameNode* name = &incDec->kid()->as<NameNode>();
+    NameOpEmitter noe(this, name->atom(),
+                      kind == ParseNodeKind::PostIncrement ? NameOpEmitter::Kind::PostIncrement
+                      : kind == ParseNodeKind::PreIncrement ? NameOpEmitter::Kind::PreIncrement
+                      : kind == ParseNodeKind::PostDecrement ? NameOpEmitter::Kind::PostDecrement
+                      : NameOpEmitter::Kind::PreDecrement);
+    if (!noe.emitIncDec()) {
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitElemOpBase(JSOp op)
@@ -2520,52 +2216,58 @@ BytecodeEmitter::emitSetThis(BinaryNode*
 {
     // ParseNodeKind::SetThis is used to update |this| after a super() call
     // in a derived class constructor.
 
     MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis));
     MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name));
 
     RootedAtom name(cx, setThisNode->left()->name());
-    auto emitRhs = [&name, setThisNode](BytecodeEmitter* bce, const NameLocation&, bool) {
-        // Emit the new |this| value.
-        if (!bce->emitTree(setThisNode->right())) {
-            return false;
-        }
-        // Get the original |this| and throw if we already initialized
-        // it. Do *not* use the NameLocation argument, as that's the special
-        // lexical location below to deal with super() semantics.
-        if (!bce->emitGetName(name)) {
-            return false;
-        }
-        if (!bce->emit1(JSOP_CHECKTHISREINIT)) {
-            return false;
-        }
-        if (!bce->emit1(JSOP_POP)) {
-            return false;
-        }
-        return true;
-    };
 
     // The 'this' binding is not lexical, but due to super() semantics this
     // initialization needs to be treated as a lexical one.
     NameLocation loc = lookupName(name);
     NameLocation lexicalLoc;
     if (loc.kind() == NameLocation::Kind::FrameSlot) {
         lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot());
     } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) {
         EnvironmentCoordinate coord = loc.environmentCoordinate();
         uint8_t hops = AssertedCast<uint8_t>(coord.hops());
         lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops, coord.slot());
     } else {
         MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic);
         lexicalLoc = loc;
     }
 
-    return emitSetOrInitializeNameAtLocation(name, lexicalLoc, emitRhs, true);
+    NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize);
+    if (!noe.prepareForRhs()) {                           //
+        return false;
+    }
+
+    // Emit the new |this| value.
+    if (!emitTree(setThisNode->right()))                  // NEWTHIS
+        return false;
+
+    // Get the original |this| and throw if we already initialized
+    // it. Do *not* use the NameLocation argument, as that's the special
+    // lexical location below to deal with super() semantics.
+    if (!emitGetName(name)) {                             // NEWTHIS THIS
+        return false;
+    }
+    if (!emit1(JSOP_CHECKTHISREINIT)) {                   // NEWTHIS THIS
+        return false;
+    }
+    if (!emit1(JSOP_POP)) {                               // NEWTHIS
+        return false;
+    }
+    if (!noe.emitAssignment()) {                          // NEWTHIS
+        return false;
+    }
+
+    return true;
 }
 
 bool
 BytecodeEmitter::emitScript(ParseNode* body)
 {
     AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, parser->errorReporter(), body);
 
     setScriptStartOffsetIfUnset(body->pn_pos);
@@ -2850,69 +2552,68 @@ BytecodeEmitter::emitSetOrInitializeDest
         // Per its post-condition, emitDestructuringOps has left the
         // to-be-destructured value on top of the stack.
         if (!emit1(JSOP_POP)) {
             return false;
         }
     } else {
         switch (target->getKind()) {
           case ParseNodeKind::Name: {
-            auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
-                                          bool emittedBindOp)
-            {
-                if (emittedBindOp) {
-                    // This is like ordinary assignment, but with one
-                    // difference.
-                    //
-                    // In `a = b`, we first determine a binding for `a` (using
-                    // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`,
-                    // then a JSOP_SETNAME instruction.
-                    //
-                    // In `[a] = [b]`, per spec, `b` is evaluated first, then
-                    // we determine a binding for `a`. Then we need to do
-                    // assignment-- but the operands are on the stack in the
-                    // wrong order for JSOP_SETPROP, so we have to add a
-                    // JSOP_SWAP.
-                    //
-                    // In the cases where we are emitting a name op, emit a
-                    // swap because of this.
-                    return bce->emit1(JSOP_SWAP);
-                }
-
-                // In cases of emitting a frame slot or environment slot,
-                // nothing needs be done.
-                return true;
-            };
-
             RootedAtom name(cx, target->name());
+            NameLocation loc;
+            NameOpEmitter::Kind kind;
             switch (flav) {
               case DestructuringDeclaration:
-                if (!emitInitializeName(name, emitSwapScopeAndRhs)) {
-                    return false;
-                }
+                loc = lookupName(name);
+                kind = NameOpEmitter::Kind::Initialize;
                 break;
-
               case DestructuringFormalParameterInVarScope: {
                 // If there's an parameter expression var scope, the
                 // destructuring declaration needs to initialize the name in
                 // the function scope. The innermost scope is the var scope,
                 // and its enclosing scope is the function scope.
                 EmitterScope* funScope = innermostEmitterScope()->enclosingInFrame();
-                NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope);
-                if (!emitSetOrInitializeNameAtLocation(name, paramLoc, emitSwapScopeAndRhs, true)) {
-                    return false;
-                }
+                loc = *locationOfNameBoundInScope(name, funScope);
+                kind = NameOpEmitter::Kind::Initialize;
                 break;
               }
 
               case DestructuringAssignment:
-                if (!emitSetName(name, emitSwapScopeAndRhs)) {
+                loc = lookupName(name);
+                kind = NameOpEmitter::Kind::SimpleAssignment;
+                break;
+            }
+
+            NameOpEmitter noe(this, name, loc, kind);
+            if (!noe.prepareForRhs()) {                   // V ENV?
+                return false;
+            }
+            if (noe.emittedBindOp()) {
+                // This is like ordinary assignment, but with one difference.
+                //
+                // In `a = b`, we first determine a binding for `a` (using
+                // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, then
+                // a JSOP_SETNAME instruction.
+                //
+                // In `[a] = [b]`, per spec, `b` is evaluated first, then we
+                // determine a binding for `a`. Then we need to do assignment--
+                // but the operands are on the stack in the wrong order for
+                // JSOP_SETPROP, so we have to add a JSOP_SWAP.
+                //
+                // In the cases where we are emitting a name op, emit a swap
+                // because of this.
+                if (!emit1(JSOP_SWAP)) {                  // ENV V
                     return false;
                 }
-                break;
+            } else {
+                // In cases of emitting a frame slot or environment slot,
+                // nothing needs be done.
+            }
+            if (!noe.emitAssignment()) {                  // V
+                return false;
             }
 
             break;
           }
 
           case ParseNodeKind::Dot: {
             // The reference is already pushed by emitDestructuringLHSRef.
             //                                            // [Super]
@@ -4081,37 +3782,43 @@ BytecodeEmitter::emitSingleDeclaration(P
 {
     MOZ_ASSERT(decl->isKind(ParseNodeKind::Name));
 
     // Nothing to do for initializer-less 'var' declarations, as there's no TDZ.
     if (!initializer && declList->isKind(ParseNodeKind::Var)) {
         return true;
     }
 
-    auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) {
-        if (!initializer) {
-            // Lexical declarations are initialized to undefined without an
-            // initializer.
-            MOZ_ASSERT(declList->isKind(ParseNodeKind::Let),
-                       "var declarations without initializers handled above, "
-                       "and const declarations must have initializers");
-            Unused << declList; // silence clang -Wunused-lambda-capture in opt builds
-            return bce->emit1(JSOP_UNDEFINED);
-        }
-
+    NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize);
+    if (!noe.prepareForRhs()) {                           // ENV?
+        return false;
+    }
+    if (!initializer) {
+        // Lexical declarations are initialized to undefined without an
+        // initializer.
+        MOZ_ASSERT(declList->isKind(ParseNodeKind::Let),
+                   "var declarations without initializers handled above, "
+                   "and const declarations must have initializers");
+        if (!emit1(JSOP_UNDEFINED)) {                     // ENV? UNDEF
+            return false;
+        }
+    } else {
         MOZ_ASSERT(initializer);
-        return bce->emitInitializer(initializer, decl);
-    };
-
-    if (!emitInitializeName(decl, emitRhs)) {
-        return false;
-    }
-
-    // Pop the RHS.
-    return emit1(JSOP_POP);
+        if (!emitInitializer(initializer, decl)) {        // ENV? V
+            return false;
+        }
+    }
+    if (!noe.emitAssignment()) {                          // V
+         return false;
+    }
+    if (!emit1(JSOP_POP)) {                               //
+        return false;
+    }
+
+    return true;
 }
 
 static bool
 EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset)
 {
     // If there is a RHS tree, emit the tree.
     if (rhs) {
         return bce->emitTree(rhs);
@@ -4152,54 +3859,51 @@ CompoundAssignmentParseNodeKindToJSOp(Pa
 bool
 BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
 {
     bool isCompound = compoundOp != JSOP_NOP;
 
     // Name assignments are handled separately because choosing ops and when
     // to emit BINDNAME is involved and should avoid duplication.
     if (lhs->isKind(ParseNodeKind::Name)) {
-        auto emitRhs = [lhs, compoundOp, rhs, isCompound](BytecodeEmitter* bce,
-                                                          const NameLocation& lhsLoc,
-                                                          bool emittedBindOp)
-        {
-            // For compound assignments, first get the LHS value, then emit
-            // the RHS and the compoundOp.
-            if (isCompound) {
-                if (!bce->emitGetNameAtLocationForCompoundAssignment(lhs->name(), lhsLoc)) {
-                    return false;
-                }
-            }
-
-            // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
-            // the top of the stack and we need to pick the right RHS value.
-            if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) {
-                return false;
-            }
-
-            if (rhs && rhs->isDirectRHSAnonFunction()) {
-                MOZ_ASSERT(!lhs->isInParens());
-                MOZ_ASSERT(!isCompound);
-                RootedAtom name(bce->cx, lhs->name());
-                if (!bce->setOrEmitSetFunName(rhs, name)) {
-                    return false;
-                }
-            }
-
-            // Emit the compound assignment op if there is one.
-            if (isCompound) {
-                if (!bce->emit1(compoundOp)) {
-                    return false;
-                }
-            }
-
-            return true;
-        };
-
-        return emitSetName(lhs, emitRhs);
+        NameOpEmitter noe(this,
+                          lhs->name(),
+                          isCompound
+                          ? NameOpEmitter::Kind::CompoundAssignment
+                          : NameOpEmitter::Kind::SimpleAssignment);
+        if (!noe.prepareForRhs()) {                       // ENV? VAL?
+            return false;
+        }
+
+        // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
+        // the top of the stack and we need to pick the right RHS value.
+        uint8_t offset = noe.emittedBindOp() ? 2 : 1;
+        if (!EmitAssignmentRhs(this, rhs, offset)) {      // ENV? VAL? RHS
+            return false;
+        }
+        if (rhs && rhs->isDirectRHSAnonFunction()) {
+            MOZ_ASSERT(!lhs->isInParens());
+            MOZ_ASSERT(!isCompound);
+            RootedAtom name(cx, lhs->name());
+            if (!setOrEmitSetFunName(rhs, name)) {         // ENV? VAL? RHS
+                return false;
+            }
+        }
+
+        // Emit the compound assignment op if there is one.
+        if (isCompound) {
+            if (!emit1(compoundOp)) {                     // ENV? VAL
+                return false;
+            }
+        }
+        if (!noe.emitAssignment()) {                      // VAL
+            return false;
+        }
+
+        return true;
     }
 
     Maybe<PropOpEmitter> poe;
     Maybe<ElemOpEmitter> eoe;
 
     // Deal with non-name assignments.
     uint8_t offset = 1;
 
@@ -5199,36 +4903,40 @@ BytecodeEmitter::emitInitializeForInOrOf
     if (!updateSourceCoordNotes(target->pn_pos.begin)) {
         return false;
     }
 
     MOZ_ASSERT(target->isForLoopDeclaration());
     target = parser->astGenerator().singleBindingFromDeclaration(&target->as<ListNode>());
 
     if (target->isKind(ParseNodeKind::Name)) {
-        auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
-                                      bool emittedBindOp)
-        {
-            if (emittedBindOp) {
-                // Per-iteration initialization in for-in/of loops computes the
-                // iteration value *before* initializing.  Thus the
-                // initializing value may be buried under a bind-specific value
-                // on the stack.  Swap it to the top of the stack.
-                MOZ_ASSERT(bce->stackDepth >= 2);
-                return bce->emit1(JSOP_SWAP);
-            }
-
-            // In cases of emitting a frame slot or environment slot,
-            // nothing needs be done.
-            MOZ_ASSERT(bce->stackDepth >= 1);
-            return true;
-        };
+        NameOpEmitter noe(this, target->name(), NameOpEmitter::Kind::Initialize);
+        if (!noe.prepareForRhs()) {
+            return false;
+        }
+        if (noe.emittedBindOp()) {
+            // Per-iteration initialization in for-in/of loops computes the
+            // iteration value *before* initializing.  Thus the initializing
+            // value may be buried under a bind-specific value on the stack.
+            // Swap it to the top of the stack.
+            MOZ_ASSERT(stackDepth >= 2);
+            if (!emit1(JSOP_SWAP)) {
+                return false;
+            }
+        } else {
+             // In cases of emitting a frame slot or environment slot,
+             // nothing needs be done.
+            MOZ_ASSERT(stackDepth >= 1);
+        }
+        if (!noe.emitAssignment()) {
+            return false;
+        }
 
         // The caller handles removing the iteration value from the stack.
-        return emitInitializeName(target, emitSwapScopeAndRhs);
+        return true;
     }
 
     MOZ_ASSERT(!target->isKind(ParseNodeKind::Assign),
                "for-in/of loop destructuring declarations can't have initializers");
 
     MOZ_ASSERT(target->isKind(ParseNodeKind::Array) ||
                target->isKind(ParseNodeKind::Object));
     return emitDestructuringOps(&target->as<ListNode>(), DestructuringDeclaration);
@@ -5322,21 +5030,24 @@ BytecodeEmitter::emitForIn(ForNode* forI
             if (ParseNode* initializer = decl->as<NameNode>().initializer()) {
                 MOZ_ASSERT(forInTarget->isKind(ParseNodeKind::Var),
                            "for-in initializers are only permitted for |var| declarations");
 
                 if (!updateSourceCoordNotes(decl->pn_pos.begin)) {
                     return false;
                 }
 
-                auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) {
-                    return bce->emitInitializer(initializer, decl);
-                };
-
-                if (!emitInitializeName(decl, emitRhs)) {
+                NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize);
+                if (!noe.prepareForRhs()) {
+                    return false;
+                }
+                if (!emitInitializer(initializer, decl)) {
+                    return false;
+                }
+                if (!noe.emitAssignment()) {
                     return false;
                 }
 
                 // Pop the initializer.
                 if (!emit1(JSOP_POP)) {
                     return false;
                 }
             }
@@ -5499,22 +5210,16 @@ BytecodeEmitter::emitFunction(CodeNode* 
     // emitted. Function definitions that need hoisting to the top of the
     // function will be seen by emitFunction in two places.
     if (funbox->wasEmitted) {
         // Annex B block-scoped functions are hoisted like any other
         // block-scoped function to the top of their scope. When their
         // definitions are seen for the second time, we need to emit the
         // assignment that assigns the function to the outer 'var' binding.
         if (funbox->isAnnexB) {
-            auto emitRhs = [&name](BytecodeEmitter* bce, const NameLocation&, bool) {
-                // The RHS is the value of the lexically bound name in the
-                // innermost scope.
-                return bce->emitGetName(name);
-            };
-
             // Get the location of the 'var' binding in the body scope. The
             // name must be found, else there is a bug in the Annex B handling
             // in Parser.
             //
             // In sloppy eval contexts, this location is dynamic.
             Maybe<NameLocation> lhsLoc = locationOfNameBoundInScope(name, varEmitterScope);
 
             // If there are parameter expressions, the var name could be a
@@ -5527,17 +5232,24 @@ BytecodeEmitter::emitFunction(CodeNode* 
                 lhsLoc = Some(NameLocation::DynamicAnnexBVar());
             } else {
                 MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var ||
                            lhsLoc->bindingKind() == BindingKind::FormalParameter ||
                            (lhsLoc->bindingKind() == BindingKind::Let &&
                             sc->asFunctionBox()->hasParameterExprs));
             }
 
-            if (!emitSetOrInitializeNameAtLocation(name, *lhsLoc, emitRhs, false)) {
+            NameOpEmitter noe(this, name, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment);
+            if (!noe.prepareForRhs()) {
+                return false;
+            }
+            if (!emitGetName(name)) {
+                return false;
+            }
+            if (!noe.emitAssignment()) {
                 return false;
             }
             if (!emit1(JSOP_POP)) {
                 return false;
             }
         }
 
         MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript());
@@ -5696,28 +5408,32 @@ BytecodeEmitter::emitFunction(CodeNode* 
                 return false;
             }
             switchToMain();
         }
     } else {
         // For functions nested within functions and blocks, make a lambda and
         // initialize the binding name of the function in the current scope.
 
-        bool isAsync = funbox->isAsync();
-        bool isGenerator = funbox->isGenerator();
-        auto emitLambda = [index, isAsync, isGenerator](BytecodeEmitter* bce,
-                                                        const NameLocation&, bool) {
-            if (isAsync) {
-                return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false,
-                                             /* isArrow = */ false, isGenerator);
-            }
-            return bce->emitIndexOp(JSOP_LAMBDA, index);
-        };
-
-        if (!emitInitializeName(name, emitLambda)) {
+        NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
+        if (!noe.prepareForRhs()) {
+            return false;
+        }
+        if (funbox->isAsync()) {
+            if (!emitAsyncWrapper(index, /* needsHomeObject = */ false,
+                                  /* isArrow = */ false, funbox->isGenerator()))
+            {
+                return false;
+            }
+        } else {
+            if (!emitIndexOp(JSOP_LAMBDA, index)) {
+                return false;
+            }
+        }
+        if (!noe.emitAssignment()) {
             return false;
         }
         if (!emit1(JSOP_POP)) {
             return false;
         }
     }
 
     return true;
@@ -7061,53 +6777,23 @@ BytecodeEmitter::isRestParameter(ParseNo
 
 bool
 BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, bool isCall, bool isNew)
 {
     bool needsThis = !isCall;
     switch (callee->getKind()) {
       case ParseNodeKind::Name: {
         JSAtom* name = callee->name();
-        NameLocation loc = lookupName(name);
-        if (!emitGetNameAtLocation(name, loc)) {          // CALLEE
-            return false;
-        }
-
-        if (isCall) {
-            switch (loc.kind()) {
-              case NameLocation::Kind::Dynamic: {
-                JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
-                if (!emitAtomOp(name, thisOp)) {
-                    return false;
-                }
-                break;
-              }
-
-              case NameLocation::Kind::Global:
-                if (!emitAtomOp(name, JSOP_GIMPLICITTHIS)) {
-                    return false;
-                }
-                break;
-
-              case NameLocation::Kind::Intrinsic:
-              case NameLocation::Kind::NamedLambdaCallee:
-              case NameLocation::Kind::Import:
-              case NameLocation::Kind::ArgumentSlot:
-              case NameLocation::Kind::FrameSlot:
-              case NameLocation::Kind::EnvironmentCoordinate:
-                if (!emit1(JSOP_UNDEFINED)) {
-                    return false;
-                }
-                break;
-
-              case NameLocation::Kind::DynamicAnnexBVar:
-                MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
-            }
-        }
-
+        NameOpEmitter noe(this, name,
+                          isCall
+                          ? NameOpEmitter::Kind::Call
+                          : NameOpEmitter::Kind::Get);
+        if (!noe.emitGet()) {                             // 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
@@ -8248,24 +7934,26 @@ BytecodeEmitter::emitFunctionFormalParam
                         }
 
                         // The '.this' and '.generator' function special
                         // bindings should never appear in the extra var
                         // scope. 'arguments', however, may.
                         MOZ_ASSERT(name != cx->names().dotThis &&
                                    name != cx->names().dotGenerator);
 
+                        NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
+                        if (!noe.prepareForRhs()) {
+                            return false;
+                        }
+
                         NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope);
-                        auto emitRhs = [&name, &paramLoc](BytecodeEmitter* bce,
-                                                          const NameLocation&, bool)
-                        {
-                            return bce->emitGetNameAtLocation(name, paramLoc);
-                        };
-
-                        if (!emitInitializeName(name, emitRhs)) {
+                        if (!emitGetNameAtLocation(name, paramLoc)) {
+                            return false;
+                        }
+                        if (!noe.emitAssignment()) {
                             return false;
                         }
                         if (!emit1(JSOP_POP)) {
                             return false;
                         }
                     }
                 }
             }
@@ -8395,50 +8083,37 @@ BytecodeEmitter::emitFunctionFormalParam
                                       : DestructuringDeclaration))
             {
                 return false;
             }
 
             if (!emit1(JSOP_POP)) {
                 return false;
             }
-        } else {
+        } else if (hasParameterExprs || isRest) {
             RootedAtom paramName(cx, bindingElement->name());
             NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope);
-
+            NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize);
+            if (!noe.prepareForRhs()) {
+                return false;
+            }
             if (hasParameterExprs) {
-                auto emitRhs = [argSlot, initializer, isRest](BytecodeEmitter* bce,
-                                                              const NameLocation&, bool)
-                {
-                    // If we had an initializer or a rest parameter, the value is
-                    // already on the stack.
-                    if (!initializer && !isRest) {
-                        return bce->emitArgOp(JSOP_GETARG, argSlot);
+                // If we had an initializer or a rest parameter, the value is
+                // already on the stack.
+                if (!initializer && !isRest) {
+                    if (!emitArgOp(JSOP_GETARG, argSlot)) {
+                        return false;
                     }
-                    return true;
-                };
-
-                if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, emitRhs, true)) {
-                    return false;
                 }
-                if (!emit1(JSOP_POP)) {
-                    return false;
-                }
-            } else if (isRest) {
-                // The rest value is already on top of the stack.
-                auto nop = [](BytecodeEmitter*, const NameLocation&, bool) {
-                    return true;
-                };
-
-                if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, nop, true)) {
-                    return false;
-                }
-                if (!emit1(JSOP_POP)) {
-                    return false;
-                }
+            }
+            if (!noe.emitAssignment()) {
+                return false;
+            }
+            if (!emit1(JSOP_POP)) {
+                return false;
             }
         }
 
         if (paramExprVarScope) {
             if (!paramExprVarScope->leave(this)) {
                 return false;
             }
         }
@@ -8454,21 +8129,24 @@ BytecodeEmitter::emitInitializeFunctionS
 
     auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name,
                                                 JSOp op)
     {
         // A special name must be slotful, either on the frame or on the
         // call environment.
         MOZ_ASSERT(bce->lookupName(name).hasKnownSlot());
 
-        auto emitInitial = [op](BytecodeEmitter* bce, const NameLocation&, bool) {
-            return bce->emit1(op);
-        };
-
-        if (!bce->emitInitializeName(name, emitInitial)) {
+        NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize);
+        if (!noe.prepareForRhs()) {
+            return false;
+        }
+        if (!bce->emit1(op)) {
+            return false;
+        }
+        if (!noe.emitAssignment()) {
             return false;
         }
         if (!bce->emit1(JSOP_POP)) {
             return false;
         }
 
         return true;
     };
@@ -8555,24 +8233,31 @@ BytecodeEmitter::emitFunctionBody(ParseN
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitLexicalInitialization(ParseNode* pn)
 {
+    NameOpEmitter noe(this, pn->name(), NameOpEmitter::Kind::Initialize);
+    if (!noe.prepareForRhs()) {
+        return false;
+    }
+
     // The caller has pushed the RHS to the top of the stack. Assert that the
     // name is lexical and no BIND[G]NAME ops were emitted.
-    auto assertLexical = [](BytecodeEmitter*, const NameLocation& loc, bool emittedBindOp) {
-        MOZ_ASSERT(loc.isLexical());
-        MOZ_ASSERT(!emittedBindOp);
-        return true;
-    };
-    return emitInitializeName(pn, assertLexical);
+    MOZ_ASSERT(noe.loc().isLexical());
+    MOZ_ASSERT(!noe.emittedBindOp());
+
+    if (!noe.emitAssignment()) {
+        return false;
+    }
+
+    return true;
 }
 
 // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
 // (BindingClassDeclarationEvaluation).
 bool
 BytecodeEmitter::emitClass(ClassNode* classNode)
 {
     ClassNames* names = classNode->names();
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -576,51 +576,21 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     // definitely knows that a given local slot is unaliased, this function may be
     // used as a non-asserting version of emitUint16Operand.
     MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot);
 
     MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot);
     MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec);
 
     MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc);
-    MOZ_MUST_USE bool emitGetNameAtLocationForCompoundAssignment(JSAtom* name,
-                                                                 const NameLocation& loc);
     MOZ_MUST_USE bool emitGetName(JSAtom* name) {
         return emitGetNameAtLocation(name, lookupName(name));
     }
     MOZ_MUST_USE bool emitGetName(ParseNode* pn);
 
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
-                                                        RHSEmitter emitRhs, bool initialize);
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitSetOrInitializeName(HandleAtom name, RHSEmitter emitRhs,
-                                              bool initialize)
-    {
-        return emitSetOrInitializeNameAtLocation(name, lookupName(name), emitRhs, initialize);
-    }
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitSetName(ParseNode* pn, RHSEmitter emitRhs) {
-        RootedAtom name(cx, pn->name());
-        return emitSetName(name, emitRhs);
-    }
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitSetName(HandleAtom name, RHSEmitter emitRhs) {
-        return emitSetOrInitializeName(name, emitRhs, false);
-    }
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitInitializeName(ParseNode* pn, RHSEmitter emitRhs) {
-        RootedAtom name(cx, pn->name());
-        return emitInitializeName(name, emitRhs);
-    }
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitInitializeName(HandleAtom name, RHSEmitter emitRhs) {
-        return emitSetOrInitializeName(name, emitRhs, true);
-    }
-
     MOZ_MUST_USE bool emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc);
 
     MOZ_MUST_USE bool emitNameIncDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitDeclarationList(ListNode* declList);
     MOZ_MUST_USE bool emitSingleDeclaration(ParseNode* declList, ParseNode* decl,
                                             ParseNode* initializer);
 
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/NameOpEmitter.cpp
@@ -0,0 +1,381 @@
+/* -*- 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/NameOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SharedContext.h"
+#include "frontend/TDZCheckCache.h"
+#include "vm/Opcodes.h"
+#include "vm/Scope.h"
+#include "vm/StringType.h"
+
+using namespace js;
+using namespace js::frontend;
+
+NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, Kind kind)
+  : bce_(bce),
+    kind_(kind),
+    name_(bce_->cx, name),
+    loc_(bce_->lookupName(name_))
+{}
+
+NameOpEmitter::NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, const NameLocation& loc,
+                             Kind kind)
+  : bce_(bce),
+    kind_(kind),
+    name_(bce_->cx, name),
+    loc_(loc)
+{}
+
+bool
+NameOpEmitter::emitGet()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    switch (loc_.kind()) {
+      case NameLocation::Kind::Dynamic:
+        if (!bce_->emitAtomOp(name_, JSOP_GETNAME)) { // VAL
+            return false;
+        }
+        break;
+      case NameLocation::Kind::Global:
+        if (!bce_->emitAtomOp(name_, JSOP_GETGNAME)) {// VAL
+            return false;
+        }
+        break;
+      case NameLocation::Kind::Intrinsic:
+        if (!bce_->emitAtomOp(name_, JSOP_GETINTRINSIC)) {
+            return false;                             // VAL
+        }
+        break;
+      case NameLocation::Kind::NamedLambdaCallee:
+        if (!bce_->emit1(JSOP_CALLEE)) {              // VAL
+            return false;
+        }
+        break;
+      case NameLocation::Kind::Import:
+        if (!bce_->emitAtomOp(name_, JSOP_GETIMPORT)) {
+            return false;                             // VAL
+        }
+        break;
+      case NameLocation::Kind::ArgumentSlot:
+        if (!bce_->emitArgOp(JSOP_GETARG, loc_.argumentSlot())) {
+            return false;                             // VAL
+        }
+        break;
+      case NameLocation::Kind::FrameSlot:
+        if (loc_.isLexical()) {
+            if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
+                return false;
+            }
+        }
+        if (!bce_->emitLocalOp(JSOP_GETLOCAL, loc_.frameSlot())) {
+            return false;                             // VAL
+        }
+        break;
+      case NameLocation::Kind::EnvironmentCoordinate:
+        if (loc_.isLexical()) {
+            if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
+                return false;
+            }
+        }
+        if (!bce_->emitEnvCoordOp(JSOP_GETALIASEDVAR, loc_.environmentCoordinate())) {
+            return false;                             // VAL
+        }
+        break;
+      case NameLocation::Kind::DynamicAnnexBVar:
+        MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+    }
+
+    if (isCall()) {
+        switch (loc_.kind()) {
+          case NameLocation::Kind::Dynamic: {
+            JSOp thisOp = bce_->needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
+            if (!bce_->emitAtomOp(name_, thisOp)) {   // CALLEE THIS
+                return false;
+            }
+            break;
+          }
+          case NameLocation::Kind::Global:
+            if (!bce_->emitAtomOp(name_, JSOP_GIMPLICITTHIS)) {
+                return false;                         // CALLEE THIS
+            }
+            break;
+          case NameLocation::Kind::Intrinsic:
+          case NameLocation::Kind::NamedLambdaCallee:
+          case NameLocation::Kind::Import:
+          case NameLocation::Kind::ArgumentSlot:
+          case NameLocation::Kind::FrameSlot:
+          case NameLocation::Kind::EnvironmentCoordinate:
+            if (!bce_->emit1(JSOP_UNDEFINED)) {       // CALLEE UNDEF
+                return false;
+            }
+            break;
+          case NameLocation::Kind::DynamicAnnexBVar:
+            MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Get;
+#endif
+    return true;
+}
+
+bool
+NameOpEmitter::prepareForRhs()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    switch (loc_.kind()) {
+      case NameLocation::Kind::Dynamic:
+      case NameLocation::Kind::Import:
+      case NameLocation::Kind::DynamicAnnexBVar:
+        if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
+            return false;
+        }
+        if (loc_.kind() == NameLocation::Kind::DynamicAnnexBVar) {
+            // Annex B vars always go on the nearest variable environment,
+            // even if lexical environments in between contain same-named
+            // bindings.
+            if (!bce_->emit1(JSOP_BINDVAR)) {         // ENV
+                return false;
+            }
+        } else {
+            if (!bce_->emitIndexOp(JSOP_BINDNAME, atomIndex_)) {
+                return false;                         // ENV
+            }
+        }
+        emittedBindOp_ = true;
+        break;
+      case NameLocation::Kind::Global:
+        if (!bce_->makeAtomIndex(name_, &atomIndex_)) {
+            return false;
+        }
+        if (loc_.isLexical() && isInitialize()) {
+            // INITGLEXICAL always gets the global lexical scope. It doesn't
+            // need a BINDGNAME.
+            MOZ_ASSERT(bce_->innermostScope()->is<GlobalScope>());
+        } else {
+            if (!bce_->emitIndexOp(JSOP_BINDGNAME, atomIndex_)) {
+                return false;                         // ENV
+            }
+            emittedBindOp_ = true;
+        }
+        break;
+      case NameLocation::Kind::Intrinsic:
+        break;
+      case NameLocation::Kind::NamedLambdaCallee:
+        break;
+      case NameLocation::Kind::ArgumentSlot: {
+        // If we assign to a positional formal parameter and the arguments
+        // object is unmapped (strict mode or function with
+        // default/rest/destructing args), parameters do not alias
+        // arguments[i], and to make the arguments object reflect initial
+        // parameter values prior to any mutation we create it eagerly
+        // whenever parameters are (or might, in the case of calls to eval)
+        // assigned.
+        FunctionBox* funbox = bce_->sc->asFunctionBox();
+        if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) {
+            funbox->setDefinitelyNeedsArgsObj();
+        }
+        break;
+      }
+      case NameLocation::Kind::FrameSlot:
+        break;
+      case NameLocation::Kind::EnvironmentCoordinate:
+        break;
+    }
+
+    // For compound assignments, first get the LHS value, then emit
+    // the RHS and the op.
+    if (isCompoundAssignment() || isIncDec()) {
+        if (loc_.kind() == NameLocation::Kind::Dynamic) {
+            // For dynamic accesses we need to emit GETBOUNDNAME instead of
+            // GETNAME for correctness: looking up @@unscopables on the
+            // environment chain (due to 'with' environments) must only happen
+            // once.
+            //
+            // GETBOUNDNAME uses the environment already pushed on the stack
+            // from the earlier BINDNAME.
+            if (!bce_->emit1(JSOP_DUP)) {             // ENV ENV
+                return false;
+            }
+            if (!bce_->emitAtomOp(name_, JSOP_GETBOUNDNAME)) {
+                return false;                         // ENV V
+            }
+        } else {
+            if (!emitGet()) {                         // ENV? V
+                return false;
+            }
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Rhs;
+#endif
+    return true;
+}
+
+bool
+NameOpEmitter::emitAssignment()
+{
+    MOZ_ASSERT(state_ == State::Rhs);
+
+    switch (loc_.kind()) {
+      case NameLocation::Kind::Dynamic:
+      case NameLocation::Kind::Import:
+      case NameLocation::Kind::DynamicAnnexBVar:
+        if (!bce_->emitIndexOp(bce_->strictifySetNameOp(JSOP_SETNAME), atomIndex_)) {
+            return false;
+        }
+        break;
+      case NameLocation::Kind::Global: {
+        JSOp op;
+        if (emittedBindOp_) {
+            op = bce_->strictifySetNameOp(JSOP_SETGNAME);
+        } else {
+            op = JSOP_INITGLEXICAL;
+        }
+        if (!bce_->emitIndexOp(op, atomIndex_)) {
+            return false;
+        }
+        break;
+      }
+      case NameLocation::Kind::Intrinsic:
+        if (!bce_->emitAtomOp(name_, JSOP_SETINTRINSIC)) {
+            return false;
+        }
+        break;
+      case NameLocation::Kind::NamedLambdaCallee:
+        // Assigning to the named lambda is a no-op in sloppy mode but
+        // throws in strict mode.
+        if (bce_->sc->strict()) {
+            if (!bce_->emit1(JSOP_THROWSETCALLEE)) {
+                return false;
+            }
+        }
+        break;
+      case NameLocation::Kind::ArgumentSlot:
+        if (!bce_->emitArgOp(JSOP_SETARG, loc_.argumentSlot())) {
+            return false;
+        }
+        break;
+      case NameLocation::Kind::FrameSlot: {
+        JSOp op = JSOP_SETLOCAL;
+        if (loc_.isLexical()) {
+            if (isInitialize()) {
+                op = JSOP_INITLEXICAL;
+            } else {
+                if (loc_.isConst()) {
+                    op = JSOP_THROWSETCONST;
+                }
+
+                if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
+                    return false;
+                }
+            }
+        }
+        if (!bce_->emitLocalOp(op, loc_.frameSlot())) {
+            return false;
+        }
+        if (op == JSOP_INITLEXICAL) {
+            if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, DontCheckTDZ)) {
+                return false;
+            }
+        }
+        break;
+      }
+      case NameLocation::Kind::EnvironmentCoordinate: {
+        JSOp op = JSOP_SETALIASEDVAR;
+        if (loc_.isLexical()) {
+            if (isInitialize()) {
+                op = JSOP_INITALIASEDLEXICAL;
+            } else {
+                if (loc_.isConst()) {
+                    op = JSOP_THROWSETALIASEDCONST;
+                }
+
+                if (!bce_->emitTDZCheckIfNeeded(name_, loc_)) {
+                    return false;
+                }
+            }
+        }
+        if (loc_.bindingKind() == BindingKind::NamedLambdaCallee) {
+            // Assigning to the named lambda is a no-op in sloppy mode and throws
+            // in strict mode.
+            op = JSOP_THROWSETALIASEDCONST;
+            if (bce_->sc->strict()) {
+                if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) {
+                    return false;
+                }
+            }
+        } else {
+            if (!bce_->emitEnvCoordOp(op, loc_.environmentCoordinate())) {
+                return false;
+            }
+        }
+        if (op == JSOP_INITALIASEDLEXICAL) {
+            if (!bce_->innermostTDZCheckCache->noteTDZCheck(bce_, name_, DontCheckTDZ)) {
+                return false;
+            }
+        }
+        break;
+      }
+    }
+
+#ifdef DEBUG
+    state_ = State::Assignment;
+#endif
+    return true;
+}
+
+bool
+NameOpEmitter::emitIncDec()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+    if (!prepareForRhs()) {                           // ENV? V
+        return false;
+    }
+    if (!bce_->emit1(JSOP_POS)) {                     // ENV? N
+        return false;
+    }
+    if (isPostIncDec()) {
+        if (!bce_->emit1(JSOP_DUP)) {                 // ENV? N? N
+            return false;
+        }
+    }
+    if (!bce_->emit1(JSOP_ONE)) {                     // ENV? N? N 1
+        return false;
+    }
+    if (!bce_->emit1(binOp)) {                        // ENV? N? N+1
+        return false;
+    }
+    if (isPostIncDec() && emittedBindOp()) {
+        if (!bce_->emit2(JSOP_PICK, 2)) {             // N? N+1 ENV?
+            return false;
+        }
+        if (!bce_->emit1(JSOP_SWAP)) {                // N? ENV? N+1
+            return false;
+        }
+    }
+    if (!emitAssignment()) {                          // 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/NameOpEmitter.h
@@ -0,0 +1,193 @@
+/* -*- 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_NameOpEmitter_h
+#define frontend_NameOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+#include <stdint.h>
+
+#include "frontend/NameAnalysisTypes.h"
+#include "js/TypeDecls.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for name operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `name;`
+//     NameOpEmitter noe(this, atom_of_name
+//                       ElemOpEmitter::Kind::Get);
+//     noe.emitGet();
+//
+//   `name();`
+//     this is handled in CallOrNewEmitter
+//
+//   `name++;`
+//     NameOpEmitter noe(this, atom_of_name
+//                       ElemOpEmitter::Kind::PostIncrement);
+//     noe.emitIncDec();
+//
+//   `name = 10;`
+//     NameOpEmitter noe(this, atom_of_name
+//                       ElemOpEmitter::Kind::SimpleAssignment);
+//     noe.prepareForRhs();
+//     emit(10);
+//     noe.emitAssignment();
+//
+//   `name += 10;`
+//     NameOpEmitter noe(this, atom_of_name
+//                       ElemOpEmitter::Kind::CompoundAssignment);
+//     noe.prepareForRhs();
+//     emit(10);
+//     emit_add_op_here();
+//     noe.emitAssignment();
+//
+//   `name = 10;` part of `let name = 10;`
+//     NameOpEmitter noe(this, atom_of_name
+//                       ElemOpEmitter::Kind::Initialize);
+//     noe.prepareForRhs();
+//     emit(10);
+//     noe.emitAssignment();
+//
+class MOZ_STACK_CLASS NameOpEmitter
+{
+  public:
+    enum class Kind {
+        Get,
+        Call,
+        PostIncrement,
+        PreIncrement,
+        PostDecrement,
+        PreDecrement,
+        SimpleAssignment,
+        CompoundAssignment,
+        Initialize
+    };
+
+  private:
+    BytecodeEmitter* bce_;
+
+    Kind kind_;
+
+    bool emittedBindOp_ = false;
+
+    RootedAtom name_;
+
+    uint32_t atomIndex_;
+
+    NameLocation loc_;
+
+#ifdef DEBUG
+    // The state of this emitter.
+    //
+    //               [Get]
+    //               [Call]
+    // +-------+      emitGet +-----+
+    // | Start |-+-+--------->| Get |
+    // +-------+   |          +-----+
+    //             |
+    //             | [PostIncrement]
+    //             | [PreIncrement]
+    //             | [PostDecrement]
+    //             | [PreDecrement]
+    //             |   emitIncDec +--------+
+    //             +------------->| IncDec |
+    //             |              +--------+
+    //             |
+    //             | [SimpleAssignment]
+    //             |                        prepareForRhs +-----+
+    //             +--------------------->+-------------->| Rhs |-+
+    //             |                      ^               +-----+ |
+    //             |                      |                       |
+    //             |                      |    +------------------+
+    //             | [CompoundAssignment] |    |
+    //             |   emitGet +-----+    |    | emitAssignment +------------+
+    //             +---------->| Get |----+    + -------------->| Assignment |
+    //                         +-----+                          +------------+
+    enum class State {
+        // The initial state.
+        Start,
+
+        // After calling emitGet.
+        Get,
+
+        // After calling emitIncDec.
+        IncDec,
+
+        // After calling prepareForRhs.
+        Rhs,
+
+        // After calling emitAssignment.
+        Assignment,
+    };
+    State state_ = State::Start;
+#endif
+
+  public:
+    NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, Kind kind);
+    NameOpEmitter(BytecodeEmitter* bce, JSAtom* name, const NameLocation& loc, Kind kind);
+
+  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 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 isInitialize() const {
+        return kind_ == Kind::Initialize;
+    }
+
+  public:
+    MOZ_MUST_USE bool emittedBindOp() const {
+        return emittedBindOp_;
+    }
+
+    MOZ_MUST_USE const NameLocation& loc() const {
+        return loc_;
+    }
+
+    MOZ_MUST_USE bool emitGet();
+    MOZ_MUST_USE bool prepareForRhs();
+    MOZ_MUST_USE bool emitAssignment();
+    MOZ_MUST_USE bool emitIncDec();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_NameOpEmitter_h */
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -233,16 +233,17 @@ UNIFIED_SOURCES += [
     'frontend/ExpressionStatementEmitter.cpp',
     'frontend/FoldConstants.cpp',
     'frontend/ForInEmitter.cpp',
     'frontend/ForOfEmitter.cpp',
     'frontend/ForOfLoopControl.cpp',
     'frontend/IfEmitter.cpp',
     'frontend/JumpList.cpp',
     'frontend/NameFunctions.cpp',
+    'frontend/NameOpEmitter.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',