summaryrefslogtreecommitdiffstats
path: root/js/src/vm
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
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')
-rw-r--r--js/src/vm/Interpreter-inl.h2
-rw-r--r--js/src/vm/Interpreter.cpp16
-rw-r--r--js/src/vm/JSONParser.cpp4
-rw-r--r--js/src/vm/NativeObject-inl.h32
-rw-r--r--js/src/vm/NativeObject.h15
-rw-r--r--js/src/vm/ObjectGroup.cpp103
-rw-r--r--js/src/vm/ObjectGroup.h10
-rw-r--r--js/src/vm/Opcodes.h12
-rw-r--r--js/src/vm/ReceiverGuard.cpp8
-rw-r--r--js/src/vm/Stack.cpp2
-rw-r--r--js/src/vm/Stack.h2
-rw-r--r--js/src/vm/TypeInference.cpp6
-rw-r--r--js/src/vm/TypeInference.h4
-rw-r--r--js/src/vm/UnboxedObject-inl.h663
-rw-r--r--js/src/vm/UnboxedObject.cpp816
-rw-r--r--js/src/vm/UnboxedObject.h207
16 files changed, 1814 insertions, 88 deletions
diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h
index acfa8f74b..adefa6e93 100644
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -593,7 +593,7 @@ InitArrayElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, uint32_t
JSOp op = JSOp(*pc);
MOZ_ASSERT(op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC);
- MOZ_ASSERT(obj->is<ArrayObject>());
+ MOZ_ASSERT(obj->is<ArrayObject>() || obj->is<UnboxedArrayObject>());
if (op == JSOP_INITELEM_INC && index == INT32_MAX) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SPREAD_TOO_LARGE);
diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp
index b87d12924..834084c4d 100644
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1939,7 +1939,6 @@ CASE(EnableInterruptsPseudoOpcode)
/* Various 1-byte no-ops. */
CASE(JSOP_NOP)
CASE(JSOP_NOP_DESTRUCTURING)
-CASE(JSOP_UNUSED126)
CASE(JSOP_UNUSED211)
CASE(JSOP_TRY_DESTRUCTURING_ITERCLOSE)
CASE(JSOP_UNUSED221)
@@ -3682,6 +3681,7 @@ CASE(JSOP_NEWINIT)
END_CASE(JSOP_NEWINIT)
CASE(JSOP_NEWARRAY)
+CASE(JSOP_SPREADCALLARRAY)
{
uint32_t length = GET_UINT32(REGS.pc);
JSObject* obj = NewArrayOperation(cx, script, REGS.pc, length);
@@ -4979,7 +4979,7 @@ js::NewObjectOperation(JSContext* cx, HandleScript script, jsbytecode* pc,
newKind = TenuredObject;
}
- RootedPlainObject obj(cx);
+ RootedObject obj(cx);
if (*pc == JSOP_NEWOBJECT) {
RootedPlainObject baseObject(cx, &script->getObject(pc)->as<PlainObject>());
@@ -5047,6 +5047,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32
if (group->shouldPreTenure() || group->maybePreliminaryObjects())
newKind = TenuredObject;
+
+ if (group->maybeUnboxedLayout())
+ return UnboxedArrayObject::create(cx, group, length, newKind);
}
ArrayObject* obj = NewDenseFullyAllocatedArray(cx, length, nullptr, newKind);
@@ -5057,6 +5060,9 @@ js::NewArrayOperation(JSContext* cx, HandleScript script, jsbytecode* pc, uint32
MOZ_ASSERT(obj->isSingleton());
} else {
obj->setGroup(group);
+
+ if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects())
+ preliminaryObjects->registerNewObject(obj);
}
return obj;
@@ -5069,6 +5075,12 @@ js::NewArrayOperationWithTemplate(JSContext* cx, HandleObject templateObject)
NewObjectKind newKind = templateObject->group()->shouldPreTenure() ? TenuredObject : GenericObject;
+ if (templateObject->is<UnboxedArrayObject>()) {
+ uint32_t length = templateObject->as<UnboxedArrayObject>().length();
+ RootedObjectGroup group(cx, templateObject->group());
+ return UnboxedArrayObject::create(cx, group, length, newKind);
+ }
+
ArrayObject* obj = NewDenseFullyAllocatedArray(cx, templateObject->as<ArrayObject>().length(),
nullptr, newKind);
if (!obj)
diff --git a/js/src/vm/JSONParser.cpp b/js/src/vm/JSONParser.cpp
index e50da3bc4..01883bb15 100644
--- a/js/src/vm/JSONParser.cpp
+++ b/js/src/vm/JSONParser.cpp
@@ -606,8 +606,8 @@ JSONParserBase::finishArray(MutableHandleValue vp, ElementVector& elements)
{
MOZ_ASSERT(&elements == &stack.back().elements());
- ArrayObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
- GenericObject);
+ JSObject* obj = ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
+ GenericObject);
if (!obj)
return false;
diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h
index 030d92c12..205d99aa2 100644
--- a/js/src/vm/NativeObject-inl.h
+++ b/js/src/vm/NativeObject-inl.h
@@ -235,38 +235,6 @@ NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t
return DenseElementResult::Success;
}
-inline DenseElementResult
-NativeObject::setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp,
- uint32_t count,
- ShouldUpdateTypes updateTypes)
-{
- if (denseElementsAreFrozen())
- return DenseElementResult::Incomplete;
-
- if (is<ArrayObject>() &&
- !as<ArrayObject>().lengthIsWritable() &&
- start + count >= as<ArrayObject>().length())
- {
- return DenseElementResult::Incomplete;
- }
-
- DenseElementResult result = ensureDenseElements(cx, start, count);
- if (result != DenseElementResult::Success)
- return result;
-
- if (is<ArrayObject>() && start + count >= as<ArrayObject>().length())
- as<ArrayObject>().setLengthInt32(start + count);
-
- if (updateTypes == ShouldUpdateTypes::DontUpdate && !shouldConvertDoubleElements()) {
- copyDenseElements(start, vp, count);
- } else {
- for (size_t i = 0; i < count; i++)
- setDenseElementWithType(cx, start + i, vp[i]);
- }
-
- return DenseElementResult::Success;
-}
-
inline Value
NativeObject::getDenseOrTypedArrayElement(uint32_t idx)
{
diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h
index 6595703dc..107fade8c 100644
--- a/js/src/vm/NativeObject.h
+++ b/js/src/vm/NativeObject.h
@@ -339,19 +339,16 @@ IsObjectValueInCompartment(const Value& v, JSCompartment* comp);
#endif
// Operations which change an object's dense elements can either succeed, fail,
-// or be unable to complete. The latter is used when the object's elements must
-// become sparse instead. The enum below is used for such operations.
+// or be unable to complete. For native objects, the latter is used when the
+// object's elements must become sparse instead. The enum below is used for
+// such operations, and for similar operations on unboxed arrays and methods
+// that work on both kinds of objects.
enum class DenseElementResult {
Failure,
Success,
Incomplete
};
-enum class ShouldUpdateTypes {
- Update,
- DontUpdate
-};
-
/*
* NativeObject specifies the internal implementation of a native object.
*
@@ -1154,10 +1151,6 @@ class NativeObject : public ShapedObject
elementsRangeWriteBarrierPost(dstStart, count);
}
- inline DenseElementResult
- setOrExtendDenseElements(ExclusiveContext* cx, uint32_t start, const Value* vp, uint32_t count,
- ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
-
bool shouldConvertDoubleElements() {
return getElementsHeader()->shouldConvertDoubleElements();
}
diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp
index 95fcada94..d05a48646 100644
--- a/js/src/vm/ObjectGroup.cpp
+++ b/js/src/vm/ObjectGroup.cpp
@@ -777,7 +777,7 @@ GetValueTypeForTable(const Value& v)
return type;
}
-/* static */ ArrayObject*
+/* static */ JSObject*
ObjectGroup::newArrayObject(ExclusiveContext* cx,
const Value* vp, size_t length,
NewObjectKind newKind, NewArrayKind arrayKind)
@@ -841,13 +841,56 @@ ObjectGroup::newArrayObject(ExclusiveContext* cx,
AddTypePropertyId(cx, group, nullptr, JSID_VOID, elementType);
+ if (elementType != TypeSet::UnknownType()) {
+ // Keep track of the initial objects we create with this type.
+ // If the initial ones have a consistent shape and property types, we
+ // will try to use an unboxed layout for the group.
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+ if (!preliminaryObjects)
+ return nullptr;
+ group->setPreliminaryObjects(preliminaryObjects);
+ }
+
if (!p.add(cx, *table, ObjectGroupCompartment::ArrayObjectKey(elementType), group))
return nullptr;
}
// The type of the elements being added will already be reflected in type
- // information.
+ // information, but make sure when creating an unboxed array that the
+ // common element type is suitable for the unboxed representation.
ShouldUpdateTypes updateTypes = ShouldUpdateTypes::DontUpdate;
+ if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length))
+ return nullptr;
+ if (group->maybePreliminaryObjects())
+ group->maybePreliminaryObjects()->maybeAnalyze(cx, group);
+ if (group->maybeUnboxedLayout()) {
+ switch (group->unboxedLayout().elementType()) {
+ case JSVAL_TYPE_BOOLEAN:
+ if (elementType != TypeSet::BooleanType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_INT32:
+ if (elementType != TypeSet::Int32Type())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_DOUBLE:
+ if (elementType != TypeSet::Int32Type() && elementType != TypeSet::DoubleType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_STRING:
+ if (elementType != TypeSet::StringType())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ case JSVAL_TYPE_OBJECT:
+ if (elementType != TypeSet::NullType() && !elementType.get().isObjectUnchecked())
+ updateTypes = ShouldUpdateTypes::Update;
+ break;
+ default:
+ MOZ_CRASH();
+ }
+ }
+
return NewCopiedArrayTryUseGroup(cx, group, vp, length, newKind, updateTypes);
}
@@ -857,15 +900,49 @@ GiveObjectGroup(ExclusiveContext* cx, JSObject* source, JSObject* target)
{
MOZ_ASSERT(source->group() != target->group());
- if (!target->is<ArrayObject>() || !source->is<ArrayObject>()) {
+ if (!target->is<ArrayObject>() && !target->is<UnboxedArrayObject>())
return true;
+
+ if (target->group()->maybePreliminaryObjects()) {
+ bool force = IsInsideNursery(source);
+ target->group()->maybePreliminaryObjects()->maybeAnalyze(cx, target->group(), force);
}
- source->setGroup(target->group());
+ if (target->is<ArrayObject>()) {
+ ObjectGroup* sourceGroup = source->group();
- for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
- Value v = source->as<ArrayObject>().getDenseElement(i);
- AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
+ if (source->is<UnboxedArrayObject>()) {
+ Shape* shape = target->as<ArrayObject>().lastProperty();
+ if (!UnboxedArrayObject::convertToNativeWithGroup(cx, source, target->group(), shape))
+ return false;
+ } else if (source->is<ArrayObject>()) {
+ source->setGroup(target->group());
+ } else {
+ return true;
+ }
+
+ if (sourceGroup->maybePreliminaryObjects())
+ sourceGroup->maybePreliminaryObjects()->unregisterObject(source);
+ if (target->group()->maybePreliminaryObjects())
+ target->group()->maybePreliminaryObjects()->registerNewObject(source);
+
+ for (size_t i = 0; i < source->as<ArrayObject>().getDenseInitializedLength(); i++) {
+ Value v = source->as<ArrayObject>().getDenseElement(i);
+ AddTypePropertyId(cx, source->group(), source, JSID_VOID, v);
+ }
+
+ return true;
+ }
+
+ if (target->is<UnboxedArrayObject>()) {
+ if (!source->is<UnboxedArrayObject>())
+ return true;
+ if (source->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_INT32)
+ return true;
+ if (target->as<UnboxedArrayObject>().elementType() != JSVAL_TYPE_DOUBLE)
+ return true;
+
+ return source->as<UnboxedArrayObject>().convertInt32ToDouble(cx, target->group());
}
return true;
@@ -1429,6 +1506,18 @@ ObjectGroup::allocationSiteGroup(JSContext* cx, JSScript* scriptArg, jsbytecode*
}
}
+ if (kind == JSProto_Array &&
+ (JSOp(*pc) == JSOP_NEWARRAY || IsCallPC(pc)) &&
+ cx->options().unboxedArrays())
+ {
+ PreliminaryObjectArrayWithTemplate* preliminaryObjects =
+ cx->new_<PreliminaryObjectArrayWithTemplate>(nullptr);
+ if (preliminaryObjects)
+ res->setPreliminaryObjects(preliminaryObjects);
+ else
+ cx->recoverFromOutOfMemory();
+ }
+
if (!table->add(p, key, res)) {
ReportOutOfMemory(cx);
return nullptr;
diff --git a/js/src/vm/ObjectGroup.h b/js/src/vm/ObjectGroup.h
index 553cb8366..4e24de9f1 100644
--- a/js/src/vm/ObjectGroup.h
+++ b/js/src/vm/ObjectGroup.h
@@ -505,11 +505,11 @@ class ObjectGroup : public gc::TenuredCell
UnknownIndex // Make an array with an unknown element type.
};
- // Create an ArrayObject with the specified elements and a group specialized
- // for the elements.
- static ArrayObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length,
- NewObjectKind newKind,
- NewArrayKind arrayKind = NewArrayKind::Normal);
+ // Create an ArrayObject or UnboxedArrayObject with the specified elements
+ // and a group specialized for the elements.
+ static JSObject* newArrayObject(ExclusiveContext* cx, const Value* vp, size_t length,
+ NewObjectKind newKind,
+ NewArrayKind arrayKind = NewArrayKind::Normal);
// Create a PlainObject or UnboxedPlainObject with the specified properties
// and a group specialized for those properties.
diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h
index e13cd6221..b26336815 100644
--- a/js/src/vm/Opcodes.h
+++ b/js/src/vm/Opcodes.h
@@ -1281,7 +1281,17 @@
* Stack: receiver, obj, propval => obj[propval]
*/ \
macro(JSOP_GETELEM_SUPER, 125, "getelem-super", NULL, 1, 3, 1, JOF_BYTE |JOF_ELEM|JOF_LEFTASSOC) \
- macro(JSOP_UNUSED126, 126, "unused126", NULL, 5, 0, 1, JOF_UINT32) \
+ /*
+ * Pushes newly created array for a spread call onto the stack. This has
+ * the same semantics as JSOP_NEWARRAY, but is distinguished to avoid
+ * using unboxed arrays in spread calls, which would make compiling spread
+ * calls in baseline more complex.
+ * Category: Literals
+ * Type: Array
+ * Operands: uint32_t length
+ * Stack: => obj
+ */ \
+ macro(JSOP_SPREADCALLARRAY, 126, "spreadcallarray", NULL, 5, 0, 1, JOF_UINT32) \
\
/*
* Defines the given function on the current scope.
diff --git a/js/src/vm/ReceiverGuard.cpp b/js/src/vm/ReceiverGuard.cpp
index e37bf8ee5..11c2d0727 100644
--- a/js/src/vm/ReceiverGuard.cpp
+++ b/js/src/vm/ReceiverGuard.cpp
@@ -19,7 +19,7 @@ ReceiverGuard::ReceiverGuard(JSObject* obj)
group = obj->group();
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
shape = expando->lastProperty();
- } else if (obj->is<TypedObject>()) {
+ } else if (obj->is<UnboxedArrayObject>() || obj->is<TypedObject>()) {
group = obj->group();
} else {
shape = obj->maybeShape();
@@ -34,7 +34,7 @@ ReceiverGuard::ReceiverGuard(ObjectGroup* group, Shape* shape)
const Class* clasp = group->clasp();
if (clasp == &UnboxedPlainObject::class_) {
// Keep both group and shape.
- } else if (IsTypedObjectClass(clasp)) {
+ } else if (clasp == &UnboxedArrayObject::class_ || IsTypedObjectClass(clasp)) {
this->shape = nullptr;
} else {
this->group = nullptr;
@@ -49,8 +49,8 @@ HeapReceiverGuard::keyBits(JSObject* obj)
// Both the group and shape need to be guarded for unboxed plain objects.
return obj->as<UnboxedPlainObject>().maybeExpando() ? 0 : 1;
}
- if (obj->is<TypedObject>()) {
- // Only the group needs to be guarded for typed objects.
+ if (obj->is<UnboxedArrayObject>() || obj->is<TypedObject>()) {
+ // Only the group needs to be guarded for unboxed arrays and typed objects.
return 2;
}
// Other objects only need the shape to be guarded.
diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp
index 95940eeaf..7eb8f4ab8 100644
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -82,7 +82,7 @@ InterpreterFrame::isNonGlobalEvalFrame() const
return isEvalFrame() && script()->bodyScope()->as<EvalScope>().isNonGlobal();
}
-ArrayObject*
+JSObject*
InterpreterFrame::createRestParameter(JSContext* cx)
{
MOZ_ASSERT(script()->hasRest());
diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h
index fe04a00f2..d08544213 100644
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -523,7 +523,7 @@ class InterpreterFrame
ArgumentsObject& argsObj() const;
void initArgsObj(ArgumentsObject& argsobj);
- ArrayObject* createRestParameter(JSContext* cx);
+ JSObject* createRestParameter(JSContext* cx);
/*
* Environment chain
diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp
index 39206539b..ee5bbef5d 100644
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -2509,7 +2509,7 @@ TemporaryTypeSet::propertyNeedsBarrier(CompilerConstraintList* constraints, jsid
bool
js::ClassCanHaveExtraProperties(const Class* clasp)
{
- if (clasp == &UnboxedPlainObject::class_)
+ if (clasp == &UnboxedPlainObject::class_ || clasp == &UnboxedArrayObject::class_)
return false;
return clasp->getResolve()
|| clasp->getOpsLookupProperty()
@@ -3400,7 +3400,7 @@ JSFunction::setTypeForScriptedFunction(ExclusiveContext* cx, HandleFunction fun,
/////////////////////////////////////////////////////////////////////
void
-PreliminaryObjectArray::registerNewObject(PlainObject* res)
+PreliminaryObjectArray::registerNewObject(JSObject* res)
{
// The preliminary object pointers are weak, and won't be swept properly
// during nursery collections, so the preliminary objects need to be
@@ -3418,7 +3418,7 @@ PreliminaryObjectArray::registerNewObject(PlainObject* res)
}
void
-PreliminaryObjectArray::unregisterObject(PlainObject* obj)
+PreliminaryObjectArray::unregisterObject(JSObject* obj)
{
for (size_t i = 0; i < COUNT; i++) {
if (objects[i] == obj) {
diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h
index fd021fc96..537baa21f 100644
--- a/js/src/vm/TypeInference.h
+++ b/js/src/vm/TypeInference.h
@@ -871,8 +871,8 @@ class PreliminaryObjectArray
public:
PreliminaryObjectArray() = default;
- void registerNewObject(PlainObject* res);
- void unregisterObject(PlainObject* obj);
+ void registerNewObject(JSObject* res);
+ void unregisterObject(JSObject* obj);
JSObject* get(size_t i) const {
MOZ_ASSERT(i < COUNT);
diff --git a/js/src/vm/UnboxedObject-inl.h b/js/src/vm/UnboxedObject-inl.h
index c1468a5b1..93ad7bf28 100644
--- a/js/src/vm/UnboxedObject-inl.h
+++ b/js/src/vm/UnboxedObject-inl.h
@@ -172,6 +172,669 @@ UnboxedPlainObject::layout() const
return group()->unboxedLayout();
}
+/////////////////////////////////////////////////////////////////////
+// UnboxedArrayObject
+/////////////////////////////////////////////////////////////////////
+
+inline const UnboxedLayout&
+UnboxedArrayObject::layout() const
+{
+ return group()->unboxedLayout();
+}
+
+inline void
+UnboxedArrayObject::setLength(ExclusiveContext* cx, uint32_t length)
+{
+ if (length > INT32_MAX) {
+ // Track objects with overflowing lengths in type information.
+ MarkObjectGroupFlags(cx, this, OBJECT_FLAG_LENGTH_OVERFLOW);
+ }
+
+ length_ = length;
+}
+
+inline void
+UnboxedArrayObject::setInitializedLength(uint32_t initlen)
+{
+ if (initlen < initializedLength()) {
+ switch (elementType()) {
+ case JSVAL_TYPE_STRING:
+ for (size_t i = initlen; i < initializedLength(); i++)
+ triggerPreBarrier<JSVAL_TYPE_STRING>(i);
+ break;
+ case JSVAL_TYPE_OBJECT:
+ for (size_t i = initlen; i < initializedLength(); i++)
+ triggerPreBarrier<JSVAL_TYPE_OBJECT>(i);
+ break;
+ default:
+ MOZ_ASSERT(!UnboxedTypeNeedsPreBarrier(elementType()));
+ }
+ }
+ setInitializedLengthNoBarrier(initlen);
+}
+
+template <JSValueType Type>
+inline bool
+UnboxedArrayObject::setElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ true);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::setElementNoTypeChangeSpecific(size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ true);
+}
+
+template <JSValueType Type>
+inline bool
+UnboxedArrayObject::initElementSpecific(ExclusiveContext* cx, size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValue(cx, this, JSID_VOID, p, elementType(), v, /* preBarrier = */ false);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::initElementNoTypeChangeSpecific(size_t index, const Value& v)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return SetUnboxedValueNoTypeChange(this, p, elementType(), v, /* preBarrier = */ false);
+}
+
+template <JSValueType Type>
+inline Value
+UnboxedArrayObject::getElementSpecific(size_t index)
+{
+ MOZ_ASSERT(index < initializedLength());
+ MOZ_ASSERT(Type == elementType());
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+ return GetUnboxedValue(p, Type, /* maybeUninitialized = */ false);
+}
+
+template <JSValueType Type>
+inline void
+UnboxedArrayObject::triggerPreBarrier(size_t index)
+{
+ MOZ_ASSERT(UnboxedTypeNeedsPreBarrier(Type));
+
+ uint8_t* p = elements() + index * UnboxedTypeSize(Type);
+
+ switch (Type) {
+ case JSVAL_TYPE_STRING: {
+ JSString** np = reinterpret_cast<JSString**>(p);
+ JSString::writeBarrierPre(*np);
+ break;
+ }
+
+ case JSVAL_TYPE_OBJECT: {
+ JSObject** np = reinterpret_cast<JSObject**>(p);
+ JSObject::writeBarrierPre(*np);
+ break;
+ }
+
+ default:
+ MOZ_CRASH("Bad type");
+ }
+}
+
+/////////////////////////////////////////////////////////////////////
+// Combined methods for NativeObject and UnboxedArrayObject accesses.
+/////////////////////////////////////////////////////////////////////
+
+static inline bool
+HasAnyBoxedOrUnboxedDenseElements(JSObject* obj)
+{
+ return obj->isNative() || obj->is<UnboxedArrayObject>();
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedInitializedLength(JSObject* obj)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseInitializedLength();
+ if (obj->is<UnboxedArrayObject>())
+ return obj->as<UnboxedArrayObject>().initializedLength();
+ return 0;
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedCapacity(JSObject* obj)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseCapacity();
+ if (obj->is<UnboxedArrayObject>())
+ return obj->as<UnboxedArrayObject>().capacity();
+ return 0;
+}
+
+static inline Value
+GetAnyBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
+{
+ if (obj->isNative())
+ return obj->as<NativeObject>().getDenseElement(index);
+ return obj->as<UnboxedArrayObject>().getElement(index);
+}
+
+static inline size_t
+GetAnyBoxedOrUnboxedArrayLength(JSObject* obj)
+{
+ if (obj->is<ArrayObject>())
+ return obj->as<ArrayObject>().length();
+ return obj->as<UnboxedArrayObject>().length();
+}
+
+static inline void
+SetAnyBoxedOrUnboxedArrayLength(JSContext* cx, JSObject* obj, size_t length)
+{
+ if (obj->is<ArrayObject>()) {
+ MOZ_ASSERT(length >= obj->as<ArrayObject>().length());
+ obj->as<ArrayObject>().setLength(cx, length);
+ } else {
+ MOZ_ASSERT(length >= obj->as<UnboxedArrayObject>().length());
+ obj->as<UnboxedArrayObject>().setLength(cx, length);
+ }
+}
+
+static inline bool
+SetAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (obj->isNative()) {
+ obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().setElement(cx, index, value);
+}
+
+static inline bool
+InitAnyBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (obj->isNative()) {
+ obj->as<NativeObject>().initDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().initElement(cx, index, value);
+}
+
+/////////////////////////////////////////////////////////////////////
+// Template methods for NativeObject and UnboxedArrayObject accesses.
+/////////////////////////////////////////////////////////////////////
+
+static inline JSValueType
+GetBoxedOrUnboxedType(JSObject* obj)
+{
+ if (obj->isNative())
+ return JSVAL_TYPE_MAGIC;
+ return obj->as<UnboxedArrayObject>().elementType();
+}
+
+template <JSValueType Type>
+static inline bool
+HasBoxedOrUnboxedDenseElements(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->isNative();
+ return obj->is<UnboxedArrayObject>() && obj->as<UnboxedArrayObject>().elementType() == Type;
+}
+
+template <JSValueType Type>
+static inline size_t
+GetBoxedOrUnboxedInitializedLength(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseInitializedLength();
+ return obj->as<UnboxedArrayObject>().initializedLength();
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+SetBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen)
+{
+ size_t oldInitlen = GetBoxedOrUnboxedInitializedLength<Type>(obj);
+ if (Type == JSVAL_TYPE_MAGIC) {
+ obj->as<NativeObject>().setDenseInitializedLength(initlen);
+ if (initlen < oldInitlen)
+ obj->as<NativeObject>().shrinkElements(cx, initlen);
+ } else {
+ obj->as<UnboxedArrayObject>().setInitializedLength(initlen);
+ if (initlen < oldInitlen)
+ obj->as<UnboxedArrayObject>().shrinkElements(cx, initlen);
+ }
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline size_t
+GetBoxedOrUnboxedCapacity(JSObject* obj)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseCapacity();
+ return obj->as<UnboxedArrayObject>().capacity();
+}
+
+template <JSValueType Type>
+static inline Value
+GetBoxedOrUnboxedDenseElement(JSObject* obj, size_t index)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ return obj->as<NativeObject>().getDenseElement(index);
+ return obj->as<UnboxedArrayObject>().getElementSpecific<Type>(index);
+}
+
+template <JSValueType Type>
+static inline void
+SetBoxedOrUnboxedDenseElementNoTypeChange(JSObject* obj, size_t index, const Value& value)
+{
+ if (Type == JSVAL_TYPE_MAGIC)
+ obj->as<NativeObject>().setDenseElement(index, value);
+ else
+ obj->as<UnboxedArrayObject>().setElementNoTypeChangeSpecific<Type>(index, value);
+}
+
+template <JSValueType Type>
+static inline bool
+SetBoxedOrUnboxedDenseElement(JSContext* cx, JSObject* obj, size_t index, const Value& value)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ obj->as<NativeObject>().setDenseElementWithType(cx, index, value);
+ return true;
+ }
+ return obj->as<UnboxedArrayObject>().setElementSpecific<Type>(cx, index, value);
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+EnsureBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ if (!obj->as<ArrayObject>().ensureElements(cx, count))
+ return DenseElementResult::Failure;
+ } else {
+ if (obj->as<UnboxedArrayObject>().capacity() < count) {
+ if (!obj->as<UnboxedArrayObject>().growElements(cx, count))
+ return DenseElementResult::Failure;
+ }
+ }
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+SetOrExtendBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
+ uint32_t start, const Value* vp, uint32_t count,
+ ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update)
+{
+ if (Type == JSVAL_TYPE_MAGIC) {
+ NativeObject* nobj = &obj->as<NativeObject>();
+
+ if (nobj->denseElementsAreFrozen())
+ return DenseElementResult::Incomplete;
+
+ if (obj->is<ArrayObject>() &&
+ !obj->as<ArrayObject>().lengthIsWritable() &&
+ start + count >= obj->as<ArrayObject>().length())
+ {
+ return DenseElementResult::Incomplete;
+ }
+
+ DenseElementResult result = nobj->ensureDenseElements(cx, start, count);
+ if (result != DenseElementResult::Success)
+ return result;
+
+ if (obj->is<ArrayObject>() && start + count >= obj->as<ArrayObject>().length())
+ obj->as<ArrayObject>().setLengthInt32(start + count);
+
+ if (updateTypes == ShouldUpdateTypes::DontUpdate && !nobj->shouldConvertDoubleElements()) {
+ nobj->copyDenseElements(start, vp, count);
+ } else {
+ for (size_t i = 0; i < count; i++)
+ nobj->setDenseElementWithType(cx, start + i, vp[i]);
+ }
+
+ return DenseElementResult::Success;
+ }
+
+ UnboxedArrayObject* nobj = &obj->as<UnboxedArrayObject>();
+
+ if (start > nobj->initializedLength())
+ return DenseElementResult::Incomplete;
+
+ if (start + count >= UnboxedArrayObject::MaximumCapacity)
+ return DenseElementResult::Incomplete;
+
+ if (start + count > nobj->capacity() && !nobj->growElements(cx, start + count))
+ return DenseElementResult::Failure;
+
+ size_t oldInitlen = nobj->initializedLength();
+
+ // Overwrite any existing elements covered by the new range. If we fail
+ // after this point due to some incompatible type being written to the
+ // object's elements, afterwards the contents will be different from when
+ // we started. The caller must retry the operation using a generic path,
+ // which will overwrite the already-modified elements as well as the ones
+ // that were left alone.
+ size_t i = 0;
+ if (updateTypes == ShouldUpdateTypes::DontUpdate) {
+ for (size_t j = start; i < count && j < oldInitlen; i++, j++)
+ nobj->setElementNoTypeChangeSpecific<Type>(j, vp[i]);
+ } else {
+ for (size_t j = start; i < count && j < oldInitlen; i++, j++) {
+ if (!nobj->setElementSpecific<Type>(cx, j, vp[i]))
+ return DenseElementResult::Incomplete;
+ }
+ }
+
+ if (i != count) {
+ obj->as<UnboxedArrayObject>().setInitializedLength(start + count);
+ if (updateTypes == ShouldUpdateTypes::DontUpdate) {
+ for (; i < count; i++)
+ nobj->initElementNoTypeChangeSpecific<Type>(start + i, vp[i]);
+ } else {
+ for (; i < count; i++) {
+ if (!nobj->initElementSpecific<Type>(cx, start + i, vp[i])) {
+ nobj->setInitializedLengthNoBarrier(oldInitlen);
+ return DenseElementResult::Incomplete;
+ }
+ }
+ }
+ }
+
+ if (start + count >= nobj->length())
+ nobj->setLength(cx, start + count);
+
+ return DenseElementResult::Success;
+}
+
+template <JSValueType Type>
+static inline DenseElementResult
+MoveBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, uint32_t dstStart, uint32_t srcStart,
+ uint32_t length)
+{
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<Type>(obj));
+
+ if (Type == JSVAL_TYPE_MAGIC) {
+ if (obj->as<NativeObject>().denseElementsAreFrozen())
+ return DenseElementResult::Incomplete;
+
+ if (!obj->as<NativeObject>().maybeCopyElementsForWrite(cx))
+ return DenseElementResult::Failure;
+ obj->as<NativeObject>().moveDenseElements(dstStart, srcStart, length);
+ } else {
+ uint8_t* data = obj->as<UnboxedArrayObject>().elements();
+ size_t elementSize = UnboxedTypeSize(Type);
+
+ if (UnboxedTypeNeedsPreBarrier(Type) &&
+ JS::shadow::Zone::asShadowZone(obj->zone())->needsIncrementalBarrier())
+ {
+ // Trigger pre barriers on any elements we are overwriting. See
+ // NativeObject::moveDenseElements. No post barrier is needed as
+ // only whole cell post barriers are used with unboxed objects.
+ for (size_t i = 0; i < length; i++)
+ obj->as<UnboxedArrayObject>().triggerPreBarrier<Type>(dstStart + i);
+ }
+
+ memmove(data + dstStart * elementSize,
+ data + srcStart * elementSize,
+ length * elementSize);
+ }
+
+ return DenseElementResult::Success;
+}
+
+template <JSValueType DstType, JSValueType SrcType>
+static inline DenseElementResult
+CopyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length)
+{
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<SrcType>(src));
+ MOZ_ASSERT(HasBoxedOrUnboxedDenseElements<DstType>(dst));
+ MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<DstType>(dst) == dstStart);
+ MOZ_ASSERT(GetBoxedOrUnboxedInitializedLength<SrcType>(src) >= srcStart + length);
+ MOZ_ASSERT(GetBoxedOrUnboxedCapacity<DstType>(dst) >= dstStart + length);
+
+ SetBoxedOrUnboxedInitializedLength<DstType>(cx, dst, dstStart + length);
+
+ if (DstType == JSVAL_TYPE_MAGIC) {
+ if (SrcType == JSVAL_TYPE_MAGIC) {
+ const Value* vp = src->as<NativeObject>().getDenseElements() + srcStart;
+ dst->as<NativeObject>().initDenseElements(dstStart, vp, length);
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
+ dst->as<NativeObject>().initDenseElement(dstStart + i, v);
+ }
+ }
+ } else if (DstType == SrcType) {
+ uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
+ uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
+ size_t elementSize = UnboxedTypeSize(DstType);
+
+ memcpy(dstData + dstStart * elementSize,
+ srcData + srcStart * elementSize,
+ length * elementSize);
+
+ // Add a store buffer entry if we might have copied a nursery pointer to dst.
+ if (UnboxedTypeNeedsPostBarrier(DstType) && !IsInsideNursery(dst))
+ dst->runtimeFromMainThread()->gc.storeBuffer.putWholeCell(dst);
+ } else if (DstType == JSVAL_TYPE_DOUBLE && SrcType == JSVAL_TYPE_INT32) {
+ uint8_t* dstData = dst->as<UnboxedArrayObject>().elements();
+ uint8_t* srcData = src->as<UnboxedArrayObject>().elements();
+
+ for (size_t i = 0; i < length; i++) {
+ int32_t v = *reinterpret_cast<int32_t*>(srcData + (srcStart + i) * sizeof(int32_t));
+ *reinterpret_cast<double*>(dstData + (dstStart + i) * sizeof(double)) = v;
+ }
+ } else {
+ for (size_t i = 0; i < length; i++) {
+ Value v = GetBoxedOrUnboxedDenseElement<SrcType>(src, srcStart + i);
+ dst->as<UnboxedArrayObject>().initElementNoTypeChangeSpecific<DstType>(dstStart + i, v);
+ }
+ }
+
+ return DenseElementResult::Success;
+}
+
+/////////////////////////////////////////////////////////////////////
+// Dispatch to specialized methods based on the type of an object.
+/////////////////////////////////////////////////////////////////////
+
+// Goop to fix MSVC. See DispatchTraceKindTyped in TraceKind.h.
+// The clang-cl front end defines _MSC_VER, but still requires the explicit
+// template declaration, so we must test for __clang__ here as well.
+#if defined(_MSC_VER) && !defined(__clang__)
+# define DEPENDENT_TEMPLATE_HINT
+#else
+# define DEPENDENT_TEMPLATE_HINT template
+#endif
+
+// Function to dispatch a method specialized to whatever boxed or unboxed dense
+// elements which an input object has.
+template <typename F>
+DenseElementResult
+CallBoxedOrUnboxedSpecialization(F f, JSObject* obj)
+{
+ if (!HasAnyBoxedOrUnboxedDenseElements(obj))
+ return DenseElementResult::Incomplete;
+ switch (GetBoxedOrUnboxedType(obj)) {
+ case JSVAL_TYPE_MAGIC:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_MAGIC>();
+ case JSVAL_TYPE_BOOLEAN:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_BOOLEAN>();
+ case JSVAL_TYPE_INT32:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_INT32>();
+ case JSVAL_TYPE_DOUBLE:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_DOUBLE>();
+ case JSVAL_TYPE_STRING:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_STRING>();
+ case JSVAL_TYPE_OBJECT:
+ return f. DEPENDENT_TEMPLATE_HINT operator()<JSVAL_TYPE_OBJECT>();
+ default:
+ MOZ_CRASH();
+ }
+}
+
+// As above, except the specialization can reflect the unboxed type of two objects.
+template <typename F>
+DenseElementResult
+CallBoxedOrUnboxedSpecialization(F f, JSObject* obj1, JSObject* obj2)
+{
+ if (!HasAnyBoxedOrUnboxedDenseElements(obj1) || !HasAnyBoxedOrUnboxedDenseElements(obj2))
+ return DenseElementResult::Incomplete;
+
+#define SPECIALIZE_OBJ2(TYPE) \
+ switch (GetBoxedOrUnboxedType(obj2)) { \
+ case JSVAL_TYPE_MAGIC: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_MAGIC>(); \
+ case JSVAL_TYPE_BOOLEAN: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_BOOLEAN>(); \
+ case JSVAL_TYPE_INT32: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_INT32>(); \
+ case JSVAL_TYPE_DOUBLE: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_DOUBLE>(); \
+ case JSVAL_TYPE_STRING: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_STRING>(); \
+ case JSVAL_TYPE_OBJECT: \
+ return f. DEPENDENT_TEMPLATE_HINT operator()<TYPE, JSVAL_TYPE_OBJECT>(); \
+ default: \
+ MOZ_CRASH(); \
+ }
+
+ switch (GetBoxedOrUnboxedType(obj1)) {
+ case JSVAL_TYPE_MAGIC:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_MAGIC)
+ case JSVAL_TYPE_BOOLEAN:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_BOOLEAN)
+ case JSVAL_TYPE_INT32:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_INT32)
+ case JSVAL_TYPE_DOUBLE:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_DOUBLE)
+ case JSVAL_TYPE_STRING:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_STRING)
+ case JSVAL_TYPE_OBJECT:
+ SPECIALIZE_OBJ2(JSVAL_TYPE_OBJECT)
+ default:
+ MOZ_CRASH();
+ }
+
+#undef SPECIALIZE_OBJ2
+}
+
+#undef DEPENDENT_TEMPLATE_HINT
+
+#define DefineBoxedOrUnboxedFunctor1(Signature, A) \
+struct Signature ## Functor { \
+ A a; \
+ explicit Signature ## Functor(A a) \
+ : a(a) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor3(Signature, A, B, C) \
+struct Signature ## Functor { \
+ A a; B b; C c; \
+ Signature ## Functor(A a, B b, C c) \
+ : a(a), b(b), c(c) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor4(Signature, A, B, C, D) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; \
+ Signature ## Functor(A a, B b, C c, D d) \
+ : a(a), b(b), c(c), d(d) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctorPair4(Signature, A, B, C, D) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; \
+ Signature ## Functor(A a, B b, C c, D d) \
+ : a(a), b(b), c(c), d(d) \
+ {} \
+ template <JSValueType TypeOne, JSValueType TypeTwo> \
+ DenseElementResult operator()() { \
+ return Signature<TypeOne, TypeTwo>(a, b, c, d); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor5(Signature, A, B, C, D, E) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; \
+ Signature ## Functor(A a, B b, C c, D d, E e) \
+ : a(a), b(b), c(c), d(d), e(e) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d, e); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctor6(Signature, A, B, C, D, E, F) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; F f; \
+ Signature ## Functor(A a, B b, C c, D d, E e, F f) \
+ : a(a), b(b), c(c), d(d), e(e), f(f) \
+ {} \
+ template <JSValueType Type> \
+ DenseElementResult operator()() { \
+ return Signature<Type>(a, b, c, d, e, f); \
+ } \
+}
+
+#define DefineBoxedOrUnboxedFunctorPair6(Signature, A, B, C, D, E, F) \
+struct Signature ## Functor { \
+ A a; B b; C c; D d; E e; F f; \
+ Signature ## Functor(A a, B b, C c, D d, E e, F f) \
+ : a(a), b(b), c(c), d(d), e(e), f(f) \
+ {} \
+ template <JSValueType TypeOne, JSValueType TypeTwo> \
+ DenseElementResult operator()() { \
+ return Signature<TypeOne, TypeTwo>(a, b, c, d, e, f); \
+ } \
+}
+
+DenseElementResult
+SetOrExtendAnyBoxedOrUnboxedDenseElements(ExclusiveContext* cx, JSObject* obj,
+ uint32_t start, const Value* vp, uint32_t count,
+ ShouldUpdateTypes updateTypes = ShouldUpdateTypes::Update);
+
+DenseElementResult
+MoveAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length);
+
+DenseElementResult
+CopyAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* dst, JSObject* src,
+ uint32_t dstStart, uint32_t srcStart, uint32_t length);
+
+void
+SetAnyBoxedOrUnboxedInitializedLength(JSContext* cx, JSObject* obj, size_t initlen);
+
+DenseElementResult
+EnsureAnyBoxedOrUnboxedDenseElements(JSContext* cx, JSObject* obj, size_t count);
+
} // namespace js
#endif // vm_UnboxedObject_inl_h
diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp
index 2e017ca3b..059293a2d 100644
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -417,6 +417,8 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
RootedObjectGroup replacementGroup(cx);
+ const Class* clasp = layout.isArray() ? &ArrayObject::class_ : &PlainObject::class_;
+
// Immediately clear any new script on the group. This is done by replacing
// the existing new script with one for a replacement default new group.
// This is done so that the size of the replacment group's objects is the
@@ -424,6 +426,8 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
// slot accesses later on for sites that see converted objects from this
// group and objects that were allocated using the replacement new group.
if (layout.newScript()) {
+ MOZ_ASSERT(!layout.isArray());
+
replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
if (!replacementGroup)
return false;
@@ -449,14 +453,15 @@ UnboxedLayout::makeNativeGroup(JSContext* cx, ObjectGroup* group)
RootedScript script(cx, layout.allocationScript());
jsbytecode* pc = layout.allocationPc();
- replacementGroup = ObjectGroupCompartment::makeGroup(cx, &PlainObject::class_, proto);
+ replacementGroup = ObjectGroupCompartment::makeGroup(cx, clasp, proto);
if (!replacementGroup)
return false;
PlainObject* templateObject = &script->getObject(pc)->as<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);
+}
diff --git a/js/src/vm/UnboxedObject.h b/js/src/vm/UnboxedObject.h
index ba66434bc..779dd14c7 100644
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -96,11 +96,17 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
// from an array of values.
GCPtrJitCode constructorCode_;
+ // The following members are only used for unboxed arrays.
+
+ // The type of array elements.
+ JSValueType elementType_;
+
public:
UnboxedLayout()
: nativeGroup_(nullptr), nativeShape_(nullptr),
allocationScript_(nullptr), allocationPc_(nullptr), replacementGroup_(nullptr),
- size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr)
+ size_(0), newScript_(nullptr), traceList_(nullptr), constructorCode_(nullptr),
+ elementType_(JSVAL_TYPE_MAGIC)
{}
bool initProperties(const PropertyVector& properties, size_t size) {
@@ -108,6 +114,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
return properties_.appendAll(properties);
}
+ void initArray(JSValueType elementType) {
+ elementType_ = elementType;
+ }
+
~UnboxedLayout() {
if (newScript_)
newScript_->clear();
@@ -120,6 +130,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
constructorCode_.init(nullptr);
}
+ bool isArray() const {
+ return elementType_ != JSVAL_TYPE_MAGIC;
+ }
+
void detachFromCompartment();
const PropertyVector& properties() const {
@@ -187,6 +201,10 @@ class UnboxedLayout : public mozilla::LinkedListElement<UnboxedLayout>
constructorCode_ = code;
}
+ JSValueType elementType() const {
+ return elementType_;
+ }
+
inline gc::AllocKind getAllocKind() const;
void trace(JSTracer* trc);
@@ -306,6 +324,193 @@ UnboxedLayout::getAllocKind() const
return gc::GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + size());
}
+// Class for an array object using an unboxed representation.
+class UnboxedArrayObject : public JSObject
+{
+ // Elements pointer for the object.
+ uint8_t* elements_;
+
+ // The nominal array length. This always fits in an int32_t.
+ uint32_t length_;
+
+ // Value indicating the allocated capacity and initialized length of the
+ // array. The top CapacityBits bits are an index into CapacityArray, which
+ // indicates the elements capacity. The low InitializedLengthBits store the
+ // initialized length of the array.
+ uint32_t capacityIndexAndInitializedLength_;
+
+ // If the elements are inline, they will point here.
+ uint8_t inlineElements_[1];
+
+ public:
+ static const uint32_t CapacityBits = 6;
+ static const uint32_t CapacityShift = 26;
+
+ static const uint32_t CapacityMask = uint32_t(-1) << CapacityShift;
+ static const uint32_t InitializedLengthMask = (1 << CapacityShift) - 1;
+
+ static const uint32_t MaximumCapacity = InitializedLengthMask;
+ static const uint32_t MinimumDynamicCapacity = 8;
+
+ static const uint32_t CapacityArray[];
+
+ // Capacity index which indicates the array's length is also its capacity.
+ static const uint32_t CapacityMatchesLengthIndex = 0;
+
+ private:
+ static inline uint32_t computeCapacity(uint32_t index, uint32_t length) {
+ if (index == CapacityMatchesLengthIndex)
+ return length;
+ return CapacityArray[index];
+ }
+
+ static uint32_t chooseCapacityIndex(uint32_t capacity, uint32_t length);
+ static uint32_t exactCapacityIndex(uint32_t capacity);
+
+ public:
+ static const Class class_;
+
+ static bool obj_lookupProperty(JSContext* cx, HandleObject obj,
+ HandleId id, MutableHandleObject objp,
+ MutableHandleShape propp);
+
+ static bool obj_defineProperty(JSContext* cx, HandleObject obj, HandleId id,
+ Handle<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();
+ }
+
+ JSValueType elementType() const {
+ return layoutDontCheckGeneration().elementType();
+ }
+
+ uint32_t elementSize() const {
+ return UnboxedTypeSize(elementType());
+ }
+
+ static bool convertToNative(JSContext* cx, JSObject* obj);
+ static UnboxedArrayObject* create(ExclusiveContext* cx, HandleObjectGroup group,
+ uint32_t length, NewObjectKind newKind,
+ uint32_t maxLength = MaximumCapacity);
+
+ static bool convertToNativeWithGroup(ExclusiveContext* cx, JSObject* obj,
+ ObjectGroup* group, Shape* shape);
+ bool convertInt32ToDouble(ExclusiveContext* cx, ObjectGroup* group);
+
+ void fillAfterConvert(ExclusiveContext* cx,
+ Handle<GCVector<Value>> values, size_t* valueCursor);
+
+ static void trace(JSTracer* trc, JSObject* object);
+ static void objectMoved(JSObject* obj, const JSObject* old);
+ static void finalize(FreeOp* fop, JSObject* obj);
+
+ static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
+ gc::AllocKind allocKind);
+
+ uint8_t* elements() {
+ return elements_;
+ }
+
+ bool hasInlineElements() const {
+ return elements_ == &inlineElements_[0];
+ }
+
+ uint32_t length() const {
+ return length_;
+ }
+
+ uint32_t initializedLength() const {
+ return capacityIndexAndInitializedLength_ & InitializedLengthMask;
+ }
+
+ uint32_t capacityIndex() const {
+ return (capacityIndexAndInitializedLength_ & CapacityMask) >> CapacityShift;
+ }
+
+ uint32_t capacity() const {
+ return computeCapacity(capacityIndex(), length());
+ }
+
+ bool containsProperty(ExclusiveContext* cx, jsid id);
+
+ bool setElement(ExclusiveContext* cx, size_t index, const Value& v);
+ bool initElement(ExclusiveContext* cx, size_t index, const Value& v);
+ void initElementNoTypeChange(size_t index, const Value& v);
+ Value getElement(size_t index);
+
+ template <JSValueType Type> inline bool setElementSpecific(ExclusiveContext* cx, size_t index,
+ const Value& v);
+ template <JSValueType Type> inline void setElementNoTypeChangeSpecific(size_t index, const Value& v);
+ template <JSValueType Type> inline bool initElementSpecific(ExclusiveContext* cx, size_t index,
+ const Value& v);
+ template <JSValueType Type> inline void initElementNoTypeChangeSpecific(size_t index, const Value& v);
+ template <JSValueType Type> inline Value getElementSpecific(size_t index);
+ template <JSValueType Type> inline void triggerPreBarrier(size_t index);
+
+ bool growElements(ExclusiveContext* cx, size_t cap);
+ void shrinkElements(ExclusiveContext* cx, size_t cap);
+
+ static uint32_t offsetOfElements() {
+ return offsetof(UnboxedArrayObject, elements_);
+ }
+ static uint32_t offsetOfLength() {
+ return offsetof(UnboxedArrayObject, length_);
+ }
+ static uint32_t offsetOfCapacityIndexAndInitializedLength() {
+ return offsetof(UnboxedArrayObject, capacityIndexAndInitializedLength_);
+ }
+ static uint32_t offsetOfInlineElements() {
+ return offsetof(UnboxedArrayObject, inlineElements_);
+ }
+
+ void setLengthInt32(uint32_t length) {
+ MOZ_ASSERT(length <= INT32_MAX);
+ length_ = length;
+ }
+
+ inline void setLength(ExclusiveContext* cx, uint32_t len);
+ inline void setInitializedLength(uint32_t initlen);
+
+ inline void setInitializedLengthNoBarrier(uint32_t initlen) {
+ MOZ_ASSERT(initlen <= InitializedLengthMask);
+ capacityIndexAndInitializedLength_ =
+ (capacityIndexAndInitializedLength_ & CapacityMask) | initlen;
+ }
+
+ private:
+ void setInlineElements() {
+ elements_ = &inlineElements_[0];
+ }
+
+ void setCapacityIndex(uint32_t index) {
+ MOZ_ASSERT(index <= (CapacityMask >> CapacityShift));
+ capacityIndexAndInitializedLength_ =
+ (index << CapacityShift) | initializedLength();
+ }
+};
+
} // namespace js
namespace JS {