mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-22 09:45:41 +00:00
Bug 1201869 - Part 2: Make an array sparse when exceeds the limit of dense array length. r=Waldo
--HG-- extra : commitid : Hf5cv9HAcnR extra : rebase_source : 3f8c46f4549cbfe8d267f6de4b930562681fcd25
This commit is contained in:
parent
4651e27655
commit
d6f6b9e0d0
@ -8767,7 +8767,7 @@ Parser<ParseHandler>::arrayInitializer(YieldHandling yieldHandling)
|
||||
uint32_t index = 0;
|
||||
TokenStream::Modifier modifier = TokenStream::Operand;
|
||||
for (; ; index++) {
|
||||
if (index == NativeObject::NELEMENTS_LIMIT) {
|
||||
if (index >= NativeObject::MAX_DENSE_ELEMENTS_COUNT) {
|
||||
report(ParseError, false, null(), JSMSG_ARRAY_INIT_TOO_BIG);
|
||||
return null();
|
||||
}
|
||||
|
40
js/src/jit-test/tests/arrays/dense-from-sparse.js
Normal file
40
js/src/jit-test/tests/arrays/dense-from-sparse.js
Normal file
@ -0,0 +1,40 @@
|
||||
// |jit-test| allow-oom
|
||||
// Appending elements to a dense array should make the array sparse when the
|
||||
// length exceeds the limit.
|
||||
|
||||
function test() {
|
||||
const MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1;
|
||||
const VALUES_PER_HEADER = 2;
|
||||
const MAX_DENSE_ELEMENTS_COUNT = MAX_DENSE_ELEMENTS_ALLOCATION - VALUES_PER_HEADER;
|
||||
const SPARSE_DENSITY_RATIO = 8;
|
||||
const MIN_DENSE = MAX_DENSE_ELEMENTS_COUNT / SPARSE_DENSITY_RATIO;
|
||||
const MARGIN = 16;
|
||||
|
||||
let a = [];
|
||||
// Fill the beginning of array to make it keep dense until length exceeds
|
||||
// MAX_DENSE_ELEMENTS_COUNT.
|
||||
for (let i = 0; i < MIN_DENSE; i++)
|
||||
a[i] = i;
|
||||
|
||||
// Skip from MIN_DENSE to MAX_DENSE_ELEMENTS_COUNT - MARGIN, to reduce the
|
||||
// time taken by test.
|
||||
|
||||
// Fill the ending of array to make it sparse at MAX_DENSE_ELEMENTS_COUNT.
|
||||
for (let i = MAX_DENSE_ELEMENTS_COUNT - MARGIN; i < MAX_DENSE_ELEMENTS_COUNT + MARGIN; i++)
|
||||
a[i] = i;
|
||||
|
||||
// Make sure the last element is defined.
|
||||
assertEq(a.length, MAX_DENSE_ELEMENTS_COUNT + MARGIN);
|
||||
assertEq(a[a.length - 1], MAX_DENSE_ELEMENTS_COUNT + MARGIN - 1);
|
||||
|
||||
// Make sure elements around MAX_DENSE_ELEMENTS_COUNT are also defined.
|
||||
assertEq(a[MAX_DENSE_ELEMENTS_COUNT - 1], MAX_DENSE_ELEMENTS_COUNT - 1);
|
||||
assertEq(a[MAX_DENSE_ELEMENTS_COUNT], MAX_DENSE_ELEMENTS_COUNT);
|
||||
assertEq(a[MAX_DENSE_ELEMENTS_COUNT + 1], MAX_DENSE_ELEMENTS_COUNT + 1);
|
||||
}
|
||||
|
||||
var config = getBuildConfiguration();
|
||||
// Takes too long time on debug build.
|
||||
if (!config.debug) {
|
||||
test();
|
||||
}
|
@ -4303,7 +4303,7 @@ CodeGenerator::visitNewArray(LNewArray* lir)
|
||||
JSObject* templateObject = lir->mir()->templateObject();
|
||||
DebugOnly<uint32_t> length = lir->mir()->length();
|
||||
|
||||
MOZ_ASSERT(length < NativeObject::NELEMENTS_LIMIT);
|
||||
MOZ_ASSERT(length <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
|
||||
|
||||
if (lir->mir()->shouldUseVM()) {
|
||||
visitNewArrayCallVM(lir);
|
||||
@ -6946,7 +6946,7 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool)
|
||||
key, &callStub);
|
||||
|
||||
// Update initialized length. The capacity guard above ensures this won't overflow,
|
||||
// due to NELEMENTS_LIMIT.
|
||||
// due to MAX_DENSE_ELEMENTS_COUNT.
|
||||
masm.bumpKey(&key, 1);
|
||||
masm.storeKey(key, Address(elements, ObjectElements::offsetOfInitializedLength()));
|
||||
|
||||
|
@ -442,8 +442,9 @@ IonBuilder::inlineArray(CallInfo& callInfo)
|
||||
|
||||
// Negative lengths generate a RangeError, unhandled by the inline path.
|
||||
initLength = arg->constantValue().toInt32();
|
||||
if (initLength >= NativeObject::NELEMENTS_LIMIT)
|
||||
if (initLength > NativeObject::MAX_DENSE_ELEMENTS_COUNT)
|
||||
return InliningStatus_NotInlined;
|
||||
MOZ_ASSERT(initLength <= INT32_MAX);
|
||||
|
||||
// Make sure initLength matches the template object's length. This is
|
||||
// not guaranteed to be the case, for instance if we're inlining the
|
||||
|
@ -4119,7 +4119,7 @@ MNewArray::shouldUseVM() const
|
||||
return !templateObject()->as<UnboxedArrayObject>().hasInlineElements();
|
||||
}
|
||||
|
||||
MOZ_ASSERT(length() < NativeObject::NELEMENTS_LIMIT);
|
||||
MOZ_ASSERT(length() <= NativeObject::MAX_DENSE_ELEMENTS_COUNT);
|
||||
|
||||
size_t arraySlots =
|
||||
gc::GetGCKindSlots(templateObject()->asTenured().getAllocKind()) - ObjectElements::VALUES_PER_HEADER;
|
||||
|
@ -1734,7 +1734,7 @@ MArrayLength::computeRange(TempAllocator& alloc)
|
||||
void
|
||||
MInitializedLength::computeRange(TempAllocator& alloc)
|
||||
{
|
||||
setRange(Range::NewUInt32Range(alloc, 0, NativeObject::NELEMENTS_LIMIT));
|
||||
setRange(Range::NewUInt32Range(alloc, 0, NativeObject::MAX_DENSE_ELEMENTS_COUNT));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -172,7 +172,7 @@ GetGCObjectKind(size_t numSlots)
|
||||
|
||||
/* As for GetGCObjectKind, but for dense array allocation. */
|
||||
static inline AllocKind
|
||||
GetGCArrayKind(size_t numSlots)
|
||||
GetGCArrayKind(size_t numElements)
|
||||
{
|
||||
/*
|
||||
* Dense arrays can use their fixed slots to hold their elements array
|
||||
@ -181,9 +181,12 @@ GetGCArrayKind(size_t numSlots)
|
||||
* unused.
|
||||
*/
|
||||
JS_STATIC_ASSERT(ObjectElements::VALUES_PER_HEADER == 2);
|
||||
if (numSlots > NativeObject::NELEMENTS_LIMIT || numSlots + 2 >= SLOTS_TO_THING_KIND_LIMIT)
|
||||
if (numElements > NativeObject::MAX_DENSE_ELEMENTS_COUNT ||
|
||||
numElements + ObjectElements::VALUES_PER_HEADER >= SLOTS_TO_THING_KIND_LIMIT)
|
||||
{
|
||||
return AllocKind::OBJECT2;
|
||||
return slotsToThingKind[numSlots + 2];
|
||||
}
|
||||
return slotsToThingKind[numElements + ObjectElements::VALUES_PER_HEADER];
|
||||
}
|
||||
|
||||
static inline AllocKind
|
||||
|
@ -791,23 +791,23 @@ NewObjectWithGroup(ExclusiveContext* cx, HandleObjectGroup group,
|
||||
}
|
||||
|
||||
/*
|
||||
* As for gc::GetGCObjectKind, where numSlots is a guess at the final size of
|
||||
* As for gc::GetGCObjectKind, where numElements is a guess at the final size of
|
||||
* the object, zero if the final size is unknown. This should only be used for
|
||||
* objects that do not require any fixed slots.
|
||||
*/
|
||||
static inline gc::AllocKind
|
||||
GuessObjectGCKind(size_t numSlots)
|
||||
GuessObjectGCKind(size_t numElements)
|
||||
{
|
||||
if (numSlots)
|
||||
return gc::GetGCObjectKind(numSlots);
|
||||
if (numElements)
|
||||
return gc::GetGCObjectKind(numElements);
|
||||
return gc::AllocKind::OBJECT4;
|
||||
}
|
||||
|
||||
static inline gc::AllocKind
|
||||
GuessArrayGCKind(size_t numSlots)
|
||||
GuessArrayGCKind(size_t numElements)
|
||||
{
|
||||
if (numSlots)
|
||||
return gc::GetGCArrayKind(numSlots);
|
||||
if (numElements)
|
||||
return gc::GetGCArrayKind(numElements);
|
||||
return gc::AllocKind::OBJECT8;
|
||||
}
|
||||
|
||||
|
@ -6,8 +6,8 @@
|
||||
|
||||
#include "vm/NativeObject-inl.h"
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Casting.h"
|
||||
#include "mozilla/CheckedInt.h"
|
||||
|
||||
#include "jswatchpoint.h"
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
using namespace js;
|
||||
|
||||
using JS::GenericNaN;
|
||||
using mozilla::ArrayLength;
|
||||
using mozilla::DebugOnly;
|
||||
using mozilla::PodCopy;
|
||||
using mozilla::RoundUpPow2;
|
||||
@ -398,7 +399,7 @@ NativeObject::growSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCou
|
||||
* throttled well before the slot capacity can overflow.
|
||||
*/
|
||||
NativeObject::slotsSizeMustNotOverflow();
|
||||
MOZ_ASSERT(newCount < NELEMENTS_LIMIT);
|
||||
MOZ_ASSERT(newCount <= MAX_SLOTS_COUNT);
|
||||
|
||||
if (!oldCount) {
|
||||
MOZ_ASSERT(!slots_);
|
||||
@ -520,7 +521,7 @@ NativeObject::willBeSparseElements(uint32_t requiredCapacity, uint32_t newElemen
|
||||
uint32_t cap = getDenseCapacity();
|
||||
MOZ_ASSERT(requiredCapacity >= cap);
|
||||
|
||||
if (requiredCapacity >= NELEMENTS_LIMIT)
|
||||
if (requiredCapacity > MAX_DENSE_ELEMENTS_COUNT)
|
||||
return true;
|
||||
|
||||
uint32_t minimalDenseCount = requiredCapacity / SPARSE_DENSITY_RATIO;
|
||||
@ -594,7 +595,7 @@ NativeObject::maybeDensifySparseElements(js::ExclusiveContext* cx, HandleNativeO
|
||||
if (numDenseElements * SPARSE_DENSITY_RATIO < newInitializedLength)
|
||||
return DenseElementResult::Incomplete;
|
||||
|
||||
if (newInitializedLength >= NELEMENTS_LIMIT)
|
||||
if (newInitializedLength > MAX_DENSE_ELEMENTS_COUNT)
|
||||
return DenseElementResult::Incomplete;
|
||||
|
||||
/*
|
||||
@ -672,7 +673,7 @@ NativeObject::maybeDensifySparseElements(js::ExclusiveContext* cx, HandleNativeO
|
||||
// UnboxedArrayObject::chooseCapacityIndex. Changes to the allocation strategy
|
||||
// in one should generally be matched by the other.
|
||||
/* static */ uint32_t
|
||||
NativeObject::goodAllocated(uint32_t reqAllocated, uint32_t length = 0)
|
||||
NativeObject::goodElementsAllocationAmount(uint32_t reqAllocated, uint32_t length = 0)
|
||||
{
|
||||
// Handle "small" requests primarily by doubling.
|
||||
const uint32_t Mebi = 1 << 20;
|
||||
@ -712,18 +713,19 @@ NativeObject::goodAllocated(uint32_t reqAllocated, uint32_t length = 0)
|
||||
// print('0x' + (n * (1 << 20)).toString(16) + ', ');
|
||||
// n = Math.ceil(n * 1.125);
|
||||
// }
|
||||
// print('NELEMENTS_LIMIT - 1');
|
||||
// print('MAX_DENSE_ELEMENTS_ALLOCATION');
|
||||
//
|
||||
// Dense array elements can't exceed |NELEMENTS_LIMIT|, so
|
||||
// |NELEMENTS_LIMIT - 1| is the biggest allowed length.
|
||||
// Dense array elements can't exceed |MAX_DENSE_ELEMENTS_ALLOCATION|, so
|
||||
// |MAX_DENSE_ELEMENTS_ALLOCATION| is the biggest allowed length.
|
||||
static const uint32_t BigBuckets[] = {
|
||||
0x100000, 0x200000, 0x300000, 0x400000, 0x500000, 0x600000, 0x700000,
|
||||
0x800000, 0x900000, 0xb00000, 0xd00000, 0xf00000, 0x1100000, 0x1400000,
|
||||
0x1700000, 0x1a00000, 0x1e00000, 0x2200000, 0x2700000, 0x2c00000,
|
||||
0x3200000, 0x3900000, 0x4100000, 0x4a00000, 0x5400000, 0x5f00000,
|
||||
0x6b00000, 0x7900000, 0x8900000, 0x9b00000, 0xaf00000, 0xc500000,
|
||||
0xde00000, 0xfa00000, NELEMENTS_LIMIT - 1
|
||||
0xde00000, 0xfa00000, MAX_DENSE_ELEMENTS_ALLOCATION
|
||||
};
|
||||
MOZ_ASSERT(BigBuckets[ArrayLength(BigBuckets) - 2] <= MAX_DENSE_ELEMENTS_ALLOCATION);
|
||||
|
||||
// Pick the first bucket that'll fit |reqAllocated|.
|
||||
for (uint32_t b : BigBuckets) {
|
||||
@ -732,12 +734,17 @@ NativeObject::goodAllocated(uint32_t reqAllocated, uint32_t length = 0)
|
||||
}
|
||||
|
||||
// Otherwise, return the maximum bucket size.
|
||||
return NELEMENTS_LIMIT - 1;
|
||||
return MAX_DENSE_ELEMENTS_ALLOCATION;
|
||||
}
|
||||
|
||||
bool
|
||||
NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity)
|
||||
{
|
||||
if (reqCapacity > MAX_DENSE_ELEMENTS_COUNT) {
|
||||
ReportOutOfMemory(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(nonProxyIsExtensible());
|
||||
MOZ_ASSERT(canHaveNonEmptyElements());
|
||||
if (denseElementsAreCopyOnWrite())
|
||||
@ -746,17 +753,9 @@ NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity)
|
||||
uint32_t oldCapacity = getDenseCapacity();
|
||||
MOZ_ASSERT(oldCapacity < reqCapacity);
|
||||
|
||||
using mozilla::CheckedInt;
|
||||
|
||||
CheckedInt<uint32_t> checkedOldAllocated =
|
||||
CheckedInt<uint32_t>(oldCapacity) + ObjectElements::VALUES_PER_HEADER;
|
||||
CheckedInt<uint32_t> checkedReqAllocated =
|
||||
CheckedInt<uint32_t>(reqCapacity) + ObjectElements::VALUES_PER_HEADER;
|
||||
if (!checkedOldAllocated.isValid() || !checkedReqAllocated.isValid())
|
||||
return false;
|
||||
|
||||
uint32_t reqAllocated = checkedReqAllocated.value();
|
||||
uint32_t oldAllocated = checkedOldAllocated.value();
|
||||
uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
|
||||
uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
|
||||
MOZ_ASSERT(oldAllocated <= MAX_DENSE_ELEMENTS_ALLOCATION);
|
||||
|
||||
uint32_t newAllocated;
|
||||
if (is<ArrayObject>() && !as<ArrayObject>().lengthIsWritable()) {
|
||||
@ -766,14 +765,15 @@ NativeObject::growElements(ExclusiveContext* cx, uint32_t reqCapacity)
|
||||
// enforces this requirement.
|
||||
newAllocated = reqAllocated;
|
||||
} else {
|
||||
newAllocated = goodAllocated(reqAllocated, getElementsHeader()->length);
|
||||
newAllocated = goodElementsAllocationAmount(reqAllocated, getElementsHeader()->length);
|
||||
}
|
||||
|
||||
uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
|
||||
MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity);
|
||||
|
||||
if (newCapacity >= NELEMENTS_LIMIT)
|
||||
return false;
|
||||
// If newCapacity exceeds MAX_DENSE_ELEMENTS_COUNT, the array should become
|
||||
// sparse.
|
||||
MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
|
||||
|
||||
uint32_t initlen = getDenseInitializedLength();
|
||||
|
||||
@ -814,13 +814,13 @@ NativeObject::shrinkElements(ExclusiveContext* cx, uint32_t reqCapacity)
|
||||
|
||||
uint32_t oldAllocated = oldCapacity + ObjectElements::VALUES_PER_HEADER;
|
||||
uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
|
||||
uint32_t newAllocated = goodAllocated(reqAllocated);
|
||||
uint32_t newAllocated = goodElementsAllocationAmount(reqAllocated);
|
||||
if (newAllocated == oldAllocated)
|
||||
return; // Leave elements at its old size.
|
||||
|
||||
MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER);
|
||||
uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
|
||||
MOZ_ASSERT(newCapacity < NELEMENTS_LIMIT);
|
||||
MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
|
||||
|
||||
HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
|
||||
HeapSlot* newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots,
|
||||
@ -845,12 +845,12 @@ NativeObject::CopyElementsForWrite(ExclusiveContext* cx, NativeObject* obj)
|
||||
|
||||
uint32_t initlen = obj->getDenseInitializedLength();
|
||||
uint32_t allocated = initlen + ObjectElements::VALUES_PER_HEADER;
|
||||
uint32_t newAllocated = goodAllocated(allocated);
|
||||
uint32_t newAllocated = goodElementsAllocationAmount(allocated);
|
||||
|
||||
uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
|
||||
|
||||
if (newCapacity >= NELEMENTS_LIMIT)
|
||||
return false;
|
||||
// COPY_ON_WRITE flags is set only if obj is a dense array.
|
||||
MOZ_ASSERT(newCapacity <= MAX_DENSE_ELEMENTS_COUNT);
|
||||
|
||||
JSObject::writeBarrierPre(obj->getElementsHeader()->ownerObject());
|
||||
|
||||
|
@ -556,8 +556,13 @@ class NativeObject : public JSObject
|
||||
bool shadowingShapeChange(ExclusiveContext* cx, const Shape& shape);
|
||||
bool clearFlag(ExclusiveContext* cx, BaseShape::Flag flag);
|
||||
|
||||
// The maximum number of slots in an object.
|
||||
// |MAX_SLOTS_COUNT * sizeof(JS::Value)| shouldn't overflow
|
||||
// int32_t (see slotsSizeMustNotOverflow).
|
||||
static const uint32_t MAX_SLOTS_COUNT = (1 << 28) - 1;
|
||||
|
||||
static void slotsSizeMustNotOverflow() {
|
||||
static_assert((NativeObject::NELEMENTS_LIMIT - 1) <= INT32_MAX / sizeof(JS::Value),
|
||||
static_assert(NativeObject::MAX_SLOTS_COUNT <= INT32_MAX / sizeof(JS::Value),
|
||||
"every caller of this method requires that a slot "
|
||||
"number (or slot count) count multiplied by "
|
||||
"sizeof(Value) can't overflow uint32_t (and sometimes "
|
||||
@ -882,11 +887,19 @@ class NativeObject : public JSObject
|
||||
|
||||
/* Elements accessors. */
|
||||
|
||||
/* Upper bound on the number of elements in an object. */
|
||||
static const uint32_t NELEMENTS_LIMIT = JS_BIT(28);
|
||||
// The maximum size, in sizeof(Value), of the allocation used for an
|
||||
// object's dense elements. (This includes space used to store an
|
||||
// ObjectElements instance.)
|
||||
// |MAX_DENSE_ELEMENTS_ALLOCATION * sizeof(JS::Value)| shouldn't overflow
|
||||
// int32_t (see elementsSizeMustNotOverflow).
|
||||
static const uint32_t MAX_DENSE_ELEMENTS_ALLOCATION = (1 << 28) - 1;
|
||||
|
||||
// The maximum number of usable dense elements in an object.
|
||||
static const uint32_t MAX_DENSE_ELEMENTS_COUNT =
|
||||
MAX_DENSE_ELEMENTS_ALLOCATION - ObjectElements::VALUES_PER_HEADER;
|
||||
|
||||
static void elementsSizeMustNotOverflow() {
|
||||
static_assert((NativeObject::NELEMENTS_LIMIT - 1) <= INT32_MAX / sizeof(JS::Value),
|
||||
static_assert(NativeObject::MAX_DENSE_ELEMENTS_COUNT <= INT32_MAX / sizeof(JS::Value),
|
||||
"every caller of this method require that an element "
|
||||
"count multiplied by sizeof(Value) can't overflow "
|
||||
"uint32_t (and sometimes int32_t ,too)");
|
||||
@ -904,7 +917,7 @@ class NativeObject : public JSObject
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t goodAllocated(uint32_t n, uint32_t length);
|
||||
static uint32_t goodElementsAllocationAmount(uint32_t n, uint32_t length);
|
||||
bool growElements(ExclusiveContext* cx, uint32_t newcap);
|
||||
void shrinkElements(ExclusiveContext* cx, uint32_t cap);
|
||||
void setDynamicElements(ObjectElements* header) {
|
||||
|
Loading…
Reference in New Issue
Block a user