Bug 1501577 - Add PropertyEmitter, ObjectEmitter, and ClassEmitter. r=jorendorff
authorTooru Fujisawa <arai_a@mac.com>
Fri, 18 Jan 2019 11:52:33 +0900
changeset 454406 fe634797760796afcba95f49d9288c34f3de4865
parent 454405 fa2cc1a9e81f5646a4f550b1eed01dce465542ac
child 454407 e0f21f574d36309f94a832c8985223e4b46a0dd2
push id35396
push userebalazs@mozilla.com
push dateFri, 18 Jan 2019 09:40:42 +0000
treeherdermozilla-central@3aa256c255f6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1501577
milestone66.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 1501577 - Add PropertyEmitter, ObjectEmitter, and ClassEmitter. r=jorendorff
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/ObjectEmitter.cpp
js/src/frontend/ObjectEmitter.h
js/src/frontend/moz.build
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -35,16 +35,17 @@
 #include "frontend/ExpressionStatementEmitter.h"
 #include "frontend/ForInEmitter.h"
 #include "frontend/ForOfEmitter.h"
 #include "frontend/ForOfLoopControl.h"
 #include "frontend/IfEmitter.h"
 #include "frontend/LabelEmitter.h"  // LabelEmitter
 #include "frontend/ModuleSharedContext.h"
 #include "frontend/NameOpEmitter.h"
+#include "frontend/ObjectEmitter.h"  // PropertyEmitter, ObjectEmitter, ClassEmitter
 #include "frontend/ParseNode.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"
@@ -3026,34 +3027,40 @@ bool BytecodeEmitter::emitDefault(ParseN
 
 bool BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun,
                                           HandleAtom name) {
   MOZ_ASSERT(maybeFun->isDirectRHSAnonFunction());
 
   if (maybeFun->isKind(ParseNodeKind::Function)) {
     // Function doesn't have 'name' property at this point.
     // Set function's name at compile time.
-    JSFunction* fun = maybeFun->as<CodeNode>().funbox()->function();
-
-    // The inferred name may already be set if this function is an
-    // interpreted lazy function and we OOM'ed after we set the inferred
-    // name the first time.
-    if (fun->hasInferredName()) {
-      MOZ_ASSERT(fun->isInterpretedLazy());
-      MOZ_ASSERT(fun->inferredName() == name);
-
-      return true;
-    }
-
-    fun->setInferredName(name);
-    return true;
+    return setFunName(maybeFun->as<CodeNode>().funbox()->function(), name);
   }
 
   MOZ_ASSERT(maybeFun->isKind(ParseNodeKind::ClassDecl));
 
+  return emitSetClassConstructorName(name);
+}
+
+bool BytecodeEmitter::setFunName(JSFunction* fun, JSAtom* name) {
+  // The inferred name may already be set if this function is an interpreted
+  // lazy function and we OOM'ed after we set the inferred name the first
+  // time.
+  if (fun->hasInferredName()) {
+    MOZ_ASSERT(fun->isInterpretedLazy());
+    MOZ_ASSERT(fun->inferredName() == name);
+
+    return true;
+  }
+
+  fun->setInferredName(name);
+  return true;
+}
+
+bool BytecodeEmitter::emitSetClassConstructorName(JSAtom* name) {
   uint32_t nameIndex;
   if (!makeAtomIndex(name, &nameIndex)) {
     return false;
   }
   if (!emitIndexOp(JSOP_STRING, nameIndex)) {
     //              [stack] FUN NAME
     return false;
   }
@@ -7545,279 +7552,285 @@ bool BytecodeEmitter::emitConditionalExp
   if (!cond.emitEnd()) {
     return false;
   }
   MOZ_ASSERT(cond.pushed() == 1);
 
   return true;
 }
 
-bool BytecodeEmitter::emitPropertyList(ListNode* obj,
-                                       MutableHandlePlainObject objp,
+bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe,
                                        PropListType type) {
+  //                [stack] CTOR? OBJ
+
   for (ParseNode* propdef : obj->contents()) {
     if (propdef->is<ClassField>()) {
       // TODO(khyperia): Implement private field access.
       return false;
     }
-    if (!updateSourceCoordNotes(propdef->pn_pos.begin)) {
-      return false;
-    }
 
     // Handle __proto__: v specially because *only* this form, and no other
     // involving "__proto__", performs [[Prototype]] mutation.
     if (propdef->isKind(ParseNodeKind::MutateProto)) {
+      //            [stack] OBJ
       MOZ_ASSERT(type == ObjectLiteral);
+      if (!pe.prepareForProtoValue(Some(propdef->pn_pos.begin))) {
+        //          [stack] OBJ
+        return false;
+      }
       if (!emitTree(propdef->as<UnaryNode>().kid())) {
-        return false;
-      }
-      objp.set(nullptr);
-      if (!emit1(JSOP_MUTATEPROTO)) {
+        //          [stack] OBJ PROTO
+        return false;
+      }
+      if (!pe.emitMutateProto()) {
+        //          [stack] OBJ
         return false;
       }
       continue;
     }
 
     if (propdef->isKind(ParseNodeKind::Spread)) {
       MOZ_ASSERT(type == ObjectLiteral);
-
-      if (!emit1(JSOP_DUP)) {
-        return false;
-      }
-
+      //            [stack] OBJ
+      if (!pe.prepareForSpreadOperand(Some(propdef->pn_pos.begin))) {
+        //          [stack] OBJ OBJ
+        return false;
+      }
       if (!emitTree(propdef->as<UnaryNode>().kid())) {
-        return false;
-      }
-
-      if (!emitCopyDataProperties(CopyOption::Unfiltered)) {
-        return false;
-      }
-
-      objp.set(nullptr);
+        //          [stack] OBJ OBJ VAL
+        return false;
+      }
+      if (!pe.emitSpread()) {
+        //          [stack] OBJ
+        return false;
+      }
       continue;
     }
 
-    bool extraPop = false;
-    if (type == ClassBody && propdef->as<ClassMethod>().isStatic()) {
-      extraPop = true;
-      if (!emit1(JSOP_DUP2)) {
-        return false;
-      }
-      if (!emit1(JSOP_POP)) {
-        return false;
-      }
-    }
-
-    /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */
-    ParseNode* key = propdef->as<BinaryNode>().left();
-    bool isIndex = false;
+    BinaryNode* prop = &propdef->as<BinaryNode>();
+
+    ParseNode* key = prop->left();
+    ParseNode* propVal = prop->right();
+    bool isPropertyAnonFunctionOrClass = propVal->isDirectRHSAnonFunction();
+    JSOp op = propdef->getOp();
+    MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER ||
+               op == JSOP_INITPROP_SETTER);
+
+    auto emitValue = [this, &propVal, &pe]() {
+      //            [stack] CTOR? OBJ CTOR? KEY?
+
+      if (!emitTree(propVal)) {
+        //          [stack] CTOR? OBJ CTOR? KEY? VAL
+        return false;
+      }
+
+      if (propVal->isKind(ParseNodeKind::Function) &&
+          propVal->as<CodeNode>().funbox()->needsHomeObject()) {
+        FunctionBox* funbox = propVal->as<CodeNode>().funbox();
+        MOZ_ASSERT(funbox->function()->allowSuperProperty());
+
+        if (!pe.emitInitHomeObject(funbox->asyncKind())) {
+          //        [stack] CTOR? OBJ CTOR? KEY? FUN
+          return false;
+        }
+      }
+      return true;
+    };
+
+    PropertyEmitter::Kind kind =
+        (type == ClassBody && propdef->as<ClassMethod>().isStatic())
+            ? PropertyEmitter::Kind::Static
+            : PropertyEmitter::Kind::Prototype;
     if (key->isKind(ParseNodeKind::NumberExpr)) {
+      //            [stack] CTOR? OBJ
+      if (!pe.prepareForIndexPropKey(Some(propdef->pn_pos.begin), kind)) {
+        //          [stack] CTOR? OBJ CTOR?
+        return false;
+      }
       if (!emitNumberOp(key->as<NumericLiteral>().value())) {
-        return false;
-      }
-      isIndex = true;
-    } else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
-               key->isKind(ParseNodeKind::StringExpr)) {
-      // EmitClass took care of constructor already.
+        //          [stack] CTOR? OBJ CTOR? KEY
+        return false;
+      }
+      if (!pe.prepareForIndexPropValue()) {
+        //          [stack] CTOR? OBJ CTOR? KEY
+        return false;
+      }
+      if (!emitValue()) {
+        //          [stack] CTOR? OBJ CTOR? KEY VAL
+        return false;
+      }
+
+      switch (op) {
+        case JSOP_INITPROP:
+          if (!pe.emitInitIndexProp(isPropertyAnonFunctionOrClass)) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
+          break;
+        case JSOP_INITPROP_GETTER:
+          MOZ_ASSERT(!isPropertyAnonFunctionOrClass);
+          if (!pe.emitInitIndexGetter()) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
+          break;
+        case JSOP_INITPROP_SETTER:
+          MOZ_ASSERT(!isPropertyAnonFunctionOrClass);
+          if (!pe.emitInitIndexSetter()) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
+          break;
+        default:
+          MOZ_CRASH("Invalid op");
+      }
+
+      continue;
+    }
+
+    if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
+        key->isKind(ParseNodeKind::StringExpr)) {
+      //            [stack] CTOR? OBJ
+
+      // emitClass took care of constructor already.
       if (type == ClassBody &&
           key->as<NameNode>().atom() == cx->names().constructor &&
           !propdef->as<ClassMethod>().isStatic()) {
         continue;
       }
-    } else {
-      MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
-      if (!emitComputedPropertyName(&key->as<UnaryNode>())) {
-        return false;
-      }
-      isIndex = true;
-    }
-
-    /* Emit code for the property initializer. */
-    ParseNode* propVal = propdef->as<BinaryNode>().right();
-    if (!emitTree(propVal)) {
-      return false;
-    }
-
-    JSOp op = propdef->getOp();
-    MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITPROP_GETTER ||
-               op == JSOP_INITPROP_SETTER);
-
-    FunctionPrefixKind prefixKind = op == JSOP_INITPROP_GETTER
-                                        ? FunctionPrefixKind::Get
-                                        : op == JSOP_INITPROP_SETTER
-                                              ? FunctionPrefixKind::Set
-                                              : FunctionPrefixKind::None;
-
-    if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) {
-      objp.set(nullptr);
-    }
-
-    if (propVal->isKind(ParseNodeKind::Function) &&
-        propVal->as<CodeNode>().funbox()->needsHomeObject()) {
-      FunctionBox* funbox = propVal->as<CodeNode>().funbox();
-      MOZ_ASSERT(funbox->function()->allowSuperProperty());
-      bool isAsync = funbox->isAsync();
-      if (isAsync) {
-        if (!emit1(JSOP_SWAP)) {
-          return false;
+
+      if (!pe.prepareForPropValue(Some(propdef->pn_pos.begin), kind)) {
+        //          [stack] CTOR? OBJ CTOR?
+        return false;
+      }
+      if (!emitValue()) {
+        //          [stack] CTOR? OBJ CTOR? VAL
+        return false;
+      }
+
+      RootedFunction anonFunction(cx);
+      if (isPropertyAnonFunctionOrClass) {
+        MOZ_ASSERT(op == JSOP_INITPROP);
+
+        if (propVal->isKind(ParseNodeKind::Function)) {
+          // When the value is function, we set the function's name
+          // at the compile-time, instead of emitting SETFUNNAME.
+          FunctionBox* funbox = propVal->as<CodeNode>().funbox();
+          anonFunction = funbox->function();
+        } else {
+          // Only object literal can have a property where key is
+          // name and value is an anonymous class.
+          //
+          //   ({ foo: class {} });
+          MOZ_ASSERT(type == ObjectLiteral);
+          MOZ_ASSERT(propVal->isKind(ParseNodeKind::ClassDecl));
         }
       }
-      if (!emitDupAt(1 + isIndex + isAsync)) {
-        return false;
-      }
-      if (!emit1(JSOP_INITHOMEOBJECT)) {
-        return false;
-      }
-      if (isAsync) {
-        if (!emit1(JSOP_POP)) {
-          return false;
-        }
-      }
-    }
-
-    // Class methods are not enumerable.
-    if (type == ClassBody) {
+
+      RootedAtom keyAtom(cx, key->as<NameNode>().atom());
       switch (op) {
         case JSOP_INITPROP:
-          op = JSOP_INITHIDDENPROP;
+          if (!pe.emitInitProp(keyAtom, isPropertyAnonFunctionOrClass,
+                               anonFunction)) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
           break;
         case JSOP_INITPROP_GETTER:
-          op = JSOP_INITHIDDENPROP_GETTER;
+          MOZ_ASSERT(!isPropertyAnonFunctionOrClass);
+          if (!pe.emitInitGetter(keyAtom)) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
           break;
         case JSOP_INITPROP_SETTER:
-          op = JSOP_INITHIDDENPROP_SETTER;
+          MOZ_ASSERT(!isPropertyAnonFunctionOrClass);
+          if (!pe.emitInitSetter(keyAtom)) {
+            //      [stack] CTOR? OBJ
+            return false;
+          }
           break;
         default:
           MOZ_CRASH("Invalid op");
       }
-    }
-
-    if (isIndex) {
-      objp.set(nullptr);
-      switch (op) {
-        case JSOP_INITPROP:
-          op = JSOP_INITELEM;
-          break;
-        case JSOP_INITHIDDENPROP:
-          op = JSOP_INITHIDDENELEM;
-          break;
-        case JSOP_INITPROP_GETTER:
-          op = JSOP_INITELEM_GETTER;
-          break;
-        case JSOP_INITHIDDENPROP_GETTER:
-          op = JSOP_INITHIDDENELEM_GETTER;
-          break;
-        case JSOP_INITPROP_SETTER:
-          op = JSOP_INITELEM_SETTER;
-          break;
-        case JSOP_INITHIDDENPROP_SETTER:
-          op = JSOP_INITHIDDENELEM_SETTER;
-          break;
-        default:
-          MOZ_CRASH("Invalid op");
-      }
-      if (propVal->isDirectRHSAnonFunction()) {
-        if (!emitDupAt(1)) {
-          return false;
-        }
-        if (!emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) {
+
+      continue;
+    }
+
+    MOZ_ASSERT(key->isKind(ParseNodeKind::ComputedName));
+
+    //              [stack] CTOR? OBJ
+
+    if (!pe.prepareForComputedPropKey(Some(propdef->pn_pos.begin), kind)) {
+      //            [stack] CTOR? OBJ CTOR?
+      return false;
+    }
+    if (!emitTree(key->as<UnaryNode>().kid())) {
+      //            [stack] CTOR? OBJ CTOR? KEY
+      return false;
+    }
+    if (!pe.prepareForComputedPropValue()) {
+      //            [stack] CTOR? OBJ CTOR? KEY
+      return false;
+    }
+    if (!emitValue()) {
+      //            [stack] CTOR? OBJ CTOR? KEY VAL
+      return false;
+    }
+
+    switch (op) {
+      case JSOP_INITPROP:
+        if (!pe.emitInitComputedProp(isPropertyAnonFunctionOrClass)) {
+          //        [stack] CTOR? OBJ
           return false;
         }
-      }
-      if (!emit1(op)) {
-        return false;
-      }
-    } else {
-      MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) ||
-                 key->isKind(ParseNodeKind::StringExpr));
-
-      uint32_t index;
-      if (!makeAtomIndex(key->as<NameNode>().atom(), &index)) {
-        return false;
-      }
-
-      if (objp) {
-        MOZ_ASSERT(type == ObjectLiteral);
-        MOZ_ASSERT(!IsHiddenInitOp(op));
-        MOZ_ASSERT(!objp->inDictionaryMode());
-        Rooted<jsid> id(cx, AtomToId(key->as<NameNode>().atom()));
-        if (!NativeDefineDataProperty(cx, objp, id, UndefinedHandleValue,
-                                      JSPROP_ENUMERATE)) {
+        break;
+      case JSOP_INITPROP_GETTER:
+        MOZ_ASSERT(isPropertyAnonFunctionOrClass);
+        if (!pe.emitInitComputedGetter()) {
+          //        [stack] CTOR? OBJ
           return false;
         }
-        if (objp->inDictionaryMode()) {
-          objp.set(nullptr);
-        }
-      }
-
-      if (propVal->isDirectRHSAnonFunction()) {
-        MOZ_ASSERT(prefixKind == FunctionPrefixKind::None);
-
-        RootedAtom keyName(cx, key->as<NameNode>().atom());
-        if (!setOrEmitSetFunName(propVal, keyName)) {
+        break;
+      case JSOP_INITPROP_SETTER:
+        MOZ_ASSERT(isPropertyAnonFunctionOrClass);
+        if (!pe.emitInitComputedSetter()) {
+          //        [stack] CTOR? OBJ
           return false;
         }
-      }
-      if (!emitIndex32(op, index)) {
-        return false;
-      }
-    }
-
-    if (extraPop) {
-      if (!emit1(JSOP_POP)) {
-        return false;
-      }
+        break;
+      default:
+        MOZ_CRASH("Invalid op");
     }
   }
   return true;
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
 // the comment on emitSwitch.
 MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) {
   if (!objNode->hasNonConstInitializer() && objNode->head() &&
       checkSingletonContext()) {
     return emitSingletonInitialiser(objNode);
   }
 
-  /*
-   * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
-   * a new object and defining (in source order) each property on the object
-   * (or mutating the object's [[Prototype]], in the case of __proto__).
-   */
-  ptrdiff_t offset = this->offset();
-  if (!emitNewInit()) {
-    return false;
-  }
-
-  // Try to construct the shape of the object as we go, so we can emit a
-  // JSOP_NEWOBJECT with the final shape instead.
-  // In the case of computed property names and indices, we cannot fix the
-  // shape at bytecode compile time. When the shape cannot be determined,
-  // |obj| is nulled out.
-
-  // No need to do any guessing for the object kind, since we know the upper
-  // bound of how many properties we plan to have.
-  gc::AllocKind kind = gc::GetGCObjectKind(objNode->count());
-  RootedPlainObject obj(
-      cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
-  if (!obj) {
-    return false;
-  }
-
-  if (!emitPropertyList(objNode, &obj, ObjectLiteral)) {
-    return false;
-  }
-
-  if (obj) {
-    // The object survived and has a predictable shape: update the original
-    // bytecode.
-    if (!replaceNewInitWithNewObject(obj, offset)) {
-      return false;
-    }
+  //                [stack]
+
+  ObjectEmitter oe(this);
+  if (!oe.emitObject(objNode->count())) {
+    //              [stack] OBJ
+    return false;
+  }
+  if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
+    //              [stack] OBJ
+    return false;
+  }
+  if (!oe.emitEnd()) {
+    //              [stack] OBJ
+    return false;
   }
 
   return true;
 }
 
 bool BytecodeEmitter::replaceNewInitWithNewObject(JSObject* obj,
                                                   ptrdiff_t offset) {
   ObjectBox* objbox = parser->newObjectBox(obj);
@@ -8385,299 +8398,139 @@ bool BytecodeEmitter::emitFunctionBody(P
       return false;
     }
   }
 
   return true;
 }
 
 bool BytecodeEmitter::emitLexicalInitialization(NameNode* name) {
-  NameOpEmitter noe(this, name->name(), NameOpEmitter::Kind::Initialize);
+  return emitLexicalInitialization(name->name());
+}
+
+bool BytecodeEmitter::emitLexicalInitialization(JSAtom* name) {
+  NameOpEmitter noe(this, 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.
   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();
-  ParseNode* heritageExpression = classNode->heritage();
-  ListNode* classMembers = classNode->memberList();
-  CodeNode* constructor = nullptr;
-  for (ParseNode* mn : classMembers->contents()) {
+static MOZ_ALWAYS_INLINE CodeNode* FindConstructor(JSContext* cx,
+                                                   ListNode* classMethods) {
+  for (ParseNode* mn : classMethods->contents()) {
     if (mn->is<ClassField>()) {
       // TODO(khyperia): Implement private field access.
-      return false;
-    }
+      continue;
+    }
+
     ClassMethod& method = mn->as<ClassMethod>();
     ParseNode& methodName = method.name();
     if (!method.isStatic() &&
         (methodName.isKind(ParseNodeKind::ObjectPropertyName) ||
          methodName.isKind(ParseNodeKind::StringExpr)) &&
         methodName.as<NameNode>().atom() == cx->names().constructor) {
-      constructor = &method.method();
-      break;
-    }
-  }
-
-  bool savedStrictness = sc->setLocalStrictMode(true);
-
-  Maybe<TDZCheckCache> tdzCache;
-  Maybe<EmitterScope> emitterScope;
+      return &method.method();
+    }
+  }
+
+  return nullptr;
+}
+
+// This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
+// (BindingClassDeclarationEvaluation).
+bool BytecodeEmitter::emitClass(ClassNode* classNode) {
+  ClassNames* names = classNode->names();
+  ParseNode* heritageExpression = classNode->heritage();
+  ListNode* classMembers = classNode->memberList();
+  CodeNode* constructor = FindConstructor(cx, classMembers);
+
+  //                [stack]
+
+  ClassEmitter ce(this);
+  RootedAtom innerName(cx);
+  ClassEmitter::Kind kind = ClassEmitter::Kind::Expression;
   if (names) {
-    tdzCache.emplace(this);
-    emitterScope.emplace(this);
-    if (!emitterScope->enterLexical(this, ScopeKind::Lexical,
-                                    classNode->scopeBindings())) {
-      return false;
-    }
-  }
-
-  // Pseudocode for class declarations:
-  //
-  //     class extends BaseExpression {
-  //       constructor() { ... }
-  //       ...
-  //       }
-  //
-  //
-  //   if defined <BaseExpression> {
-  //     let heritage = BaseExpression;
-  //
-  //     if (heritage !== null) {
-  //       funProto = heritage;
-  //       objProto = heritage.prototype;
-  //     } else {
-  //       funProto = %FunctionPrototype%;
-  //       objProto = null;
-  //     }
-  //   } else {
-  //     objProto = %ObjectPrototype%;
-  //   }
-  //
-  //   let homeObject = ObjectCreate(objProto);
-  //
-  //   if defined <constructor> {
-  //     if defined <BaseExpression> {
-  //       cons = DefineMethod(<constructor>, proto=homeObject,
-  //                           funProto=funProto);
-  //     } else {
-  //       cons = DefineMethod(<constructor>, proto=homeObject);
-  //     }
-  //   } else {
-  //     if defined <BaseExpression> {
-  //       cons = DefaultDerivedConstructor(proto=homeObject,
-  //                                        funProto=funProto);
-  //     } else {
-  //       cons = DefaultConstructor(proto=homeObject);
-  //     }
-  //   }
-  //
-  //   cons.prototype = homeObject;
-  //   homeObject.constructor = cons;
-  //
-  //   EmitPropertyList(...)
+    innerName = names->innerBinding()->name();
+    MOZ_ASSERT(innerName);
+
+    if (names->outerBinding()) {
+      MOZ_ASSERT(names->outerBinding()->name());
+      MOZ_ASSERT(names->outerBinding()->name() == innerName);
+      kind = ClassEmitter::Kind::Declaration;
+    }
+
+    if (!ce.emitScopeForNamedClass(classNode->scopeBindings())) {
+      //            [stack]
+      return false;
+    }
+  }
 
   // This is kind of silly. In order to the get the home object defined on
   // the constructor, we have to make it second, but we want the prototype
   // on top for EmitPropertyList, because we expect static properties to be
   // rarer. The result is a few more swaps than we would like. Such is life.
-  if (heritageExpression) {
-    InternalIfEmitter ifThenElse(this);
-
+  bool isDerived = !!heritageExpression;
+  if (isDerived) {
     if (!emitTree(heritageExpression)) {
-      //            [stack] ... HERITAGE
-      return false;
-    }
-
-    // Heritage must be null or a non-generator constructor
-    if (!emit1(JSOP_CHECKCLASSHERITAGE)) {
-      //            [stack] ... HERITAGE
-      return false;
-    }
-
-    // [IF] (heritage !== null)
-    if (!emit1(JSOP_DUP)) {
-      //            [stack] ... HERITAGE HERITAGE
-      return false;
-    }
-    if (!emit1(JSOP_NULL)) {
-      //            [stack] ... HERITAGE HERITAGE NULL
-      return false;
-    }
-    if (!emit1(JSOP_STRICTNE)) {
-      //            [stack] ... HERITAGE NE
-      return false;
-    }
-
-    // [THEN] funProto = heritage, objProto = heritage.prototype
-    if (!ifThenElse.emitThenElse()) {
-      return false;
-    }
-    if (!emit1(JSOP_DUP)) {
-      //            [stack] ... HERITAGE HERITAGE
-      return false;
-    }
-    if (!emitAtomOp(cx->names().prototype, JSOP_GETPROP)) {
-      //            [stack] ... HERITAGE PROTO
-      return false;
-    }
-
-    // [ELSE] funProto = %FunctionPrototype%, objProto = null
-    if (!ifThenElse.emitElse()) {
-      return false;
-    }
-    if (!emit1(JSOP_POP)) {
-      //            [stack] ...
-      return false;
-    }
-    if (!emit2(JSOP_BUILTINPROTO, JSProto_Function)) {
-      //            [stack] ... PROTO
-      return false;
-    }
-    if (!emit1(JSOP_NULL)) {
-      //            [stack] ... PROTO NULL
-      return false;
-    }
-
-    // [ENDIF]
-    if (!ifThenElse.emitEnd()) {
-      return false;
-    }
-
-    if (!emit1(JSOP_OBJWITHPROTO)) {
-      //            [stack] ... HERITAGE HOMEOBJ
-      return false;
-    }
-    if (!emit1(JSOP_SWAP)) {
-      //            [stack] ... HOMEOBJ HERITAGE
+      //            [stack] HERITAGE
+      return false;
+    }
+    if (!ce.emitDerivedClass(innerName)) {
+      //            [stack] HERITAGE HOMEOBJ
       return false;
     }
   } else {
-    if (!emitNewInit()) {
-      //            [stack] ... HOMEOBJ
+    if (!ce.emitClass(innerName)) {
+      //            [stack] HOMEOBJ
       return false;
     }
   }
 
   // Stack currently has HOMEOBJ followed by optional HERITAGE. When HERITAGE
   // is not used, an implicit value of %FunctionPrototype% is implied.
-
   if (constructor) {
-    if (!emitFunction(constructor, !!heritageExpression)) {
-      //            [stack] ... HOMEOBJ CONSTRUCTOR
-      return false;
-    }
-    if (constructor->funbox()->needsHomeObject()) {
-      if (!emitDupAt(1)) {
-        //          [stack] ... HOMEOBJ CONSTRUCTOR HOMEOBJ
-        return false;
-      }
-      if (!emit1(JSOP_INITHOMEOBJECT)) {
-        //          [stack] ... HOMEOBJ CONSTRUCTOR
-        return false;
-      }
+    bool needsHomeObject = constructor->funbox()->needsHomeObject();
+    // HERITAGE is consumed inside emitFunction.
+    if (!emitFunction(constructor, isDerived)) {
+      //            [stack] HOMEOBJ CTOR
+      return false;
+    }
+    if (!ce.emitInitConstructor(needsHomeObject)) {
+      //            [stack] CTOR HOMEOBJ
+      return false;
     }
   } else {
-    // In the case of default class constructors, emit the start and end
-    // offsets in the source buffer as source notes so that when we
-    // actually make the constructor during execution, we can give it the
-    // correct toString output.
-    ptrdiff_t classStart = ptrdiff_t(classNode->pn_pos.begin);
-    ptrdiff_t classEnd = ptrdiff_t(classNode->pn_pos.end);
-    if (!newSrcNote3(SRC_CLASS_SPAN, classStart, classEnd)) {
-      return false;
-    }
-
-    JSAtom* name = names ? names->innerBinding()->as<NameNode>().atom()
-                         : cx->names().empty;
-    if (heritageExpression) {
-      if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) {
-        //          [stack] ... HOMEOBJ CONSTRUCTOR
-        return false;
-      }
-    } else {
-      if (!emitAtomOp(name, JSOP_CLASSCONSTRUCTOR)) {
-        //          [stack] ... HOMEOBJ CONSTRUCTOR
-        return false;
-      }
-    }
-  }
-
-  if (!emit1(JSOP_SWAP)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ
-    return false;
-  }
-
-  if (!emit1(JSOP_DUP2)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR HOMEOBJ
-    return false;
-  }
-  if (!emitAtomOp(cx->names().prototype, JSOP_INITLOCKEDPROP)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ CONSTRUCTOR
-    return false;
-  }
-  if (!emitAtomOp(cx->names().constructor, JSOP_INITHIDDENPROP)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ
-    return false;
-  }
-
-  RootedPlainObject obj(cx);
-  if (!emitPropertyList(classMembers, &obj, ClassBody)) {
-    //              [stack] ... CONSTRUCTOR HOMEOBJ
-    return false;
-  }
-
-  if (!emit1(JSOP_POP)) {
-    //              [stack] ... CONSTRUCTOR
-    return false;
-  }
-
-  if (names) {
-    NameNode* innerName = names->innerBinding();
-    if (!emitLexicalInitialization(innerName)) {
-      //            [stack] ... CONSTRUCTOR
-      return false;
-    }
-
-    // Pop the inner scope.
-    if (!emitterScope->leave(this)) {
-      return false;
-    }
-    emitterScope.reset();
-
-    if (NameNode* outerName = names->outerBinding()) {
-      if (!emitLexicalInitialization(outerName)) {
-        //          [stack] ... CONSTRUCTOR
-        return false;
-      }
-      // Only class statements make outer bindings, and they do not leave
-      // themselves on the stack.
-      if (!emit1(JSOP_POP)) {
-        //          [stack] ...
-        return false;
-      }
-    }
-  }
-
-  // The CONSTRUCTOR is left on stack if this is an expression.
-
-  MOZ_ALWAYS_TRUE(sc->setLocalStrictMode(savedStrictness));
+    if (!ce.emitInitDefaultConstructor(Some(classNode->pn_pos.begin),
+                                       Some(classNode->pn_pos.end))) {
+      //            [stack] CTOR HOMEOBJ
+      return false;
+    }
+  }
+  if (!emitPropertyList(classMembers, ce, ClassBody)) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+  if (!ce.emitEnd(kind)) {
+    //              [stack] # class declaration
+    //              [stack]
+    //              [stack] # class expression
+    //              [stack] CTOR
+    return false;
+  }
 
   return true;
 }
 
 bool BytecodeEmitter::emitExportDefault(BinaryNode* exportNode) {
   MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportDefaultStmt));
 
   ParseNode* nameNode = exportNode->left();
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -107,16 +107,17 @@ struct CGResumeOffsetList {
 // sequences.  See bug 1390526.
 typedef Vector<jsbytecode, 64> BytecodeVector;
 typedef Vector<jssrcnote, 64> SrcNotesVector;
 
 class CallOrNewEmitter;
 class ElemOpEmitter;
 class EmitterScope;
 class NestableControl;
+class PropertyEmitter;
 class TDZCheckCache;
 
 struct MOZ_STACK_CLASS BytecodeEmitter {
   SharedContext* const
       sc; /* context shared between parsing and bytecode generation */
 
   JSContext* const cx;
 
@@ -599,18 +600,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
                                                   bool needsProto = false);
   MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode);
 
   MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj,
                                                 ptrdiff_t offset);
 
   MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList);
 
-  MOZ_MUST_USE bool emitPropertyList(ListNode* obj,
-                                     MutableHandlePlainObject objp,
+  MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe,
                                      PropListType type);
 
   // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
   // not used to unconditionally emit JSOP_GETLOCAL. Variable access should
   // instead be emitted using EmitVarOp. In special cases, when the caller
   // 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);
@@ -778,16 +778,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   // that value on the stack with the value defined by |defaultExpr|.
   // |pattern| is a lhs node of the default expression.  If it's an
   // identifier and |defaultExpr| is an anonymous function, |SetFunctionName|
   // is called at compile time.
   MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern);
 
   MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name);
 
+  MOZ_MUST_USE bool setFunName(JSFunction* fun, JSAtom* name);
+  MOZ_MUST_USE bool emitSetClassConstructorName(JSAtom* name);
   MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern);
 
   MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj);
   MOZ_MUST_USE bool emitTemplateString(ListNode* templateString);
   MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp,
                                    ParseNode* rhs);
 
   MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode);
@@ -846,16 +848,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   MOZ_MUST_USE bool emitBreak(PropertyName* label);
   MOZ_MUST_USE bool emitContinue(PropertyName* label);
 
   MOZ_MUST_USE bool emitFunctionFormalParametersAndBody(ListNode* paramsBody);
   MOZ_MUST_USE bool emitFunctionFormalParameters(ListNode* paramsBody);
   MOZ_MUST_USE bool emitInitializeFunctionSpecialNames();
   MOZ_MUST_USE bool emitFunctionBody(ParseNode* funBody);
   MOZ_MUST_USE bool emitLexicalInitialization(NameNode* name);
+  MOZ_MUST_USE bool emitLexicalInitialization(JSAtom* name);
 
   // Emit bytecode for the spread operator.
   //
   // emitSpread expects the current index (I) of the array, the array itself
   // 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 +
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ObjectEmitter.cpp
@@ -0,0 +1,813 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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/ObjectEmitter.h"
+
+#include "mozilla/Assertions.h"  // MOZ_ASSERT
+
+#include "frontend/BytecodeEmitter.h"  // BytecodeEmitter
+#include "frontend/IfEmitter.h"        // IfEmitter
+#include "frontend/SharedContext.h"    // SharedContext
+#include "frontend/SourceNotes.h"      // SRC_*
+#include "gc/AllocKind.h"              // AllocKind
+#include "js/Id.h"                     // jsid
+#include "js/Value.h"                  // UndefinedHandleValue
+#include "vm/BytecodeUtil.h"           // IsHiddenInitOp
+#include "vm/JSContext.h"              // JSContext
+#include "vm/NativeObject.h"           // NativeDefineDataProperty
+#include "vm/ObjectGroup.h"            // TenuredObject
+#include "vm/Opcodes.h"                // JSOP_*
+#include "vm/Runtime.h"                // JSAtomState (cx->names())
+
+#include "gc/ObjectKind-inl.h"  // GetGCObjectKind
+#include "vm/JSAtom-inl.h"      // AtomToId
+#include "vm/JSObject-inl.h"    // NewBuiltinClassInstance
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce)
+    : bce_(bce), obj_(bce->cx) {}
+
+bool PropertyEmitter::prepareForProtoValue(const Maybe<uint32_t>& keyPos) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] CTOR? OBJ CTOR?
+
+  if (keyPos) {
+    if (!bce_->updateSourceCoordNotes(*keyPos)) {
+      return false;
+    }
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::ProtoValue;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitMutateProto() {
+  MOZ_ASSERT(propertyState_ == PropertyState::ProtoValue);
+
+  //                [stack] OBJ PROTO
+
+  if (!bce_->emit1(JSOP_MUTATEPROTO)) {
+    //              [stack] OBJ
+    return false;
+  }
+
+  obj_ = nullptr;
+#ifdef DEBUG
+  propertyState_ = PropertyState::Init;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForSpreadOperand(
+    const Maybe<uint32_t>& spreadPos) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] OBJ
+
+  if (spreadPos) {
+    if (!bce_->updateSourceCoordNotes(*spreadPos)) {
+      return false;
+    }
+  }
+  if (!bce_->emit1(JSOP_DUP)) {
+    //              [stack] OBJ OBJ
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::SpreadOperand;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitSpread() {
+  MOZ_ASSERT(propertyState_ == PropertyState::SpreadOperand);
+
+  //                [stack] OBJ OBJ VAL
+
+  if (!bce_->emitCopyDataProperties(BytecodeEmitter::CopyOption::Unfiltered)) {
+    //              [stack] OBJ
+    return false;
+  }
+
+  obj_ = nullptr;
+#ifdef DEBUG
+  propertyState_ = PropertyState::Init;
+#endif
+  return true;
+}
+
+MOZ_ALWAYS_INLINE bool PropertyEmitter::prepareForProp(
+    const Maybe<uint32_t>& keyPos, bool isStatic, bool isIndexOrComputed) {
+  isStatic_ = isStatic;
+  isIndexOrComputed_ = isIndexOrComputed;
+
+  //                [stack] CTOR? OBJ
+
+  if (keyPos) {
+    if (!bce_->updateSourceCoordNotes(*keyPos)) {
+      return false;
+    }
+  }
+
+  if (isStatic_) {
+    if (!bce_->emit1(JSOP_DUP2)) {
+      //            [stack] CTOR HOMEOBJ CTOR HOMEOBJ
+      return false;
+    }
+    if (!bce_->emit1(JSOP_POP)) {
+      //            [stack] CTOR HOMEOBJ CTOR
+      return false;
+    }
+  }
+
+  return true;
+}
+
+bool PropertyEmitter::prepareForPropValue(const Maybe<uint32_t>& keyPos,
+                                          Kind kind /* = Kind::Prototype */) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] CTOR? OBJ
+
+  if (!prepareForProp(keyPos,
+                      /* isStatic_ = */ kind == Kind::Static,
+                      /* isIndexOrComputed = */ false)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::PropValue;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForIndexPropKey(
+    const Maybe<uint32_t>& keyPos, Kind kind /* = Kind::Prototype */) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] CTOR? OBJ
+
+  obj_ = nullptr;
+
+  if (!prepareForProp(keyPos,
+                      /* isStatic_ = */ kind == Kind::Static,
+                      /* isIndexOrComputed = */ true)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::IndexKey;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForIndexPropValue() {
+  MOZ_ASSERT(propertyState_ == PropertyState::IndexKey);
+
+  //                [stack] CTOR? OBJ CTOR? KEY
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::IndexValue;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForComputedPropKey(
+    const Maybe<uint32_t>& keyPos, Kind kind /* = Kind::Prototype */) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+
+  //                [stack] CTOR? OBJ
+
+  obj_ = nullptr;
+
+  if (!prepareForProp(keyPos,
+                      /* isStatic_ = */ kind == Kind::Static,
+                      /* isIndexOrComputed = */ true)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::ComputedKey;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::prepareForComputedPropValue() {
+  MOZ_ASSERT(propertyState_ == PropertyState::ComputedKey);
+
+  //                [stack] CTOR? OBJ CTOR? KEY
+
+  if (!bce_->emit1(JSOP_TOID)) {
+    //              [stack] CTOR? OBJ CTOR? KEY
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::ComputedValue;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitInitHomeObject(
+    FunctionAsyncKind kind /* = FunctionAsyncKind::SyncFunction */) {
+  MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
+             propertyState_ == PropertyState::IndexValue ||
+             propertyState_ == PropertyState::ComputedValue);
+
+  //                [stack] CTOR? HOMEOBJ CTOR? KEY? FUN
+
+  bool isAsync = kind == FunctionAsyncKind::AsyncFunction;
+  if (isAsync) {
+    //              [stack] CTOR? HOMEOBJ CTOR? KEY? UNWRAPPED WRAPPED
+    if (!bce_->emit1(JSOP_SWAP)) {
+      //            [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED UNWRAPPED
+      return false;
+    }
+  }
+
+  // There are the following values on the stack conditionally, between
+  // HOMEOBJ and FUN:
+  //   * the 2nd CTOR if isStatic_
+  //   * KEY if isIndexOrComputed_
+  //   * WRAPPED if isAsync
+  //
+  // JSOP_INITHOMEOBJECT uses one of the following:
+  //   * HOMEOBJ if !isStatic_
+  //     (`super.foo` points the super prototype property)
+  //   * the 2nd CTOR if isStatic_
+  //     (`super.foo` points the super constructor property)
+  if (!bce_->emitDupAt(1 + isIndexOrComputed_ + isAsync)) {
+    //              [stack] # non-static method
+    //              [stack] CTOR? HOMEOBJ CTOR KEY? WRAPPED? FUN CTOR
+    //              [stack] # static method
+    //              [stack] CTOR? HOMEOBJ KEY? WRAPPED? FUN HOMEOBJ
+    return false;
+  }
+  if (!bce_->emit1(JSOP_INITHOMEOBJECT)) {
+    //              [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED? FUN
+    return false;
+  }
+  if (isAsync) {
+    if (!bce_->emit1(JSOP_POP)) {
+      //            [stack] CTOR? HOMEOBJ CTOR? KEY? WRAPPED
+      return false;
+    }
+  }
+
+#ifdef DEBUG
+  if (propertyState_ == PropertyState::PropValue) {
+    propertyState_ = PropertyState::InitHomeObj;
+  } else if (propertyState_ == PropertyState::IndexValue) {
+    propertyState_ = PropertyState::InitHomeObjForIndex;
+  } else {
+    propertyState_ = PropertyState::InitHomeObjForComputed;
+  }
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitInitProp(
+    JS::Handle<JSAtom*> key, bool isPropertyAnonFunctionOrClass /* = false */,
+    JS::Handle<JSFunction*> anonFunction /* = nullptr */) {
+  return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key,
+                  isPropertyAnonFunctionOrClass, anonFunction);
+}
+
+bool PropertyEmitter::emitInitGetter(JS::Handle<JSAtom*> key) {
+  return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER,
+                  key, false, nullptr);
+}
+
+bool PropertyEmitter::emitInitSetter(JS::Handle<JSAtom*> key) {
+  return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER,
+                  key, false, nullptr);
+}
+
+bool PropertyEmitter::emitInitIndexProp(
+    bool isPropertyAnonFunctionOrClass /* = false */) {
+  return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM,
+                                 FunctionPrefixKind::None,
+                                 isPropertyAnonFunctionOrClass);
+}
+
+bool PropertyEmitter::emitInitIndexGetter() {
+  return emitInitIndexOrComputed(
+      isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER,
+      FunctionPrefixKind::Get, false);
+}
+
+bool PropertyEmitter::emitInitIndexSetter() {
+  return emitInitIndexOrComputed(
+      isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER,
+      FunctionPrefixKind::Set, false);
+}
+
+bool PropertyEmitter::emitInitComputedProp(
+    bool isPropertyAnonFunctionOrClass /* = false */) {
+  return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM : JSOP_INITELEM,
+                                 FunctionPrefixKind::None,
+                                 isPropertyAnonFunctionOrClass);
+}
+
+bool PropertyEmitter::emitInitComputedGetter() {
+  return emitInitIndexOrComputed(
+      isClass_ ? JSOP_INITHIDDENELEM_GETTER : JSOP_INITELEM_GETTER,
+      FunctionPrefixKind::Get, true);
+}
+
+bool PropertyEmitter::emitInitComputedSetter() {
+  return emitInitIndexOrComputed(
+      isClass_ ? JSOP_INITHIDDENELEM_SETTER : JSOP_INITELEM_SETTER,
+      FunctionPrefixKind::Set, true);
+}
+
+bool PropertyEmitter::emitInit(JSOp op, JS::Handle<JSAtom*> key,
+                               bool isPropertyAnonFunctionOrClass,
+                               JS::Handle<JSFunction*> anonFunction) {
+  MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
+             propertyState_ == PropertyState::InitHomeObj);
+
+  MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP ||
+             op == JSOP_INITPROP_GETTER || op == JSOP_INITHIDDENPROP_GETTER ||
+             op == JSOP_INITPROP_SETTER || op == JSOP_INITHIDDENPROP_SETTER);
+
+  //                [stack] CTOR? OBJ CTOR? VAL
+
+  uint32_t index;
+  if (!bce_->makeAtomIndex(key, &index)) {
+    return false;
+  }
+
+  if (obj_) {
+    MOZ_ASSERT(!IsHiddenInitOp(op));
+    MOZ_ASSERT(!obj_->inDictionaryMode());
+    JS::Rooted<JS::PropertyKey> propKey(bce_->cx, AtomToId(key));
+    if (!NativeDefineDataProperty(bce_->cx, obj_, propKey, UndefinedHandleValue,
+                                  JSPROP_ENUMERATE)) {
+      return false;
+    }
+    if (obj_->inDictionaryMode()) {
+      obj_ = nullptr;
+    }
+  }
+
+  if (isPropertyAnonFunctionOrClass) {
+    MOZ_ASSERT(op == JSOP_INITPROP || op == JSOP_INITHIDDENPROP);
+
+    if (anonFunction) {
+      if (!bce_->setFunName(anonFunction, key)) {
+        return false;
+      }
+    } else {
+      // NOTE: This is setting the constructor's name of the class which is
+      //       the property value.  Not of the enclosing class.
+      if (!bce_->emitSetClassConstructorName(key)) {
+        //          [stack] CTOR? OBJ CTOR? FUN
+        return false;
+      }
+    }
+  }
+
+  if (!bce_->emitIndex32(op, index)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+  if (!emitPopClassConstructor()) {
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::Init;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitInitIndexOrComputed(
+    JSOp op, FunctionPrefixKind prefixKind,
+    bool isPropertyAnonFunctionOrClass) {
+  MOZ_ASSERT(propertyState_ == PropertyState::IndexValue ||
+             propertyState_ == PropertyState::InitHomeObjForIndex ||
+             propertyState_ == PropertyState::ComputedValue ||
+             propertyState_ == PropertyState::InitHomeObjForComputed);
+
+  MOZ_ASSERT(op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM ||
+             op == JSOP_INITELEM_GETTER || op == JSOP_INITHIDDENELEM_GETTER ||
+             op == JSOP_INITELEM_SETTER || op == JSOP_INITHIDDENELEM_SETTER);
+
+  //                [stack] CTOR? OBJ CTOR? KEY VAL
+
+  if (isPropertyAnonFunctionOrClass) {
+    if (!bce_->emitDupAt(1)) {
+      //            [stack] CTOR? OBJ CTOR? KEY FUN FUN
+      return false;
+    }
+    if (!bce_->emit2(JSOP_SETFUNNAME, uint8_t(prefixKind))) {
+      //            [stack] CTOR? OBJ CTOR? KEY FUN
+      return false;
+    }
+  }
+
+  if (!bce_->emit1(op)) {
+    //              [stack] CTOR? OBJ CTOR?
+    return false;
+  }
+
+  if (!emitPopClassConstructor()) {
+    return false;
+  }
+
+#ifdef DEBUG
+  propertyState_ = PropertyState::Init;
+#endif
+  return true;
+}
+
+bool PropertyEmitter::emitPopClassConstructor() {
+  if (isStatic_) {
+    //              [stack] CTOR HOMEOBJ CTOR
+
+    if (!bce_->emit1(JSOP_POP)) {
+      //            [stack] CTOR HOMEOBJ
+      return false;
+    }
+  }
+
+  return true;
+}
+
+ObjectEmitter::ObjectEmitter(BytecodeEmitter* bce) : PropertyEmitter(bce) {}
+
+bool ObjectEmitter::emitObject(size_t propertyCount) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(objectState_ == ObjectState::Start);
+
+  //                [stack]
+
+  // Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
+  // a new object and defining (in source order) each property on the object
+  // (or mutating the object's [[Prototype]], in the case of __proto__).
+  top_ = bce_->offset();
+  if (!bce_->emitNewInit()) {
+    //              [stack] OBJ
+    return false;
+  }
+
+  // Try to construct the shape of the object as we go, so we can emit a
+  // JSOP_NEWOBJECT with the final shape instead.
+  // In the case of computed property names and indices, we cannot fix the
+  // shape at bytecode compile time. When the shape cannot be determined,
+  // |obj| is nulled out.
+
+  // No need to do any guessing for the object kind, since we know the upper
+  // bound of how many properties we plan to have.
+  gc::AllocKind kind = gc::GetGCObjectKind(propertyCount);
+  obj_ = NewBuiltinClassInstance<PlainObject>(bce_->cx, kind, TenuredObject);
+  if (!obj_) {
+    return false;
+  }
+
+#ifdef DEBUG
+  objectState_ = ObjectState::Object;
+#endif
+  return true;
+}
+
+bool ObjectEmitter::emitEnd() {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+  MOZ_ASSERT(objectState_ == ObjectState::Object);
+
+  //                [stack] OBJ
+
+  if (obj_) {
+    // The object survived and has a predictable shape: update the original
+    // bytecode.
+    if (!bce_->replaceNewInitWithNewObject(obj_, top_)) {
+      //            [stack] OBJ
+      return false;
+    }
+  }
+
+#ifdef DEBUG
+  objectState_ = ObjectState::End;
+#endif
+  return true;
+}
+
+AutoSaveLocalStrictMode::AutoSaveLocalStrictMode(SharedContext* sc) : sc_(sc) {
+  savedStrictness_ = sc_->setLocalStrictMode(true);
+}
+
+AutoSaveLocalStrictMode::~AutoSaveLocalStrictMode() {
+  if (sc_) {
+    restore();
+  }
+}
+
+void AutoSaveLocalStrictMode::restore() {
+  MOZ_ALWAYS_TRUE(sc_->setLocalStrictMode(savedStrictness_));
+  sc_ = nullptr;
+}
+
+ClassEmitter::ClassEmitter(BytecodeEmitter* bce)
+    : PropertyEmitter(bce), strictMode_(bce->sc), name_(bce->cx) {
+  isClass_ = true;
+}
+
+bool ClassEmitter::emitScopeForNamedClass(
+    JS::Handle<LexicalScope::Data*> scopeBindings) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Start);
+
+  tdzCacheForInnerName_.emplace(bce_);
+  innerNameScope_.emplace(bce_);
+  if (!innerNameScope_->enterLexical(bce_, ScopeKind::Lexical, scopeBindings)) {
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::Scope;
+#endif
+  return true;
+}
+
+bool ClassEmitter::emitClass(JS::Handle<JSAtom*> name) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Start ||
+             classState_ == ClassState::Scope);
+
+  //                [stack]
+
+  setName(name);
+  isDerived_ = false;
+
+  if (!bce_->emitNewInit()) {
+    //              [stack] HOMEOBJ
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::Class;
+#endif
+  return true;
+}
+
+bool ClassEmitter::emitDerivedClass(JS::Handle<JSAtom*> name) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Start ||
+             classState_ == ClassState::Scope);
+
+  //                [stack]
+
+  setName(name);
+  isDerived_ = true;
+
+  InternalIfEmitter ifThenElse(bce_);
+
+  // Heritage must be null or a non-generator constructor
+  if (!bce_->emit1(JSOP_CHECKCLASSHERITAGE)) {
+    //              [stack] HERITAGE
+    return false;
+  }
+
+  // [IF] (heritage !== null)
+  if (!bce_->emit1(JSOP_DUP)) {
+    //              [stack] HERITAGE HERITAGE
+    return false;
+  }
+  if (!bce_->emit1(JSOP_NULL)) {
+    //              [stack] HERITAGE HERITAGE NULL
+    return false;
+  }
+  if (!bce_->emit1(JSOP_STRICTNE)) {
+    //              [stack] HERITAGE NE
+    return false;
+  }
+
+  // [THEN] funProto = heritage, objProto = heritage.prototype
+  if (!ifThenElse.emitThenElse()) {
+    return false;
+  }
+  if (!bce_->emit1(JSOP_DUP)) {
+    //              [stack] HERITAGE HERITAGE
+    return false;
+  }
+  if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_GETPROP)) {
+    //              [stack] HERITAGE PROTO
+    return false;
+  }
+
+  // [ELSE] funProto = %FunctionPrototype%, objProto = null
+  if (!ifThenElse.emitElse()) {
+    return false;
+  }
+  if (!bce_->emit1(JSOP_POP)) {
+    //              [stack]
+    return false;
+  }
+  if (!bce_->emit2(JSOP_BUILTINPROTO, JSProto_Function)) {
+    //              [stack] PROTO
+    return false;
+  }
+  if (!bce_->emit1(JSOP_NULL)) {
+    //              [stack] PROTO NULL
+    return false;
+  }
+
+  // [ENDIF]
+  if (!ifThenElse.emitEnd()) {
+    return false;
+  }
+
+  if (!bce_->emit1(JSOP_OBJWITHPROTO)) {
+    //              [stack] HERITAGE HOMEOBJ
+    return false;
+  }
+  if (!bce_->emit1(JSOP_SWAP)) {
+    //              [stack] HOMEOBJ HERITAGE
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::Class;
+#endif
+  return true;
+}
+
+void ClassEmitter::setName(JS::Handle<JSAtom*> name) {
+  name_ = name;
+  if (!name_) {
+    name_ = bce_->cx->names().empty;
+  }
+}
+
+bool ClassEmitter::emitInitConstructor(bool needsHomeObject) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Class);
+
+  //                [stack] HOMEOBJ CTOR
+
+  if (needsHomeObject) {
+    if (!bce_->emitDupAt(1)) {
+      //            [stack] HOMEOBJ CTOR HOMEOBJ
+      return false;
+    }
+    if (!bce_->emit1(JSOP_INITHOMEOBJECT)) {
+      //            [stack] HOMEOBJ CTOR
+      return false;
+    }
+  }
+
+  if (!initProtoAndCtor()) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::InitConstructor;
+#endif
+  return true;
+}
+
+bool ClassEmitter::emitInitDefaultConstructor(const Maybe<uint32_t>& classStart,
+                                              const Maybe<uint32_t>& classEnd) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(classState_ == ClassState::Class);
+
+  if (classStart && classEnd) {
+    // In the case of default class constructors, emit the start and end
+    // offsets in the source buffer as source notes so that when we
+    // actually make the constructor during execution, we can give it the
+    // correct toString output.
+    if (!bce_->newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(*classStart),
+                           ptrdiff_t(*classEnd))) {
+      return false;
+    }
+  }
+
+  if (isDerived_) {
+    //              [stack] HERITAGE PROTO
+    if (!bce_->emitAtomOp(name_, JSOP_DERIVEDCONSTRUCTOR)) {
+      //            [stack] HOMEOBJ CTOR
+      return false;
+    }
+  } else {
+    //              [stack] HOMEOBJ
+    if (!bce_->emitAtomOp(name_, JSOP_CLASSCONSTRUCTOR)) {
+      //            [stack] HOMEOBJ CTOR
+      return false;
+    }
+  }
+
+  if (!initProtoAndCtor()) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+
+#ifdef DEBUG
+  classState_ = ClassState::InitConstructor;
+#endif
+  return true;
+}
+
+bool ClassEmitter::initProtoAndCtor() {
+  //                [stack] HOMEOBJ CTOR
+
+  if (!bce_->emit1(JSOP_SWAP)) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+  if (!bce_->emit1(JSOP_DUP2)) {
+    //              [stack] CTOR HOMEOBJ CTOR HOMEOBJ
+    return false;
+  }
+  if (!bce_->emitAtomOp(bce_->cx->names().prototype, JSOP_INITLOCKEDPROP)) {
+    //              [stack] CTOR HOMEOBJ CTOR
+    return false;
+  }
+  if (!bce_->emitAtomOp(bce_->cx->names().constructor, JSOP_INITHIDDENPROP)) {
+    //              [stack] CTOR HOMEOBJ
+    return false;
+  }
+
+  return true;
+}
+
+bool ClassEmitter::emitEnd(Kind kind) {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start ||
+             propertyState_ == PropertyState::Init);
+  MOZ_ASSERT(classState_ == ClassState::InitConstructor);
+
+  //                [stack] CTOR HOMEOBJ
+
+  if (!bce_->emit1(JSOP_POP)) {
+    //              [stack] CTOR
+    return false;
+  }
+
+  if (name_ != bce_->cx->names().empty) {
+    MOZ_ASSERT(tdzCacheForInnerName_.isSome());
+    MOZ_ASSERT(innerNameScope_.isSome());
+
+    if (!bce_->emitLexicalInitialization(name_)) {
+      //            [stack] CTOR
+      return false;
+    }
+
+    if (!innerNameScope_->leave(bce_)) {
+      return false;
+    }
+    innerNameScope_.reset();
+
+    if (kind == Kind::Declaration) {
+      if (!bce_->emitLexicalInitialization(name_)) {
+        //          [stack] CTOR
+        return false;
+      }
+      // Only class statements make outer bindings, and they do not leave
+      // themselves on the stack.
+      if (!bce_->emit1(JSOP_POP)) {
+        //          [stack]
+        return false;
+      }
+    }
+
+    tdzCacheForInnerName_.reset();
+  } else {
+    //              [stack] CTOR
+
+    MOZ_ASSERT(tdzCacheForInnerName_.isNothing());
+  }
+
+  //                [stack] # class declaration
+  //                [stack]
+  //                [stack] # class expression
+  //                [stack] CTOR
+
+  strictMode_.restore();
+
+#ifdef DEBUG
+  classState_ = ClassState::End;
+#endif
+  return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ObjectEmitter.h
@@ -0,0 +1,722 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sts=2 et sw=2 tw=80:
+ * 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_ObjectEmitter_h
+#define frontend_ObjectEmitter_h
+
+#include "mozilla/Attributes.h"  // MOZ_MUST_USE, MOZ_STACK_CLASS, MOZ_ALWAYS_INLINE, MOZ_RAII
+#include "mozilla/Maybe.h"  // Maybe
+
+#include <stddef.h>  // size_t, ptrdiff_t
+#include <stdint.h>  // uint32_t
+
+#include "frontend/EmitterScope.h"   // EmitterScope
+#include "frontend/TDZCheckCache.h"  // TDZCheckCache
+#include "js/RootingAPI.h"           // JS::Handle, JS::Rooted
+#include "vm/BytecodeUtil.h"         // JSOp
+#include "vm/JSAtom.h"               // JSAtom
+#include "vm/JSFunction.h"           // JSFunction, FunctionPrefixKind
+#include "vm/JSScript.h"             // FunctionAsyncKind
+#include "vm/NativeObject.h"         // PlainObject
+#include "vm/Scope.h"                // LexicalScope
+
+namespace js {
+
+namespace frontend {
+
+struct BytecodeEmitter;
+class SharedContext;
+
+// Class for emitting bytecode for object and class properties.
+// See ObjectEmitter and ClassEmitter for usage.
+class MOZ_STACK_CLASS PropertyEmitter {
+ public:
+  enum class Kind {
+    // Prototype property.
+    Prototype,
+
+    // Class static property.
+    Static
+  };
+
+ protected:
+  BytecodeEmitter* bce_;
+
+  // True if the object is class.
+  // Set by ClassEmitter.
+  bool isClass_ = false;
+
+  // True if the property is class static method.
+  bool isStatic_ = false;
+
+  // True if the property has computed or index key.
+  bool isIndexOrComputed_ = false;
+
+  // An object which keeps the shape of this object literal.
+  // This fields is reset to nullptr whenever the object literal turns out to
+  // have at least one numeric, computed, spread or __proto__ property, or
+  // the object becomes dictionary mode.
+  // This field is used only in ObjectEmitter.
+  JS::Rooted<PlainObject*> obj_;
+
+#ifdef DEBUG
+  // The state of this emitter.
+  //
+  // +-------+
+  // | Start |-+
+  // +-------+ |
+  //           |
+  // +---------+
+  // |
+  // |  +------------------------------------------------------------+
+  // |  |                                                            |
+  // |  | [normal property/method/accessor]                          |
+  // |  v   prepareForPropValue  +-----------+              +------+ |
+  // +->+----------------------->| PropValue |-+         +->| Init |-+
+  //    |                        +-----------+ |         |  +------+
+  //    |                                      |         |
+  //    |  +----------------------------------+          +-----------+
+  //    |  |                                                         |
+  //    |  +-+---------------------------------------+               |
+  //    |    |                                       |               |
+  //    |    | [method with super]                   |               |
+  //    |    |   emitInitHomeObject +-------------+  v               |
+  //    |    +--------------------->| InitHomeObj |->+               |
+  //    |                           +-------------+  |               |
+  //    |                                            |               |
+  //    |    +-------------------------------------- +               |
+  //    |    |                                                       |
+  //    |    | emitInitProp                                          |
+  //    |    | emitInitGetter                                        |
+  //    |    | emitInitSetter                                        |
+  //    |    +------------------------------------------------------>+
+  //    |                                                            ^
+  //    | [index property/method/accessor]                           |
+  //    |   prepareForIndexPropKey  +----------+                     |
+  //    +-------------------------->| IndexKey |-+                   |
+  //    |                           +----------+ |                   |
+  //    |                                        |                   |
+  //    |  +-------------------------------------+                   |
+  //    |  |                                                         |
+  //    |  | prepareForIndexPropValue +------------+                 |
+  //    |  +------------------------->| IndexValue |-+               |
+  //    |                             +------------+ |               |
+  //    |                                            |               |
+  //    |    +---------------------------------------+               |
+  //    |    |                                                       |
+  //    |    +-+--------------------------------------------------+  |
+  //    |      |                                                  |  |
+  //    |      | [method with super]                              |  |
+  //    |      |   emitInitHomeObject +---------------------+     v  |
+  //    |      +--------------------->| InitHomeObjForIndex |---->+  |
+  //    |                             +---------------------+     |  |
+  //    |                                                         |  |
+  //    |      +--------------------------------------------------+  |
+  //    |      |                                                     |
+  //    |      | emitInitIndexProp                                   |
+  //    |      | emitInitIndexGetter                                 |
+  //    |      | emitInitIndexSetter                                 |
+  //    |      +---------------------------------------------------->+
+  //    |                                                            |
+  //    | [computed property/method/accessor]                        |
+  //    |   prepareForComputedPropKey  +-------------+               |
+  //    +----------------------------->| ComputedKey |-+             |
+  //    |                              +-------------+ |             |
+  //    |                                              |             |
+  //    |  +-------------------------------------------+             |
+  //    |  |                                                         |
+  //    |  | prepareForComputedPropValue +---------------+           |
+  //    |  +---------------------------->| ComputedValue |-+         |
+  //    |                                +---------------+ |         |
+  //    |                                                  |         |
+  //    |    +---------------------------------------------+         |
+  //    |    |                                                       |
+  //    |    +-+--------------------------------------------------+  |
+  //    |      |                                                  |  |
+  //    |      | [method with super]                              |  |
+  //    |      |   emitInitHomeObject +------------------------+  v  |
+  //    |      +--------------------->| InitHomeObjForComputed |->+  |
+  //    |                             +------------------------+  |  |
+  //    |                                                         |  |
+  //    |      +--------------------------------------------------+  |
+  //    |      |                                                     |
+  //    |      | emitInitComputedProp                                |
+  //    |      | emitInitComputedGetter                              |
+  //    |      | emitInitComputedSetter                              |
+  //    |      +---------------------------------------------------->+
+  //    |                                                            ^
+  //    |                                                            |
+  //    | [__proto__]                                                |
+  //    |   prepareForProtoValue  +------------+ emitMutateProto     |
+  //    +------------------------>| ProtoValue |-------------------->+
+  //    |                         +------------+                     ^
+  //    |                                                            |
+  //    | [...prop]                                                  |
+  //    |   prepareForSpreadOperand +---------------+ emitSpread     |
+  //    +-------------------------->| SpreadOperand |----------------+
+  //                                +---------------+
+  enum class PropertyState {
+    // The initial state.
+    Start,
+
+    // After calling prepareForPropValue.
+    PropValue,
+
+    // After calling emitInitHomeObject, from PropValue.
+    InitHomeObj,
+
+    // After calling prepareForIndexPropKey.
+    IndexKey,
+
+    // prepareForIndexPropValue.
+    IndexValue,
+
+    // After calling emitInitHomeObject, from IndexValue.
+    InitHomeObjForIndex,
+
+    // After calling prepareForComputedPropKey.
+    ComputedKey,
+
+    // prepareForComputedPropValue.
+    ComputedValue,
+
+    // After calling emitInitHomeObject, from ComputedValue.
+    InitHomeObjForComputed,
+
+    // After calling prepareForProtoValue.
+    ProtoValue,
+
+    // After calling prepareForSpreadOperand.
+    SpreadOperand,
+
+    // After calling one of emitInitProp, emitInitGetter, emitInitSetter,
+    // emitInitIndexOrComputedProp, emitInitIndexOrComputedGetter,
+    // emitInitIndexOrComputedSetter, emitMutateProto, or emitSpread.
+    Init,
+  };
+  PropertyState propertyState_ = PropertyState::Start;
+#endif
+
+ public:
+  explicit PropertyEmitter(BytecodeEmitter* bce);
+
+  // Parameters are the offset in the source code for each character below:
+  //
+  // { __proto__: protoValue }
+  //   ^
+  //   |
+  //   keyPos
+  MOZ_MUST_USE bool prepareForProtoValue(
+      const mozilla::Maybe<uint32_t>& keyPos);
+  MOZ_MUST_USE bool emitMutateProto();
+
+  // { ...obj }
+  //   ^
+  //   |
+  //   spreadPos
+  MOZ_MUST_USE bool prepareForSpreadOperand(
+      const mozilla::Maybe<uint32_t>& spreadPos);
+  MOZ_MUST_USE bool emitSpread();
+
+  // { key: value }
+  //   ^
+  //   |
+  //   keyPos
+  MOZ_MUST_USE bool prepareForPropValue(const mozilla::Maybe<uint32_t>& keyPos,
+                                        Kind kind = Kind::Prototype);
+
+  // { 1: value }
+  //   ^
+  //   |
+  //   keyPos
+  MOZ_MUST_USE bool prepareForIndexPropKey(
+      const mozilla::Maybe<uint32_t>& keyPos, Kind kind = Kind::Prototype);
+  MOZ_MUST_USE bool prepareForIndexPropValue();
+
+  // { [ key ]: value }
+  //   ^
+  //   |
+  //   keyPos
+  MOZ_MUST_USE bool prepareForComputedPropKey(
+      const mozilla::Maybe<uint32_t>& keyPos, Kind kind = Kind::Prototype);
+  MOZ_MUST_USE bool prepareForComputedPropValue();
+
+  MOZ_MUST_USE bool emitInitHomeObject(
+      FunctionAsyncKind kind = FunctionAsyncKind::SyncFunction);
+
+  // @param key
+  //        Property key
+  // @param isPropertyAnonFunctionOrClass
+  //        True if the property value is an anonymous function or
+  //        an anonymous class
+  // @param anonFunction
+  //        The anonymous function object for property value
+  MOZ_MUST_USE bool emitInitProp(
+      JS::Handle<JSAtom*> key, bool isPropertyAnonFunctionOrClass = false,
+      JS::Handle<JSFunction*> anonFunction = nullptr);
+  MOZ_MUST_USE bool emitInitGetter(JS::Handle<JSAtom*> key);
+  MOZ_MUST_USE bool emitInitSetter(JS::Handle<JSAtom*> key);
+
+  MOZ_MUST_USE bool emitInitIndexProp(
+      bool isPropertyAnonFunctionOrClass = false);
+  MOZ_MUST_USE bool emitInitIndexGetter();
+  MOZ_MUST_USE bool emitInitIndexSetter();
+
+  MOZ_MUST_USE bool emitInitComputedProp(
+      bool isPropertyAnonFunctionOrClass = false);
+  MOZ_MUST_USE bool emitInitComputedGetter();
+  MOZ_MUST_USE bool emitInitComputedSetter();
+
+ private:
+  MOZ_MUST_USE MOZ_ALWAYS_INLINE bool prepareForProp(
+      const mozilla::Maybe<uint32_t>& keyPos, bool isStatic, bool isComputed);
+
+  // @param op
+  //        Opcode for initializing property
+  // @param prefixKind
+  //        None, Get, or Set
+  // @param key
+  //        Atom of the property if the property key is not computed
+  // @param isPropertyAnonFunctionOrClass
+  //        True if the property is either an anonymous function or an
+  //        anonymous class
+  // @param anonFunction
+  //        Anonymous function object for the property
+  MOZ_MUST_USE bool emitInit(JSOp op, JS::Handle<JSAtom*> key,
+                             bool isPropertyAnonFunctionOrClass,
+                             JS::Handle<JSFunction*> anonFunction);
+  MOZ_MUST_USE bool emitInitIndexOrComputed(JSOp op,
+                                            FunctionPrefixKind prefixKind,
+                                            bool isPropertyAnonFunctionOrClass);
+
+  MOZ_MUST_USE bool emitPopClassConstructor();
+};
+
+// Class for emitting bytecode for object literal.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `{}`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(0);
+//     oe.emitEnd();
+//
+//   `{ prop: 10 }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(1);
+//
+//     oe.prepareForPropValue(Some(offset_of_prop));
+//     emit(10);
+//     oe.emitInitProp(atom_of_prop);
+//
+//     oe.emitEnd();
+//
+//   `{ prop: function() {} }`, when property value is anonymous function
+//     ObjectEmitter oe(this);
+//     oe.emitObject(1);
+//
+//     oe.prepareForPropValue(Some(offset_of_prop));
+//     emit(function);
+//     oe.emitInitProp(atom_of_prop, true, function_object);
+//
+//     oe.emitEnd();
+//
+//   `{ get prop() { ... }, set prop(v) { ... } }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(2);
+//
+//     oe.prepareForPropValue(Some(offset_of_prop));
+//     emit(function_for_getter);
+//     oe.emitInitGetter(atom_of_prop);
+//
+//     oe.prepareForPropValue(Some(offset_of_prop));
+//     emit(function_for_setter);
+//     oe.emitInitSetter(atom_of_prop);
+//
+//     oe.emitEnd();
+//
+//   `{ 1: 10, get 2() { ... }, set 3(v) { ... } }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(3);
+//
+//     oe.prepareForIndexPropKey(Some(offset_of_prop));
+//     emit(1);
+//     oe.prepareForIndexPropValue();
+//     emit(10);
+//     oe.emitInitIndexedProp(atom_of_prop);
+//
+//     oe.prepareForIndexPropKey(Some(offset_of_opening_bracket));
+//     emit(2);
+//     oe.prepareForIndexPropValue();
+//     emit(function_for_getter);
+//     oe.emitInitIndexGetter();
+//
+//     oe.prepareForIndexPropKey(Some(offset_of_opening_bracket));
+//     emit(3);
+//     oe.prepareForIndexPropValue();
+//     emit(function_for_setter);
+//     oe.emitInitIndexSetter();
+//
+//     oe.emitEnd();
+//
+//   `{ [prop1]: 10, get [prop2]() { ... }, set [prop3](v) { ... } }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(3);
+//
+//     oe.prepareForComputedPropKey(Some(offset_of_opening_bracket));
+//     emit(prop1);
+//     oe.prepareForComputedPropValue();
+//     emit(10);
+//     oe.emitInitComputedProp();
+//
+//     oe.prepareForComputedPropKey(Some(offset_of_opening_bracket));
+//     emit(prop2);
+//     oe.prepareForComputedPropValue();
+//     emit(function_for_getter);
+//     oe.emitInitComputedGetter();
+//
+//     oe.prepareForComputedPropKey(Some(offset_of_opening_bracket));
+//     emit(prop3);
+//     oe.prepareForComputedPropValue();
+//     emit(function_for_setter);
+//     oe.emitInitComputedSetter();
+//
+//     oe.emitEnd();
+//
+//   `{ __proto__: obj }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(1);
+//     oe.prepareForProtoValue(Some(offset_of___proto__));
+//     emit(obj);
+//     oe.emitMutateProto();
+//     oe.emitEnd();
+//
+//   `{ ...obj }`
+//     ObjectEmitter oe(this);
+//     oe.emitObject(1);
+//     oe.prepareForSpreadOperand(Some(offset_of_triple_dots));
+//     emit(obj);
+//     oe.emitSpread();
+//     oe.emitEnd();
+//
+class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter {
+ private:
+  // The offset of JSOP_NEWINIT, which is replced by JSOP_NEWOBJECT later
+  // when the object is known to have a fixed shape.
+  ptrdiff_t top_ = 0;
+
+#ifdef DEBUG
+  // The state of this emitter.
+  //
+  // +-------+ emitObject +--------+
+  // | Start |----------->| Object |-+
+  // +-------+            +--------+ |
+  //                                 |
+  //   +-----------------------------+
+  //   |
+  //   | (do PropertyEmitter operation)  emitEnd  +-----+
+  //   +-------------------------------+--------->| End |
+  //                                              +-----+
+  enum class ObjectState {
+    // The initial state.
+    Start,
+
+    // After calling emitObject.
+    Object,
+
+    // After calling emitEnd.
+    End,
+  };
+  ObjectState objectState_ = ObjectState::Start;
+#endif
+
+ public:
+  explicit ObjectEmitter(BytecodeEmitter* bce);
+
+  MOZ_MUST_USE bool emitObject(size_t propertyCount);
+  MOZ_MUST_USE bool emitEnd();
+};
+
+// Save and restore the strictness.
+// Used by class declaration/expression to temporarily enable strict mode.
+class MOZ_RAII AutoSaveLocalStrictMode {
+  SharedContext* sc_;
+  bool savedStrictness_;
+
+ public:
+  explicit AutoSaveLocalStrictMode(SharedContext* sc);
+  ~AutoSaveLocalStrictMode();
+
+  // Force restore the strictness now.
+  void restore();
+};
+
+// Class for emitting bytecode for JS class.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `class {}`
+//     ClassEmitter ce(this);
+//     ce.emitClass();
+//
+//     ce.emitInitDefaultConstructor(Some(offset_of_class),
+//                                   Some(offset_of_closing_bracket));
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class { constructor() { ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitClass();
+//
+//     emit(function_for_constructor);
+//     ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class X { constructor() { ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitScopeForNamedClass(scopeBindingForName);
+//     ce.emitClass(atom_of_X);
+//
+//     ce.emitInitDefaultConstructor(Some(offset_of_class),
+//                                   Some(offset_of_closing_bracket));
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class X { constructor() { ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitScopeForNamedClass(scopeBindingForName);
+//     ce.emitClass(atom_of_X);
+//
+//     emit(function_for_constructor);
+//     ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class X extends Y { constructor() { ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitScopeForNamedClass(scopeBindingForName);
+//
+//     emit(Y);
+//     ce.emitDerivedClass(atom_of_X);
+//
+//     emit(function_for_constructor);
+//     ce.emitInitConstructor(/* needsHomeObject = */ false);
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `class X extends Y { constructor() { ... super.f(); ... } }`
+//     ClassEmitter ce(this);
+//     ce.emitScopeForNamedClass(scopeBindingForName);
+//
+//     emit(Y);
+//     ce.emitDerivedClass(atom_of_X);
+//
+//     emit(function_for_constructor);
+//     // pass true if constructor contains super.prop access
+//     ce.emitInitConstructor(/* needsHomeObject = */ true);
+//
+//     ce.emitEnd(ClassEmitter::Kind::Expression);
+//
+//   `m() {}` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_m));
+//     emit(function_for_m);
+//     ce.emitInitProp(atom_of_m);
+//
+//   `m() { super.f(); }` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_m));
+//     emit(function_for_m);
+//     ce.emitInitHomeObject();
+//     ce.emitInitProp(atom_of_m);
+//
+//   `async m() { super.f(); }` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_m));
+//     emit(function_for_m);
+//     ce.emitInitHomeObject(FunctionAsyncKind::Async);
+//     ce.emitInitProp(atom_of_m);
+//
+//   `get p() { super.f(); }` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_p));
+//     emit(function_for_p);
+//     ce.emitInitHomeObject();
+//     ce.emitInitGetter(atom_of_m);
+//
+//   `static m() {}` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForPropValue(Some(offset_of_m),
+//                            PropertyEmitter::Kind::Static);
+//     emit(function_for_m);
+//     ce.emitInitProp(atom_of_m);
+//
+//   `static get [p]() { super.f(); }` in class
+//     // after emitInitConstructor/emitInitDefaultConstructor
+//     ce.prepareForComputedPropValue(Some(offset_of_m),
+//                                    PropertyEmitter::Kind::Static);
+//     emit(p);
+//     ce.prepareForComputedPropValue();
+//     emit(function_for_m);
+//     ce.emitInitHomeObject();
+//     ce.emitInitComputedGetter();
+//
+class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter {
+ public:
+  enum class Kind {
+    // Class expression.
+    Expression,
+
+    // Class declaration.
+    Declaration,
+  };
+
+ private:
+  // Pseudocode for class declarations:
+  //
+  //     class extends BaseExpression {
+  //       constructor() { ... }
+  //       ...
+  //       }
+  //
+  //
+  //   if defined <BaseExpression> {
+  //     let heritage = BaseExpression;
+  //
+  //     if (heritage !== null) {
+  //       funProto = heritage;
+  //       objProto = heritage.prototype;
+  //     } else {
+  //       funProto = %FunctionPrototype%;
+  //       objProto = null;
+  //     }
+  //   } else {
+  //     objProto = %ObjectPrototype%;
+  //   }
+  //
+  //   let homeObject = ObjectCreate(objProto);
+  //
+  //   if defined <constructor> {
+  //     if defined <BaseExpression> {
+  //       cons = DefineMethod(<constructor>, proto=homeObject,
+  //       funProto=funProto);
+  //     } else {
+  //       cons = DefineMethod(<constructor>, proto=homeObject);
+  //     }
+  //   } else {
+  //     if defined <BaseExpression> {
+  //       cons = DefaultDerivedConstructor(proto=homeObject,
+  //       funProto=funProto);
+  //     } else {
+  //       cons = DefaultConstructor(proto=homeObject);
+  //     }
+  //   }
+  //
+  //   cons.prototype = homeObject;
+  //   homeObject.constructor = cons;
+  //
+  //   EmitPropertyList(...)
+
+  bool isDerived_ = false;
+
+  mozilla::Maybe<TDZCheckCache> tdzCacheForInnerName_;
+  mozilla::Maybe<EmitterScope> innerNameScope_;
+  AutoSaveLocalStrictMode strictMode_;
+
+#ifdef DEBUG
+  // The state of this emitter.
+  //
+  // +-------+
+  // | Start |-+------------------------------------>+-+
+  // +-------+ |                                     ^ |
+  //           | [named class]                       | |
+  //           |   emitScopeForNamedClass  +-------+ | |
+  //           +-------------------------->| Scope |-+ |
+  //                                       +-------+   |
+  //                                                   |
+  //   +-----------------------------------------------+
+  //   |
+  //   |   emitClass           +-------+
+  //   +-+----------------->+->| Class |-+
+  //     |                  ^  +-------+ |
+  //     | emitDerivedClass |            |
+  //     +------------------+            |
+  //                                     |
+  //     +-------------------------------+
+  //     |
+  //     |
+  //     |   emitInitConstructor           +-----------------+
+  //     +-+--------------------------->+->| InitConstructor |-+
+  //       |                            ^  +-----------------+ |
+  //       | emitInitDefaultConstructor |                      |
+  //       +----------------------------+                      |
+  //                                                           |
+  //       +---------------------------------------------------+
+  //       |
+  //       | (do PropertyEmitter operation)  emitEnd  +-----+
+  //       +-------------------------------+--------->| End |
+  //                                                  +-----+
+  enum class ClassState {
+    // The initial state.
+    Start,
+
+    // After calling emitScopeForNamedClass.
+    Scope,
+
+    // After calling emitClass or emitDerivedClass.
+    Class,
+
+    // After calling emitInitConstructor or emitInitDefaultConstructor.
+    InitConstructor,
+
+    // After calling emitEnd.
+    End,
+  };
+  ClassState classState_ = ClassState::Start;
+#endif
+
+  JS::Rooted<JSAtom*> name_;
+
+ public:
+  explicit ClassEmitter(BytecodeEmitter* bce);
+
+  MOZ_MUST_USE bool emitScopeForNamedClass(
+      JS::Handle<LexicalScope::Data*> scopeBindings);
+
+  // @param name
+  //        Name of the class (nullptr if this is anonymous class)
+  MOZ_MUST_USE bool emitClass(JS::Handle<JSAtom*> name);
+  MOZ_MUST_USE bool emitDerivedClass(JS::Handle<JSAtom*> name);
+
+  // @param needsHomeObject
+  //        True if the constructor contains `super.foo`
+  MOZ_MUST_USE bool emitInitConstructor(bool needsHomeObject);
+
+  // Parameters are the offset in the source code for each character below:
+  //
+  //   class X { foo() {} }
+  //   ^                  ^
+  //   |                  |
+  //   |                  classEnd
+  //   |
+  //   classStart
+  //
+  MOZ_MUST_USE bool emitInitDefaultConstructor(
+      const mozilla::Maybe<uint32_t>& classStart,
+      const mozilla::Maybe<uint32_t>& classEnd);
+
+  MOZ_MUST_USE bool emitEnd(Kind kind);
+
+ private:
+  void setName(JS::Handle<JSAtom*> name);
+  MOZ_MUST_USE bool initProtoAndCtor();
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_ObjectEmitter_h */
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -39,16 +39,17 @@ UNIFIED_SOURCES += [
     'ForInEmitter.cpp',
     'ForOfEmitter.cpp',
     'ForOfLoopControl.cpp',
     'IfEmitter.cpp',
     'JumpList.cpp',
     'LabelEmitter.cpp',
     'NameFunctions.cpp',
     'NameOpEmitter.cpp',
+    'ObjectEmitter.cpp',
     'ParseContext.cpp',
     'ParseNode.cpp',
     'PropOpEmitter.cpp',
     'SharedContext.cpp',
     'SwitchEmitter.cpp',
     'TDZCheckCache.cpp',
     'TokenStream.cpp',
     'TryEmitter.cpp',