mirror of
https://github.com/classilla/tenfourfox.git
synced 2025-02-06 02:30:56 +00:00
closes #509: M1364346 parts 2+3 (unshift)
This commit is contained in:
parent
fb3fd442d9
commit
41a7492086
11
js/src/jit-test/tests/basic/shifted-elements4-unshift.js
Normal file
11
js/src/jit-test/tests/basic/shifted-elements4-unshift.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
function f() {
|
||||||
|
var arr = [];
|
||||||
|
for (var i = 0; i < 2; i++) {
|
||||||
|
for (var j = 0; j < 90000; j++)
|
||||||
|
arr.unshift(j);
|
||||||
|
for (var j = 0; j < 90000; j++)
|
||||||
|
assertEq(arr.pop(), j);
|
||||||
|
assertEq(arr.length, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f();
|
47
js/src/jit-test/tests/basic/shifted-elements7.js
Normal file
47
js/src/jit-test/tests/basic/shifted-elements7.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
function test1() {
|
||||||
|
var a = [];
|
||||||
|
for (var i = 0; i < 100; i++)
|
||||||
|
a.unshift("foo" + i);
|
||||||
|
for (var i = 99; i >= 0; i--) {
|
||||||
|
assertEq(a.shift(), "foo" + i);
|
||||||
|
a.unshift("foo" + (i - 1));
|
||||||
|
}
|
||||||
|
assertEq(a.length, 100);
|
||||||
|
}
|
||||||
|
test1();
|
||||||
|
|
||||||
|
function sum(arr) {
|
||||||
|
var res = 0;
|
||||||
|
for (var i = 0; i < arr.length; i++)
|
||||||
|
res += arr[i];
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
function test2() {
|
||||||
|
var a = [];
|
||||||
|
for (var i = 0; i < 200; i++)
|
||||||
|
a.push(i);
|
||||||
|
for (var i = 0; i < 100; i++)
|
||||||
|
a.shift();
|
||||||
|
for (var i = 0; i < 200; i++)
|
||||||
|
a.unshift(i);
|
||||||
|
assertEq(a.length, 300);
|
||||||
|
assertEq(sum(a), 34850);
|
||||||
|
}
|
||||||
|
test2();
|
||||||
|
|
||||||
|
function test3() {
|
||||||
|
var a = [];
|
||||||
|
for (var i = 0; i < 200; i++)
|
||||||
|
a.push(i);
|
||||||
|
var toAdd = [];
|
||||||
|
var step = 1;
|
||||||
|
for (var i = 0; i < 2500; i += step) {
|
||||||
|
for (var j = 0; j < step; j++)
|
||||||
|
toAdd.unshift(i + j);
|
||||||
|
a.unshift(...toAdd);
|
||||||
|
step = Math.max((i / 16)|0, 1);
|
||||||
|
}
|
||||||
|
assertEq(a.length, 41463);
|
||||||
|
assertEq(sum(a), 26657756);
|
||||||
|
}
|
||||||
|
test3();
|
@ -769,7 +769,7 @@ js::ArraySetLength(JSContext* cx, Handle<ArrayObject*> arr, HandleId id,
|
|||||||
|
|
||||||
if (attrs & JSPROP_READONLY) {
|
if (attrs & JSPROP_READONLY) {
|
||||||
if (header->numShiftedElements() > 0) {
|
if (header->numShiftedElements() > 0) {
|
||||||
arr->unshiftElements();
|
arr->moveShiftedElements();
|
||||||
header = arr->getElementsHeader();
|
header = arr->getElementsHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2255,7 +2255,7 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
|
|||||||
if (args.length() > 0) {
|
if (args.length() > 0) {
|
||||||
/* Slide up the array to make room for all args at the bottom. */
|
/* Slide up the array to make room for all args at the bottom. */
|
||||||
if (length > 0) {
|
if (length > 0) {
|
||||||
// Only include a fast path for boxed arrays. Unboxed arrays can'nt
|
// Only include a fast path for boxed arrays. Unboxed arrays can't
|
||||||
// be optimized here because unshifting temporarily places holes at
|
// be optimized here because unshifting temporarily places holes at
|
||||||
// the start of the array.
|
// the start of the array.
|
||||||
bool optimized = false;
|
bool optimized = false;
|
||||||
@ -2267,14 +2267,16 @@ js::array_unshift(JSContext* cx, unsigned argc, Value* vp)
|
|||||||
ArrayObject* aobj = &obj->as<ArrayObject>();
|
ArrayObject* aobj = &obj->as<ArrayObject>();
|
||||||
if (!aobj->lengthIsWritable())
|
if (!aobj->lengthIsWritable())
|
||||||
break;
|
break;
|
||||||
DenseElementResult result = aobj->ensureDenseElements(cx, length, args.length());
|
if (MOZ_UNLIKELY(length > UINT32_MAX) || !aobj->tryUnshiftDenseElements(args.length())) {
|
||||||
if (result != DenseElementResult::Success) {
|
DenseElementResult result = aobj->ensureDenseElements(cx, length, args.length());
|
||||||
if (result == DenseElementResult::Failure)
|
if (result != DenseElementResult::Success) {
|
||||||
return false;
|
if (result == DenseElementResult::Failure)
|
||||||
MOZ_ASSERT(result == DenseElementResult::Incomplete);
|
return false;
|
||||||
break;
|
MOZ_ASSERT(result == DenseElementResult::Incomplete);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
aobj->moveDenseElements(args.length(), 0, length);
|
||||||
}
|
}
|
||||||
aobj->moveDenseElements(args.length(), 0, length);
|
|
||||||
for (uint32_t i = 0; i < args.length(); i++)
|
for (uint32_t i = 0; i < args.length(); i++)
|
||||||
aobj->setDenseElement(i, MagicValue(JS_ELEMENTS_HOLE));
|
aobj->setDenseElement(i, MagicValue(JS_ELEMENTS_HOLE));
|
||||||
optimized = true;
|
optimized = true;
|
||||||
|
@ -537,7 +537,7 @@ js::SetIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level)
|
|||||||
if (!obj->as<ArrayObject>().maybeCopyElementsForWrite(cx))
|
if (!obj->as<ArrayObject>().maybeCopyElementsForWrite(cx))
|
||||||
return false;
|
return false;
|
||||||
if (nobj->getElementsHeader()->numShiftedElements() > 0)
|
if (nobj->getElementsHeader()->numShiftedElements() > 0)
|
||||||
nobj->unshiftElements();
|
nobj->moveShiftedElements();
|
||||||
obj->as<ArrayObject>().getElementsHeader()->setNonwritableArrayLength();
|
obj->as<ArrayObject>().getElementsHeader()->setNonwritableArrayLength();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -300,11 +300,19 @@ NativeObject::tryShiftDenseElements(uint32_t count)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shiftDenseElementsUnchecked(count);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void
|
||||||
|
NativeObject::shiftDenseElementsUnchecked(uint32_t count)
|
||||||
|
{
|
||||||
|
ObjectElements* header = getElementsHeader();
|
||||||
MOZ_ASSERT(count > 0);
|
MOZ_ASSERT(count > 0);
|
||||||
MOZ_ASSERT(count < header->initializedLength);
|
MOZ_ASSERT(count < header->initializedLength);
|
||||||
|
|
||||||
if (MOZ_UNLIKELY(header->numShiftedElements() + count > ObjectElements::MaxShiftedElements)) {
|
if (MOZ_UNLIKELY(header->numShiftedElements() + count > ObjectElements::MaxShiftedElements)) {
|
||||||
unshiftElements();
|
moveShiftedElements();
|
||||||
header = getElementsHeader();
|
header = getElementsHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,7 +322,6 @@ NativeObject::tryShiftDenseElements(uint32_t count)
|
|||||||
elements_ += count;
|
elements_ += count;
|
||||||
ObjectElements* newHeader = getElementsHeader();
|
ObjectElements* newHeader = getElementsHeader();
|
||||||
memmove(newHeader, header, sizeof(ObjectElements));
|
memmove(newHeader, header, sizeof(ObjectElements));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make an object with pregenerated shape from a NEWOBJECT bytecode. */
|
/* Make an object with pregenerated shape from a NEWOBJECT bytecode. */
|
||||||
|
@ -760,7 +760,7 @@ NativeObject::goodElementsAllocationAmount(ExclusiveContext* cx, uint32_t reqCap
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
NativeObject::unshiftElements()
|
NativeObject::moveShiftedElements()
|
||||||
{
|
{
|
||||||
ObjectElements* header = getElementsHeader();
|
ObjectElements* header = getElementsHeader();
|
||||||
uint32_t numShifted = header->numShiftedElements();
|
uint32_t numShifted = header->numShiftedElements();
|
||||||
@ -776,7 +776,7 @@ NativeObject::unshiftElements()
|
|||||||
elements_ = newHeader->elements();
|
elements_ = newHeader->elements();
|
||||||
|
|
||||||
// To move the elements, temporarily update initializedLength to include
|
// To move the elements, temporarily update initializedLength to include
|
||||||
// both shifted and unshifted elements.
|
// the shifted elements.
|
||||||
newHeader->initializedLength += numShifted;
|
newHeader->initializedLength += numShifted;
|
||||||
|
|
||||||
// Move the elements. Initialize to |undefined| to ensure pre-barriers
|
// Move the elements. Initialize to |undefined| to ensure pre-barriers
|
||||||
@ -792,14 +792,95 @@ NativeObject::unshiftElements()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
NativeObject::maybeUnshiftElements()
|
NativeObject::maybeMoveShiftedElements()
|
||||||
{
|
{
|
||||||
ObjectElements* header = getElementsHeader();
|
ObjectElements* header = getElementsHeader();
|
||||||
MOZ_ASSERT(header->numShiftedElements() > 0);
|
MOZ_ASSERT(header->numShiftedElements() > 0);
|
||||||
|
|
||||||
// Unshift if less than a third of the allocated space is in use.
|
// Move the elements if less than a third of the allocated space is in use.
|
||||||
if (header->capacity < header->numAllocatedElements() / 3)
|
if (header->capacity < header->numAllocatedElements() / 3)
|
||||||
unshiftElements();
|
moveShiftedElements();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
NativeObject::tryUnshiftDenseElements(uint32_t count)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(count > 0);
|
||||||
|
|
||||||
|
ObjectElements* header = getElementsHeader();
|
||||||
|
uint32_t numShifted = header->numShiftedElements();
|
||||||
|
|
||||||
|
if (count > numShifted) {
|
||||||
|
// We need more elements than are easily available. Try to make space
|
||||||
|
// for more elements than we need (and shift the remaining ones) so
|
||||||
|
// that unshifting more elements later will be fast.
|
||||||
|
|
||||||
|
// Don't bother reserving elements if the number of elements is small.
|
||||||
|
// Note that there's no technical reason for using this particular
|
||||||
|
// limit.
|
||||||
|
if (header->initializedLength <= 10 ||
|
||||||
|
header->isCopyOnWrite() ||
|
||||||
|
// header->isFrozen() || // NYI
|
||||||
|
header->hasNonwritableArrayLength() ||
|
||||||
|
MOZ_UNLIKELY(count > ObjectElements::MaxShiftedElements))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOZ_ASSERT(header->capacity >= header->initializedLength);
|
||||||
|
uint32_t unusedCapacity = header->capacity - header->initializedLength;
|
||||||
|
|
||||||
|
// Determine toShift, the number of extra elements we want to make
|
||||||
|
// available.
|
||||||
|
uint32_t toShift = count - numShifted;
|
||||||
|
MOZ_ASSERT(toShift <= ObjectElements::MaxShiftedElements,
|
||||||
|
"count <= MaxShiftedElements so toShift <= MaxShiftedElements");
|
||||||
|
|
||||||
|
// Give up if we need to allocate more elements.
|
||||||
|
if (toShift > unusedCapacity)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Move more elements than we need, so that other unshift calls will be
|
||||||
|
// fast. We just have to make sure we don't exceed unusedCapacity.
|
||||||
|
toShift = Min(toShift + unusedCapacity / 2, unusedCapacity);
|
||||||
|
|
||||||
|
// Ensure |numShifted + toShift| does not exceed MaxShiftedElements.
|
||||||
|
if (numShifted + toShift > ObjectElements::MaxShiftedElements)
|
||||||
|
toShift = ObjectElements::MaxShiftedElements - numShifted;
|
||||||
|
|
||||||
|
MOZ_ASSERT(count <= numShifted + toShift);
|
||||||
|
MOZ_ASSERT(numShifted + toShift <= ObjectElements::MaxShiftedElements);
|
||||||
|
MOZ_ASSERT(toShift <= unusedCapacity);
|
||||||
|
|
||||||
|
// Now move/unshift the elements.
|
||||||
|
uint32_t initLen = header->initializedLength;
|
||||||
|
setDenseInitializedLength(initLen + toShift);
|
||||||
|
for (uint32_t i = 0; i < toShift; i++)
|
||||||
|
initDenseElement(initLen + i, UndefinedValue());
|
||||||
|
moveDenseElements(toShift, 0, initLen);
|
||||||
|
|
||||||
|
// Shift the elements we just prepended.
|
||||||
|
shiftDenseElementsUnchecked(toShift);
|
||||||
|
|
||||||
|
// We can now fall-through to the fast path below.
|
||||||
|
header = getElementsHeader();
|
||||||
|
MOZ_ASSERT(header->numShiftedElements() == numShifted + toShift);
|
||||||
|
|
||||||
|
numShifted = header->numShiftedElements();
|
||||||
|
MOZ_ASSERT(count <= numShifted);
|
||||||
|
}
|
||||||
|
|
||||||
|
elements_ -= count;
|
||||||
|
ObjectElements* newHeader = getElementsHeader();
|
||||||
|
memmove(newHeader, header, sizeof(ObjectElements));
|
||||||
|
|
||||||
|
newHeader->unshiftShiftedElements(count);
|
||||||
|
|
||||||
|
// Initialize to |undefined| to ensure pre-barriers don't see garbage.
|
||||||
|
for (uint32_t i = 0; i < count; i++)
|
||||||
|
initDenseElement(i, UndefinedValue());
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
@ -810,22 +891,31 @@ NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity)
|
|||||||
if (denseElementsAreCopyOnWrite())
|
if (denseElementsAreCopyOnWrite())
|
||||||
MOZ_CRASH();
|
MOZ_CRASH();
|
||||||
|
|
||||||
// If there are shifted elements, consider unshifting them first. If we
|
// If there are shifted elements, consider moving them first. If we don't
|
||||||
// don't unshift here, the code below will include the shifted elements in
|
// move them here, the code below will include the shifted elements in the
|
||||||
// the resize.
|
// resize.
|
||||||
uint32_t numShifted = getElementsHeader()->numShiftedElements();
|
uint32_t numShifted = getElementsHeader()->numShiftedElements();
|
||||||
if (numShifted > 0) {
|
if (numShifted > 0) {
|
||||||
maybeUnshiftElements();
|
// If the number of elements is small, it's cheaper to just move them as
|
||||||
|
// it may avoid a malloc/realloc. Note that there's no technical reason
|
||||||
|
// for using this particular value, but it works well in real-world use
|
||||||
|
// cases.
|
||||||
|
static const size_t MaxElementsToMoveEagerly = 20;
|
||||||
|
|
||||||
|
if (getElementsHeader()->initializedLength <= MaxElementsToMoveEagerly)
|
||||||
|
moveShiftedElements();
|
||||||
|
else
|
||||||
|
maybeMoveShiftedElements();
|
||||||
if (getDenseCapacity() >= reqCapacity)
|
if (getDenseCapacity() >= reqCapacity)
|
||||||
return true;
|
return true;
|
||||||
numShifted = getElementsHeader()->numShiftedElements();
|
numShifted = getElementsHeader()->numShiftedElements();
|
||||||
|
|
||||||
// Ensure |reqCapacity + numShifted| below won't overflow by forcing an
|
// If |reqCapacity + numShifted| overflows, we just move all shifted
|
||||||
// unshift in that case.
|
// elements to avoid the problem.
|
||||||
CheckedInt<uint32_t> checkedReqCapacity(reqCapacity);
|
CheckedInt<uint32_t> checkedReqCapacity(reqCapacity);
|
||||||
checkedReqCapacity += numShifted;
|
checkedReqCapacity += numShifted;
|
||||||
if (MOZ_UNLIKELY(!checkedReqCapacity.isValid())) {
|
if (MOZ_UNLIKELY(!checkedReqCapacity.isValid())) {
|
||||||
unshiftElements();
|
moveShiftedElements();
|
||||||
numShifted = 0;
|
numShifted = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -895,10 +985,10 @@ NativeObject::shrinkElements(ExclusiveContext* cx, uint32_t reqCapacity)
|
|||||||
if (!hasDynamicElements())
|
if (!hasDynamicElements())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// If we have shifted elements, consider unshifting them.
|
// If we have shifted elements, consider moving them.
|
||||||
uint32_t numShifted = getElementsHeader()->numShiftedElements();
|
uint32_t numShifted = getElementsHeader()->numShiftedElements();
|
||||||
if (numShifted > 0) {
|
if (numShifted > 0) {
|
||||||
maybeUnshiftElements();
|
maybeMoveShiftedElements();
|
||||||
numShifted = getElementsHeader()->numShiftedElements();
|
numShifted = getElementsHeader()->numShiftedElements();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ ArraySetLength(JSContext* cx, Handle<ArrayObject*> obj, HandleId id,
|
|||||||
* to the next element and moving the ObjectElements header in memory (so it's
|
* to the next element and moving the ObjectElements header in memory (so it's
|
||||||
* stored where the shifted Value used to be).
|
* stored where the shifted Value used to be).
|
||||||
*
|
*
|
||||||
* Shifted elements can be unshifted when we grow the array, when the array is
|
* Shifted elements can be moved when we grow the array, when the array is
|
||||||
* frozen (for simplicity, shifted elements are not supported on objects that
|
* frozen (for simplicity, shifted elements are not supported on objects that
|
||||||
* are frozen, have copy-on-write elements, or on arrays with non-writable
|
* are frozen, have copy-on-write elements, or on arrays with non-writable
|
||||||
* length).
|
* length).
|
||||||
@ -205,12 +205,13 @@ class ObjectElements
|
|||||||
};
|
};
|
||||||
|
|
||||||
// The flags word stores both the flags and the number of shifted elements.
|
// The flags word stores both the flags and the number of shifted elements.
|
||||||
// Allow shifting 2047 elements before unshifting.
|
// Allow shifting 1023 elements before actually moving the elements.
|
||||||
static const size_t NumShiftedElementsBits = 11;
|
// (Reduced from 2047 due to memory concerns on our 32-bit Power Macs.)
|
||||||
|
static const size_t NumShiftedElementsBits = 10;
|
||||||
static const size_t MaxShiftedElements = (1 << NumShiftedElementsBits) - 1;
|
static const size_t MaxShiftedElements = (1 << NumShiftedElementsBits) - 1;
|
||||||
static const size_t NumShiftedElementsShift = 32 - NumShiftedElementsBits;
|
static const size_t NumShiftedElementsShift = 32 - NumShiftedElementsBits;
|
||||||
static const size_t FlagsMask = (1 << NumShiftedElementsShift) - 1;
|
static const size_t FlagsMask = (1 << NumShiftedElementsShift) - 1;
|
||||||
static_assert(MaxShiftedElements == 2047,
|
static_assert(MaxShiftedElements == 1023,
|
||||||
"MaxShiftedElements should match the comment");
|
"MaxShiftedElements should match the comment");
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -280,6 +281,16 @@ class ObjectElements
|
|||||||
capacity -= count;
|
capacity -= count;
|
||||||
initializedLength -= count;
|
initializedLength -= count;
|
||||||
}
|
}
|
||||||
|
void unshiftShiftedElements(uint32_t count) {
|
||||||
|
MOZ_ASSERT(count > 0);
|
||||||
|
MOZ_ASSERT(!(flags & (NONWRITABLE_ARRAY_LENGTH | FROZEN | COPY_ON_WRITE)));
|
||||||
|
uint32_t numShifted = numShiftedElements();
|
||||||
|
MOZ_ASSERT(count <= numShifted);
|
||||||
|
numShifted -= count;
|
||||||
|
flags = (numShifted << NumShiftedElementsShift) | (flags & FlagsMask);
|
||||||
|
capacity += count;
|
||||||
|
initializedLength += count;
|
||||||
|
}
|
||||||
void clearShiftedElements() {
|
void clearShiftedElements() {
|
||||||
flags &= FlagsMask;
|
flags &= FlagsMask;
|
||||||
MOZ_ASSERT(numShiftedElements() == 0);
|
MOZ_ASSERT(numShiftedElements() == 0);
|
||||||
@ -917,6 +928,8 @@ class NativeObject : public JSObject
|
|||||||
elements_[i].HeapSlot::~HeapSlot();
|
elements_[i].HeapSlot::~HeapSlot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void shiftDenseElementsUnchecked(uint32_t count);
|
||||||
|
|
||||||
static bool rollbackProperties(ExclusiveContext* cx, HandleNativeObject obj,
|
static bool rollbackProperties(ExclusiveContext* cx, HandleNativeObject obj,
|
||||||
uint32_t slotSpan);
|
uint32_t slotSpan);
|
||||||
|
|
||||||
@ -1032,11 +1045,15 @@ class NativeObject : public JSObject
|
|||||||
// Try to shift |count| dense elements, see the "Shifted elements" comment.
|
// Try to shift |count| dense elements, see the "Shifted elements" comment.
|
||||||
inline bool tryShiftDenseElements(uint32_t count);
|
inline bool tryShiftDenseElements(uint32_t count);
|
||||||
|
|
||||||
// Unshift all shifted elements so that numShiftedElements is 0.
|
// Try to make space for |count| dense elements at the start of the array.
|
||||||
void unshiftElements();
|
bool tryUnshiftDenseElements(uint32_t count);
|
||||||
|
|
||||||
// If this object has many shifted elements, unshift them.
|
// Move the elements header and all shifted elements to the start of the
|
||||||
void maybeUnshiftElements();
|
// allocated elements space, so that numShiftedElements is 0 afterwards.
|
||||||
|
void moveShiftedElements();
|
||||||
|
|
||||||
|
// If this object has many shifted elements call moveShiftedElements.
|
||||||
|
void maybeMoveShiftedElements();
|
||||||
|
|
||||||
static bool goodElementsAllocationAmount(ExclusiveContext* cx, uint32_t reqAllocated,
|
static bool goodElementsAllocationAmount(ExclusiveContext* cx, uint32_t reqAllocated,
|
||||||
uint32_t length, uint32_t* goodAmount);
|
uint32_t length, uint32_t* goodAmount);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user