Bug 1137978 - Access an object's compartment and zone via its group, remove ObjectGroup::singleton_, r=jandem,terrence.
authorBrian Hackett <bhackett1024@gmail.com>
Tue, 03 Mar 2015 06:23:47 -0600
changeset 247022 73591b41e7777f562591310246277470b0b1ea65
parent 247021 bae236f4b8c129154a4b7e1315d064bdc76ad476
child 247023 d530c6ab9f119fd5ef6c281d66a8594e34b33e81
push id884
push userdburns@mozilla.com
push dateTue, 03 Mar 2015 15:29:12 +0000
reviewersjandem, terrence
bugs1137978
milestone39.0a1
Bug 1137978 - Access an object's compartment and zone via its group, remove ObjectGroup::singleton_, r=jandem,terrence.
js/src/gc/Marking.cpp
js/src/jsarray.cpp
js/src/jsfriendapi.h
js/src/jsgc.cpp
js/src/jsgc.h
js/src/jsobj.h
js/src/jspropertytree.cpp
js/src/jsstr.cpp
js/src/vm/ObjectGroup.cpp
js/src/vm/ObjectGroup.h
js/src/vm/Shape.h
js/src/vm/TypeInference-inl.h
js/src/vm/TypeInference.cpp
js/src/vm/TypeInference.h
js/src/vm/UnboxedObject.cpp
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1299,18 +1299,20 @@ ScanObjectGroup(GCMarker *gcmarker, Obje
     for (unsigned i = 0; i < count; i++) {
         if (ObjectGroup::Property *prop = group->getProperty(i))
             MarkId(gcmarker, &prop->id, "ObjectGroup property id");
     }
 
     if (group->proto().isObject())
         gcmarker->traverse(group->proto().toObject());
 
-    if (group->singleton() && !group->lazy())
-        gcmarker->traverse(group->singleton());
+    group->compartment()->mark();
+
+    if (GlobalObject *global = group->compartment()->unsafeUnbarrieredMaybeGlobal())
+        PushMarkStack(gcmarker, global);
 
     if (group->newScript())
         group->newScript()->trace(gcmarker);
 
     if (group->maybePreliminaryObjects())
         group->maybePreliminaryObjects()->trace(gcmarker);
 
     if (group->maybeUnboxedLayout())
@@ -1333,19 +1335,16 @@ gc::MarkChildren(JSTracer *trc, ObjectGr
     for (unsigned i = 0; i < count; i++) {
         if (ObjectGroup::Property *prop = group->getProperty(i))
             MarkId(trc, &prop->id, "group_property");
     }
 
     if (group->proto().isObject())
         MarkObject(trc, &group->protoRaw(), "group_proto");
 
-    if (group->singleton() && !group->lazy())
-        MarkObject(trc, &group->singletonRaw(), "group_singleton");
-
     if (group->newScript())
         group->newScript()->trace(trc);
 
     if (group->maybePreliminaryObjects())
         group->maybePreliminaryObjects()->trace(trc);
 
     if (group->maybeUnboxedLayout())
         group->unboxedLayout().trace(trc);
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -1207,22 +1207,23 @@ js::array_join(JSContext *cx, unsigned a
 {
     JS_CHECK_RECURSION(cx, return false);
 
     CallArgs args = CallArgsFromVp(argc, vp);
     return ArrayJoin<false>(cx, args);
 }
 
 static inline bool
-InitArrayTypes(JSContext *cx, ObjectGroup *group, const Value *vector, unsigned count)
+InitArrayTypes(JSContext *cx, ObjectGroup *group, JSObject *obj,
+               const Value *vector, unsigned count)
 {
     if (!group->unknownProperties()) {
         AutoEnterAnalysis enter(cx);
 
-        HeapTypeSet *types = group->getProperty(cx, JSID_VOID);
+        HeapTypeSet *types = group->getProperty(cx, obj, JSID_VOID);
         if (!types)
             return false;
 
         for (unsigned i = 0; i < count; i++) {
             if (vector[i].isMagic(JS_ELEMENTS_HOLE))
                 continue;
             types->addType(cx, TypeSet::GetValueType(vector[i]));
         }
@@ -1243,17 +1244,17 @@ InitArrayElements(JSContext *cx, HandleO
     MOZ_ASSERT(count <= MAX_ARRAY_INDEX);
 
     if (count == 0)
         return true;
 
     ObjectGroup *group = obj->getGroup(cx);
     if (!group)
         return false;
-    if (updateTypes && !InitArrayTypes(cx, group, vector, count))
+    if (updateTypes && !InitArrayTypes(cx, group, obj, vector, count))
         return false;
 
     /*
      * Optimize for dense arrays so long as adding the given set of elements
      * wouldn't otherwise make the array slow or exceed a non-writable array
      * length.
      */
     do {
@@ -3055,17 +3056,17 @@ IsArrayConstructor(const Value &v)
            v.toObject().is<JSFunction>() &&
            v.toObject().as<JSFunction>().isNative() &&
            v.toObject().as<JSFunction>().native() == ArrayConstructor;
 }
 
 static bool
 ArrayFromCallArgs(JSContext *cx, HandleObjectGroup group, CallArgs &args)
 {
-    if (!InitArrayTypes(cx, group, args.array(), args.length()))
+    if (!InitArrayTypes(cx, group, nullptr, args.array(), args.length()))
         return false;
     JSObject *obj = (args.length() == 0)
         ? NewDenseEmptyArray(cx)
         : NewDenseCopiedArray(cx, args.length(), args.array());
     if (!obj)
         return false;
     obj->setGroup(group);
     args.rval().setObject(*obj);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -541,39 +541,38 @@ GetAnyCompartmentInZone(JS::Zone *zone);
  * new fields for access by inline methods, make sure to add static asserts to
  * the original header file to ensure that offsets are consistent.
  */
 namespace shadow {
 
 struct ObjectGroup {
     const Class *clasp;
     JSObject    *proto;
+    JSCompartment *compartment;
 };
 
 struct BaseShape {
     const js::Class *clasp_;
     JSObject *parent;
-    JSObject *_1;
-    JSCompartment *compartment;
 };
 
 class Shape {
 public:
     shadow::BaseShape *base;
     jsid              _1;
     uint32_t          slotInfo;
 
     static const uint32_t FIXED_SLOTS_SHIFT = 27;
 };
 
 // This layout is shared by all objects except for Typed Objects (which still
 // have a shape and group).
 struct Object {
+    shadow::ObjectGroup *group;
     shadow::Shape       *shape;
-    shadow::ObjectGroup *group;
     JS::Value           *slots;
     void                *_1;
 
     size_t numFixedSlots() const { return shape->slotInfo >> Shape::FIXED_SLOTS_SHIFT; }
     JS::Value *fixedSlots() const {
         return (JS::Value *)(uintptr_t(this) + sizeof(shadow::Object));
     }
 
@@ -689,17 +688,17 @@ GetObjectParent(JSObject *obj)
 {
     MOZ_ASSERT(!IsScopeObject(obj));
     return reinterpret_cast<shadow::Object*>(obj)->shape->base->parent;
 }
 
 static MOZ_ALWAYS_INLINE JSCompartment *
 GetObjectCompartment(JSObject *obj)
 {
-    return reinterpret_cast<shadow::Object*>(obj)->shape->base->compartment;
+    return reinterpret_cast<shadow::Object*>(obj)->group->compartment;
 }
 
 JS_FRIEND_API(JSObject *)
 GetObjectParentMaybeScope(JSObject *obj);
 
 JS_FRIEND_API(JSObject *)
 GetGlobalForObjectCrossCompartment(JSObject *obj);
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -6602,16 +6602,17 @@ gc::MergeCompartments(JSCompartment *sou
         BaseShape *base = iter.get<BaseShape>();
         MOZ_ASSERT(base->compartment() == source);
         base->compartment_ = target;
     }
 
     for (ZoneCellIter iter(source->zone(), FINALIZE_OBJECT_GROUP); !iter.done(); iter.next()) {
         ObjectGroup *group = iter.get<ObjectGroup>();
         group->setGeneration(target->zone()->types.generation);
+        group->compartment_ = target;
     }
 
     // Fixup zone pointers in source's zone to refer to target's zone.
 
     for (size_t thingKind = 0; thingKind != FINALIZE_LIMIT; thingKind++) {
         for (ArenaIter aiter(source->zone(), AllocKind(thingKind)); !aiter.done(); aiter.next()) {
             ArenaHeader *aheader = aiter.get();
             aheader->zone = target->zone();
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -1158,18 +1158,18 @@ class RelocationOverlay
 
     Cell *forwardingAddress() const {
         MOZ_ASSERT(isForwarded());
         return newLocation_;
     }
 
     void forwardTo(Cell *cell) {
         MOZ_ASSERT(!isForwarded());
-        static_assert(offsetof(JSObject, shape_) == offsetof(RelocationOverlay, newLocation_),
-                      "forwarding pointer and shape should be at same location, "
+        static_assert(offsetof(JSObject, group_) == offsetof(RelocationOverlay, newLocation_),
+                      "forwarding pointer and group should be at same location, "
                       "so that obj->zone() works on forwarded objects");
         newLocation_ = cell;
         magic_ = Relocated;
         next_ = nullptr;
     }
 
     RelocationOverlay *next() const {
         return next_;
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -99,18 +99,18 @@ bool SetImmutablePrototype(js::Exclusive
  *   prototype object and the possible types of its properties.
  *
  * Subclasses of JSObject --- mainly NativeObject and JSFunction --- add more
  * members.
  */
 class JSObject : public js::gc::Cell
 {
   protected:
+    js::HeapPtrObjectGroup group_;
     js::HeapPtrShape shape_;
-    js::HeapPtrObjectGroup group_;
 
   private:
     friend class js::Shape;
     friend class js::GCMarker;
     friend class js::NewObjectCache;
     friend class js::Nursery;
     friend class js::gc::RelocationOverlay;
     friend bool js::PreventExtensions(JSContext *cx, JS::HandleObject obj, bool *succeeded);
@@ -152,29 +152,29 @@ class JSObject : public js::gc::Cell
         return group_;
     }
 
     /*
      * Whether this is the only object which has its specified gbroup. This
      * object will have its group constructed lazily as needed by analysis.
      */
     bool isSingleton() const {
-        return !!group_->singleton();
+        return group_->singleton();
     }
 
     /*
      * Whether the object's group has not been constructed yet. If an object
      * might have a lazy group, use getGroup() below, otherwise group().
      */
     bool hasLazyGroup() const {
         return group_->lazy();
     }
 
     JSCompartment *compartment() const {
-        return lastProperty()->base()->compartment();
+        return group_->compartment();
     }
 
     /*
      * Make a non-array object with the specified initial state. This method
      * takes ownership of any extantSlots it is passed.
      */
     static inline JSObject *create(js::ExclusiveContext *cx,
                                    js::gc::AllocKind kind,
@@ -284,17 +284,17 @@ class JSObject : public js::gc::Cell
 
     void fixupAfterMovingGC();
 
     static js::ThingRootKind rootKind() { return js::THING_ROOT_OBJECT; }
     static const size_t MaxTagBits = 3;
     static bool isNullLike(const JSObject *obj) { return uintptr_t(obj) < (1 << MaxTagBits); }
 
     MOZ_ALWAYS_INLINE JS::Zone *zone() const {
-        return shape_->zone();
+        return group_->zone();
     }
     MOZ_ALWAYS_INLINE JS::shadow::Zone *shadowZone() const {
         return JS::shadow::Zone::asShadowZone(zone());
     }
     MOZ_ALWAYS_INLINE JS::Zone *zoneFromAnyThread() const {
         return shape_->zoneFromAnyThread();
     }
     MOZ_ALWAYS_INLINE JS::shadow::Zone *shadowZoneFromAnyThread() const {
--- a/js/src/jspropertytree.cpp
+++ b/js/src/jspropertytree.cpp
@@ -223,26 +223,30 @@ Shape::finalize(FreeOp *fop)
 }
 
 void
 Shape::fixupDictionaryShapeAfterMovingGC()
 {
     if (!listp)
         return;
 
+    // Get a fake cell pointer to use for the calls below. This might not point
+    // to the beginning of a cell, but will point into the right arena and will
+    // have the right alignment.
+    Cell *cell = reinterpret_cast<Cell *>(uintptr_t(listp) & ~CellMask);
+
     // It's possible that this shape is unreachable and that listp points to the
     // location of a dead object in the nursery, in which case we should never
     // touch it again.
-    if (IsInsideNursery(reinterpret_cast<Cell *>(listp))) {
+    if (IsInsideNursery(cell)) {
         listp = nullptr;
         return;
     }
 
-    MOZ_ASSERT(!IsInsideNursery(reinterpret_cast<Cell *>(listp)));
-    AllocKind kind = TenuredCell::fromPointer(listp)->getAllocKind();
+    AllocKind kind = TenuredCell::fromPointer(cell)->getAllocKind();
     MOZ_ASSERT(kind == FINALIZE_SHAPE ||
                kind == FINALIZE_ACCESSOR_SHAPE ||
                kind <= FINALIZE_OBJECT_LAST);
     if (kind == FINALIZE_SHAPE || kind == FINALIZE_ACCESSOR_SHAPE) {
         // listp points to the parent field of the next shape.
         Shape *next = reinterpret_cast<Shape *>(uintptr_t(listp) -
                                                 offsetof(Shape, parent));
         listp = &gc::MaybeForwarded(next)->parent;
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -3650,17 +3650,17 @@ SplitHelper(JSContext *cx, HandleLinearS
                 if (!matches[i + 1].isUndefined()) {
                     JSSubString parsub;
                     res->getParen(i + 1, &parsub);
                     sub = NewDependentString(cx, parsub.base, parsub.offset, parsub.length);
                     if (!sub || !splits.append(StringValue(sub)))
                         return nullptr;
                 } else {
                     /* Only string entries have been accounted for so far. */
-                    AddTypePropertyId(cx, group, JSID_VOID, UndefinedValue());
+                    AddTypePropertyId(cx, group, nullptr, JSID_VOID, UndefinedValue());
                     if (!splits.append(UndefinedValue()))
                         return nullptr;
                 }
 
                 /* Step 13(c)(iii)(7)(d). */
                 if (splits.length() == limit)
                     return NewDenseCopiedArray(cx, splits.length(), splits.begin());
             }
@@ -3781,17 +3781,17 @@ js::str_split(JSContext *cx, unsigned ar
     /* Steps 1-2. */
     RootedString str(cx, ThisToStringForStringProto(cx, args));
     if (!str)
         return false;
 
     RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array));
     if (!group)
         return false;
-    AddTypePropertyId(cx, group, JSID_VOID, TypeSet::StringType());
+    AddTypePropertyId(cx, group, nullptr, JSID_VOID, TypeSet::StringType());
 
     /* Step 5: Use the second argument as the split limit, if given. */
     uint32_t limit;
     if (args.hasDefined(1)) {
         double d;
         if (!ToNumber(cx, args[1], &d))
             return false;
         limit = ToUint32(d);
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -20,25 +20,27 @@
 using namespace js;
 
 using mozilla::PodZero;
 
 /////////////////////////////////////////////////////////////////////
 // ObjectGroup
 /////////////////////////////////////////////////////////////////////
 
-ObjectGroup::ObjectGroup(const Class *clasp, TaggedProto proto, ObjectGroupFlags initialFlags)
+ObjectGroup::ObjectGroup(const Class *clasp, TaggedProto proto, JSCompartment *comp,
+                         ObjectGroupFlags initialFlags)
 {
     PodZero(this);
 
     /* Inner objects may not appear on prototype chains. */
     MOZ_ASSERT_IF(proto.isObject(), !proto.toObject()->getClass()->ext.outerObject);
 
     this->clasp_ = clasp;
     this->proto_ = proto.raw();
+    this->compartment_ = comp;
     this->flags_ = initialFlags;
 
     setGeneration(zone()->types.generation);
 }
 
 void
 ObjectGroup::finalize(FreeOp *fop)
 {
@@ -303,17 +305,17 @@ JSObject::makeLazyGroup(JSContext *cx, H
     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpretedLazy()) {
         RootedFunction fun(cx, &obj->as<JSFunction>());
         if (!fun->getOrCreateScript(cx))
             return nullptr;
     }
 
     // Find flags which need to be specified immediately on the object.
     // Don't track whether singletons are packed.
-    ObjectGroupFlags initialFlags = OBJECT_FLAG_NON_PACKED;
+    ObjectGroupFlags initialFlags = OBJECT_FLAG_SINGLETON | OBJECT_FLAG_NON_PACKED;
 
     if (obj->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON))
         initialFlags |= OBJECT_FLAG_ITERATED;
 
     if (obj->isIndexed())
         initialFlags |= OBJECT_FLAG_SPARSE_INDEXES;
 
     if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() > INT32_MAX)
@@ -324,18 +326,16 @@ JSObject::makeLazyGroup(JSContext *cx, H
                                                            initialFlags);
     if (!group)
         return nullptr;
 
     AutoEnterAnalysis enter(cx);
 
     /* Fill in the type according to the state of this object. */
 
-    group->initSingleton(obj);
-
     if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted())
         group->setInterpretedFunction(&obj->as<JSFunction>());
 
     obj->group_ = group;
 
     return group;
 }
 
@@ -548,32 +548,32 @@ ObjectGroup::defaultNewGroup(ExclusiveCo
          * creation via the Shape::{insert,get}initialShape mechanism. Since
          * these properties are never explicitly defined on new objects, update
          * the type information for them here.
          */
 
         const JSAtomState &names = cx->names();
 
         if (obj->is<RegExpObject>()) {
-            AddTypePropertyId(cx, group, NameToId(names.source), TypeSet::StringType());
-            AddTypePropertyId(cx, group, NameToId(names.global), TypeSet::BooleanType());
-            AddTypePropertyId(cx, group, NameToId(names.ignoreCase), TypeSet::BooleanType());
-            AddTypePropertyId(cx, group, NameToId(names.multiline), TypeSet::BooleanType());
-            AddTypePropertyId(cx, group, NameToId(names.sticky), TypeSet::BooleanType());
-            AddTypePropertyId(cx, group, NameToId(names.lastIndex), TypeSet::Int32Type());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.source), TypeSet::StringType());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.global), TypeSet::BooleanType());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.ignoreCase), TypeSet::BooleanType());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.multiline), TypeSet::BooleanType());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.sticky), TypeSet::BooleanType());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type());
         }
 
         if (obj->is<StringObject>())
-            AddTypePropertyId(cx, group, NameToId(names.length), TypeSet::Int32Type());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type());
 
         if (obj->is<ErrorObject>()) {
-            AddTypePropertyId(cx, group, NameToId(names.fileName), TypeSet::StringType());
-            AddTypePropertyId(cx, group, NameToId(names.lineNumber), TypeSet::Int32Type());
-            AddTypePropertyId(cx, group, NameToId(names.columnNumber), TypeSet::Int32Type());
-            AddTypePropertyId(cx, group, NameToId(names.stack), TypeSet::StringType());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.fileName), TypeSet::StringType());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.lineNumber), TypeSet::Int32Type());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.columnNumber), TypeSet::Int32Type());
+            AddTypePropertyId(cx, group, nullptr, NameToId(names.stack), TypeSet::StringType());
         }
     }
 
     return group;
 }
 
 /* static */ ObjectGroup *
 ObjectGroup::lazySingletonGroup(ExclusiveContext *cx, const Class *clasp, TaggedProto proto)
@@ -598,28 +598,27 @@ ObjectGroup::lazySingletonGroup(Exclusiv
         MOZ_ASSERT(group->lazy());
 
         return group;
     }
 
     AutoEnterAnalysis enter(cx);
 
     Rooted<TaggedProto> protoRoot(cx, proto);
-    ObjectGroup *group = ObjectGroupCompartment::makeGroup(cx, clasp, protoRoot);
+    ObjectGroup *group =
+        ObjectGroupCompartment::makeGroup(cx, clasp, protoRoot,
+                                          OBJECT_FLAG_SINGLETON | OBJECT_FLAG_LAZY_SINGLETON);
     if (!group)
         return nullptr;
 
     if (!table->add(p, ObjectGroupCompartment::NewEntry(group, nullptr)))
         return nullptr;
 
     ObjectGroupCompartment::newTablePostBarrier(cx, table, clasp, proto, nullptr);
 
-    group->initSingleton((JSObject *) ObjectGroup::LAZY_SINGLETON);
-    MOZ_ASSERT(group->singleton(), "created group must be a proper singleton");
-
     return group;
 }
 
 /* static */ void
 ObjectGroup::setDefaultNewGroupUnknown(JSContext *cx, const Class *clasp, HandleObject obj)
 {
     // If the object already has a new group, mark that group as unknown.
     ObjectGroupCompartment::NewTable *table = cx->compartment()->objectGroups.defaultNewTable;
@@ -834,17 +833,17 @@ ObjectGroup::setGroupToHomogenousArray(E
         // Make a new group to use for future arrays with the same elements.
         RootedObject objProto(cx, obj->getProto());
         Rooted<TaggedProto> taggedProto(cx, TaggedProto(objProto));
         ObjectGroup *group = ObjectGroupCompartment::makeGroup(cx, &ArrayObject::class_, taggedProto);
         if (!group)
             return;
         obj->setGroup(group);
 
-        AddTypePropertyId(cx, group, JSID_VOID, elementType);
+        AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType);
 
         key.proto = objProto;
         (void) p.add(cx, *table, key, group);
     }
 }
 
 /////////////////////////////////////////////////////////////////////
 // ObjectGroupCompartment PlainObjectTable
@@ -913,17 +912,17 @@ ObjectGroupCompartment::updatePlainObjec
             /* The property types already reflect 'int32'. */
         } else {
             if (ntype.isPrimitive(JSVAL_TYPE_DOUBLE) &&
                 type.isPrimitive(JSVAL_TYPE_INT32))
             {
                 /* Include 'double' in the property types to avoid the update below later. */
                 entry.types[i] = TypeSet::DoubleType();
             }
-            AddTypePropertyId(cx, entry.group, IdToTypeId(properties[i].id), ntype);
+            AddTypePropertyId(cx, entry.group, nullptr, IdToTypeId(properties[i].id), ntype);
         }
     }
 }
 
 /* static */ void
 ObjectGroup::fixPlainObjectGroup(ExclusiveContext *cx, PlainObject *obj)
 {
     AutoEnterAnalysis enter(cx);
@@ -994,17 +993,17 @@ ObjectGroup::fixPlainObjectGroup(Exclusi
     ScopedJSFreePtr<TypeSet::Type> types(
         group->zone()->pod_calloc<TypeSet::Type>(properties.length()));
     if (!types)
         return;
 
     for (size_t i = 0; i < properties.length(); i++) {
         ids[i] = properties[i].id;
         types[i] = GetValueTypeForTable(obj->getSlot(i));
-        AddTypePropertyId(cx, group, IdToTypeId(ids[i]), types[i]);
+        AddTypePropertyId(cx, group, nullptr, IdToTypeId(ids[i]), types[i]);
     }
 
     ObjectGroupCompartment::PlainObjectKey key;
     key.properties = ids;
     key.nproperties = properties.length();
     key.nfixed = obj->numFixedSlots();
     MOZ_ASSERT(ObjectGroupCompartment::PlainObjectKey::match(key, lookup));
 
@@ -1223,17 +1222,17 @@ ObjectGroup::getOrFixupCopyOnWriteObject
         return nullptr;
 
     group->addFlags(OBJECT_FLAG_COPY_ON_WRITE);
 
     // Update type information in the initializer object group.
     MOZ_ASSERT(obj->slotSpan() == 0);
     for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) {
         const Value &v = obj->getDenseElement(i);
-        AddTypePropertyId(cx, group, JSID_VOID, v);
+        AddTypePropertyId(cx, group, nullptr, JSID_VOID, v);
     }
 
     obj->setGroup(group);
     return obj;
 }
 
 /* static */ ArrayObject *
 ObjectGroup::getCopyOnWriteObject(JSScript *script, jsbytecode *pc)
@@ -1322,17 +1321,17 @@ ObjectGroupCompartment::makeGroup(Exclus
                                   Handle<TaggedProto> proto,
                                   ObjectGroupFlags initialFlags /* = 0 */)
 {
     MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
 
     ObjectGroup *group = NewObjectGroup(cx);
     if (!group)
         return nullptr;
-    new(group) ObjectGroup(clasp, proto, initialFlags);
+    new(group) ObjectGroup(clasp, proto, cx->compartment(), initialFlags);
 
     return group;
 }
 
 void
 ObjectGroupCompartment::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
                                                size_t *allocationSiteTables,
                                                size_t *arrayObjectGroupTables,
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -122,16 +122,20 @@ inline Handle<TaggedProto>
 AsTaggedProto(HandleObject obj)
 {
     static_assert(sizeof(JSObject*) == sizeof(TaggedProto),
                   "TaggedProto must be binary compatible with JSObject");
     return Handle<TaggedProto>::fromMarkedLocation(
             reinterpret_cast<TaggedProto const*>(obj.address()));
 }
 
+namespace gc {
+void MergeCompartments(JSCompartment *source, JSCompartment *target);
+}
+
 /*
  * Lazy object groups overview.
  *
  * Object groups which represent at most one JS object are constructed lazily.
  * These include groups for native functions, standard classes, scripted
  * functions defined at the top level of global/eval scripts, and in some
  * other cases. Typical web workloads often create many windows (and many
  * copies of standard natives) and many scripts, with comparatively few
@@ -145,64 +149,60 @@ AsTaggedProto(HandleObject obj)
  * information is sensitive to changes in the property's type. Future changes
  * to the property (whether those uncovered by analysis or those occurring
  * in the VM) will treat these properties like those of any other object group.
  */
 
 /* Type information about an object accessed by a script. */
 class ObjectGroup : public gc::TenuredCell
 {
+    friend void gc::MergeCompartments(JSCompartment *source, JSCompartment *target);
+
     /* Class shared by objects in this group. */
     const Class *clasp_;
 
     /* Prototype shared by objects in this group. */
     HeapPtrObject proto_;
 
-    /*
-     * Whether there is a singleton JS object with this group. That JS object
-     * must appear in type sets instead of this; we include the back reference
-     * here to allow reverting the JS object to a lazy group.
-     */
-    HeapPtrObject singleton_;
+    /* Compartment shared by objects in this group. */
+    JSCompartment *compartment_;
 
   public:
 
     const Class *clasp() const {
         return clasp_;
     }
 
     void setClasp(const Class *clasp) {
         clasp_ = clasp;
     }
 
     TaggedProto proto() const {
         return TaggedProto(proto_);
     }
 
-    JSObject *singleton() const {
-        return singleton_;
-    }
-
     // For use during marking, don't call otherwise.
     HeapPtrObject &protoRaw() { return proto_; }
-    HeapPtrObject &singletonRaw() { return singleton_; }
 
     void setProto(TaggedProto proto);
     void setProtoUnchecked(TaggedProto proto);
 
-    void initSingleton(JSObject *singleton) {
-        singleton_ = singleton;
+    bool singleton() const {
+        return flagsDontCheckGeneration() & OBJECT_FLAG_SINGLETON;
     }
 
-    /*
-     * Value held by singleton if this is a standin group for a singleton JS
-     * object whose group has not been constructed yet.
-     */
-    static const size_t LAZY_SINGLETON = 1;
-    bool lazy() const { return singleton() == (JSObject *) LAZY_SINGLETON; }
+    bool lazy() const {
+        bool res = flagsDontCheckGeneration() & OBJECT_FLAG_LAZY_SINGLETON;
+        MOZ_ASSERT_IF(res, singleton());
+        return res;
+    }
+
+    JSCompartment *compartment() const {
+        return compartment_;
+    }
 
   private:
     /* Flags for this group. */
     ObjectGroupFlags flags_;
 
     // Kinds of addendums which can be attached to ObjectGroups.
     enum AddendumKind {
         Addendum_None,
@@ -253,17 +253,17 @@ class ObjectGroup : public gc::TenuredCe
         if (addendumKind() == Addendum_UnboxedLayout)
             return reinterpret_cast<UnboxedLayout *>(addendum_);
         return nullptr;
     }
 
     TypeNewScript *anyNewScript();
     void detachNewScript(bool writeBarrier, ObjectGroup *replacement);
 
-    ObjectGroupFlags flagsDontCheckGeneration() {
+    ObjectGroupFlags flagsDontCheckGeneration() const {
         return flags_;
     }
 
   public:
 
     ObjectGroupFlags flags() {
         maybeSweep(nullptr);
         return flagsDontCheckGeneration();
@@ -428,17 +428,18 @@ class ObjectGroup : public gc::TenuredCe
      *
      * We establish these by using write barriers on calls to setProperty and
      * defineProperty which are on native properties, and on any jitcode which
      * might update the property with a new type.
      */
     Property **propertySet;
   public:
 
-    inline ObjectGroup(const Class *clasp, TaggedProto proto, ObjectGroupFlags initialFlags);
+    inline ObjectGroup(const Class *clasp, TaggedProto proto, JSCompartment *comp,
+                       ObjectGroupFlags initialFlags);
 
     inline bool hasAnyFlags(ObjectGroupFlags flags) {
         MOZ_ASSERT((flags & OBJECT_FLAG_DYNAMIC_MASK) == flags);
         return !!(this->flags() & flags);
     }
 
     bool hasAllFlags(ObjectGroupFlags flags) {
         MOZ_ASSERT((flags & OBJECT_FLAG_DYNAMIC_MASK) == flags);
@@ -480,31 +481,31 @@ class ObjectGroup : public gc::TenuredCe
         MOZ_ASSERT(canPreTenure());
         setFlags(cx, OBJECT_FLAG_PRE_TENURE);
     }
 
     /*
      * Get or create a property of this object. Only call this for properties which
      * a script accesses explicitly.
      */
-    inline HeapTypeSet *getProperty(ExclusiveContext *cx, jsid id);
+    inline HeapTypeSet *getProperty(ExclusiveContext *cx, JSObject *obj, jsid id);
 
     /* Get a property only if it already exists. */
     inline HeapTypeSet *maybeGetProperty(jsid id);
 
     inline unsigned getPropertyCount();
     inline Property *getProperty(unsigned i);
 
     /* Helpers */
 
-    void updateNewPropertyTypes(ExclusiveContext *cx, jsid id, HeapTypeSet *types);
+    void updateNewPropertyTypes(ExclusiveContext *cx, JSObject *obj, jsid id, HeapTypeSet *types);
     bool addDefiniteProperties(ExclusiveContext *cx, Shape *shape);
     bool matchDefiniteProperties(HandleObject obj);
-    void markPropertyNonData(ExclusiveContext *cx, jsid id);
-    void markPropertyNonWritable(ExclusiveContext *cx, jsid id);
+    void markPropertyNonData(ExclusiveContext *cx, JSObject *obj, jsid id);
+    void markPropertyNonWritable(ExclusiveContext *cx, JSObject *obj, jsid id);
     void markStateChange(ExclusiveContext *cx);
     void setFlags(ExclusiveContext *cx, ObjectGroupFlags flags);
     void markUnknown(ExclusiveContext *cx);
     void maybeClearNewScriptOnOOM();
     void clearNewScript(ExclusiveContext *cx, ObjectGroup *replacement = nullptr);
     bool isPropertyNonData(jsid id);
     bool isPropertyNonWritable(jsid id);
 
--- a/js/src/vm/Shape.h
+++ b/js/src/vm/Shape.h
@@ -315,20 +315,16 @@ class ShapeTable {
  * an earlier property, however.
  */
 
 class AccessorShape;
 class Shape;
 class UnownedBaseShape;
 struct StackBaseShape;
 
-namespace gc {
-void MergeCompartments(JSCompartment *source, JSCompartment *target);
-}
-
 // This class is used to add a post barrier on the AccessorShape's getter/setter
 // objects. It updates the shape's entry in the parent's KidsHash table.
 class ShapeGetterSetterRef : public gc::BufferableRef
 {
     AccessorShape *shape;
     JSObject **objp;
 
   public:
--- a/js/src/vm/TypeInference-inl.h
+++ b/js/src/vm/TypeInference-inl.h
@@ -359,17 +359,17 @@ EnsureTrackPropertyTypes(JSContext *cx, 
     id = IdToTypeId(id);
 
     if (obj->isSingleton()) {
         AutoEnterAnalysis enter(cx);
         if (obj->hasLazyGroup() && !obj->getGroup(cx)) {
             CrashAtUnhandlableOOM("Could not allocate ObjectGroup in EnsureTrackPropertyTypes");
             return;
         }
-        if (!obj->group()->unknownProperties() && !obj->group()->getProperty(cx, id)) {
+        if (!obj->group()->unknownProperties() && !obj->group()->getProperty(cx, obj, id)) {
             MOZ_ASSERT(obj->group()->unknownProperties());
             return;
         }
     }
 
     MOZ_ASSERT(obj->group()->unknownProperties() || TrackPropertyTypes(cx, obj, id));
 }
 
@@ -412,34 +412,34 @@ HasTypePropertyId(JSObject *obj, jsid id
 }
 
 inline bool
 HasTypePropertyId(JSObject *obj, jsid id, const Value &value)
 {
     return HasTypePropertyId(obj, id, TypeSet::GetValueType(value));
 }
 
-void AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, jsid id, TypeSet::Type type);
-void AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, jsid id, const Value &value);
+void AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, JSObject *obj, jsid id, TypeSet::Type type);
+void AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, JSObject *obj, jsid id, const Value &value);
 
 /* Add a possible type for a property of obj. */
 inline void
 AddTypePropertyId(ExclusiveContext *cx, JSObject *obj, jsid id, TypeSet::Type type)
 {
     id = IdToTypeId(id);
     if (TrackPropertyTypes(cx, obj, id))
-        AddTypePropertyId(cx, obj->group(), id, type);
+        AddTypePropertyId(cx, obj->group(), obj, id, type);
 }
 
 inline void
 AddTypePropertyId(ExclusiveContext *cx, JSObject *obj, jsid id, const Value &value)
 {
     id = IdToTypeId(id);
     if (TrackPropertyTypes(cx, obj, id))
-        AddTypePropertyId(cx, obj->group(), id, value);
+        AddTypePropertyId(cx, obj->group(), obj, id, value);
 }
 
 inline void
 MarkObjectGroupFlags(ExclusiveContext *cx, JSObject *obj, ObjectGroupFlags flags)
 {
     if (!obj->hasLazyGroup() && !obj->group()->hasAllFlags(flags))
         obj->group()->setFlags(cx, flags);
 }
@@ -451,25 +451,25 @@ MarkObjectGroupUnknownProperties(JSConte
         obj->markUnknown(cx);
 }
 
 inline void
 MarkTypePropertyNonData(ExclusiveContext *cx, JSObject *obj, jsid id)
 {
     id = IdToTypeId(id);
     if (TrackPropertyTypes(cx, obj, id))
-        obj->group()->markPropertyNonData(cx, id);
+        obj->group()->markPropertyNonData(cx, obj, id);
 }
 
 inline void
 MarkTypePropertyNonWritable(ExclusiveContext *cx, JSObject *obj, jsid id)
 {
     id = IdToTypeId(id);
     if (TrackPropertyTypes(cx, obj, id))
-        obj->group()->markPropertyNonWritable(cx, id);
+        obj->group()->markPropertyNonWritable(cx, obj, id);
 }
 
 inline bool
 IsTypePropertyIdMarkedNonData(JSObject *obj, jsid id)
 {
     return obj->group()->isPropertyNonData(id);
 }
 
@@ -1014,21 +1014,23 @@ ObjectGroup::setBasePropertyCount(uint32
 {
     // Note: Callers must ensure they are performing threadsafe operations.
     MOZ_ASSERT(count <= OBJECT_FLAG_PROPERTY_COUNT_LIMIT);
     flags_ = (flags() & ~OBJECT_FLAG_PROPERTY_COUNT_MASK)
            | (count << OBJECT_FLAG_PROPERTY_COUNT_SHIFT);
 }
 
 inline HeapTypeSet *
-ObjectGroup::getProperty(ExclusiveContext *cx, jsid id)
+ObjectGroup::getProperty(ExclusiveContext *cx, JSObject *obj, jsid id)
 {
     MOZ_ASSERT(JSID_IS_VOID(id) || JSID_IS_EMPTY(id) || JSID_IS_STRING(id) || JSID_IS_SYMBOL(id));
     MOZ_ASSERT_IF(!JSID_IS_EMPTY(id), id == IdToTypeId(id));
     MOZ_ASSERT(!unknownProperties());
+    MOZ_ASSERT_IF(obj, obj->group() == this);
+    MOZ_ASSERT_IF(singleton(), obj);
 
     if (HeapTypeSet *types = maybeGetProperty(id))
         return types;
 
     Property *base = cx->typeLifoAlloc().new_<Property>(id);
     if (!base) {
         markUnknown(cx);
         return nullptr;
@@ -1042,17 +1044,17 @@ ObjectGroup::getProperty(ExclusiveContex
         return nullptr;
     }
 
     MOZ_ASSERT(!*pprop);
 
     setBasePropertyCount(propertyCount);
     *pprop = base;
 
-    updateNewPropertyTypes(cx, id, &base->types);
+    updateNewPropertyTypes(cx, obj, id, &base->types);
 
     if (propertyCount == OBJECT_FLAG_PROPERTY_COUNT_LIMIT) {
         // We hit the maximum number of properties the object can have, mark
         // the object unknown so that new properties will not be added in the
         // future.
         markUnknown(cx);
     }
 
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -1144,17 +1144,18 @@ bool
 HeapTypeSetKey::instantiate(JSContext *cx)
 {
     if (maybeTypes())
         return true;
     if (object()->isSingleton() && !object()->singleton()->getGroup(cx)) {
         cx->clearPendingException();
         return false;
     }
-    maybeTypes_ = object()->maybeGroup()->getProperty(cx, id());
+    JSObject *obj = object()->isSingleton() ? object()->singleton() : nullptr;
+    maybeTypes_ = object()->maybeGroup()->getProperty(cx, obj, id());
     return maybeTypes_ != nullptr;
 }
 
 static bool
 CheckFrozenTypeSet(JSContext *cx, TemporaryTypeSet *frozen, StackTypeSet *actual)
 {
     // Return whether the types frozen for a script during compilation are
     // still valid. Also check for any new types added to the frozen set during
@@ -1687,43 +1688,49 @@ class ConstraintDataFreezeObjectForInlin
 
     bool shouldSweep() { return false; }
 };
 
 // Constraint which triggers recompilation when a typed array's data becomes
 // invalid.
 class ConstraintDataFreezeObjectForTypedArrayData
 {
+    NativeObject *obj;
+
     void *viewData;
     uint32_t length;
 
   public:
     explicit ConstraintDataFreezeObjectForTypedArrayData(TypedArrayObject &tarray)
-      : viewData(tarray.viewData()),
+      : obj(&tarray),
+        viewData(tarray.viewData()),
         length(tarray.length())
-    {}
+    {
+        MOZ_ASSERT(tarray.isSingleton());
+    }
 
     const char *kind() { return "freezeObjectForTypedArrayData"; }
 
     bool invalidateOnNewType(TypeSet::Type type) { return false; }
     bool invalidateOnNewPropertyState(TypeSet *property) { return false; }
     bool invalidateOnNewObjectState(ObjectGroup *group) {
-        TypedArrayObject &tarray = group->singleton()->as<TypedArrayObject>();
-        return tarray.viewData() != viewData || tarray.length() != length;
+        MOZ_ASSERT(obj->group() == group);
+        TypedArrayObject &tarr = obj->as<TypedArrayObject>();
+        return tarr.viewData() != viewData || tarr.length() != length;
     }
 
     bool constraintHolds(JSContext *cx,
                          const HeapTypeSetKey &property, TemporaryTypeSet *expected)
     {
         return !invalidateOnNewObjectState(property.object()->maybeGroup());
     }
 
     bool shouldSweep() {
         // Note: |viewData| is only used for equality testing.
-        return false;
+        return IsObjectAboutToBeFinalized(&obj);
     }
 };
 
 // Constraint which triggers recompilation if an unboxed object in some group
 // is converted to a native object.
 class ConstraintDataFreezeObjectForUnboxedConvertedToNative
 {
   public:
@@ -2469,28 +2476,31 @@ UpdatePropertyType(ExclusiveContext *cx,
             InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s - setConstant",
                       InferSpewColor(types), types, InferSpewColorReset(),
                       TypeSet::ObjectGroupString(obj->group()), TypeIdString(shape->propid()));
         }
     }
 }
 
 void
-ObjectGroup::updateNewPropertyTypes(ExclusiveContext *cx, jsid id, HeapTypeSet *types)
+ObjectGroup::updateNewPropertyTypes(ExclusiveContext *cx, JSObject *objArg, jsid id, HeapTypeSet *types)
 {
     InferSpew(ISpewOps, "typeSet: %sT%p%s property %s %s",
               InferSpewColor(types), types, InferSpewColorReset(),
               TypeSet::ObjectGroupString(this), TypeIdString(id));
 
-    if (!singleton() || !singleton()->isNative()) {
+    MOZ_ASSERT_IF(objArg, objArg->group() == this);
+    MOZ_ASSERT_IF(singleton(), objArg);
+
+    if (!singleton() || !objArg->isNative()) {
         types->setNonConstantProperty(cx);
         return;
     }
 
-    NativeObject *obj = &singleton()->as<NativeObject>();
+    NativeObject *obj = &objArg->as<NativeObject>();
 
     /*
      * Fill the property in with any type the object already has in an own
      * property. We are only interested in plain native properties and
      * dense elements which don't go through a barrier when read by the VM
      * or jitcode.
      */
 
@@ -2536,17 +2546,17 @@ ObjectGroup::addDefiniteProperties(Exclu
     // Mark all properties of shape as definite properties of this group.
     AutoEnterAnalysis enter(cx);
 
     while (!shape->isEmptyShape()) {
         jsid id = IdToTypeId(shape->propid());
         if (!JSID_IS_VOID(id)) {
             MOZ_ASSERT_IF(shape->slot() >= shape->numFixedSlots(),
                           shape->numFixedSlots() == NativeObject::MAX_FIXED_SLOTS);
-            TypeSet *types = getProperty(cx, id);
+            TypeSet *types = getProperty(cx, nullptr, id);
             if (!types)
                 return false;
             if (types->canSetDefinite(shape->slot()))
                 types->setDefinite(shape->slot());
         }
 
         shape = shape->previous();
     }
@@ -2578,26 +2588,26 @@ ObjectGroup::matchDefiniteProperties(Han
                 return false;
         }
     }
 
     return true;
 }
 
 void
-js::AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, jsid id, TypeSet::Type type)
+js::AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, JSObject *obj, jsid id, TypeSet::Type type)
 {
     MOZ_ASSERT(id == IdToTypeId(id));
 
     if (group->unknownProperties())
         return;
 
     AutoEnterAnalysis enter(cx);
 
-    HeapTypeSet *types = group->getProperty(cx, id);
+    HeapTypeSet *types = group->getProperty(cx, obj, id);
     if (!types)
         return;
 
     // Clear any constant flag if it exists.
     if (!types->empty() && !types->nonConstantProperty()) {
         InferSpew(ISpewOps, "constantMutated: %sT%p%s %s",
                   InferSpewColor(types), types, InferSpewColorReset(), TypeSet::TypeString(type));
         types->setNonConstantProperty(cx);
@@ -2617,54 +2627,54 @@ js::AddTypePropertyId(ExclusiveContext *
         type = TypeSet::AnyObjectType();
 
     // Propagate new types from partially initialized groups to fully
     // initialized groups for the acquired properties analysis. Note that we
     // don't need to do this for other property changes, as these will also be
     // reflected via shape changes on the object that will prevent the object
     // from acquiring the fully initialized group.
     if (group->newScript() && group->newScript()->initializedGroup())
-        AddTypePropertyId(cx, group->newScript()->initializedGroup(), id, type);
+        AddTypePropertyId(cx, group->newScript()->initializedGroup(), nullptr, id, type);
 
     // Maintain equivalent type information for unboxed object groups and their
     // corresponding native group. Since type sets might contain the unboxed
     // group but not the native group, this ensures optimizations based on the
     // unboxed group are valid for the native group.
     if (group->maybeUnboxedLayout() && group->maybeUnboxedLayout()->nativeGroup())
-        AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), id, type);
+        AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), nullptr, id, type);
     if (ObjectGroup *unboxedGroup = group->maybeOriginalUnboxedGroup())
-        AddTypePropertyId(cx, unboxedGroup, id, type);
+        AddTypePropertyId(cx, unboxedGroup, nullptr, id, type);
 }
 
 void
-js::AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, jsid id, const Value &value)
+js::AddTypePropertyId(ExclusiveContext *cx, ObjectGroup *group, JSObject *obj, jsid id, const Value &value)
 {
-    AddTypePropertyId(cx, group, id, TypeSet::GetValueType(value));
+    AddTypePropertyId(cx, group, obj, id, TypeSet::GetValueType(value));
 }
 
 void
-ObjectGroup::markPropertyNonData(ExclusiveContext *cx, jsid id)
+ObjectGroup::markPropertyNonData(ExclusiveContext *cx, JSObject *obj, jsid id)
 {
     AutoEnterAnalysis enter(cx);
 
     id = IdToTypeId(id);
 
-    HeapTypeSet *types = getProperty(cx, id);
+    HeapTypeSet *types = getProperty(cx, obj, id);
     if (types)
         types->setNonDataProperty(cx);
 }
 
 void
-ObjectGroup::markPropertyNonWritable(ExclusiveContext *cx, jsid id)
+ObjectGroup::markPropertyNonWritable(ExclusiveContext *cx, JSObject *obj, jsid id)
 {
     AutoEnterAnalysis enter(cx);
 
     id = IdToTypeId(id);
 
-    HeapTypeSet *types = getProperty(cx, id);
+    HeapTypeSet *types = getProperty(cx, obj, id);
     if (types)
         types->setNonWritableProperty(cx);
 }
 
 bool
 ObjectGroup::isPropertyNonData(jsid id)
 {
     TypeSet *types = maybeGetProperty(id);
@@ -2706,22 +2716,16 @@ ObjectGroup::markStateChange(ExclusiveCo
 void
 ObjectGroup::setFlags(ExclusiveContext *cx, ObjectGroupFlags flags)
 {
     if (hasAllFlags(flags))
         return;
 
     AutoEnterAnalysis enter(cx);
 
-    if (singleton()) {
-        /* Make sure flags are consistent with persistent object state. */
-        MOZ_ASSERT_IF(flags & OBJECT_FLAG_ITERATED,
-                      singleton()->lastProperty()->hasObjectFlag(BaseShape::ITERATED_SINGLETON));
-    }
-
     addFlags(flags);
 
     InferSpew(ISpewOps, "%s: setFlags 0x%x", TypeSet::ObjectGroupString(this), flags);
 
     ObjectStateChange(cx, this, false);
 
     // Propagate flag changes from partially to fully initialized groups for the
     // acquired properties analysis.
@@ -2979,17 +2983,17 @@ js::AddClearDefiniteGetterSetterForProto
      * a permanent property in any transitive prototype, the definite
      * properties get cleared from the group.
      */
     RootedObject proto(cx, group->proto().toObjectOrNull());
     while (proto) {
         ObjectGroup *protoGroup = proto->getGroup(cx);
         if (!protoGroup || protoGroup->unknownProperties())
             return false;
-        HeapTypeSet *protoTypes = protoGroup->getProperty(cx, id);
+        HeapTypeSet *protoTypes = protoGroup->getProperty(cx, proto, id);
         if (!protoTypes || protoTypes->nonDataProperty() || protoTypes->nonWritableProperty())
             return false;
         if (!protoTypes->addConstraint(cx, cx->typeLifoAlloc().new_<TypeConstraintClearDefiniteGetterSetter>(group)))
             return false;
         proto = proto->getProto();
     }
     return true;
 }
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -110,17 +110,24 @@ enum : uint32_t {
 };
 typedef uint32_t TypeFlags;
 
 /* Flags and other state stored in ObjectGroup::Flags */
 enum : uint32_t {
     /* Whether this group is associated with some allocation site. */
     OBJECT_FLAG_FROM_ALLOCATION_SITE  = 0x1,
 
-    /* (0x2 and 0x4 are unused) */
+    /* Whether this group is associated with a single object. */
+    OBJECT_FLAG_SINGLETON             = 0x2,
+
+    /*
+     * Whether this group is used by objects whose singleton groups have not
+     * been created yet.
+     */
+    OBJECT_FLAG_LAZY_SINGLETON        = 0x4,
 
     /* Mask/shift for the number of properties in propertySet */
     OBJECT_FLAG_PROPERTY_COUNT_MASK   = 0xfff8,
     OBJECT_FLAG_PROPERTY_COUNT_SHIFT  = 3,
     OBJECT_FLAG_PROPERTY_COUNT_LIMIT  =
         OBJECT_FLAG_PROPERTY_COUNT_MASK >> OBJECT_FLAG_PROPERTY_COUNT_SHIFT,
 
     /* Whether any objects this represents may have sparse indexes. */
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -248,17 +248,17 @@ UnboxedLayout::makeNativeGroup(JSContext
         jsid id = NameToId(property.name);
 
         HeapTypeSet *typeProperty = group->maybeGetProperty(id);
         TypeSet::TypeList types;
         if (!typeProperty->enumerateTypes(&types))
             return false;
         MOZ_ASSERT(!types.empty());
         for (size_t j = 0; j < types.length(); j++)
-            AddTypePropertyId(cx, nativeGroup, id, types[j]);
+            AddTypePropertyId(cx, nativeGroup, nullptr, id, types[j]);
         HeapTypeSet *nativeProperty = nativeGroup->maybeGetProperty(id);
         if (nativeProperty->canSetDefinite(i))
             nativeProperty->setDefinite(i);
     }
 
     layout.nativeGroup_ = nativeGroup;
     layout.nativeShape_ = shape;
     layout.replacementNewGroup_ = replacementNewGroup;