diff options
Diffstat (limited to 'js/src/vm/ObjectGroup.cpp')
-rw-r--r-- | js/src/vm/ObjectGroup.cpp | 1862 |
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 |