summaryrefslogtreecommitdiffstats
path: root/js/src/vm/UnboxedObject.cpp
diff options
context:
space:
mode:
authorwolfbeast <mcwerewolf@wolfbeast.com>2020-02-23 14:41:40 +0100
committerwolfbeast <mcwerewolf@wolfbeast.com>2020-04-14 12:57:13 +0200
commitaf69cb07db0d810a1a1a507b890e6beb23dc421c (patch)
tree2d52b9d0ac0dc9c48cd91631b7431b90d6703be6 /js/src/vm/UnboxedObject.cpp
parentdc4695406f02e26009f5f54a858344911f1aa404 (diff)
downloadUXP-af69cb07db0d810a1a1a507b890e6beb23dc421c.tar
UXP-af69cb07db0d810a1a1a507b890e6beb23dc421c.tar.gz
UXP-af69cb07db0d810a1a1a507b890e6beb23dc421c.tar.lz
UXP-af69cb07db0d810a1a1a507b890e6beb23dc421c.tar.xz
UXP-af69cb07db0d810a1a1a507b890e6beb23dc421c.zip
Revert #1137 - Remove unboxed arrays
- accounting for removal of watch()/unwatch() - updated for intermediate code changes.
Diffstat (limited to 'js/src/vm/UnboxedObject.cpp')
-rw-r--r--js/src/vm/UnboxedObject.cpp816
1 files changed, 801 insertions, 15 deletions
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<PlainObject>();
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.
@@ -926,6 +946,692 @@ const Class UnboxedPlainObject::class_ = {
};
/////////////////////////////////////////////////////////////////////
+// UnboxedArrayObject
+/////////////////////////////////////////////////////////////////////
+
+template <JSValueType Type>
+DenseElementResult
+AppendUnboxedDenseElements(UnboxedArrayObject* obj, uint32_t initlen,
+ MutableHandle<GCVector<Value>> values)
+{
+ for (size_t i = 0; i < initlen; i++)
+ values.infallibleAppend(obj->getElementSpecific<Type>(i));
+ return DenseElementResult::Success;
+}
+
+DefineBoxedOrUnboxedFunctor3(AppendUnboxedDenseElements,
+ UnboxedArrayObject*, uint32_t, MutableHandle<GCVector<Value>>);
+
+/* static */ bool
+UnboxedArrayObject::convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj,
+ ObjectGroup* group, Shape* shape)
+{
+ size_t length = obj->as<UnboxedArrayObject>().length();
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+
+ Rooted<GCVector<Value>> values(cx, GCVector<Value>(cx));
+ if (!values.reserve(initlen))
+ return false;
+
+ AppendUnboxedDenseElementsFunctor functor(&obj->as<UnboxedArrayObject>(), initlen, &values);
+ DebugOnly<DenseElementResult> result = CallBoxedOrUnboxedSpecialization(functor, obj);
+ MOZ_ASSERT(result.value == DenseElementResult::Success);
+
+ obj->setGroup(group);
+
+ ArrayObject* aobj = &obj->as<ArrayObject>();
+ 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<size_t>(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<UnboxedArrayObject>().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<int32_t> values(cx);
+ if (!values.reserve(initializedLength()))
+ return false;
+ for (size_t i = 0; i < initializedLength(); i++)
+ values.infallibleAppend(getElementSpecific<JSVAL_TYPE_INT32>(i).toInt32());
+
+ uint8_t* newElements;
+ if (hasInlineElements()) {
+ newElements = AllocateObjectBuffer<uint8_t>(cx, this, capacity() * sizeof(double));
+ } else {
+ newElements = ReallocateObjectBuffer<uint8_t>(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<JSVAL_TYPE_DOUBLE>(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<UnboxedArrayObject>(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<UnboxedArrayObject>(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<uint8_t>(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<void**>(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<UnboxedArrayObject>().elementType();
+ if (!UnboxedTypeNeedsPreBarrier(type))
+ return;
+
+ MOZ_ASSERT(obj->as<UnboxedArrayObject>().elementSize() == sizeof(uintptr_t));
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+ void** elements = reinterpret_cast<void**>(obj->as<UnboxedArrayObject>().elements());
+
+ switch (type) {
+ case JSVAL_TYPE_OBJECT:
+ for (size_t i = 0; i < initlen; i++) {
+ GCPtrObject* heap = reinterpret_cast<GCPtrObject*>(elements + i);
+ TraceNullableEdge(trc, heap, "unboxed_object");
+ }
+ break;
+
+ case JSVAL_TYPE_STRING:
+ for (size_t i = 0; i < initlen; i++) {
+ GCPtrString* heap = reinterpret_cast<GCPtrString*>(elements + i);
+ TraceEdge(trc, heap, "unboxed_string");
+ }
+ break;
+
+ default:
+ MOZ_CRASH();
+ }
+}
+
+/* static */ void
+UnboxedArrayObject::objectMoved(JSObject* obj, const JSObject* old)
+{
+ UnboxedArrayObject& dst = obj->as<UnboxedArrayObject>();
+ const UnboxedArrayObject& src = old->as<UnboxedArrayObject>();
+
+ // 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<UnboxedArrayObject>().hasInlineElements())
+ js_free(obj->as<UnboxedArrayObject>().elements());
+}
+
+/* static */ size_t
+UnboxedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
+ gc::AllocKind allocKind)
+{
+ UnboxedArrayObject* ndst = &dst->as<UnboxedArrayObject>();
+ UnboxedArrayObject* nsrc = &src->as<UnboxedArrayObject>();
+ 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<uint8_t>(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<uint8_t>(cx, this, newCapacity * elementSize());
+ if (!newElements)
+ return false;
+ js_memcpy(newElements, elements(), initializedLength() * elementSize());
+ } else {
+ newElements = ReallocateObjectBuffer<uint8_t>(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<uint8_t>(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<UnboxedArrayObject>().containsProperty(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
+UnboxedArrayObject::obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<PropertyDescriptor> desc,
+ ObjectOpResult& result)
+{
+ if (JSID_IS_INT(id) && !desc.getter() && !desc.setter() && desc.attributes() == JSPROP_ENUMERATE) {
+ UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+
+ 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<UnboxedArrayObject>().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<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (JSID_IS_INT(id))
+ vp.set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
+ else
+ vp.set(Int32Value(obj->as<UnboxedArrayObject>().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<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (receiver.isObject() && obj == &receiver.toObject()) {
+ if (JSID_IS_INT(id)) {
+ if (obj->as<UnboxedArrayObject>().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<UnboxedArrayObject>();
+ 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<PropertyDescriptor> desc)
+{
+ if (obj->as<UnboxedArrayObject>().containsProperty(cx, id)) {
+ if (JSID_IS_INT(id)) {
+ desc.value().set(obj->as<UnboxedArrayObject>().getElement(JSID_TO_INT(id)));
+ desc.setAttributes(JSPROP_ENUMERATE);
+ } else {
+ desc.value().set(Int32Value(obj->as<UnboxedArrayObject>().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<UnboxedArrayObject>().containsProperty(cx, id)) {
+ size_t initlen = obj->as<UnboxedArrayObject>().initializedLength();
+ if (JSID_IS_INT(id) && JSID_TO_INT(id) == int32_t(initlen - 1)) {
+ obj->as<UnboxedArrayObject>().setInitializedLength(initlen - 1);
+ obj->as<UnboxedArrayObject>().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<UnboxedArrayObject>().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
/////////////////////////////////////////////////////////////////////
@@ -936,6 +1642,31 @@ NextValue(Handle<GCVector<Value>> values, size_t* valueCursor)
}
void
+UnboxedArrayObject::fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> 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<GCVector<Value>> 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);
+}