diff options
author | wolfbeast <mcwerewolf@wolfbeast.com> | 2020-02-22 21:09:32 +0100 |
---|---|---|
committer | wolfbeast <mcwerewolf@wolfbeast.com> | 2020-04-14 12:56:40 +0200 |
commit | c22a493144e39d76bfa42c46f9d6d17a5143ac35 (patch) | |
tree | 83ec59e07c9948eebd529ba56771a622c605a13b /js/src/vm | |
parent | d20ca24a070d547be3bce4d513ef151b6be5f955 (diff) | |
download | UXP-c22a493144e39d76bfa42c46f9d6d17a5143ac35.tar UXP-c22a493144e39d76bfa42c46f9d6d17a5143ac35.tar.gz UXP-c22a493144e39d76bfa42c46f9d6d17a5143ac35.tar.lz UXP-c22a493144e39d76bfa42c46f9d6d17a5143ac35.tar.xz UXP-c22a493144e39d76bfa42c46f9d6d17a5143ac35.zip |
Revert #1142 - Remove unboxed objects
- accounting for removal of watch()/unwatch()
Diffstat (limited to 'js/src/vm')
-rw-r--r-- | js/src/vm/Interpreter.cpp | 5 | ||||
-rw-r--r-- | js/src/vm/NativeObject.cpp | 27 | ||||
-rw-r--r-- | js/src/vm/NativeObject.h | 5 | ||||
-rw-r--r-- | js/src/vm/ObjectGroup-inl.h | 14 | ||||
-rw-r--r-- | js/src/vm/ObjectGroup.cpp | 55 | ||||
-rw-r--r-- | js/src/vm/ObjectGroup.h | 62 | ||||
-rw-r--r-- | js/src/vm/ReceiverGuard.cpp | 14 | ||||
-rw-r--r-- | js/src/vm/TypeInference-inl.h | 5 | ||||
-rw-r--r-- | js/src/vm/TypeInference.cpp | 156 | ||||
-rw-r--r-- | js/src/vm/UnboxedObject-inl.h | 177 | ||||
-rw-r--r-- | js/src/vm/UnboxedObject.cpp | 946 | ||||
-rw-r--r-- | js/src/vm/UnboxedObject.h | 319 |
12 files changed, 1765 insertions, 20 deletions
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<PlainObject>(), newKind); if (!obj) return nullptr; diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index d801fad06..bd7484e07 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 9cc6d5436..6595703dc 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<UnboxedPlainObject>()) { + const UnboxedLayout& layout = newObj->as<UnboxedPlainObject>().layout(); + const int32_t* traceList = layout.traceList(); + if (!traceList) + return true; + + uint8_t* newData = newObj->as<UnboxedPlainObject>().data(); + uint8_t* oldData = oldObj->as<UnboxedPlainObject>().data(); + + for (; *traceList != -1; traceList++) {} + traceList++; + for (; *traceList != -1; traceList++) { + JSObject* newInnerObj = *reinterpret_cast<JSObject**>(newData + *traceList); + JSObject* oldInnerObj = *reinterpret_cast<JSObject**>(oldData + *traceList); + + if (!newInnerObj || !oldInnerObj || SameGroup(oldInnerObj, newInnerObj)) + continue; + + if (!GiveObjectGroup(cx, newInnerObj, oldInnerObj)) + return false; + + if (SameGroup(oldInnerObj, newInnerObj)) + continue; + + if (!GiveObjectGroup(cx, oldInnerObj, newInnerObj)) + return false; + + if (SameGroup(oldInnerObj, newInnerObj)) { + for (size_t i = 1; i < ncompare; i++) { + if (compare[i].isObject() && SameGroup(&compare[i].toObject(), newObj)) { + uint8_t* otherData = compare[i].toObject().as<UnboxedPlainObject>().data(); + JSObject* otherInnerObj = *reinterpret_cast<JSObject**>(otherData + *traceList); + if (otherInnerObj && !SameGroup(otherInnerObj, newInnerObj)) { + if (!GiveObjectGroup(cx, otherInnerObj, newInnerObj)) + return false; + } + } + } + } + } } return true; @@ -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<UnboxedLayout*>(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<ObjectGroup*>(addendum_); + return nullptr; + } + + void setOriginalUnboxedGroup(ObjectGroup* group) { + setAddendum(Addendum_OriginalUnboxedGroup, group); + } + TypeDescr* maybeTypeDescr() { // Note: there is no need to sweep when accessing the type descriptor // of an object, as it is strongly held and immutable. @@ -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<TypedObject>()) { + if (obj->is<UnboxedPlainObject>()) { + group = obj->group(); + if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando()) + shape = expando->lastProperty(); + } else if (obj->is<TypedObject>()) { 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<UnboxedPlainObject>()) { + // Both the group and shape need to be guarded for unboxed plain objects. + return obj->as<UnboxedPlainObject>().maybeExpando() ? 0 : 1; + } if (obj->is<TypedObject>()) { // 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<UnboxedLayout> 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<NativeObject>()); + 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<UnboxedPlainObject>()) { + AutoEnterOOMUnsafeRegion oomUnsafe; + if (!UnboxedPlainObject::convertToNative(cx, &thisv.toObject())) + oomUnsafe.crash("rollbackPartiallyInitializedObjects"); + } + // Found a matching frame. RootedPlainObject obj(cx, &thisv.toObject().as<PlainObject>()); @@ -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<AutoClearTypeInferenceStateOnOOM> 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<int32_t*>(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<double*>(p); + if (maybeUninitialized) + return DoubleValue(JS::CanonicalizeNaN(d)); + return DoubleValue(d); + } + + case JSVAL_TYPE_STRING: + return StringValue(*reinterpret_cast<JSString**>(p)); + + case JSVAL_TYPE_OBJECT: + return ObjectOrNullValue(*reinterpret_cast<JSObject**>(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<int32_t*>(p) = v.toInt32(); + return; + + case JSVAL_TYPE_DOUBLE: + *reinterpret_cast<double*>(p) = v.toNumber(); + return; + + case JSVAL_TYPE_STRING: { + MOZ_ASSERT(!IsInsideNursery(v.toString())); + JSString** np = reinterpret_cast<JSString**>(p); + if (preBarrier) + JSString::writeBarrierPre(*np); + *np = v.toString(); + return; + } + + case JSVAL_TYPE_OBJECT: { + JSObject** np = reinterpret_cast<JSObject**>(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<int32_t*>(p) = v.toInt32(); + return true; + } + return false; + + case JSVAL_TYPE_DOUBLE: + if (v.isNumber()) { + *reinterpret_cast<double*>(p) = v.toNumber(); + return true; + } + return false; + + case JSVAL_TYPE_STRING: + if (v.isString()) { + MOZ_ASSERT(!IsInsideNursery(v.toString())); + JSString** np = reinterpret_cast<JSString**>(p); + if (preBarrier) + JSString::writeBarrierPre(*np); + *np = v.toString(); + return true; + } + return false; + + case JSVAL_TYPE_OBJECT: + if (v.isObjectOrNull()) { + JSObject** np = reinterpret_cast<JSObject**>(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<NoGC>(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<UnboxedPlainObject>().expando_) { + TraceManuallyBarrieredEdge(trc, + reinterpret_cast<NativeObject**>(&obj->as<UnboxedPlainObject>().expando_), + "unboxed_expando"); + } + + const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layoutDontCheckGeneration(); + const int32_t* list = layout.traceList(); + if (!list) + return; + + uint8_t* data = obj->as<UnboxedPlainObject>().data(); + while (*list != -1) { + GCPtrString* heap = reinterpret_cast<GCPtrString*>(data + *list); + TraceEdge(trc, heap, "unboxed_string"); + list++; + } + list++; + while (*list != -1) { + GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(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<UnboxedPlainObject*> obj) +{ + if (obj->expando_) + return obj->expando_; + + UnboxedExpandoObject* expando = + NewObjectWithGivenProto<UnboxedExpandoObject>(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<PlainObject>(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<TaggedProto> 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<PlainObject>(); + 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<StackShape> 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<UnboxedPlainObject>().layout(); + UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando(); + + if (!layout.nativeGroup()) { + if (!UnboxedLayout::makeNativeGroup(cx, obj->group())) + return false; + + // makeNativeGroup can reentrantly invoke this method. + if (obj->is<PlainObject>()) + 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<UnboxedPlainObject>().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<PlainObject>().setLastPropertyMakeNative(cx, layout.nativeShape()); + + for (size_t i = 0; i < values.length(); i++) + obj->as<PlainObject>().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<jsid> ids(cx); + for (Shape::Range<NoGC> 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<PlainObject>()); + Rooted<UnboxedExpandoObject*> nexpando(cx, expando); + RootedId id(cx); + Rooted<PropertyDescriptor> 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<UnboxedPlainObject>(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<GCPtrString*>(data + *list); + heap->init(cx->names().empty); + list++; + } + list++; + while (*list != -1) { + GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(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<ConstructorCodeSignature>(layout.constructorCode()->raw()); + + JSObject* obj; + { + JS::AutoSuppressGCAnalysis nogc; + obj = reinterpret_cast<JSObject*>(CALL_GENERATED_2(function, properties, newKind)); + } + if (obj > reinterpret_cast<JSObject*>(CLEAR_CONSTRUCTOR_CODE_TOKEN)) + return obj; + + if (obj == reinterpret_cast<JSObject*>(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<UnboxedPlainObject>().containsUnboxedOrExpandoProperty(cx, id)) { + MarkNonNativePropertyFound<CanGC>(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<PropertyDescriptor> desc, + ObjectOpResult& result) +{ + const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().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<UnboxedPlainObject>().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<UnboxedExpandoObject*> expando(cx, ensureExpando(cx, obj.as<UnboxedPlainObject>())); + 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<UnboxedPlainObject>().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<UnboxedPlainObject>().layout(); + + if (const UnboxedLayout::Property* property = layout.lookup(id)) { + vp.set(obj->as<UnboxedPlainObject>().getValue(*property)); + return true; + } + + if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().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<UnboxedPlainObject>().layout(); + + if (const UnboxedLayout::Property* property = layout.lookup(id)) { + if (receiver.isObject() && obj == &receiver.toObject()) { + if (obj->as<UnboxedPlainObject>().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<UnboxedPlainObject>().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<PropertyDescriptor> desc) +{ + const UnboxedLayout& layout = obj->as<UnboxedPlainObject>().layout(); + + if (const UnboxedLayout::Property* property = layout.lookup(id)) { + desc.value().set(obj->as<UnboxedPlainObject>().getValue(*property)); + desc.setAttributes(JSPROP_ENUMERATE); + desc.object().set(obj); + return true; + } + + if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().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<UnboxedPlainObject>().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<GCVector<Value>> values, size_t* valueCursor) +{ + return values[(*valueCursor)++]; +} + +void +UnboxedPlainObject::fillAfterConvert(ExclusiveContext* cx, + Handle<GCVector<Value>> 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<UnboxedLayout> +{ + public: + struct Property { + PropertyName* name; + uint32_t offset; + JSValueType type; + + Property() + : name(nullptr), offset(UINT32_MAX), type(JSVAL_TYPE_MAGIC) + {} + }; + + typedef Vector<Property, 0, SystemAllocPolicy> 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<PropertyDescriptor> 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<PropertyDescriptor> 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<JSObject**>(&expando_); + } + + bool containsUnboxedOrExpandoProperty(ExclusiveContext* cx, jsid id) const; + + static UnboxedExpandoObject* ensureExpando(JSContext* cx, Handle<UnboxedPlainObject*> 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<GCVector<Value>> 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<js::UnboxedLayout> : public js::GCManagedDeletePolicy<js::UnboxedLayout> +{}; + +} /* namespace JS */ + +#endif /* vm_UnboxedObject_h */ |