From 68d3bc54fbc9b99310197c51dfd84b6f72b7fb01 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Mon, 3 Feb 2020 04:52:44 +0100 Subject: Issue #1382 - Remove invalid assertion. There is flexibility in exactly the value the initialized length must hold, i.e. if an array is completely empty, it is valid for the initialized length to be any value between zero and the length of the array, as long as the in-memory values below the initialized length have been initialized with a hole value. In the case of 0, the array is packed and the move operation would be a nop, so simply convert the assert to a condition to save some cycles. --- js/src/jsarray.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'js/src/jsarray.cpp') diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index e618c319f..73243d918 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2105,14 +2105,15 @@ js::ArrayShiftMoveElements(NativeObject* obj) MOZ_ASSERT_IF(obj->is(), obj->as().lengthIsWritable()); size_t initlen = obj->getDenseInitializedLength(); - MOZ_ASSERT(initlen > 0); - - /* - * At this point the length and initialized length have already been - * decremented and the result fetched, so just shift the array elements - * themselves. - */ - obj->moveDenseElementsNoPreBarrier(0, 1, initlen); + + if (initlen > 0) { + /* + * At this point the length and initialized length have already been + * decremented and the result fetched, so just shift the array elements + * themselves. + */ + obj->moveDenseElementsNoPreBarrier(0, 1, initlen); + } } static inline void -- cgit v1.2.3 From c22a493144e39d76bfa42c46f9d6d17a5143ac35 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 22 Feb 2020 21:09:32 +0100 Subject: Revert #1142 - Remove unboxed objects - accounting for removal of watch()/unwatch() --- js/src/jsarray.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'js/src/jsarray.cpp') diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 73243d918..87d1a5acc 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -45,6 +45,7 @@ #include "vm/Caches-inl.h" #include "vm/Interpreter-inl.h" #include "vm/NativeObject-inl.h" +#include "vm/UnboxedObject-inl.h" using namespace js; using namespace js::gc; -- cgit v1.2.3 From dc4695406f02e26009f5f54a858344911f1aa404 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 23 Feb 2020 11:33:50 +0100 Subject: Revert "Issue #1382 - Remove invalid assertion." This reverts commit 9c6a8450b3e96442035b84025b0dd13be3a9e5f8. --- js/src/jsarray.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'js/src/jsarray.cpp') diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 87d1a5acc..4ee967d4c 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2106,15 +2106,14 @@ js::ArrayShiftMoveElements(NativeObject* obj) MOZ_ASSERT_IF(obj->is(), obj->as().lengthIsWritable()); size_t initlen = obj->getDenseInitializedLength(); - - if (initlen > 0) { - /* - * At this point the length and initialized length have already been - * decremented and the result fetched, so just shift the array elements - * themselves. - */ - obj->moveDenseElementsNoPreBarrier(0, 1, initlen); - } + MOZ_ASSERT(initlen > 0); + + /* + * At this point the length and initialized length have already been + * decremented and the result fetched, so just shift the array elements + * themselves. + */ + obj->moveDenseElementsNoPreBarrier(0, 1, initlen); } static inline void -- cgit v1.2.3 From af69cb07db0d810a1a1a507b890e6beb23dc421c Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sun, 23 Feb 2020 14:41:40 +0100 Subject: Revert #1137 - Remove unboxed arrays - accounting for removal of watch()/unwatch() - updated for intermediate code changes. --- js/src/jsarray.cpp | 520 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 310 insertions(+), 210 deletions(-) (limited to 'js/src/jsarray.cpp') diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 4ee967d4c..1724a0a66 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -64,7 +64,7 @@ using JS::ToUint32; bool JS::IsArray(JSContext* cx, HandleObject obj, IsArrayAnswer* answer) { - if (obj->is()) { + if (obj->is() || obj->is()) { *answer = IsArrayAnswer::Array; return true; } @@ -100,6 +100,11 @@ js::GetLengthProperty(JSContext* cx, HandleObject obj, uint32_t* lengthp) return true; } + if (obj->is()) { + *lengthp = obj->as().length(); + return true; + } + if (obj->is()) { ArgumentsObject& argsobj = obj->as(); if (!argsobj.hasOverriddenLength()) { @@ -248,20 +253,18 @@ static bool GetElement(JSContext* cx, HandleObject obj, HandleObject receiver, uint32_t index, bool* hole, MutableHandleValue vp) { - if (obj->isNative()) { - NativeObject* nobj = &obj->as(); - if (index < nobj->getDenseInitializedLength()) { - vp.set(nobj->getDenseElement(size_t(index))); - if (!vp.isMagic(JS_ELEMENTS_HOLE)) { - *hole = false; - return true; - } + AssertGreaterThanZero(index); + if (index < GetAnyBoxedOrUnboxedInitializedLength(obj)) { + vp.set(GetAnyBoxedOrUnboxedDenseElement(obj, uint32_t(index))); + if (!vp.isMagic(JS_ELEMENTS_HOLE)) { + *hole = false; + return true; } - if (nobj->is() && index <= UINT32_MAX) { - if (nobj->as().maybeGetElement(uint32_t(index), vp)) { - *hole = false; - return true; - } + } + if (obj->is()) { + if (obj->as().maybeGetElement(uint32_t(index), vp)) { + *hole = false; + return true; } } @@ -280,8 +283,8 @@ ElementAdder::append(JSContext* cx, HandleValue v) { MOZ_ASSERT(index_ < length_); if (resObj_) { - NativeObject* resObj = &resObj_->as(); - DenseElementResult result = resObj->setOrExtendDenseElements(cx, index_, v.address(), 1); + DenseElementResult result = + SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, resObj_, index_, v.address(), 1); if (result == DenseElementResult::Failure) return false; if (result == DenseElementResult::Incomplete) { @@ -333,31 +336,37 @@ js::GetElementsWithAdder(JSContext* cx, HandleObject obj, HandleObject receiver, return true; } -static bool -GetDenseElements(NativeObject* aobj, uint32_t length, Value* vp) +template +DenseElementResult +GetBoxedOrUnboxedDenseElements(JSObject* aobj, uint32_t length, Value* vp) { MOZ_ASSERT(!ObjectMayHaveExtraIndexedProperties(aobj)); - if (length > aobj->getDenseInitializedLength()) - return false; + if (length > GetBoxedOrUnboxedInitializedLength(aobj)) + return DenseElementResult::Incomplete; for (size_t i = 0; i < length; i++) { - vp[i] = aobj->getDenseElement(i); + vp[i] = GetBoxedOrUnboxedDenseElement(aobj, i); // No other indexed properties so hole => undefined. if (vp[i].isMagic(JS_ELEMENTS_HOLE)) vp[i] = UndefinedValue(); } - return true; + return DenseElementResult::Success; } +DefineBoxedOrUnboxedFunctor3(GetBoxedOrUnboxedDenseElements, + JSObject*, uint32_t, Value*); + bool js::GetElements(JSContext* cx, HandleObject aobj, uint32_t length, Value* vp) { if (!ObjectMayHaveExtraIndexedProperties(aobj)) { - if (GetDenseElements(&aobj->as(), length, vp)) - return true; + GetBoxedOrUnboxedDenseElementsFunctor functor(aobj, length, vp); + DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, aobj); + if (result != DenseElementResult::Incomplete) + return result == DenseElementResult::Success; } if (aobj->is()) { @@ -389,9 +398,9 @@ SetArrayElement(JSContext* cx, HandleObject obj, double index, HandleValue v) { MOZ_ASSERT(index >= 0); - if (obj->is() && !obj->isIndexed() && index <= UINT32_MAX) { - NativeObject* nobj = &obj->as(); - DenseElementResult result = nobj->setOrExtendDenseElements(cx, uint32_t(index), v.address(), 1); + if ((obj->is() || obj->is()) && !obj->isIndexed() && index <= UINT32_MAX) { + DenseElementResult result = + SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, uint32_t(index), v.address(), 1); if (result != DenseElementResult::Incomplete) return result == DenseElementResult::Success; } @@ -511,6 +520,24 @@ struct ReverseIndexComparator } }; +bool +js::CanonicalizeArrayLengthValue(JSContext* cx, HandleValue v, uint32_t* newLen) +{ + double d; + + if (!ToUint32(cx, v, newLen)) + return false; + + if (!ToNumber(cx, v, &d)) + return false; + + if (d == *newLen) + return true; + + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); + return false; +} + /* ES6 draft rev 34 (2015 Feb 20) 9.4.2.4 ArraySetLength */ bool js::ArraySetLength(JSContext* cx, Handle arr, HandleId id, @@ -532,22 +559,12 @@ js::ArraySetLength(JSContext* cx, Handle arr, HandleId id, } else { // Step 2 is irrelevant in our implementation. - // Step 3. - if (!ToUint32(cx, value, &newLen)) - return false; - - // Step 4. - double d; - if (!ToNumber(cx, value, &d)) + // Steps 3-7. + MOZ_ASSERT_IF(attrs & JSPROP_IGNORE_VALUE, value.isUndefined()); + if (!CanonicalizeArrayLengthValue(cx, value, &newLen)) return false; - // Step 5. - if (d != newLen) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); - return false; - } - - // Steps 6-8 are irrelevant in our implementation. + // Step 8 is irrelevant in our implementation. } // Steps 9-11. @@ -806,7 +823,7 @@ array_addProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) static inline bool ObjectMayHaveExtraIndexedOwnProperties(JSObject* obj) { - return !obj->isNative() || + return (!obj->isNative() && !obj->is()) || obj->isIndexed() || obj->is() || ClassMayResolveId(*obj->runtimeFromAnyThread()->commonNames, @@ -837,7 +854,7 @@ js::ObjectMayHaveExtraIndexedProperties(JSObject* obj) if (ObjectMayHaveExtraIndexedOwnProperties(obj)) return true; - if (obj->as().getDenseInitializedLength() != 0) + if (GetAnyBoxedOrUnboxedInitializedLength(obj) != 0) return true; } while (true); } @@ -1047,32 +1064,32 @@ struct StringSeparatorOp } }; -template -static bool -ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleNativeObject obj, uint32_t length, +template +static DenseElementResult +ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length, StringBuffer& sb, uint32_t* numProcessed) { // This loop handles all elements up to initializedLength. If // length > initLength we rely on the second loop to add the // other elements. MOZ_ASSERT(*numProcessed == 0); - uint32_t initLength = Min(obj->getDenseInitializedLength(), + uint32_t initLength = Min(GetBoxedOrUnboxedInitializedLength(obj), length); while (*numProcessed < initLength) { if (!CheckForInterrupt(cx)) - return false; + return DenseElementResult::Failure; Value elem = obj->as().getDenseElement(*numProcessed); if (elem.isString()) { if (!sb.append(elem.toString())) - return false; + return DenseElementResult::Failure; } else if (elem.isNumber()) { if (!NumberValueToStringBuffer(cx, elem, sb)) - return false; + return DenseElementResult::Failure; } else if (elem.isBoolean()) { if (!BooleanToStringBuffer(elem.toBoolean(), sb)) - return false; + return DenseElementResult::Failure; } else if (elem.isObject() || elem.isSymbol()) { /* * Object stringifying could modify the initialized length or make @@ -1088,12 +1105,32 @@ ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleNativeObject obj, u } if (++(*numProcessed) != length && !sepOp(cx, sb)) - return false; + return DenseElementResult::Failure; } - return true; + return DenseElementResult::Incomplete; } +template +struct ArrayJoinDenseKernelFunctor { + JSContext* cx; + SeparatorOp sepOp; + HandleObject obj; + uint32_t length; + StringBuffer& sb; + uint32_t* numProcessed; + + ArrayJoinDenseKernelFunctor(JSContext* cx, SeparatorOp sepOp, HandleObject obj, + uint32_t length, StringBuffer& sb, uint32_t* numProcessed) + : cx(cx), sepOp(sepOp), obj(obj), length(length), sb(sb), numProcessed(numProcessed) + {} + + template + DenseElementResult operator()() { + return ArrayJoinDenseKernel(cx, sepOp, obj, length, sb, numProcessed); + } +}; + template static bool ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t length, @@ -1102,10 +1139,10 @@ ArrayJoinKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_t len uint32_t i = 0; if (!ObjectMayHaveExtraIndexedProperties(obj)) { - if (!ArrayJoinDenseKernel(cx, sepOp, obj.as(), length, sb, &i)) - { + ArrayJoinDenseKernelFunctor functor(cx, sepOp, obj, length, sb, &i); + DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj); + if (result == DenseElementResult::Failure) return false; - } } if (i != length) { @@ -1176,14 +1213,11 @@ js::array_join(JSContext* cx, unsigned argc, Value* vp) // An optimized version of a special case of steps 7-11: when length==1 and // the 0th element is a string, ToString() of that element is a no-op and // so it can be immediately returned as the result. - if (length == 1 && obj->isNative()) { - NativeObject* nobj = &obj->as(); - if (nobj->getDenseInitializedLength() == 1) { - Value elem0 = nobj->getDenseElement(0); - if (elem0.isString()) { - args.rval().set(elem0); - return true; - } + if (length == 1 && GetAnyBoxedOrUnboxedInitializedLength(obj) == 1) { + Value elem0 = GetAnyBoxedOrUnboxedDenseElement(obj, 0); + if (elem0.isString()) { + args.rval().set(elem0); + return true; } } @@ -1226,7 +1260,7 @@ js::array_join(JSContext* cx, unsigned argc, Value* vp) } // Step 11 - JSString* str = sb.finishString(); + JSString *str = sb.finishString(); if (!str) return false; @@ -1255,6 +1289,10 @@ array_toLocaleString(JSContext* cx, unsigned argc, Value* vp) args.rval().setString(cx->names().empty); return true; } + if (obj->is() && obj->as().length() == 0) { + args.rval().setString(cx->names().empty); + return true; + } AutoCycleDetector detector(cx, obj); if (!detector.init()) @@ -1291,9 +1329,8 @@ InitArrayElements(JSContext* cx, HandleObject obj, uint32_t start, return false; if (!ObjectMayHaveExtraIndexedProperties(obj)) { - NativeObject* nobj = &obj->as(); - DenseElementResult result = nobj->setOrExtendDenseElements(cx, uint32_t(start), vector, - count, updateTypes); + DenseElementResult result = + SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, start, vector, count, updateTypes); if (result != DenseElementResult::Incomplete) return result == DenseElementResult::Success; } @@ -1327,45 +1364,54 @@ InitArrayElements(JSContext* cx, HandleObject obj, uint32_t start, return true; } -static DenseElementResult -ArrayReverseDenseKernel(JSContext* cx, HandleNativeObject obj, uint32_t length) +template +DenseElementResult +ArrayReverseDenseKernel(JSContext* cx, HandleObject obj, uint32_t length) { /* An empty array or an array with no elements is already reversed. */ - if (length == 0 || obj->getDenseInitializedLength() == 0) + if (length == 0 || GetBoxedOrUnboxedInitializedLength(obj) == 0) return DenseElementResult::Success; - if (obj->denseElementsAreFrozen()) - return DenseElementResult::Incomplete; + if (Type == JSVAL_TYPE_MAGIC) { + if (obj->as().denseElementsAreFrozen()) + return DenseElementResult::Incomplete; - /* - * It's actually surprisingly complicated to reverse an array due to the - * orthogonality of array length and array capacity while handling - * leading and trailing holes correctly. Reversing seems less likely to - * be a common operation than other array mass-mutation methods, so for - * now just take a probably-small memory hit (in the absence of too many - * holes in the array at its start) and ensure that the capacity is - * sufficient to hold all the elements in the array if it were full. - */ - DenseElementResult result = obj->ensureDenseElements(cx, length, 0); - if (result != DenseElementResult::Success) - return result; + /* + * It's actually surprisingly complicated to reverse an array due to the + * orthogonality of array length and array capacity while handling + * leading and trailing holes correctly. Reversing seems less likely to + * be a common operation than other array mass-mutation methods, so for + * now just take a probably-small memory hit (in the absence of too many + * holes in the array at its start) and ensure that the capacity is + * sufficient to hold all the elements in the array if it were full. + */ + DenseElementResult result = obj->as().ensureDenseElements(cx, length, 0); + if (result != DenseElementResult::Success) + return result; - /* Fill out the array's initialized length to its proper length. */ - obj->ensureDenseInitializedLength(cx, length, 0); + /* Fill out the array's initialized length to its proper length. */ + obj->as().ensureDenseInitializedLength(cx, length, 0); + } else { + // Unboxed arrays can only be reversed here if their initialized length + // matches their actual length. Otherwise the reversal will place holes + // at the beginning of the array, which we don't support. + if (length != obj->as().initializedLength()) + return DenseElementResult::Incomplete; + } RootedValue origlo(cx), orighi(cx); uint32_t lo = 0, hi = length - 1; for (; lo < hi; lo++, hi--) { - origlo = obj->getDenseElement(lo); - orighi = obj->getDenseElement(hi); - obj->setDenseElement(lo, orighi); + origlo = GetBoxedOrUnboxedDenseElement(obj, lo); + orighi = GetBoxedOrUnboxedDenseElement(obj, hi); + SetBoxedOrUnboxedDenseElementNoTypeChange(obj, lo, orighi); if (orighi.isMagic(JS_ELEMENTS_HOLE) && !SuppressDeletedProperty(cx, obj, INT_TO_JSID(lo))) { return DenseElementResult::Failure; } - obj->setDenseElement(hi, origlo); + SetBoxedOrUnboxedDenseElementNoTypeChange(obj, hi, origlo); if (origlo.isMagic(JS_ELEMENTS_HOLE) && !SuppressDeletedProperty(cx, obj, INT_TO_JSID(hi))) { @@ -1376,6 +1422,9 @@ ArrayReverseDenseKernel(JSContext* cx, HandleNativeObject obj, uint32_t length) return DenseElementResult::Success; } +DefineBoxedOrUnboxedFunctor3(ArrayReverseDenseKernel, + JSContext*, HandleObject, uint32_t); + bool js::array_reverse(JSContext* cx, unsigned argc, Value* vp) { @@ -1390,8 +1439,8 @@ js::array_reverse(JSContext* cx, unsigned argc, Value* vp) return false; if (!ObjectMayHaveExtraIndexedProperties(obj)) { - DenseElementResult result = - ArrayReverseDenseKernel(cx, obj.as(), uint32_t(len)); + ArrayReverseDenseKernelFunctor functor(cx, obj, len); + DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj); if (result != DenseElementResult::Incomplete) { /* * Per ECMA-262, don't update the length of the array, even if the new @@ -2034,8 +2083,8 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp) if (!ObjectMayHaveExtraIndexedProperties(obj)) { DenseElementResult result = - obj->as().setOrExtendDenseElements(cx, uint32_t(length), - args.array(), args.length()); + SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, length, + args.array(), args.length()); if (result != DenseElementResult::Incomplete) { if (result == DenseElementResult::Failure) return false; @@ -2043,8 +2092,14 @@ js::array_push(JSContext* cx, unsigned argc, Value* vp) uint32_t newlength = length + args.length(); args.rval().setNumber(newlength); - // Handle updates to the length of non-arrays here. - if (!obj->is()) + // SetOrExtendAnyBoxedOrUnboxedDenseElements takes care of updating the + // length for boxed and unboxed arrays. Handle updates to the length of + // non-arrays here. + bool isArray; + if (!IsArray(cx, obj, &isArray)) + return false; + + if (!isArray) return SetLengthProperty(cx, obj, newlength); return true; @@ -2100,46 +2155,42 @@ js::array_pop(JSContext* cx, unsigned argc, Value* vp) return SetLengthProperty(cx, obj, index); } -void -js::ArrayShiftMoveElements(NativeObject* obj) +template +static inline DenseElementResult +ShiftMoveBoxedOrUnboxedDenseElements(JSObject* obj) { - MOZ_ASSERT_IF(obj->is(), obj->as().lengthIsWritable()); - - size_t initlen = obj->getDenseInitializedLength(); - MOZ_ASSERT(initlen > 0); + MOZ_ASSERT(HasBoxedOrUnboxedDenseElements(obj)); /* * At this point the length and initialized length have already been * decremented and the result fetched, so just shift the array elements * themselves. */ - obj->moveDenseElementsNoPreBarrier(0, 1, initlen); -} + size_t initlen = GetBoxedOrUnboxedInitializedLength(obj); + if (Type == JSVAL_TYPE_MAGIC) { + obj->as().moveDenseElementsNoPreBarrier(0, 1, initlen); + } else { + uint8_t* data = obj->as().elements(); + size_t elementSize = UnboxedTypeSize(Type); + memmove(data, data + elementSize, initlen * elementSize); + } -static inline void -SetInitializedLength(JSContext* cx, NativeObject* obj, size_t initlen) -{ - size_t oldInitlen = obj->getDenseInitializedLength(); - obj->setDenseInitializedLength(initlen); - if (initlen < oldInitlen) - obj->shrinkElements(cx, initlen); + return DenseElementResult::Success; } -static DenseElementResult -MoveDenseElements(JSContext* cx, NativeObject* obj, uint32_t dstStart, uint32_t srcStart, - uint32_t length) -{ - if (obj->denseElementsAreFrozen()) - return DenseElementResult::Incomplete; +DefineBoxedOrUnboxedFunctor1(ShiftMoveBoxedOrUnboxedDenseElements, JSObject*); - if (!obj->maybeCopyElementsForWrite(cx)) - return DenseElementResult::Failure; - obj->moveDenseElements(dstStart, srcStart, length); +void +js::ArrayShiftMoveElements(JSObject* obj) +{ + MOZ_ASSERT_IF(obj->is(), obj->as().lengthIsWritable()); - return DenseElementResult::Success; + ShiftMoveBoxedOrUnboxedDenseElementsFunctor functor(obj); + JS_ALWAYS_TRUE(CallBoxedOrUnboxedSpecialization(functor, obj) == DenseElementResult::Success); } -static DenseElementResult +template +DenseElementResult ArrayShiftDenseKernel(JSContext* cx, HandleObject obj, MutableHandleValue rval) { if (ObjectMayHaveExtraIndexedProperties(obj)) @@ -2152,22 +2203,25 @@ ArrayShiftDenseKernel(JSContext* cx, HandleObject obj, MutableHandleValue rval) if (MOZ_UNLIKELY(group->hasAllFlags(OBJECT_FLAG_ITERATED))) return DenseElementResult::Incomplete; - size_t initlen = obj->as().getDenseInitializedLength(); + size_t initlen = GetBoxedOrUnboxedInitializedLength(obj); if (initlen == 0) return DenseElementResult::Incomplete; - rval.set(obj->as().getDenseElement(0)); + rval.set(GetBoxedOrUnboxedDenseElement(obj, 0)); if (rval.isMagic(JS_ELEMENTS_HOLE)) rval.setUndefined(); - DenseElementResult result = MoveDenseElements(cx, &obj->as(), 0, 1, initlen - 1); + DenseElementResult result = MoveBoxedOrUnboxedDenseElements(cx, obj, 0, 1, initlen - 1); if (result != DenseElementResult::Success) return result; - SetInitializedLength(cx, obj.as(), initlen - 1); + SetBoxedOrUnboxedInitializedLength(cx, obj, initlen - 1); return DenseElementResult::Success; } +DefineBoxedOrUnboxedFunctor3(ArrayShiftDenseKernel, + JSContext*, HandleObject, MutableHandleValue); + /* ES5 15.4.4.9 */ bool js::array_shift(JSContext* cx, unsigned argc, Value* vp) @@ -2199,7 +2253,8 @@ js::array_shift(JSContext* cx, unsigned argc, Value* vp) uint32_t newlen = len - 1; /* Fast paths. */ - DenseElementResult result = ArrayShiftDenseKernel(cx, obj, args.rval()); + ArrayShiftDenseKernelFunctor functor(cx, obj, args.rval()); + DenseElementResult result = CallBoxedOrUnboxedSpecialization(functor, obj); if (result != DenseElementResult::Incomplete) { if (result == DenseElementResult::Failure) return false; @@ -2253,6 +2308,9 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp) if (args.length() > 0) { /* Slide up the array to make room for all args at the bottom. */ if (length > 0) { + // Only include a fast path for boxed arrays. Unboxed arrays can'nt + // be optimized here because unshifting temporarily places holes at + // the start of the array. bool optimized = false; do { if (!obj->is()) @@ -2312,10 +2370,10 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp) } /* - * Returns true if this is a dense array whose properties ending at |endIndex| - * (exclusive) may be accessed (get, set, delete) directly through its - * contiguous vector of elements without fear of getters, setters, etc. along - * the prototype chain, or of enumerators requiring notification of + * Returns true if this is a dense or unboxed array whose |count| properties + * starting from |startingIndex| may be accessed (get, set, delete) directly + * through its contiguous vector of elements without fear of getters, setters, + * etc. along the prototype chain, or of enumerators requiring notification of * modifications. */ static inline bool @@ -2326,11 +2384,11 @@ CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t co return false; /* There's no optimizing possible if it's not an array. */ - if (!arr->is()) + if (!arr->is() && !arr->is()) return false; /* If it's a frozen array, always pick the slow path */ - if (arr->as().denseElementsAreFrozen()) + if (arr->is() && arr->as().denseElementsAreFrozen()) return false; /* @@ -2362,23 +2420,7 @@ CanOptimizeForDenseStorage(HandleObject arr, uint32_t startingIndex, uint32_t co * is subsumed by the initializedLength comparison.) */ return !ObjectMayHaveExtraIndexedProperties(arr) && - startingIndex + count <= arr->as().getDenseInitializedLength(); -} - -static inline DenseElementResult -CopyDenseElements(JSContext* cx, NativeObject* dst, NativeObject* src, - uint32_t dstStart, uint32_t srcStart, uint32_t length) -{ - MOZ_ASSERT(dst->getDenseInitializedLength() == dstStart); - MOZ_ASSERT(src->getDenseInitializedLength() >= srcStart + length); - MOZ_ASSERT(dst->getDenseCapacity() >= dstStart + length); - - dst->setDenseInitializedLength(dstStart + length); - - const Value* vp = src->getDenseElements() + srcStart; - dst->initDenseElements(dstStart, vp, length); - - return DenseElementResult::Success; + startingIndex + count <= GetAnyBoxedOrUnboxedInitializedLength(arr); } static inline bool @@ -2472,9 +2514,7 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse /* Steps 10-11. */ DebugOnly result = - CopyDenseElements(cx, &arr->as(), - &obj->as(), 0, - actualStart, actualDeleteCount); + CopyAnyBoxedOrUnboxedDenseElements(cx, arr, obj, 0, actualStart, actualDeleteCount); MOZ_ASSERT(result.value == DenseElementResult::Success); /* Step 12 (implicit). */ @@ -2511,13 +2551,14 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse if (CanOptimizeForDenseStorage(obj, 0, len, cx)) { /* Steps 15.a-b. */ DenseElementResult result = - MoveDenseElements(cx, &obj->as(), targetIndex, sourceIndex, len - sourceIndex); + MoveAnyBoxedOrUnboxedDenseElements(cx, obj, targetIndex, sourceIndex, + len - sourceIndex); MOZ_ASSERT(result != DenseElementResult::Incomplete); if (result == DenseElementResult::Failure) return false; /* Steps 15.c-d. */ - SetInitializedLength(cx, obj.as(), finalLength); + SetAnyBoxedOrUnboxedInitializedLength(cx, obj, finalLength); } else { /* * This is all very slow if the length is very large. We don't yet @@ -2597,15 +2638,15 @@ array_splice_impl(JSContext* cx, unsigned argc, Value* vp, bool returnValueIsUse if (CanOptimizeForDenseStorage(obj, len, itemCount - actualDeleteCount, cx)) { DenseElementResult result = - MoveDenseElements(cx, &obj->as(), actualStart + itemCount, - actualStart + actualDeleteCount, - len - (actualStart + actualDeleteCount)); + MoveAnyBoxedOrUnboxedDenseElements(cx, obj, actualStart + itemCount, + actualStart + actualDeleteCount, + len - (actualStart + actualDeleteCount)); MOZ_ASSERT(result != DenseElementResult::Incomplete); if (result == DenseElementResult::Failure) return false; /* Steps 16.a-b. */ - SetInitializedLength(cx, obj.as(), len + itemCount - actualDeleteCount); + SetAnyBoxedOrUnboxedInitializedLength(cx, obj, len + itemCount - actualDeleteCount); } else { RootedValue fromValue(cx); for (double k = len - actualDeleteCount; k > actualStart; k--) { @@ -2790,7 +2831,7 @@ SliceSlowly(JSContext* cx, HandleObject obj, HandleObject receiver, } static bool -SliceSparse(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, HandleArrayObject result) +SliceSparse(JSContext* cx, HandleObject obj, uint32_t begin, uint32_t end, HandleObject result) { MOZ_ASSERT(begin <= end); @@ -2840,28 +2881,26 @@ ArraySliceOrdinary(JSContext* cx, HandleObject obj, uint32_t length, uint32_t be begin = end; if (!ObjectMayHaveExtraIndexedProperties(obj)) { - size_t initlen = obj->as().getDenseInitializedLength(); + size_t initlen = GetAnyBoxedOrUnboxedInitializedLength(obj); size_t count = 0; if (initlen > begin) count = Min(initlen - begin, end - begin); - RootedArrayObject narr(cx, NewFullyAllocatedArrayTryReuseGroup(cx, obj, count)); + RootedObject narr(cx, NewFullyAllocatedArrayTryReuseGroup(cx, obj, count)); if (!narr) return false; - - MOZ_ASSERT(count >= narr->as().length()); - narr->as().setLength(cx, count); + SetAnyBoxedOrUnboxedArrayLength(cx, narr, end - begin); if (count) { DebugOnly result = - CopyDenseElements(cx, &narr->as(), &obj->as(), 0, begin, count); + CopyAnyBoxedOrUnboxedDenseElements(cx, narr, obj, 0, begin, count); MOZ_ASSERT(result.value == DenseElementResult::Success); } arr.set(narr); return true; } - RootedArrayObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, end - begin)); + RootedObject narr(cx, NewPartlyAllocatedArrayTryReuseGroup(cx, obj, end - begin)); if (!narr) return false; @@ -2978,10 +3017,11 @@ js::array_slice(JSContext* cx, unsigned argc, Value* vp) return true; } -static bool -ArraySliceDenseKernel(JSContext* cx, ArrayObject* arr, int32_t beginArg, int32_t endArg, ArrayObject* result) +template +DenseElementResult +ArraySliceDenseKernel(JSContext* cx, JSObject* obj, int32_t beginArg, int32_t endArg, JSObject* result) { - int32_t length = arr->length(); + int32_t length = GetAnyBoxedOrUnboxedArrayLength(obj); uint32_t begin = NormalizeSliceTerm(beginArg, length); uint32_t end = NormalizeSliceTerm(endArg, length); @@ -2989,33 +3029,33 @@ ArraySliceDenseKernel(JSContext* cx, ArrayObject* arr, int32_t beginArg, int32_t if (begin > end) begin = end; - size_t initlen = arr->getDenseInitializedLength(); - size_t count = Min(initlen - begin, end - begin); + size_t initlen = GetBoxedOrUnboxedInitializedLength(obj); if (initlen > begin) { + size_t count = Min(initlen - begin, end - begin); if (count) { - if (!result->ensureElements(cx, count)) - return false; - CopyDenseElements(cx, &result->as(), &arr->as(), 0, begin, count); + DenseElementResult rv = EnsureBoxedOrUnboxedDenseElements(cx, result, count); + if (rv != DenseElementResult::Success) + return rv; + CopyBoxedOrUnboxedDenseElements(cx, result, obj, 0, begin, count); } } - MOZ_ASSERT(count >= result->length()); - result->setLength(cx, count); - - return true; + SetAnyBoxedOrUnboxedArrayLength(cx, result, end - begin); + return DenseElementResult::Success; } +DefineBoxedOrUnboxedFunctor5(ArraySliceDenseKernel, + JSContext*, JSObject*, int32_t, int32_t, JSObject*); + JSObject* js::array_slice_dense(JSContext* cx, HandleObject obj, int32_t begin, int32_t end, HandleObject result) { if (result && IsArraySpecies(cx, obj)) { - if (!ArraySliceDenseKernel(cx, &obj->as(), begin, end, - &result->as())) - { - return nullptr; - } - return result; + ArraySliceDenseKernelFunctor functor(cx, obj, begin, end, result); + DenseElementResult rv = CallBoxedOrUnboxedSpecialization(functor, result); + MOZ_ASSERT(rv != DenseElementResult::Incomplete); + return rv == DenseElementResult::Success ? result : nullptr; } // Slower path if the JIT wasn't able to allocate an object inline. @@ -3046,7 +3086,7 @@ array_isArray(JSContext* cx, unsigned argc, Value* vp) static bool ArrayFromCallArgs(JSContext* cx, CallArgs& args, HandleObject proto = nullptr) { - ArrayObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), args.length(), proto); + JSObject* obj = NewCopiedArrayForCallingAllocationSite(cx, args.array(), args.length(), proto); if (!obj) return false; @@ -3220,7 +3260,7 @@ ArrayConstructorImpl(JSContext* cx, CallArgs& args, bool isConstructor) } } - ArrayObject* obj = NewPartlyAllocatedArrayForCallingAllocationSite(cx, length, proto); + JSObject* obj = NewPartlyAllocatedArrayForCallingAllocationSite(cx, length, proto); if (!obj) return false; @@ -3246,7 +3286,7 @@ js::array_construct(JSContext* cx, unsigned argc, Value* vp) return ArrayConstructorImpl(cx, args, /* isConstructor = */ false); } -ArrayObject* +JSObject* js::ArrayConstructorOneArg(JSContext* cx, HandleObjectGroup group, int32_t lengthInt) { if (lengthInt < 0) { @@ -3541,7 +3581,7 @@ js::NewDenseFullyAllocatedArrayWithTemplate(JSContext* cx, uint32_t length, JSOb return arr; } -ArrayObject* +JSObject* js::NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc::InitialHeap heap) { MOZ_ASSERT(!gc::IsInsideNursery(templateObject)); @@ -3554,21 +3594,30 @@ js::NewDenseCopyOnWriteArray(JSContext* cx, HandleArrayObject templateObject, gc return arr; } -// Return a new array with the specified length and allocated capacity (up to -// maxLength), using the specified group if possible. If the specified group -// cannot be used, ensure that the created array at least has the given -// [[Prototype]]. +// Return a new boxed or unboxed array with the specified length and allocated +// capacity (up to maxLength), using the specified group if possible. If the +// specified group cannot be used, ensure that the created array at least has +// the given [[Prototype]]. template -static inline ArrayObject* +static inline JSObject* NewArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length, NewObjectKind newKind = GenericObject) { MOZ_ASSERT(newKind != SingletonObject); - if (group->shouldPreTenure()) + if (group->maybePreliminaryObjects()) + group->maybePreliminaryObjects()->maybeAnalyze(cx, group); + + if (group->shouldPreTenure() || group->maybePreliminaryObjects()) newKind = TenuredObject; RootedObject proto(cx, group->proto().toObject()); + if (group->maybeUnboxedLayout()) { + if (length > UnboxedArrayObject::MaximumCapacity) + return NewArray(cx, length, proto, newKind); + return UnboxedArrayObject::create(cx, group, length, newKind, maxLength); + } + ArrayObject* res = NewArray(cx, length, proto, newKind); if (!res) return nullptr; @@ -3580,17 +3629,20 @@ NewArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length if (res->length() > INT32_MAX) res->setLength(cx, res->length()); + if (PreliminaryObjectArray* preliminaryObjects = group->maybePreliminaryObjects()) + preliminaryObjects->registerNewObject(res); + return res; } -ArrayObject* +JSObject* js::NewFullyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length, NewObjectKind newKind) { return NewArrayTryUseGroup(cx, group, length, newKind); } -ArrayObject* +JSObject* js::NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, size_t length) { return NewArrayTryUseGroup(cx, group, length); @@ -3599,13 +3651,16 @@ js::NewPartlyAllocatedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup g // Return a new array with the default prototype and specified allocated // capacity and length. If possible, try to reuse the group of the input // object. The resulting array will either reuse the input object's group or -// will have unknown property types. +// will have unknown property types. Additionally, the result will have the +// same boxed/unboxed elements representation as the input object, unless +// |length| is larger than the input object's initialized length (in which case +// UnboxedArrayObject::MaximumCapacity might be exceeded). template -static inline ArrayObject* +static inline JSObject* NewArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length, NewObjectKind newKind = GenericObject) { - if (!obj->is()) + if (!obj->is() && !obj->is()) return NewArray(cx, length, nullptr, newKind); if (obj->staticPrototype() != cx->global()->maybeGetArrayPrototype()) @@ -3618,20 +3673,20 @@ NewArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length, return NewArrayTryUseGroup(cx, group, length, newKind); } -ArrayObject* +JSObject* js::NewFullyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length, NewObjectKind newKind) { return NewArrayTryReuseGroup(cx, obj, length, newKind); } -ArrayObject* +JSObject* js::NewPartlyAllocatedArrayTryReuseGroup(JSContext* cx, HandleObject obj, size_t length) { return NewArrayTryReuseGroup(cx, obj, length); } -ArrayObject* +JSObject* js::NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, NewObjectKind newKind) { @@ -3641,7 +3696,7 @@ js::NewFullyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, return NewArrayTryUseGroup(cx, group, length, newKind); } -ArrayObject* +JSObject* js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length, HandleObject proto) { RootedObjectGroup group(cx, ObjectGroup::callingAllocationSiteGroup(cx, JSProto_Array, proto)); @@ -3650,23 +3705,68 @@ js::NewPartlyAllocatedArrayForCallingAllocationSite(JSContext* cx, size_t length return NewArrayTryUseGroup(cx, group, length); } -ArrayObject* +bool +js::MaybeAnalyzeBeforeCreatingLargeArray(ExclusiveContext* cx, HandleObjectGroup group, + const Value* vp, size_t length) +{ + static const size_t EagerPreliminaryObjectAnalysisThreshold = 800; + + // Force analysis to see if an unboxed array can be used when making a + // sufficiently large array, to avoid excessive analysis and copying later + // on. If this is the first array of its group that is being created, first + // make a dummy array with the initial elements of the array we are about + // to make, so there is some basis for the unboxed array analysis. + if (length > EagerPreliminaryObjectAnalysisThreshold) { + if (PreliminaryObjectArrayWithTemplate* objects = group->maybePreliminaryObjects()) { + if (objects->empty()) { + size_t nlength = Min(length, 100); + JSObject* obj = NewFullyAllocatedArrayTryUseGroup(cx, group, nlength); + if (!obj) + return false; + DebugOnly result = + SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, 0, vp, nlength, + ShouldUpdateTypes::Update); + MOZ_ASSERT(result.value == DenseElementResult::Success); + } + objects->maybeAnalyze(cx, group, /* forceAnalyze = */ true); + } + } + return true; +} + +JSObject* js::NewCopiedArrayTryUseGroup(ExclusiveContext* cx, HandleObjectGroup group, const Value* vp, size_t length, NewObjectKind newKind, ShouldUpdateTypes updateTypes) { - ArrayObject* obj = NewFullyAllocatedArrayTryUseGroup(cx, group, length, newKind); + if (!MaybeAnalyzeBeforeCreatingLargeArray(cx, group, vp, length)) + return nullptr; + + JSObject* obj = NewFullyAllocatedArrayTryUseGroup(cx, group, length, newKind); if (!obj) return nullptr; - DenseElementResult result = obj->setOrExtendDenseElements(cx, 0, vp, length, updateTypes); + DenseElementResult result = + SetOrExtendAnyBoxedOrUnboxedDenseElements(cx, obj, 0, vp, length, updateTypes); + if (result == DenseElementResult::Failure) + return nullptr; + if (result == DenseElementResult::Success) + return obj; + + MOZ_ASSERT(obj->is()); + if (!UnboxedArrayObject::convertToNative(cx->asJSContext(), obj)) + return nullptr; + + result = SetOrExtendBoxedOrUnboxedDenseElements(cx, obj, 0, vp, length, + updateTypes); + MOZ_ASSERT(result != DenseElementResult::Incomplete); if (result == DenseElementResult::Failure) return nullptr; - MOZ_ASSERT(result == DenseElementResult::Success); + return obj; } -ArrayObject* +JSObject* js::NewCopiedArrayForCallingAllocationSite(JSContext* cx, const Value* vp, size_t length, HandleObject proto /* = nullptr */) { -- cgit v1.2.3