blob: 6ef50de88c3f30fc79cc750b1162e2a9925dbc5b [file] [log] [blame]
/* -*- 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/StoreBuffer.h"
#include "gc/Zone.h"
#include "vm/ArrayObject.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()));
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 and typed
* arrays, but not normal arrays.
*/
if (script->functionNonDelazifying() && !script->treatAsRunOnce())
return GenericObject;
if (key != JSProto_Object &&
!(key >= JSProto_Int8Array && key <= JSProto_Uint8ClampedArray))
{
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 (getProto() != 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->getTaggedProto());
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 hashProto;
TaggedProto matchProto;
JSObject* associated;
Lookup(const Class* clasp, TaggedProto proto, JSObject* associated)
: clasp(clasp), hashProto(proto), matchProto(proto), associated(associated)
{}
/*
* For use by generational post barriers only. Look up an entry whose
* proto has been moved, but was hashed with the original value.
*/
Lookup(const Class* clasp, TaggedProto hashProto, TaggedProto matchProto, JSObject* associated)
: clasp(clasp), hashProto(hashProto), matchProto(matchProto), associated(associated)
{}
};
static inline HashNumber hash(const Lookup& lookup) {
return PointerHasher<JSObject*, 3>::hash(lookup.hashProto.raw()) ^
PointerHasher<const Class*, 3>::hash(lookup.clasp) ^
PointerHasher<JSObject*, 3>::hash(lookup.associated);
}
static inline bool match(const NewEntry& key, const Lookup& lookup) {
return key.group.unbarrieredGet()->proto() == lookup.matchProto &&
(!lookup.clasp || key.group.unbarrieredGet()->clasp() == lookup.clasp) &&
key.associated == lookup.associated;
}
static void rekey(NewEntry& k, const NewEntry& newKey) { k = newKey; }
};
// This class is used to add a post barrier on a NewTable entry, as the key is
// calculated from a prototype object which may be moved by generational GC.
class ObjectGroupCompartment::NewTableRef : public gc::BufferableRef
{
NewTable* table;
const Class* clasp;
JSObject* proto;
JSObject* associated;
public:
NewTableRef(NewTable* table, const Class* clasp, JSObject* proto, JSObject* associated)
: table(table), clasp(clasp), proto(proto), associated(associated)
{}
void trace(JSTracer* trc) override {
JSObject* prior = proto;
TraceManuallyBarrieredEdge(trc, &proto, "newObjectGroups set prototype");
if (prior == proto)
return;
NewTable::Ptr p = table->lookup(NewTable::Lookup(clasp, TaggedProto(prior),
TaggedProto(proto),
associated));
if (!p)
return;
table->rekeyAs(NewTable::Lookup(clasp, TaggedProto(prior), TaggedProto(proto), associated),
NewTable::Lookup(clasp, TaggedProto(proto), associated), *p);
}
};
/* static */ void
ObjectGroupCompartment::newTablePostBarrier(ExclusiveContext* cx, NewTable* table,
const Class* clasp, TaggedProto proto,
JSObject* associated)
{
MOZ_ASSERT_IF(associated, !IsInsideNursery(associated));
if (!proto.isObject())
return;
if (!cx->isJSContext()) {
MOZ_ASSERT(!IsInsideNursery(proto.toObject()));
return;
}
if (IsInsideNursery(proto.toObject())) {
gc::StoreBuffer& sb = cx->asJSContext()->runtime()->gc.storeBuffer;
sb.putGeneric(NewTableRef(table, clasp, proto.toObject(), associated));
}
}
/* 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>();
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.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;
}
ObjectGroupCompartment::newTablePostBarrier(cx, table, clasp, proto, associated);
if (proto.isObject()) {
RootedObject obj(cx, proto.toObject());
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 (obj->is<RegExpObject>()) {
AddTypePropertyId(cx, group, nullptr, NameToId(names.source), TypeSet::StringType());
AddTypePropertyId(cx, group, nullptr, NameToId(names.global), TypeSet::BooleanType());
AddTypePropertyId(cx, group, nullptr, NameToId(names.ignoreCase), TypeSet::BooleanType());
AddTypePropertyId(cx, group, nullptr, NameToId(names.multiline), TypeSet::BooleanType());
AddTypePropertyId(cx, group, nullptr, NameToId(names.sticky), TypeSet::BooleanType());
AddTypePropertyId(cx, group, nullptr, NameToId(names.lastIndex), TypeSet::Int32Type());
}
if (obj->is<StringObject>())
AddTypePropertyId(cx, group, nullptr, NameToId(names.length), TypeSet::Int32Type());
if (obj->is<ErrorObject>()) {
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>();
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;
}
ObjectGroupCompartment::newTablePostBarrier(cx, table, clasp, proto, 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));
ObjectGroupCompartment::NewTable::Ptr p =
table->lookup(ObjectGroupCompartment::NewEntry::Lookup(clasp, taggedProto, nullptr));
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) {
ObjectGroupCompartment::NewTable::Ptr p =
table->lookup(ObjectGroupCompartment::NewEntry::Lookup(clasp, TaggedProto(proto), nullptr));
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);
}
};
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 (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) (JSID_BITS(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;
}
};
struct ObjectGroupCompartment::PlainObjectEntry
{
ReadBarrieredObjectGroup group;
ReadBarrieredShape shape;
TypeSet::Type* types;
};
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->getTaggedProto());
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))
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>,
public JS::Traceable {
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(AllocationSiteKey&& key)
: script(mozilla::Move(key.script)),
offset(key.offset),
kind(key.kind),
proto(mozilla::Move(key.proto))
{ }
AllocationSiteKey(const AllocationSiteKey& key)
: script(key.script),
offset(key.offset),
kind(key.kind),
proto(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);
}
static void trace(AllocationSiteKey* key, JSTracer* trc) {
TraceRoot(trc, &key->script, "AllocationSiteKey script");
TraceNullableRoot(trc, &key->proto, "AllocationSiteKey proto");
}
};
/* 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>();
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->runtime()->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->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)
{
NewTable::Ptr p = defaultNewTable->lookup(NewEntry::Lookup(clasp, proto, associated));
MOZ_RELEASE_ASSERT(p);
defaultNewTable->remove(p);
}
void
ObjectGroupCompartment::replaceDefaultNewGroup(const Class* clasp, TaggedProto proto,
JSObject* associated, ObjectGroup* group)
{
NewEntry::Lookup lookup(clasp, proto, associated);
NewTable::Ptr p = defaultNewTable->lookup(lookup);
MOZ_RELEASE_ASSERT(p);
defaultNewTable->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();
}
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) {
for (ArrayObjectTable::Enum e(*arrayObjectTable); !e.empty(); e.popFront()) {
ArrayObjectKey key = e.front().key();
MOZ_ASSERT(key.type.isUnknown() || !key.type.isSingleton());
bool remove = false;
if (!key.type.isUnknown() && key.type.isGroup()) {
ObjectGroup* group = key.type.groupNoBarrier();
if (IsAboutToBeFinalizedUnbarriered(&group))
remove = true;
else
key.type = TypeSet::ObjectType(group);
}
if (IsAboutToBeFinalized(&e.front().value()))
remove = true;
if (remove)
e.removeFront();
else if (key != e.front().key())
e.rekeyFront(key);
}
}
if (plainObjectTable) {
for (PlainObjectTable::Enum e(*plainObjectTable); !e.empty(); e.popFront()) {
const PlainObjectKey& key = e.front().key();
PlainObjectEntry& entry = e.front().value();
bool remove = false;
if (IsAboutToBeFinalized(&entry.group))
remove = true;
if (IsAboutToBeFinalized(&entry.shape))
remove = true;
for (unsigned i = 0; !remove && i < key.nproperties; i++) {
if (gc::IsAboutToBeFinalizedUnbarriered(&key.properties[i]))
remove = true;
MOZ_ASSERT(!entry.types[i].isSingleton());
if (entry.types[i].isGroup()) {
ObjectGroup* group = entry.types[i].groupNoBarrier();
if (IsAboutToBeFinalizedUnbarriered(&group))
remove = true;
else if (group != entry.types[i].groupNoBarrier())
entry.types[i] = TypeSet::ObjectType(group);
}
}
if (remove) {
js_free(key.properties);
js_free(entry.types);
e.removeFront();
}
}
}
if (allocationSiteTable) {
for (AllocationSiteTable::Enum e(*allocationSiteTable); !e.empty(); e.popFront()) {
bool keyDying = IsAboutToBeFinalized(&e.front().mutableKey().script) ||
(e.front().key().proto && IsAboutToBeFinalized(&e.front().mutableKey().proto));
bool valDying = IsAboutToBeFinalized(&e.front().value());
if (keyDying || valDying)
e.removeFront();
}
}
sweepNewTable(defaultNewTable);
sweepNewTable(lazyTable);
}
void
ObjectGroupCompartment::sweepNewTable(NewTable* table)
{
if (table && table->initialized()) {
for (NewTable::Enum e(*table); !e.empty(); e.popFront()) {
NewEntry entry = e.front();
if (IsAboutToBeFinalized(&entry.group) ||
(entry.associated && IsAboutToBeFinalizedUnbarriered(&entry.associated)))
{
e.removeFront();
} else {
/* Any rekeying necessary is handled by fixupNewObjectGroupTable() below. */
MOZ_ASSERT(entry.group.unbarrieredGet() == e.front().group.unbarrieredGet());
MOZ_ASSERT(entry.associated == e.front().associated);
}
}
}
}
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.front();
bool needRekey = false;
if (IsForwarded(entry.group.unbarrieredGet())) {
entry.group.set(Forwarded(entry.group.unbarrieredGet()));
needRekey = true;
}
TaggedProto proto = entry.group.unbarrieredGet()->proto();
if (proto.isObject() && IsForwarded(proto.toObject())) {
proto = TaggedProto(Forwarded(proto.toObject()));
needRekey = true;
}
if (entry.associated && IsForwarded(entry.associated)) {
entry.associated = Forwarded(entry.associated);
needRekey = true;
}
if (needRekey) {
const Class* clasp = entry.group.unbarrieredGet()->clasp();
if (entry.associated && entry.associated->is<JSFunction>())
clasp = nullptr;
NewEntry::Lookup lookup(clasp, proto, entry.associated);
e.rekeyFront(lookup, entry);
}
}
}
}
#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);
NewTable::Ptr ptr = table->lookup(lookup);
MOZ_RELEASE_ASSERT(ptr.found() && &*ptr == &e.front());
}
}
#endif // JSGC_HASH_TABLE_CHECKS