Flexible length JSObject, bug 584917. r=brendan,igor

This commit is contained in:
Brian Hackett
2010-10-13 11:49:22 -07:00
parent f33cac8c13
commit 462c3fd58a
48 changed files with 1286 additions and 1367 deletions

View File

@@ -312,109 +312,6 @@ BigIndexToId(JSContext *cx, JSObject *obj, jsuint index, JSBool createAtom,
return JS_TRUE;
}
bool
JSObject::growDenseArrayElements(JSContext *cx, uint32 oldcap, uint32 newcap)
{
JS_ASSERT(isDenseArray());
JS_ASSERT(newcap >= ARRAY_CAPACITY_MIN);
JS_ASSERT(newcap >= oldcap);
if (newcap > MAX_DSLOTS_LENGTH32) {
if (!JS_ON_TRACE(cx))
js_ReportAllocationOverflow(cx);
return JS_FALSE;
}
/* dslots can be NULL during array creation. */
Value *slots = dslots ? dslots - 1 : NULL;
Value *newslots = (Value *) cx->realloc(slots, (size_t(newcap) + 1) * sizeof(Value));
if (!newslots)
return false;
dslots = newslots + 1;
setDenseArrayCapacity(newcap);
Value *base = addressOfDenseArrayElement(0);
for (Value *vp = base + oldcap, *end = base + newcap; vp < end; ++vp)
vp->setMagic(JS_ARRAY_HOLE);
return true;
}
bool
JSObject::ensureDenseArrayElements(JSContext *cx, uint32 newcap)
{
/*
* When a dense array with CAPACITY_DOUBLING_MAX or fewer slots needs to
* grow, double its capacity, to push() N elements in amortized O(N) time.
*
* Above this limit, grow by 12.5% each time. Speed is still amortized
* O(N), with a higher constant factor, and we waste less space.
*/
static const size_t CAPACITY_DOUBLING_MAX = 1024 * 1024;
/*
* Round up all large allocations to a multiple of this (1MB), so as not
* to waste space if malloc gives us 1MB-sized chunks (as jemalloc does).
*/
static const size_t CAPACITY_CHUNK = 1024 * 1024 / sizeof(Value);
uint32 oldcap = getDenseArrayCapacity();
if (newcap > oldcap) {
/*
* If this overflows uint32, newcap is very large. nextsize will end
* up being less than newcap, the code below will thus disregard it,
* and resizeDenseArrayElements() will fail.
*
* The way we use dslots[-1] forces a few +1s and -1s here. For
* example, (oldcap * 2 + 1) produces the sequence 7, 15, 31, 63, ...
* which makes the total allocation size (with dslots[-1]) a power
* of two.
*/
uint32 nextsize = (oldcap <= CAPACITY_DOUBLING_MAX)
? oldcap * 2 + 1
: oldcap + (oldcap >> 3);
uint32 actualCapacity = JS_MAX(newcap, nextsize);
if (actualCapacity >= CAPACITY_CHUNK)
actualCapacity = JS_ROUNDUP(actualCapacity + 1, CAPACITY_CHUNK) - 1; /* -1 for dslots[-1] */
else if (actualCapacity < ARRAY_CAPACITY_MIN)
actualCapacity = ARRAY_CAPACITY_MIN;
if (!growDenseArrayElements(cx, oldcap, actualCapacity))
return false;
}
return true;
}
bool
JSObject::shrinkDenseArrayElements(JSContext *cx, uint32 newcap)
{
JS_ASSERT(isDenseArray());
JS_ASSERT(newcap < getDenseArrayCapacity());
JS_ASSERT(dslots);
uint32 fill = newcap;
if (newcap < ARRAY_CAPACITY_MIN)
newcap = ARRAY_CAPACITY_MIN;
Value *newslots = (Value *) cx->realloc(dslots - 1, (size_t(newcap) + 1) * sizeof(Value));
if (!newslots)
return false;
dslots = newslots + 1;
setDenseArrayCapacity(newcap);
/* we refuse to shrink below a minimum value, so we have to clear the excess space */
Value *base = addressOfDenseArrayElement(0);
while (fill < newcap)
base[fill++].setMagic(JS_ARRAY_HOLE);
return true;
}
static bool
ReallyBigIndexToId(JSContext* cx, jsdouble index, jsid* idp)
{
@@ -542,8 +439,8 @@ js_EnsureDenseArrayCapacity(JSContext *cx, JSObject *obj, jsint i)
return true;
if (INDEX_TOO_SPARSE(obj, u))
return false;
return obj->ensureDenseArrayElements(cx, u + 1);
return obj->ensureDenseArrayElements(cx, u + 1);
}
JS_DEFINE_CALLINFO_3(extern, BOOL, js_EnsureDenseArrayCapacity, CONTEXT, OBJECT, INT32, 0,
nanojit::ACCSET_STORE_ANY)
@@ -672,13 +569,13 @@ array_length_setter(JSContext *cx, JSObject *obj, jsid id, Value *vp, JSBool str
if (obj->isDenseArray()) {
/*
* Don't reallocate if we're not actually shrinking our slots. If we do
* shrink slots here, resizeDenseArrayElements will fill all slots to the
* shrink slots here, ensureDenseArrayElements 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 oldcap = obj->getDenseArrayCapacity();
if (oldcap > newlen && !obj->shrinkDenseArrayElements(cx, newlen))
return false;
if (oldcap > newlen)
obj->shrinkDenseArrayElements(cx, newlen);
obj->setArrayLength(newlen);
} else if (oldlen - newlen < (1 << 24)) {
do {
@@ -986,20 +883,11 @@ array_deleteProperty(JSContext *cx, JSObject *obj, jsid id, Value *rval, JSBool
return JS_TRUE;
}
static void
array_finalize(JSContext *cx, JSObject *obj)
{
obj->freeDenseArrayElements(cx);
}
static void
array_trace(JSTracer *trc, JSObject *obj)
{
JS_ASSERT(obj->isDenseArray());
if (!obj->dslots)
return;
size_t holes = 0;
uint32 capacity = obj->getDenseArrayCapacity();
for (uint32 i = 0; i < capacity; i++) {
@@ -1040,7 +928,7 @@ array_fix(JSContext *cx, JSObject *obj, bool *success, AutoIdVector *props)
Class js_ArrayClass = {
"Array",
Class::NON_NATIVE |
JSCLASS_HAS_RESERVED_SLOTS(JSObject::DENSE_ARRAY_CLASS_RESERVED_SLOTS) |
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_CACHED_PROTO(JSProto_Array),
PropertyStub, /* addProperty */
PropertyStub, /* delProperty */
@@ -1049,7 +937,7 @@ Class js_ArrayClass = {
EnumerateStub,
ResolveStub,
js_TryValueOf,
array_finalize,
NULL,
NULL, /* reserved0 */
NULL, /* checkAccess */
NULL, /* call */
@@ -1134,26 +1022,18 @@ JSObject::makeDenseArraySlow(JSContext *cx)
* the same initial shape.
*/
JSObject *arrayProto = getProto();
if (!InitScopeForObject(cx, this, &js_SlowArrayClass, arrayProto))
if (!InitScopeForObject(cx, this, &js_SlowArrayClass, arrayProto, FINALIZE_OBJECT0))
return false;
uint32 capacity;
uint32 capacity = getDenseArrayCapacity();
if (dslots) {
capacity = getDenseArrayCapacity();
dslots[-1].setPrivateUint32(JS_INITIAL_NSLOTS + capacity);
} else {
/*
* Array.prototype is constructed as a dense array, but is immediately slowified before
* we have time to set capacity.
*/
capacity = 0;
}
/* Begin with the length property to share more of the property tree. */
/*
* Begin with the length property to share more of the property tree.
* The getter/setter here will directly access the object's private value.
*/
if (!addProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lengthAtom),
array_length_getter, NULL,
JSSLOT_ARRAY_LENGTH, JSPROP_PERMANENT | JSPROP_SHARED, 0, 0)) {
SHAPE_INVALID_SLOT, JSPROP_PERMANENT | JSPROP_SHARED, 0, 0)) {
setMap(oldMap);
return false;
}
@@ -1171,24 +1051,12 @@ JSObject::makeDenseArraySlow(JSContext *cx)
continue;
}
/* Assert that the length covering i fits in the alloted bits. */
JS_ASSERT(JS_INITIAL_NSLOTS + i + 1 < NSLOTS_LIMIT);
if (!addDataProperty(cx, id, JS_INITIAL_NSLOTS + i, JSPROP_ENUMERATE)) {
if (!addDataProperty(cx, id, i, JSPROP_ENUMERATE)) {
setMap(oldMap);
return false;
}
}
/*
* Render our formerly-reserved non-private properties GC-safe. We do not
* need to make the length slot GC-safe because it is the private slot
* (this is statically asserted within JSObject) where the implementation
* can store an arbitrary value.
*/
JS_ASSERT(js_SlowArrayClass.flags & JSCLASS_HAS_PRIVATE);
voidDenseOnlyArraySlots();
/*
* Finally, update class. If |this| is Array.prototype, then js_InitClass
* will create an emptyShape whose class is &js_SlowArrayClass, to ensure
@@ -1530,7 +1398,6 @@ InitArrayObject(JSContext *cx, JSObject *obj, jsuint length, const Value *vector
JS_ASSERT(obj->isDenseArray());
obj->setArrayLength(length);
obj->setDenseArrayCapacity(0);
if (!vector || !length)
return true;
if (!obj->ensureDenseArrayElements(cx, length))
@@ -2488,10 +2355,8 @@ array_concat(JSContext *cx, uintN argc, Value *vp)
/*
* Clone aobj but pass the minimum of its length and capacity, to
* handle a = [1,2,3]; a.length = 10000 "dense" cases efficiently. In
* such a case we'll pass 8 (not 3) due to ARRAY_CAPACITY_MIN, which
* will cause nobj to be over-allocated to 16. But in the normal case
* where length is <= capacity, nobj and aobj will have the same
* capacity.
* the normal case where length is <= capacity, nobj and aobj will have
* the same capacity.
*/
length = aobj->getArrayLength();
jsuint capacity = aobj->getDenseArrayCapacity();
@@ -3000,10 +2865,12 @@ static JSFunctionSpec array_static_methods[] = {
JS_FS_END
};
/* The count here is a guess for the final capacity. */
static inline JSObject *
NewDenseArrayObject(JSContext *cx)
NewDenseArrayObject(JSContext *cx, jsuint count)
{
return NewNonFunction<WithProto::Class>(cx, &js_ArrayClass, NULL, NULL);
gc::FinalizeKind kind = GuessObjectGCKind(count, true);
return NewNonFunction<WithProto::Class>(cx, &js_ArrayClass, NULL, NULL, kind);
}
JSBool
@@ -3012,12 +2879,6 @@ js_Array(JSContext *cx, uintN argc, Value *vp)
jsuint length;
const Value *vector;
/* Whether called with 'new' or not, use a new Array object. */
JSObject *obj = NewDenseArrayObject(cx);
if (!obj)
return JS_FALSE;
vp->setObject(*obj);
if (argc == 0) {
length = 0;
vector = NULL;
@@ -3034,6 +2895,12 @@ js_Array(JSContext *cx, uintN argc, Value *vp)
vector = NULL;
}
/* Whether called with 'new' or not, use a new Array object. */
JSObject *obj = NewDenseArrayObject(cx, length);
if (!obj)
return JS_FALSE;
vp->setObject(*obj);
return InitArrayObject(cx, obj, length, vector);
}
@@ -3045,15 +2912,15 @@ js_NewEmptyArray(JSContext* cx, JSObject* proto, int32 len)
JS_ASSERT(proto->isArray());
JSObject* obj = js_NewGCObject(cx);
gc::FinalizeKind kind = GuessObjectGCKind(len, true);
JSObject* obj = js_NewGCObject(cx, kind);
if (!obj)
return NULL;
/* Initialize all fields, calling init before setting obj->map. */
obj->init(&js_ArrayClass, proto, proto->getParent(), NullValue(), cx);
/* Initialize all fields of JSObject. */
obj->init(cx, &js_ArrayClass, proto, proto->getParent(),
(void*) len, true);
obj->setSharedNonNativeMap();
obj->setArrayLength(len);
obj->setDenseArrayCapacity(0);
return obj;
}
#ifdef JS_TRACER
@@ -3067,7 +2934,7 @@ js_NewPreallocatedArray(JSContext* cx, JSObject* proto, int32 len)
JSObject *obj = js_NewEmptyArray(cx, proto, len);
if (!obj)
return NULL;
if (!obj->growDenseArrayElements(cx, 0, JS_MAX(len, ARRAY_CAPACITY_MIN)))
if (!obj->ensureDenseArrayElements(cx, len))
return NULL;
return obj;
}
@@ -3076,6 +2943,17 @@ JS_DEFINE_CALLINFO_3(extern, OBJECT, js_NewPreallocatedArray, CONTEXT, OBJECT, I
0, nanojit::ACCSET_STORE_ANY)
#endif
JSObject* JS_FASTCALL
js_InitializerArray(JSContext* cx, int32 count)
{
gc::FinalizeKind kind = GuessObjectGCKind(count, true);
return NewArrayWithKind(cx, kind);
}
#ifdef JS_TRACER
JS_DEFINE_CALLINFO_2(extern, OBJECT, js_InitializerArray, CONTEXT, INT32, 0,
nanojit::ACCSET_STORE_ANY)
#endif
JSObject *
js_InitArrayClass(JSContext *cx, JSObject *obj)
{
@@ -3088,7 +2966,7 @@ js_InitArrayClass(JSContext *cx, JSObject *obj)
* Assert that js_InitClass used the correct (slow array, not dense array)
* class for proto's emptyShape class.
*/
JS_ASSERT(proto->emptyShape->getClass() == proto->getClass());
JS_ASSERT(proto->emptyShapes && proto->emptyShapes[0]->getClass() == proto->getClass());
proto->setArrayLength(0);
return proto;
@@ -3097,7 +2975,7 @@ js_InitArrayClass(JSContext *cx, JSObject *obj)
JSObject *
js_NewArrayObject(JSContext *cx, jsuint length, const Value *vector)
{
JSObject *obj = NewDenseArrayObject(cx);
JSObject *obj = NewDenseArrayObject(cx, length);
if (!obj)
return NULL;
@@ -3220,7 +3098,7 @@ js_IsDensePrimitiveArray(JSObject *obj)
jsuint capacity = obj->getDenseArrayCapacity();
for (jsuint i = 0; i < capacity; i++) {
if (obj->dslots[i].isObject())
if (obj->getDenseArrayElement(i).isObject())
return JS_FALSE;
}
@@ -3244,10 +3122,9 @@ js_CloneDensePrimitiveArray(JSContext *cx, JSObject *obj, JSObject **clone)
/*
* Must use the minimum of original array's length and capacity, to handle
* |a = [1,2,3]; a.length = 10000| "dense" cases efficiently. In such a case
* we would use ARRAY_CAPACITY_MIN (not 3), which will cause the clone to be
* over-allocated. In the normal case where length is <= capacity the
* clone and original array will have the same capacity.
* |a = [1,2,3]; a.length = 10000| "dense" cases efficiently. In the normal
* case where length is <= capacity, the clone and original array will have
* the same capacity.
*/
jsuint jsvalCount = JS_MIN(obj->getDenseArrayCapacity(), length);
@@ -3256,7 +3133,7 @@ js_CloneDensePrimitiveArray(JSContext *cx, JSObject *obj, JSObject **clone)
return JS_FALSE;
for (jsuint i = 0; i < jsvalCount; i++) {
const Value &val = obj->dslots[i];
const Value &val = obj->getDenseArrayElement(i);
if (val.isString()) {
// Strings must be made immutable before being copied to a clone.