Bug 1580246: Remove object-literal singleton objects allocated at parse. r=djvj,mgaudet
authorChris Fallin <cfallin@mozilla.com>
Mon, 04 Nov 2019 21:31:27 +0000
changeset 500468 78d02a12be591b6260f878e70fc8ba73d857e893
parent 500467 2786362c4a78aafdf678370e1e6c5646f328fa81
child 500469 99fc19f68c763d7ff6a685dd19ea6f98256ea0c8
push id114164
push useraiakab@mozilla.com
push dateTue, 05 Nov 2019 10:06:15 +0000
treeherdermozilla-inbound@4d585c7edc76 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdjvj, mgaudet
bugs1580246
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=djvj,mgaudet 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. Differential Revision: https://phabricator.services.mozilla.com/D47985
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
js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -7775,16 +7775,101 @@ 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) {
   //                [stack] CTOR? OBJ
 
   size_t curFieldKeyIndex = 0;
   for (ParseNode* propdef : obj->contents()) {
     if (propdef->is<ClassField>()) {
       MOZ_ASSERT(type == ClassBody);
@@ -7820,18 +7905,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)) {
@@ -8083,34 +8168,188 @@ bool BytecodeEmitter::emitPropertyList(L
       default:
         MOZ_CRASH("Invalid op");
     }
   }
 
   return true;
 }
 
+bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj,
+                                                 PropListType type,
+                                                 bool isSingletonContext,
+                                                 bool noValuesMode) {
+#ifdef DEBUG
+  bool withValues = false, withoutValues = false;
+  isPropertyListObjLiteralCompatible(obj, type, &withValues, &withoutValues);
+  MOZ_ASSERT_IF(noValuesMode, withoutValues);
+  MOZ_ASSERT_IF(!noValuesMode, withValues);
+#endif
+
+  int32_t stackDepth = bytecodeSection().stackDepth();
+
+  ObjLiteralCreationData data(cx);
+  ObjLiteralFlags flags = (isSingletonContext ? OBJ_LITERAL_SINGLETON : 0) |
+                          (noValuesMode ? OBJ_LITERAL_TEMPLATE : 0);
+  data.writer().beginObject(flags);
+
+  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>());
+    ParseNode* value = noValuesMode ? nullptr : prop->right();
+
+    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 (!emitObjLiteralValue(&data, value)) {
+      return false;
+    }
+  }
+
+  uint32_t gcThingIndex = 0;
+  if (!perScriptData().gcThingList().append(std::move(data), &gcThingIndex)) {
+    return false;
+  }
+
+  bool success = noValuesMode ? emitIndex32(JSOP_NEWOBJECT, gcThingIndex)
+                              : emitIndex32(JSOP_OBJECT, gcThingIndex);
+  if (!success) {
+    return false;
+  }
+
+  bytecodeSection().setStackDepth(stackDepth + 1);
+  return true;
+}
+
+bool BytecodeEmitter::emitObjLiteralArray(ParseNode* arrayHead,
+                                          bool isSingleton) {
+  int32_t stackDepth = bytecodeSection().stackDepth();
+
+  ObjLiteralCreationData data(cx);
+  data.writer().beginObject(OBJ_LITERAL_ARRAY |
+                            (isSingleton ? OBJ_LITERAL_SINGLETON : 0));
+
+  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 = isSingleton ? JSOP_OBJECT : JSOP_NEWARRAY_COPYONWRITE;
+  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) {
+    // Template-only / no-values mode.
+    if (!data->writer().propWithUndefinedValue()) {
+      return false;
+    }
+  } else 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 = [
 //   () => {
@@ -8251,33 +8490,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
@@ -8296,32 +8535,99 @@ 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);
-  }
+  bool isSingletonContext = !objNode->hasNonConstInitializer() &&
+                            objNode->head() && checkSingletonContext();
+
+  // Note: this method uses the ObjLiteralWriter and emits
+  // ObjLiteralCreationData objects into the GC list will eventually evaluate
+  // these object-literal creation instructions.  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 okWithValues = false, okWithoutValues = false;
+  isPropertyListObjLiteralCompatible(objNode, ObjectLiteral, &okWithValues,
+                                     &okWithoutValues);
+  bool isObjLiteralWithValues = okWithValues && isSingletonContext;
+  bool isObjLiteralTemplate = !isObjLiteralWithValues && okWithoutValues;
 
   //                [stack]
-
+  //
   ObjectEmitter oe(this);
-  if (!oe.emitObject(objNode->count())) {
-    //              [stack] OBJ
-    return false;
-  }
-  if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
-    //              [stack] OBJ
-    return false;
-  }
+  if (isObjLiteralWithValues || isObjLiteralTemplate) {
+    // 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,
+            /* isSingletonContext = */ isObjLiteralWithValues,
+            /* noValuesMode = */ !isObjLiteralWithValues)) {
+      //              [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 (isObjLiteralTemplate) {
+      // 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)) {
+        //              [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;
 }
 
@@ -8347,52 +8653,26 @@ 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);
-    }
-
     // 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.
     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 &&
+        isArrayObjLiteralCompatible(array->head())) {
+      bool isSingleton = checkSingletonContext();
+      return emitObjLiteralArray(array->head(), isSingleton);
     }
   }
 
   return emitArray(array->head(), array->count());
 }
 
 bool BytecodeEmitter::emitArray(ParseNode* arrayHead, uint32_t count) {
   /*
@@ -8982,90 +9262,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());
 
--- 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
@@ -477,19 +478,39 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
       ListNode* classContentsIfConstructor = nullptr);
   MOZ_NEVER_INLINE MOZ_MUST_USE bool emitObject(ListNode* objNode);
 
   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);
 
+  MOZ_MUST_USE bool emitPropertyListObjLiteral(ListNode* obj, PropListType type,
+                                               bool isSingletonContext,
+                                               bool noValuesMode);
+
+  MOZ_MUST_USE bool emitObjLiteralArray(ParseNode* arrayHead, bool isSingleton);
+
+  // 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();
 
   // To catch accidental misuse, emitUint16Operand/emit3 assert that they are
--- a/js/src/frontend/BytecodeSection.cpp
+++ b/js/src/frontend/BytecodeSection.cpp
@@ -59,16 +59,24 @@ bool GCThingList::finish(JSContext* cx, 
     bool operator()(BigIntCreationData& data) {
       BigInt* bi = data.createBigInt(cx);
       if (!bi) {
         return false;
       }
       array[i] = JS::GCCellPtr(bi);
       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;
     }
   }
@@ -141,16 +149,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,17 +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>;
+  using ListType = mozilla::Variant<StackGCCellPtr, BigIntCreationData,
+                                    ObjLiteralCreationData>;
   JS::RootedVector<ListType> vector;
 
   // Last emitted object.
   ObjectBox* lastbox = nullptr;
 
   // Index of the first scope in the vector.
   mozilla::Maybe<uint32_t> firstScopeIndex;
 
@@ -68,16 +70,20 @@ struct MOZ_STACK_CLASS GCThingList {
   MOZ_MUST_USE bool append(BigIntLiteral* literal, uint32_t* index) {
     *index = vector.length();
     if (literal->isDeferred()) {
       return vector.append(mozilla::AsVariant(literal->creationData()));
     }
     return vector.append(
         mozilla::AsVariant(StackGCCellPtr(JS::GCCellPtr(literal->value()))));
   }
+  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,161 @@
+/* -*- 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 singleton = flags & OBJ_LITERAL_SINGLETON;
+
+  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()]);
+    }
+    propVal.setUndefined();
+    InterpretObjLiteralValue(atoms, insn, &propVal);
+    if (!properties.append(IdValuePair(propId, propVal))) {
+      return nullptr;
+    }
+  }
+
+  // Actually build the object:
+  // - In the singleton case, we want to collect all properties *first*, then
+  //   call ObjectGroup::newPlainObject at the end to build a group specific to
+  //   this singleton object.
+  // - In the non-singleton case (template case), we want to allocate an
+  //   ordinary tenured object, *not* necessarily with its own group, to prevent
+  //   memory regressions (too many groups compared to old behavior).
+  if (singleton) {
+    return ObjectGroup::newPlainObject(cx, properties.begin(),
+                                       properties.length(), SingletonObject);
+  }
+
+  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) {
+  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 =
+      (flags & OBJ_LITERAL_SINGLETON) ? ObjectGroup::NewArrayKind::Normal
+                                      : ObjectGroup::NewArrayKind::CopyOnWrite;
+  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 & OBJ_LITERAL_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/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 ObjLiteralOpcode : uint8_t {
+  INVALID = 0,
+
+  // --- not supported: we had this working in interpreter and baseline, but
+  // --- values from stack create nontrivial issues in Ion.
+  // Stack = 1,
+  // ---
+
+  ConstValue = 2,  // numeric types only.
+  ConstAtom = 3,
+  Null = 4,
+  Undefined = 5,
+  True = 6,
+  False = 7,
+
+  MAX = False,
+};
+
+static_assert(ObjLiteralOpcode::MAX <= UINT8_MAX,
+              "ObjLiteralOpcode must be a single byte");
+
+// Flags that are associated with a sequence of object-literal instructions.
+enum : uint8_t {
+  // If set, the created object will be created with a singleton object group.
+  OBJ_LITERAL_SINGLETON = 0x01,
+  // If set, this object is used as a template, and has no values. Mutually
+  // exclusive with SINGLETON above.
+  OBJ_LITERAL_TEMPLATE = 0x02,
+  // If set, this object is an array.
+  OBJ_LITERAL_ARRAY = 0x04,
+};
+
+using ObjLiteralFlags = uint8_t;
+
+inline bool ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op) {
+  return op == ObjLiteralOpcode::ConstValue;
+}
+
+inline bool ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op) {
+  return op == ObjLiteralOpcode::ConstAtom;
+}
+
+struct ObjLiteralReaderBase;
+
+// An unpacked representation of a property in the ObjLiteral instructions:
+// either a property name (as an index into the ObjLiteralWriter's atom table)
+// 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_(0) {}
+
+  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 > 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]);
+}
--- a/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
+++ b/js/src/jit-test/tests/debug/Script-getAllColumnOffsets.js
@@ -16,18 +16,19 @@ assertOffsetColumns(
 // getColumnOffsets correctly places comma separated expressions.
 assertOffsetColumns(
   "function f(n){print(n),print(n),print(n)}",
   "              ^    ^   ^        ^       ^"
 );
 
 // getColumnOffsets correctly places object properties.
 assertOffsetColumns(
-  // Should hit each property in the object.
-  "function f(n){var o={a:1,b:2,c:3}}",
+  // Should hit each property in the object if OBJLITERAL optimization is not
+  // hit.
+  "function f(n){var o={a:1,b:2,c:n}}",
   "                    ^^   ^   ^   ^"
 );
 
 // getColumnOffsets correctly places array properties.
 assertOffsetColumns(
   // Should hit each item in the array.
   "function f(n){var a=[1,2,n]}",
   "                    ^^ ^ ^ ^"