summaryrefslogtreecommitdiffstats
path: root/js/src/vm/UnboxedObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/UnboxedObject.cpp')
-rw-r--r--js/src/vm/UnboxedObject.cpp400
1 files changed, 400 insertions, 0 deletions
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<NoGC> 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<int32_t, 8, SystemAllocPolicy> 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<int32_t, 8, SystemAllocPolicy> 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<int32_t>(entries.length());
+ if (!traceList)
+ return false;
+ PodCopy(traceList, entries.begin(), entries.length());
+ layout->setTraceList(traceList);
+ }
+
+ return true;
+}
+
static inline Value
NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
{
return values[(*valueCursor)++];
}
+static bool
+GetValuesFromPreliminaryArrayObject(ArrayObject* obj, MutableHandle<GCVector<Value>> 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<GCVector<Value>> 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<GCVector<Value>> 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<GCVector<Value>> 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<ArrayObject>(), &elementType))
+ return true;
+ } else {
+ if (!CombinePlainObjectProperties(&obj->as<PlainObject>(), 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<NoGC> 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<UnboxedLayout>& layout = enter.unboxedLayoutToCleanUp;
+ MOZ_ASSERT(!layout);
+ layout = group->zone()->make_unique<UnboxedLayout>();
+ 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<GCVector<Value>> values(cx, GCVector<Value>(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<ArrayObject>(), &values);
+ else
+ ok = GetValuesFromPreliminaryPlainObject(&obj->as<PlainObject>(), &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<NativeObject>().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<UnboxedArrayObject>().fillAfterConvert(cx, values, &valueCursor);
+ else
+ obj->as<UnboxedPlainObject>().fillAfterConvert(cx, values, &valueCursor);
+ }
+
+ MOZ_ASSERT(valueCursor == values.length());
+ return true;
+}
+
DefineBoxedOrUnboxedFunctor6(SetOrExtendBoxedOrUnboxedDenseElements,
ExclusiveContext*, JSObject*, uint32_t, const Value*, uint32_t,
ShouldUpdateTypes);