js/src/vm/ObjectGroup.h
author Jon Coppeard <jcoppeard@mozilla.com>
Wed, 06 Mar 2019 16:38:25 +0000
changeset 520784 1b4fd78107e2bcf7fe0f44038176ca745b07cd88
parent 512215 f052949e151b1f2cfd9203a507a94ddd42392ec4
child 521273 e6a4cd115b45542c35f2ac2ab64758602a619f30
permissions -rw-r--r--
Bug 1532376 - Fix places where we don't respect the shouldPretenure flag when creating an object r=jandem This adds an overload of GetInitialHeap that takes an ObjectGroup* instead of a Class* and also takes into account whether the group's shouldPreTenure flag is set. I moved this to JSObject-inl.h too. I removed the heap parameter in a few places, in particular in NewDenseCopyOnWriteArray which required a bunch of changes elsewhere including the JITs. I left the heap parameter intact for environment objects where we may have reason prefer these objects to be allocated in the tenure heap. It's possible we should just remove all these parameters too and make allocation more uniform. Differential Revision: https://phabricator.services.mozilla.com/D22324

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * vim: set ts=8 sts=2 et sw=2 tw=80:
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef vm_ObjectGroup_h
#define vm_ObjectGroup_h

#include "jsfriendapi.h"

#include "ds/IdValuePair.h"
#include "gc/Barrier.h"
#include "gc/GCTrace.h"
#include "js/CharacterEncoding.h"
#include "js/GCHashTable.h"
#include "js/TypeDecls.h"
#include "vm/TaggedProto.h"
#include "vm/TypeSet.h"

namespace js {

class TypeDescr;
class UnboxedLayout;

class PreliminaryObjectArrayWithTemplate;
class TypeNewScript;
class AutoClearTypeInferenceStateOnOOM;
class AutoSweepObjectGroup;
class CompilerConstraintList;
class ObjectGroupRealm;

namespace gc {
void MergeRealms(JS::Realm* source, JS::Realm* target);
}  // namespace gc

/*
 * The NewObjectKind allows an allocation site to specify the type properties
 * and lifetime requirements that must be fixed at allocation time.
 */
enum NewObjectKind {
  /* This is the default. Most objects are generic. */
  GenericObject,

  /*
   * Singleton objects are treated specially by the type system. This flag
   * ensures that the new object is automatically set up correctly as a
   * singleton and is allocated in the tenured heap.
   */
  SingletonObject,

  /*
   * CrossCompartmentWrappers use the common Proxy class, but are allowed
   * to have nursery lifetime.
   */
  NurseryAllocatedProxy,

  /*
   * Objects which will not benefit from being allocated in the nursery
   * (e.g. because they are known to have a long lifetime) may be allocated
   * with this kind to place them immediately into the tenured generation.
   */
  TenuredObject
};

/*
 * [SMDOC] Type-Inference lazy ObjectGroup
 *
 * 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, objects which
 * dynamically become the prototype of some other object, and in some other
 * cases. Typical web workloads often create many windows (and many copies of
 * standard natives) and many scripts, with comparatively few non-singleton
 * groups.
 *
 * We can recover the type information for the object from examining it,
 * so don't normally track the possible types of its properties as it is
 * updated. Property type sets for the object are only constructed when an
 * analyzed script attaches constraints to it: the script is querying that
 * property off the object or another which delegates to it, and the analysis
 * 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 {
 public:
  class Property;

 private:
  /* Class shared by objects in this group. */
  const Class* clasp_;  // set by constructor

  /* Prototype shared by objects in this group. */
  GCPtr<TaggedProto> proto_;  // set by constructor

  /* Realm shared by objects in this group. */
  JS::Realm* realm_;
  ;  // set by constructor

  /* Flags for this group. */
  ObjectGroupFlags flags_;  // set by constructor

  // If non-null, holds additional information about this object, whose
  // format is indicated by the object's addendum kind.
  void* addendum_ = nullptr;

  /*
   * [SMDOC] Type-Inference object properties
   *
   * Properties of this object.
   *
   * The type sets in the properties of a group describe the possible values
   * that can be read out of that property in actual JS objects. In native
   * objects, property types account for plain data properties (those with a
   * slot and no getter or setter hook) and dense elements. In typed objects
   * and unboxed objects, property types account for object and value
   * properties and elements in the object, and expando properties in unboxed
   * objects.
   *
   * For accesses on these properties, the correspondence is as follows:
   *
   * 1. If the group has unknownProperties(), the possible properties and
   *    value types for associated JSObjects are unknown.
   *
   * 2. Otherwise, for any |obj| in |group|, and any |id| which is a property
   *    in |obj|, before obj->getProperty(id) the property in |group| for
   *    |id| must reflect the result of the getProperty.
   *
   * There are several exceptions to this:
   *
   * 1. For properties of global JS objects which are undefined at the point
   *    where the property was (lazily) generated, the property type set will
   *    remain empty, and the 'undefined' type will only be added after a
   *    subsequent assignment or deletion. After these properties have been
   *    assigned a defined value, the only way they can become undefined
   *    again is after such an assign or deletion.
   *
   * 2. Array lengths are special cased by the compiler and VM and are not
   *    reflected in property types.
   *
   * 3. In typed objects (but not unboxed objects), the initial values of
   *    properties (null pointers and undefined values) are not reflected in
   *    the property types. These values are always possible when reading the
   *    property.
   *
   * 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 = nullptr;

  // END OF PROPERTIES

 private:
  static inline uint32_t offsetOfClasp() {
    return offsetof(ObjectGroup, clasp_);
  }

  static inline uint32_t offsetOfProto() {
    return offsetof(ObjectGroup, proto_);
  }

  static inline uint32_t offsetOfRealm() {
    return offsetof(ObjectGroup, realm_);
  }

  static inline uint32_t offsetOfFlags() {
    return offsetof(ObjectGroup, flags_);
  }

  static inline uint32_t offsetOfAddendum() {
    return offsetof(ObjectGroup, addendum_);
  }

  friend class gc::GCRuntime;
  friend class gc::GCTrace;

  // See JSObject::offsetOfGroup() comment.
  friend class js::jit::MacroAssembler;

 public:
  const Class* clasp() const { return clasp_; }

  void setClasp(const Class* clasp) {
    MOZ_ASSERT(JS::StringIsASCII(clasp->name));
    MOZ_ASSERT(hasUncacheableClass());
    clasp_ = clasp;
  }

  // Certain optimizations may mutate the class of an ObjectGroup - and thus
  // all objects in it - after it is created. If true, the JIT must not
  // assume objects of a previously seen group have the same class as before.
  //
  // See: TryConvertToUnboxedLayout
  bool hasUncacheableClass() const { return clasp_->isNative(); }

  bool hasDynamicPrototype() const { return proto_.isDynamic(); }

  const GCPtr<TaggedProto>& proto() const { return proto_; }

  GCPtr<TaggedProto>& proto() { return proto_; }

  void setProto(TaggedProto proto);
  void setProtoUnchecked(TaggedProto proto);

  bool hasUncacheableProto() const {
    // We allow singletons to mutate their prototype after the group has
    // been created. If true, the JIT must re-check prototype even if group
    // has been seen before.
    MOZ_ASSERT(!hasDynamicPrototype());
    return singleton();
  }

  bool singleton() const {
    return flagsDontCheckGeneration() & OBJECT_FLAG_SINGLETON;
  }

  bool lazy() const {
    bool res = flagsDontCheckGeneration() & OBJECT_FLAG_LAZY_SINGLETON;
    MOZ_ASSERT_IF(res, singleton());
    return res;
  }

  JS::Compartment* compartment() const {
    return JS::GetCompartmentForRealm(realm_);
  }
  JS::Compartment* maybeCompartment() const { return compartment(); }
  JS::Realm* realm() const { return realm_; }

 public:
  // Kinds of addendums which can be attached to ObjectGroups.
  enum AddendumKind {
    Addendum_None,

    // When used by interpreted function, the addendum stores the
    // canonical JSFunction object.
    Addendum_InterpretedFunction,

    // When used by the 'new' group when constructing an interpreted
    // function, the addendum stores a TypeNewScript.
    Addendum_NewScript,

    // For some plain objects, the addendum stores a
    // PreliminaryObjectArrayWithTemplate.
    Addendum_PreliminaryObjects,

    // When objects in this group have an unboxed representation, the
    // addendum stores an UnboxedLayout (which might have a TypeNewScript
    // as well, if the group is also constructed using 'new').
    Addendum_UnboxedLayout,

    // If this group is used by objects that have been converted from an
    // unboxed representation and/or have the same allocation kind as such
    // objects, the addendum points to that unboxed group.
    Addendum_OriginalUnboxedGroup,

    // When used by typed objects, the addendum stores a TypeDescr.
    Addendum_TypeDescr
  };

 private:
  void setAddendum(AddendumKind kind, void* addendum, bool writeBarrier = true);

  AddendumKind addendumKind() const {
    return (AddendumKind)((flags_ & OBJECT_FLAG_ADDENDUM_MASK) >>
                          OBJECT_FLAG_ADDENDUM_SHIFT);
  }

  TypeNewScript* newScriptDontCheckGeneration() const {
    if (addendumKind() == Addendum_NewScript) {
      return reinterpret_cast<TypeNewScript*>(addendum_);
    }
    return nullptr;
  }

  TypeNewScript* anyNewScript(const AutoSweepObjectGroup& sweep);
  void detachNewScript(bool writeBarrier, ObjectGroup* replacement);

  ObjectGroupFlags flagsDontCheckGeneration() const { return flags_; }

 public:
  inline ObjectGroupFlags flags(const AutoSweepObjectGroup&);
  inline void addFlags(const AutoSweepObjectGroup&, ObjectGroupFlags flags);
  inline void clearFlags(const AutoSweepObjectGroup&, ObjectGroupFlags flags);
  inline TypeNewScript* newScript(const AutoSweepObjectGroup& sweep);

  void setNewScript(TypeNewScript* newScript) {
    MOZ_ASSERT(newScript);
    setAddendum(Addendum_NewScript, newScript);
  }
  void detachNewScript() { setAddendum(Addendum_None, nullptr); }

  inline PreliminaryObjectArrayWithTemplate* maybePreliminaryObjects(
      const AutoSweepObjectGroup& sweep);

  PreliminaryObjectArrayWithTemplate*
  maybePreliminaryObjectsDontCheckGeneration() {
    if (addendumKind() == Addendum_PreliminaryObjects) {
      return reinterpret_cast<PreliminaryObjectArrayWithTemplate*>(addendum_);
    }
    return nullptr;
  }

  void setPreliminaryObjects(
      PreliminaryObjectArrayWithTemplate* preliminaryObjects) {
    setAddendum(Addendum_PreliminaryObjects, preliminaryObjects);
  }

  void detachPreliminaryObjects() {
    MOZ_ASSERT(maybePreliminaryObjectsDontCheckGeneration());
    setAddendum(Addendum_None, nullptr);
  }

  inline bool hasUnanalyzedPreliminaryObjects();

  inline UnboxedLayout* maybeUnboxedLayout(const AutoSweepObjectGroup& sweep);
  inline UnboxedLayout& unboxedLayout(const AutoSweepObjectGroup& sweep);

  UnboxedLayout* maybeUnboxedLayoutDontCheckGeneration() const {
    if (addendumKind() == Addendum_UnboxedLayout) {
      return &unboxedLayoutDontCheckGeneration();
    }
    return nullptr;
  }

  UnboxedLayout& unboxedLayoutDontCheckGeneration() const {
    MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout);
    return *reinterpret_cast<UnboxedLayout*>(addendum_);
  }

  void setUnboxedLayout(UnboxedLayout* layout) {
    setAddendum(Addendum_UnboxedLayout, layout);
  }

  ObjectGroup* maybeOriginalUnboxedGroup() const {
    if (addendumKind() == Addendum_OriginalUnboxedGroup) {
      return reinterpret_cast<ObjectGroup*>(addendum_);
    }
    return nullptr;
  }

  void setOriginalUnboxedGroup(ObjectGroup* group) {
    setAddendum(Addendum_OriginalUnboxedGroup, group);
  }

  TypeDescr* maybeTypeDescr() {
    // Note: there is no need to sweep when accessing the type descriptor
    // of an object, as it is strongly held and immutable.
    if (addendumKind() == Addendum_TypeDescr) {
      return &typeDescr();
    }
    return nullptr;
  }

  TypeDescr& typeDescr() {
    MOZ_ASSERT(addendumKind() == Addendum_TypeDescr);
    return *reinterpret_cast<TypeDescr*>(addendum_);
  }

  void setTypeDescr(TypeDescr* descr) {
    setAddendum(Addendum_TypeDescr, descr);
  }

  JSFunction* maybeInterpretedFunction() {
    // Note: as with type descriptors, there is no need to sweep when
    // accessing the interpreted function associated with an object.
    if (addendumKind() == Addendum_InterpretedFunction) {
      return reinterpret_cast<JSFunction*>(addendum_);
    }
    return nullptr;
  }

  void setInterpretedFunction(JSFunction* fun) {
    setAddendum(Addendum_InterpretedFunction, fun);
  }

  class Property {
   public:
    // Identifier for this property, JSID_VOID for the aggregate integer
    // index property, or JSID_EMPTY for properties holding constraints
    // listening to changes in the group's state.
    GCPtrId id;

    // Possible own types for this property.
    HeapTypeSet types;

    explicit Property(jsid id) : id(id) {}

    Property(const Property& o) : id(o.id.get()), types(o.types) {}

    static uint32_t keyBits(jsid id) { return uint32_t(JSID_BITS(id)); }
    static jsid getKey(Property* p) { return p->id; }
  };

 public:
  inline ObjectGroup(const Class* clasp, TaggedProto proto, JS::Realm* realm,
                     ObjectGroupFlags initialFlags);

  inline bool hasAnyFlags(const AutoSweepObjectGroup& sweep,
                          ObjectGroupFlags flags);
  inline bool hasAllFlags(const AutoSweepObjectGroup& sweep,
                          ObjectGroupFlags flags);

  bool hasAnyFlagsDontCheckGeneration(ObjectGroupFlags flags) {
    MOZ_ASSERT((flags & OBJECT_FLAG_DYNAMIC_MASK) == flags);
    return !!(this->flagsDontCheckGeneration() & flags);
  }
  bool hasAllFlagsDontCheckGeneration(ObjectGroupFlags flags) {
    MOZ_ASSERT((flags & OBJECT_FLAG_DYNAMIC_MASK) == flags);
    return (this->flagsDontCheckGeneration() & flags) == flags;
  }

  inline bool unknownProperties(const AutoSweepObjectGroup& sweep);

  bool unknownPropertiesDontCheckGeneration() {
    MOZ_ASSERT_IF(flagsDontCheckGeneration() & OBJECT_FLAG_UNKNOWN_PROPERTIES,
                  hasAllFlagsDontCheckGeneration(OBJECT_FLAG_DYNAMIC_MASK));
    return !!(flagsDontCheckGeneration() & OBJECT_FLAG_UNKNOWN_PROPERTIES);
  }

  inline bool shouldPreTenure(const AutoSweepObjectGroup& sweep);
  inline bool shouldPreTenureDontCheckGeneration();

  gc::InitialHeap initialHeap(CompilerConstraintList* constraints);

  inline bool canPreTenure(const AutoSweepObjectGroup& sweep);
  inline bool fromAllocationSite(const AutoSweepObjectGroup& sweep);
  inline void setShouldPreTenure(const AutoSweepObjectGroup& sweep,
                                 JSContext* cx);

  /*
   * Get or create a property of this object. Only call this for properties
   * which a script accesses explicitly.
   */
  inline HeapTypeSet* getProperty(const AutoSweepObjectGroup& sweep,
                                  JSContext* cx, JSObject* obj, jsid id);

  /* Get a property only if it already exists. */
  MOZ_ALWAYS_INLINE HeapTypeSet* maybeGetProperty(
      const AutoSweepObjectGroup& sweep, jsid id);
  MOZ_ALWAYS_INLINE HeapTypeSet* maybeGetPropertyDontCheckGeneration(jsid id);

  /*
   * Iterate through the group's properties. getPropertyCount overapproximates
   * in the hash case (see SET_ARRAY_SIZE in TypeInference-inl.h), and
   * getProperty may return nullptr.
   */
  inline unsigned getPropertyCount(const AutoSweepObjectGroup& sweep);
  inline Property* getProperty(const AutoSweepObjectGroup& sweep, unsigned i);

  /* Helpers */

  void updateNewPropertyTypes(const AutoSweepObjectGroup& sweep, JSContext* cx,
                              JSObject* obj, jsid id, HeapTypeSet* types);
  void addDefiniteProperties(JSContext* cx, Shape* shape);
  bool matchDefiniteProperties(HandleObject obj);
  void markPropertyNonData(JSContext* cx, JSObject* obj, jsid id);
  void markPropertyNonWritable(JSContext* cx, JSObject* obj, jsid id);
  void markStateChange(const AutoSweepObjectGroup& sweep, JSContext* cx);
  void setFlags(const AutoSweepObjectGroup& sweep, JSContext* cx,
                ObjectGroupFlags flags);
  void markUnknown(const AutoSweepObjectGroup& sweep, JSContext* cx);
  void maybeClearNewScriptOnOOM();
  void clearNewScript(JSContext* cx, ObjectGroup* replacement = nullptr);

  void print(const AutoSweepObjectGroup& sweep);

  inline void clearProperties(const AutoSweepObjectGroup& sweep);
  void traceChildren(JSTracer* trc);

  inline bool needsSweep();
  void sweep(const AutoSweepObjectGroup& sweep);

 private:
  uint32_t generation() {
    return (flags_ & OBJECT_FLAG_GENERATION_MASK) >>
           OBJECT_FLAG_GENERATION_SHIFT;
  }

 public:
  void setGeneration(uint32_t generation) {
    MOZ_ASSERT(generation <=
               (OBJECT_FLAG_GENERATION_MASK >> OBJECT_FLAG_GENERATION_SHIFT));
    flags_ &= ~OBJECT_FLAG_GENERATION_MASK;
    flags_ |= generation << OBJECT_FLAG_GENERATION_SHIFT;
  }

  size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const;

  void finalize(FreeOp* fop);

  static const JS::TraceKind TraceKind = JS::TraceKind::ObjectGroup;

 public:
  const ObjectGroupFlags* addressOfFlags() const { return &flags_; }

  // Get the bit pattern stored in an object's addendum when it has an
  // original unboxed group.
  static inline int32_t addendumOriginalUnboxedGroupValue() {
    return Addendum_OriginalUnboxedGroup << OBJECT_FLAG_ADDENDUM_SHIFT;
  }

  inline uint32_t basePropertyCount(const AutoSweepObjectGroup& sweep);
  inline uint32_t basePropertyCountDontCheckGeneration();

 private:
  inline void setBasePropertyCount(const AutoSweepObjectGroup& sweep,
                                   uint32_t count);

  static void staticAsserts() {
    JS_STATIC_ASSERT(offsetof(ObjectGroup, proto_) ==
                     offsetof(js::shadow::ObjectGroup, proto));
  }

 public:
  // Whether to make a deep cloned singleton when cloning fun.
  static bool useSingletonForClone(JSFunction* fun);

  // Whether to make a singleton when calling 'new' at script/pc.
  static bool useSingletonForNewObject(JSContext* cx, JSScript* script,
                                       jsbytecode* pc);

  // Whether to make a singleton object at an allocation site.
  static bool useSingletonForAllocationSite(JSScript* script, jsbytecode* pc,
                                            JSProtoKey key);

  // Static accessors for ObjectGroupRealm NewTable.

  static ObjectGroup* defaultNewGroup(JSContext* cx, const Class* clasp,
                                      TaggedProto proto,
                                      JSObject* associated = nullptr);
  static ObjectGroup* lazySingletonGroup(JSContext* cx, ObjectGroup* oldGroup,
                                         const Class* clasp, TaggedProto proto);

  static void setDefaultNewGroupUnknown(JSContext* cx, ObjectGroupRealm& realm,
                                        const js::Class* clasp,
                                        JS::HandleObject obj);

#ifdef DEBUG
  static bool hasDefaultNewGroup(JSObject* proto, const Class* clasp,
                                 ObjectGroup* group);
#endif

  // Static accessors for ObjectGroupRealm ArrayObjectTable and
  // PlainObjectTable.

  enum class NewArrayKind {
    Normal,       // Specialize array group based on its element type.
    CopyOnWrite,  // Make an array with copy-on-write elements.
    UnknownIndex  // Make an array with an unknown element type.
  };

  // Create an ArrayObject with the specified elements and a group specialized
  // for the elements.
  static ArrayObject* newArrayObject(
      JSContext* cx, const Value* vp, size_t length, NewObjectKind newKind,
      NewArrayKind arrayKind = NewArrayKind::Normal);

  // Create a PlainObject or UnboxedPlainObject with the specified properties
  // and a group specialized for those properties.
  static JSObject* newPlainObject(JSContext* cx, IdValuePair* properties,
                                  size_t nproperties, NewObjectKind newKind);

  // Static accessors for ObjectGroupRealm AllocationSiteTable.

  // Get a non-singleton group to use for objects created at the specified
  // allocation site.
  static ObjectGroup* allocationSiteGroup(JSContext* cx, JSScript* script,
                                          jsbytecode* pc, JSProtoKey key,
                                          HandleObject proto = nullptr);

  // Get a non-singleton group to use for objects created in a JSNative call.
  static ObjectGroup* callingAllocationSiteGroup(JSContext* cx, JSProtoKey key,
                                                 HandleObject proto = nullptr);

  // Set the group or singleton-ness of an object created for an allocation
  // site.
  static bool setAllocationSiteObjectGroup(JSContext* cx, HandleScript script,
                                           jsbytecode* pc, HandleObject obj,
                                           bool singleton);

  static ArrayObject* getOrFixupCopyOnWriteObject(JSContext* cx,
                                                  HandleScript script,
                                                  jsbytecode* pc);
  static ArrayObject* getCopyOnWriteObject(JSScript* script, jsbytecode* pc);

  // Returns false if not found.
  static bool findAllocationSite(JSContext* cx, const ObjectGroup* group,
                                 JSScript** script, uint32_t* offset);

 private:
  static ObjectGroup* defaultNewGroup(JSContext* cx, JSProtoKey key);
};

// Structure used to manage the groups in a realm.
class ObjectGroupRealm {
 private:
  class NewTable;

  struct ArrayObjectKey;
  using ArrayObjectTable =
      js::GCRekeyableHashMap<ArrayObjectKey, ReadBarrieredObjectGroup,
                             ArrayObjectKey, SystemAllocPolicy>;

  struct PlainObjectKey;
  struct PlainObjectEntry;
  struct PlainObjectTableSweepPolicy {
    static bool needsSweep(PlainObjectKey* key, PlainObjectEntry* entry);
  };
  using PlainObjectTable =
      JS::GCHashMap<PlainObjectKey, PlainObjectEntry, PlainObjectKey,
                    SystemAllocPolicy, PlainObjectTableSweepPolicy>;

  class AllocationSiteTable;

 private:
  // Set of default 'new' or lazy groups in the realm.
  NewTable* defaultNewTable = nullptr;
  NewTable* lazyTable = nullptr;

  // This cache is purged on GC.
  class DefaultNewGroupCache {
    ObjectGroup* group_;
    JSObject* associated_;

   public:
    DefaultNewGroupCache() : associated_(nullptr) { purge(); }

    void purge() { group_ = nullptr; }
    void put(ObjectGroup* group, JSObject* associated) {
      group_ = group;
      associated_ = associated;
    }

    MOZ_ALWAYS_INLINE ObjectGroup* lookup(const Class* clasp, TaggedProto proto,
                                          JSObject* associated);
  } defaultNewGroupCache = {};

  // Tables for managing groups common to the contents of large script
  // singleton objects and JSON objects. These are vanilla ArrayObjects and
  // PlainObjects, so we distinguish the groups of different ones by looking
  // at the types of their properties.
  //
  // All singleton/JSON arrays which have the same prototype, are homogenous
  // and of the same element type will share a group. All singleton/JSON
  // objects which have the same shape and property types will also share a
  // group. We don't try to collate arrays or objects with type mismatches.
  ArrayObjectTable* arrayObjectTable = nullptr;
  PlainObjectTable* plainObjectTable = nullptr;

  // Table for referencing types of objects keyed to an allocation site.
  AllocationSiteTable* allocationSiteTable = nullptr;

  // A single per-realm ObjectGroup for all calls to StringSplitString.
  // StringSplitString is always called from self-hosted code, and conceptually
  // the return object for a string.split(string) operation should have a
  // unified type.  Having a global group for this also allows us to remove
  // the hash-table lookup that would be required if we allocated this group
  // on the basis of call-site pc.
  ReadBarrieredObjectGroup stringSplitStringGroup = {};

 public:
  // All unboxed layouts in the realm.
  mozilla::LinkedList<js::UnboxedLayout> unboxedLayouts;

  // END OF PROPERTIES

 private:
  friend class ObjectGroup;

  struct AllocationSiteKey;

 public:
  struct NewEntry;

  ObjectGroupRealm() = default;
  ~ObjectGroupRealm();

  ObjectGroupRealm(ObjectGroupRealm&) = delete;
  void operator=(ObjectGroupRealm&) = delete;

  static ObjectGroupRealm& get(const ObjectGroup* group);
  static ObjectGroupRealm& getForNewObject(JSContext* cx);

  void replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc,
                                  JSProtoKey kind, ObjectGroup* group);

  void removeDefaultNewGroup(const Class* clasp, TaggedProto proto,
                             JSObject* associated);
  void replaceDefaultNewGroup(const Class* clasp, TaggedProto proto,
                              JSObject* associated, ObjectGroup* group);

  static ObjectGroup* makeGroup(JSContext* cx, JS::Realm* realm,
                                const Class* clasp, Handle<TaggedProto> proto,
                                ObjectGroupFlags initialFlags = 0);

  static ObjectGroup* getStringSplitStringGroup(JSContext* cx);

  void addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
                              size_t* allocationSiteTables,
                              size_t* arrayGroupTables,
                              size_t* plainObjectGroupTables,
                              size_t* realmTables);

  void clearTables();

  void sweep();

  void purge() { defaultNewGroupCache.purge(); }

#ifdef JSGC_HASH_TABLE_CHECKS
  void checkTablesAfterMovingGC() {
    checkNewTableAfterMovingGC(defaultNewTable);
    checkNewTableAfterMovingGC(lazyTable);
  }
#endif

  void fixupTablesAfterMovingGC() {
    fixupNewTableAfterMovingGC(defaultNewTable);
    fixupNewTableAfterMovingGC(lazyTable);
  }

 private:
#ifdef JSGC_HASH_TABLE_CHECKS
  void checkNewTableAfterMovingGC(NewTable* table);
#endif

  void fixupNewTableAfterMovingGC(NewTable* table);
};

PlainObject* NewPlainObjectWithProperties(JSContext* cx,
                                          IdValuePair* properties,
                                          size_t nproperties,
                                          NewObjectKind newKind);

bool CombineArrayElementTypes(JSContext* cx, JSObject* newObj,
                              const Value* compare, size_t ncompare);

bool CombinePlainObjectPropertyTypes(JSContext* cx, JSObject* newObj,
                                     const Value* compare, size_t ncompare);

}  // namespace js

#endif /* vm_ObjectGroup_h */