Bug 1580246: Remove object-literal singleton objects allocated at parse. r=mgaudet,jandem
authorChris Fallin <cfallin@mozilla.com>
Mon, 18 Nov 2019 20:57:50 +0000
changeset 502478 21f755c04005255c5305a13ad9087420e4489b7b
parent 502477 91697065e99f5b88ceaaf92af99ebdbbfc1dda88
child 502479 765b9da8b818804ee68c8e18f92d2bdaa1794d8e
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmgaudet, jandem
bugs1580246, 1594753
milestone72.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 1580246: Remove object-literal singleton objects allocated at parse. r=mgaudet,jandem Instead, this patch introduces a new `ObjLiteral` mini-bytecode format that is used to carry object-literal information from parse time to a later time at which GC objects are safe to allocate. The mini-bytecode simply specifies a list of fields and constant field values. The original intent of this patch (realized in previous versions of it) was to make this an opcode, and completely replace object creation sequences (NEWINIT, INITPROP, INITPROP, ...) with one OBJLITERAL opcode. However, there are quite a few performance regressions that occur when replacing the finely-tuned set of optimizations around this with a new mechanism. As a result, this patch only defers allocation of the objects until the very end of parse. Each object literal adds an ObjLiteralCreationData instance to the GC-things list, and when the GC-things list is processed to perform deferred allocations, the described objects will be created. This is a rebased version of the original patch (landed as D47985 and then backed out) with the Kraken regression (bug 1594753) fixed as noted in the bug above. Differential Revision: https://phabricator.services.mozilla.com/D52383
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/BytecodeSection.cpp
js/src/frontend/BytecodeSection.h
js/src/frontend/ObjLiteral.cpp
js/src/frontend/ObjLiteral.h
js/src/frontend/ObjectEmitter.cpp
js/src/frontend/ObjectEmitter.h
js/src/frontend/moz.build
js/src/jit-test/tests/bug1580246.js
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -7727,18 +7727,104 @@ bool BytecodeEmitter::emitConditionalExp
   if (!cond.emitEnd()) {
     return false;
   }
   MOZ_ASSERT(cond.pushed() == 1);
 
   return true;
 }
 
+// Check for an object-literal property list that can be handled by the
+// ObjLiteral writer. We immediately rule out class bodies. Then, we ensure
+// that for each `prop: value` pair, the key is a constant name (and not a
+// numeric index), there is no accessor specified, and the value can be encoded
+// by an ObjLiteral instruction (constant number, string, boolean,
+// null/undefined).
+void BytecodeEmitter::isPropertyListObjLiteralCompatible(ListNode* obj,
+                                                         PropListType type,
+                                                         bool* withValues,
+                                                         bool* withoutValues) {
+  if (type == ClassBody) {
+    *withValues = false;
+    *withoutValues = false;
+    return;
+  }
+
+  bool keysOK = true;
+  bool valuesOK = true;
+  int propCount = 0;
+
+  for (ParseNode* propdef : obj->contents()) {
+    if (!propdef->is<BinaryNode>()) {
+      keysOK = false;
+      break;
+    }
+    propCount++;
+
+    BinaryNode* prop = &propdef->as<BinaryNode>();
+    ParseNode* key = prop->left();
+    ParseNode* value = prop->right();
+
+    // Computed keys not OK (ObjLiteral data stores constant keys).
+    if (key->isKind(ParseNodeKind::ComputedName)) {
+      keysOK = false;
+      break;
+    }
+    // Numeric keys OK as long as they are integers and in range.
+    if (key->isKind(ParseNodeKind::NumberExpr)) {
+      double numValue = key->as<NumericLiteral>().value();
+      int32_t i = 0;
+      if (!NumberIsInt32(numValue, &i)) {
+        keysOK = false;
+        break;
+      }
+      if (!ObjLiteralWriter::arrayIndexInRange(i)) {
+        keysOK = false;
+        break;
+      }
+    }
+
+    AccessorType accessorType =
+        prop->is<PropertyDefinition>()
+            ? prop->as<PropertyDefinition>().accessorType()
+            : AccessorType::None;
+    if (accessorType != AccessorType::None) {
+      keysOK = false;
+      break;
+    }
+
+    if (!isRHSObjLiteralCompatible(value)) {
+      valuesOK = false;
+    }
+  }
+
+  if (propCount >= PropertyTree::MAX_HEIGHT) {
+    // JSOP_NEWOBJECT cannot accept dictionary-mode objects.
+    keysOK = false;
+  }
+
+  *withValues = keysOK && valuesOK;
+  *withoutValues = keysOK;
+}
+
+bool BytecodeEmitter::isArrayObjLiteralCompatible(ParseNode* arrayHead) {
+  for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) {
+    if (elem->isKind(ParseNodeKind::Spread)) {
+      return false;
+    }
+    if (!isRHSObjLiteralCompatible(elem)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe,
-                                       PropListType type) {
+                                       PropListType type,
+                                       bool isInnerSingleton) {
   //                [stack] CTOR? OBJ
 
   size_t curFieldKeyIndex = 0;
   for (ParseNode* propdef : obj->contents()) {
     if (propdef->is<ClassField>()) {
       MOZ_ASSERT(type == ClassBody);
       // Only handle computing field keys here: the .initializers lambda array
       // is created elsewhere.
@@ -7746,17 +7832,18 @@ bool BytecodeEmitter::emitPropertyList(L
       if (field->name().getKind() == ParseNodeKind::ComputedName) {
         if (!emitGetName(cx->names().dotFieldKeys)) {
           //        [stack] CTOR? OBJ ARRAY
           return false;
         }
 
         ParseNode* nameExpr = field->name().as<UnaryNode>().kid();
 
-        if (!emitTree(nameExpr)) {
+        if (!emitTree(nameExpr, ValueUsage::WantValue, EMIT_LINENOTE,
+                      /* isInnerSingleton = */ isInnerSingleton)) {
           //        [stack] CTOR? OBJ ARRAY KEY
           return false;
         }
 
         if (!emit1(JSOP_TOID)) {
           //        [stack] CTOR? OBJ ARRAY KEY
           return false;
         }
@@ -7772,18 +7859,18 @@ bool BytecodeEmitter::emitPropertyList(L
         }
 
         curFieldKeyIndex++;
       }
       continue;
     }
 
     if (propdef->is<LexicalScopeNode>()) {
-      // Constructors are sometimes wrapped in LexicalScopeNodes. As we already
-      // handled emitting the constructor, skip it.
+      // Constructors are sometimes wrapped in LexicalScopeNodes. As we
+      // already handled emitting the constructor, skip it.
       MOZ_ASSERT(propdef->as<LexicalScopeNode>().scopeBody()->isKind(
           ParseNodeKind::ClassMethod));
       continue;
     }
 
     // Handle __proto__: v specially because *only* this form, and no other
     // involving "__proto__", performs [[Prototype]] mutation.
     if (propdef->isKind(ParseNodeKind::MutateProto)) {
@@ -8035,34 +8122,183 @@ bool BytecodeEmitter::emitPropertyList(L
       default:
         MOZ_CRASH("Invalid op");
     }
   }
 
   return true;
 }
 
+bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj,
+                                                 PropListType type,
+                                                 ObjLiteralFlags flags) {
+  int32_t stackDepth = bytecodeSection().stackDepth();
+
+  ObjLiteralCreationData data(cx);
+  data.writer().beginObject(flags);
+  bool noValues = flags.contains(ObjLiteralFlag::NoValues);
+  bool singleton = flags.contains(ObjLiteralFlag::Singleton);
+
+  for (ParseNode* propdef : obj->contents()) {
+    MOZ_ASSERT(propdef->is<BinaryNode>());
+    BinaryNode* prop = &propdef->as<BinaryNode>();
+    ParseNode* key = prop->left();
+    MOZ_ASSERT(key->is<NameNode>() || key->is<NumericLiteral>());
+
+    if (key->is<NameNode>()) {
+      uint32_t propNameIndex = 0;
+      if (!data.addAtom(key->as<NameNode>().atom(), &propNameIndex)) {
+        return false;
+      }
+      data.writer().setPropName(propNameIndex);
+    } else {
+      MOZ_ASSERT(key->is<NumericLiteral>());
+      double numValue = key->as<NumericLiteral>().value();
+      int32_t i = 0;
+      DebugOnly<bool> numIsInt =
+          NumberIsInt32(numValue, &i);  // checked previously.
+      MOZ_ASSERT(numIsInt);
+      MOZ_ASSERT(
+          ObjLiteralWriter::arrayIndexInRange(i));  // checked previously.
+      data.writer().setPropIndex(i);
+    }
+
+    if (noValues) {
+      if (!data.writer().propWithUndefinedValue()) {
+        return false;
+      }
+    } else {
+      ParseNode* value = prop->right();
+      if (!emitObjLiteralValue(&data, value)) {
+        return false;
+      }
+    }
+  }
+
+  uint32_t gcThingIndex = 0;
+  if (!perScriptData().gcThingList().append(std::move(data), &gcThingIndex)) {
+    return false;
+  }
+
+  bool success = singleton ? emitIndex32(JSOP_OBJECT, gcThingIndex)
+                           : emitIndex32(JSOP_NEWOBJECT, gcThingIndex);
+  if (!success) {
+    return false;
+  }
+
+  bytecodeSection().setStackDepth(stackDepth + 1);
+  return true;
+}
+
+bool BytecodeEmitter::emitObjLiteralArray(ParseNode* arrayHead, bool isCow) {
+  int32_t stackDepth = bytecodeSection().stackDepth();
+
+  ObjLiteralCreationData data(cx);
+  ObjLiteralFlags flags(ObjLiteralFlag::Array);
+  if (isCow) {
+    flags += ObjLiteralFlag::ArrayCOW;
+  }
+  data.writer().beginObject(flags);
+
+  uint32_t index = 0;
+  for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next, index++) {
+    data.writer().setPropIndex(index);
+    if (!emitObjLiteralValue(&data, elem)) {
+      return false;
+    }
+  }
+
+  uint32_t gcThingIndex = 0;
+  if (!perScriptData().gcThingList().append(std::move(data), &gcThingIndex)) {
+    return false;
+  }
+
+  JSOp op = isCow ? JSOP_NEWARRAY_COPYONWRITE : JSOP_OBJECT;
+  if (!emitIndex32(op, gcThingIndex)) {
+    return false;
+  }
+
+  bytecodeSection().setStackDepth(stackDepth + 1);
+  return true;
+}
+
+bool BytecodeEmitter::isRHSObjLiteralCompatible(ParseNode* value) {
+  return value->isKind(ParseNodeKind::NumberExpr) ||
+         value->isKind(ParseNodeKind::TrueExpr) ||
+         value->isKind(ParseNodeKind::FalseExpr) ||
+         value->isKind(ParseNodeKind::NullExpr) ||
+         value->isKind(ParseNodeKind::RawUndefinedExpr) ||
+         value->isKind(ParseNodeKind::StringExpr) ||
+         value->isKind(ParseNodeKind::TemplateStringExpr);
+}
+
+bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralCreationData* data,
+                                          ParseNode* value) {
+  if (value->isKind(ParseNodeKind::NumberExpr)) {
+    double numValue = value->as<NumericLiteral>().value();
+    int32_t i = 0;
+    js::Value v;
+    if (NumberIsInt32(numValue, &i)) {
+      v.setInt32(i);
+    } else {
+      v.setDouble(numValue);
+    }
+    if (!data->writer().propWithConstNumericValue(v)) {
+      return false;
+    }
+  } else if (value->isKind(ParseNodeKind::TrueExpr)) {
+    if (!data->writer().propWithTrueValue()) {
+      return false;
+    }
+  } else if (value->isKind(ParseNodeKind::FalseExpr)) {
+    if (!data->writer().propWithFalseValue()) {
+      return false;
+    }
+  } else if (value->isKind(ParseNodeKind::NullExpr)) {
+    if (!data->writer().propWithNullValue()) {
+      return false;
+    }
+  } else if (value->isKind(ParseNodeKind::RawUndefinedExpr)) {
+    if (!data->writer().propWithUndefinedValue()) {
+      return false;
+    }
+  } else if (value->isKind(ParseNodeKind::StringExpr) ||
+             value->isKind(ParseNodeKind::TemplateStringExpr)) {
+    uint32_t valueAtomIndex = 0;
+    if (!data->addAtom(value->as<NameNode>().atom(), &valueAtomIndex)) {
+      return false;
+    }
+    if (!data->writer().propWithAtomValue(valueAtomIndex)) {
+      return false;
+    }
+  } else {
+    return false;
+  }
+  return true;
+}
+
 FieldInitializers BytecodeEmitter::setupFieldInitializers(
     ListNode* classMembers) {
   size_t numFields = 0;
   for (ParseNode* propdef : classMembers->contents()) {
     if (propdef->is<ClassField>()) {
       FunctionNode* initializer = propdef->as<ClassField>().initializer();
       // Don't include fields without initializers.
       if (initializer != nullptr) {
         numFields++;
       }
     }
   }
   return FieldInitializers(numFields);
 }
 
 // Purpose of .fieldKeys:
-// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, not
-// object construction time. The transformation to do so is roughly as follows:
+// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time,
+// not object construction time. The transformation to do so is roughly as
+// follows:
 //
 // class C {
 //   [keyExpr] = valueExpr;
 // }
 // -->
 // let .fieldKeys = [keyExpr];
 // let .initializers = [
 //   () => {
@@ -8203,33 +8439,33 @@ bool BytecodeEmitter::emitInitializeInst
 
   if (!emitGetName(cx->names().dotInitializers)) {
     //              [stack] ARRAY
     return false;
   }
 
   for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) {
     if (fieldIndex < numFields - 1) {
-      // We DUP to keep the array around (it is consumed in the bytecode below)
-      // for next iterations of this loop, except for the last iteration, which
-      // avoids an extra POP at the end of the loop.
+      // We DUP to keep the array around (it is consumed in the bytecode
+      // below) for next iterations of this loop, except for the last
+      // iteration, which avoids an extra POP at the end of the loop.
       if (!emit1(JSOP_DUP)) {
         //          [stack] ARRAY ARRAY
         return false;
       }
     }
 
     if (!emitNumberOp(fieldIndex)) {
       //            [stack] ARRAY? ARRAY INDEX
       return false;
     }
 
-    // Don't use CALLELEM here, because the receiver of the call != the receiver
-    // of this getelem. (Specifically, the call receiver is `this`, and the
-    // receiver of this getelem is `.initializers`)
+    // Don't use CALLELEM here, because the receiver of the call != the
+    // receiver of this getelem. (Specifically, the call receiver is `this`,
+    // and the receiver of this getelem is `.initializers`)
     if (!emit1(JSOP_GETELEM)) {
       //            [stack] ARRAY? FUNC
       return false;
     }
 
     // This is guaranteed to run after super(), so we don't need TDZ checks.
     if (!emitGetName(cx->names().dotThis)) {
       //            [stack] ARRAY? FUNC THIS
@@ -8247,33 +8483,121 @@ bool BytecodeEmitter::emitInitializeInst
     }
   }
 
   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);
+MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode,
+                                                  bool isInnerSingleton) {
+  bool isSingletonContext = !objNode->hasNonConstInitializer() &&
+                            objNode->head() && checkSingletonContext();
+
+  // Note: this method uses the ObjLiteralWriter and emits
+  // ObjLiteralCreationData objects into the GCThingList, which will evaluate
+  // them into real GC objects during JSScript::fullyInitFromEmitter.
+  // Eventually we want OBJLITERAL to be a real opcode, but for now,
+  // performance constraints limit us to evaluating object literals at the end
+  // of parse, when we're allowed to allocate GC things.
+  //
+  // There are three cases here, in descending order of preference:
+  //
+  // 1. The list of property names is "normal" and constant (no computed
+  //    values, no integer indices), the values are all simple constants
+  //    (numbers, booleans, strings), *and* this occurs in a run-once
+  //    (singleton) context. In this case, we can add emit ObjLiteral
+  //    instructions to build an object with values, and the object will be
+  //    attached to a JSOP_OBJECT opcode, whose semantics are for the backend to
+  //    simply steal the object from the script.
+  //
+  // 2. The list of property names is "normal" and constant as above, but some
+  //    values are complex (computed expressions, sub-objects, functions,
+  //    etc.), or else this occurs in a non-run-once (non-singleton) context.
+  //    In this case, we can use the ObjLiteral functionality to describe an
+  //    *empty* object (all values left undefined) with the right fields, which
+  //    will become a JSOP_NEWOBJECT opcode using this template object to speed
+  //    the creation of the object each time it executes (stealing its shape,
+  //    etc.). The emitted bytecode still needs INITPROP ops to set the values
+  //    in this case.
+  //
+  // 3. Any other case. As a fallback, we use NEWINIT to create a new, empty
+  //    object (i.e., `{}`) and then emit bytecode to initialize its properties
+  //    one-by-one.
+
+  bool useObjLiteral = false;
+  bool useObjLiteralValues = false;
+  isPropertyListObjLiteralCompatible(objNode, ObjectLiteral,
+                                     &useObjLiteralValues, &useObjLiteral);
+  if (!isSingletonContext) {
+    useObjLiteralValues = false;
   }
 
   //                [stack]
-
+  //
   ObjectEmitter oe(this);
-  if (!oe.emitObject(objNode->count())) {
-    //              [stack] OBJ
-    return false;
-  }
-  if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
-    //              [stack] OBJ
-    return false;
-  }
+  if (useObjLiteral) {
+    // The flags here determine how the object is eventually constructed. The
+    // rules below are made to *exactly match* the frontend/parser behavior
+    // before the ObjLiteral functionality was added. Be very, very careful
+    // changing these rules: any deviation is bound to cause a regression in
+    // some benchmark that depends on the way the object groups are created. Do
+    // not change without running (at least) Speedometer, Octane, Kraken, TP6,
+    // and AWSY tests.
+    ObjLiteralFlags flags;
+    if (isSingletonContext) {
+      // Use `ObjectGroup::newPlainObject` rather than
+      // `NewBuiltinClassInstance<PlainObject>`.
+      flags += ObjLiteralFlag::SpecificGroup;
+      if (!isInnerSingleton) {
+        flags += ObjLiteralFlag::Singleton;
+      }
+    }
+    if (!useObjLiteralValues) {
+      flags += ObjLiteralFlag::NoValues;
+    }
+
+    // Use an ObjLiteral op. This will record ObjLiteral insns in the
+    // objLiteralWriter's buffer and add a fixup to the list of ObjLiteral
+    // fixups so that at GC-publish time at the end of parse, the full (case 1)
+    // or template-without-values (case 2) object can be allocated and the
+    // bytecode can be patched to refer to it.
+    if (!emitPropertyListObjLiteral(objNode, ObjectLiteral, flags)) {
+      //              [stack] OBJ
+      return false;
+    }
+    // Put the ObjectEmitter in the right state. This tells it that there will
+    // already be an object on the stack as a result of the (eventual)
+    // NEWOBJECT or OBJECT op, and prepares it to emit values if needed.
+    if (!oe.emitObjectWithTemplateOnStack()) {
+      //              [stack] OBJ
+      return false;
+    }
+    if (!useObjLiteralValues) {
+      // Case 2 above: the ObjLiteral only created a template object. We still
+      // need to emit bytecode to fill in its values.
+      if (!emitPropertyList(objNode, oe, ObjectLiteral,
+                            /* isInnerSingleton = */ true)) {
+        //              [stack] OBJ
+        return false;
+      }
+    }
+  } else {
+    // No ObjLiteral use, just bytecode to build the object from scratch.
+    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;
 }
 
@@ -8299,52 +8623,27 @@ bool BytecodeEmitter::replaceNewInitWith
   code[0] = JSOP_NEWOBJECT;
   SET_UINT32(code, index);
 
   return true;
 }
 
 bool BytecodeEmitter::emitArrayLiteral(ListNode* array) {
   if (!array->hasNonConstInitializer() && array->head()) {
-    if (checkSingletonContext()) {
-      // Bake in the object entirely if it will only be created once.
-      return emitSingletonInitialiser(array);
-    }
-
+    bool isSingleton = checkSingletonContext();
     // If the array consists entirely of primitive values, make a
     // template object with copy on write elements that can be reused
-    // every time the initializer executes. Don't do this if the array is
-    // small: copying the elements lazily is not worth it in that case.
+    // every time the initializer executes. In non-singleton mode, don't do
+    // this if the array is small: copying the elements lazily is not worth it
+    // in that case.
     static const size_t MinElementsForCopyOnWrite = 5;
     if (emitterMode != BytecodeEmitter::SelfHosting &&
-        array->count() >= MinElementsForCopyOnWrite) {
-      RootedValue value(cx);
-      if (!array->getConstantValue(cx, ParseNode::ForCopyOnWriteArray,
-                                   &value)) {
-        return false;
-      }
-      if (!value.isMagic(JS_GENERIC_MAGIC)) {
-        // Note: the group of the template object might not yet reflect
-        // that the object has copy on write elements. When the
-        // interpreter or JIT compiler fetches the template, it should
-        // use ObjectGroup::getOrFixupCopyOnWriteObject to make sure the
-        // group for the template is accurate. We don't do this here as we
-        // want to use ObjectGroup::allocationSiteGroup, which requires a
-        // finished script.
-        JSObject* obj = &value.toObject();
-        MOZ_ASSERT(obj->is<ArrayObject>() &&
-                   obj->as<ArrayObject>().denseElementsAreCopyOnWrite());
-
-        ObjectBox* objbox = parser->newObjectBox(obj);
-        if (!objbox) {
-          return false;
-        }
-
-        return emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE);
-      }
+        (array->count() >= MinElementsForCopyOnWrite || isSingleton) &&
+        isArrayObjLiteralCompatible(array->head())) {
+      return emitObjLiteralArray(array->head(), /* isCow = */ !isSingleton);
     }
   }
 
   return emitArray(array->head(), array->count());
 }
 
 bool BytecodeEmitter::emitArray(ParseNode* arrayHead, uint32_t count) {
   /*
@@ -8934,90 +9233,90 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
 
   if (!(instrumentationKinds & (uint32_t)kind)) {
     return true;
   }
 
   // Instrumentation is emitted in the form of a call to the realm's
   // instrumentation callback, guarded by a test of whether instrumentation is
   // currently active in the realm. The callback is invoked with the kind of
-  // operation which is executing, the current script's instrumentation ID, and
-  // the offset of the bytecode location after the instrumentation. Some
+  // operation which is executing, the current script's instrumentation ID,
+  // and the offset of the bytecode location after the instrumentation. Some
   // operation kinds have more arguments, which will be pushed by
   // pushOperandsCallback.
 
   unsigned initialDepth = bytecodeSection().stackDepth();
   InternalIfEmitter ifEmitter(this);
 
   if (!emit1(JSOP_INSTRUMENTATION_ACTIVE)) {
     return false;
   }
-  //                [stack] ACTIVE
+  //            [stack] ACTIVE
 
   if (!ifEmitter.emitThen()) {
     return false;
   }
-  //                [stack]
+  //            [stack]
 
   // Push the instrumentation callback for the current realm as the callee.
   if (!emit1(JSOP_INSTRUMENTATION_CALLBACK)) {
     return false;
   }
-  //                [stack] CALLBACK
+  //            [stack] CALLBACK
 
   // Push undefined for the call's |this| value.
   if (!emit1(JSOP_UNDEFINED)) {
     return false;
   }
-  //                [stack] CALLBACK UNDEFINED
+  //            [stack] CALLBACK UNDEFINED
 
   JSAtom* atom = RealmInstrumentation::getInstrumentationKindName(cx, kind);
   if (!atom) {
     return false;
   }
 
   uint32_t index;
   if (!makeAtomIndex(atom, &index)) {
     return false;
   }
 
   if (!emitAtomOp(index, JSOP_STRING)) {
     return false;
   }
-  //                [stack] CALLBACK UNDEFINED KIND
+  //            [stack] CALLBACK UNDEFINED KIND
 
   if (!emit1(JSOP_INSTRUMENTATION_SCRIPT_ID)) {
     return false;
   }
-  //                [stack] CALLBACK UNDEFINED KIND SCRIPT
+  //            [stack] CALLBACK UNDEFINED KIND SCRIPT
 
   // Push the offset of the bytecode location following the instrumentation.
   BytecodeOffset updateOffset;
   if (!emitN(JSOP_INT32, 4, &updateOffset)) {
     return false;
   }
-  //                [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET
+  //            [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET
 
   unsigned numPushed = bytecodeSection().stackDepth() - initialDepth;
 
   if (pushOperandsCallback && !pushOperandsCallback(numPushed)) {
     return false;
   }
-  //                [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET ...EXTRA_ARGS
+  //            [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET ...EXTRA_ARGS
 
   unsigned argc = bytecodeSection().stackDepth() - initialDepth - 2;
   if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) {
     return false;
   }
-  //                [stack] RV
+  //            [stack] RV
 
   if (!emit1(JSOP_POP)) {
     return false;
   }
-  //                [stack]
+  //            [stack]
 
   if (!ifEmitter.emitEnd()) {
     return false;
   }
 
   SET_INT32(bytecodeSection().code(updateOffset),
             bytecodeSection().code().length());
 
@@ -9055,17 +9354,18 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::e
           [=](uint32_t pushed) { return emitDupAt(pushed + 2, 3); });
     default:
       return true;
   }
 }
 
 bool BytecodeEmitter::emitTree(
     ParseNode* pn, ValueUsage valueUsage /* = ValueUsage::WantValue */,
-    EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */) {
+    EmitLineNumberNote emitLineNote /* = EMIT_LINENOTE */,
+    bool isInnerSingleton /* = false */) {
   if (!CheckRecursionLimit(cx)) {
     return false;
   }
 
   /* Emit notes to tell the current bytecode's source line number.
      However, a couple trees require special treatment; see the
      relevant emitter functions for details. */
   if (emitLineNote == EMIT_LINENOTE &&
@@ -9476,17 +9776,17 @@ bool BytecodeEmitter::emitTree(
 
     case ParseNodeKind::ArrayExpr:
       if (!emitArrayLiteral(&pn->as<ListNode>())) {
         return false;
       }
       break;
 
     case ParseNodeKind::ObjectExpr:
-      if (!emitObject(&pn->as<ListNode>())) {
+      if (!emitObject(&pn->as<ListNode>(), isInnerSingleton)) {
         return false;
       }
       break;
 
     case ParseNodeKind::Name:
       if (!emitGetName(&pn->as<NameNode>())) {
         return false;
       }
@@ -9514,17 +9814,18 @@ bool BytecodeEmitter::emitTree(
     case ParseNodeKind::BigIntExpr:
       if (!emitBigIntOp(&pn->as<BigIntLiteral>())) {
         return false;
       }
       break;
 
     case ParseNodeKind::RegExpExpr: {
       uint32_t index;
-      if (!perScriptData().gcThingList().append(&pn->as<RegExpLiteral>(), &index)) {
+      if (!perScriptData().gcThingList().append(&pn->as<RegExpLiteral>(),
+                                                &index)) {
         return false;
       }
       if (!emitRegExp(index)) {
         return false;
       }
       break;
     }
 
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -6,18 +6,19 @@
 
 /* JS bytecode generation. */
 
 #ifndef frontend_BytecodeEmitter_h
 #define frontend_BytecodeEmitter_h
 
 #include "mozilla/Assertions.h"  // MOZ_ASSERT
 #include "mozilla/Attributes.h"  // MOZ_STACK_CLASS, MOZ_MUST_USE, MOZ_ALWAYS_INLINE, MOZ_NEVER_INLINE, MOZ_RAII
-#include "mozilla/Maybe.h"  // mozilla::Maybe, mozilla::Some
-#include "mozilla/Span.h"   // mozilla::Span
+#include "mozilla/Maybe.h"   // mozilla::Maybe, mozilla::Some
+#include "mozilla/Span.h"    // mozilla::Span
+#include "mozilla/Vector.h"  // mozilla::Vector
 
 #include <stddef.h>  // ptrdiff_t
 #include <stdint.h>  // uint16_t, uint32_t
 
 #include "jsapi.h"  // CompletionKind
 
 #include "frontend/BCEParserHandle.h"            // BCEParserHandle
 #include "frontend/BytecodeControlStructures.h"  // NestableControl
@@ -357,17 +358,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
                                      BytecodeOffsetDiff offset);
 
   // Control whether emitTree emits a line number note.
   enum EmitLineNumberNote { EMIT_LINENOTE, SUPPRESS_LINENOTE };
 
   // Emit code for the tree rooted at pn.
   MOZ_MUST_USE bool emitTree(ParseNode* pn,
                              ValueUsage valueUsage = ValueUsage::WantValue,
-                             EmitLineNumberNote emitLineNote = EMIT_LINENOTE);
+                             EmitLineNumberNote emitLineNote = EMIT_LINENOTE,
+                             bool isInnerSingleton = false);
 
   // Emit global, eval, or module code for tree rooted at body. Always
   // encompasses the entire source.
   MOZ_MUST_USE bool emitScript(ParseNode* body);
 
   // Emit function code for the tree rooted at body.
   enum class TopLevelFunction { No, Yes };
   MOZ_MUST_USE bool emitFunctionScript(FunctionNode* funNode,
@@ -470,25 +472,46 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
   MOZ_MUST_USE bool emitObjectOp(ObjectBox* objbox, JSOp op);
   MOZ_MUST_USE bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2,
                                      JSOp op);
   MOZ_MUST_USE bool emitRegExp(uint32_t index);
 
   MOZ_NEVER_INLINE MOZ_MUST_USE bool emitFunction(
       FunctionNode* funNode, bool needsProto = false,
       ListNode* classContentsIfConstructor = nullptr);
-  MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode);
+  MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode,
+                                                bool isInnerSingleton = false);
 
   MOZ_MUST_USE bool replaceNewInitWithNewObject(JSObject* obj,
                                                 BytecodeOffset offset);
 
   MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList);
 
+  // Can we use the object-literal writer either in singleton-object mode (with
+  // values) or in template mode (field names only, no values) for the property
+  // list?
+  void isPropertyListObjLiteralCompatible(ListNode* obj, PropListType type,
+                                          bool* withValues,
+                                          bool* withoutValues);
+  bool isArrayObjLiteralCompatible(ParseNode* arrayHead);
+
   MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe,
-                                     PropListType type);
+                                     PropListType type,
+                                     bool isInnerSingleton = false);
+
+  MOZ_MUST_USE bool emitPropertyListObjLiteral(ListNode* obj, PropListType type,
+                                               ObjLiteralFlags flags);
+
+  MOZ_MUST_USE bool emitObjLiteralArray(ParseNode* arrayHead, bool isCow);
+
+  // Is a field value OBJLITERAL-compatible?
+  MOZ_MUST_USE bool isRHSObjLiteralCompatible(ParseNode* value);
+
+  MOZ_MUST_USE bool emitObjLiteralValue(ObjLiteralCreationData* data,
+                                        ParseNode* value);
 
   FieldInitializers setupFieldInitializers(ListNode* classMembers);
   MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj);
   MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce,
                                                 ListNode* obj);
   const FieldInitializers& findFieldInitializersForCall();
   MOZ_MUST_USE bool emitInitializeInstanceFields();
 
--- a/js/src/frontend/BytecodeSection.cpp
+++ b/js/src/frontend/BytecodeSection.cpp
@@ -69,16 +69,25 @@ bool GCThingList::finish(JSContext* cx, 
     bool operator()(RegExpCreationData& data) {
       RegExpObject* regexp = data.createRegExp(cx);
       if (!regexp) {
         return false;
       }
       array[i] = JS::GCCellPtr(regexp);
       return true;
     }
+
+    bool operator()(ObjLiteralCreationData& data) {
+      JSObject* obj = data.create(cx);
+      if (!obj) {
+        return false;
+      }
+      array[i] = JS::GCCellPtr(obj);
+      return true;
+    }
   };
 
   for (uint32_t i = 0; i < length(); i++) {
     Matcher m{cx, i, array};
     if (!vector[i].get().match(m)) {
       return false;
     }
   }
@@ -151,16 +160,20 @@ void CGScopeNoteList::finish(mozilla::Sp
 void CGResumeOffsetList::finish(mozilla::Span<uint32_t> array) {
   MOZ_ASSERT(length() == array.size());
 
   for (unsigned i = 0; i < length(); i++) {
     array[i] = list[i];
   }
 }
 
+JSObject* ObjLiteralCreationData::create(JSContext* cx) {
+  return InterpretObjLiteral(cx, atoms_, writer_);
+}
+
 BytecodeSection::BytecodeSection(JSContext* cx, uint32_t lineNum)
     : code_(cx),
       notes_(cx),
       lastNoteOffset_(0),
       tryNoteList_(cx),
       scopeNoteList_(cx),
       resumeOffsetList_(cx),
       currentLine_(lineNum) {}
--- a/js/src/frontend/BytecodeSection.h
+++ b/js/src/frontend/BytecodeSection.h
@@ -14,16 +14,17 @@
 #include <stddef.h>  // ptrdiff_t, size_t
 #include <stdint.h>  // uint16_t, int32_t, uint32_t
 
 #include "jstypes.h"                   // JS_PUBLIC_API
 #include "NamespaceImports.h"          // ValueVector
 #include "frontend/BytecodeOffset.h"   // BytecodeOffset
 #include "frontend/JumpList.h"         // JumpTarget
 #include "frontend/NameCollections.h"  // AtomIndexMap, PooledMapPtr
+#include "frontend/ObjLiteral.h"       // ObjLiteralCreationData
 #include "frontend/ParseNode.h"        // BigIntLiteral
 #include "frontend/SourceNotes.h"      // jssrcnote
 #include "gc/Barrier.h"                // GCPtrObject, GCPtrScope, GCPtrValue
 #include "gc/Rooting.h"                // JS::Rooted
 #include "js/GCVariant.h"              // GCPolicy<mozilla::Variant>
 #include "js/GCVector.h"               // GCVector
 #include "js/TypeDecls.h"              // jsbytecode, JSContext
 #include "js/Value.h"                  // JS::Vector
@@ -38,18 +39,18 @@ class Scope;
 using BigIntVector = JS::GCVector<js::BigInt*>;
 
 namespace frontend {
 
 class BigIntLiteral;
 class ObjectBox;
 
 struct MOZ_STACK_CLASS GCThingList {
-  using ListType =
-      mozilla::Variant<StackGCCellPtr, BigIntCreationData, RegExpCreationData>;
+  using ListType = mozilla::Variant<StackGCCellPtr, BigIntCreationData,
+                                    ObjLiteralCreationData, RegExpCreationData>;
   JS::RootedVector<ListType> vector;
 
   // Last emitted object.
   ObjectBox* lastbox = nullptr;
 
   // Index of the first scope in the vector.
   mozilla::Maybe<uint32_t> firstScopeIndex;
 
@@ -78,16 +79,20 @@ struct MOZ_STACK_CLASS GCThingList {
     *index = vector.length();
     if (literal->isDeferred()) {
       return vector.append(
           mozilla::AsVariant(std::move(literal->creationData())));
     }
     return vector.append(mozilla::AsVariant(
         StackGCCellPtr(JS::GCCellPtr(literal->objbox()->object()))));
   }
+  MOZ_MUST_USE bool append(ObjLiteralCreationData&& objlit, uint32_t* index) {
+    *index = vector.length();
+    return vector.append(mozilla::AsVariant(std::move(objlit)));
+  }
   MOZ_MUST_USE bool append(ObjectBox* obj, uint32_t* index);
 
   uint32_t length() const { return vector.length(); }
   MOZ_MUST_USE bool finish(JSContext* cx, mozilla::Span<JS::GCCellPtr> array);
   void finishInnerFunctions();
 
   Scope* getScope(size_t index) const {
     return &vector[index].get().as<StackGCCellPtr>().get().as<Scope>();
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ObjLiteral.cpp
@@ -0,0 +1,163 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=2 et tw=0 ft=c:
+ *
+ * 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/ObjLiteral.h"
+#include "mozilla/DebugOnly.h"
+#include "js/RootingAPI.h"
+#include "vm/JSAtom.h"
+#include "vm/JSObject.h"
+#include "vm/ObjectGroup.h"
+
+#include "gc/ObjectKind-inl.h"
+#include "vm/JSAtom-inl.h"
+#include "vm/JSObject-inl.h"
+
+namespace js {
+
+static void InterpretObjLiteralValue(ObjLiteralAtomVector& atoms,
+                                     const ObjLiteralInsn& insn,
+                                     MutableHandleValue propVal) {
+  switch (insn.getOp()) {
+    case ObjLiteralOpcode::ConstValue:
+      propVal.set(insn.getConstValue());
+      break;
+    case ObjLiteralOpcode::ConstAtom: {
+      uint32_t index = insn.getAtomIndex();
+      propVal.setString(atoms[index]);
+      break;
+    }
+    case ObjLiteralOpcode::Null:
+      propVal.setNull();
+      break;
+    case ObjLiteralOpcode::Undefined:
+      propVal.setUndefined();
+      break;
+    case ObjLiteralOpcode::True:
+      propVal.setBoolean(true);
+      break;
+    case ObjLiteralOpcode::False:
+      propVal.setBoolean(false);
+      break;
+    default:
+      MOZ_CRASH("Unexpected object-literal instruction opcode");
+  }
+}
+
+static JSObject* InterpretObjLiteralObj(
+    JSContext* cx, ObjLiteralAtomVector& atoms,
+    mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) {
+  bool specificGroup = flags.contains(ObjLiteralFlag::SpecificGroup);
+  bool singleton = flags.contains(ObjLiteralFlag::Singleton);
+  bool noValues = flags.contains(ObjLiteralFlag::NoValues);
+
+  ObjLiteralReader reader(literalInsns);
+  ObjLiteralInsn insn;
+
+  jsid propId;
+  Rooted<Value> propVal(cx);
+  Rooted<IdValueVector> properties(cx, IdValueVector(cx));
+
+  // Compute property values and build the key/value-pair list.
+  while (true) {
+    if (!reader.readInsn(&insn)) {
+      break;
+    }
+    MOZ_ASSERT(insn.isValid());
+
+    if (insn.getKey().isArrayIndex()) {
+      propId = INT_TO_JSID(insn.getKey().getArrayIndex());
+    } else {
+      propId = AtomToId(atoms[insn.getKey().getAtomIndex()]);
+    }
+
+    if (noValues) {
+      propVal.setUndefined();
+    } else {
+      InterpretObjLiteralValue(atoms, insn, &propVal);
+    }
+
+    if (!properties.append(IdValuePair(propId, propVal))) {
+      return nullptr;
+    }
+  }
+
+  if (specificGroup) {
+    return ObjectGroup::newPlainObject(
+        cx, properties.begin(), properties.length(),
+        singleton ? SingletonObject : TenuredObject);
+  }
+
+  gc::AllocKind allocKind = gc::GetGCObjectKind(properties.length());
+  RootedPlainObject result(
+      cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, TenuredObject));
+  if (!result) {
+    return nullptr;
+  }
+
+  Rooted<JS::PropertyKey> propKey(cx);
+  for (const auto& kvPair : properties) {
+    propKey.set(kvPair.id);
+    propVal.set(kvPair.value);
+    if (!NativeDefineDataProperty(cx, result, propKey, propVal,
+                                  JSPROP_ENUMERATE)) {
+      return nullptr;
+    }
+  }
+  return result;
+}
+
+static JSObject* InterpretObjLiteralArray(
+    JSContext* cx, ObjLiteralAtomVector& atoms,
+    mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) {
+  bool isCow = flags.contains(ObjLiteralFlag::ArrayCOW);
+  ObjLiteralReader reader(literalInsns);
+  ObjLiteralInsn insn;
+
+  Rooted<ValueVector> elements(cx, ValueVector(cx));
+  Rooted<Value> propVal(cx);
+
+  mozilla::DebugOnly<uint32_t> index = 0;
+  while (true) {
+    if (!reader.readInsn(&insn)) {
+      break;
+    }
+    MOZ_ASSERT(insn.isValid());
+
+    MOZ_ASSERT(insn.getKey().isArrayIndex());
+    MOZ_ASSERT(insn.getKey().getArrayIndex() == index);
+#ifdef DEBUG
+    index++;
+#endif
+    propVal.setUndefined();
+    InterpretObjLiteralValue(atoms, insn, &propVal);
+    if (!elements.append(propVal)) {
+      return nullptr;
+    }
+  }
+
+  ObjectGroup::NewArrayKind arrayKind =
+      isCow ? ObjectGroup::NewArrayKind::CopyOnWrite
+            : ObjectGroup::NewArrayKind::Normal;
+  RootedObject result(
+      cx, ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
+                                      NewObjectKind::TenuredObject, arrayKind));
+  if (!result) {
+    return nullptr;
+  }
+
+  return result;
+}
+
+JSObject* InterpretObjLiteral(JSContext* cx, ObjLiteralAtomVector& atoms,
+                              mozilla::Span<const uint8_t> literalInsns,
+                              ObjLiteralFlags flags) {
+  return flags.contains(ObjLiteralFlag::Array)
+             ? InterpretObjLiteralArray(cx, atoms, literalInsns, flags)
+             : InterpretObjLiteralObj(cx, atoms, literalInsns, flags);
+}
+
+}  // namespace js
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ObjLiteral.h
@@ -0,0 +1,426 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: set ts=8 sw=2 et tw=0 ft=c:
+ *
+ * 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_ObjLiteral_h
+#define frontend_ObjLiteral_h
+
+#include "mozilla/EndianUtils.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/Span.h"
+
+#include "js/AllocPolicy.h"
+#include "js/GCPolicyAPI.h"
+#include "js/Value.h"
+#include "js/Vector.h"
+
+namespace js {
+
+// Object-literal instruction opcodes. An object literal is constructed by a
+// straight-line sequence of these ops, each adding one property to the
+// object.
+enum class ObjLiteralOpcode : uint8_t {
+  INVALID = 0,
+
+  ConstValue = 1,  // numeric types only.
+  ConstAtom = 2,
+  Null = 3,
+  Undefined = 4,
+  True = 5,
+  False = 6,
+
+  MAX = False,
+};
+
+// Flags that are associated with a sequence of object-literal instructions.
+// (These become bitflags by wrapping with EnumSet below.)
+enum class ObjLiteralFlag : uint8_t {
+  // If set, this object is an array.
+  Array = 1,
+
+  // If set, the created object will be created with an object group either
+  // freshly allocated or determined by property names by calling
+  // `ObjectGroup::newPlainObject`.
+  SpecificGroup = 2,
+  // If set, the created object will be created with newType == SingletonObject
+  // rather than TenuredObject.
+  Singleton = 3,
+  // If set, the created array will be created as a COW array rather than a
+  // normal array.
+  ArrayCOW = 4,
+
+  // No values are provided; the object is meant as a template object.
+  NoValues = 5,
+};
+
+using ObjLiteralFlags = mozilla::EnumSet<ObjLiteralFlag>;
+
+inline bool ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op) {
+  return op == ObjLiteralOpcode::ConstValue;
+}
+
+inline bool ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op) {
+  return op == ObjLiteralOpcode::ConstAtom;
+}
+
+struct ObjLiteralReaderBase;
+// or an integer index.
+struct ObjLiteralKey {
+ private:
+  uint32_t value_;
+  bool isArrayIndex_;
+
+ public:
+  ObjLiteralKey() : value_(0), isArrayIndex_(true) {}
+  ObjLiteralKey(uint32_t value, bool isArrayIndex)
+      : value_(value), isArrayIndex_(isArrayIndex) {}
+  ObjLiteralKey(const ObjLiteralKey& other) = default;
+
+  static ObjLiteralKey fromPropName(uint32_t atomIndex) {
+    return ObjLiteralKey(atomIndex, false);
+  }
+  static ObjLiteralKey fromArrayIndex(uint32_t index) {
+    return ObjLiteralKey(index, true);
+  }
+
+  bool isAtomIndex() const { return !isArrayIndex_; }
+  bool isArrayIndex() const { return isArrayIndex_; }
+
+  uint32_t getAtomIndex() const {
+    MOZ_ASSERT(isAtomIndex());
+    return value_;
+  }
+  uint32_t getArrayIndex() const {
+    MOZ_ASSERT(isArrayIndex());
+    return value_;
+  }
+
+  uint32_t rawIndex() const { return value_; }
+};
+
+struct ObjLiteralWriterBase {
+ protected:
+  friend struct ObjLiteralReaderBase;  // for access to mask and shift.
+  static const uint32_t ATOM_INDEX_MASK = 0x007fffff;
+  // If set, the atom index field is an array index, not an atom index.
+  static const uint32_t INDEXED_PROP = 0x00800000;
+  static const int OP_SHIFT = 24;
+
+ protected:
+  Vector<uint8_t, 64> code_;
+
+ public:
+  explicit ObjLiteralWriterBase(JSContext* cx) : code_(cx) {}
+
+  uint32_t curOffset() const { return code_.length(); }
+
+  MOZ_MUST_USE bool prepareBytes(size_t len, uint8_t** p) {
+    size_t offset = code_.length();
+    if (!code_.growByUninitialized(len)) {
+      return false;
+    }
+    *p = &code_[offset];
+    return true;
+  }
+
+  template <typename T>
+  MOZ_MUST_USE bool pushRawData(T data) {
+    uint8_t* p = nullptr;
+    if (!prepareBytes(sizeof(T), &p)) {
+      return false;
+    }
+    mozilla::NativeEndian::copyAndSwapToLittleEndian(reinterpret_cast<void*>(p),
+                                                     &data, 1);
+    return true;
+  }
+
+  MOZ_MUST_USE bool pushOpAndName(ObjLiteralOpcode op, ObjLiteralKey key) {
+    uint32_t data = (key.rawIndex() & ATOM_INDEX_MASK) |
+                    (key.isArrayIndex() ? INDEXED_PROP : 0) |
+                    (static_cast<uint8_t>(op) << OP_SHIFT);
+    return pushRawData(data);
+  }
+
+  MOZ_MUST_USE bool pushValueArg(const JS::Value& value) {
+    MOZ_ASSERT(value.isNumber() || value.isNullOrUndefined() ||
+               value.isBoolean());
+    uint64_t data = value.asRawBits();
+    return pushRawData(data);
+  }
+
+  MOZ_MUST_USE bool pushAtomArg(uint32_t atomIndex) {
+    return pushRawData(atomIndex);
+  }
+};
+
+// An object-literal instruction writer. This class, held by the bytecode
+// emitter, keeps a sequence of object-literal instructions emitted as object
+// literal expressions are parsed. It allows the user to 'begin' and 'end'
+// straight-line sequences, returning the offsets for this range of instructions
+// within the writer.
+struct ObjLiteralWriter : private ObjLiteralWriterBase {
+ public:
+  explicit ObjLiteralWriter(JSContext* cx)
+      : ObjLiteralWriterBase(cx), flags_() {}
+
+  void clear() { code_.clear(); }
+
+  mozilla::Span<const uint8_t> getCode() const { return code_; }
+  ObjLiteralFlags getFlags() const { return flags_; }
+
+  void beginObject(ObjLiteralFlags flags) { flags_ = flags; }
+  void setPropName(uint32_t propName) {
+    MOZ_ASSERT(propName <= ATOM_INDEX_MASK);
+    nextKey_ = ObjLiteralKey::fromPropName(propName);
+  }
+  void setPropIndex(uint32_t propIndex) {
+    MOZ_ASSERT(propIndex <= ATOM_INDEX_MASK);
+    nextKey_ = ObjLiteralKey::fromArrayIndex(propIndex);
+  }
+
+  MOZ_MUST_USE bool propWithConstNumericValue(const JS::Value& value) {
+    MOZ_ASSERT(value.isNumber());
+    return pushOpAndName(ObjLiteralOpcode::ConstValue, nextKey_) &&
+           pushValueArg(value);
+  }
+  MOZ_MUST_USE bool propWithAtomValue(uint32_t value) {
+    return pushOpAndName(ObjLiteralOpcode::ConstAtom, nextKey_) &&
+           pushAtomArg(value);
+  }
+  MOZ_MUST_USE bool propWithNullValue() {
+    return pushOpAndName(ObjLiteralOpcode::Null, nextKey_);
+  }
+  MOZ_MUST_USE bool propWithUndefinedValue() {
+    return pushOpAndName(ObjLiteralOpcode::Undefined, nextKey_);
+  }
+  MOZ_MUST_USE bool propWithTrueValue() {
+    return pushOpAndName(ObjLiteralOpcode::True, nextKey_);
+  }
+  MOZ_MUST_USE bool propWithFalseValue() {
+    return pushOpAndName(ObjLiteralOpcode::False, nextKey_);
+  }
+
+  static bool arrayIndexInRange(int32_t i) {
+    return i >= 0 && static_cast<uint32_t>(i) <= ATOM_INDEX_MASK;
+  }
+
+ private:
+  ObjLiteralFlags flags_;
+  ObjLiteralKey nextKey_;
+};
+
+struct ObjLiteralReaderBase {
+ private:
+  mozilla::Span<const uint8_t> data_;
+  size_t cursor_;
+
+  MOZ_MUST_USE bool readBytes(size_t size, const uint8_t** p) {
+    if (cursor_ + size > data_.Length()) {
+      return false;
+    }
+    *p = data_.From(cursor_).data();
+    cursor_ += size;
+    return true;
+  }
+
+  template <typename T>
+  MOZ_MUST_USE bool readRawData(T* data) {
+    const uint8_t* p = nullptr;
+    if (!readBytes(sizeof(T), &p)) {
+      return false;
+    }
+    mozilla::NativeEndian::copyAndSwapFromLittleEndian(
+        data, reinterpret_cast<const void*>(p), 1);
+    return true;
+  }
+
+ public:
+  explicit ObjLiteralReaderBase(mozilla::Span<const uint8_t> data)
+      : data_(data), cursor_(0) {}
+
+  MOZ_MUST_USE bool readOpAndKey(ObjLiteralOpcode* op, ObjLiteralKey* key) {
+    uint32_t data;
+    if (!readRawData(&data)) {
+      return false;
+    }
+    uint8_t opbyte =
+        static_cast<uint8_t>(data >> ObjLiteralWriterBase::OP_SHIFT);
+    if (MOZ_UNLIKELY(opbyte > static_cast<uint8_t>(ObjLiteralOpcode::MAX))) {
+      return false;
+    }
+    *op = static_cast<ObjLiteralOpcode>(opbyte);
+    bool isArray = data & ObjLiteralWriterBase::INDEXED_PROP;
+    uint32_t rawIndex = data & ObjLiteralWriterBase::ATOM_INDEX_MASK;
+    *key = ObjLiteralKey(rawIndex, isArray);
+    return true;
+  }
+
+  MOZ_MUST_USE bool readValueArg(JS::Value* value) {
+    uint64_t data;
+    if (!readRawData(&data)) {
+      return false;
+    }
+    *value = JS::Value::fromRawBits(data);
+    return true;
+  }
+
+  MOZ_MUST_USE bool readAtomArg(uint32_t* atomIndex) {
+    return readRawData(atomIndex);
+  }
+};
+
+// A single object-literal instruction, creating one property on an object.
+struct ObjLiteralInsn {
+ private:
+  ObjLiteralOpcode op_;
+  ObjLiteralKey key_;
+  union Arg {
+    explicit Arg(uint64_t raw_) : raw(raw_) {}
+
+    JS::Value constValue;
+    uint32_t atomIndex;
+    uint64_t raw;
+  } arg_;
+
+ public:
+  ObjLiteralInsn() : op_(ObjLiteralOpcode::INVALID), arg_(0) {}
+  ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key)
+      : op_(op), key_(key), arg_(0) {
+    MOZ_ASSERT(!hasConstValue());
+    MOZ_ASSERT(!hasAtomIndex());
+  }
+  ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, const JS::Value& value)
+      : op_(op), key_(key), arg_(0) {
+    MOZ_ASSERT(hasConstValue());
+    MOZ_ASSERT(!hasAtomIndex());
+    arg_.constValue = value;
+  }
+  ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, uint32_t atomIndex)
+      : op_(op), key_(key), arg_(0) {
+    MOZ_ASSERT(!hasConstValue());
+    MOZ_ASSERT(hasAtomIndex());
+    arg_.atomIndex = atomIndex;
+  }
+  ObjLiteralInsn(const ObjLiteralInsn& other) : ObjLiteralInsn() {
+    *this = other;
+  }
+  ObjLiteralInsn& operator=(const ObjLiteralInsn& other) {
+    op_ = other.op_;
+    key_ = other.key_;
+    arg_.raw = other.arg_.raw;
+    return *this;
+  }
+
+  bool isValid() const {
+    return op_ > ObjLiteralOpcode::INVALID && op_ <= ObjLiteralOpcode::MAX;
+  }
+
+  ObjLiteralOpcode getOp() const {
+    MOZ_ASSERT(isValid());
+    return op_;
+  }
+  const ObjLiteralKey& getKey() const {
+    MOZ_ASSERT(isValid());
+    return key_;
+  }
+
+  bool hasConstValue() const {
+    MOZ_ASSERT(isValid());
+    return ObjLiteralOpcodeHasValueArg(op_);
+  }
+  bool hasAtomIndex() const {
+    MOZ_ASSERT(isValid());
+    return ObjLiteralOpcodeHasAtomArg(op_);
+  }
+
+  JS::Value getConstValue() const {
+    MOZ_ASSERT(isValid());
+    MOZ_ASSERT(hasConstValue());
+    return arg_.constValue;
+  }
+  uint32_t getAtomIndex() const {
+    MOZ_ASSERT(isValid());
+    MOZ_ASSERT(hasAtomIndex());
+    return arg_.atomIndex;
+  };
+};
+
+// A reader that parses a sequence of object-literal instructions out of the
+// encoded form.
+struct ObjLiteralReader : private ObjLiteralReaderBase {
+ public:
+  explicit ObjLiteralReader(mozilla::Span<const uint8_t> data)
+      : ObjLiteralReaderBase(data) {}
+
+  MOZ_MUST_USE bool readInsn(ObjLiteralInsn* insn) {
+    ObjLiteralOpcode op;
+    ObjLiteralKey key;
+    if (!readOpAndKey(&op, &key)) {
+      return false;
+    }
+    if (ObjLiteralOpcodeHasValueArg(op)) {
+      JS::Value value;
+      if (!readValueArg(&value)) {
+        return false;
+      }
+      *insn = ObjLiteralInsn(op, key, value);
+      return true;
+    }
+    if (ObjLiteralOpcodeHasAtomArg(op)) {
+      uint32_t atomIndex;
+      if (!readAtomArg(&atomIndex)) {
+        return false;
+      }
+      *insn = ObjLiteralInsn(op, key, atomIndex);
+      return true;
+    }
+    *insn = ObjLiteralInsn(op, key);
+    return true;
+  }
+};
+
+typedef Vector<JSAtom*, 4> ObjLiteralAtomVector;
+
+JSObject* InterpretObjLiteral(JSContext* cx, ObjLiteralAtomVector& atoms,
+                              mozilla::Span<const uint8_t> insns,
+                              ObjLiteralFlags flags);
+
+inline JSObject* InterpretObjLiteral(JSContext* cx, ObjLiteralAtomVector& atoms,
+                                     const ObjLiteralWriter& writer) {
+  return InterpretObjLiteral(cx, atoms, writer.getCode(), writer.getFlags());
+}
+
+class ObjLiteralCreationData {
+ private:
+  ObjLiteralWriter writer_;
+  ObjLiteralAtomVector atoms_;
+
+ public:
+  explicit ObjLiteralCreationData(JSContext* cx) : writer_(cx), atoms_(cx) {}
+
+  ObjLiteralWriter& writer() { return writer_; }
+
+  bool addAtom(JSAtom* atom, uint32_t* index) {
+    *index = atoms_.length();
+    return atoms_.append(atom);
+  }
+
+  JSObject* create(JSContext* cx);
+};
+
+}  // namespace js
+
+namespace JS {
+// Ignore GC tracing for the ObjLiteralCreationData. It contains JSAtom
+// pointers, but these are already held and rooted by the parser. (We must
+// specify GC policy for the creation data because it is placed in the
+// GC-things vector.)
+template <>
+struct GCPolicy<js::ObjLiteralCreationData>
+    : JS::IgnoreGCPolicy<js::ObjLiteralCreationData> {};
+}  // namespace JS
+
+#endif  // frontend_ObjLiteral_h
--- a/js/src/frontend/ObjectEmitter.cpp
+++ b/js/src/frontend/ObjectEmitter.cpp
@@ -26,18 +26,17 @@
 #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) {}
+PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) : bce_(bce) {}
 
 bool PropertyEmitter::prepareForProtoValue(const Maybe<uint32_t>& keyPos) {
   MOZ_ASSERT(propertyState_ == PropertyState::Start ||
              propertyState_ == PropertyState::Init);
 
   //                [stack] CTOR? OBJ CTOR?
 
   if (keyPos) {
@@ -57,17 +56,16 @@ bool PropertyEmitter::emitMutateProto() 
 
   //                [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) {
@@ -97,17 +95,16 @@ bool PropertyEmitter::emitSpread() {
 
   //                [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) {
@@ -158,18 +155,16 @@ bool PropertyEmitter::prepareForPropValu
 
 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
@@ -191,18 +186,16 @@ bool PropertyEmitter::prepareForIndexPro
 
 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
@@ -268,57 +261,51 @@ bool PropertyEmitter::emitInitHomeObject
   return true;
 }
 
 bool PropertyEmitter::emitInitProp(JS::Handle<JSAtom*> key) {
   return emitInit(isClass_ ? JSOP_INITHIDDENPROP : JSOP_INITPROP, key);
 }
 
 bool PropertyEmitter::emitInitGetter(JS::Handle<JSAtom*> key) {
-  obj_ = nullptr;
   return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER,
                   key);
 }
 
 bool PropertyEmitter::emitInitSetter(JS::Handle<JSAtom*> key) {
-  obj_ = nullptr;
   return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER,
                   key);
 }
 
 bool PropertyEmitter::emitInitIndexProp() {
   return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM
                                           : JSOP_INITELEM);
 }
 
 bool PropertyEmitter::emitInitIndexGetter() {
-  obj_ = nullptr;
   return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER
                                           : JSOP_INITELEM_GETTER);
 }
 
 bool PropertyEmitter::emitInitIndexSetter() {
-  obj_ = nullptr;
   return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER
                                           : JSOP_INITELEM_SETTER);
 }
 
 bool PropertyEmitter::emitInitComputedProp() {
   return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM
                                           : JSOP_INITELEM);
 }
 
 bool PropertyEmitter::emitInitComputedGetter() {
-  obj_ = nullptr;
   return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER
                                           : JSOP_INITELEM_GETTER);
 }
 
 bool PropertyEmitter::emitInitComputedSetter() {
-  obj_ = nullptr;
   return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER
                                           : JSOP_INITELEM_SETTER);
 }
 
 bool PropertyEmitter::emitInit(JSOp op, JS::Handle<JSAtom*> key) {
   MOZ_ASSERT(propertyState_ == PropertyState::PropValue ||
              propertyState_ == PropertyState::InitHomeObj);
 
@@ -328,29 +315,16 @@ bool PropertyEmitter::emitInit(JSOp op, 
 
   //                [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 (!bce_->emitIndex32(op, index)) {
     //              [stack] CTOR? OBJ CTOR?
     return false;
   }
 
   if (!emitPopClassConstructor()) {
     return false;
   }
@@ -407,58 +381,44 @@ bool ObjectEmitter::emitObject(size_t pr
   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_->bytecodeSection().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.
+#ifdef DEBUG
+  objectState_ = ObjectState::Object;
+#endif
+  return true;
+}
 
-  // 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;
-  }
+bool ObjectEmitter::emitObjectWithTemplateOnStack() {
+  MOZ_ASSERT(propertyState_ == PropertyState::Start);
+  MOZ_ASSERT(objectState_ == ObjectState::Start);
 
 #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);
--- a/js/src/frontend/ObjectEmitter.h
+++ b/js/src/frontend/ObjectEmitter.h
@@ -11,22 +11,23 @@
 #include "mozilla/Maybe.h"  // Maybe
 
 #include <stddef.h>  // size_t
 #include <stdint.h>  // uint32_t
 
 #include "frontend/BytecodeOffset.h"  // BytecodeOffset
 #include "frontend/EmitterScope.h"    // EmitterScope
 #include "frontend/NameOpEmitter.h"   // NameOpEmitter
-#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/NativeObject.h"          // PlainObject
-#include "vm/Scope.h"                 // LexicalScope
+#include "frontend/ObjLiteral.h"     // ObjLiteralWriter, ObjLiteralCreationData
+#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/NativeObject.h"         // PlainObject
+#include "vm/Scope.h"                // LexicalScope
 
 namespace js {
 
 namespace frontend {
 
 struct BytecodeEmitter;
 class SharedContext;
 
@@ -50,23 +51,16 @@ class MOZ_STACK_CLASS PropertyEmitter {
   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 |-+
   // +-------+ |
   //           |
   // +---------+
@@ -378,20 +372,16 @@ class MOZ_STACK_CLASS PropertyEmitter {
 //     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.
-  BytecodeOffset top_;
-
 #ifdef DEBUG
   // The state of this emitter.
   //
   // +-------+ emitObject +--------+
   // | Start |----------->| Object |-+
   // +-------+            +--------+ |
   //                                 |
   //   +-----------------------------+
@@ -411,16 +401,19 @@ class MOZ_STACK_CLASS ObjectEmitter : pu
   };
   ObjectState objectState_ = ObjectState::Start;
 #endif
 
  public:
   explicit ObjectEmitter(BytecodeEmitter* bce);
 
   MOZ_MUST_USE bool emitObject(size_t propertyCount);
+  // Same as `emitObject()`, but start with an empty template object already on
+  // the stack.
+  MOZ_MUST_USE bool emitObjectWithTemplateOnStack();
   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_;
--- a/js/src/frontend/moz.build
+++ b/js/src/frontend/moz.build
@@ -40,16 +40,17 @@ UNIFIED_SOURCES += [
     'FunctionEmitter.cpp',
     'IfEmitter.cpp',
     'JumpList.cpp',
     'LabelEmitter.cpp',
     'LexicalScopeEmitter.cpp',
     'NameFunctions.cpp',
     'NameOpEmitter.cpp',
     'ObjectEmitter.cpp',
+    'ObjLiteral.cpp',
     'ParseContext.cpp',
     'ParseNode.cpp',
     'ParseNodeVerify.cpp',
     'PropOpEmitter.cpp',
     'SharedContext.cpp',
     'SwitchEmitter.cpp',
     'TDZCheckCache.cpp',
     'TokenStream.cpp',
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/bug1580246.js
@@ -0,0 +1,59 @@
+load(libdir + "./asserts.js");
+
+// Exercise object-literal creation. Many other tests also exercise details of
+// objects created by object literals -- e.g., byteSize-of-objects.js. Here we
+// simply want to hit the cases {run-once ctx, repeated ctx} x {constant,
+// parameterized} x {small, large}.
+
+function build_large_literal(var_name, num_keys, extra) {
+  let s = "var " + var_name + " = {";
+  for (let i = 0; i < num_keys; i++) {
+    s += "prop" + i + ": " + i + ",";
+  }
+  s += extra;
+  s += "};";
+  return s;
+}
+
+let small_singleton = {a: 1, b: 2, 0: "I'm an indexed property" };
+// Large enough to force dictionary mode -- should inhibit objliteral use in
+// frontend:
+eval(build_large_literal("large_singleton", 513, ""));
+
+let an_integer = 42;
+let small_singleton_param = { a: 1, b: 2, c: an_integer };
+eval(build_large_literal("large_singleton_param", 513, "prop_int: an_integer"));
+
+function f(a_parameterized_integer) {
+  let small_templated = {a: 1, b: 2, 0: "I'm an indexed property" };
+  // Large enough to force dictionary mode -- should inhibit objliteral use in
+  // frontend:
+  eval(build_large_literal("large_templated", 513, ""));
+
+  let small_templated_param = { a: 1, b: 2, c: a_parameterized_integer };
+  eval(build_large_literal("large_templated_param", 513, "prop_int: a_parameterized_integer"));
+
+  return {small_templated, large_templated,
+          small_templated_param, large_templated_param};
+}
+
+for (let i = 0; i < 10; i++) {
+  let {small_templated, large_templated,
+       small_templated_param, large_templated_param} = f(42);
+
+  assertDeepEq(small_templated, small_singleton);
+  assertDeepEq(large_templated, large_singleton);
+  assertDeepEq(small_templated_param, small_singleton_param);
+  assertDeepEq(large_templated_param, large_singleton_param);
+}
+
+let small_lit_array = [0, 1, 2, 3];
+let large_cow_lit_array = [0, 1, 2, 3, 4, 5, 6, 7];
+assertEq(4, small_lit_array.length);
+assertEq(8, large_cow_lit_array.length);
+for (let i = 0; i < small_lit_array.length; i++) {
+  assertEq(i, small_lit_array[i]);
+}
+for (let i = 0; i < large_cow_lit_array.length; i++) {
+  assertEq(i, large_cow_lit_array[i]);
+}