From b1cd96989bb5107f83895c2320e0046d84b448ab Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 22 Feb 2020 21:07:48 +0100 Subject: Revert 1320408 part 15: Make addDataProperty static --- js/src/vm/ErrorObject.cpp | 8 ++++---- js/src/vm/NativeObject.cpp | 17 +++++++++-------- js/src/vm/NativeObject.h | 8 ++++---- js/src/vm/RegExpObject.cpp | 3 +-- 4 files changed, 18 insertions(+), 18 deletions(-) (limited to 'js/src/vm') diff --git a/js/src/vm/ErrorObject.cpp b/js/src/vm/ErrorObject.cpp index 271132801..d8d29830b 100644 --- a/js/src/vm/ErrorObject.cpp +++ b/js/src/vm/ErrorObject.cpp @@ -29,11 +29,11 @@ js::ErrorObject::assignInitialShape(ExclusiveContext* cx, Handle o { MOZ_ASSERT(obj->empty()); - if (!NativeObject::addDataProperty(cx, obj, cx->names().fileName, FILENAME_SLOT, 0)) + if (!obj->addDataProperty(cx, cx->names().fileName, FILENAME_SLOT, 0)) return nullptr; - if (!NativeObject::addDataProperty(cx, obj, cx->names().lineNumber, LINENUMBER_SLOT, 0)) + if (!obj->addDataProperty(cx, cx->names().lineNumber, LINENUMBER_SLOT, 0)) return nullptr; - return NativeObject::addDataProperty(cx, obj, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0); + return obj->addDataProperty(cx, cx->names().columnNumber, COLUMNNUMBER_SLOT, 0); } /* static */ bool @@ -57,7 +57,7 @@ js::ErrorObject::init(JSContext* cx, Handle obj, JSExnType type, // |new Error()|. RootedShape messageShape(cx); if (message) { - messageShape = NativeObject::addDataProperty(cx, obj, cx->names().message, MESSAGE_SLOT, 0); + messageShape = obj->addDataProperty(cx, cx->names().message, MESSAGE_SLOT, 0); if (!messageShape) return false; MOZ_ASSERT(messageShape->slot() == MESSAGE_SLOT); diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index d801fad06..e7de36f13 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -994,22 +994,23 @@ NativeObject::freeSlot(ExclusiveContext* cx, uint32_t slot) setSlot(slot, UndefinedValue()); } -/* static */ Shape* -NativeObject::addDataProperty(ExclusiveContext* cx, HandleNativeObject obj, - jsid idArg, uint32_t slot, unsigned attrs) +Shape* +NativeObject::addDataProperty(ExclusiveContext* cx, jsid idArg, uint32_t slot, unsigned attrs) { MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); + RootedNativeObject self(cx, this); RootedId id(cx, idArg); - return addProperty(cx, obj, id, nullptr, nullptr, slot, attrs, 0); + return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); } -/* static */ Shape* -NativeObject::addDataProperty(ExclusiveContext* cx, HandleNativeObject obj, - HandlePropertyName name, uint32_t slot, unsigned attrs) +Shape* +NativeObject::addDataProperty(ExclusiveContext* cx, HandlePropertyName name, + uint32_t slot, unsigned attrs) { MOZ_ASSERT(!(attrs & (JSPROP_GETTER | JSPROP_SETTER))); + RootedNativeObject self(cx, this); RootedId id(cx, NameToId(name)); - return addProperty(cx, obj, id, nullptr, nullptr, slot, attrs, 0); + return addProperty(cx, self, id, nullptr, nullptr, slot, attrs, 0); } template diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 9cc6d5436..b17920ef9 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -744,10 +744,10 @@ class NativeObject : public ShapedObject bool allowDictionary = true); /* Add a data property whose id is not yet in this scope. */ - static Shape* addDataProperty(ExclusiveContext* cx, HandleNativeObject obj, - jsid id_, uint32_t slot, unsigned attrs); - static Shape* addDataProperty(ExclusiveContext* cx, HandleNativeObject obj, - HandlePropertyName name, uint32_t slot, unsigned attrs); + Shape* addDataProperty(ExclusiveContext* cx, + jsid id_, uint32_t slot, unsigned attrs); + Shape* addDataProperty(ExclusiveContext* cx, HandlePropertyName name, + uint32_t slot, unsigned attrs); /* Add or overwrite a property for id in this scope. */ static Shape* diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index cd0b54c9d..6223fc10d 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -299,8 +299,7 @@ RegExpObject::assignInitialShape(ExclusiveContext* cx, Handle sel JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0); /* The lastIndex property alone is writable but non-configurable. */ - return NativeObject::addDataProperty(cx, self, cx->names().lastIndex, LAST_INDEX_SLOT, - JSPROP_PERMANENT); + return self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT); } void -- cgit v1.2.3 From 5ee844f71001af821244696414bd92973f78fc09 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 22 Feb 2020 21:09:32 +0100 Subject: Revert #1142 - Remove unboxed objects - accounting for removal of watch()/unwatch() --- js/src/vm/Interpreter.cpp | 5 + js/src/vm/NativeObject.cpp | 27 ++ js/src/vm/NativeObject.h | 5 + js/src/vm/ObjectGroup-inl.h | 14 + js/src/vm/ObjectGroup.cpp | 55 ++- js/src/vm/ObjectGroup.h | 62 ++- js/src/vm/ReceiverGuard.cpp | 14 +- js/src/vm/TypeInference-inl.h | 5 + js/src/vm/TypeInference.cpp | 156 ++++++- js/src/vm/UnboxedObject-inl.h | 177 ++++++++ js/src/vm/UnboxedObject.cpp | 946 ++++++++++++++++++++++++++++++++++++++++++ js/src/vm/UnboxedObject.h | 319 ++++++++++++++ 12 files changed, 1765 insertions(+), 20 deletions(-) create mode 100644 js/src/vm/UnboxedObject-inl.h create mode 100644 js/src/vm/UnboxedObject.cpp create mode 100644 js/src/vm/UnboxedObject.h (limited to 'js/src/vm') diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 3cf9b57f6..b87d12924 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -5016,6 +5016,11 @@ js::NewObjectOperationWithTemplate(JSContext* cx, HandleObject templateObject) NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject; + if (templateObject->group()->maybeUnboxedLayout()) { + RootedObjectGroup group(cx, templateObject->group()); + return UnboxedPlainObject::create(cx, group, newKind); + } + JSObject* obj = CopyInitializerObject(cx, templateObject.as(), newKind); if (!obj) return nullptr; diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index e7de36f13..8b7543d12 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -388,6 +388,33 @@ NativeObject::setLastPropertyMakeNonNative(Shape* shape) shape_ = shape; } +void +NativeObject::setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape) +{ + MOZ_ASSERT(getClass()->isNative()); + MOZ_ASSERT(shape->getObjectClass()->isNative()); + MOZ_ASSERT(!shape->inDictionary()); + + // This method is used to convert unboxed objects into native objects. In + // this case, the shape_ field was previously used to store other data and + // this should be treated as an initialization. + shape_.init(shape); + + slots_ = nullptr; + elements_ = emptyObjectElements; + + size_t oldSpan = shape->numFixedSlots(); + size_t newSpan = shape->slotSpan(); + + initializeSlotRange(0, oldSpan); + + // A failure at this point will leave the object as a mutant, and we + // can't recover. + AutoEnterOOMUnsafeRegion oomUnsafe; + if (oldSpan != newSpan && !updateSlotsForSpan(cx, oldSpan, newSpan)) + oomUnsafe.crash("NativeObject::setLastPropertyMakeNative"); +} + bool NativeObject::setSlotSpan(ExclusiveContext* cx, uint32_t span) { diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index b17920ef9..c774308af 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -470,6 +470,11 @@ class NativeObject : public ShapedObject // that are (temporarily) inconsistent. void setLastPropertyMakeNonNative(Shape* shape); + // As for setLastProperty(), but changes the class associated with the + // object to a native one. The object's type has already been changed, and + // this brings the shape into sync with it. + void setLastPropertyMakeNative(ExclusiveContext* cx, Shape* shape); + // Newly-created TypedArrays that map a SharedArrayBuffer are // marked as shared by giving them an ObjectElements that has the // ObjectElements::SHARED_MEMORY flag set. diff --git a/js/src/vm/ObjectGroup-inl.h b/js/src/vm/ObjectGroup-inl.h index d41343be6..9074f4d97 100644 --- a/js/src/vm/ObjectGroup-inl.h +++ b/js/src/vm/ObjectGroup-inl.h @@ -108,6 +108,20 @@ ObjectGroup::maybePreliminaryObjects() return maybePreliminaryObjectsDontCheckGeneration(); } +inline UnboxedLayout* +ObjectGroup::maybeUnboxedLayout() +{ + maybeSweep(nullptr); + return maybeUnboxedLayoutDontCheckGeneration(); +} + +inline UnboxedLayout& +ObjectGroup::unboxedLayout() +{ + maybeSweep(nullptr); + return unboxedLayoutDontCheckGeneration(); +} + } // namespace js #endif /* vm_ObjectGroup_inl_h */ diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index 91070b3f6..95fcada94 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -18,10 +18,11 @@ #include "vm/ArrayObject.h" #include "vm/Shape.h" #include "vm/TaggedProto.h" +#include "vm/UnboxedObject.h" #include "jsobjinlines.h" -#include "vm/NativeObject-inl.h" +#include "vm/UnboxedObject-inl.h" using namespace js; @@ -55,6 +56,7 @@ ObjectGroup::finalize(FreeOp* fop) if (newScriptDontCheckGeneration()) newScriptDontCheckGeneration()->clear(); fop->delete_(newScriptDontCheckGeneration()); + fop->delete_(maybeUnboxedLayoutDontCheckGeneration()); if (maybePreliminaryObjectsDontCheckGeneration()) maybePreliminaryObjectsDontCheckGeneration()->clear(); fop->delete_(maybePreliminaryObjectsDontCheckGeneration()); @@ -81,6 +83,8 @@ 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; } @@ -529,7 +533,8 @@ ObjectGroup::defaultNewGroup(ExclusiveContext* cx, const Class* clasp, if (p) { ObjectGroup* group = p->group; MOZ_ASSERT_IF(clasp, group->clasp() == clasp); - MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_); + MOZ_ASSERT_IF(!clasp, group->clasp() == &PlainObject::class_ || + group->clasp() == &UnboxedPlainObject::class_); MOZ_ASSERT(group->proto() == proto); return group; } @@ -969,6 +974,46 @@ js::CombinePlainObjectPropertyTypes(ExclusiveContext* cx, JSObject* newObj, } } } + } else if (newObj->is()) { + const UnboxedLayout& layout = newObj->as().layout(); + const int32_t* traceList = layout.traceList(); + if (!traceList) + return true; + + uint8_t* newData = newObj->as().data(); + uint8_t* oldData = oldObj->as().data(); + + for (; *traceList != -1; traceList++) {} + traceList++; + for (; *traceList != -1; traceList++) { + JSObject* newInnerObj = *reinterpret_cast(newData + *traceList); + JSObject* oldInnerObj = *reinterpret_cast(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().data(); + JSObject* otherInnerObj = *reinterpret_cast(otherData + *traceList); + if (otherInnerObj && !SameGroup(otherInnerObj, newInnerObj)) { + if (!GiveObjectGroup(cx, otherInnerObj, newInnerObj)) + return false; + } + } + } + } + } } return true; @@ -1192,6 +1237,12 @@ ObjectGroup::newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_ 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. diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h index 0b6eaee51..553cb8366 100644 --- a/js/src/vm/ObjectGroup.h +++ b/js/src/vm/ObjectGroup.h @@ -20,6 +20,7 @@ namespace js { class TypeDescr; +class UnboxedLayout; class PreliminaryObjectArrayWithTemplate; class TypeNewScript; @@ -153,6 +154,16 @@ class ObjectGroup : public gc::TenuredCell // For some plain objects, the addendum stores a PreliminaryObjectArrayWithTemplate. Addendum_PreliminaryObjects, + // When objects in this group have an unboxed representation, the + // addendum stores an UnboxedLayout (which might have a TypeNewScript + // as well, if the group is also constructed using 'new'). + Addendum_UnboxedLayout, + + // If this group is used by objects that have been converted from an + // unboxed representation and/or have the same allocation kind as such + // objects, the addendum points to that unboxed group. + Addendum_OriginalUnboxedGroup, + // When used by typed objects, the addendum stores a TypeDescr. Addendum_TypeDescr }; @@ -174,6 +185,7 @@ class ObjectGroup : public gc::TenuredCell return nullptr; } + TypeNewScript* anyNewScript(); void detachNewScript(bool writeBarrier, ObjectGroup* replacement); ObjectGroupFlags flagsDontCheckGeneration() const { @@ -213,6 +225,34 @@ class ObjectGroup : public gc::TenuredCell maybePreliminaryObjectsDontCheckGeneration(); } + inline UnboxedLayout* maybeUnboxedLayout(); + inline UnboxedLayout& unboxedLayout(); + + UnboxedLayout* maybeUnboxedLayoutDontCheckGeneration() const { + if (addendumKind() == Addendum_UnboxedLayout) + return reinterpret_cast(addendum_); + return nullptr; + } + + UnboxedLayout& unboxedLayoutDontCheckGeneration() const { + MOZ_ASSERT(addendumKind() == Addendum_UnboxedLayout); + return *maybeUnboxedLayoutDontCheckGeneration(); + } + + void setUnboxedLayout(UnboxedLayout* layout) { + setAddendum(Addendum_UnboxedLayout, layout); + } + + ObjectGroup* maybeOriginalUnboxedGroup() const { + if (addendumKind() == Addendum_OriginalUnboxedGroup) + return reinterpret_cast(addendum_); + return nullptr; + } + + void setOriginalUnboxedGroup(ObjectGroup* group) { + setAddendum(Addendum_OriginalUnboxedGroup, group); + } + TypeDescr* maybeTypeDescr() { // Note: there is no need to sweep when accessing the type descriptor // of an object, as it is strongly held and immutable. @@ -273,8 +313,9 @@ class ObjectGroup : public gc::TenuredCell * that can be read out of that property in actual JS objects. In native * objects, property types account for plain data properties (those with a * slot and no getter or setter hook) and dense elements. In typed objects - * property types account for object and value properties and elements in - * the object. + * and unboxed objects, property types account for object and value + * properties and elements in the object, and expando properties in unboxed + * objects. * * For accesses on these properties, the correspondence is as follows: * @@ -297,9 +338,10 @@ class ObjectGroup : public gc::TenuredCell * 2. Array lengths are special cased by the compiler and VM and are not * reflected in property types. * - * 3. In typed objects, the initial values of properties (null pointers and - * undefined values) are not reflected in the property types. These - * values are always possible when reading the property. + * 3. In typed objects (but not unboxed objects), the initial values of + * properties (null pointers and undefined values) are not reflected in + * the property types. These values are always possible when reading the + * property. * * We establish these by using write barriers on calls to setProperty and * defineProperty which are on native properties, and on any jitcode which @@ -413,6 +455,12 @@ class ObjectGroup : public gc::TenuredCell return &flags_; } + // Get the bit pattern stored in an object's addendum when it has an + // original unboxed group. + static inline int32_t addendumOriginalUnboxedGroupValue() { + return Addendum_OriginalUnboxedGroup << OBJECT_FLAG_ADDENDUM_SHIFT; + } + inline uint32_t basePropertyCount(); private: @@ -463,8 +511,8 @@ class ObjectGroup : public gc::TenuredCell NewObjectKind newKind, NewArrayKind arrayKind = NewArrayKind::Normal); - // Create a PlainObject with the specified properties and a group specialized - // for those properties. + // Create a PlainObject or UnboxedPlainObject with the specified properties + // and a group specialized for those properties. static JSObject* newPlainObject(ExclusiveContext* cx, IdValuePair* properties, size_t nproperties, NewObjectKind newKind); diff --git a/js/src/vm/ReceiverGuard.cpp b/js/src/vm/ReceiverGuard.cpp index e95e8a208..e37bf8ee5 100644 --- a/js/src/vm/ReceiverGuard.cpp +++ b/js/src/vm/ReceiverGuard.cpp @@ -15,7 +15,11 @@ ReceiverGuard::ReceiverGuard(JSObject* obj) : group(nullptr), shape(nullptr) { if (obj) { - if (obj->is()) { + if (obj->is()) { + group = obj->group(); + if (UnboxedExpandoObject* expando = obj->as().maybeExpando()) + shape = expando->lastProperty(); + } else if (obj->is()) { group = obj->group(); } else { shape = obj->maybeShape(); @@ -28,7 +32,9 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape) { if (group) { const Class* clasp = group->clasp(); - if (IsTypedObjectClass(clasp)) { + if (clasp == &UnboxedPlainObject::class_) { + // Keep both group and shape. + } else if (IsTypedObjectClass(clasp)) { this->shape = nullptr; } else { this->group = nullptr; @@ -39,6 +45,10 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape) /* static */ int32_t HeapReceiverGuard::keyBits(JSObject* obj) { + if (obj->is()) { + // Both the group and shape need to be guarded for unboxed plain objects. + return obj->as().maybeExpando() ? 0 : 1; + } if (obj->is()) { // Only the group needs to be guarded for typed objects. return 2; diff --git a/js/src/vm/TypeInference-inl.h b/js/src/vm/TypeInference-inl.h index 2af252cea..da47fa898 100644 --- a/js/src/vm/TypeInference-inl.h +++ b/js/src/vm/TypeInference-inl.h @@ -23,6 +23,7 @@ #include "vm/SharedArrayObject.h" #include "vm/StringObject.h" #include "vm/TypedArrayObject.h" +#include "vm/UnboxedObject.h" #include "jscntxtinlines.h" @@ -284,6 +285,10 @@ TypeIdString(jsid id) */ struct AutoEnterAnalysis { + // For use when initializing an UnboxedLayout. The UniquePtr's destructor + // must run when GC is not suppressed. + UniquePtr unboxedLayoutToCleanUp; + // Prevent GC activity in the middle of analysis. gc::AutoSuppressGC suppressGC; diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 2b1fa0e3b..39206539b 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -35,6 +35,7 @@ #include "vm/Opcodes.h" #include "vm/Shape.h" #include "vm/Time.h" +#include "vm/UnboxedObject.h" #include "jsatominlines.h" #include "jsscriptinlines.h" @@ -296,6 +297,9 @@ js::ObjectGroupHasProperty(JSContext* cx, ObjectGroup* group, jsid id, const Val return true; } } + JSObject* obj = &value.toObject(); + if (!obj->hasLazyGroup() && obj->group()->maybeOriginalUnboxedGroup()) + return true; } if (!types->hasType(type)) { @@ -1944,6 +1948,33 @@ class ConstraintDataFreezeObjectForTypedArrayData } }; +// Constraint which triggers recompilation if an unboxed object in some group +// is converted to a native object. +class ConstraintDataFreezeObjectForUnboxedConvertedToNative +{ + public: + ConstraintDataFreezeObjectForUnboxedConvertedToNative() + {} + + const char* kind() { return "freezeObjectForUnboxedConvertedToNative"; } + + bool invalidateOnNewType(TypeSet::Type type) { return false; } + bool invalidateOnNewPropertyState(TypeSet* property) { return false; } + bool invalidateOnNewObjectState(ObjectGroup* group) { + return group->unboxedLayout().nativeGroup() != nullptr; + } + + bool constraintHolds(JSContext* cx, + const HeapTypeSetKey& property, TemporaryTypeSet* expected) + { + return !invalidateOnNewObjectState(property.object()->maybeGroup()); + } + + bool shouldSweep() { return false; } + + JSCompartment* maybeCompartment() { return nullptr; } +}; + } /* anonymous namespace */ void @@ -2478,6 +2509,8 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid bool js::ClassCanHaveExtraProperties(const Class* clasp) { + if (clasp == &UnboxedPlainObject::class_) + return false; return clasp->getResolve() || clasp->getOpsLookupProperty() || clasp->getOpsGetProperty() @@ -2768,6 +2801,15 @@ js::AddTypePropertyId(ExclusiveContext* cx, ObjectGroup* group, JSObject* obj, j // from acquiring the fully initialized group. if (group->newScript() && group->newScript()->initializedGroup()) AddTypePropertyId(cx, group->newScript()->initializedGroup(), nullptr, id, type); + + // Maintain equivalent type information for unboxed object groups and their + // corresponding native group. Since type sets might contain the unboxed + // group but not the native group, this ensures optimizations based on the + // unboxed group are valid for the native group. + if (group->maybeUnboxedLayout() && group->maybeUnboxedLayout()->nativeGroup()) + AddTypePropertyId(cx, group->maybeUnboxedLayout()->nativeGroup(), nullptr, id, type); + if (ObjectGroup* unboxedGroup = group->maybeOriginalUnboxedGroup()) + AddTypePropertyId(cx, unboxedGroup, nullptr, id, type); } void @@ -2839,6 +2881,12 @@ ObjectGroup::setFlags(ExclusiveContext* cx, ObjectGroupFlags flags) // acquired properties analysis. if (newScript() && newScript()->initializedGroup()) newScript()->initializedGroup()->setFlags(cx, flags); + + // Propagate flag changes between unboxed and corresponding native groups. + if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup()) + maybeUnboxedLayout()->nativeGroup()->setFlags(cx, flags); + if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup()) + unboxedGroup->setFlags(cx, flags); } void @@ -2871,6 +2919,23 @@ ObjectGroup::markUnknown(ExclusiveContext* cx) prop->types.setNonDataProperty(cx); } } + + if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup()) + MarkObjectGroupUnknownProperties(cx, unboxedGroup); + if (maybeUnboxedLayout() && maybeUnboxedLayout()->nativeGroup()) + MarkObjectGroupUnknownProperties(cx, maybeUnboxedLayout()->nativeGroup()); + if (ObjectGroup* unboxedGroup = maybeOriginalUnboxedGroup()) + MarkObjectGroupUnknownProperties(cx, unboxedGroup); +} + +TypeNewScript* +ObjectGroup::anyNewScript() +{ + if (newScript()) + return newScript(); + if (maybeUnboxedLayout()) + return unboxedLayout().newScript(); + return nullptr; } void @@ -2880,7 +2945,7 @@ ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement) // analyzed, remove it from the newObjectGroups table so that it will not be // produced by calling 'new' on the associated function anymore. // The TypeNewScript is not actually destroyed. - TypeNewScript* newScript = this->newScript(); + TypeNewScript* newScript = anyNewScript(); MOZ_ASSERT(newScript); if (newScript->analyzed()) { @@ -2899,7 +2964,10 @@ ObjectGroup::detachNewScript(bool writeBarrier, ObjectGroup* replacement) MOZ_ASSERT(!replacement); } - setAddendum(Addendum_None, nullptr, writeBarrier); + if (this->newScript()) + setAddendum(Addendum_None, nullptr, writeBarrier); + else + unboxedLayout().setNewScript(nullptr, writeBarrier); } void @@ -2910,7 +2978,7 @@ ObjectGroup::maybeClearNewScriptOnOOM() if (!isMarked()) return; - TypeNewScript* newScript = this->newScript(); + TypeNewScript* newScript = anyNewScript(); if (!newScript) return; @@ -2925,7 +2993,7 @@ ObjectGroup::maybeClearNewScriptOnOOM() void ObjectGroup::clearNewScript(ExclusiveContext* cx, ObjectGroup* replacement /* = nullptr*/) { - TypeNewScript* newScript = this->newScript(); + TypeNewScript* newScript = anyNewScript(); if (!newScript) return; @@ -3390,6 +3458,22 @@ PreliminaryObjectArray::sweep() for (size_t i = 0; i < COUNT; i++) { JSObject** ptr = &objects[i]; if (*ptr && IsAboutToBeFinalizedUnbarriered(ptr)) { + // Before we clear this reference, change the object's group to the + // Object.prototype group. This is done to ensure JSObject::finalize + // sees a NativeObject Class even if we change the current group's + // Class to one of the unboxed object classes in the meantime. If + // the compartment's global is dead, we don't do anything as the + // group's Class is not going to change in that case. + JSObject* obj = *ptr; + GlobalObject* global = obj->compartment()->unsafeUnbarrieredMaybeGlobal(); + if (global && !obj->isSingleton()) { + JSObject* objectProto = GetBuiltinPrototypePure(global, JSProto_Object); + obj->setGroup(objectProto->groupRaw()); + MOZ_ASSERT(obj->is()); + MOZ_ASSERT(obj->getClass() == objectProto->getClass()); + MOZ_ASSERT(!obj->getClass()->hasFinalize()); + } + *ptr = nullptr; } } @@ -3489,11 +3573,16 @@ PreliminaryObjectArrayWithTemplate::maybeAnalyze(ExclusiveContext* cx, ObjectGro } } - // Since the preliminary objects still reflect the template object's - // properties, and all objects in the future will be created with those - // properties, the properties can be marked as definitive for objects in - // the group. - group->addDefiniteProperties(cx, shape()); + if (group->maybeUnboxedLayout()) + return; + + if (shape()) { + // We weren't able to use an unboxed layout, but since the preliminary + // objects still reflect the template object's properties, and all + // objects in the future will be created with those properties, the + // properties can be marked as definite for objects in the group. + group->addDefiniteProperties(cx, shape()); + } } ///////////////////////////////////////////////////////////////////// @@ -3507,6 +3596,7 @@ TypeNewScript::make(JSContext* cx, ObjectGroup* group, JSFunction* fun) { MOZ_ASSERT(cx->zone()->types.activeAnalysis); MOZ_ASSERT(!group->newScript()); + MOZ_ASSERT(!group->maybeUnboxedLayout()); // rollbackPartiallyInitializedObjects expects function_ to be // canonicalized. @@ -3814,6 +3904,27 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, js_delete(preliminaryObjects); preliminaryObjects = nullptr; + if (group->maybeUnboxedLayout()) { + // An unboxed layout was constructed for the group, and this has already + // been hooked into it. + MOZ_ASSERT(group->unboxedLayout().newScript() == this); + destroyNewScript.group = nullptr; + + // Clear out the template object, which is not used for TypeNewScripts + // with an unboxed layout. Currently it is a mutant object with a + // non-native group and native shape, so make it safe for GC by changing + // its group to the default for its prototype. + AutoEnterOOMUnsafeRegion oomUnsafe; + ObjectGroup* plainGroup = ObjectGroup::defaultNewGroup(cx, &PlainObject::class_, + group->proto()); + if (!plainGroup) + oomUnsafe.crash("TypeNewScript::maybeAnalyze"); + templateObject_->setGroup(plainGroup); + templateObject_ = nullptr; + + return true; + } + if (prefixShape->slotSpan() == templateObject()->slotSpan()) { // The definite properties analysis found exactly the properties that // are held in common by the preliminary objects. No further analysis @@ -3927,6 +4038,12 @@ TypeNewScript::rollbackPartiallyInitializedObjects(JSContext* cx, ObjectGroup* g continue; } + if (thisv.toObject().is()) { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject())) + oomUnsafe.crash("rollbackPartiallyInitializedObjects"); + } + // Found a matching frame. RootedPlainObject obj(cx, &thisv.toObject().as()); @@ -4120,6 +4237,12 @@ ConstraintTypeSet::sweep(Zone* zone, AutoClearTypeInferenceStateOnOOM& oom) // Object sets containing objects with unknown properties might // not be complete. Mark the type set as unknown, which it will // be treated as during Ion compilation. + // + // Note that we don't have to do this when the type set might + // be missing the native group corresponding to an unboxed + // object group. In this case, the native group points to the + // unboxed object group via its addendum, so as long as objects + // with either group exist, neither group will be finalized. flags |= TYPE_FLAG_ANYOBJECT; clearObjects(); objectCount = 0; @@ -4203,6 +4326,21 @@ ObjectGroup::sweep(AutoClearTypeInferenceStateOnOOM* oom) Maybe fallbackOOM; EnsureHasAutoClearTypeInferenceStateOnOOM(oom, zone(), fallbackOOM); + if (maybeUnboxedLayout()) { + // Remove unboxed layouts that are about to be finalized from the + // compartment wide list while we are still on the main thread. + ObjectGroup* group = this; + if (IsAboutToBeFinalizedUnbarriered(&group)) + unboxedLayout().detachFromCompartment(); + + if (unboxedLayout().newScript()) + unboxedLayout().newScript()->sweep(); + + // Discard constructor code to avoid holding onto ExecutablePools. + if (zone()->isGCCompacting()) + unboxedLayout().setConstructorCode(nullptr); + } + if (maybePreliminaryObjects()) maybePreliminaryObjects()->sweep(); diff --git a/js/src/vm/UnboxedObject-inl.h b/js/src/vm/UnboxedObject-inl.h new file mode 100644 index 000000000..c1468a5b1 --- /dev/null +++ b/js/src/vm/UnboxedObject-inl.h @@ -0,0 +1,177 @@ +/* -*- 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/. */ + +#ifndef vm_UnboxedObject_inl_h +#define vm_UnboxedObject_inl_h + +#include "vm/UnboxedObject.h" + +#include "gc/StoreBuffer-inl.h" +#include "vm/ArrayObject-inl.h" +#include "vm/NativeObject-inl.h" + +namespace js { + +static inline Value +GetUnboxedValue(uint8_t* p, JSValueType type, bool maybeUninitialized) +{ + switch (type) { + case JSVAL_TYPE_BOOLEAN: + return BooleanValue(*p != 0); + + case JSVAL_TYPE_INT32: + return Int32Value(*reinterpret_cast(p)); + + case JSVAL_TYPE_DOUBLE: { + // During unboxed plain object creation, non-GC thing properties are + // left uninitialized. This is normally fine, since the properties will + // be filled in shortly, but if they are read before that happens we + // need to make sure that doubles are canonical. + double d = *reinterpret_cast(p); + if (maybeUninitialized) + return DoubleValue(JS::CanonicalizeNaN(d)); + return DoubleValue(d); + } + + case JSVAL_TYPE_STRING: + return StringValue(*reinterpret_cast(p)); + + case JSVAL_TYPE_OBJECT: + return ObjectOrNullValue(*reinterpret_cast(p)); + + default: + MOZ_CRASH("Invalid type for unboxed value"); + } +} + +static inline void +SetUnboxedValueNoTypeChange(JSObject* unboxedObject, + uint8_t* p, JSValueType type, const Value& v, + bool preBarrier) +{ + switch (type) { + case JSVAL_TYPE_BOOLEAN: + *p = v.toBoolean(); + return; + + case JSVAL_TYPE_INT32: + *reinterpret_cast(p) = v.toInt32(); + return; + + case JSVAL_TYPE_DOUBLE: + *reinterpret_cast(p) = v.toNumber(); + return; + + case JSVAL_TYPE_STRING: { + MOZ_ASSERT(!IsInsideNursery(v.toString())); + JSString** np = reinterpret_cast(p); + if (preBarrier) + JSString::writeBarrierPre(*np); + *np = v.toString(); + return; + } + + case JSVAL_TYPE_OBJECT: { + JSObject** np = reinterpret_cast(p); + + // Manually trigger post barriers on the whole object. If we treat + // the pointer as a HeapPtrObject we will get confused later if the + // object is converted to its native representation. + JSObject* obj = v.toObjectOrNull(); + if (IsInsideNursery(obj) && !IsInsideNursery(unboxedObject)) { + JSRuntime* rt = unboxedObject->runtimeFromMainThread(); + rt->gc.storeBuffer.putWholeCell(unboxedObject); + } + + if (preBarrier) + JSObject::writeBarrierPre(*np); + *np = obj; + return; + } + + default: + MOZ_CRASH("Invalid type for unboxed value"); + } +} + +static inline bool +SetUnboxedValue(ExclusiveContext* cx, JSObject* unboxedObject, jsid id, + uint8_t* p, JSValueType type, const Value& v, bool preBarrier) +{ + switch (type) { + case JSVAL_TYPE_BOOLEAN: + if (v.isBoolean()) { + *p = v.toBoolean(); + return true; + } + return false; + + case JSVAL_TYPE_INT32: + if (v.isInt32()) { + *reinterpret_cast(p) = v.toInt32(); + return true; + } + return false; + + case JSVAL_TYPE_DOUBLE: + if (v.isNumber()) { + *reinterpret_cast(p) = v.toNumber(); + return true; + } + return false; + + case JSVAL_TYPE_STRING: + if (v.isString()) { + MOZ_ASSERT(!IsInsideNursery(v.toString())); + JSString** np = reinterpret_cast(p); + if (preBarrier) + JSString::writeBarrierPre(*np); + *np = v.toString(); + return true; + } + return false; + + case JSVAL_TYPE_OBJECT: + if (v.isObjectOrNull()) { + JSObject** np = reinterpret_cast(p); + + // Update property types when writing object properties. Types for + // other properties were captured when the unboxed layout was + // created. + AddTypePropertyId(cx, unboxedObject, id, v); + + // As above, trigger post barriers on the whole object. + JSObject* obj = v.toObjectOrNull(); + if (IsInsideNursery(v.toObjectOrNull()) && !IsInsideNursery(unboxedObject)) { + JSRuntime* rt = unboxedObject->runtimeFromMainThread(); + rt->gc.storeBuffer.putWholeCell(unboxedObject); + } + + if (preBarrier) + JSObject::writeBarrierPre(*np); + *np = obj; + return true; + } + return false; + + default: + MOZ_CRASH("Invalid type for unboxed value"); + } +} + +///////////////////////////////////////////////////////////////////// +// UnboxedPlainObject +///////////////////////////////////////////////////////////////////// + +inline const UnboxedLayout& +UnboxedPlainObject::layout() const +{ + return group()->unboxedLayout(); +} + +} // namespace js + +#endif // vm_UnboxedObject_inl_h diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp new file mode 100644 index 000000000..2e017ca3b --- /dev/null +++ b/js/src/vm/UnboxedObject.cpp @@ -0,0 +1,946 @@ +/* -*- 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/UnboxedObject-inl.h" + +#include "jit/BaselineIC.h" +#include "jit/ExecutableAllocator.h" +#include "jit/JitCommon.h" +#include "jit/Linker.h" + +#include "jsobjinlines.h" + +#include "gc/Nursery-inl.h" +#include "jit/MacroAssembler-inl.h" +#include "vm/Shape-inl.h" + +using mozilla::ArrayLength; +using mozilla::DebugOnly; +using mozilla::PodCopy; + +using namespace js; + +///////////////////////////////////////////////////////////////////// +// UnboxedLayout +///////////////////////////////////////////////////////////////////// + +void +UnboxedLayout::trace(JSTracer* trc) +{ + for (size_t i = 0; i < properties_.length(); i++) + TraceManuallyBarrieredEdge(trc, &properties_[i].name, "unboxed_layout_name"); + + if (newScript()) + newScript()->trace(trc); + + TraceNullableEdge(trc, &nativeGroup_, "unboxed_layout_nativeGroup"); + TraceNullableEdge(trc, &nativeShape_, "unboxed_layout_nativeShape"); + TraceNullableEdge(trc, &allocationScript_, "unboxed_layout_allocationScript"); + TraceNullableEdge(trc, &replacementGroup_, "unboxed_layout_replacementGroup"); + TraceNullableEdge(trc, &constructorCode_, "unboxed_layout_constructorCode"); +} + +size_t +UnboxedLayout::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) +{ + return mallocSizeOf(this) + + properties_.sizeOfExcludingThis(mallocSizeOf) + + (newScript() ? newScript()->sizeOfIncludingThis(mallocSizeOf) : 0) + + mallocSizeOf(traceList()); +} + +void +UnboxedLayout::setNewScript(TypeNewScript* newScript, bool writeBarrier /* = true */) +{ + if (newScript_ && writeBarrier) + TypeNewScript::writeBarrierPre(newScript_); + newScript_ = newScript; +} + +// Constructor code returns a 0x1 value to indicate the constructor code should +// be cleared. +static const uintptr_t CLEAR_CONSTRUCTOR_CODE_TOKEN = 0x1; + +/* static */ bool +UnboxedLayout::makeConstructorCode(JSContext* cx, HandleObjectGroup group) +{ + gc::AutoSuppressGC suppress(cx); + + using namespace jit; + + if (!cx->compartment()->ensureJitCompartmentExists(cx)) + return false; + + UnboxedLayout& layout = group->unboxedLayout(); + MOZ_ASSERT(!layout.constructorCode()); + + UnboxedPlainObject* templateObject = UnboxedPlainObject::create(cx, group, TenuredObject); + if (!templateObject) + return false; + + JitContext jitContext(cx, nullptr); + + MacroAssembler masm; + + Register propertiesReg, newKindReg; +#ifdef JS_CODEGEN_X86 + propertiesReg = eax; + newKindReg = ecx; + masm.loadPtr(Address(masm.getStackPointer(), sizeof(void*)), propertiesReg); + masm.loadPtr(Address(masm.getStackPointer(), 2 * sizeof(void*)), newKindReg); +#else + propertiesReg = IntArgReg0; + newKindReg = IntArgReg1; +#endif + +#ifdef JS_CODEGEN_ARM64 + // ARM64 communicates stack address via sp, but uses a pseudo-sp for addressing. + masm.initStackPtr(); +#endif + + MOZ_ASSERT(propertiesReg.volatile_()); + MOZ_ASSERT(newKindReg.volatile_()); + + AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); + regs.take(propertiesReg); + regs.take(newKindReg); + Register object = regs.takeAny(), scratch1 = regs.takeAny(), scratch2 = regs.takeAny(); + + LiveGeneralRegisterSet savedNonVolatileRegisters = SavedNonVolatileRegisters(regs); + masm.PushRegsInMask(savedNonVolatileRegisters); + + // The scratch double register might be used by MacroAssembler methods. + if (ScratchDoubleReg.volatile_()) + masm.push(ScratchDoubleReg); + + Label failure, tenuredObject, allocated; + masm.branch32(Assembler::NotEqual, newKindReg, Imm32(GenericObject), &tenuredObject); + masm.branchTest32(Assembler::NonZero, AbsoluteAddress(group->addressOfFlags()), + Imm32(OBJECT_FLAG_PRE_TENURE), &tenuredObject); + + // Allocate an object in the nursery + masm.createGCObject(object, scratch1, templateObject, gc::DefaultHeap, &failure, + /* initFixedSlots = */ false); + + masm.jump(&allocated); + masm.bind(&tenuredObject); + + // Allocate an object in the tenured heap. + masm.createGCObject(object, scratch1, templateObject, gc::TenuredHeap, &failure, + /* initFixedSlots = */ false); + + // If any of the properties being stored are in the nursery, add a store + // buffer entry for the new object. + Label postBarrier; + for (size_t i = 0; i < layout.properties().length(); i++) { + const UnboxedLayout::Property& property = layout.properties()[i]; + if (property.type == JSVAL_TYPE_OBJECT) { + Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value)); + Label notObject; + masm.branchTestObject(Assembler::NotEqual, valueAddress, ¬Object); + Register valueObject = masm.extractObject(valueAddress, scratch1); + masm.branchPtrInNurseryChunk(Assembler::Equal, valueObject, scratch2, &postBarrier); + masm.bind(¬Object); + } + } + + masm.jump(&allocated); + masm.bind(&postBarrier); + + LiveGeneralRegisterSet liveVolatileRegisters; + liveVolatileRegisters.add(propertiesReg); + if (object.volatile_()) + liveVolatileRegisters.add(object); + masm.PushRegsInMask(liveVolatileRegisters); + + masm.mov(ImmPtr(cx->runtime()), scratch1); + masm.setupUnalignedABICall(scratch2); + masm.passABIArg(scratch1); + masm.passABIArg(object); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, PostWriteBarrier)); + + masm.PopRegsInMask(liveVolatileRegisters); + + masm.bind(&allocated); + + ValueOperand valueOperand; +#ifdef JS_NUNBOX32 + valueOperand = ValueOperand(scratch1, scratch2); +#else + valueOperand = ValueOperand(scratch1); +#endif + + Label failureStoreOther, failureStoreObject; + + for (size_t i = 0; i < layout.properties().length(); i++) { + const UnboxedLayout::Property& property = layout.properties()[i]; + Address valueAddress(propertiesReg, i * sizeof(IdValuePair) + offsetof(IdValuePair, value)); + Address targetAddress(object, UnboxedPlainObject::offsetOfData() + property.offset); + + masm.loadValue(valueAddress, valueOperand); + + if (property.type == JSVAL_TYPE_OBJECT) { + HeapTypeSet* types = group->maybeGetProperty(IdToTypeId(NameToId(property.name))); + + Label notObject; + masm.branchTestObject(Assembler::NotEqual, valueOperand, + types->mightBeMIRType(MIRType::Null) ? ¬Object : &failureStoreObject); + + Register payloadReg = masm.extractObject(valueOperand, scratch1); + + if (!types->hasType(TypeSet::AnyObjectType())) { + Register scratch = (payloadReg == scratch1) ? scratch2 : scratch1; + masm.guardObjectType(payloadReg, types, scratch, &failureStoreObject); + } + + masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT, + TypedOrValueRegister(MIRType::Object, + AnyRegister(payloadReg)), nullptr); + + if (notObject.used()) { + Label done; + masm.jump(&done); + masm.bind(¬Object); + masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther); + masm.storeUnboxedProperty(targetAddress, JSVAL_TYPE_OBJECT, NullValue(), nullptr); + masm.bind(&done); + } + } else { + masm.storeUnboxedProperty(targetAddress, property.type, + ConstantOrRegister(valueOperand), &failureStoreOther); + } + } + + Label done; + masm.bind(&done); + + if (object != ReturnReg) + masm.movePtr(object, ReturnReg); + + // Restore non-volatile registers which were saved on entry. + if (ScratchDoubleReg.volatile_()) + masm.pop(ScratchDoubleReg); + masm.PopRegsInMask(savedNonVolatileRegisters); + + masm.abiret(); + + masm.bind(&failureStoreOther); + + // There was a failure while storing a value which cannot be stored at all + // in the unboxed object. Initialize the object so it is safe for GC and + // return null. + masm.initUnboxedObjectContents(object, templateObject); + + masm.bind(&failure); + + masm.movePtr(ImmWord(0), object); + masm.jump(&done); + + masm.bind(&failureStoreObject); + + // There was a failure while storing a value to an object slot of the + // unboxed object. If the value is storable, the failure occurred due to + // incomplete type information in the object, so return a token to trigger + // regeneration of the jitcode after a new object is created in the VM. + { + Label isObject; + masm.branchTestObject(Assembler::Equal, valueOperand, &isObject); + masm.branchTestNull(Assembler::NotEqual, valueOperand, &failureStoreOther); + masm.bind(&isObject); + } + + // Initialize the object so it is safe for GC. + masm.initUnboxedObjectContents(object, templateObject); + + masm.movePtr(ImmWord(CLEAR_CONSTRUCTOR_CODE_TOKEN), object); + masm.jump(&done); + + Linker linker(masm); + AutoFlushICache afc("UnboxedObject"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + if (!code) + return false; + + layout.setConstructorCode(code); + return true; +} + +void +UnboxedLayout::detachFromCompartment() +{ + if (isInList()) + remove(); +} + +///////////////////////////////////////////////////////////////////// +// UnboxedPlainObject +///////////////////////////////////////////////////////////////////// + +bool +UnboxedPlainObject::setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property, + const Value& v) +{ + uint8_t* p = &data_[property.offset]; + return SetUnboxedValue(cx, this, NameToId(property.name), p, property.type, v, + /* preBarrier = */ true); +} + +Value +UnboxedPlainObject::getValue(const UnboxedLayout::Property& property, + bool maybeUninitialized /* = false */) +{ + uint8_t* p = &data_[property.offset]; + return GetUnboxedValue(p, property.type, maybeUninitialized); +} + +void +UnboxedPlainObject::trace(JSTracer* trc, JSObject* obj) +{ + if (obj->as().expando_) { + TraceManuallyBarrieredEdge(trc, + reinterpret_cast(&obj->as().expando_), + "unboxed_expando"); + } + + const UnboxedLayout& layout = obj->as().layoutDontCheckGeneration(); + const int32_t* list = layout.traceList(); + if (!list) + return; + + uint8_t* data = obj->as().data(); + while (*list != -1) { + GCPtrString* heap = reinterpret_cast(data + *list); + TraceEdge(trc, heap, "unboxed_string"); + list++; + } + list++; + while (*list != -1) { + GCPtrObject* heap = reinterpret_cast(data + *list); + TraceNullableEdge(trc, heap, "unboxed_object"); + list++; + } + + // Unboxed objects don't have Values to trace. + MOZ_ASSERT(*(list + 1) == -1); +} + +/* static */ UnboxedExpandoObject* +UnboxedPlainObject::ensureExpando(JSContext* cx, Handle obj) +{ + if (obj->expando_) + return obj->expando_; + + UnboxedExpandoObject* expando = + NewObjectWithGivenProto(cx, nullptr, gc::AllocKind::OBJECT4); + if (!expando) + return nullptr; + + // Don't track property types for expando objects. This allows Baseline + // and Ion AddSlot ICs to guard on the unboxed group without guarding on + // the expando group. + MarkObjectGroupUnknownProperties(cx, expando->group()); + + // If the expando is tenured then the original object must also be tenured. + // Otherwise barriers triggered on the original object for writes to the + // expando (as can happen in the JIT) won't see the tenured->nursery edge. + // See WholeCellEdges::mark. + MOZ_ASSERT_IF(!IsInsideNursery(expando), !IsInsideNursery(obj)); + + // As with setValue(), we need to manually trigger post barriers on the + // whole object. If we treat the field as a GCPtrObject and later + // convert the object to its native representation, we will end up with a + // corrupted store buffer entry. + if (IsInsideNursery(expando) && !IsInsideNursery(obj)) + cx->runtime()->gc.storeBuffer.putWholeCell(obj); + + obj->expando_ = expando; + return expando; +} + +bool +UnboxedPlainObject::containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const +{ + if (layout().lookup(id)) + return true; + + if (maybeExpando() && maybeExpando()->containsShapeOrElement(cx, id)) + return true; + + return false; +} + +static bool +PropagatePropertyTypes(JSContext* cx, jsid id, ObjectGroup* oldGroup, ObjectGroup* newGroup) +{ + HeapTypeSet* typeProperty = oldGroup->maybeGetProperty(id); + TypeSet::TypeList types; + if (!typeProperty->enumerateTypes(&types)) { + ReportOutOfMemory(cx); + return false; + } + for (size_t j = 0; j < types.length(); j++) + AddTypePropertyId(cx, newGroup, nullptr, id, types[j]); + return true; +} + +static PlainObject* +MakeReplacementTemplateObject(JSContext* cx, HandleObjectGroup group, const UnboxedLayout &layout) +{ + PlainObject* obj = NewObjectWithGroup(cx, group, layout.getAllocKind(), + TenuredObject); + if (!obj) + return nullptr; + + for (size_t i = 0; i < layout.properties().length(); i++) { + const UnboxedLayout::Property& property = layout.properties()[i]; + if (!obj->addDataProperty(cx, NameToId(property.name), i, JSPROP_ENUMERATE)) + return nullptr; + MOZ_ASSERT(obj->slotSpan() == i + 1); + MOZ_ASSERT(!obj->inDictionaryMode()); + } + + return obj; +} + +/* static */ bool +UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) +{ + AutoEnterAnalysis enter(cx); + + UnboxedLayout& layout = group->unboxedLayout(); + Rooted proto(cx, group->proto()); + + MOZ_ASSERT(!layout.nativeGroup()); + + RootedObjectGroup replacementGroup(cx); + + // Immediately clear any new script on the group. This is done by replacing + // the existing new script with one for a replacement default new group. + // This is done so that the size of the replacment group's objects is the + // same as that for the unboxed group, so that we do not see polymorphic + // slot accesses later on for sites that see converted objects from this + // group and objects that were allocated using the replacement new group. + if (layout.newScript()) { + replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto); + if (!replacementGroup) + return false; + + PlainObject* templateObject = MakeReplacementTemplateObject(cx, replacementGroup, layout); + if (!templateObject) + return false; + + TypeNewScript* replacementNewScript = + TypeNewScript::makeNativeVersion(cx, layout.newScript(), templateObject); + if (!replacementNewScript) + return false; + + replacementGroup->setNewScript(replacementNewScript); + gc::TraceTypeNewScript(replacementGroup); + + group->clearNewScript(cx, replacementGroup); + } + + // Similarly, if this group is keyed to an allocation site, replace its + // entry with a new group that has no unboxed layout. + if (layout.allocationScript()) { + RootedScript script(cx, layout.allocationScript()); + jsbytecode* pc = layout.allocationPc(); + + replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto); + if (!replacementGroup) + return false; + + PlainObject* templateObject = &script->getObject(pc)->as(); + replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty()); + + cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object, + replacementGroup); + + // Clear any baseline information at this opcode which might use the old group. + if (script->hasBaselineScript()) { + jit::ICEntry& entry = script->baselineScript()->icEntryFromPCOffset(script->pcToOffset(pc)); + jit::ICFallbackStub* fallback = entry.fallbackStub(); + for (jit::ICStubIterator iter = fallback->beginChain(); !iter.atEnd(); iter++) + iter.unlink(cx); + if (fallback->isNewObject_Fallback()) + fallback->toNewObject_Fallback()->setTemplateObject(nullptr); + else if (fallback->isNewArray_Fallback()) + fallback->toNewArray_Fallback()->setTemplateGroup(replacementGroup); + } + } + + size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind()); + + RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0)); + if (!shape) + return false; + + // Add shapes for each property, if this is for a plain object. + for (size_t i = 0; i < layout.properties().length(); i++) { + const UnboxedLayout::Property& property = layout.properties()[i]; + + Rooted child(cx, StackShape(shape->base()->unowned(), NameToId(property.name), + i, JSPROP_ENUMERATE, 0)); + shape = cx->zone()->propertyTree.getChild(cx, shape, child); + if (!shape) + return false; + } + + ObjectGroup* nativeGroup = + ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto, + group->flags() & OBJECT_FLAG_DYNAMIC_MASK); + if (!nativeGroup) + return false; + + // No sense propagating if we don't know what we started with. + if (!group->unknownProperties()) { + // Propagate all property types from the old group to the new group. + for (size_t i = 0; i < layout.properties().length(); i++) { + const UnboxedLayout::Property& property = layout.properties()[i]; + jsid id = NameToId(property.name); + if (!PropagatePropertyTypes(cx, id, group, nativeGroup)) + return false; + + // If we are OOM we may not be able to propagate properties. + if (nativeGroup->unknownProperties()) + break; + + HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id); + if (nativeProperty && nativeProperty->canSetDefinite(i)) + nativeProperty->setDefinite(i); + } + } else { + // If we skip, though, the new group had better agree. + MOZ_ASSERT(nativeGroup->unknownProperties()); + } + + layout.nativeGroup_ = nativeGroup; + layout.nativeShape_ = shape; + layout.replacementGroup_ = replacementGroup; + + nativeGroup->setOriginalUnboxedGroup(group); + + group->markStateChange(cx); + + return true; +} + +/* static */ bool +UnboxedPlainObject::convertToNative(JSContext* cx, JSObject* obj) +{ + const UnboxedLayout& layout = obj->as().layout(); + UnboxedExpandoObject* expando = obj->as().maybeExpando(); + + if (!layout.nativeGroup()) { + if (!UnboxedLayout::makeNativeGroup(cx, obj->group())) + return false; + + // makeNativeGroup can reentrantly invoke this method. + if (obj->is()) + return true; + } + + AutoValueVector values(cx); + for (size_t i = 0; i < layout.properties().length(); i++) { + // We might be reading properties off the object which have not been + // initialized yet. Make sure any double values we read here are + // canonicalized. + if (!values.append(obj->as().getValue(layout.properties()[i], true))) + return false; + } + + // We are eliminating the expando edge with the conversion, so trigger a + // pre barrier. + JSObject::writeBarrierPre(expando); + + // Additionally trigger a post barrier on the expando itself. Whole cell + // store buffer entries can be added on the original unboxed object for + // writes to the expando (see WholeCellEdges::trace), so after conversion + // we need to make sure the expando itself will still be traced. + if (expando && !IsInsideNursery(expando)) + cx->runtime()->gc.storeBuffer.putWholeCell(expando); + + obj->setGroup(layout.nativeGroup()); + obj->as().setLastPropertyMakeNative(cx, layout.nativeShape()); + + for (size_t i = 0; i < values.length(); i++) + obj->as().initSlotUnchecked(i, values[i]); + + if (expando) { + // Add properties from the expando object to the object, in order. + // Suppress GC here, so that callers don't need to worry about this + // method collecting. The stuff below can only fail due to OOM, in + // which case the object will not have been completely filled back in. + gc::AutoSuppressGC suppress(cx); + + Vector ids(cx); + for (Shape::Range r(expando->lastProperty()); !r.empty(); r.popFront()) { + if (!ids.append(r.front().propid())) + return false; + } + for (size_t i = 0; i < expando->getDenseInitializedLength(); i++) { + if (!expando->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) { + if (!ids.append(INT_TO_JSID(i))) + return false; + } + } + ::Reverse(ids.begin(), ids.end()); + + RootedPlainObject nobj(cx, &obj->as()); + Rooted nexpando(cx, expando); + RootedId id(cx); + Rooted desc(cx); + for (size_t i = 0; i < ids.length(); i++) { + id = ids[i]; + if (!GetOwnPropertyDescriptor(cx, nexpando, id, &desc)) + return false; + ObjectOpResult result; + if (!DefineProperty(cx, nobj, id, desc, result)) + return false; + MOZ_ASSERT(result.ok()); + } + } + + return true; +} + +/* static */ +UnboxedPlainObject* +UnboxedPlainObject::create(ExclusiveContext* cx, HandleObjectGroup group, NewObjectKind newKind) +{ + AutoSetNewObjectMetadata metadata(cx); + + MOZ_ASSERT(group->clasp() == &class_); + gc::AllocKind allocKind = group->unboxedLayout().getAllocKind(); + + UnboxedPlainObject* res = + NewObjectWithGroup(cx, group, allocKind, newKind); + if (!res) + return nullptr; + + // Overwrite the dummy shape which was written to the object's expando field. + res->initExpando(); + + // Initialize reference fields of the object. All fields in the object will + // be overwritten shortly, but references need to be safe for the GC. + const int32_t* list = res->layout().traceList(); + if (list) { + uint8_t* data = res->data(); + while (*list != -1) { + GCPtrString* heap = reinterpret_cast(data + *list); + heap->init(cx->names().empty); + list++; + } + list++; + while (*list != -1) { + GCPtrObject* heap = reinterpret_cast(data + *list); + heap->init(nullptr); + list++; + } + // Unboxed objects don't have Values to initialize. + MOZ_ASSERT(*(list + 1) == -1); + } + + return res; +} + +/* static */ JSObject* +UnboxedPlainObject::createWithProperties(ExclusiveContext* cx, HandleObjectGroup group, + NewObjectKind newKind, IdValuePair* properties) +{ + MOZ_ASSERT(newKind == GenericObject || newKind == TenuredObject); + + UnboxedLayout& layout = group->unboxedLayout(); + + if (layout.constructorCode()) { + MOZ_ASSERT(cx->isJSContext()); + + typedef JSObject* (*ConstructorCodeSignature)(IdValuePair*, NewObjectKind); + ConstructorCodeSignature function = + reinterpret_cast(layout.constructorCode()->raw()); + + JSObject* obj; + { + JS::AutoSuppressGCAnalysis nogc; + obj = reinterpret_cast(CALL_GENERATED_2(function, properties, newKind)); + } + if (obj > reinterpret_cast(CLEAR_CONSTRUCTOR_CODE_TOKEN)) + return obj; + + if (obj == reinterpret_cast(CLEAR_CONSTRUCTOR_CODE_TOKEN)) + layout.setConstructorCode(nullptr); + } + + UnboxedPlainObject* obj = UnboxedPlainObject::create(cx, group, newKind); + if (!obj) + return nullptr; + + for (size_t i = 0; i < layout.properties().length(); i++) { + if (!obj->setValue(cx, layout.properties()[i], properties[i].value)) + return NewPlainObjectWithProperties(cx, properties, layout.properties().length(), newKind); + } + +#ifndef JS_CODEGEN_NONE + if (cx->isJSContext() && + !group->unknownProperties() && + !layout.constructorCode() && + cx->asJSContext()->runtime()->jitSupportsFloatingPoint && + jit::CanLikelyAllocateMoreExecutableMemory()) + { + if (!UnboxedLayout::makeConstructorCode(cx->asJSContext(), group)) + return nullptr; + } +#endif + + return obj; +} + +/* static */ bool +UnboxedPlainObject::obj_lookupProperty(JSContext* cx, HandleObject obj, + HandleId id, MutableHandleObject objp, + MutableHandleShape propp) +{ + if (obj->as().containsUnboxedOrExpandoProperty(cx, id)) { + MarkNonNativePropertyFound(propp); + objp.set(obj); + return true; + } + + RootedObject proto(cx, obj->staticPrototype()); + if (!proto) { + objp.set(nullptr); + propp.set(nullptr); + return true; + } + + return LookupProperty(cx, proto, id, objp, propp); +} + +/* static */ bool +UnboxedPlainObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, + Handle desc, + ObjectOpResult& result) +{ + const UnboxedLayout& layout = obj->as().layout(); + + if (const UnboxedLayout::Property* property = layout.lookup(id)) { + if (!desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) { + // This define is equivalent to setting an existing property. + if (obj->as().setValue(cx, *property, desc.value())) + return result.succeed(); + } + + // Trying to incompatibly redefine an existing property requires the + // object to be converted to a native object. + if (!convertToNative(cx, obj)) + return false; + + return DefineProperty(cx, obj, id, desc, result); + } + + // Define the property on the expando object. + Rooted expando(cx, ensureExpando(cx, obj.as())); + if (!expando) + return false; + + // Update property types on the unboxed object as well. + AddTypePropertyId(cx, obj, id, desc.value()); + + return DefineProperty(cx, expando, id, desc, result); +} + +/* static */ bool +UnboxedPlainObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) +{ + if (obj->as().containsUnboxedOrExpandoProperty(cx, id)) { + *foundp = true; + return true; + } + + RootedObject proto(cx, obj->staticPrototype()); + if (!proto) { + *foundp = false; + return true; + } + + return HasProperty(cx, proto, id, foundp); +} + +/* static */ bool +UnboxedPlainObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, + HandleId id, MutableHandleValue vp) +{ + const UnboxedLayout& layout = obj->as().layout(); + + if (const UnboxedLayout::Property* property = layout.lookup(id)) { + vp.set(obj->as().getValue(*property)); + return true; + } + + if (UnboxedExpandoObject* expando = obj->as().maybeExpando()) { + if (expando->containsShapeOrElement(cx, id)) { + RootedObject nexpando(cx, expando); + return GetProperty(cx, nexpando, receiver, id, vp); + } + } + + RootedObject proto(cx, obj->staticPrototype()); + if (!proto) { + vp.setUndefined(); + return true; + } + + return GetProperty(cx, proto, receiver, id, vp); +} + +/* static */ bool +UnboxedPlainObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, + HandleValue receiver, ObjectOpResult& result) +{ + const UnboxedLayout& layout = obj->as().layout(); + + if (const UnboxedLayout::Property* property = layout.lookup(id)) { + if (receiver.isObject() && obj == &receiver.toObject()) { + if (obj->as().setValue(cx, *property, v)) + return result.succeed(); + + if (!convertToNative(cx, obj)) + return false; + return SetProperty(cx, obj, id, v, receiver, result); + } + + return SetPropertyByDefining(cx, id, v, receiver, result); + } + + if (UnboxedExpandoObject* expando = obj->as().maybeExpando()) { + if (expando->containsShapeOrElement(cx, id)) { + // Update property types on the unboxed object as well. + AddTypePropertyId(cx, obj, id, v); + + RootedObject nexpando(cx, expando); + return SetProperty(cx, nexpando, id, v, receiver, result); + } + } + + return SetPropertyOnProto(cx, obj, id, v, receiver, result); +} + +/* static */ bool +UnboxedPlainObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, + MutableHandle desc) +{ + const UnboxedLayout& layout = obj->as().layout(); + + if (const UnboxedLayout::Property* property = layout.lookup(id)) { + desc.value().set(obj->as().getValue(*property)); + desc.setAttributes(JSPROP_ENUMERATE); + desc.object().set(obj); + return true; + } + + if (UnboxedExpandoObject* expando = obj->as().maybeExpando()) { + if (expando->containsShapeOrElement(cx, id)) { + RootedObject nexpando(cx, expando); + if (!GetOwnPropertyDescriptor(cx, nexpando, id, desc)) + return false; + if (desc.object() == nexpando) + desc.object().set(obj); + return true; + } + } + + desc.object().set(nullptr); + return true; +} + +/* static */ bool +UnboxedPlainObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result) +{ + if (!convertToNative(cx, obj)) + return false; + return DeleteProperty(cx, obj, id, result); +} + +/* static */ bool +UnboxedPlainObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, + bool enumerableOnly) +{ + // Ignore expando properties here, they are special-cased by the property + // enumeration code. + + const UnboxedLayout::PropertyVector& unboxed = obj->as().layout().properties(); + for (size_t i = 0; i < unboxed.length(); i++) { + if (!properties.append(NameToId(unboxed[i].name))) + return false; + } + + return true; +} + +const Class UnboxedExpandoObject::class_ = { + "UnboxedExpandoObject", + 0 +}; + +static const ClassOps UnboxedPlainObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + UnboxedPlainObject::trace, +}; + +static const ObjectOps UnboxedPlainObjectObjectOps = { + UnboxedPlainObject::obj_lookupProperty, + UnboxedPlainObject::obj_defineProperty, + UnboxedPlainObject::obj_hasProperty, + UnboxedPlainObject::obj_getProperty, + UnboxedPlainObject::obj_setProperty, + UnboxedPlainObject::obj_getOwnPropertyDescriptor, + UnboxedPlainObject::obj_deleteProperty, + nullptr, /* getElements */ + UnboxedPlainObject::obj_enumerate, + nullptr /* funToString */ +}; + +const Class UnboxedPlainObject::class_ = { + js_Object_str, + Class::NON_NATIVE | + JSCLASS_HAS_CACHED_PROTO(JSProto_Object) | + JSCLASS_DELAY_METADATA_BUILDER, + &UnboxedPlainObjectClassOps, + JS_NULL_CLASS_SPEC, + JS_NULL_CLASS_EXT, + &UnboxedPlainObjectObjectOps +}; + +///////////////////////////////////////////////////////////////////// +// API +///////////////////////////////////////////////////////////////////// + +static inline Value +NextValue(Handle> values, size_t* valueCursor) +{ + return values[(*valueCursor)++]; +} + +void +UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx, + Handle> values, size_t* valueCursor) +{ + initExpando(); + memset(data(), 0, layout().size()); + for (size_t i = 0; i < layout().properties().length(); i++) + JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor))); +} diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h new file mode 100644 index 000000000..ba66434bc --- /dev/null +++ b/js/src/vm/UnboxedObject.h @@ -0,0 +1,319 @@ +/* -*- 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/. */ + +#ifndef vm_UnboxedObject_h +#define vm_UnboxedObject_h + +#include "jsgc.h" +#include "jsobj.h" + +#include "vm/Runtime.h" +#include "vm/TypeInference.h" + +namespace js { + +// Memory required for an unboxed value of a given type. Returns zero for types +// which can't be used for unboxed objects. +static inline size_t +UnboxedTypeSize(JSValueType type) +{ + switch (type) { + case JSVAL_TYPE_BOOLEAN: return 1; + case JSVAL_TYPE_INT32: return 4; + case JSVAL_TYPE_DOUBLE: return 8; + case JSVAL_TYPE_STRING: return sizeof(void*); + case JSVAL_TYPE_OBJECT: return sizeof(void*); + default: return 0; + } +} + +static inline bool +UnboxedTypeNeedsPreBarrier(JSValueType type) +{ + return type == JSVAL_TYPE_STRING || type == JSVAL_TYPE_OBJECT; +} + +static inline bool +UnboxedTypeNeedsPostBarrier(JSValueType type) +{ + return type == JSVAL_TYPE_OBJECT; +} + +// Class tracking information specific to unboxed objects. +class UnboxedLayout : public mozilla::LinkedListElement +{ + public: + struct Property { + PropertyName* name; + uint32_t offset; + JSValueType type; + + Property() + : name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC) + {} + }; + + typedef Vector PropertyVector; + + private: + // If objects in this group have ever been converted to native objects, + // these store the corresponding native group and initial shape for such + // objects. Type information for this object is reflected in nativeGroup. + GCPtrObjectGroup nativeGroup_; + GCPtrShape nativeShape_; + + // Any script/pc which the associated group is created for. + GCPtrScript allocationScript_; + jsbytecode* allocationPc_; + + // If nativeGroup is set and this object originally had a TypeNewScript or + // was keyed to an allocation site, this points to the group which replaced + // this one. This link is only needed to keep the replacement group from + // being GC'ed. If it were GC'ed and a new one regenerated later, that new + // group might have a different allocation kind from this group. + GCPtrObjectGroup replacementGroup_; + + // The following members are only used for unboxed plain objects. + + // All properties on objects with this layout, in enumeration order. + PropertyVector properties_; + + // Byte size of the data for objects with this layout. + size_t size_; + + // Any 'new' script information associated with this layout. + TypeNewScript* newScript_; + + // List for use in tracing objects with this layout. This has the same + // structure as the trace list on a TypeDescr. + int32_t* traceList_; + + // If this layout has been used to construct script or JSON constant + // objects, this code might be filled in to more quickly fill in objects + // from an array of values. + GCPtrJitCode constructorCode_; + + public: + UnboxedLayout() + : nativeGroup_(nullptr), nativeShape_(nullptr), + allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr), + size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr) + {} + + bool initProperties(const PropertyVector& properties, size_t size) { + size_ = size; + return properties_.appendAll(properties); + } + + ~UnboxedLayout() { + if (newScript_) + newScript_->clear(); + js_delete(newScript_); + js_free(traceList_); + + nativeGroup_.init(nullptr); + nativeShape_.init(nullptr); + replacementGroup_.init(nullptr); + constructorCode_.init(nullptr); + } + + void detachFromCompartment(); + + const PropertyVector& properties() const { + return properties_; + } + + TypeNewScript* newScript() const { + return newScript_; + } + + void setNewScript(TypeNewScript* newScript, bool writeBarrier = true); + + JSScript* allocationScript() const { + return allocationScript_; + } + + jsbytecode* allocationPc() const { + return allocationPc_; + } + + void setAllocationSite(JSScript* script, jsbytecode* pc) { + allocationScript_ = script; + allocationPc_ = pc; + } + + const int32_t* traceList() const { + return traceList_; + } + + void setTraceList(int32_t* traceList) { + traceList_ = traceList; + } + + const Property* lookup(JSAtom* atom) const { + for (size_t i = 0; i < properties_.length(); i++) { + if (properties_[i].name == atom) + return &properties_[i]; + } + return nullptr; + } + + const Property* lookup(jsid id) const { + if (JSID_IS_STRING(id)) + return lookup(JSID_TO_ATOM(id)); + return nullptr; + } + + size_t size() const { + return size_; + } + + ObjectGroup* nativeGroup() const { + return nativeGroup_; + } + + Shape* nativeShape() const { + return nativeShape_; + } + + jit::JitCode* constructorCode() const { + return constructorCode_; + } + + void setConstructorCode(jit::JitCode* code) { + constructorCode_ = code; + } + + inline gc::AllocKind getAllocKind() const; + + void trace(JSTracer* trc); + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + static bool makeNativeGroup(JSContext* cx, ObjectGroup* group); + static bool makeConstructorCode(JSContext* cx, HandleObjectGroup group); +}; + +// Class for expando objects holding extra properties given to an unboxed plain +// object. These objects behave identically to normal native plain objects, and +// have a separate Class to distinguish them for memory usage reporting. +class UnboxedExpandoObject : public NativeObject +{ + public: + static const Class class_; +}; + +// Class for a plain object using an unboxed representation. The physical +// layout of these objects is identical to that of an InlineTypedObject, though +// these objects use an UnboxedLayout instead of a TypeDescr to keep track of +// how their properties are stored. +class UnboxedPlainObject : public JSObject +{ + // Optional object which stores extra properties on this object. This is + // not automatically barriered to avoid problems if the object is converted + // to a native. See ensureExpando(). + UnboxedExpandoObject* expando_; + + // Start of the inline data, which immediately follows the group and extra properties. + uint8_t data_[1]; + + public: + static const Class class_; + + static bool obj_lookupProperty(JSContext* cx, HandleObject obj, + HandleId id, MutableHandleObject objp, + MutableHandleShape propp); + + static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, + Handle desc, + ObjectOpResult& result); + + static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp); + + static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, + HandleId id, MutableHandleValue vp); + + static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, + HandleValue receiver, ObjectOpResult& result); + + static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, + MutableHandle desc); + + static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result); + + static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, + bool enumerableOnly); + static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable); + + inline const UnboxedLayout& layout() const; + + const UnboxedLayout& layoutDontCheckGeneration() const { + return group()->unboxedLayoutDontCheckGeneration(); + } + + uint8_t* data() { + return &data_[0]; + } + + UnboxedExpandoObject* maybeExpando() const { + return expando_; + } + + void initExpando() { + expando_ = nullptr; + } + + // For use during GC. + JSObject** addressOfExpando() { + return reinterpret_cast(&expando_); + } + + bool containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const; + + static UnboxedExpandoObject* ensureExpando(JSContext* cx, Handle obj); + + bool setValue(ExclusiveContext* cx, const UnboxedLayout::Property& property, const Value& v); + Value getValue(const UnboxedLayout::Property& property, bool maybeUninitialized = false); + + static bool convertToNative(JSContext* cx, JSObject* obj); + static UnboxedPlainObject* create(ExclusiveContext* cx, HandleObjectGroup group, + NewObjectKind newKind); + static JSObject* createWithProperties(ExclusiveContext* cx, HandleObjectGroup group, + NewObjectKind newKind, IdValuePair* properties); + + void fillAfterConvert(ExclusiveContext* cx, + Handle> values, size_t* valueCursor); + + static void trace(JSTracer* trc, JSObject* object); + + static size_t offsetOfExpando() { + return offsetof(UnboxedPlainObject, expando_); + } + + static size_t offsetOfData() { + return offsetof(UnboxedPlainObject, data_[0]); + } +}; + +inline gc::AllocKind +UnboxedLayout::getAllocKind() const +{ + MOZ_ASSERT(size()); + return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size()); +} + +} // namespace js + +namespace JS { + +template <> +struct DeletePolicy : public js::GCManagedDeletePolicy +{}; + +} /* namespace JS */ + +#endif /* vm_UnboxedObject_h */ -- cgit v1.2.3 From dd57b9273c7c95a7cdabc94854c8dc63b0653f02 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 23 Feb 2020 14:41:40 +0100 Subject: Revert #1137 - Remove unboxed arrays - accounting for removal of watch()/unwatch() - updated for intermediate code changes. --- js/src/vm/Interpreter-inl.h | 2 +- js/src/vm/Interpreter.cpp | 16 +- js/src/vm/JSONParser.cpp | 4 +- js/src/vm/NativeObject-inl.h | 32 -- js/src/vm/NativeObject.h | 15 +- js/src/vm/ObjectGroup.cpp | 103 +++++- js/src/vm/ObjectGroup.h | 10 +- js/src/vm/Opcodes.h | 12 +- js/src/vm/ReceiverGuard.cpp | 8 +- js/src/vm/Stack.cpp | 2 +- js/src/vm/Stack.h | 2 +- js/src/vm/TypeInference.cpp | 6 +- js/src/vm/TypeInference.h | 4 +- js/src/vm/UnboxedObject-inl.h | 663 ++++++++++++++++++++++++++++++++++ js/src/vm/UnboxedObject.cpp | 816 +++++++++++++++++++++++++++++++++++++++++- js/src/vm/UnboxedObject.h | 207 ++++++++++- 16 files changed, 1814 insertions(+), 88 deletions(-) (limited to 'js/src/vm') diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index acfa8f74b..adefa6e93 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -593,7 +593,7 @@ InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t JSOp op = JSOp(*pc); MOZ_ASSERT(op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC); - MOZ_ASSERT(obj->is()); + MOZ_ASSERT(obj->is() || obj->is()); if (op == JSOP_INITELEM_INC && index == INT32_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SPREAD_TOO_LARGE); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index b87d12924..834084c4d 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1939,7 +1939,6 @@ CASE(EnableInterruptsPseudoOpcode) /* Various 1-byte no-ops. */ CASE(JSOP_NOP) CASE(JSOP_NOP_DESTRUCTURING) -CASE(JSOP_UNUSED126) CASE(JSOP_UNUSED211) CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE) CASE(JSOP_UNUSED221) @@ -3682,6 +3681,7 @@ CASE(JSOP_NEWINIT) END_CASE(JSOP_NEWINIT) CASE(JSOP_NEWARRAY) +CASE(JSOP_SPREADCALLARRAY) { uint32_t length = GET_UINT32(REGS.pc); JSObject* obj = NewArrayOperation(cx, script, REGS.pc, length); @@ -4979,7 +4979,7 @@ js::NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc, newKind = TenuredObject; } - RootedPlainObject obj(cx); + RootedObject obj(cx); if (*pc == JSOP_NEWOBJECT) { RootedPlainObject baseObject(cx, &script->getObject(pc)->as()); @@ -5047,6 +5047,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32 if (group->shouldPreTenure() || group->maybePreliminaryObjects()) newKind = TenuredObject; + + if (group->maybeUnboxedLayout()) + return UnboxedArrayObject::create(cx, group, length, newKind); } ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind); @@ -5057,6 +5060,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32 MOZ_ASSERT(obj->isSingleton()); } else { obj->setGroup(group); + + if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects()) + preliminaryObjects->registerNewObject(obj); } return obj; @@ -5069,6 +5075,12 @@ js::NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject) NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject; + if (templateObject->is()) { + uint32_t length = templateObject->as().length(); + RootedObjectGroup group(cx, templateObject->group()); + return UnboxedArrayObject::create(cx, group, length, newKind); + } + ArrayObject* obj = NewDenseFullyAllocatedArray(cx, templateObject->as().length(), nullptr, newKind); if (!obj) diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp index e50da3bc4..01883bb15 100644 --- a/js/src/vm/JSONParser.cpp +++ b/js/src/vm/JSONParser.cpp @@ -606,8 +606,8 @@ JSONParserBase::finishArray(MutableHandleValue vp, ElementVector& elements) { MOZ_ASSERT(&elements == &stack.back().elements()); - ArrayObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(), - GenericObject); + JSObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(), + GenericObject); if (!obj) return false; diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h index 030d92c12..205d99aa2 100644 --- a/js/src/vm/NativeObject-inl.h +++ b/js/src/vm/NativeObject-inl.h @@ -235,38 +235,6 @@ NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t return DenseElementResult::Success; } -inline DenseElementResult -NativeObject::setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp, - uint32_t count, - ShouldUpdateTypes updateTypes) -{ - if (denseElementsAreFrozen()) - return DenseElementResult::Incomplete; - - if (is() && - !as().lengthIsWritable() && - start + count >= as().length()) - { - return DenseElementResult::Incomplete; - } - - DenseElementResult result = ensureDenseElements(cx, start, count); - if (result != DenseElementResult::Success) - return result; - - if (is() && start + count >= as().length()) - as().setLengthInt32(start + count); - - if (updateTypes == ShouldUpdateTypes::DontUpdate && !shouldConvertDoubleElements()) { - copyDenseElements(start, vp, count); - } else { - for (size_t i = 0; i < count; i++) - setDenseElementWithType(cx, start + i, vp[i]); - } - - return DenseElementResult::Success; -} - inline Value NativeObject::getDenseOrTypedArrayElement(uint32_t idx) { diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index c774308af..3a3e50244 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -339,19 +339,16 @@ IsObjectValueInCompartment(const Value& v, JSCompartment* comp); #endif // Operations which change an object's dense elements can either succeed, fail, -// or be unable to complete. The latter is used when the object's elements must -// become sparse instead. The enum below is used for such operations. +// or be unable to complete. For native objects, the latter is used when the +// object's elements must become sparse instead. The enum below is used for +// such operations, and for similar operations on unboxed arrays and methods +// that work on both kinds of objects. enum class DenseElementResult { Failure, Success, Incomplete }; -enum class ShouldUpdateTypes { - Update, - DontUpdate -}; - /* * NativeObject specifies the internal implementation of a native object. * @@ -1154,10 +1151,6 @@ class NativeObject : public ShapedObject elementsRangeWriteBarrierPost(dstStart, count); } - inline DenseElementResult - setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp, uint32_t count, - ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update); - bool shouldConvertDoubleElements() { return getElementsHeader()->shouldConvertDoubleElements(); } diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index 95fcada94..d05a48646 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -777,7 +777,7 @@ GetValueTypeForTable(const Value& v) return type; } -/* static */ ArrayObject* +/* static */ JSObject* ObjectGroup::newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length, NewObjectKind newKind, NewArrayKind arrayKind) @@ -841,13 +841,56 @@ ObjectGroup::newArrayObject(ExclusiveContext* cx, 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_(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. + // 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); } @@ -857,15 +900,49 @@ GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target) { MOZ_ASSERT(source->group() != target->group()); - if (!target->is() || !source->is()) { + if (!target->is() && !target->is()) return true; + + if (target->group()->maybePreliminaryObjects()) { + bool force = IsInsideNursery(source); + target->group()->maybePreliminaryObjects()->maybeAnalyze(cx, target->group(), force); } - source->setGroup(target->group()); + if (target->is()) { + ObjectGroup* sourceGroup = source->group(); - for (size_t i = 0; i < source->as().getDenseInitializedLength(); i++) { - Value v = source->as().getDenseElement(i); - AddTypePropertyId(cx, source->group(), source, JSID_VOID, v); + if (source->is()) { + Shape* shape = target->as().lastProperty(); + if (!UnboxedArrayObject::convertToNativeWithGroup(cx, source, target->group(), shape)) + return false; + } else if (source->is()) { + 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().getDenseInitializedLength(); i++) { + Value v = source->as().getDenseElement(i); + AddTypePropertyId(cx, source->group(), source, JSID_VOID, v); + } + + return true; + } + + if (target->is()) { + if (!source->is()) + return true; + if (source->as().elementType() != JSVAL_TYPE_INT32) + return true; + if (target->as().elementType() != JSVAL_TYPE_DOUBLE) + return true; + + return source->as().convertInt32ToDouble(cx, target->group()); } return true; @@ -1429,6 +1506,18 @@ ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode* } } + if (kind == JSProto_Array && + (JSOp(*pc) == JSOP_NEWARRAY || IsCallPC(pc)) && + cx->options().unboxedArrays()) + { + PreliminaryObjectArrayWithTemplate* preliminaryObjects = + cx->new_(nullptr); + if (preliminaryObjects) + res->setPreliminaryObjects(preliminaryObjects); + else + cx->recoverFromOutOfMemory(); + } + if (!table->add(p, key, res)) { ReportOutOfMemory(cx); return nullptr; diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h index 553cb8366..4e24de9f1 100644 --- a/js/src/vm/ObjectGroup.h +++ b/js/src/vm/ObjectGroup.h @@ -505,11 +505,11 @@ class ObjectGroup : public gc::TenuredCell UnknownIndex // Make an array with an unknown element type. }; - // Create an ArrayObject with the specified elements and a group specialized - // for the elements. - static ArrayObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length, - NewObjectKind newKind, - NewArrayKind arrayKind = NewArrayKind::Normal); + // Create an ArrayObject or UnboxedArrayObject with the specified elements + // and a group specialized for the elements. + static JSObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length, + NewObjectKind newKind, + NewArrayKind arrayKind = NewArrayKind::Normal); // Create a PlainObject or UnboxedPlainObject with the specified properties // and a group specialized for those properties. diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index e13cd6221..b26336815 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1281,7 +1281,17 @@ * Stack: receiver, obj, propval => obj[propval] */ \ macro(JSOP_GETELEM_SUPER, 125, "getelem-super", NULL, 1, 3, 1, JOF_BYTE |JOF_ELEM|JOF_LEFTASSOC) \ - macro(JSOP_UNUSED126, 126, "unused126", NULL, 5, 0, 1, JOF_UINT32) \ + /* + * Pushes newly created array for a spread call onto the stack. This has + * the same semantics as JSOP_NEWARRAY, but is distinguished to avoid + * using unboxed arrays in spread calls, which would make compiling spread + * calls in baseline more complex. + * Category: Literals + * Type: Array + * Operands: uint32_t length + * Stack: => obj + */ \ + macro(JSOP_SPREADCALLARRAY, 126, "spreadcallarray", NULL, 5, 0, 1, JOF_UINT32) \ \ /* * Defines the given function on the current scope. diff --git a/js/src/vm/ReceiverGuard.cpp b/js/src/vm/ReceiverGuard.cpp index e37bf8ee5..11c2d0727 100644 --- a/js/src/vm/ReceiverGuard.cpp +++ b/js/src/vm/ReceiverGuard.cpp @@ -19,7 +19,7 @@ ReceiverGuard::ReceiverGuard(JSObject* obj) group = obj->group(); if (UnboxedExpandoObject* expando = obj->as().maybeExpando()) shape = expando->lastProperty(); - } else if (obj->is()) { + } else if (obj->is() || obj->is()) { group = obj->group(); } else { shape = obj->maybeShape(); @@ -34,7 +34,7 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape) const Class* clasp = group->clasp(); if (clasp == &UnboxedPlainObject::class_) { // Keep both group and shape. - } else if (IsTypedObjectClass(clasp)) { + } else if (clasp == &UnboxedArrayObject::class_ || IsTypedObjectClass(clasp)) { this->shape = nullptr; } else { this->group = nullptr; @@ -49,8 +49,8 @@ HeapReceiverGuard::keyBits(JSObject* obj) // Both the group and shape need to be guarded for unboxed plain objects. return obj->as().maybeExpando() ? 0 : 1; } - if (obj->is()) { - // Only the group needs to be guarded for typed objects. + if (obj->is() || obj->is()) { + // Only the group needs to be guarded for unboxed arrays and typed objects. return 2; } // Other objects only need the shape to be guarded. diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 95940eeaf..7eb8f4ab8 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -82,7 +82,7 @@ InterpreterFrame::isNonGlobalEvalFrame() const return isEvalFrame() && script()->bodyScope()->as().isNonGlobal(); } -ArrayObject* +JSObject* InterpreterFrame::createRestParameter(JSContext* cx) { MOZ_ASSERT(script()->hasRest()); diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index fe04a00f2..d08544213 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -523,7 +523,7 @@ class InterpreterFrame ArgumentsObject& argsObj() const; void initArgsObj(ArgumentsObject& argsobj); - ArrayObject* createRestParameter(JSContext* cx); + JSObject* createRestParameter(JSContext* cx); /* * Environment chain diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 39206539b..ee5bbef5d 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -2509,7 +2509,7 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid bool js::ClassCanHaveExtraProperties(const Class* clasp) { - if (clasp == &UnboxedPlainObject::class_) + if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_) return false; return clasp->getResolve() || clasp->getOpsLookupProperty() @@ -3400,7 +3400,7 @@ JSFunction::setTypeForScriptedFunction(ExclusiveContext* cx, HandleFunction fun, ///////////////////////////////////////////////////////////////////// void -PreliminaryObjectArray::registerNewObject(PlainObject* res) +PreliminaryObjectArray::registerNewObject(JSObject* res) { // The preliminary object pointers are weak, and won't be swept properly // during nursery collections, so the preliminary objects need to be @@ -3418,7 +3418,7 @@ PreliminaryObjectArray::registerNewObject(PlainObject* res) } void -PreliminaryObjectArray::unregisterObject(PlainObject* obj) +PreliminaryObjectArray::unregisterObject(JSObject* obj) { for (size_t i = 0; i < COUNT; i++) { if (objects[i] == obj) { diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index fd021fc96..537baa21f 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -871,8 +871,8 @@ class PreliminaryObjectArray public: PreliminaryObjectArray() = default; - void registerNewObject(PlainObject* res); - void unregisterObject(PlainObject* obj); + void registerNewObject(JSObject* res); + void unregisterObject(JSObject* obj); JSObject* get(size_t i) const { MOZ_ASSERT(i < COUNT); diff --git a/js/src/vm/UnboxedObject-inl.h b/js/src/vm/UnboxedObject-inl.h index c1468a5b1..93ad7bf28 100644 --- a/js/src/vm/UnboxedObject-inl.h +++ b/js/src/vm/UnboxedObject-inl.h @@ -172,6 +172,669 @@ UnboxedPlainObject::layout() const return group()->unboxedLayout(); } +///////////////////////////////////////////////////////////////////// +// UnboxedArrayObject +///////////////////////////////////////////////////////////////////// + +inline const UnboxedLayout& +UnboxedArrayObject::layout() const +{ + return group()->unboxedLayout(); +} + +inline void +UnboxedArrayObject::setLength(ExclusiveContext* cx, uint32_t length) +{ + if (length > INT32_MAX) { + // Track objects with overflowing lengths in type information. + MarkObjectGroupFlags(cx, this, OBJECT_FLAG_LENGTH_OVERFLOW); + } + + length_ = length; +} + +inline void +UnboxedArrayObject::setInitializedLength(uint32_t initlen) +{ + if (initlen < initializedLength()) { + switch (elementType()) { + case JSVAL_TYPE_STRING: + for (size_t i = initlen; i < initializedLength(); i++) + triggerPreBarrier(i); + break; + case JSVAL_TYPE_OBJECT: + for (size_t i = initlen; i < initializedLength(); i++) + triggerPreBarrier(i); + break; + default: + MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(elementType())); + } + } + setInitializedLengthNoBarrier(initlen); +} + +template +inline bool +UnboxedArrayObject::setElementSpecific(ExclusiveContext* cx, size_t index, const Value& v) +{ + MOZ_ASSERT(index < initializedLength()); + MOZ_ASSERT(Type == elementType()); + uint8_t* p = elements() + index * UnboxedTypeSize(Type); + return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true); +} + +template +inline void +UnboxedArrayObject::setElementNoTypeChangeSpecific(size_t index, const Value& v) +{ + MOZ_ASSERT(index < initializedLength()); + MOZ_ASSERT(Type == elementType()); + uint8_t* p = elements() + index * UnboxedTypeSize(Type); + return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ true); +} + +template +inline bool +UnboxedArrayObject::initElementSpecific(ExclusiveContext* cx, size_t index, const Value& v) +{ + MOZ_ASSERT(index < initializedLength()); + MOZ_ASSERT(Type == elementType()); + uint8_t* p = elements() + index * UnboxedTypeSize(Type); + return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false); +} + +template +inline void +UnboxedArrayObject::initElementNoTypeChangeSpecific(size_t index, const Value& v) +{ + MOZ_ASSERT(index < initializedLength()); + MOZ_ASSERT(Type == elementType()); + uint8_t* p = elements() + index * UnboxedTypeSize(Type); + return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false); +} + +template +inline Value +UnboxedArrayObject::getElementSpecific(size_t index) +{ + MOZ_ASSERT(index < initializedLength()); + MOZ_ASSERT(Type == elementType()); + uint8_t* p = elements() + index * UnboxedTypeSize(Type); + return GetUnboxedValue(p, Type, /* maybeUninitialized = */ false); +} + +template +inline void +UnboxedArrayObject::triggerPreBarrier(size_t index) +{ + MOZ_ASSERT(UnboxedTypeNeedsPreBarrier(Type)); + + uint8_t* p = elements() + index * UnboxedTypeSize(Type); + + switch (Type) { + case JSVAL_TYPE_STRING: { + JSString** np = reinterpret_cast(p); + JSString::writeBarrierPre(*np); + break; + } + + case JSVAL_TYPE_OBJECT: { + JSObject** np = reinterpret_cast(p); + JSObject::writeBarrierPre(*np); + break; + } + + default: + MOZ_CRASH("Bad type"); + } +} + +///////////////////////////////////////////////////////////////////// +// Combined methods for NativeObject and UnboxedArrayObject accesses. +///////////////////////////////////////////////////////////////////// + +static inline bool +HasAnyBoxedOrUnboxedDenseElements(JSObject* obj) +{ + return obj->isNative() || obj->is(); +} + +static inline size_t +GetAnyBoxedOrUnboxedInitializedLength(JSObject* obj) +{ + if (obj->isNative()) + return obj->as().getDenseInitializedLength(); + if (obj->is()) + return obj->as().initializedLength(); + return 0; +} + +static inline size_t +GetAnyBoxedOrUnboxedCapacity(JSObject* obj) +{ + if (obj->isNative()) + return obj->as().getDenseCapacity(); + if (obj->is()) + return obj->as().capacity(); + return 0; +} + +static inline Value +GetAnyBoxedOrUnboxedDenseElement(JSObject* obj, size_t index) +{ + if (obj->isNative()) + return obj->as().getDenseElement(index); + return obj->as().getElement(index); +} + +static inline size_t +GetAnyBoxedOrUnboxedArrayLength(JSObject* obj) +{ + if (obj->is()) + return obj->as().length(); + return obj->as().length(); +} + +static inline void +SetAnyBoxedOrUnboxedArrayLength(JSContext* cx, JSObject* obj, size_t length) +{ + if (obj->is()) { + MOZ_ASSERT(length >= obj->as().length()); + obj->as().setLength(cx, length); + } else { + MOZ_ASSERT(length >= obj->as().length()); + obj->as().setLength(cx, length); + } +} + +static inline bool +SetAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value) +{ + if (obj->isNative()) { + obj->as().setDenseElementWithType(cx, index, value); + return true; + } + return obj->as().setElement(cx, index, value); +} + +static inline bool +InitAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value) +{ + if (obj->isNative()) { + obj->as().initDenseElementWithType(cx, index, value); + return true; + } + return obj->as().initElement(cx, index, value); +} + +///////////////////////////////////////////////////////////////////// +// Template methods for NativeObject and UnboxedArrayObject accesses. +///////////////////////////////////////////////////////////////////// + +static inline JSValueType +GetBoxedOrUnboxedType(JSObject* obj) +{ + if (obj->isNative()) + return JSVAL_TYPE_MAGIC; + return obj->as().elementType(); +} + +template +static inline bool +HasBoxedOrUnboxedDenseElements(JSObject* obj) +{ + if (Type == JSVAL_TYPE_MAGIC) + return obj->isNative(); + return obj->is() && obj->as().elementType() == Type; +} + +template +static inline size_t +GetBoxedOrUnboxedInitializedLength(JSObject* obj) +{ + if (Type == JSVAL_TYPE_MAGIC) + return obj->as().getDenseInitializedLength(); + return obj->as().initializedLength(); +} + +template +static inline DenseElementResult +SetBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen) +{ + size_t oldInitlen = GetBoxedOrUnboxedInitializedLength(obj); + if (Type == JSVAL_TYPE_MAGIC) { + obj->as().setDenseInitializedLength(initlen); + if (initlen < oldInitlen) + obj->as().shrinkElements(cx, initlen); + } else { + obj->as().setInitializedLength(initlen); + if (initlen < oldInitlen) + obj->as().shrinkElements(cx, initlen); + } + return DenseElementResult::Success; +} + +template +static inline size_t +GetBoxedOrUnboxedCapacity(JSObject* obj) +{ + if (Type == JSVAL_TYPE_MAGIC) + return obj->as().getDenseCapacity(); + return obj->as().capacity(); +} + +template +static inline Value +GetBoxedOrUnboxedDenseElement(JSObject* obj, size_t index) +{ + if (Type == JSVAL_TYPE_MAGIC) + return obj->as().getDenseElement(index); + return obj->as().getElementSpecific(index); +} + +template +static inline void +SetBoxedOrUnboxedDenseElementNoTypeChange(JSObject* obj, size_t index, const Value& value) +{ + if (Type == JSVAL_TYPE_MAGIC) + obj->as().setDenseElement(index, value); + else + obj->as().setElementNoTypeChangeSpecific(index, value); +} + +template +static inline bool +SetBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value) +{ + if (Type == JSVAL_TYPE_MAGIC) { + obj->as().setDenseElementWithType(cx, index, value); + return true; + } + return obj->as().setElementSpecific(cx, index, value); +} + +template +static inline DenseElementResult +EnsureBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count) +{ + if (Type == JSVAL_TYPE_MAGIC) { + if (!obj->as().ensureElements(cx, count)) + return DenseElementResult::Failure; + } else { + if (obj->as().capacity() < count) { + if (!obj->as().growElements(cx, count)) + return DenseElementResult::Failure; + } + } + return DenseElementResult::Success; +} + +template +static inline DenseElementResult +SetOrExtendBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj, + uint32_t start, const Value* vp, uint32_t count, + ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update) +{ + if (Type == JSVAL_TYPE_MAGIC) { + NativeObject* nobj = &obj->as(); + + if (nobj->denseElementsAreFrozen()) + return DenseElementResult::Incomplete; + + if (obj->is() && + !obj->as().lengthIsWritable() && + start + count >= obj->as().length()) + { + return DenseElementResult::Incomplete; + } + + DenseElementResult result = nobj->ensureDenseElements(cx, start, count); + if (result != DenseElementResult::Success) + return result; + + if (obj->is() && start + count >= obj->as().length()) + obj->as().setLengthInt32(start + count); + + if (updateTypes == ShouldUpdateTypes::DontUpdate && !nobj->shouldConvertDoubleElements()) { + nobj->copyDenseElements(start, vp, count); + } else { + for (size_t i = 0; i < count; i++) + nobj->setDenseElementWithType(cx, start + i, vp[i]); + } + + return DenseElementResult::Success; + } + + UnboxedArrayObject* nobj = &obj->as(); + + if (start > nobj->initializedLength()) + return DenseElementResult::Incomplete; + + if (start + count >= UnboxedArrayObject::MaximumCapacity) + return DenseElementResult::Incomplete; + + if (start + count > nobj->capacity() && !nobj->growElements(cx, start + count)) + return DenseElementResult::Failure; + + size_t oldInitlen = nobj->initializedLength(); + + // Overwrite any existing elements covered by the new range. If we fail + // after this point due to some incompatible type being written to the + // object's elements, afterwards the contents will be different from when + // we started. The caller must retry the operation using a generic path, + // which will overwrite the already-modified elements as well as the ones + // that were left alone. + size_t i = 0; + if (updateTypes == ShouldUpdateTypes::DontUpdate) { + for (size_t j = start; i < count && j < oldInitlen; i++, j++) + nobj->setElementNoTypeChangeSpecific(j, vp[i]); + } else { + for (size_t j = start; i < count && j < oldInitlen; i++, j++) { + if (!nobj->setElementSpecific(cx, j, vp[i])) + return DenseElementResult::Incomplete; + } + } + + if (i != count) { + obj->as().setInitializedLength(start + count); + if (updateTypes == ShouldUpdateTypes::DontUpdate) { + for (; i < count; i++) + nobj->initElementNoTypeChangeSpecific(start + i, vp[i]); + } else { + for (; i < count; i++) { + if (!nobj->initElementSpecific(cx, start + i, vp[i])) { + nobj->setInitializedLengthNoBarrier(oldInitlen); + return DenseElementResult::Incomplete; + } + } + } + } + + if (start + count >= nobj->length()) + nobj->setLength(cx, start + count); + + return DenseElementResult::Success; +} + +template +static inline DenseElementResult +MoveBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, uint32_t dstStart, uint32_t srcStart, + uint32_t length) +{ + MOZ_ASSERT(HasBoxedOrUnboxedDenseElements(obj)); + + if (Type == JSVAL_TYPE_MAGIC) { + if (obj->as().denseElementsAreFrozen()) + return DenseElementResult::Incomplete; + + if (!obj->as().maybeCopyElementsForWrite(cx)) + return DenseElementResult::Failure; + obj->as().moveDenseElements(dstStart, srcStart, length); + } else { + uint8_t* data = obj->as().elements(); + size_t elementSize = UnboxedTypeSize(Type); + + if (UnboxedTypeNeedsPreBarrier(Type) && + JS::shadow::Zone::asShadowZone(obj->zone())->needsIncrementalBarrier()) + { + // Trigger pre barriers on any elements we are overwriting. See + // NativeObject::moveDenseElements. No post barrier is needed as + // only whole cell post barriers are used with unboxed objects. + for (size_t i = 0; i < length; i++) + obj->as().triggerPreBarrier(dstStart + i); + } + + memmove(data + dstStart * elementSize, + data + srcStart * elementSize, + length * elementSize); + } + + return DenseElementResult::Success; +} + +template +static inline DenseElementResult +CopyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src, + uint32_t dstStart, uint32_t srcStart, uint32_t length) +{ + MOZ_ASSERT(HasBoxedOrUnboxedDenseElements(src)); + MOZ_ASSERT(HasBoxedOrUnboxedDenseElements(dst)); + MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength(dst) == dstStart); + MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength(src) >= srcStart + length); + MOZ_ASSERT(GetBoxedOrUnboxedCapacity(dst) >= dstStart + length); + + SetBoxedOrUnboxedInitializedLength(cx, dst, dstStart + length); + + if (DstType == JSVAL_TYPE_MAGIC) { + if (SrcType == JSVAL_TYPE_MAGIC) { + const Value* vp = src->as().getDenseElements() + srcStart; + dst->as().initDenseElements(dstStart, vp, length); + } else { + for (size_t i = 0; i < length; i++) { + Value v = GetBoxedOrUnboxedDenseElement(src, srcStart + i); + dst->as().initDenseElement(dstStart + i, v); + } + } + } else if (DstType == SrcType) { + uint8_t* dstData = dst->as().elements(); + uint8_t* srcData = src->as().elements(); + size_t elementSize = UnboxedTypeSize(DstType); + + memcpy(dstData + dstStart * elementSize, + srcData + srcStart * elementSize, + length * elementSize); + + // Add a store buffer entry if we might have copied a nursery pointer to dst. + if (UnboxedTypeNeedsPostBarrier(DstType) && !IsInsideNursery(dst)) + dst->runtimeFromMainThread()->gc.storeBuffer.putWholeCell(dst); + } else if (DstType == JSVAL_TYPE_DOUBLE && SrcType == JSVAL_TYPE_INT32) { + uint8_t* dstData = dst->as().elements(); + uint8_t* srcData = src->as().elements(); + + for (size_t i = 0; i < length; i++) { + int32_t v = *reinterpret_cast(srcData + (srcStart + i) * sizeof(int32_t)); + *reinterpret_cast(dstData + (dstStart + i) * sizeof(double)) = v; + } + } else { + for (size_t i = 0; i < length; i++) { + Value v = GetBoxedOrUnboxedDenseElement(src, srcStart + i); + dst->as().initElementNoTypeChangeSpecific(dstStart + i, v); + } + } + + return DenseElementResult::Success; +} + +///////////////////////////////////////////////////////////////////// +// Dispatch to specialized methods based on the type of an object. +///////////////////////////////////////////////////////////////////// + +// Goop to fix MSVC. See DispatchTraceKindTyped in TraceKind.h. +// The clang-cl front end defines _MSC_VER, but still requires the explicit +// template declaration, so we must test for __clang__ here as well. +#if defined(_MSC_VER) && !defined(__clang__) +# define DEPENDENT_TEMPLATE_HINT +#else +# define DEPENDENT_TEMPLATE_HINT template +#endif + +// Function to dispatch a method specialized to whatever boxed or unboxed dense +// elements which an input object has. +template +DenseElementResult +CallBoxedOrUnboxedSpecialization(F f, JSObject* obj) +{ + if (!HasAnyBoxedOrUnboxedDenseElements(obj)) + return DenseElementResult::Incomplete; + switch (GetBoxedOrUnboxedType(obj)) { + case JSVAL_TYPE_MAGIC: + return f. DEPENDENT_TEMPLATE_HINT operator()(); + case JSVAL_TYPE_BOOLEAN: + return f. DEPENDENT_TEMPLATE_HINT operator()(); + case JSVAL_TYPE_INT32: + return f. DEPENDENT_TEMPLATE_HINT operator()(); + case JSVAL_TYPE_DOUBLE: + return f. DEPENDENT_TEMPLATE_HINT operator()(); + case JSVAL_TYPE_STRING: + return f. DEPENDENT_TEMPLATE_HINT operator()(); + case JSVAL_TYPE_OBJECT: + return f. DEPENDENT_TEMPLATE_HINT operator()(); + default: + MOZ_CRASH(); + } +} + +// As above, except the specialization can reflect the unboxed type of two objects. +template +DenseElementResult +CallBoxedOrUnboxedSpecialization(F f, JSObject* obj1, JSObject* obj2) +{ + if (!HasAnyBoxedOrUnboxedDenseElements(obj1) || !HasAnyBoxedOrUnboxedDenseElements(obj2)) + return DenseElementResult::Incomplete; + +#define SPECIALIZE_OBJ2(TYPE) \ + switch (GetBoxedOrUnboxedType(obj2)) { \ + case JSVAL_TYPE_MAGIC: \ + return f. DEPENDENT_TEMPLATE_HINT operator()(); \ + case JSVAL_TYPE_BOOLEAN: \ + return f. DEPENDENT_TEMPLATE_HINT operator()(); \ + case JSVAL_TYPE_INT32: \ + return f. DEPENDENT_TEMPLATE_HINT operator()(); \ + case JSVAL_TYPE_DOUBLE: \ + return f. DEPENDENT_TEMPLATE_HINT operator()(); \ + case JSVAL_TYPE_STRING: \ + return f. DEPENDENT_TEMPLATE_HINT operator()(); \ + case JSVAL_TYPE_OBJECT: \ + return f. DEPENDENT_TEMPLATE_HINT operator()(); \ + default: \ + MOZ_CRASH(); \ + } + + switch (GetBoxedOrUnboxedType(obj1)) { + case JSVAL_TYPE_MAGIC: + SPECIALIZE_OBJ2(JSVAL_TYPE_MAGIC) + case JSVAL_TYPE_BOOLEAN: + SPECIALIZE_OBJ2(JSVAL_TYPE_BOOLEAN) + case JSVAL_TYPE_INT32: + SPECIALIZE_OBJ2(JSVAL_TYPE_INT32) + case JSVAL_TYPE_DOUBLE: + SPECIALIZE_OBJ2(JSVAL_TYPE_DOUBLE) + case JSVAL_TYPE_STRING: + SPECIALIZE_OBJ2(JSVAL_TYPE_STRING) + case JSVAL_TYPE_OBJECT: + SPECIALIZE_OBJ2(JSVAL_TYPE_OBJECT) + default: + MOZ_CRASH(); + } + +#undef SPECIALIZE_OBJ2 +} + +#undef DEPENDENT_TEMPLATE_HINT + +#define DefineBoxedOrUnboxedFunctor1(Signature, A) \ +struct Signature ## Functor { \ + A a; \ + explicit Signature ## Functor(A a) \ + : a(a) \ + {} \ + template \ + DenseElementResult operator()() { \ + return Signature(a); \ + } \ +} + +#define DefineBoxedOrUnboxedFunctor3(Signature, A, B, C) \ +struct Signature ## Functor { \ + A a; B b; C c; \ + Signature ## Functor(A a, B b, C c) \ + : a(a), b(b), c(c) \ + {} \ + template \ + DenseElementResult operator()() { \ + return Signature(a, b, c); \ + } \ +} + +#define DefineBoxedOrUnboxedFunctor4(Signature, A, B, C, D) \ +struct Signature ## Functor { \ + A a; B b; C c; D d; \ + Signature ## Functor(A a, B b, C c, D d) \ + : a(a), b(b), c(c), d(d) \ + {} \ + template \ + DenseElementResult operator()() { \ + return Signature(a, b, c, d); \ + } \ +} + +#define DefineBoxedOrUnboxedFunctorPair4(Signature, A, B, C, D) \ +struct Signature ## Functor { \ + A a; B b; C c; D d; \ + Signature ## Functor(A a, B b, C c, D d) \ + : a(a), b(b), c(c), d(d) \ + {} \ + template \ + DenseElementResult operator()() { \ + return Signature(a, b, c, d); \ + } \ +} + +#define DefineBoxedOrUnboxedFunctor5(Signature, A, B, C, D, E) \ +struct Signature ## Functor { \ + A a; B b; C c; D d; E e; \ + Signature ## Functor(A a, B b, C c, D d, E e) \ + : a(a), b(b), c(c), d(d), e(e) \ + {} \ + template \ + DenseElementResult operator()() { \ + return Signature(a, b, c, d, e); \ + } \ +} + +#define DefineBoxedOrUnboxedFunctor6(Signature, A, B, C, D, E, F) \ +struct Signature ## Functor { \ + A a; B b; C c; D d; E e; F f; \ + Signature ## Functor(A a, B b, C c, D d, E e, F f) \ + : a(a), b(b), c(c), d(d), e(e), f(f) \ + {} \ + template \ + DenseElementResult operator()() { \ + return Signature(a, b, c, d, e, f); \ + } \ +} + +#define DefineBoxedOrUnboxedFunctorPair6(Signature, A, B, C, D, E, F) \ +struct Signature ## Functor { \ + A a; B b; C c; D d; E e; F f; \ + Signature ## Functor(A a, B b, C c, D d, E e, F f) \ + : a(a), b(b), c(c), d(d), e(e), f(f) \ + {} \ + template \ + DenseElementResult operator()() { \ + return Signature(a, b, c, d, e, f); \ + } \ +} + +DenseElementResult +SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj, + uint32_t start, const Value* vp, uint32_t count, + ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update); + +DenseElementResult +MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, + uint32_t dstStart, uint32_t srcStart, uint32_t length); + +DenseElementResult +CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src, + uint32_t dstStart, uint32_t srcStart, uint32_t length); + +void +SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen); + +DenseElementResult +EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count); + } // namespace js #endif // vm_UnboxedObject_inl_h diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp index 2e017ca3b..059293a2d 100644 --- a/js/src/vm/UnboxedObject.cpp +++ b/js/src/vm/UnboxedObject.cpp @@ -417,6 +417,8 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) RootedObjectGroup replacementGroup(cx); + const Class* clasp = layout.isArray() ? &ArrayObject::class_ : &PlainObject::class_; + // Immediately clear any new script on the group. This is done by replacing // the existing new script with one for a replacement default new group. // This is done so that the size of the replacment group's objects is the @@ -424,6 +426,8 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) // slot accesses later on for sites that see converted objects from this // group and objects that were allocated using the replacement new group. if (layout.newScript()) { + MOZ_ASSERT(!layout.isArray()); + replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto); if (!replacementGroup) return false; @@ -449,14 +453,15 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) RootedScript script(cx, layout.allocationScript()); jsbytecode* pc = layout.allocationPc(); - replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto); + replacementGroup = ObjectGroupCompartment::makeGroup(cx, clasp, proto); if (!replacementGroup) return false; PlainObject* templateObject = &script->getObject(pc)->as(); replacementGroup->addDefiniteProperties(cx, templateObject->lastProperty()); - cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, JSProto_Object, + JSProtoKey key = layout.isArray() ? JSProto_Array : JSProto_Object; + cx->compartment()->objectGroups.replaceAllocationSiteGroup(script, pc, key, replacementGroup); // Clear any baseline information at this opcode which might use the old group. @@ -472,12 +477,22 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) } } - size_t nfixed = gc::GetGCKindSlots(layout.getAllocKind()); + size_t nfixed = layout.isArray() ? 0 : gc::GetGCKindSlots(layout.getAllocKind()); + + if (layout.isArray()) { + // The length shape to use for arrays is cached via a modified initial + // shape for array objects. Create an array now to make sure this entry + // is instantiated. + if (!NewDenseEmptyArray(cx)) + return false; + } - RootedShape shape(cx, EmptyShape::getInitialShape(cx, &PlainObject::class_, proto, nfixed, 0)); + RootedShape shape(cx, EmptyShape::getInitialShape(cx, clasp, proto, nfixed, 0)); if (!shape) return false; + MOZ_ASSERT_IF(layout.isArray(), !shape->isEmptyShape() && shape->slotSpan() == 0); + // Add shapes for each property, if this is for a plain object. for (size_t i = 0; i < layout.properties().length(); i++) { const UnboxedLayout::Property& property = layout.properties()[i]; @@ -490,7 +505,7 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) } ObjectGroup* nativeGroup = - ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto, + ObjectGroupCompartment::makeGroup(cx, clasp, proto, group->flags() & OBJECT_FLAG_DYNAMIC_MASK); if (!nativeGroup) return false; @@ -498,19 +513,24 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group) // No sense propagating if we don't know what we started with. if (!group->unknownProperties()) { // Propagate all property types from the old group to the new group. - for (size_t i = 0; i < layout.properties().length(); i++) { - const UnboxedLayout::Property& property = layout.properties()[i]; - jsid id = NameToId(property.name); - if (!PropagatePropertyTypes(cx, id, group, nativeGroup)) + if (layout.isArray()) { + if (!PropagatePropertyTypes(cx, JSID_VOID, group, nativeGroup)) return false; + } else { + for (size_t i = 0; i < layout.properties().length(); i++) { + const UnboxedLayout::Property& property = layout.properties()[i]; + jsid id = NameToId(property.name); + if (!PropagatePropertyTypes(cx, id, group, nativeGroup)) + return false; - // If we are OOM we may not be able to propagate properties. - if (nativeGroup->unknownProperties()) - break; + // If we are OOM we may not be able to propagate properties. + if (nativeGroup->unknownProperties()) + break; - HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id); - if (nativeProperty && nativeProperty->canSetDefinite(i)) - nativeProperty->setDefinite(i); + HeapTypeSet* nativeProperty = nativeGroup->maybeGetProperty(id); + if (nativeProperty && nativeProperty->canSetDefinite(i)) + nativeProperty->setDefinite(i); + } } } else { // If we skip, though, the new group had better agree. @@ -925,6 +945,692 @@ const Class UnboxedPlainObject::class_ = { &UnboxedPlainObjectObjectOps }; +///////////////////////////////////////////////////////////////////// +// UnboxedArrayObject +///////////////////////////////////////////////////////////////////// + +template +DenseElementResult +AppendUnboxedDenseElements(UnboxedArrayObject* obj, uint32_t initlen, + MutableHandle> values) +{ + for (size_t i = 0; i < initlen; i++) + values.infallibleAppend(obj->getElementSpecific(i)); + return DenseElementResult::Success; +} + +DefineBoxedOrUnboxedFunctor3(AppendUnboxedDenseElements, + UnboxedArrayObject*, uint32_t, MutableHandle>); + +/* static */ bool +UnboxedArrayObject::convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj, + ObjectGroup* group, Shape* shape) +{ + size_t length = obj->as().length(); + size_t initlen = obj->as().initializedLength(); + + Rooted> values(cx, GCVector(cx)); + if (!values.reserve(initlen)) + return false; + + AppendUnboxedDenseElementsFunctor functor(&obj->as(), initlen, &values); + DebugOnly result = CallBoxedOrUnboxedSpecialization(functor, obj); + MOZ_ASSERT(result.value == DenseElementResult::Success); + + obj->setGroup(group); + + ArrayObject* aobj = &obj->as(); + aobj->setLastPropertyMakeNative(cx, shape); + + // Make sure there is at least one element, so that this array does not + // use emptyObjectElements / emptyObjectElementsShared. + if (!aobj->ensureElements(cx, Max(initlen, 1))) + return false; + + MOZ_ASSERT(!aobj->getDenseInitializedLength()); + aobj->setDenseInitializedLength(initlen); + aobj->initDenseElements(0, values.begin(), initlen); + aobj->setLengthInt32(length); + + return true; +} + +/* static */ bool +UnboxedArrayObject::convertToNative(JSContext* cx, JSObject* obj) +{ + const UnboxedLayout& layout = obj->as().layout(); + + if (!layout.nativeGroup()) { + if (!UnboxedLayout::makeNativeGroup(cx, obj->group())) + return false; + } + + return convertToNativeWithGroup(cx, obj, layout.nativeGroup(), layout.nativeShape()); +} + +bool +UnboxedArrayObject::convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group) +{ + MOZ_ASSERT(elementType() == JSVAL_TYPE_INT32); + MOZ_ASSERT(group->unboxedLayout().elementType() == JSVAL_TYPE_DOUBLE); + + Vector values(cx); + if (!values.reserve(initializedLength())) + return false; + for (size_t i = 0; i < initializedLength(); i++) + values.infallibleAppend(getElementSpecific(i).toInt32()); + + uint8_t* newElements; + if (hasInlineElements()) { + newElements = AllocateObjectBuffer(cx, this, capacity() * sizeof(double)); + } else { + newElements = ReallocateObjectBuffer(cx, this, elements(), + capacity() * sizeof(int32_t), + capacity() * sizeof(double)); + } + if (!newElements) + return false; + + setGroup(group); + elements_ = newElements; + + for (size_t i = 0; i < initializedLength(); i++) + setElementNoTypeChangeSpecific(i, DoubleValue(values[i])); + + return true; +} + +/* static */ UnboxedArrayObject* +UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32_t length, + NewObjectKind newKind, uint32_t maxLength) +{ + MOZ_ASSERT(length <= MaximumCapacity); + + MOZ_ASSERT(group->clasp() == &class_); + uint32_t elementSize = UnboxedTypeSize(group->unboxedLayout().elementType()); + uint32_t capacity = Min(length, maxLength); + uint32_t nbytes = offsetOfInlineElements() + elementSize * capacity; + + UnboxedArrayObject* res; + if (nbytes <= JSObject::MAX_BYTE_SIZE) { + gc::AllocKind allocKind = gc::GetGCObjectKindForBytes(nbytes); + + // If there was no provided length information, pick an allocation kind + // to accommodate small arrays (as is done for normal native arrays). + if (capacity == 0) + allocKind = gc::AllocKind::OBJECT8; + + res = NewObjectWithGroup(cx, group, allocKind, newKind); + if (!res) + return nullptr; + res->setInitializedLengthNoBarrier(0); + res->setInlineElements(); + + size_t actualCapacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize; + MOZ_ASSERT(actualCapacity >= capacity); + res->setCapacityIndex(exactCapacityIndex(actualCapacity)); + } else { + res = NewObjectWithGroup(cx, group, gc::AllocKind::OBJECT0, newKind); + if (!res) + return nullptr; + res->setInitializedLengthNoBarrier(0); + + uint32_t capacityIndex = (capacity == length) + ? CapacityMatchesLengthIndex + : chooseCapacityIndex(capacity, length); + uint32_t actualCapacity = computeCapacity(capacityIndex, length); + + res->elements_ = AllocateObjectBuffer(cx, res, actualCapacity * elementSize); + if (!res->elements_) { + // Make the object safe for GC. + res->setInlineElements(); + return nullptr; + } + + res->setCapacityIndex(capacityIndex); + } + + res->setLength(cx, length); + return res; +} + +bool +UnboxedArrayObject::setElement(ExclusiveContext* cx, size_t index, const Value& v) +{ + MOZ_ASSERT(index < initializedLength()); + uint8_t* p = elements() + index * elementSize(); + return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true); +} + +bool +UnboxedArrayObject::initElement(ExclusiveContext* cx, size_t index, const Value& v) +{ + MOZ_ASSERT(index < initializedLength()); + uint8_t* p = elements() + index * elementSize(); + return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false); +} + +void +UnboxedArrayObject::initElementNoTypeChange(size_t index, const Value& v) +{ + MOZ_ASSERT(index < initializedLength()); + uint8_t* p = elements() + index * elementSize(); + if (UnboxedTypeNeedsPreBarrier(elementType())) + *reinterpret_cast(p) = nullptr; + SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false); +} + +Value +UnboxedArrayObject::getElement(size_t index) +{ + MOZ_ASSERT(index < initializedLength()); + uint8_t* p = elements() + index * elementSize(); + return GetUnboxedValue(p, elementType(), /* maybeUninitialized = */ false); +} + +/* static */ void +UnboxedArrayObject::trace(JSTracer* trc, JSObject* obj) +{ + JSValueType type = obj->as().elementType(); + if (!UnboxedTypeNeedsPreBarrier(type)) + return; + + MOZ_ASSERT(obj->as().elementSize() == sizeof(uintptr_t)); + size_t initlen = obj->as().initializedLength(); + void** elements = reinterpret_cast(obj->as().elements()); + + switch (type) { + case JSVAL_TYPE_OBJECT: + for (size_t i = 0; i < initlen; i++) { + GCPtrObject* heap = reinterpret_cast(elements + i); + TraceNullableEdge(trc, heap, "unboxed_object"); + } + break; + + case JSVAL_TYPE_STRING: + for (size_t i = 0; i < initlen; i++) { + GCPtrString* heap = reinterpret_cast(elements + i); + TraceEdge(trc, heap, "unboxed_string"); + } + break; + + default: + MOZ_CRASH(); + } +} + +/* static */ void +UnboxedArrayObject::objectMoved(JSObject* obj, const JSObject* old) +{ + UnboxedArrayObject& dst = obj->as(); + const UnboxedArrayObject& src = old->as(); + + // Fix up possible inline data pointer. + if (src.hasInlineElements()) + dst.setInlineElements(); +} + +/* static */ void +UnboxedArrayObject::finalize(FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(!IsInsideNursery(obj)); + if (!obj->as().hasInlineElements()) + js_free(obj->as().elements()); +} + +/* static */ size_t +UnboxedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src, + gc::AllocKind allocKind) +{ + UnboxedArrayObject* ndst = &dst->as(); + UnboxedArrayObject* nsrc = &src->as(); + MOZ_ASSERT(ndst->elements() == nsrc->elements()); + + Nursery& nursery = trc->runtime()->gc.nursery; + + if (!nursery.isInside(nsrc->elements())) { + nursery.removeMallocedBuffer(nsrc->elements()); + return 0; + } + + // Determine if we can use inline data for the target array. If this is + // possible, the nursery will have picked an allocation size that is large + // enough. + size_t nbytes = nsrc->capacity() * nsrc->elementSize(); + if (offsetOfInlineElements() + nbytes <= GetGCKindBytes(allocKind)) { + ndst->setInlineElements(); + } else { + MOZ_ASSERT(allocKind == gc::AllocKind::OBJECT0); + + AutoEnterOOMUnsafeRegion oomUnsafe; + uint8_t* data = nsrc->zone()->pod_malloc(nbytes); + if (!data) + oomUnsafe.crash("Failed to allocate unboxed array elements while tenuring."); + ndst->elements_ = data; + } + + PodCopy(ndst->elements(), nsrc->elements(), nsrc->initializedLength() * nsrc->elementSize()); + + // Set a forwarding pointer for the element buffers in case they were + // preserved on the stack by Ion. + bool direct = nsrc->capacity() * nsrc->elementSize() >= sizeof(uintptr_t); + nursery.maybeSetForwardingPointer(trc, nsrc->elements(), ndst->elements(), direct); + + return ndst->hasInlineElements() ? 0 : nbytes; +} + +// Possible capacities for unboxed arrays. Some of these capacities might seem +// a little weird, but were chosen to allow the inline data of objects of each +// size to be fully utilized for arrays of the various types on both 32 bit and +// 64 bit platforms. +// +// To find the possible inline capacities, the following script was used: +// +// var fixedSlotCapacities = [0, 2, 4, 8, 12, 16]; +// var dataSizes = [1, 4, 8]; +// var header32 = 4 * 2 + 4 * 2; +// var header64 = 8 * 2 + 4 * 2; +// +// for (var i = 0; i < fixedSlotCapacities.length; i++) { +// var nfixed = fixedSlotCapacities[i]; +// var size32 = 4 * 4 + 8 * nfixed - header32; +// var size64 = 8 * 4 + 8 * nfixed - header64; +// for (var j = 0; j < dataSizes.length; j++) { +// print(size32 / dataSizes[j]); +// print(size64 / dataSizes[j]); +// } +// } +// +/* static */ const uint32_t +UnboxedArrayObject::CapacityArray[] = { + UINT32_MAX, // For CapacityMatchesLengthIndex. + 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 16, 17, 18, 24, 26, 32, 34, 40, 64, 72, 96, 104, 128, 136, + 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, + 1048576, 2097152, 3145728, 4194304, 5242880, 6291456, 7340032, 8388608, 9437184, 11534336, + 13631488, 15728640, 17825792, 20971520, 24117248, 27262976, 31457280, 35651584, 40894464, + 46137344, 52428800, 59768832, MaximumCapacity +}; + +static const uint32_t +Pow2CapacityIndexes[] = { + 2, // 1 + 3, // 2 + 5, // 4 + 8, // 8 + 13, // 16 + 18, // 32 + 21, // 64 + 25, // 128 + 27, // 256 + 28, // 512 + 29, // 1024 + 30, // 2048 + 31, // 4096 + 32, // 8192 + 33, // 16384 + 34, // 32768 + 35, // 65536 + 36, // 131072 + 37, // 262144 + 38, // 524288 + 39 // 1048576 +}; + +static const uint32_t MebiCapacityIndex = 39; + +/* static */ uint32_t +UnboxedArrayObject::chooseCapacityIndex(uint32_t capacity, uint32_t length) +{ + // Note: the structure and behavior of this method follow along with + // NativeObject::goodAllocated. Changes to the allocation strategy in one + // should generally be matched by the other. + + // Make sure we have enough space to store all possible values for the capacity index. + // This ought to be a static_assert, but MSVC doesn't like that. + MOZ_ASSERT(mozilla::ArrayLength(CapacityArray) - 1 <= (CapacityMask >> CapacityShift)); + + // The caller should have ensured the capacity is possible for an unboxed array. + MOZ_ASSERT(capacity <= MaximumCapacity); + + static const uint32_t Mebi = 1024 * 1024; + + if (capacity <= Mebi) { + capacity = mozilla::RoundUpPow2(capacity); + + // When the required capacity is close to the array length, then round + // up to the array length itself, as for NativeObject. + if (length >= capacity && capacity > (length / 3) * 2) + return CapacityMatchesLengthIndex; + + if (capacity < MinimumDynamicCapacity) + capacity = MinimumDynamicCapacity; + + uint32_t bit = mozilla::FloorLog2Size(capacity); + MOZ_ASSERT(capacity == uint32_t(1 << bit)); + MOZ_ASSERT(bit <= 20); + MOZ_ASSERT(mozilla::ArrayLength(Pow2CapacityIndexes) == 21); + + uint32_t index = Pow2CapacityIndexes[bit]; + MOZ_ASSERT(CapacityArray[index] == capacity); + + return index; + } + + MOZ_ASSERT(CapacityArray[MebiCapacityIndex] == Mebi); + + for (uint32_t i = MebiCapacityIndex + 1;; i++) { + if (CapacityArray[i] >= capacity) + return i; + } + + MOZ_CRASH("Invalid capacity"); +} + +/* static */ uint32_t +UnboxedArrayObject::exactCapacityIndex(uint32_t capacity) +{ + for (size_t i = CapacityMatchesLengthIndex + 1; i < ArrayLength(CapacityArray); i++) { + if (CapacityArray[i] == capacity) + return i; + } + MOZ_CRASH(); +} + +bool +UnboxedArrayObject::growElements(ExclusiveContext* cx, size_t cap) +{ + // The caller should have checked if this capacity is possible for an + // unboxed array, so the only way this call can fail is from OOM. + MOZ_ASSERT(cap <= MaximumCapacity); + + uint32_t oldCapacity = capacity(); + uint32_t newCapacityIndex = chooseCapacityIndex(cap, length()); + uint32_t newCapacity = computeCapacity(newCapacityIndex, length()); + + MOZ_ASSERT(oldCapacity < cap); + MOZ_ASSERT(cap <= newCapacity); + + // The allocation size computation below cannot have integer overflows. + JS_STATIC_ASSERT(MaximumCapacity < UINT32_MAX / sizeof(double)); + + uint8_t* newElements; + if (hasInlineElements()) { + newElements = AllocateObjectBuffer(cx, this, newCapacity * elementSize()); + if (!newElements) + return false; + js_memcpy(newElements, elements(), initializedLength() * elementSize()); + } else { + newElements = ReallocateObjectBuffer(cx, this, elements(), + oldCapacity * elementSize(), + newCapacity * elementSize()); + if (!newElements) + return false; + } + + elements_ = newElements; + setCapacityIndex(newCapacityIndex); + + return true; +} + +void +UnboxedArrayObject::shrinkElements(ExclusiveContext* cx, size_t cap) +{ + if (hasInlineElements()) + return; + + uint32_t oldCapacity = capacity(); + uint32_t newCapacityIndex = chooseCapacityIndex(cap, 0); + uint32_t newCapacity = computeCapacity(newCapacityIndex, 0); + + MOZ_ASSERT(cap < oldCapacity); + MOZ_ASSERT(cap <= newCapacity); + + if (newCapacity >= oldCapacity) + return; + + uint8_t* newElements = ReallocateObjectBuffer(cx, this, elements(), + oldCapacity * elementSize(), + newCapacity * elementSize()); + if (!newElements) + return; + + elements_ = newElements; + setCapacityIndex(newCapacityIndex); +} + +bool +UnboxedArrayObject::containsProperty(ExclusiveContext* cx, jsid id) +{ + if (JSID_IS_INT(id) && uint32_t(JSID_TO_INT(id)) < initializedLength()) + return true; + if (JSID_IS_ATOM(id) && JSID_TO_ATOM(id) == cx->names().length) + return true; + return false; +} + +/* static */ bool +UnboxedArrayObject::obj_lookupProperty(JSContext* cx, HandleObject obj, + HandleId id, MutableHandleObject objp, + MutableHandleShape propp) +{ + if (obj->as().containsProperty(cx, id)) { + MarkNonNativePropertyFound(propp); + objp.set(obj); + return true; + } + + RootedObject proto(cx, obj->staticPrototype()); + if (!proto) { + objp.set(nullptr); + propp.set(nullptr); + return true; + } + + return LookupProperty(cx, proto, id, objp, propp); +} + +/* static */ bool +UnboxedArrayObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, + Handle desc, + ObjectOpResult& result) +{ + if (JSID_IS_INT(id) && !desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) { + UnboxedArrayObject* nobj = &obj->as(); + + uint32_t index = JSID_TO_INT(id); + if (index < nobj->initializedLength()) { + if (nobj->setElement(cx, index, desc.value())) + return result.succeed(); + } else if (index == nobj->initializedLength() && index < MaximumCapacity) { + if (nobj->initializedLength() == nobj->capacity()) { + if (!nobj->growElements(cx, index + 1)) + return false; + } + nobj->setInitializedLength(index + 1); + if (nobj->initElement(cx, index, desc.value())) { + if (nobj->length() <= index) + nobj->setLengthInt32(index + 1); + return result.succeed(); + } + nobj->setInitializedLengthNoBarrier(index); + } + } + + if (!convertToNative(cx, obj)) + return false; + + return DefineProperty(cx, obj, id, desc, result); +} + +/* static */ bool +UnboxedArrayObject::obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) +{ + if (obj->as().containsProperty(cx, id)) { + *foundp = true; + return true; + } + + RootedObject proto(cx, obj->staticPrototype()); + if (!proto) { + *foundp = false; + return true; + } + + return HasProperty(cx, proto, id, foundp); +} + +/* static */ bool +UnboxedArrayObject::obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, + HandleId id, MutableHandleValue vp) +{ + if (obj->as().containsProperty(cx, id)) { + if (JSID_IS_INT(id)) + vp.set(obj->as().getElement(JSID_TO_INT(id))); + else + vp.set(Int32Value(obj->as().length())); + return true; + } + + RootedObject proto(cx, obj->staticPrototype()); + if (!proto) { + vp.setUndefined(); + return true; + } + + return GetProperty(cx, proto, receiver, id, vp); +} + +/* static */ bool +UnboxedArrayObject::obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, + HandleValue receiver, ObjectOpResult& result) +{ + if (obj->as().containsProperty(cx, id)) { + if (receiver.isObject() && obj == &receiver.toObject()) { + if (JSID_IS_INT(id)) { + if (obj->as().setElement(cx, JSID_TO_INT(id), v)) + return result.succeed(); + } else { + uint32_t len; + if (!CanonicalizeArrayLengthValue(cx, v, &len)) + return false; + UnboxedArrayObject* nobj = &obj->as(); + if (len < nobj->initializedLength()) { + nobj->setInitializedLength(len); + nobj->shrinkElements(cx, len); + } + nobj->setLength(cx, len); + return result.succeed(); + } + + if (!convertToNative(cx, obj)) + return false; + return SetProperty(cx, obj, id, v, receiver, result); + } + + return SetPropertyByDefining(cx, id, v, receiver, result); + } + + return SetPropertyOnProto(cx, obj, id, v, receiver, result); +} + +/* static */ bool +UnboxedArrayObject::obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, + MutableHandle desc) +{ + if (obj->as().containsProperty(cx, id)) { + if (JSID_IS_INT(id)) { + desc.value().set(obj->as().getElement(JSID_TO_INT(id))); + desc.setAttributes(JSPROP_ENUMERATE); + } else { + desc.value().set(Int32Value(obj->as().length())); + desc.setAttributes(JSPROP_PERMANENT); + } + desc.object().set(obj); + return true; + } + + desc.object().set(nullptr); + return true; +} + +/* static */ bool +UnboxedArrayObject::obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result) +{ + if (obj->as().containsProperty(cx, id)) { + size_t initlen = obj->as().initializedLength(); + if (JSID_IS_INT(id) && JSID_TO_INT(id) == int32_t(initlen - 1)) { + obj->as().setInitializedLength(initlen - 1); + obj->as().shrinkElements(cx, initlen - 1); + return result.succeed(); + } + } + + if (!convertToNative(cx, obj)) + return false; + return DeleteProperty(cx, obj, id, result); +} + +/* static */ bool +UnboxedArrayObject::obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, + bool enumerableOnly) +{ + for (size_t i = 0; i < obj->as().initializedLength(); i++) { + if (!properties.append(INT_TO_JSID(i))) + return false; + } + + if (!enumerableOnly && !properties.append(NameToId(cx->names().length))) + return false; + + return true; +} + +static const ClassOps UnboxedArrayObjectClassOps = { + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + UnboxedArrayObject::finalize, + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + UnboxedArrayObject::trace, +}; + +static const ClassExtension UnboxedArrayObjectClassExtension = { + nullptr, /* weakmapKeyDelegateOp */ + UnboxedArrayObject::objectMoved +}; + +static const ObjectOps UnboxedArrayObjectObjectOps = { + UnboxedArrayObject::obj_lookupProperty, + UnboxedArrayObject::obj_defineProperty, + UnboxedArrayObject::obj_hasProperty, + UnboxedArrayObject::obj_getProperty, + UnboxedArrayObject::obj_setProperty, + UnboxedArrayObject::obj_getOwnPropertyDescriptor, + UnboxedArrayObject::obj_deleteProperty, + nullptr, /* getElements */ + UnboxedArrayObject::obj_enumerate, + nullptr /* funToString */ +}; + +const Class UnboxedArrayObject::class_ = { + "Array", + Class::NON_NATIVE | + JSCLASS_SKIP_NURSERY_FINALIZE | + JSCLASS_BACKGROUND_FINALIZE, + &UnboxedArrayObjectClassOps, + JS_NULL_CLASS_SPEC, + &UnboxedArrayObjectClassExtension, + &UnboxedArrayObjectObjectOps +}; + ///////////////////////////////////////////////////////////////////// // API ///////////////////////////////////////////////////////////////////// @@ -935,6 +1641,31 @@ NextValue(Handle> values, size_t* valueCursor) return values[(*valueCursor)++]; } +void +UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx, + Handle> values, size_t* valueCursor) +{ + MOZ_ASSERT(CapacityArray[1] == 0); + setCapacityIndex(1); + setInitializedLengthNoBarrier(0); + setInlineElements(); + + setLength(cx, NextValue(values, valueCursor).toInt32()); + + int32_t initlen = NextValue(values, valueCursor).toInt32(); + if (!initlen) + return; + + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!growElements(cx, initlen)) + oomUnsafe.crash("UnboxedArrayObject::fillAfterConvert"); + + setInitializedLength(initlen); + + for (size_t i = 0; i < size_t(initlen); i++) + JS_ALWAYS_TRUE(initElement(cx, i, NextValue(values, valueCursor))); +} + void UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx, Handle> values, size_t* valueCursor) @@ -944,3 +1675,58 @@ UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx, for (size_t i = 0; i < layout().properties().length(); i++) JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor))); } + +DefineBoxedOrUnboxedFunctor6(SetOrExtendBoxedOrUnboxedDenseElements, + ExclusiveContext*, JSObject*, uint32_t, const Value*, uint32_t, + ShouldUpdateTypes); + +DenseElementResult +js::SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj, + uint32_t start, const Value* vp, uint32_t count, + ShouldUpdateTypes updateTypes) +{ + SetOrExtendBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, start, vp, count, updateTypes); + return CallBoxedOrUnboxedSpecialization(functor, obj); +}; + +DefineBoxedOrUnboxedFunctor5(MoveBoxedOrUnboxedDenseElements, + JSContext*, JSObject*, uint32_t, uint32_t, uint32_t); + +DenseElementResult +js::MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, + uint32_t dstStart, uint32_t srcStart, uint32_t length) +{ + MoveBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, dstStart, srcStart, length); + return CallBoxedOrUnboxedSpecialization(functor, obj); +} + +DefineBoxedOrUnboxedFunctorPair6(CopyBoxedOrUnboxedDenseElements, + JSContext*, JSObject*, JSObject*, uint32_t, uint32_t, uint32_t); + +DenseElementResult +js::CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src, + uint32_t dstStart, uint32_t srcStart, uint32_t length) +{ + CopyBoxedOrUnboxedDenseElementsFunctor functor(cx, dst, src, dstStart, srcStart, length); + return CallBoxedOrUnboxedSpecialization(functor, dst, src); +} + +DefineBoxedOrUnboxedFunctor3(SetBoxedOrUnboxedInitializedLength, + JSContext*, JSObject*, size_t); + +void +js::SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen) +{ + SetBoxedOrUnboxedInitializedLengthFunctor functor(cx, obj, initlen); + JS_ALWAYS_TRUE(CallBoxedOrUnboxedSpecialization(functor, obj) == DenseElementResult::Success); +} + +DefineBoxedOrUnboxedFunctor3(EnsureBoxedOrUnboxedDenseElements, + JSContext*, JSObject*, size_t); + +DenseElementResult +js::EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t initlen) +{ + EnsureBoxedOrUnboxedDenseElementsFunctor functor(cx, obj, initlen); + return CallBoxedOrUnboxedSpecialization(functor, obj); +} diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h index ba66434bc..779dd14c7 100644 --- a/js/src/vm/UnboxedObject.h +++ b/js/src/vm/UnboxedObject.h @@ -96,11 +96,17 @@ class UnboxedLayout : public mozilla::LinkedListElement // from an array of values. GCPtrJitCode constructorCode_; + // The following members are only used for unboxed arrays. + + // The type of array elements. + JSValueType elementType_; + public: UnboxedLayout() : nativeGroup_(nullptr), nativeShape_(nullptr), allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr), - size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr) + size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr), + elementType_(JSVAL_TYPE_MAGIC) {} bool initProperties(const PropertyVector& properties, size_t size) { @@ -108,6 +114,10 @@ class UnboxedLayout : public mozilla::LinkedListElement return properties_.appendAll(properties); } + void initArray(JSValueType elementType) { + elementType_ = elementType; + } + ~UnboxedLayout() { if (newScript_) newScript_->clear(); @@ -120,6 +130,10 @@ class UnboxedLayout : public mozilla::LinkedListElement constructorCode_.init(nullptr); } + bool isArray() const { + return elementType_ != JSVAL_TYPE_MAGIC; + } + void detachFromCompartment(); const PropertyVector& properties() const { @@ -187,6 +201,10 @@ class UnboxedLayout : public mozilla::LinkedListElement constructorCode_ = code; } + JSValueType elementType() const { + return elementType_; + } + inline gc::AllocKind getAllocKind() const; void trace(JSTracer* trc); @@ -306,6 +324,193 @@ UnboxedLayout::getAllocKind() const return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size()); } +// Class for an array object using an unboxed representation. +class UnboxedArrayObject : public JSObject +{ + // Elements pointer for the object. + uint8_t* elements_; + + // The nominal array length. This always fits in an int32_t. + uint32_t length_; + + // Value indicating the allocated capacity and initialized length of the + // array. The top CapacityBits bits are an index into CapacityArray, which + // indicates the elements capacity. The low InitializedLengthBits store the + // initialized length of the array. + uint32_t capacityIndexAndInitializedLength_; + + // If the elements are inline, they will point here. + uint8_t inlineElements_[1]; + + public: + static const uint32_t CapacityBits = 6; + static const uint32_t CapacityShift = 26; + + static const uint32_t CapacityMask = uint32_t(-1) << CapacityShift; + static const uint32_t InitializedLengthMask = (1 << CapacityShift) - 1; + + static const uint32_t MaximumCapacity = InitializedLengthMask; + static const uint32_t MinimumDynamicCapacity = 8; + + static const uint32_t CapacityArray[]; + + // Capacity index which indicates the array's length is also its capacity. + static const uint32_t CapacityMatchesLengthIndex = 0; + + private: + static inline uint32_t computeCapacity(uint32_t index, uint32_t length) { + if (index == CapacityMatchesLengthIndex) + return length; + return CapacityArray[index]; + } + + static uint32_t chooseCapacityIndex(uint32_t capacity, uint32_t length); + static uint32_t exactCapacityIndex(uint32_t capacity); + + public: + static const Class class_; + + static bool obj_lookupProperty(JSContext* cx, HandleObject obj, + HandleId id, MutableHandleObject objp, + MutableHandleShape propp); + + static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id, + Handle desc, + ObjectOpResult& result); + + static bool obj_hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp); + + static bool obj_getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, + HandleId id, MutableHandleValue vp); + + static bool obj_setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, + HandleValue receiver, ObjectOpResult& result); + + static bool obj_getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, + MutableHandle desc); + + static bool obj_deleteProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result); + + static bool obj_enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, + bool enumerableOnly); + static bool obj_watch(JSContext* cx, HandleObject obj, HandleId id, HandleObject callable); + + inline const UnboxedLayout& layout() const; + + const UnboxedLayout& layoutDontCheckGeneration() const { + return group()->unboxedLayoutDontCheckGeneration(); + } + + JSValueType elementType() const { + return layoutDontCheckGeneration().elementType(); + } + + uint32_t elementSize() const { + return UnboxedTypeSize(elementType()); + } + + static bool convertToNative(JSContext* cx, JSObject* obj); + static UnboxedArrayObject* create(ExclusiveContext* cx, HandleObjectGroup group, + uint32_t length, NewObjectKind newKind, + uint32_t maxLength = MaximumCapacity); + + static bool convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj, + ObjectGroup* group, Shape* shape); + bool convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group); + + void fillAfterConvert(ExclusiveContext* cx, + Handle> values, size_t* valueCursor); + + static void trace(JSTracer* trc, JSObject* object); + static void objectMoved(JSObject* obj, const JSObject* old); + static void finalize(FreeOp* fop, JSObject* obj); + + static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src, + gc::AllocKind allocKind); + + uint8_t* elements() { + return elements_; + } + + bool hasInlineElements() const { + return elements_ == &inlineElements_[0]; + } + + uint32_t length() const { + return length_; + } + + uint32_t initializedLength() const { + return capacityIndexAndInitializedLength_ & InitializedLengthMask; + } + + uint32_t capacityIndex() const { + return (capacityIndexAndInitializedLength_ & CapacityMask) >> CapacityShift; + } + + uint32_t capacity() const { + return computeCapacity(capacityIndex(), length()); + } + + bool containsProperty(ExclusiveContext* cx, jsid id); + + bool setElement(ExclusiveContext* cx, size_t index, const Value& v); + bool initElement(ExclusiveContext* cx, size_t index, const Value& v); + void initElementNoTypeChange(size_t index, const Value& v); + Value getElement(size_t index); + + template inline bool setElementSpecific(ExclusiveContext* cx, size_t index, + const Value& v); + template inline void setElementNoTypeChangeSpecific(size_t index, const Value& v); + template inline bool initElementSpecific(ExclusiveContext* cx, size_t index, + const Value& v); + template inline void initElementNoTypeChangeSpecific(size_t index, const Value& v); + template inline Value getElementSpecific(size_t index); + template inline void triggerPreBarrier(size_t index); + + bool growElements(ExclusiveContext* cx, size_t cap); + void shrinkElements(ExclusiveContext* cx, size_t cap); + + static uint32_t offsetOfElements() { + return offsetof(UnboxedArrayObject, elements_); + } + static uint32_t offsetOfLength() { + return offsetof(UnboxedArrayObject, length_); + } + static uint32_t offsetOfCapacityIndexAndInitializedLength() { + return offsetof(UnboxedArrayObject, capacityIndexAndInitializedLength_); + } + static uint32_t offsetOfInlineElements() { + return offsetof(UnboxedArrayObject, inlineElements_); + } + + void setLengthInt32(uint32_t length) { + MOZ_ASSERT(length <= INT32_MAX); + length_ = length; + } + + inline void setLength(ExclusiveContext* cx, uint32_t len); + inline void setInitializedLength(uint32_t initlen); + + inline void setInitializedLengthNoBarrier(uint32_t initlen) { + MOZ_ASSERT(initlen <= InitializedLengthMask); + capacityIndexAndInitializedLength_ = + (capacityIndexAndInitializedLength_ & CapacityMask) | initlen; + } + + private: + void setInlineElements() { + elements_ = &inlineElements_[0]; + } + + void setCapacityIndex(uint32_t index) { + MOZ_ASSERT(index <= (CapacityMask >> CapacityShift)); + capacityIndexAndInitializedLength_ = + (index << CapacityShift) | initializedLength(); + } +}; + } // namespace js namespace JS { -- cgit v1.2.3 From ecdeefc4dd5624e824e696ac1c492c0b103f4acd Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 23 Feb 2020 19:43:47 +0100 Subject: Revert #1091 Remove unboxed object code phase 1 + extras. This should be the last code backout for this. merging this branch should get us back to the way we were (+ additional code changes for later changes) as fasr as the unused unboxed code is concerned. --- js/src/vm/Interpreter-inl.h | 13 +- js/src/vm/Interpreter.cpp | 7 +- js/src/vm/ReceiverGuard.cpp | 1 + js/src/vm/ReceiverGuard.h | 5 + js/src/vm/TypeInference.cpp | 16 ++ js/src/vm/TypeInference.h | 1 + js/src/vm/UnboxedObject.cpp | 400 ++++++++++++++++++++++++++++++++++++++++++++ js/src/vm/UnboxedObject.h | 7 + 8 files changed, 445 insertions(+), 5 deletions(-) (limited to 'js/src/vm') diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index adefa6e93..0e81dfef4 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -22,6 +22,7 @@ #include "vm/EnvironmentObject-inl.h" #include "vm/Stack-inl.h" #include "vm/String-inl.h" +#include "vm/UnboxedObject-inl.h" namespace js { @@ -336,10 +337,14 @@ InitGlobalLexicalOperation(JSContext* cx, LexicalEnvironmentObject* lexicalEnvAr inline bool InitPropertyOperation(JSContext* cx, JSOp op, HandleObject obj, HandleId id, HandleValue rhs) { - MOZ_ASSERT(obj->is() || obj->is()); - unsigned propAttrs = GetInitDataPropAttrs(op); - return NativeDefineProperty(cx, obj.as(), id, rhs, - nullptr, nullptr, propAttrs); + if (obj->is() || obj->is()) { + unsigned propAttrs = GetInitDataPropAttrs(op); + return NativeDefineProperty(cx, obj.as(), id, rhs, nullptr, nullptr, + propAttrs); + } + + MOZ_ASSERT(obj->as().layout().lookup(id)); + return PutProperty(cx, obj, id, rhs, false); } inline bool diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 834084c4d..274392335 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -4157,7 +4157,7 @@ CASE(JSOP_INITHOMEOBJECT) /* Load the home object */ ReservedRooted obj(&rootObject0); obj = ®S.sp[int(-2 - skipOver)].toObject(); - MOZ_ASSERT(obj->is() || obj->is()); + MOZ_ASSERT(obj->is() || obj->is() || obj->is()); func->setExtendedSlot(FunctionExtended::METHOD_HOMEOBJECT_SLOT, ObjectValue(*obj)); } @@ -4973,10 +4973,15 @@ js::NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc, return nullptr; if (group->maybePreliminaryObjects()) { group->maybePreliminaryObjects()->maybeAnalyze(cx, group); + if (group->maybeUnboxedLayout()) + group->maybeUnboxedLayout()->setAllocationSite(script, pc); } if (group->shouldPreTenure() || group->maybePreliminaryObjects()) newKind = TenuredObject; + + if (group->maybeUnboxedLayout()) + return UnboxedPlainObject::create(cx, group, newKind); } RootedObject obj(cx); diff --git a/js/src/vm/ReceiverGuard.cpp b/js/src/vm/ReceiverGuard.cpp index 11c2d0727..97df908c3 100644 --- a/js/src/vm/ReceiverGuard.cpp +++ b/js/src/vm/ReceiverGuard.cpp @@ -7,6 +7,7 @@ #include "vm/ReceiverGuard.h" #include "builtin/TypedObject.h" +#include "vm/UnboxedObject.h" #include "jsobjinlines.h" using namespace js; diff --git a/js/src/vm/ReceiverGuard.h b/js/src/vm/ReceiverGuard.h index c14f0d83b..459cc0012 100644 --- a/js/src/vm/ReceiverGuard.h +++ b/js/src/vm/ReceiverGuard.h @@ -28,6 +28,11 @@ namespace js { // TypedObject: The structure of a typed object is determined by its group. // All typed objects with the same group have the same class, prototype, and // own properties. +// +// UnboxedPlainObject: The structure of an unboxed plain object is determined +// by its group and its expando object's shape, if there is one. All unboxed +// plain objects with the same group and expando shape have the same +// properties except those stored in the expando's dense elements. class HeapReceiverGuard; class RootedReceiverGuard; diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index ee5bbef5d..438188a36 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -1999,6 +1999,17 @@ TypeSet::ObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList* co ConstraintDataFreezeObjectForTypedArrayData(tarray))); } +void +TypeSet::ObjectKey::watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints) +{ + HeapTypeSetKey objectProperty = property(JSID_EMPTY); + LifoAlloc* alloc = constraints->alloc(); + + typedef CompilerConstraintInstance T; + constraints->add(alloc->new_(alloc, objectProperty, + ConstraintDataFreezeObjectForUnboxedConvertedToNative())); +} + static void ObjectStateChange(ExclusiveContext* cxArg, ObjectGroup* group, bool markingUnknown) { @@ -3573,6 +3584,7 @@ PreliminaryObjectArrayWithTemplate::maybeAnalyze(ExclusiveContext* cx, ObjectGro } } + TryConvertToUnboxedLayout(cx, enter, shape(), group, preliminaryObjects); if (group->maybeUnboxedLayout()) return; @@ -3901,6 +3913,10 @@ TypeNewScript::maybeAnalyze(JSContext* cx, ObjectGroup* group, bool* regenerate, PodCopy(initializerList, initializerVector.begin(), initializerVector.length()); } + // Try to use an unboxed representation for the group. + if (!TryConvertToUnboxedLayout(cx, enter, templateObject()->lastProperty(), group, preliminaryObjects)) + return false; + js_delete(preliminaryObjects); preliminaryObjects = nullptr; diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 537baa21f..9ec1bf927 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -262,6 +262,7 @@ class TypeSet bool hasStableClassAndProto(CompilerConstraintList* constraints); void watchStateChangeForInlinedCall(CompilerConstraintList* constraints); void watchStateChangeForTypedArrayData(CompilerConstraintList* constraints); + void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints); HeapTypeSetKey property(jsid id); void ensureTrackedProperty(JSContext* cx, jsid id); diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp index 059293a2d..d8c9c774a 100644 --- a/js/src/vm/UnboxedObject.cpp +++ b/js/src/vm/UnboxedObject.cpp @@ -1635,12 +1635,227 @@ const Class UnboxedArrayObject::class_ = { // API ///////////////////////////////////////////////////////////////////// +static bool +UnboxedTypeIncludes(JSValueType supertype, JSValueType subtype) +{ + if (supertype == JSVAL_TYPE_DOUBLE && subtype == JSVAL_TYPE_INT32) + return true; + if (supertype == JSVAL_TYPE_OBJECT && subtype == JSVAL_TYPE_NULL) + return true; + return false; +} + +static bool +CombineUnboxedTypes(const Value& value, JSValueType* existing) +{ + JSValueType type = value.isDouble() ? JSVAL_TYPE_DOUBLE : value.extractNonDoubleType(); + + if (*existing == JSVAL_TYPE_MAGIC || *existing == type || UnboxedTypeIncludes(type, *existing)) { + *existing = type; + return true; + } + if (UnboxedTypeIncludes(*existing, type)) + return true; + return false; +} + +// Return whether the property names and types in layout are a subset of the +// specified vector. +static bool +PropertiesAreSuperset(const UnboxedLayout::PropertyVector& properties, UnboxedLayout* layout) +{ + for (size_t i = 0; i < layout->properties().length(); i++) { + const UnboxedLayout::Property& layoutProperty = layout->properties()[i]; + bool found = false; + for (size_t j = 0; j < properties.length(); j++) { + if (layoutProperty.name == properties[j].name) { + found = (layoutProperty.type == properties[j].type); + break; + } + } + if (!found) + return false; + } + return true; +} + +static bool +CombinePlainObjectProperties(PlainObject* obj, Shape* templateShape, + UnboxedLayout::PropertyVector& properties) +{ + // All preliminary objects must have been created with enough space to + // fill in their unboxed data inline. This is ensured either by using + // the largest allocation kind (which limits the maximum size of an + // unboxed object), or by using an allocation kind that covers all + // properties in the template, as the space used by unboxed properties + // is less than or equal to that used by boxed properties. + MOZ_ASSERT(gc::GetGCKindSlots(obj->asTenured().getAllocKind()) >= + Min(NativeObject::MAX_FIXED_SLOTS, templateShape->slotSpan())); + + if (obj->lastProperty() != templateShape || obj->hasDynamicElements()) { + // Only use an unboxed representation if all created objects match + // the template shape exactly. + return false; + } + + for (size_t i = 0; i < templateShape->slotSpan(); i++) { + Value val = obj->getSlot(i); + + JSValueType& existing = properties[i].type; + if (!CombineUnboxedTypes(val, &existing)) + return false; + } + + return true; +} + +static bool +CombineArrayObjectElements(ExclusiveContext* cx, ArrayObject* obj, JSValueType* elementType) +{ + if (obj->inDictionaryMode() || + obj->lastProperty()->propid() != AtomToId(cx->names().length) || + !obj->lastProperty()->previous()->isEmptyShape()) + { + // Only use an unboxed representation if the object has no properties. + return false; + } + + for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) { + Value val = obj->getDenseElement(i); + + // For now, unboxed arrays cannot have holes. + if (val.isMagic(JS_ELEMENTS_HOLE)) + return false; + + if (!CombineUnboxedTypes(val, elementType)) + return false; + } + + return true; +} + +static size_t +ComputePlainObjectLayout(ExclusiveContext* cx, Shape* templateShape, + UnboxedLayout::PropertyVector& properties) +{ + // Fill in the names for all the object's properties. + for (Shape::Range r(templateShape); !r.empty(); r.popFront()) { + size_t slot = r.front().slot(); + MOZ_ASSERT(!properties[slot].name); + properties[slot].name = JSID_TO_ATOM(r.front().propid())->asPropertyName(); + } + + // Fill in all the unboxed object's property offsets. + uint32_t offset = 0; + + // Search for an existing unboxed layout which is a subset of this one. + // If there are multiple such layouts, use the largest one. If we're able + // to find such a layout, use the same property offsets for the shared + // properties, which will allow us to generate better code if the objects + // have a subtype/supertype relation and are accessed at common sites. + UnboxedLayout* bestExisting = nullptr; + for (UnboxedLayout* existing : cx->compartment()->unboxedLayouts) { + if (PropertiesAreSuperset(properties, existing)) { + if (!bestExisting || + existing->properties().length() > bestExisting->properties().length()) + { + bestExisting = existing; + } + } + } + if (bestExisting) { + for (size_t i = 0; i < bestExisting->properties().length(); i++) { + const UnboxedLayout::Property& existingProperty = bestExisting->properties()[i]; + for (size_t j = 0; j < templateShape->slotSpan(); j++) { + if (existingProperty.name == properties[j].name) { + MOZ_ASSERT(existingProperty.type == properties[j].type); + properties[j].offset = existingProperty.offset; + } + } + } + offset = bestExisting->size(); + } + + // Order remaining properties from the largest down for the best space + // utilization. + static const size_t typeSizes[] = { 8, 4, 1 }; + + for (size_t i = 0; i < ArrayLength(typeSizes); i++) { + size_t size = typeSizes[i]; + for (size_t j = 0; j < templateShape->slotSpan(); j++) { + if (properties[j].offset != UINT32_MAX) + continue; + JSValueType type = properties[j].type; + if (UnboxedTypeSize(type) == size) { + offset = JS_ROUNDUP(offset, size); + properties[j].offset = offset; + offset += size; + } + } + } + + // The final offset is the amount of data needed by the object. + return offset; +} + +static bool +SetLayoutTraceList(ExclusiveContext* cx, UnboxedLayout* layout) +{ + // Figure out the offsets of any objects or string properties. + Vector objectOffsets, stringOffsets; + for (size_t i = 0; i < layout->properties().length(); i++) { + const UnboxedLayout::Property& property = layout->properties()[i]; + MOZ_ASSERT(property.offset != UINT32_MAX); + if (property.type == JSVAL_TYPE_OBJECT) { + if (!objectOffsets.append(property.offset)) + return false; + } else if (property.type == JSVAL_TYPE_STRING) { + if (!stringOffsets.append(property.offset)) + return false; + } + } + + // Construct the layout's trace list. + if (!objectOffsets.empty() || !stringOffsets.empty()) { + Vector entries; + if (!entries.appendAll(stringOffsets) || + !entries.append(-1) || + !entries.appendAll(objectOffsets) || + !entries.append(-1) || + !entries.append(-1)) + { + return false; + } + int32_t* traceList = cx->zone()->pod_malloc(entries.length()); + if (!traceList) + return false; + PodCopy(traceList, entries.begin(), entries.length()); + layout->setTraceList(traceList); + } + + return true; +} + static inline Value NextValue(Handle> values, size_t* valueCursor) { return values[(*valueCursor)++]; } +static bool +GetValuesFromPreliminaryArrayObject(ArrayObject* obj, MutableHandle> values) +{ + if (!values.append(Int32Value(obj->length()))) + return false; + if (!values.append(Int32Value(obj->getDenseInitializedLength()))) + return false; + for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) { + if (!values.append(obj->getDenseElement(i))) + return false; + } + return true; +} + void UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx, Handle> values, size_t* valueCursor) @@ -1666,6 +1881,16 @@ UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx, JS_ALWAYS_TRUE(initElement(cx, i, NextValue(values, valueCursor))); } +static bool +GetValuesFromPreliminaryPlainObject(PlainObject* obj, MutableHandle> values) +{ + for (size_t i = 0; i < obj->slotSpan(); i++) { + if (!values.append(obj->getSlot(i))) + return false; + } + return true; +} + void UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx, Handle> values, size_t* valueCursor) @@ -1676,6 +1901,181 @@ UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx, JS_ALWAYS_TRUE(setValue(cx, layout().properties()[i], NextValue(values, valueCursor))); } +bool +js::TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape, + ObjectGroup* group, PreliminaryObjectArray* objects) +{ + bool isArray = !templateShape; + + // Unboxed arrays are nightly only for now. The getenv() call will be + // removed when they are on by default. See bug 1153266. + if (isArray) { +#ifdef NIGHTLY_BUILD + if (!getenv("JS_OPTION_USE_UNBOXED_ARRAYS")) { + if (!cx->options().unboxedArrays()) + return true; + } +#else + return true; +#endif + } else { + if (jit::JitOptions.disableUnboxedObjects) + return true; + } + + MOZ_ASSERT_IF(templateShape, !templateShape->getObjectFlags()); + + if (group->runtimeFromAnyThread()->isSelfHostingGlobal(cx->global())) + return true; + + if (!isArray && templateShape->slotSpan() == 0) + return true; + + UnboxedLayout::PropertyVector properties; + if (!isArray) { + if (!properties.appendN(UnboxedLayout::Property(), templateShape->slotSpan())) + return false; + } + JSValueType elementType = JSVAL_TYPE_MAGIC; + + size_t objectCount = 0; + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + JSObject* obj = objects->get(i); + if (!obj) + continue; + + if (obj->isSingleton() || obj->group() != group) + return true; + + objectCount++; + + if (isArray) { + if (!CombineArrayObjectElements(cx, &obj->as(), &elementType)) + return true; + } else { + if (!CombinePlainObjectProperties(&obj->as(), templateShape, properties)) + return true; + } + } + + size_t layoutSize = 0; + if (isArray) { + // Don't use an unboxed representation if we couldn't determine an + // element type for the objects. + if (UnboxedTypeSize(elementType) == 0) + return true; + } else { + if (objectCount <= 1) { + // If only one of the objects has been created, it is more likely + // to have new properties added later. This heuristic is not used + // for array objects, where we might want an unboxed representation + // even if there is only one large array. + return true; + } + + for (size_t i = 0; i < templateShape->slotSpan(); i++) { + // We can't use an unboxed representation if e.g. all the objects have + // a null value for one of the properties, as we can't decide what type + // it is supposed to have. + if (UnboxedTypeSize(properties[i].type) == 0) + return true; + } + + // Make sure that all properties on the template shape are property + // names, and not indexes. + for (Shape::Range r(templateShape); !r.empty(); r.popFront()) { + jsid id = r.front().propid(); + uint32_t dummy; + if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&dummy)) + return true; + } + + layoutSize = ComputePlainObjectLayout(cx, templateShape, properties); + + // The entire object must be allocatable inline. + if (UnboxedPlainObject::offsetOfData() + layoutSize > JSObject::MAX_BYTE_SIZE) + return true; + } + + UniquePtr& layout = enter.unboxedLayoutToCleanUp; + MOZ_ASSERT(!layout); + layout = group->zone()->make_unique(); + if (!layout) + return false; + + if (isArray) { + layout->initArray(elementType); + } else { + if (!layout->initProperties(properties, layoutSize)) + return false; + + // The unboxedLayouts list only tracks layouts for plain objects. + cx->compartment()->unboxedLayouts.insertFront(layout.get()); + + if (!SetLayoutTraceList(cx, layout.get())) + return false; + } + + // We've determined that all the preliminary objects can use the new layout + // just constructed, so convert the existing group to use the unboxed class, + // and update the preliminary objects to use the new layout. Do the + // fallible stuff first before modifying any objects. + + // Get an empty shape which we can use for the preliminary objects. + const Class* clasp = isArray ? &UnboxedArrayObject::class_ : &UnboxedPlainObject::class_; + Shape* newShape = EmptyShape::getInitialShape(cx, clasp, group->proto(), 0); + if (!newShape) { + cx->recoverFromOutOfMemory(); + return false; + } + + // Accumulate a list of all the values in each preliminary object, and + // update their shapes. + Rooted> values(cx, GCVector(cx)); + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + JSObject* obj = objects->get(i); + if (!obj) + continue; + + bool ok; + if (isArray) + ok = GetValuesFromPreliminaryArrayObject(&obj->as(), &values); + else + ok = GetValuesFromPreliminaryPlainObject(&obj->as(), &values); + + if (!ok) { + cx->recoverFromOutOfMemory(); + return false; + } + } + + if (TypeNewScript* newScript = group->newScript()) + layout->setNewScript(newScript); + + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + if (JSObject* obj = objects->get(i)) + obj->as().setLastPropertyMakeNonNative(newShape); + } + + group->setClasp(clasp); + group->setUnboxedLayout(layout.release()); + + size_t valueCursor = 0; + for (size_t i = 0; i < PreliminaryObjectArray::COUNT; i++) { + JSObject* obj = objects->get(i); + if (!obj) + continue; + + if (isArray) + obj->as().fillAfterConvert(cx, values, &valueCursor); + else + obj->as().fillAfterConvert(cx, values, &valueCursor); + } + + MOZ_ASSERT(valueCursor == values.length()); + return true; +} + DefineBoxedOrUnboxedFunctor6(SetOrExtendBoxedOrUnboxedDenseElements, ExclusiveContext*, JSObject*, uint32_t, const Value*, uint32_t, ShouldUpdateTypes); diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h index 779dd14c7..ecff8be5b 100644 --- a/js/src/vm/UnboxedObject.h +++ b/js/src/vm/UnboxedObject.h @@ -317,6 +317,13 @@ class UnboxedPlainObject : public JSObject } }; +// Try to construct an UnboxedLayout for each of the preliminary objects, +// provided they all match the template shape. If successful, converts the +// preliminary objects and their group to the new unboxed representation. +bool +TryConvertToUnboxedLayout(ExclusiveContext* cx, AutoEnterAnalysis& enter, Shape* templateShape, + ObjectGroup* group, PreliminaryObjectArray* objects); + inline gc::AllocKind UnboxedLayout::getAllocKind() const { -- cgit v1.2.3