summaryrefslogtreecommitdiffstats
path: root/js/src/vm/ObjectGroup.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/ObjectGroup.cpp')
-rw-r--r--js/src/vm/ObjectGroup.cpp1862
1 files changed, 1862 insertions, 0 deletions
diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp
new file mode 100644
index 000000000..7be697fb6
--- /dev/null
+++ b/js/src/vm/ObjectGroup.cpp
@@ -0,0 +1,1862 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "vm/ObjectGroup.h"
+
+#include "jshashutil.h"
+#include "jsobj.h"
+
+#include "gc/Marking.h"
+#include "gc/Policy.h"
+#include "gc/StoreBuffer.h"
+#include "gc/Zone.h"
+#include "js/CharacterEncoding.h"
+#include "vm/ArrayObject.h"
+#include "vm/Shape.h"
+#include "vm/TaggedProto.h"
+#include "vm/UnboxedObject.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/UnboxedObject-inl.h"
+
+using namespace js;
+
+using mozilla::DebugOnly;
+using mozilla::PodZero;
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroup
+/////////////////////////////////////////////////////////////////////
+
+ObjectGroup::ObjectGroup(const Class* clasp, TaggedProto proto, JSCompartment* comp,
+ ObjectGroupFlags initialFlags)
+{
+ PodZero(this);
+
+ /* Windows may not appear on prototype chains. */
+ MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
+ MOZ_ASSERT(JS::StringIsASCII(clasp->name));
+
+ this->clasp_ = clasp;
+ this->proto_ = proto;
+ this->compartment_ = comp;
+ this->flags_ = initialFlags;
+
+ setGeneration(zone()->types.generation);
+}
+
+void
+ObjectGroup::finalize(FreeOp* fop)
+{
+ if (newScriptDontCheckGeneration())
+ newScriptDontCheckGeneration()->clear();
+ fop->delete_(newScriptDontCheckGeneration());
+ fop->delete_(maybeUnboxedLayoutDontCheckGeneration());
+ if (maybePreliminaryObjectsDontCheckGeneration())
+ maybePreliminaryObjectsDontCheckGeneration()->clear();
+ fop->delete_(maybePreliminaryObjectsDontCheckGeneration());
+}
+
+void
+ObjectGroup::setProtoUnchecked(TaggedProto proto)
+{
+ proto_ = proto;
+ MOZ_ASSERT_IF(proto_.isObject() && proto_.toObject()->isNative(),
+ proto_.toObject()->isDelegate());
+}
+
+void
+ObjectGroup::setProto(TaggedProto proto)
+{
+ MOZ_ASSERT(singleton());
+ setProtoUnchecked(proto);
+}
+
+size_t
+ObjectGroup::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const
+{
+ size_t n = 0;
+ if (TypeNewScript* newScript = newScriptDontCheckGeneration())
+ n += newScript->sizeOfIncludingThis(mallocSizeOf);
+ if (UnboxedLayout* layout = maybeUnboxedLayoutDontCheckGeneration())
+ n += layout->sizeOfIncludingThis(mallocSizeOf);
+ return n;
+}
+
+void
+ObjectGroup::setAddendum(AddendumKind kind, void* addendum, bool writeBarrier /* = true */)
+{
+ MOZ_ASSERT(!needsSweep());
+ MOZ_ASSERT(kind <= (OBJECT_FLAG_ADDENDUM_MASK >> OBJECT_FLAG_ADDENDUM_SHIFT));
+
+ if (writeBarrier) {
+ // Manually trigger barriers if we are clearing new script or
+ // preliminary object information. Other addendums are immutable.
+ switch (addendumKind()) {
+ case Addendum_PreliminaryObjects:
+ PreliminaryObjectArrayWithTemplate::writeBarrierPre(maybePreliminaryObjects());
+ break;
+ case Addendum_NewScript:
+ TypeNewScript::writeBarrierPre(newScript());
+ break;
+ case Addendum_None:
+ break;
+ default:
+ MOZ_ASSERT(addendumKind() == kind);
+ }
+ }
+
+ flags_ &= ~OBJECT_FLAG_ADDENDUM_MASK;
+ flags_ |= kind << OBJECT_FLAG_ADDENDUM_SHIFT;
+ addendum_ = addendum;
+}
+
+/* static */ bool
+ObjectGroup::useSingletonForClone(JSFunction* fun)
+{
+ if (!fun->isInterpreted())
+ return false;
+
+ if (fun->isArrow())
+ return false;
+
+ if (fun->isSingleton())
+ return false;
+
+ /*
+ * When a function is being used as a wrapper for another function, it
+ * improves precision greatly to distinguish between different instances of
+ * the wrapper; otherwise we will conflate much of the information about
+ * the wrapped functions.
+ *
+ * An important example is the Class.create function at the core of the
+ * Prototype.js library, which looks like:
+ *
+ * var Class = {
+ * create: function() {
+ * return function() {
+ * this.initialize.apply(this, arguments);
+ * }
+ * }
+ * };
+ *
+ * Each instance of the innermost function will have a different wrapped
+ * initialize method. We capture this, along with similar cases, by looking
+ * for short scripts which use both .apply and arguments. For such scripts,
+ * whenever creating a new instance of the function we both give that
+ * instance a singleton type and clone the underlying script.
+ */
+
+ uint32_t begin, end;
+ if (fun->hasScript()) {
+ if (!fun->nonLazyScript()->isLikelyConstructorWrapper())
+ return false;
+ begin = fun->nonLazyScript()->sourceStart();
+ end = fun->nonLazyScript()->sourceEnd();
+ } else {
+ if (!fun->lazyScript()->isLikelyConstructorWrapper())
+ return false;
+ begin = fun->lazyScript()->begin();
+ end = fun->lazyScript()->end();
+ }
+
+ return end - begin <= 100;
+}
+
+/* static */ bool
+ObjectGroup::useSingletonForNewObject(JSContext* cx, JSScript* script, jsbytecode* pc)
+{
+ /*
+ * Make a heuristic guess at a use of JSOP_NEW that the constructed object
+ * should have a fresh group. We do this when the NEW is immediately
+ * followed by a simple assignment to an object's .prototype field.
+ * This is designed to catch common patterns for subclassing in JS:
+ *
+ * function Super() { ... }
+ * function Sub1() { ... }
+ * function Sub2() { ... }
+ *
+ * Sub1.prototype = new Super();
+ * Sub2.prototype = new Super();
+ *
+ * Using distinct groups for the particular prototypes of Sub1 and
+ * Sub2 lets us continue to distinguish the two subclasses and any extra
+ * properties added to those prototype objects.
+ */
+ if (script->isGenerator())
+ return false;
+ if (JSOp(*pc) != JSOP_NEW)
+ return false;
+ pc += JSOP_NEW_LENGTH;
+ if (JSOp(*pc) == JSOP_SETPROP) {
+ if (script->getName(pc) == cx->names().prototype)
+ return true;
+ }
+ return false;
+}
+
+/* static */ bool
+ObjectGroup::useSingletonForAllocationSite(JSScript* script, jsbytecode* pc, JSProtoKey key)
+{
+ // The return value of this method can either be tested like a boolean or
+ // passed to a NewObject method.
+ JS_STATIC_ASSERT(GenericObject == 0);
+
+ /*
+ * Objects created outside loops in global and eval scripts should have
+ * singleton types. For now this is only done for plain objects, but not
+ * typed arrays or normal arrays.
+ */
+
+ if (script->functionNonDelazifying() && !script->treatAsRunOnce())
+ return GenericObject;
+
+ if (key != JSProto_Object)
+ return GenericObject;
+
+ // All loops in the script will have a try note indicating their boundary.
+
+ if (!script->hasTrynotes())
+ return SingletonObject;
+
+ unsigned offset = script->pcToOffset(pc);
+
+ JSTryNote* tn = script->trynotes()->vector;
+ JSTryNote* tnlimit = tn + script->trynotes()->length;
+ for (; tn < tnlimit; tn++) {
+ if (tn->kind != JSTRY_FOR_IN && tn->kind != JSTRY_FOR_OF && tn->kind != JSTRY_LOOP)
+ continue;
+
+ unsigned startOffset = script->mainOffset() + tn->start;
+ unsigned endOffset = startOffset + tn->length;
+
+ if (offset >= startOffset && offset < endOffset)
+ return GenericObject;
+ }
+
+ return SingletonObject;
+}
+
+/* static */ bool
+ObjectGroup::useSingletonForAllocationSite(JSScript* script, jsbytecode* pc, const Class* clasp)
+{
+ return useSingletonForAllocationSite(script, pc, JSCLASS_CACHED_PROTO_KEY(clasp));
+}
+
+/////////////////////////////////////////////////////////////////////
+// JSObject
+/////////////////////////////////////////////////////////////////////
+
+bool
+JSObject::shouldSplicePrototype(JSContext* cx)
+{
+ /*
+ * During bootstrapping, if inference is enabled we need to make sure not
+ * to splice a new prototype in for Function.prototype or the global
+ * object if their __proto__ had previously been set to null, as this
+ * will change the prototype for all other objects with the same type.
+ */
+ if (staticPrototype() != nullptr)
+ return false;
+ return isSingleton();
+}
+
+bool
+JSObject::splicePrototype(JSContext* cx, const Class* clasp, Handle<TaggedProto> proto)
+{
+ MOZ_ASSERT(cx->compartment() == compartment());
+
+ RootedObject self(cx, this);
+
+ /*
+ * For singleton groups representing only a single JSObject, the proto
+ * can be rearranged as needed without destroying type information for
+ * the old or new types.
+ */
+ MOZ_ASSERT(self->isSingleton());
+
+ // Windows may not appear on prototype chains.
+ MOZ_ASSERT_IF(proto.isObject(), !IsWindow(proto.toObject()));
+
+ if (proto.isObject() && !proto.toObject()->setDelegate(cx))
+ return false;
+
+ // Force type instantiation when splicing lazy group.
+ RootedObjectGroup group(cx, self->getGroup(cx));
+ if (!group)
+ return false;
+ RootedObjectGroup protoGroup(cx, nullptr);
+ if (proto.isObject()) {
+ protoGroup = proto.toObject()->getGroup(cx);
+ if (!protoGroup)
+ return false;
+ }
+
+ group->setClasp(clasp);
+ group->setProto(proto);
+ return true;
+}
+
+/* static */ ObjectGroup*
+JSObject::makeLazyGroup(JSContext* cx, HandleObject obj)
+{
+ MOZ_ASSERT(obj->hasLazyGroup());
+ MOZ_ASSERT(cx->compartment() == obj->compartment());
+
+ /* De-lazification of functions can GC, so we need to do it up here. */
+ 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_SINGLETON | OBJECT_FLAG_NON_PACKED;
+
+ if (obj->isIteratedSingleton())
+ initialFlags |= OBJECT_FLAG_ITERATED;
+
+ if (obj->isIndexed())
+ initialFlags |= OBJECT_FLAG_SPARSE_INDEXES;
+
+ if (obj->is<ArrayObject>() && obj->as<ArrayObject>().length() > INT32_MAX)
+ initialFlags |= OBJECT_FLAG_LENGTH_OVERFLOW;
+
+ Rooted<TaggedProto> proto(cx, obj->taggedProto());
+ ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, obj->getClass(), proto,
+ initialFlags);
+ if (!group)
+ return nullptr;
+
+ AutoEnterAnalysis enter(cx);
+
+ /* Fill in the type according to the state of this object. */
+
+ if (obj->is<JSFunction>() && obj->as<JSFunction>().isInterpreted())
+ group->setInterpretedFunction(&obj->as<JSFunction>());
+
+ obj->group_ = group;
+
+ return group;
+}
+
+/* static */ bool
+JSObject::setNewGroupUnknown(JSContext* cx, const js::Class* clasp, JS::HandleObject obj)
+{
+ ObjectGroup::setDefaultNewGroupUnknown(cx, clasp, obj);
+ return obj->setFlags(cx, BaseShape::NEW_GROUP_UNKNOWN);
+}
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroupCompartment NewTable
+/////////////////////////////////////////////////////////////////////
+
+/*
+ * Entries for the per-compartment set of groups which are the default
+ * types to use for some prototype. An optional associated object is used which
+ * allows multiple groups to be created with the same prototype. The
+ * associated object may be a function (for types constructed with 'new') or a
+ * type descriptor (for typed objects). These entries are also used for the set
+ * of lazy groups in the compartment, which use a null associated object
+ * (though there are only a few of these per compartment).
+ */
+struct ObjectGroupCompartment::NewEntry
+{
+ ReadBarrieredObjectGroup group;
+
+ // Note: This pointer is only used for equality and does not need a read barrier.
+ JSObject* associated;
+
+ NewEntry(ObjectGroup* group, JSObject* associated)
+ : group(group), associated(associated)
+ {}
+
+ struct Lookup {
+ const Class* clasp;
+ TaggedProto proto;
+ JSObject* associated;
+
+ Lookup(const Class* clasp, TaggedProto proto, JSObject* associated)
+ : clasp(clasp), proto(proto), associated(associated)
+ {}
+
+ bool hasAssocId() const {
+ return !associated || associated->zone()->hasUniqueId(associated);
+ }
+
+ bool ensureAssocId() const {
+ uint64_t unusedId;
+ return !associated ||
+ associated->zoneFromAnyThread()->getUniqueId(associated, &unusedId);
+ }
+
+ uint64_t getAssocId() const {
+ return associated ? associated->zone()->getUniqueIdInfallible(associated) : 0;
+ }
+ };
+
+ static bool hasHash(const Lookup& l) {
+ return l.proto.hasUniqueId() && l.hasAssocId();
+ }
+
+ static bool ensureHash(const Lookup& l) {
+ return l.proto.ensureUniqueId() && l.ensureAssocId();
+ }
+
+ static inline HashNumber hash(const Lookup& lookup) {
+ MOZ_ASSERT(lookup.proto.hasUniqueId());
+ MOZ_ASSERT(lookup.hasAssocId());
+ HashNumber hash = uintptr_t(lookup.clasp);
+ hash = mozilla::RotateLeft(hash, 4) ^ Zone::UniqueIdToHash(lookup.proto.uniqueId());
+ hash = mozilla::RotateLeft(hash, 4) ^ Zone::UniqueIdToHash(lookup.getAssocId());
+ return hash;
+ }
+
+ static inline bool match(const ObjectGroupCompartment::NewEntry& key, const Lookup& lookup) {
+ TaggedProto proto = key.group.unbarrieredGet()->proto().unbarrieredGet();
+ JSObject* assoc = key.associated;
+ MOZ_ASSERT(proto.hasUniqueId());
+ MOZ_ASSERT_IF(assoc, assoc->zone()->hasUniqueId(assoc));
+ MOZ_ASSERT(lookup.proto.hasUniqueId());
+ MOZ_ASSERT(lookup.hasAssocId());
+
+ if (lookup.clasp && key.group.unbarrieredGet()->clasp() != lookup.clasp)
+ return false;
+ if (proto.uniqueId() != lookup.proto.uniqueId())
+ return false;
+ return !assoc || assoc->zone()->getUniqueIdInfallible(assoc) == lookup.getAssocId();
+ }
+
+ static void rekey(NewEntry& k, const NewEntry& newKey) { k = newKey; }
+
+ bool needsSweep() {
+ return (IsAboutToBeFinalized(&group) ||
+ (associated && IsAboutToBeFinalizedUnbarriered(&associated)));
+ }
+};
+
+namespace js {
+template <>
+struct FallibleHashMethods<ObjectGroupCompartment::NewEntry>
+{
+ template <typename Lookup> static bool hasHash(Lookup&& l) {
+ return ObjectGroupCompartment::NewEntry::hasHash(mozilla::Forward<Lookup>(l));
+ }
+ template <typename Lookup> static bool ensureHash(Lookup&& l) {
+ return ObjectGroupCompartment::NewEntry::ensureHash(mozilla::Forward<Lookup>(l));
+ }
+};
+} // namespace js
+
+class ObjectGroupCompartment::NewTable : public JS::WeakCache<js::GCHashSet<NewEntry, NewEntry,
+ SystemAllocPolicy>>
+{
+ using Table = js::GCHashSet<NewEntry, NewEntry, SystemAllocPolicy>;
+ using Base = JS::WeakCache<Table>;
+
+ public:
+ explicit NewTable(Zone* zone) : Base(zone, Table()) {}
+};
+
+/* static */ ObjectGroup*
+ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp,
+ TaggedProto proto, JSObject* associated)
+{
+ MOZ_ASSERT_IF(associated, proto.isObject());
+ MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
+
+ // A null lookup clasp is used for 'new' groups with an associated
+ // function. The group starts out as a plain object but might mutate into an
+ // unboxed plain object.
+ MOZ_ASSERT_IF(!clasp, !!associated);
+
+ AutoEnterAnalysis enter(cx);
+
+ ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.defaultNewTable;
+
+ if (!table) {
+ table = cx->new_<ObjectGroupCompartment::NewTable>(cx->zone());
+ if (!table || !table->init()) {
+ js_delete(table);
+ table = nullptr;
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ }
+
+ if (associated && !associated->is<TypeDescr>()) {
+ MOZ_ASSERT(!clasp);
+ if (associated->is<JSFunction>()) {
+
+ // Canonicalize new functions to use the original one associated with its script.
+ JSFunction* fun = &associated->as<JSFunction>();
+ if (fun->hasScript())
+ associated = fun->nonLazyScript()->functionNonDelazifying();
+ else if (fun->isInterpretedLazy() && !fun->isSelfHostedBuiltin())
+ associated = fun->lazyScript()->functionNonDelazifying();
+ else
+ associated = nullptr;
+
+ // If we have previously cleared the 'new' script information for this
+ // function, don't try to construct another one.
+ if (associated && associated->wasNewScriptCleared())
+ associated = nullptr;
+
+ } else {
+ associated = nullptr;
+ }
+
+ if (!associated)
+ clasp = &PlainObject::class_;
+ }
+
+ if (proto.isObject() && !proto.toObject()->isDelegate()) {
+ RootedObject protoObj(cx, proto.toObject());
+ if (!protoObj->setDelegate(cx))
+ return nullptr;
+
+ // Objects which are prototypes of one another should be singletons, so
+ // that their type information can be tracked more precisely. Limit
+ // this group change to plain objects, to avoid issues with other types
+ // of singletons like typed arrays.
+ if (protoObj->is<PlainObject>() && !protoObj->isSingleton()) {
+ if (!JSObject::changeToSingleton(cx->asJSContext(), protoObj))
+ return nullptr;
+ }
+ }
+
+ ObjectGroupCompartment::NewTable::AddPtr p =
+ table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, associated));
+ if (p) {
+ ObjectGroup* group = p->group;
+ MOZ_ASSERT_IF(clasp, group->clasp() == clasp);
+ MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_ ||
+ group->clasp() == &UnboxedPlainObject::class_);
+ MOZ_ASSERT(group->proto() == proto);
+ return group;
+ }
+
+ ObjectGroupFlags initialFlags = 0;
+ if (proto.isDynamic() || (proto.isObject() && proto.toObject()->isNewGroupUnknown()))
+ initialFlags = OBJECT_FLAG_DYNAMIC_MASK;
+
+ Rooted<TaggedProto> protoRoot(cx, proto);
+ ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, clasp ? clasp : &PlainObject::class_,
+ protoRoot, initialFlags);
+ if (!group)
+ return nullptr;
+
+ if (!table->add(p, ObjectGroupCompartment::NewEntry(group, associated))) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ if (associated) {
+ if (associated->is<JSFunction>()) {
+ if (!TypeNewScript::make(cx->asJSContext(), group, &associated->as<JSFunction>()))
+ return nullptr;
+ } else {
+ group->setTypeDescr(&associated->as<TypeDescr>());
+ }
+ }
+
+ /*
+ * Some builtin objects have slotful native properties baked in at
+ * 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 (clasp == &RegExpObject::class_) {
+ AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type());
+ } else if (clasp == &StringObject::class_) {
+ AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type());
+ } else if (ErrorObject::isErrorClass((clasp))) {
+ 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)
+{
+ MOZ_ASSERT_IF(proto.isObject(), cx->compartment() == proto.toObject()->compartment());
+
+ ObjectGroupCompartment::NewTable*& table = cx->compartment()->objectGroups.lazyTable;
+
+ if (!table) {
+ table = cx->new_<ObjectGroupCompartment::NewTable>(cx->zone());
+ if (!table || !table->init()) {
+ ReportOutOfMemory(cx);
+ js_delete(table);
+ table = nullptr;
+ return nullptr;
+ }
+ }
+
+ ObjectGroupCompartment::NewTable::AddPtr p =
+ table->lookupForAdd(ObjectGroupCompartment::NewEntry::Lookup(clasp, proto, nullptr));
+ if (p) {
+ ObjectGroup* group = p->group;
+ MOZ_ASSERT(group->lazy());
+
+ return group;
+ }
+
+ AutoEnterAnalysis enter(cx);
+
+ Rooted<TaggedProto> protoRoot(cx, proto);
+ 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))) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ 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;
+ if (table) {
+ Rooted<TaggedProto> taggedProto(cx, TaggedProto(obj));
+ auto lookup = ObjectGroupCompartment::NewEntry::Lookup(clasp, taggedProto, nullptr);
+ auto p = table->lookup(lookup);
+ if (p)
+ MarkObjectGroupUnknownProperties(cx, p->group);
+ }
+}
+
+#ifdef DEBUG
+/* static */ bool
+ObjectGroup::hasDefaultNewGroup(JSObject* proto, const Class* clasp, ObjectGroup* group)
+{
+ ObjectGroupCompartment::NewTable* table = proto->compartment()->objectGroups.defaultNewTable;
+
+ if (table) {
+ auto lookup = ObjectGroupCompartment::NewEntry::Lookup(clasp, TaggedProto(proto), nullptr);
+ auto p = table->lookup(lookup);
+ return p && p->group == group;
+ }
+ return false;
+}
+#endif /* DEBUG */
+
+inline const Class*
+GetClassForProtoKey(JSProtoKey key)
+{
+ switch (key) {
+ case JSProto_Null:
+ case JSProto_Object:
+ return &PlainObject::class_;
+ case JSProto_Array:
+ return &ArrayObject::class_;
+
+ case JSProto_Number:
+ return &NumberObject::class_;
+ case JSProto_Boolean:
+ return &BooleanObject::class_;
+ case JSProto_String:
+ return &StringObject::class_;
+ case JSProto_Symbol:
+ return &SymbolObject::class_;
+ case JSProto_RegExp:
+ return &RegExpObject::class_;
+
+ case JSProto_Int8Array:
+ case JSProto_Uint8Array:
+ case JSProto_Int16Array:
+ case JSProto_Uint16Array:
+ case JSProto_Int32Array:
+ case JSProto_Uint32Array:
+ case JSProto_Float32Array:
+ case JSProto_Float64Array:
+ case JSProto_Uint8ClampedArray:
+ return &TypedArrayObject::classes[key - JSProto_Int8Array];
+
+ case JSProto_ArrayBuffer:
+ return &ArrayBufferObject::class_;
+
+ case JSProto_SharedArrayBuffer:
+ return &SharedArrayBufferObject::class_;
+
+ case JSProto_DataView:
+ return &DataViewObject::class_;
+
+ default:
+ MOZ_CRASH("Bad proto key");
+ }
+}
+
+/* static */ ObjectGroup*
+ObjectGroup::defaultNewGroup(JSContext* cx, JSProtoKey key)
+{
+ RootedObject proto(cx);
+ if (key != JSProto_Null && !GetBuiltinPrototype(cx, key, &proto))
+ return nullptr;
+ return defaultNewGroup(cx, GetClassForProtoKey(key), TaggedProto(proto.get()));
+}
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroupCompartment ArrayObjectTable
+/////////////////////////////////////////////////////////////////////
+
+struct ObjectGroupCompartment::ArrayObjectKey : public DefaultHasher<ArrayObjectKey>
+{
+ TypeSet::Type type;
+
+ ArrayObjectKey()
+ : type(TypeSet::UndefinedType())
+ {}
+
+ explicit ArrayObjectKey(TypeSet::Type type)
+ : type(type)
+ {}
+
+ static inline uint32_t hash(const ArrayObjectKey& v) {
+ return v.type.raw();
+ }
+
+ static inline bool match(const ArrayObjectKey& v1, const ArrayObjectKey& v2) {
+ return v1.type == v2.type;
+ }
+
+ bool operator==(const ArrayObjectKey& other) {
+ return type == other.type;
+ }
+
+ bool operator!=(const ArrayObjectKey& other) {
+ return !(*this == other);
+ }
+
+ bool needsSweep() {
+ MOZ_ASSERT(type.isUnknown() || !type.isSingleton());
+ if (!type.isUnknown() && type.isGroup()) {
+ ObjectGroup* group = type.groupNoBarrier();
+ if (IsAboutToBeFinalizedUnbarriered(&group))
+ return true;
+ if (group != type.groupNoBarrier())
+ type = TypeSet::ObjectType(group);
+ }
+ return false;
+ }
+};
+
+static inline bool
+NumberTypes(TypeSet::Type a, TypeSet::Type b)
+{
+ return (a.isPrimitive(JSVAL_TYPE_INT32) || a.isPrimitive(JSVAL_TYPE_DOUBLE))
+ && (b.isPrimitive(JSVAL_TYPE_INT32) || b.isPrimitive(JSVAL_TYPE_DOUBLE));
+}
+
+/*
+ * As for GetValueType, but requires object types to be non-singletons with
+ * their default prototype. These are the only values that should appear in
+ * arrays and objects whose type can be fixed.
+ */
+static inline TypeSet::Type
+GetValueTypeForTable(const Value& v)
+{
+ TypeSet::Type type = TypeSet::GetValueType(v);
+ MOZ_ASSERT(!type.isSingleton());
+ return type;
+}
+
+/* static */ JSObject*
+ObjectGroup::newArrayObject(ExclusiveContext* cx,
+ const Value* vp, size_t length,
+ NewObjectKind newKind, NewArrayKind arrayKind)
+{
+ MOZ_ASSERT(newKind != SingletonObject);
+
+ // If we are making a copy on write array, don't try to adjust the group as
+ // getOrFixupCopyOnWriteObject will do this before any objects are copied
+ // from this one.
+ if (arrayKind == NewArrayKind::CopyOnWrite) {
+ ArrayObject* obj = NewDenseCopiedArray(cx, length, vp, nullptr, newKind);
+ if (!obj || !ObjectElements::MakeElementsCopyOnWrite(cx, obj))
+ return nullptr;
+ return obj;
+ }
+
+ // Get a type which captures all the elements in the array to be created.
+ Rooted<TypeSet::Type> elementType(cx, TypeSet::UnknownType());
+ if (arrayKind != NewArrayKind::UnknownIndex && length != 0) {
+ elementType = GetValueTypeForTable(vp[0]);
+ for (unsigned i = 1; i < length; i++) {
+ TypeSet::Type ntype = GetValueTypeForTable(vp[i]);
+ if (ntype != elementType) {
+ if (NumberTypes(elementType, ntype)) {
+ elementType = TypeSet::DoubleType();
+ } else {
+ elementType = TypeSet::UnknownType();
+ break;
+ }
+ }
+ }
+ }
+
+ ObjectGroupCompartment::ArrayObjectTable*& table =
+ cx->compartment()->objectGroups.arrayObjectTable;
+
+ if (!table) {
+ table = cx->new_<ObjectGroupCompartment::ArrayObjectTable>();
+ if (!table || !table->init()) {
+ ReportOutOfMemory(cx);
+ js_delete(table);
+ table = nullptr;
+ return nullptr;
+ }
+ }
+
+ ObjectGroupCompartment::ArrayObjectKey key(elementType);
+ DependentAddPtr<ObjectGroupCompartment::ArrayObjectTable> p(cx, *table, key);
+
+ RootedObjectGroup group(cx);
+ if (p) {
+ group = p->value();
+ } else {
+ RootedObject proto(cx);
+ if (!GetBuiltinPrototype(cx, JSProto_Array, &proto))
+ return nullptr;
+ Rooted<TaggedProto> taggedProto(cx, TaggedProto(proto));
+ group = ObjectGroupCompartment::makeGroup(cx, &ArrayObject::class_, taggedProto);
+ if (!group)
+ return nullptr;
+
+ AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType);
+
+ if (elementType != TypeSet::UnknownType()) {
+ // Keep track of the initial objects we create with this type.
+ // If the initial ones have a consistent shape and property types, we
+ // will try to use an unboxed layout for the group.
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+ if (!preliminaryObjects)
+ return nullptr;
+ group->setPreliminaryObjects(preliminaryObjects);
+ }
+
+ if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group))
+ return nullptr;
+ }
+
+ // The type of the elements being added will already be reflected in type
+ // information, but make sure when creating an unboxed array that the
+ // common element type is suitable for the unboxed representation.
+ ShouldUpdateTypes updateTypes = ShouldUpdateTypes::DontUpdate;
+ if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length))
+ return nullptr;
+ if (group->maybePreliminaryObjects())
+ group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+ if (group->maybeUnboxedLayout()) {
+ switch (group->unboxedLayout().elementType()) {
+ case JSVAL_TYPE_BOOLEAN:
+ if (elementType != TypeSet::BooleanType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_INT32:
+ if (elementType != TypeSet::Int32Type())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_DOUBLE:
+ if (elementType != TypeSet::Int32Type() && elementType != TypeSet::DoubleType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_STRING:
+ if (elementType != TypeSet::StringType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_OBJECT:
+ if (elementType != TypeSet::NullType() && !elementType.get().isObjectUnchecked())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ }
+
+ return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes);
+}
+
+// Try to change the group of |source| to match that of |target|.
+static bool
+GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target)
+{
+ MOZ_ASSERT(source->group() != target->group());
+
+ if (!target->is<ArrayObject>() && !target->is<UnboxedArrayObject>())
+ return true;
+
+ if (target->group()->maybePreliminaryObjects()) {
+ bool force = IsInsideNursery(source);
+ target->group()->maybePreliminaryObjects()->maybeAnalyze(cx, target->group(), force);
+ }
+
+ if (target->is<ArrayObject>()) {
+ ObjectGroup* sourceGroup = source->group();
+
+ if (source->is<UnboxedArrayObject>()) {
+ Shape* shape = target->as<ArrayObject>().lastProperty();
+ if (!UnboxedArrayObject::convertToNativeWithGroup(cx, source, target->group(), shape))
+ return false;
+ } else if (source->is<ArrayObject>()) {
+ source->setGroup(target->group());
+ } else {
+ return true;
+ }
+
+ if (sourceGroup->maybePreliminaryObjects())
+ sourceGroup->maybePreliminaryObjects()->unregisterObject(source);
+ if (target->group()->maybePreliminaryObjects())
+ target->group()->maybePreliminaryObjects()->registerNewObject(source);
+
+ for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
+ Value v = source->as<ArrayObject>().getDenseElement(i);
+ AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
+ }
+
+ return true;
+ }
+
+ if (target->is<UnboxedArrayObject>()) {
+ if (!source->is<UnboxedArrayObject>())
+ return true;
+ if (source->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_INT32)
+ return true;
+ if (target->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_DOUBLE)
+ return true;
+
+ return source->as<UnboxedArrayObject>().convertInt32ToDouble(cx, target->group());
+ }
+
+ return true;
+}
+
+static bool
+SameGroup(JSObject* first, JSObject* second)
+{
+ return first->group() == second->group();
+}
+
+// When generating a multidimensional array of literals, such as
+// [[1,2],[3,4],[5.5,6.5]], try to ensure that each element of the array has
+// the same group. This is mainly important when the elements might have
+// different native vs. unboxed layouts, or different unboxed layouts, and
+// accessing the heterogenous layouts from JIT code will be much slower than
+// if they were homogenous.
+//
+// To do this, with each new array element we compare it with one of the
+// previous ones, and try to mutate the group of the new element to fit that
+// of the old element. If this isn't possible, the groups for all old elements
+// are mutated to fit that of the new element.
+bool
+js::CombineArrayElementTypes(ExclusiveContext* cx, JSObject* newObj,
+ const Value* compare, size_t ncompare)
+{
+ if (!ncompare || !compare[0].isObject())
+ return true;
+
+ JSObject* oldObj = &compare[0].toObject();
+ if (SameGroup(oldObj, newObj))
+ return true;
+
+ if (!GiveObjectGroup(cx, newObj, oldObj))
+ return false;
+
+ if (SameGroup(oldObj, newObj))
+ return true;
+
+ if (!GiveObjectGroup(cx, oldObj, newObj))
+ return false;
+
+ if (SameGroup(oldObj, newObj)) {
+ for (size_t i = 1; i < ncompare; i++) {
+ if (compare[i].isObject() && !SameGroup(&compare[i].toObject(), newObj)) {
+ if (!GiveObjectGroup(cx, &compare[i].toObject(), newObj))
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+// Similarly to CombineArrayElementTypes, if we are generating an array of
+// plain objects with a consistent property layout, such as
+// [{p:[1,2]},{p:[3,4]},{p:[5.5,6.5]}], where those plain objects in
+// turn have arrays as their own properties, try to ensure that a consistent
+// group is given to each array held by the same property of the plain objects.
+bool
+js::CombinePlainObjectPropertyTypes(ExclusiveContext* cx, JSObject* newObj,
+ const Value* compare, size_t ncompare)
+{
+ if (!ncompare || !compare[0].isObject())
+ return true;
+
+ JSObject* oldObj = &compare[0].toObject();
+ if (!SameGroup(oldObj, newObj))
+ return true;
+
+ if (newObj->is<PlainObject>()) {
+ if (newObj->as<PlainObject>().lastProperty() != oldObj->as<PlainObject>().lastProperty())
+ return true;
+
+ for (size_t slot = 0; slot < newObj->as<PlainObject>().slotSpan(); slot++) {
+ Value newValue = newObj->as<PlainObject>().getSlot(slot);
+ Value oldValue = oldObj->as<PlainObject>().getSlot(slot);
+
+ if (!newValue.isObject() || !oldValue.isObject())
+ continue;
+
+ JSObject* newInnerObj = &newValue.toObject();
+ JSObject* oldInnerObj = &oldValue.toObject();
+
+ if (SameGroup(oldInnerObj, newInnerObj))
+ continue;
+
+ if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj))
+ return false;
+
+ if (SameGroup(oldInnerObj, newInnerObj))
+ continue;
+
+ if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj))
+ return false;
+
+ if (SameGroup(oldInnerObj, newInnerObj)) {
+ for (size_t i = 1; i < ncompare; i++) {
+ if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) {
+ Value otherValue = compare[i].toObject().as<PlainObject>().getSlot(slot);
+ if (otherValue.isObject() && !SameGroup(&otherValue.toObject(), newInnerObj)) {
+ if (!GiveObjectGroup(cx, &otherValue.toObject(), newInnerObj))
+ return false;
+ }
+ }
+ }
+ }
+ }
+ } else if (newObj->is<UnboxedPlainObject>()) {
+ const UnboxedLayout& layout = newObj->as<UnboxedPlainObject>().layout();
+ const int32_t* traceList = layout.traceList();
+ if (!traceList)
+ return true;
+
+ uint8_t* newData = newObj->as<UnboxedPlainObject>().data();
+ uint8_t* oldData = oldObj->as<UnboxedPlainObject>().data();
+
+ for (; *traceList != -1; traceList++) {}
+ traceList++;
+ for (; *traceList != -1; traceList++) {
+ JSObject* newInnerObj = *reinterpret_cast<JSObject**>(newData + *traceList);
+ JSObject* oldInnerObj = *reinterpret_cast<JSObject**>(oldData + *traceList);
+
+ if (!newInnerObj || !oldInnerObj || SameGroup(oldInnerObj, newInnerObj))
+ continue;
+
+ if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj))
+ return false;
+
+ if (SameGroup(oldInnerObj, newInnerObj))
+ continue;
+
+ if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj))
+ return false;
+
+ if (SameGroup(oldInnerObj, newInnerObj)) {
+ for (size_t i = 1; i < ncompare; i++) {
+ if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) {
+ uint8_t* otherData = compare[i].toObject().as<UnboxedPlainObject>().data();
+ JSObject* otherInnerObj = *reinterpret_cast<JSObject**>(otherData + *traceList);
+ if (otherInnerObj && !SameGroup(otherInnerObj, newInnerObj)) {
+ if (!GiveObjectGroup(cx, otherInnerObj, newInnerObj))
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroupCompartment PlainObjectTable
+/////////////////////////////////////////////////////////////////////
+
+struct ObjectGroupCompartment::PlainObjectKey
+{
+ jsid* properties;
+ uint32_t nproperties;
+
+ struct Lookup {
+ IdValuePair* properties;
+ uint32_t nproperties;
+
+ Lookup(IdValuePair* properties, uint32_t nproperties)
+ : properties(properties), nproperties(nproperties)
+ {}
+ };
+
+ static inline HashNumber hash(const Lookup& lookup) {
+ return (HashNumber) (HashId(lookup.properties[lookup.nproperties - 1].id) ^
+ lookup.nproperties);
+ }
+
+ static inline bool match(const PlainObjectKey& v, const Lookup& lookup) {
+ if (lookup.nproperties != v.nproperties)
+ return false;
+ for (size_t i = 0; i < lookup.nproperties; i++) {
+ if (lookup.properties[i].id != v.properties[i])
+ return false;
+ }
+ return true;
+ }
+
+ bool needsSweep() {
+ for (unsigned i = 0; i < nproperties; i++) {
+ if (gc::IsAboutToBeFinalizedUnbarriered(&properties[i]))
+ return true;
+ }
+ return false;
+ }
+};
+
+struct ObjectGroupCompartment::PlainObjectEntry
+{
+ ReadBarrieredObjectGroup group;
+ ReadBarrieredShape shape;
+ TypeSet::Type* types;
+
+ bool needsSweep(unsigned nproperties) {
+ if (IsAboutToBeFinalized(&group))
+ return true;
+ if (IsAboutToBeFinalized(&shape))
+ return true;
+ for (unsigned i = 0; i < nproperties; i++) {
+ MOZ_ASSERT(!types[i].isSingleton());
+ if (types[i].isGroup()) {
+ ObjectGroup* group = types[i].groupNoBarrier();
+ if (IsAboutToBeFinalizedUnbarriered(&group))
+ return true;
+ if (group != types[i].groupNoBarrier())
+ types[i] = TypeSet::ObjectType(group);
+ }
+ }
+ return false;
+ }
+};
+
+static bool
+CanShareObjectGroup(IdValuePair* properties, size_t nproperties)
+{
+ // Don't reuse groups for objects containing indexed properties, which
+ // might end up as dense elements.
+ for (size_t i = 0; i < nproperties; i++) {
+ uint32_t index;
+ if (IdIsIndex(properties[i].id, &index))
+ return false;
+ }
+ return true;
+}
+
+static bool
+AddPlainObjectProperties(ExclusiveContext* cx, HandlePlainObject obj,
+ IdValuePair* properties, size_t nproperties)
+{
+ RootedId propid(cx);
+ RootedValue value(cx);
+
+ for (size_t i = 0; i < nproperties; i++) {
+ propid = properties[i].id;
+ value = properties[i].value;
+ if (!NativeDefineProperty(cx, obj, propid, value, nullptr, nullptr, JSPROP_ENUMERATE))
+ return false;
+ }
+
+ return true;
+}
+
+PlainObject*
+js::NewPlainObjectWithProperties(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties,
+ NewObjectKind newKind)
+{
+ gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
+ RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, newKind));
+ if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties))
+ return nullptr;
+ return obj;
+}
+
+/* static */ JSObject*
+ObjectGroup::newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties,
+ NewObjectKind newKind)
+{
+ // Watch for simple cases where we don't try to reuse plain object groups.
+ if (newKind == SingletonObject || nproperties == 0 || nproperties >= PropertyTree::MAX_HEIGHT)
+ return NewPlainObjectWithProperties(cx, properties, nproperties, newKind);
+
+ ObjectGroupCompartment::PlainObjectTable*& table =
+ cx->compartment()->objectGroups.plainObjectTable;
+
+ if (!table) {
+ table = cx->new_<ObjectGroupCompartment::PlainObjectTable>();
+ if (!table || !table->init()) {
+ ReportOutOfMemory(cx);
+ js_delete(table);
+ table = nullptr;
+ return nullptr;
+ }
+ }
+
+ ObjectGroupCompartment::PlainObjectKey::Lookup lookup(properties, nproperties);
+ ObjectGroupCompartment::PlainObjectTable::Ptr p = table->lookup(lookup);
+
+ if (!p) {
+ if (!CanShareObjectGroup(properties, nproperties))
+ return NewPlainObjectWithProperties(cx, properties, nproperties, newKind);
+
+ RootedObject proto(cx);
+ if (!GetBuiltinPrototype(cx, JSProto_Object, &proto))
+ return nullptr;
+
+ Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
+ RootedObjectGroup group(cx, ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_,
+ tagged));
+ if (!group)
+ return nullptr;
+
+ gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
+ RootedPlainObject obj(cx, NewObjectWithGroup<PlainObject>(cx, group,
+ allocKind, TenuredObject));
+ if (!obj || !AddPlainObjectProperties(cx, obj, properties, nproperties))
+ return nullptr;
+
+ // Don't make entries with duplicate property names, which will show up
+ // here as objects with fewer properties than we thought we were
+ // adding to the object. In this case, reset the object's group to the
+ // default (which will have unknown properties) so that the group we
+ // just created will be collected by the GC.
+ if (obj->slotSpan() != nproperties) {
+ ObjectGroup* group = defaultNewGroup(cx, obj->getClass(), obj->taggedProto());
+ if (!group)
+ return nullptr;
+ obj->setGroup(group);
+ return obj;
+ }
+
+ // Keep track of the initial objects we create with this type.
+ // If the initial ones have a consistent shape and property types, we
+ // will try to use an unboxed layout for the group.
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(obj->lastProperty());
+ if (!preliminaryObjects)
+ return nullptr;
+ group->setPreliminaryObjects(preliminaryObjects);
+ preliminaryObjects->registerNewObject(obj);
+
+ ScopedJSFreePtr<jsid> ids(group->zone()->pod_calloc<jsid>(nproperties));
+ if (!ids) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ ScopedJSFreePtr<TypeSet::Type> types(
+ group->zone()->pod_calloc<TypeSet::Type>(nproperties));
+ if (!types) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ for (size_t i = 0; i < nproperties; i++) {
+ ids[i] = properties[i].id;
+ types[i] = GetValueTypeForTable(obj->getSlot(i));
+ AddTypePropertyId(cx, group, nullptr, IdToTypeId(ids[i]), types[i]);
+ }
+
+ ObjectGroupCompartment::PlainObjectKey key;
+ key.properties = ids;
+ key.nproperties = nproperties;
+ MOZ_ASSERT(ObjectGroupCompartment::PlainObjectKey::match(key, lookup));
+
+ ObjectGroupCompartment::PlainObjectEntry entry;
+ entry.group.set(group);
+ entry.shape.set(obj->lastProperty());
+ entry.types = types;
+
+ ObjectGroupCompartment::PlainObjectTable::AddPtr np = table->lookupForAdd(lookup);
+ if (!table->add(np, key, entry)) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ ids.forget();
+ types.forget();
+
+ return obj;
+ }
+
+ RootedObjectGroup group(cx, p->value().group);
+
+ // Watch for existing groups which now use an unboxed layout.
+ if (group->maybeUnboxedLayout()) {
+ MOZ_ASSERT(group->unboxedLayout().properties().length() == nproperties);
+ return UnboxedPlainObject::createWithProperties(cx, group, newKind, properties);
+ }
+
+ // Update property types according to the properties we are about to add.
+ // Do this before we do anything which can GC, which might move or remove
+ // this table entry.
+ if (!group->unknownProperties()) {
+ for (size_t i = 0; i < nproperties; i++) {
+ TypeSet::Type type = p->value().types[i];
+ TypeSet::Type ntype = GetValueTypeForTable(properties[i].value);
+ if (ntype == type)
+ continue;
+ if (ntype.isPrimitive(JSVAL_TYPE_INT32) &&
+ type.isPrimitive(JSVAL_TYPE_DOUBLE))
+ {
+ // 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.
+ p->value().types[i] = TypeSet::DoubleType();
+ }
+ AddTypePropertyId(cx, group, nullptr, IdToTypeId(properties[i].id), ntype);
+ }
+ }
+ }
+
+ RootedShape shape(cx, p->value().shape);
+
+ if (group->maybePreliminaryObjects())
+ newKind = TenuredObject;
+
+ gc::AllocKind allocKind = gc::GetGCObjectKind(nproperties);
+ RootedPlainObject obj(cx, NewObjectWithGroup<PlainObject>(cx, group, allocKind,
+ newKind));
+
+ if (!obj || !obj->setLastProperty(cx, shape))
+ return nullptr;
+
+ for (size_t i = 0; i < nproperties; i++)
+ obj->setSlot(i, properties[i].value);
+
+ if (group->maybePreliminaryObjects()) {
+ group->maybePreliminaryObjects()->registerNewObject(obj);
+ group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+ }
+
+ return obj;
+}
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroupCompartment AllocationSiteTable
+/////////////////////////////////////////////////////////////////////
+
+struct ObjectGroupCompartment::AllocationSiteKey : public DefaultHasher<AllocationSiteKey> {
+ ReadBarrieredScript script;
+
+ uint32_t offset : 24;
+ JSProtoKey kind : 8;
+
+ ReadBarrieredObject proto;
+
+ static const uint32_t OFFSET_LIMIT = (1 << 23);
+
+ AllocationSiteKey(JSScript* script_, uint32_t offset_, JSProtoKey kind_, JSObject* proto_)
+ : script(script_), offset(offset_), kind(kind_), proto(proto_)
+ {
+ MOZ_ASSERT(offset_ < OFFSET_LIMIT);
+ }
+
+ AllocationSiteKey(const AllocationSiteKey& key)
+ : script(key.script),
+ offset(key.offset),
+ kind(key.kind),
+ proto(key.proto)
+ { }
+
+ AllocationSiteKey(AllocationSiteKey&& key)
+ : script(mozilla::Move(key.script)),
+ offset(key.offset),
+ kind(key.kind),
+ proto(mozilla::Move(key.proto))
+ { }
+
+ void operator=(AllocationSiteKey&& key) {
+ script = mozilla::Move(key.script);
+ offset = key.offset;
+ kind = key.kind;
+ proto = mozilla::Move(key.proto);
+ }
+
+ static inline uint32_t hash(AllocationSiteKey key) {
+ return uint32_t(size_t(key.script->offsetToPC(key.offset)) ^ key.kind ^
+ MovableCellHasher<JSObject*>::hash(key.proto));
+ }
+
+ static inline bool match(const AllocationSiteKey& a, const AllocationSiteKey& b) {
+ return DefaultHasher<JSScript*>::match(a.script, b.script) &&
+ a.offset == b.offset &&
+ a.kind == b.kind &&
+ MovableCellHasher<JSObject*>::match(a.proto, b.proto);
+ }
+
+ void trace(JSTracer* trc) {
+ TraceRoot(trc, &script, "AllocationSiteKey script");
+ TraceNullableRoot(trc, &proto, "AllocationSiteKey proto");
+ }
+
+ bool needsSweep() {
+ return IsAboutToBeFinalizedUnbarriered(script.unsafeGet()) ||
+ (proto && IsAboutToBeFinalizedUnbarriered(proto.unsafeGet()));
+ }
+};
+
+class ObjectGroupCompartment::AllocationSiteTable
+ : public JS::WeakCache<js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup,
+ AllocationSiteKey, SystemAllocPolicy>>
+{
+ using Table = js::GCHashMap<AllocationSiteKey, ReadBarrieredObjectGroup,
+ AllocationSiteKey, SystemAllocPolicy>;
+ using Base = JS::WeakCache<Table>;
+
+ public:
+ explicit AllocationSiteTable(Zone* zone) : Base(zone, Table()) {}
+};
+
+/* static */ ObjectGroup*
+ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode* pc,
+ JSProtoKey kind, HandleObject protoArg /* = nullptr */)
+{
+ MOZ_ASSERT(!useSingletonForAllocationSite(scriptArg, pc, kind));
+ MOZ_ASSERT_IF(protoArg, kind == JSProto_Array);
+
+ uint32_t offset = scriptArg->pcToOffset(pc);
+
+ if (offset >= ObjectGroupCompartment::AllocationSiteKey::OFFSET_LIMIT) {
+ if (protoArg)
+ return defaultNewGroup(cx, GetClassForProtoKey(kind), TaggedProto(protoArg));
+ return defaultNewGroup(cx, kind);
+ }
+
+ ObjectGroupCompartment::AllocationSiteTable*& table =
+ cx->compartment()->objectGroups.allocationSiteTable;
+
+ if (!table) {
+ table = cx->new_<ObjectGroupCompartment::AllocationSiteTable>(cx->zone());
+ if (!table || !table->init()) {
+ ReportOutOfMemory(cx);
+ js_delete(table);
+ table = nullptr;
+ return nullptr;
+ }
+ }
+
+ RootedScript script(cx, scriptArg);
+ RootedObject proto(cx, protoArg);
+ if (!proto && kind != JSProto_Null && !GetBuiltinPrototype(cx, kind, &proto))
+ return nullptr;
+
+ Rooted<ObjectGroupCompartment::AllocationSiteKey> key(cx,
+ ObjectGroupCompartment::AllocationSiteKey(script, offset, kind, proto));
+
+ ObjectGroupCompartment::AllocationSiteTable::AddPtr p = table->lookupForAdd(key);
+ if (p)
+ return p->value();
+
+ AutoEnterAnalysis enter(cx);
+
+ Rooted<TaggedProto> tagged(cx, TaggedProto(proto));
+ ObjectGroup* res = ObjectGroupCompartment::makeGroup(cx, GetClassForProtoKey(kind), tagged,
+ OBJECT_FLAG_FROM_ALLOCATION_SITE);
+ if (!res)
+ return nullptr;
+
+ if (JSOp(*pc) == JSOP_NEWOBJECT) {
+ // Keep track of the preliminary objects with this group, so we can try
+ // to use an unboxed layout for the object once some are allocated.
+ Shape* shape = script->getObject(pc)->as<PlainObject>().lastProperty();
+ if (!shape->isEmptyShape()) {
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(shape);
+ if (preliminaryObjects)
+ res->setPreliminaryObjects(preliminaryObjects);
+ else
+ cx->recoverFromOutOfMemory();
+ }
+ }
+
+ if (kind == JSProto_Array &&
+ (JSOp(*pc) == JSOP_NEWARRAY || IsCallPC(pc)) &&
+ cx->options().unboxedArrays())
+ {
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+ if (preliminaryObjects)
+ res->setPreliminaryObjects(preliminaryObjects);
+ else
+ cx->recoverFromOutOfMemory();
+ }
+
+ if (!table->add(p, key, res)) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+
+ return res;
+}
+
+void
+ObjectGroupCompartment::replaceAllocationSiteGroup(JSScript* script, jsbytecode* pc,
+ JSProtoKey kind, ObjectGroup* group)
+{
+ AllocationSiteKey key(script, script->pcToOffset(pc), kind, group->proto().toObjectOrNull());
+
+ AllocationSiteTable::Ptr p = allocationSiteTable->lookup(key);
+ MOZ_RELEASE_ASSERT(p);
+ allocationSiteTable->get().remove(p);
+ {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!allocationSiteTable->putNew(key, group))
+ oomUnsafe.crash("Inconsistent object table");
+ }
+}
+
+/* static */ ObjectGroup*
+ObjectGroup::callingAllocationSiteGroup(JSContext* cx, JSProtoKey key, HandleObject proto)
+{
+ MOZ_ASSERT_IF(proto, key == JSProto_Array);
+
+ jsbytecode* pc;
+ RootedScript script(cx, cx->currentScript(&pc));
+ if (script)
+ return allocationSiteGroup(cx, script, pc, key, proto);
+ if (proto)
+ return defaultNewGroup(cx, GetClassForProtoKey(key), TaggedProto(proto));
+ return defaultNewGroup(cx, key);
+}
+
+/* static */ bool
+ObjectGroup::setAllocationSiteObjectGroup(JSContext* cx,
+ HandleScript script, jsbytecode* pc,
+ HandleObject obj, bool singleton)
+{
+ JSProtoKey key = JSCLASS_CACHED_PROTO_KEY(obj->getClass());
+ MOZ_ASSERT(key != JSProto_Null);
+ MOZ_ASSERT(singleton == useSingletonForAllocationSite(script, pc, key));
+
+ if (singleton) {
+ MOZ_ASSERT(obj->isSingleton());
+
+ /*
+ * Inference does not account for types of run-once initializer
+ * objects, as these may not be created until after the script
+ * has been analyzed.
+ */
+ TypeScript::Monitor(cx, script, pc, ObjectValue(*obj));
+ } else {
+ ObjectGroup* group = allocationSiteGroup(cx, script, pc, key);
+ if (!group)
+ return false;
+ obj->setGroup(group);
+ }
+
+ return true;
+}
+
+/* static */ ArrayObject*
+ObjectGroup::getOrFixupCopyOnWriteObject(JSContext* cx, HandleScript script, jsbytecode* pc)
+{
+ // Make sure that the template object for script/pc has a type indicating
+ // that the object and its copies have copy on write elements.
+ RootedArrayObject obj(cx, &script->getObject(GET_UINT32_INDEX(pc))->as<ArrayObject>());
+ MOZ_ASSERT(obj->denseElementsAreCopyOnWrite());
+
+ if (obj->group()->fromAllocationSite()) {
+ MOZ_ASSERT(obj->group()->hasAnyFlags(OBJECT_FLAG_COPY_ON_WRITE));
+ return obj;
+ }
+
+ RootedObjectGroup group(cx, allocationSiteGroup(cx, script, pc, JSProto_Array));
+ if (!group)
+ 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, nullptr, JSID_VOID, v);
+ }
+
+ obj->setGroup(group);
+ return obj;
+}
+
+/* static */ ArrayObject*
+ObjectGroup::getCopyOnWriteObject(JSScript* script, jsbytecode* pc)
+{
+ // getOrFixupCopyOnWriteObject should already have been called for
+ // script/pc, ensuring that the template object has a group with the
+ // COPY_ON_WRITE flag. We don't assert this here, due to a corner case
+ // where this property doesn't hold. See jsop_newarray_copyonwrite in
+ // IonBuilder.
+ ArrayObject* obj = &script->getObject(GET_UINT32_INDEX(pc))->as<ArrayObject>();
+ MOZ_ASSERT(obj->denseElementsAreCopyOnWrite());
+
+ return obj;
+}
+
+/* static */ bool
+ObjectGroup::findAllocationSite(JSContext* cx, ObjectGroup* group,
+ JSScript** script, uint32_t* offset)
+{
+ *script = nullptr;
+ *offset = 0;
+
+ const ObjectGroupCompartment::AllocationSiteTable* table =
+ cx->compartment()->objectGroups.allocationSiteTable;
+
+ if (!table)
+ return false;
+
+ for (ObjectGroupCompartment::AllocationSiteTable::Range r = table->all();
+ !r.empty();
+ r.popFront())
+ {
+ if (group == r.front().value()) {
+ *script = r.front().key().script;
+ *offset = r.front().key().offset;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////
+// ObjectGroupCompartment
+/////////////////////////////////////////////////////////////////////
+
+ObjectGroupCompartment::ObjectGroupCompartment()
+{
+ PodZero(this);
+}
+
+ObjectGroupCompartment::~ObjectGroupCompartment()
+{
+ js_delete(defaultNewTable);
+ js_delete(lazyTable);
+ js_delete(arrayObjectTable);
+ js_delete(plainObjectTable);
+ js_delete(allocationSiteTable);
+}
+
+void
+ObjectGroupCompartment::removeDefaultNewGroup(const Class* clasp, TaggedProto proto,
+ JSObject* associated)
+{
+ auto p = defaultNewTable->lookup(NewEntry::Lookup(clasp, proto, associated));
+ MOZ_RELEASE_ASSERT(p);
+
+ defaultNewTable->get().remove(p);
+}
+
+void
+ObjectGroupCompartment::replaceDefaultNewGroup(const Class* clasp, TaggedProto proto,
+ JSObject* associated, ObjectGroup* group)
+{
+ NewEntry::Lookup lookup(clasp, proto, associated);
+
+ auto p = defaultNewTable->lookup(lookup);
+ MOZ_RELEASE_ASSERT(p);
+ defaultNewTable->get().remove(p);
+ {
+ AutoEnterOOMUnsafeRegion oomUnsafe;
+ if (!defaultNewTable->putNew(lookup, NewEntry(group, associated)))
+ oomUnsafe.crash("Inconsistent object table");
+ }
+}
+
+/* static */
+ObjectGroup*
+ObjectGroupCompartment::makeGroup(ExclusiveContext* cx, const Class* clasp,
+ Handle<TaggedProto> proto,
+ ObjectGroupFlags initialFlags /* = 0 */)
+{
+ MOZ_ASSERT_IF(proto.isObject(), cx->isInsideCurrentCompartment(proto.toObject()));
+
+ ObjectGroup* group = Allocate<ObjectGroup>(cx);
+ if (!group)
+ return nullptr;
+ new(group) ObjectGroup(clasp, proto, cx->compartment(), initialFlags);
+
+ return group;
+}
+
+void
+ObjectGroupCompartment::addSizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf,
+ size_t* allocationSiteTables,
+ size_t* arrayObjectGroupTables,
+ size_t* plainObjectGroupTables,
+ size_t* compartmentTables)
+{
+ if (allocationSiteTable)
+ *allocationSiteTables += allocationSiteTable->sizeOfIncludingThis(mallocSizeOf);
+
+ if (arrayObjectTable)
+ *arrayObjectGroupTables += arrayObjectTable->sizeOfIncludingThis(mallocSizeOf);
+
+ if (plainObjectTable) {
+ *plainObjectGroupTables += plainObjectTable->sizeOfIncludingThis(mallocSizeOf);
+
+ for (PlainObjectTable::Enum e(*plainObjectTable);
+ !e.empty();
+ e.popFront())
+ {
+ const PlainObjectKey& key = e.front().key();
+ const PlainObjectEntry& value = e.front().value();
+
+ /* key.ids and values.types have the same length. */
+ *plainObjectGroupTables += mallocSizeOf(key.properties) + mallocSizeOf(value.types);
+ }
+ }
+
+ if (defaultNewTable)
+ *compartmentTables += defaultNewTable->sizeOfIncludingThis(mallocSizeOf);
+
+ if (lazyTable)
+ *compartmentTables += lazyTable->sizeOfIncludingThis(mallocSizeOf);
+}
+
+void
+ObjectGroupCompartment::clearTables()
+{
+ if (allocationSiteTable && allocationSiteTable->initialized())
+ allocationSiteTable->clear();
+ if (arrayObjectTable && arrayObjectTable->initialized())
+ arrayObjectTable->clear();
+ if (plainObjectTable && plainObjectTable->initialized()) {
+ for (PlainObjectTable::Enum e(*plainObjectTable); !e.empty(); e.popFront()) {
+ const PlainObjectKey& key = e.front().key();
+ PlainObjectEntry& entry = e.front().value();
+ js_free(key.properties);
+ js_free(entry.types);
+ }
+ plainObjectTable->clear();
+ }
+ if (defaultNewTable && defaultNewTable->initialized())
+ defaultNewTable->clear();
+ if (lazyTable && lazyTable->initialized())
+ lazyTable->clear();
+}
+
+/* static */ bool
+ObjectGroupCompartment::PlainObjectTableSweepPolicy::needsSweep(PlainObjectKey* key,
+ PlainObjectEntry* entry)
+{
+ if (!(JS::GCPolicy<PlainObjectKey>::needsSweep(key) || entry->needsSweep(key->nproperties)))
+ return false;
+ js_free(key->properties);
+ js_free(entry->types);
+ return true;
+}
+
+void
+ObjectGroupCompartment::sweep(FreeOp* fop)
+{
+ /*
+ * Iterate through the array/object group tables and remove all entries
+ * referencing collected data. These tables only hold weak references.
+ */
+
+ if (arrayObjectTable)
+ arrayObjectTable->sweep();
+ if (plainObjectTable)
+ plainObjectTable->sweep();
+}
+
+void
+ObjectGroupCompartment::fixupNewTableAfterMovingGC(NewTable* table)
+{
+ /*
+ * Each entry's hash depends on the object's prototype and we can't tell
+ * whether that has been moved or not in sweepNewObjectGroupTable().
+ */
+ if (table && table->initialized()) {
+ for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
+ NewEntry& entry = e.mutableFront();
+
+ ObjectGroup* group = entry.group.unbarrieredGet();
+ if (IsForwarded(group)) {
+ group = Forwarded(group);
+ entry.group.set(group);
+ }
+ TaggedProto proto = group->proto();
+ if (proto.isObject() && IsForwarded(proto.toObject())) {
+ proto = TaggedProto(Forwarded(proto.toObject()));
+ // Update the group's proto here so that we are able to lookup
+ // entries in this table before all object pointers are updated.
+ group->proto() = proto;
+ }
+ if (entry.associated && IsForwarded(entry.associated))
+ entry.associated = Forwarded(entry.associated);
+ }
+ }
+}
+
+#ifdef JSGC_HASH_TABLE_CHECKS
+
+void
+ObjectGroupCompartment::checkNewTableAfterMovingGC(NewTable* table)
+{
+ /*
+ * Assert that nothing points into the nursery or needs to be relocated, and
+ * that the hash table entries are discoverable.
+ */
+ if (!table || !table->initialized())
+ return;
+
+ for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
+ NewEntry entry = e.front();
+ CheckGCThingAfterMovingGC(entry.group.unbarrieredGet());
+ TaggedProto proto = entry.group.unbarrieredGet()->proto();
+ if (proto.isObject())
+ CheckGCThingAfterMovingGC(proto.toObject());
+ CheckGCThingAfterMovingGC(entry.associated);
+
+ const Class* clasp = entry.group.unbarrieredGet()->clasp();
+ if (entry.associated && entry.associated->is<JSFunction>())
+ clasp = nullptr;
+
+ NewEntry::Lookup lookup(clasp, proto, entry.associated);
+ auto ptr = table->lookup(lookup);
+ MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
+ }
+}
+
+#endif // JSGC_HASH_TABLE_CHECKS