Remove hole count from dense arrays (580846, r=njn).

This commit is contained in:
Andreas Gal
2010-07-22 18:45:21 -07:00
parent 583a9af1bf
commit bf0a5193fe
10 changed files with 128 additions and 294 deletions

View File

@@ -48,20 +48,12 @@
*
* We track these pieces of metadata for arrays in dense mode:
* - The array's length property as a uint32, accessible with
* getArrayLength(), setDenseArrayLength().
* - The number of indices that are filled (non-holes), accessible with
* {get,set}DenseArrayCount().
* getArrayLength(), setArrayLength().
* - The number of element slots (capacity), gettable with
* getDenseArrayCapacity().
* - The minimum of length and capacity (minLenCap). There are no explicit
* setters, it's updated automatically by setDenseArrayLength() and
* setDenseArrayCapacity(). There are also no explicit getters, the only
* user is TraceRecorder which can access it directly because it's a
* friend. The function isDenseArrayMinLenCapOk() checks that it is set
* correctly; a call to it should be put in an assertion at use points.
*
* In dense mode, holes in the array are represented by (JS_ARRAY_HOLE) invalid
* values. The final slot in fslots is unused.
* values. The final two slot in fslots are unused.
*
* NB: the capacity and length of a dense array are entirely unrelated! The
* length may be greater than, less than, or equal to the capacity. See
@@ -131,13 +123,26 @@ INDEX_TOO_BIG(jsuint index)
return index > JS_BIT(29) - 1;
}
#define INDEX_TOO_SPARSE(array, index) \
(INDEX_TOO_BIG(index) || \
((index) > array->getDenseArrayCapacity() && (index) >= MIN_SPARSE_INDEX && \
(index) > ((array)->getDenseArrayCount() + 1) * 4))
static inline bool
INDEX_TOO_SPARSE(JSObject *array, jsuint index)
{
/* Small arrays with less than 256 elements are dense, no matter what. */
if (index < 256)
return false;
#define ENSURE_SLOW_ARRAY(cx, obj) \
(obj->getClass() == &js_SlowArrayClass || obj->makeDenseArraySlow(cx))
/*
* Otherwise if the index becomes too large or is more than 256 past
* the current capacity, we have to slowify.
*/
return INDEX_TOO_BIG(index) || (index > array->getDenseArrayCapacity() + 256);
}
static inline bool
ENSURE_SLOW_ARRAY(JSContext *cx, JSObject *obj)
{
return obj->getClass() == &js_SlowArrayClass ||
obj->makeDenseArraySlow(cx);
}
/*
* Determine if the id represents an array index or an XML property index.
@@ -478,9 +483,7 @@ SetArrayElement(JSContext *cx, JSObject *obj, jsdouble index, const Value &v)
if (!obj->ensureDenseArrayElements(cx, idx + 1))
return JS_FALSE;
if (idx >= obj->getArrayLength())
obj->setDenseArrayLength(idx + 1);
if (obj->getDenseArrayElement(idx).isMagic(JS_ARRAY_HOLE))
obj->incDenseArrayCountBy(1);
obj->setArrayLength(idx + 1);
obj->setDenseArrayElement(idx, v);
return JS_TRUE;
}
@@ -507,9 +510,7 @@ DeleteArrayElement(JSContext *cx, JSObject *obj, jsdouble index)
if (obj->isDenseArray()) {
if (index <= jsuint(-1)) {
jsuint idx = jsuint(index);
if (!INDEX_TOO_SPARSE(obj, idx) && idx < obj->getDenseArrayCapacity()) {
if (!obj->getDenseArrayElement(idx).isMagic(JS_ARRAY_HOLE))
obj->decDenseArrayCountBy(1);
if (idx < obj->getDenseArrayCapacity()) {
obj->setDenseArrayElement(idx, MagicValue(JS_ARRAY_HOLE));
return JS_TRUE;
}
@@ -623,26 +624,28 @@ array_length_setter(JSContext *cx, JSObject *obj, jsid id, Value *vp)
vp->setNumber(newlen);
if (oldlen < newlen) {
if (obj->isDenseArray())
obj->setDenseArrayLength(newlen);
else
obj->setSlowArrayLength(newlen);
obj->setArrayLength(newlen);
return true;
}
if (obj->isDenseArray()) {
/* Don't reallocate if we're not actually shrinking our slots. */
/*
* Don't reallocate if we're not actually shrinking our slots. If we do
* shrink slots here, resizeDenseArrayElements will fill all slots to the
* right of newlen with JS_ARRAY_HOLE. This permits us to disregard
* length when reading from arrays as long we are within the capacity.
*/
jsuint capacity = obj->getDenseArrayCapacity();
if (capacity > newlen && !obj->resizeDenseArrayElements(cx, capacity, newlen))
return false;
obj->setDenseArrayLength(newlen);
obj->setArrayLength(newlen);
} else if (oldlen - newlen < (1 << 24)) {
do {
--oldlen;
if (!JS_CHECK_OPERATION_LIMIT(cx) || !DeleteArrayElement(cx, obj, oldlen))
return false;
} while (oldlen != newlen);
obj->setSlowArrayLength(newlen);
obj->setArrayLength(newlen);
} else {
/*
* We are going to remove a lot of indexes in a presumably sparse
@@ -669,7 +672,7 @@ array_length_setter(JSContext *cx, JSObject *obj, jsid id, Value *vp)
return false;
}
}
obj->setSlowArrayLength(newlen);
obj->setArrayLength(newlen);
}
return true;
@@ -786,7 +789,7 @@ slowarray_addProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
return JS_TRUE;
length = obj->getArrayLength();
if (index >= length)
obj->setSlowArrayLength(index + 1);
obj->setArrayLength(index + 1);
return JS_TRUE;
}
@@ -817,9 +820,7 @@ array_setProperty(JSContext *cx, JSObject *obj, jsid id, Value *vp)
return JS_FALSE;
if (i >= obj->getArrayLength())
obj->setDenseArrayLength(i + 1);
if (obj->getDenseArrayElement(i).isMagic(JS_ARRAY_HOLE))
obj->incDenseArrayCountBy(1);
obj->setArrayLength(i + 1);
obj->setDenseArrayElement(i, *vp);
return JS_TRUE;
}
@@ -879,8 +880,7 @@ dense_grow(JSContext* cx, JSObject* obj, jsint i, const Value &v)
return JS_FALSE;
if (u >= obj->getArrayLength())
obj->setDenseArrayLength(u + 1);
obj->incDenseArrayCountBy(1);
obj->setArrayLength(u + 1);
}
obj->setDenseArrayElement(u, v);
@@ -962,11 +962,8 @@ array_deleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval)
return JS_TRUE;
}
if (js_IdIsIndex(id, &i) && i < obj->getDenseArrayCapacity() &&
!obj->getDenseArrayElement(i).isMagic(JS_ARRAY_HOLE)) {
obj->decDenseArrayCountBy(1);
if (js_IdIsIndex(id, &i) && i < obj->getDenseArrayCapacity())
obj->setDenseArrayElement(i, MagicValue(JS_ARRAY_HOLE));
}
if (!js_SuppressDeletedProperty(cx, obj, id))
return false;
@@ -987,9 +984,23 @@ array_trace(JSTracer *trc, JSObject *obj)
JS_ASSERT(obj->isDenseArray());
obj->traceProtoAndParent(trc);
size_t holes = 0;
uint32 capacity = obj->getDenseArrayCapacity();
for (uint32 i = 0; i < capacity; i++)
MarkValue(trc, obj->getDenseArrayElement(i), "dense_array_elems");
for (uint32 i = 0; i < capacity; i++) {
Value v = obj->getDenseArrayElement(i);
if (v.isMagic(JS_ARRAY_HOLE))
++holes;
else
MarkValue(trc, obj->getDenseArrayElement(i), "dense_array_elems");
}
if (trc == trc->context->runtime->gcMarkingTracer &&
holes > MIN_SPARSE_INDEX &&
holes > capacity / 4 * 3) {
/* This might fail, in which case we don't slowify it. */
reinterpret_cast<JSGCTracer *>(trc)->arraysToSlowify.append(obj);
}
}
extern JSObjectOps js_ArrayObjectOps;
@@ -1381,19 +1392,8 @@ array_toLocaleString(JSContext *cx, uintN argc, Value *vp)
return array_toString_sub(cx, obj, JS_TRUE, NULL, vp);
}
enum TargetElementsType {
TargetElementsAllHoles,
TargetElementsMayContainValues
};
enum SourceVectorType {
SourceVectorAllValues,
SourceVectorMayContainHoles
};
static JSBool
InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, Value *vector,
TargetElementsType targetType, SourceVectorType vectorType)
InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, Value *vector)
{
JS_ASSERT(count < MAXINDEX);
@@ -1404,54 +1404,16 @@ InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, Valu
if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
start <= MAXINDEX - count && !INDEX_TOO_BIG(start + count)) {
#ifdef DEBUG_jwalden
{
/* Verify that overwriteType and writeType were accurate. */
AutoIdRooter idr(cx);
for (jsuint i = 0; i < count; i++) {
JS_ASSERT_IF(vectorType == SourceVectorAllValues, !vector[i].isMagic(JS_ARRAY_HOLE));
jsdouble index = jsdouble(start) + i;
if (targetType == TargetElementsAllHoles && index < jsuint(-1)) {
JS_ASSERT(ReallyBigIndexToId(cx, index, idr.addr()));
JSObject* obj2;
JSProperty* prop;
JS_ASSERT(obj->lookupProperty(cx, idr.id(), &obj2, &prop));
JS_ASSERT(!prop);
}
}
}
#endif
jsuint newlen = start + count;
JS_ASSERT(jsdouble(start) + count == jsdouble(newlen));
if (!obj->ensureDenseArrayElements(cx, newlen))
return JS_FALSE;
if (newlen > obj->getArrayLength())
obj->setDenseArrayLength(newlen);
obj->setArrayLength(newlen);
JS_ASSERT(count < uint32(-1) / sizeof(Value));
if (targetType == TargetElementsMayContainValues) {
jsuint valueCount = 0;
for (jsuint i = 0; i < count; i++) {
if (!obj->getDenseArrayElement(start + i).isMagic(JS_ARRAY_HOLE))
valueCount++;
}
JS_ASSERT(obj->getDenseArrayCount() >= valueCount);
obj->decDenseArrayCountBy(valueCount);
}
memcpy(obj->getDenseArrayElements() + start, vector, sizeof(jsval) * count);
if (vectorType == SourceVectorAllValues) {
obj->incDenseArrayCountBy(count);
} else {
jsuint valueCount = 0;
for (jsuint i = 0; i < count; i++) {
if (!obj->getDenseArrayElement(start + i).isMagic(JS_ARRAY_HOLE))
valueCount++;
}
obj->incDenseArrayCountBy(valueCount);
}
JS_ASSERT_IF(count != 0, !obj->getDenseArrayElement(newlen - 1).isMagic(JS_ARRAY_HOLE));
return JS_TRUE;
}
@@ -1488,34 +1450,21 @@ InitArrayElements(JSContext *cx, JSObject *obj, jsuint start, jsuint count, Valu
}
static JSBool
InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector,
bool holey = false)
InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector)
{
JS_ASSERT(obj->isArray());
if (vector) {
JS_ASSERT(obj->isDenseArray());
obj->setDenseArrayLength(length);
obj->setArrayLength(length);
if (!obj->ensureDenseArrayElements(cx, length))
return JS_FALSE;
jsuint count = length;
if (!holey) {
memcpy(obj->getDenseArrayElements(), vector, length * sizeof(Value));
} else {
for (jsuint i = 0; i < length; i++) {
if (vector[i].isMagic(JS_ARRAY_HOLE))
--count;
obj->setDenseArrayElement(i, vector[i]);
}
}
obj->setDenseArrayCount(count);
memcpy(obj->getDenseArrayElements(), vector, length * sizeof(Value));
} else {
if (obj->isDenseArray()) {
obj->setDenseArrayLength(length);
obj->setDenseArrayCount(0);
obj->setArrayLength(length);
} else {
obj->setSlowArrayLength(length);
obj->setArrayLength(length);
}
}
return JS_TRUE;
@@ -2043,10 +1992,8 @@ array_sort(JSContext *cx, uintN argc, Value *vp)
* InitArrayElements easier.
*/
tvr.changeLength(newlen);
if (!InitArrayElements(cx, obj, 0, newlen, vec, TargetElementsMayContainValues,
SourceVectorAllValues)) {
if (!InitArrayElements(cx, obj, 0, newlen, vec))
return false;
}
}
/* Set undefs that sorted after the rest of elements. */
@@ -2077,10 +2024,8 @@ array_push_slowly(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *
if (!js_GetLengthProperty(cx, obj, &length))
return JS_FALSE;
if (!InitArrayElements(cx, obj, length, argc, argv, TargetElementsMayContainValues,
SourceVectorAllValues)) {
if (!InitArrayElements(cx, obj, length, argc, argv))
return JS_FALSE;
}
/* Per ECMA-262, return the new array length. */
jsdouble newlength = length + jsdouble(argc);
@@ -2101,10 +2046,9 @@ array_push1_dense(JSContext* cx, JSObject* obj, const Value &v, Value *rval)
if (!obj->ensureDenseArrayElements(cx, length + 1))
return JS_FALSE;
obj->setDenseArrayLength(length + 1);
obj->setArrayLength(length + 1);
JS_ASSERT(obj->getDenseArrayElement(length).isMagic(JS_ARRAY_HOLE));
obj->incDenseArrayCountBy(1);
obj->setDenseArrayElement(length, v);
rval->setNumber(obj->getArrayLength());
return JS_TRUE;
@@ -2127,8 +2071,7 @@ ArrayCompPushImpl(JSContext *cx, JSObject *obj, const Value &v)
if (!obj->ensureDenseArrayElements(cx, length + 1))
return JS_FALSE;
}
obj->setDenseArrayLength(length + 1);
obj->incDenseArrayCountBy(1);
obj->setArrayLength(length + 1);
obj->setDenseArrayElement(length, v);
return JS_TRUE;
}
@@ -2198,7 +2141,7 @@ array_pop_dense(JSContext *cx, JSObject* obj, Value *vp)
return JS_FALSE;
if (!hole && !DeleteArrayElement(cx, obj, index))
return JS_FALSE;
obj->setDenseArrayLength(index);
obj->setArrayLength(index);
return JS_TRUE;
}
@@ -2233,12 +2176,10 @@ array_shift(JSContext *cx, uintN argc, Value *vp)
*vp = obj->getDenseArrayElement(0);
if (vp->isMagic(JS_ARRAY_HOLE))
vp->setUndefined();
else
obj->decDenseArrayCountBy(1);
Value *elems = obj->getDenseArrayElements();
memmove(elems, elems + 1, length * sizeof(jsval));
obj->setDenseArrayElement(length, MagicValue(JS_ARRAY_HOLE));
obj->setDenseArrayLength(length);
obj->setArrayLength(length);
return JS_TRUE;
}
@@ -2304,7 +2245,7 @@ array_unshift(JSContext *cx, uintN argc, Value *vp)
}
/* Copy from argv to the bottom of the array. */
if (!InitArrayElements(cx, obj, 0, argc, argv, TargetElementsAllHoles, SourceVectorAllValues))
if (!InitArrayElements(cx, obj, 0, argc, argv))
return JS_FALSE;
newlen += argc;
@@ -2384,10 +2325,8 @@ array_splice(JSContext *cx, uintN argc, Value *vp)
if (obj->isDenseArray() && !js_PrototypeHasIndexedProperties(cx, obj) &&
!js_PrototypeHasIndexedProperties(cx, obj2) &&
end <= obj->getDenseArrayCapacity()) {
if (!InitArrayObject(cx, obj2, count, obj->getDenseArrayElements() + begin,
obj->getDenseArrayCount() != obj->getArrayLength())) {
if (!InitArrayObject(cx, obj2, count, obj->getDenseArrayElements() + begin))
return JS_FALSE;
}
} else {
for (last = begin; last < end; last++) {
if (!JS_CHECK_OPERATION_LIMIT(cx) ||
@@ -2419,14 +2358,10 @@ array_splice(JSContext *cx, uintN argc, Value *vp)
Value *srcbeg = arraybeg + last - 1;
Value *srcend = arraybeg + end - 1;
Value *dstbeg = srcbeg + delta;
for (Value *src = srcbeg, *dst = dstbeg; src > srcend; --src, --dst) {
Value srcval = *src;
if (JS_UNLIKELY(!srcval.isMagic(JS_ARRAY_HOLE) && dst->isMagic(JS_ARRAY_HOLE)))
obj->incDenseArrayCountBy(1);
*dst = srcval;
}
for (Value *src = srcbeg, *dst = dstbeg; src > srcend; --src, --dst)
*dst = *src;
obj->setDenseArrayLength(obj->getArrayLength() + delta);
obj->setArrayLength(obj->getArrayLength() + delta);
} else {
/* (uint) end could be 0, so we can't use a vanilla >= test. */
while (last-- > end) {
@@ -2447,12 +2382,8 @@ array_splice(JSContext *cx, uintN argc, Value *vp)
Value *srcbeg = arraybeg + end;
Value *srcend = arraybeg + length;
Value *dstbeg = srcbeg - delta;
for (Value *src = srcbeg, *dst = dstbeg; src < srcend; ++src, ++dst) {
Value srcval = *src;
if (JS_UNLIKELY(!srcval.isMagic(JS_ARRAY_HOLE) && dst->isMagic(JS_ARRAY_HOLE)))
obj->incDenseArrayCountBy(1);
*dst = srcval;
}
for (Value *src = srcbeg, *dst = dstbeg; src < srcend; ++src, ++dst)
*dst = *src;
} else {
for (last = end; last < length; last++) {
if (!JS_CHECK_OPERATION_LIMIT(cx) ||
@@ -2469,8 +2400,7 @@ array_splice(JSContext *cx, uintN argc, Value *vp)
* Copy from argv into the hole to complete the splice, and update length in
* case we deleted elements from the end.
*/
return InitArrayElements(cx, obj, begin, argc, argv, TargetElementsMayContainValues,
SourceVectorAllValues) &&
return InitArrayElements(cx, obj, begin, argc, argv) &&
js_SetLengthProperty(cx, obj, length);
}
@@ -2498,11 +2428,10 @@ array_concat(JSContext *cx, uintN argc, Value *vp)
*/
length = aobj->getArrayLength();
jsuint capacity = aobj->getDenseArrayCapacity();
nobj = js_NewArrayObject(cx, JS_MIN(length, capacity), aobj->getDenseArrayElements(),
aobj->getDenseArrayCount() != length);
nobj = js_NewArrayObject(cx, JS_MIN(length, capacity), aobj->getDenseArrayElements());
if (!nobj)
return JS_FALSE;
nobj->setDenseArrayLength(length);
nobj->setArrayLength(length);
vp->setObject(*nobj);
if (argc == 0)
return JS_TRUE;
@@ -2614,8 +2543,7 @@ array_slice(JSContext *cx, uintN argc, Value *vp)
if (obj->isDenseArray() && end <= obj->getDenseArrayCapacity() &&
!js_PrototypeHasIndexedProperties(cx, obj)) {
nobj = js_NewArrayObject(cx, end - begin, obj->getDenseArrayElements() + begin,
obj->getDenseArrayCount() != obj->getArrayLength());
nobj = js_NewArrayObject(cx, end - begin, obj->getDenseArrayElements() + begin);
if (!nobj)
return JS_FALSE;
vp->setObject(*nobj);
@@ -3070,8 +2998,7 @@ js_NewEmptyArray(JSContext* cx, JSObject* proto)
obj->map = const_cast<JSObjectMap *>(&SharedArrayMap);
obj->init(&js_ArrayClass, proto, proto->getParent(), NullValue());
obj->setDenseArrayLength(0);
obj->setDenseArrayCount(0);
obj->setArrayLength(0);
return obj;
}
#ifdef JS_TRACER
@@ -3086,7 +3013,7 @@ js_NewEmptyArrayWithLength(JSContext* cx, JSObject* proto, int32 len)
JSObject *obj = js_NewEmptyArray(cx, proto);
if (!obj)
return NULL;
obj->setDenseArrayLength(len);
obj->setArrayLength(len);
return obj;
}
#ifdef JS_TRACER
@@ -3100,7 +3027,7 @@ js_NewArrayWithSlots(JSContext* cx, JSObject* proto, uint32 len)
JSObject* obj = js_NewEmptyArray(cx, proto);
if (!obj)
return NULL;
obj->setDenseArrayLength(len);
obj->setArrayLength(len);
if (!obj->resizeDenseArrayElements(cx, 0, JS_MAX(len, ARRAY_CAPACITY_MIN)))
return NULL;
return obj;
@@ -3123,7 +3050,7 @@ js_InitArrayClass(JSContext *cx, JSObject *obj)
}
JSObject *
js_NewArrayObject(JSContext *cx, jsuint length, const Value *vector, bool holey)
js_NewArrayObject(JSContext *cx, jsuint length, const Value *vector)
{
JSObject *obj = NewDenseArrayObject(cx);
if (!obj)
@@ -3137,7 +3064,7 @@ js_NewArrayObject(JSContext *cx, jsuint length, const Value *vector, bool holey)
{
AutoObjectRooter tvr(cx, obj);
if (!InitArrayObject(cx, obj, length, vector, holey))
if (!InitArrayObject(cx, obj, length, vector))
obj = NULL;
}
@@ -3151,7 +3078,7 @@ js_NewSlowArrayObject(JSContext *cx)
{
JSObject *obj = NewObject(cx, &js_SlowArrayClass, NULL, NULL);
if (obj)
obj->setSlowArrayLength(0);
obj->setArrayLength(0);
return obj;
}
@@ -3178,8 +3105,7 @@ js_ArrayInfo(JSContext *cx, JSObject *obj, uintN argc, Value *argv, Value *rval)
array->isDenseArray()) ? "dense" : "sparse",
array->getArrayLength());
if (array->isDenseArray()) {
fprintf(stderr, ", count %lu, capacity %lu",
array->getDenseArrayCount(),
fprintf(stderr, ", capacity %lu",
array->getDenseArrayCapacity());
}
fputs(")\n", stderr);
@@ -3263,7 +3189,6 @@ js_NewArrayObjectWithCapacity(JSContext *cx, uint32_t capacity, jsval **vector)
if (!obj)
return NULL;
obj->setDenseArrayCount(capacity);
*vector = Jsvalify(obj->getDenseArrayElements());
return obj;
}
@@ -3311,8 +3236,6 @@ js_CloneDensePrimitiveArray(JSContext *cx, JSObject *obj, JSObject **clone)
if (!vector.reserve(jsvalCount))
return JS_FALSE;
jsuint holeCount = 0;
for (jsuint i = 0; i < jsvalCount; i++) {
const Value &val = obj->dslots[i];
@@ -3320,8 +3243,6 @@ js_CloneDensePrimitiveArray(JSContext *cx, JSObject *obj, JSObject **clone)
// Strings must be made immutable before being copied to a clone.
if (!js_MakeStringImmutable(cx, val.toString()))
return JS_FALSE;
} else if (val.isMagic(JS_ARRAY_HOLE)) {
holeCount++;
} else if (val.isObject()) {
/*
* This wasn't an array of primitives. Return JS_TRUE but a null
@@ -3342,8 +3263,7 @@ js_CloneDensePrimitiveArray(JSContext *cx, JSObject *obj, JSObject **clone)
AutoObjectRooter cloneRoot(cx, *clone);
memcpy(buffer, vector.begin(), jsvalCount * sizeof (jsval));
(*clone)->setDenseArrayLength(length);
(*clone)->setDenseArrayCount(length - holeCount);
(*clone)->setArrayLength(length);
return JS_TRUE;
}