Array Type Specialization diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -46,16 +46,17 @@ * methods optimize for denseness by testing that the object's class is * &js_ArrayClass, and can then directly manipulate the slots for efficiency. * * We track these pieces of metadata for arrays in dense mode: * - the array's length property as a uint32, in JSSLOT_ARRAY_LENGTH, * - the number of indices that are filled (non-holes), in JSSLOT_ARRAY_COUNT, * - the net number of slots starting at dslots (capacity), in dslots[-1] if * dslots is non-NULL. + * - the array's specialization in fslots[JSSLOT_ARRAY_SPEC] if it is a specialized array * * In dense mode, holes in the array are represented by JSVAL_HOLE. The final * slot in fslots is 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 * array_length_setter for an explanation of how the first, most surprising * case may occur. @@ -118,16 +119,268 @@ INDEX_TOO_BIG(jsuint index) ((index) > js_DenseArrayCapacity(array) && (index) >= MIN_SPARSE_INDEX && \ (index) > (uint32)((array)->fslots[JSSLOT_ARRAY_COUNT] + 1) * 4)) JS_STATIC_ASSERT(sizeof(JSScopeProperty) > 4 * sizeof(jsval)); #define ENSURE_SLOW_ARRAY(cx, obj) \ (OBJ_GET_CLASS(cx, obj) == &js_SlowArrayClass || js_MakeArraySlow(cx, obj)) +// Forward definition for storeNativeValue +static JSBool +SetArrayElement(JSContext *cx, JSObject *obj, jsdouble index, jsval v); + +// This specialization appeases the linker +// ArraySpec::promoteToHoldValue references this specialization +// but it will never be called so it is actually dead code +template <> +class ArraySpecConversion +{ +public: + // Used by js_ArrayToJSDoubleBuffer only + static JSBool + copy(JSContext *cx, jsdouble *tSlots, jsdouble *sSlots, uint32 size) { + memcpy(tSlots, sSlots, size * sizeof(jsdouble)); + return JS_TRUE; + } + + static void updateObject(JSObject *obj) { + JS_ASSERT(JS_FALSE && "double to double array conversion unexpected"); + } +}; + + +template +size_t ArraySpec::value_size = sizeof(T); + +// TODO: pick a better number...JSVAL_HOLE? +template <> +const uint32 ArraySpec::hole_value = 0x7197a9fd; + +template <> +const jsval ArraySpec::hole_value = JSVAL_HOLE; + +// sign = 1, exponent = 0xFF, mantissa = 0 +// this is a signalling NaN, assumes 64 bit jsdoubles +static JSUint8 jsdouble_hole[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x7F }; + +template <> +const jsdouble ArraySpec::hole_value = *(jsdouble *) jsdouble_hole; + +template <> +JSBool ArraySpec::storeNativeValue(JSContext *cx, JSObject *obj, jsuint + idx, const uint32& v) { + if (v == hole_value) { + if (!promoteTo(cx, obj)) + return JS_FALSE; + ArraySpec::storeRawValue(obj, idx, static_cast(v)); + } else { + storeRawValue(obj, idx, v); + } + return JS_TRUE; +} + +template <> +JSBool ArraySpec::storeNativeValue(JSContext *cx, JSObject *obj, jsuint + idx, const jsdouble& v) { + if (v == hole_value) { + if (!promoteTo(cx, obj)) + return JS_FALSE; + jsval jv; + if (!toJSVal(cx, v, &jv)) + return JS_FALSE; + ArraySpec::storeRawValue(obj, idx, jv); + } else { + storeRawValue(obj, idx, v); + } + return JS_TRUE; +} + +template <> +JSBool ArraySpec::storeNativeValue(JSContext *cx, JSObject *obj, jsuint + idx, const jsval& v) { + storeRawValue(obj, idx, v); + return JS_TRUE; +} + +template +template +JSBool +ArraySpec::promoteTo(JSContext *cx, JSObject *obj) { + jsval *slots, *newslots; + uint32 size; + ArraySpecConversion::updateObject(obj); + slots = obj->dslots ? obj->dslots - 1 : NULL; + if (!slots) return JS_TRUE; + size = js_DenseArrayCapacity(obj); + + if (sizeof(TargetType) > sizeof(T)) { + newslots = (jsval *) JS_realloc(cx, slots, sizeof(TargetType) * (size + 1)); + if (!newslots) return JS_FALSE; + obj->dslots = slots = newslots + 1; + + TargetType *tSlots = reinterpret_cast(slots); + T *sSlots = reinterpret_cast(slots); + return ArraySpecConversion::copy(cx, tSlots, + sSlots, size); + } else { + TargetType *tSlots = reinterpret_cast(slots+1); + T *sSlots = reinterpret_cast(slots+1); + if (!ArraySpecConversion::copy(cx, tSlots, + sSlots, size)) + return JS_FALSE; + + newslots = (jsval *) JS_realloc(cx, slots, sizeof(TargetType) * (size + 1)); + if (!newslots) return JS_FALSE; + obj->dslots = newslots + 1; + return JS_TRUE; + } +} + +template +JSBool +ArraySpec::promoteToHoldValue(JSContext *cx, JSObject *obj, + jsuint idx, jsval v) { + JS_ASSERT(!JSVAL_IS_INT(v)); + if (JSVAL_IS_DOUBLE(v) && ArraySpec::hole_value != + *JSVAL_TO_DOUBLE(v)) { + // Promote to double + if (!promoteTo(cx, obj)) + return JS_FALSE; + ArraySpec::storeRawValue(obj, idx, *JSVAL_TO_DOUBLE(v)); + } else { + // Promote to jsval + if (!promoteTo(cx, obj)) + return JS_FALSE; + ArraySpec::storeRawValue(obj, idx, v); + } + return JS_TRUE; +} + +template <> +JSBool +ArraySpec::promoteToHoldValue(JSContext *cx, JSObject *obj, jsuint + idx, jsval v) { + return v == JSVAL_HOLE + ? SetArrayElement(cx, obj, idx, v) + : JS_TRUE; +} + +// TODO: more comments! +template +JSBool +ArraySpec::copyFromVector(JSContext *cx, JSObject *obj, jsuint start, + jsuint count, jsval *vector) { + // Try to copy every element from vector[:count] to obj[start:start+count] + JSBool promoted = JS_FALSE; + for (jsuint i = 0; i < count; i++) { + if (!storeValue(cx, obj, start+i, vector[i], &promoted)) + return JS_FALSE; + if (promoted) { + // i + 1 is the number of elements copied so far + return JSARRAY_TYPEDISPATCH(obj, copyFromVector(cx, obj, + start + i + 1, + count - i - 1, + vector + i + 1)); + } + } + return JS_TRUE; +} + +template <> +JSBool +ArraySpec::copyFromVector(JSContext *cx, JSObject *obj, jsuint + start, jsuint count, jsval *vector) { + memcpy(obj->dslots + start, vector, sizeof(jsval) * count); + return JS_TRUE; +} + +static JSBool +EnsureCapacity(JSContext *cx, JSObject *obj, uint32 capacity); + +template +JSBool +ArraySpec::setelem(JSContext* cx, JSObject* obj, jsint i, T v) { + JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj)); + + /* + * Let the interpreter worry about negative array indexes. + */ + JS_ASSERT((MAX_DSLOTS_LENGTH > JSVAL_INT_MAX) == (sizeof(jsval) != sizeof(uint32))); + if (MAX_DSLOTS_LENGTH > JSVAL_INT_MAX) { + /* + * Have to check for negative values bleeding through on 64-bit machines only, + * since we can't allocate large enough arrays for this on 32-bit machines. + */ + if (i < 0) + return JS_FALSE; + } + + /* + * If needed, grow the array as long it remains dense, otherwise fall off trace. + */ + jsuint u = jsuint(i); + jsuint capacity = js_DenseArrayCapacity(obj); + if ((u >= capacity) && (INDEX_TOO_SPARSE(obj, u) || !EnsureCapacity(cx, obj, u + 1))) + return JS_FALSE; + + if (hasHole(obj, u)) { + if (js_PrototypeHasIndexedProperties(cx, obj)) + return JS_FALSE; + + if (u >= jsuint(obj->fslots[JSSLOT_ARRAY_LENGTH])) + obj->fslots[JSSLOT_ARRAY_LENGTH] = u + 1; + ++obj->fslots[JSSLOT_ARRAY_COUNT]; + } + + return storeNativeValue(cx, obj, u, v); +} + +template +JSBool +ArraySpecConversion::copy(JSContext *cx, jsval *tSlots, SourceType *sSlots, uint32 size) { + // Need to work front to back for inplace update since + // sizeof(SourceType) > sizeof(jsval) when SourceType == jsdouble + for (uint32 i = 0; i < size; i++) { + SourceType &val = sSlots[i]; + if (val == ArraySpec::hole_value) { + tSlots[i] = JSVAL_HOLE; + } else if (!ArraySpec::toJSVal(cx, val, &(tSlots[i]))) + return JS_FALSE; + } + return JS_TRUE; +} + +template +void +ArraySpecConversion::updateObject(JSObject *obj) { + /* Make sure we preserve any flags borrowing bits in classword. */ + obj->classword ^= (jsuword) &js_ArrayClass; + obj->classword |= (jsuword) &js_DenseArrayClass; +} + +JSBool +ArraySpecConversion::copy(JSContext *cx, jsdouble *tSlots, + uint32 *sSlots, uint32 size) { + // Need to work back to front for inplace update since + // sizeof(jsdouble) > sizeof(uint32) + while (size--) { + int32 val = sSlots[size]; + tSlots[size] = (val == ArraySpec::hole_value) ? + ArraySpec::hole_value : static_cast(val); + } + return JS_TRUE; +} + +void +ArraySpecConversion::updateObject(JSObject *obj) { + JS_ASSERT(JSAS_INT == js_SpecArrayType(obj)); + js_SetSpecArrayType(obj, JSAS_DOUBLE); +} + /* * Determine if the id represents an array index or an XML property index. * * An id is an array index according to ECMA by (15.4): * * "Array objects give special treatment to a certain class of property names. * A property name P (in the form of a string value) is an array index if and * only if ToString(ToUint32(P)) is equal to P and ToUint32(P) is not equal @@ -316,35 +569,46 @@ ResizeSlots(JSContext *cx, JSObject *obj } return JS_TRUE; } /* * MAX_DSLOTS_LENGTH is the maximum net capacity supported. Since we allocate * one additional slot to hold the array length, we have to use >= here. */ + // XXX: fixme for spec arrays? if (size >= MAX_DSLOTS_LENGTH) { js_ReportAllocationOverflow(cx); return JS_FALSE; } slots = obj->dslots ? obj->dslots - 1 : NULL; - newslots = (jsval *) JS_realloc(cx, slots, (size + 1) * sizeof(jsval)); + newslots = (jsval *) JS_realloc(cx, slots, (size + 1) * + JSARRAY_TYPEDISPATCH(obj, value_size)); if (!newslots) return JS_FALSE; obj->dslots = newslots + 1; js_SetDenseArrayCapacity(obj, size); - for (slots = obj->dslots + oldsize; slots < obj->dslots + size; slots++) - *slots = JSVAL_HOLE; + JSARRAY_TYPEDISPATCH(obj, fillSlotsWithHole(obj->dslots, oldsize, size)); return JS_TRUE; } +JSBool +js_ReallocArraySlots(JSContext *cx, JSObject *obj, uint32 nslots, + JSBool exactAllocation) { + uint32 oldsize = OBJ_IS_DENSE_ARRAY(cx, obj) + ? js_DenseArrayCapacity(obj) + : STOBJ_NSLOTS(obj); + return ResizeSlots(cx, obj, oldsize, nslots); +} + + /* * 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. */ #define CAPACITY_DOUBLING_MAX (1024 * 1024) @@ -422,20 +686,23 @@ IndexToId(JSContext* cx, JSObject* obj, * to JSVAL_VOID. This function assumes that the location pointed by vp is * properly rooted and can be used as GC-protected storage for temporaries. */ static JSBool GetArrayElement(JSContext *cx, JSObject *obj, jsdouble index, JSBool *hole, jsval *vp) { JS_ASSERT(index >= 0); - if (OBJ_IS_DENSE_ARRAY(cx, obj) && index < js_DenseArrayCapacity(obj) && - (*vp = obj->dslots[jsuint(index)]) != JSVAL_HOLE) { - *hole = JS_FALSE; - return JS_TRUE; + if (OBJ_IS_DENSE_ARRAY(cx, obj) && index < js_DenseArrayCapacity(obj)) { + jsuint uindex = jsuint(index); + JSARRAY_TYPEDISPATCH(obj, getValue(cx, obj, uindex, vp)); + if (*vp != JSVAL_HOLE) { + *hole = JS_FALSE; + return JS_TRUE; + } } JSAutoTempIdRooter idr(cx); *hole = JS_FALSE; if (!IndexToId(cx, obj, index, hole, idr.addr())) return JS_FALSE; if (*hole) { @@ -472,20 +739,19 @@ SetArrayElement(JSContext *cx, JSObject if (index <= jsuint(-1)) { jsuint idx = jsuint(index); if (!INDEX_TOO_SPARSE(obj, idx)) { JS_ASSERT(idx + 1 > idx); if (!EnsureCapacity(cx, obj, idx + 1)) return JS_FALSE; if (idx >= uint32(obj->fslots[JSSLOT_ARRAY_LENGTH])) obj->fslots[JSSLOT_ARRAY_LENGTH] = idx + 1; - if (obj->dslots[idx] == JSVAL_HOLE) + if (JSARRAY_TYPEDISPATCH(obj, hasHole(obj, idx))) obj->fslots[JSSLOT_ARRAY_COUNT]++; - obj->dslots[idx] = v; - return JS_TRUE; + return JSARRAY_TYPEDISPATCH(obj, storeValue(cx, obj, idx, v)); } } if (!js_MakeArraySlow(cx, obj)) return JS_FALSE; } JSAutoTempIdRooter idr(cx); @@ -500,19 +766,19 @@ SetArrayElement(JSContext *cx, JSObject static JSBool DeleteArrayElement(JSContext *cx, JSObject *obj, jsdouble index) { JS_ASSERT(index >= 0); if (OBJ_IS_DENSE_ARRAY(cx, obj)) { if (index <= jsuint(-1)) { jsuint idx = jsuint(index); if (!INDEX_TOO_SPARSE(obj, idx) && idx < js_DenseArrayCapacity(obj)) { - if (obj->dslots[idx] != JSVAL_HOLE) + if (!JSARRAY_TYPEDISPATCH(obj, hasHole(obj,idx))) obj->fslots[JSSLOT_ARRAY_COUNT]--; - obj->dslots[idx] = JSVAL_HOLE; + JSARRAY_TYPEDISPATCH(obj, storeHole(obj, idx)); return JS_TRUE; } } return JS_TRUE; } JSAutoTempIdRooter idr(cx); @@ -575,17 +841,17 @@ js_HasLengthProperty(JSContext *cx, JSOb JSBool js_IsArrayLike(JSContext *cx, JSObject *obj, JSBool *answerp, jsuint *lengthp) { JSClass *clasp; clasp = OBJ_GET_CLASS(cx, obj); *answerp = (clasp == &js_ArgumentsClass || clasp == &js_ArrayClass || - clasp == &js_SlowArrayClass); + clasp == &js_DenseArrayClass || clasp == &js_SlowArrayClass); if (!*answerp) { *lengthp = 0; return JS_TRUE; } return js_GetLengthProperty(cx, obj, lengthp); } /* @@ -701,17 +967,17 @@ IsDenseArrayId(JSContext *cx, JSObject * { JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj)); uint32 i; return id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom) || (js_IdIsIndex(id, &i) && obj->fslots[JSSLOT_ARRAY_LENGTH] != 0 && i < js_DenseArrayCapacity(obj) && - obj->dslots[i] != JSVAL_HOLE); + !JSARRAY_TYPEDISPATCH(obj, hasHole(obj, i))); } static JSBool array_lookupProperty(JSContext *cx, JSObject *obj, jsid id, JSObject **objp, JSProperty **propp) { if (!OBJ_IS_DENSE_ARRAY(cx, obj)) return js_LookupProperty(cx, obj, id, objp, propp); @@ -744,18 +1010,20 @@ js_GetDenseArrayElementValue(JSContext * jsid id = (jsid) prop; JS_ASSERT(IsDenseArrayId(cx, obj, id)); uint32 i; if (!js_IdIsIndex(id, &i)) { JS_ASSERT(id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom)); return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], vp); } - *vp = obj->dslots[i]; - return JS_TRUE; + JSBool ok; + ok = JSARRAY_TYPEDISPATCH(obj, getValue(cx, obj, i, vp)); + JS_ASSERT(!JSARRAY_TYPEDISPATCH(obj, hasHole(obj, i))); + return ok; } static JSBool array_getProperty(JSContext *cx, JSObject *obj, jsid id, jsval *vp) { uint32 i; if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom)) @@ -765,17 +1033,17 @@ array_getProperty(JSContext *cx, JSObjec *vp = STOBJ_GET_SLOT(obj, JSSLOT_PROTO); return JS_TRUE; } if (!OBJ_IS_DENSE_ARRAY(cx, obj)) return js_GetProperty(cx, obj, id, vp); if (!js_IdIsIndex(ID_TO_VALUE(id), &i) || i >= js_DenseArrayCapacity(obj) || - obj->dslots[i] == JSVAL_HOLE) { + JSARRAY_TYPEDISPATCH(obj, hasHole(obj, i))) { JSObject *obj2; JSProperty *prop; JSScopeProperty *sprop; JSObject *proto = STOBJ_GET_PROTO(obj); if (!proto) { *vp = JSVAL_VOID; return JS_TRUE; @@ -792,18 +1060,17 @@ array_getProperty(JSContext *cx, JSObjec if (!js_NativeGet(cx, obj, obj2, sprop, vp)) return JS_FALSE; } OBJ_DROP_PROPERTY(cx, obj2, prop); } return JS_TRUE; } - *vp = obj->dslots[i]; - return JS_TRUE; + return JSARRAY_TYPEDISPATCH(obj, getValue(cx, obj, i, vp)); } static JSBool slowarray_addProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) { jsuint index, length; if (!js_IdIsIndex(id, &index)) @@ -855,20 +1122,19 @@ array_setProperty(JSContext *cx, JSObjec return js_SetProperty(cx, obj, id, vp); } if (!EnsureCapacity(cx, obj, i + 1)) return JS_FALSE; if (i >= (uint32)obj->fslots[JSSLOT_ARRAY_LENGTH]) obj->fslots[JSSLOT_ARRAY_LENGTH] = i + 1; - if (obj->dslots[i] == JSVAL_HOLE) + if (JSARRAY_TYPEDISPATCH(obj, hasHole(obj, i))) obj->fslots[JSSLOT_ARRAY_COUNT]++; - obj->dslots[i] = *vp; - return JS_TRUE; + return JSARRAY_TYPEDISPATCH(obj, storeValue(cx, obj, i, *vp)); } JSBool js_PrototypeHasIndexedProperties(JSContext *cx, JSObject *obj) { /* * Walk up the prototype chain and see if this indexed element already * exists. If we hit the end of the prototype chain, it's safe to set the @@ -887,52 +1153,71 @@ js_PrototypeHasIndexedProperties(JSConte } return JS_FALSE; } #ifdef JS_TRACER JSBool FASTCALL js_Array_dense_setelem(JSContext* cx, JSObject* obj, jsint i, jsval v) { - JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj)); + JSBool ok = JS_TRUE; + if (OBJ_IS_SPECIALIZED_ARRAY(obj)) { + if (JSAS_INT == js_SpecArrayType(obj) && JSVAL_IS_INT(v)) + return ArraySpec::setelem(cx, obj, i, JSVAL_TO_INT(v)); + if (JSAS_DOUBLE == js_SpecArrayType(obj)) { + if (JSVAL_IS_INT(v)) { + return ArraySpec::setelem(cx, obj, i, + static_cast(JSVAL_TO_INT(v))); + } else if (JSVAL_IS_DOUBLE(v)) { + return ArraySpec::setelem(cx, obj, i, *JSVAL_TO_DOUBLE(v)); + } + } + // Could not do conversion + (void)JSARRAY_SPECDISPATCH(obj, promoteTo(cx, obj)); + ok = !js_DenseArrayCapacity(obj); + } + return ArraySpec::setelem(cx, obj, i, v) && ok; +} - /* - * Let the interpreter worry about negative array indexes. - */ - JS_ASSERT((MAX_DSLOTS_LENGTH > JSVAL_INT_MAX) == (sizeof(jsval) != sizeof(uint32))); - if (MAX_DSLOTS_LENGTH > JSVAL_INT_MAX) { - /* - * Have to check for negative values bleeding through on 64-bit machines only, - * since we can't allocate large enough arrays for this on 32-bit machines. - */ - if (i < 0) - return JS_FALSE; +JSBool FASTCALL +js_Array_dense_setelem_d(JSContext* cx, JSObject* obj, jsint i, jsdouble v) +{ + JSBool ok = JS_TRUE; + if (OBJ_IS_SPECIALIZED_ARRAY(obj)) { + if(JSAS_INT == js_SpecArrayType(obj)) { + uint32 iv = int(v); + if (iv == v) + return ArraySpec::setelem(cx, obj, i, iv); + (void)ArraySpec::promoteTo(cx, obj); + ok = !js_DenseArrayCapacity(obj); + } + // JSAS_DOUBLE + return ArraySpec::setelem(cx, obj, i, v) && ok; } + // obj is a dense array + // Promote d to jsval + jsval jv; + (void)ArraySpec::toJSVal(cx, v, &jv); + return ArraySpec::setelem(cx, obj, i, jv); +} - /* - * If needed, grow the array as long it remains dense, otherwise fall off trace. - */ - jsuint u = jsuint(i); - jsuint capacity = js_DenseArrayCapacity(obj); - if ((u >= capacity) && (INDEX_TOO_SPARSE(obj, u) || !EnsureCapacity(cx, obj, u + 1))) - return JS_FALSE; - - if (obj->dslots[u] == JSVAL_HOLE) { - if (js_PrototypeHasIndexedProperties(cx, obj)) - return JS_FALSE; - - if (u >= jsuint(obj->fslots[JSSLOT_ARRAY_LENGTH])) - obj->fslots[JSSLOT_ARRAY_LENGTH] = u + 1; - ++obj->fslots[JSSLOT_ARRAY_COUNT]; +JSBool FASTCALL +js_Array_dense_setelem_i(JSContext* cx, JSObject* obj, jsint i, uint32 v) +{ + if (OBJ_IS_SPECIALIZED_ARRAY(obj)) { + if (JSAS_INT == js_SpecArrayType(obj)) + return ArraySpec::setelem(cx, obj, i, v); + // JSAS_DOUBLE + return ArraySpec::setelem(cx, obj, i, jsdouble(v)); } - - obj->dslots[u] = v; - return JS_TRUE; + return ArraySpec::setelem(cx, obj, i, INT_TO_JSVAL(v)); } JS_DEFINE_CALLINFO_4(extern, BOOL, js_Array_dense_setelem, CONTEXT, OBJECT, INT32, JSVAL, 0, 0) +JS_DEFINE_CALLINFO_4(extern, BOOL, js_Array_dense_setelem_d, CONTEXT, OBJECT, INT32, DOUBLE, 0, 0) +JS_DEFINE_CALLINFO_4(extern, BOOL, js_Array_dense_setelem_i, CONTEXT, OBJECT, INT32, UINT32, 0, 0) #endif static JSBool array_defineProperty(JSContext *cx, JSObject *obj, jsid id, jsval value, JSPropertyOp getter, JSPropertyOp setter, uintN attrs, JSProperty **propp) { uint32 i; @@ -978,19 +1263,19 @@ array_deleteProperty(JSContext *cx, JSOb return js_DeleteProperty(cx, obj, id, rval); if (id == ATOM_TO_JSID(cx->runtime->atomState.lengthAtom)) { *rval = JSVAL_FALSE; return JS_TRUE; } if (js_IdIsIndex(id, &i) && i < js_DenseArrayCapacity(obj) && - obj->dslots[i] != JSVAL_HOLE) { + !JSARRAY_TYPEDISPATCH(obj, hasHole(obj, i))) { obj->fslots[JSSLOT_ARRAY_COUNT]--; - obj->dslots[i] = JSVAL_HOLE; + JSARRAY_TYPEDISPATCH(obj, storeHole(obj, i)); } *rval = JSVAL_TRUE; return JS_TRUE; } /* * JSObjectOps.enumerate implementation. @@ -1063,17 +1348,17 @@ array_enumerate(JSContext *cx, JSObject switch (enum_op) { case JSENUMERATE_INIT: JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj)); capacity = js_DenseArrayCapacity(obj); if (idp) *idp = INT_TO_JSVAL(obj->fslots[JSSLOT_ARRAY_COUNT]); ii = NULL; for (i = 0; i != capacity; ++i) { - if (obj->dslots[i] == JSVAL_HOLE) { + if (JSARRAY_TYPEDISPATCH(obj, hasHole(obj, i))) { if (!ii) { ii = (JSIndexIterState *) JS_malloc(cx, offsetof(JSIndexIterState, holes) + JS_BITMAP_SIZE(capacity)); if (!ii) return JS_FALSE; ii->hasHoles = JS_TRUE; memset(ii->holes, 0, JS_BITMAP_SIZE(capacity)); @@ -1168,22 +1453,24 @@ static void array_trace(JSTracer *trc, JSObject *obj) { uint32 capacity; size_t i; jsval v; JS_ASSERT(js_IsDenseArray(obj)); - capacity = js_DenseArrayCapacity(obj); - for (i = 0; i < capacity; i++) { - v = obj->dslots[i]; - if (JSVAL_IS_TRACEABLE(v)) { - JS_SET_TRACING_INDEX(trc, "array_dslots", i); - JS_CallTracer(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v)); + if (!OBJ_IS_SPECIALIZED_ARRAY(obj)) { + capacity = js_DenseArrayCapacity(obj); + for (i = 0; i < capacity; i++) { + v = obj->dslots[i]; + if (JSVAL_IS_TRACEABLE(v)) { + JS_SET_TRACING_INDEX(trc, "array_dslots", i); + JS_CallTracer(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v)); + } } } for (i = JSSLOT_PROTO; i <= JSSLOT_PARENT; ++i) { v = STOBJ_GET_SLOT(obj, i); if (JSVAL_IS_TRACEABLE(v)) { JS_SET_TRACING_DETAILS(trc, js_PrintObjectSlotName, obj, i); JS_CallTracer(trc, JSVAL_TO_TRACEABLE(v), JSVAL_TRACE_KIND(v)); @@ -1213,16 +1500,27 @@ static JSObjectOps * array_getObjectOps(JSContext *cx, JSClass *clasp) { return &js_ArrayObjectOps; } JSClass js_ArrayClass = { "Array", JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Array) | + JSCLASS_HAS_RESERVED_SLOTS(2) | JSCLASS_NEW_ENUMERATE | + JSCLASS_CONSTRUCT_PROTOTYPE, + JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, + JS_EnumerateStub, JS_ResolveStub, js_TryValueOf, array_finalize, + array_getObjectOps, NULL, NULL, NULL, + NULL, NULL, NULL, NULL +}; + +JSClass js_DenseArrayClass = { + "Array", + JSCLASS_HAS_PRIVATE | JSCLASS_HAS_CACHED_PROTO(JSProto_Array) | JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_NEW_ENUMERATE, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, js_TryValueOf, array_finalize, array_getObjectOps, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; JSClass js_SlowArrayClass = { @@ -1235,17 +1533,22 @@ JSClass js_SlowArrayClass = { }; /* * Convert an array object from fast-and-dense to slow-and-flexible. */ JSBool js_MakeArraySlow(JSContext *cx, JSObject *obj) { - JS_ASSERT(OBJ_GET_CLASS(cx, obj) == &js_ArrayClass); + JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj)); + + // Ensure we have a dense array + if (OBJ_IS_SPECIALIZED_ARRAY(obj)) { + JSARRAY_SPECDISPATCH(obj, promoteTo(cx, obj)); + } /* Create a native scope. */ JSScope *scope = js_NewScope(cx, &js_SlowArrayObjectOps, &js_SlowArrayClass, obj); if (!scope) return JS_FALSE; uint32 capacity = js_DenseArrayCapacity(obj); @@ -1285,17 +1588,17 @@ js_MakeArraySlow(JSContext *cx, JSObject { uint32 length = obj->fslots[JSSLOT_ARRAY_LENGTH]; obj->fslots[JSSLOT_ARRAY_COUNT] = INT_FITS_IN_JSVAL(length) ? INT_TO_JSVAL(length) : JSVAL_VOID; } /* Make sure we preserve any flags borrowing bits in classword. */ - obj->classword ^= (jsuword) &js_ArrayClass; + obj->classword ^= (jsuword) &js_DenseArrayClass; obj->classword |= (jsuword) &js_SlowArrayClass; obj->map = &scope->map; return JS_TRUE; out_bad: js_DestroyScope(cx, scope); return JS_FALSE; @@ -1529,43 +1832,46 @@ array_join_sub(JSContext *cx, JSObject * #if JS_HAS_TOSOURCE static JSBool array_toSource(JSContext *cx, uintN argc, jsval *vp) { JSObject *obj; obj = JS_THIS_OBJECT(cx, vp); if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && + OBJ_GET_CLASS(cx, obj) != &js_DenseArrayClass && !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) { return JS_FALSE; } return array_join_sub(cx, obj, TO_SOURCE, NULL, vp); } #endif static JSBool array_toString(JSContext *cx, uintN argc, jsval *vp) { JSObject *obj; obj = JS_THIS_OBJECT(cx, vp); if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && + OBJ_GET_CLASS(cx, obj) != &js_DenseArrayClass && !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) { return JS_FALSE; } return array_join_sub(cx, obj, TO_STRING, NULL, vp); } static JSBool array_toLocaleString(JSContext *cx, uintN argc, jsval *vp) { JSObject *obj; obj = JS_THIS_OBJECT(cx, vp); if (OBJ_GET_CLASS(cx, obj) != &js_SlowArrayClass && + OBJ_GET_CLASS(cx, obj) != &js_DenseArrayClass && !JS_InstanceOf(cx, obj, &js_ArrayClass, vp + 2)) { return JS_FALSE; } /* * Passing comma here as the separator. Need a way to get a * locale-specific version. */ @@ -1627,29 +1933,32 @@ InitArrayElements(JSContext *cx, JSObjec jsuint valueCount = 0; for (jsuint i = 0; i < count; i++) { if (obj->dslots[start + i] != JSVAL_HOLE) valueCount++; } JS_ASSERT(uint32(obj->fslots[JSSLOT_ARRAY_COUNT]) >= valueCount); obj->fslots[JSSLOT_ARRAY_COUNT] -= valueCount; } - memcpy(obj->dslots + start, vector, sizeof(jsval) * count); + JSBool success = JSARRAY_TYPEDISPATCH(obj, + copyFromVector(cx, obj, start, + count, vector)); if (vectorType == SourceVectorAllValues) { obj->fslots[JSSLOT_ARRAY_COUNT] += count; } else { jsuint valueCount = 0; for (jsuint i = 0; i < count; i++) { - if (obj->dslots[start + i] != JSVAL_HOLE) + if (!JSARRAY_TYPEDISPATCH(obj, hasHole(obj, start+i))) valueCount++; } obj->fslots[JSSLOT_ARRAY_COUNT] += valueCount; } - JS_ASSERT_IF(count != 0, obj->dslots[newlen - 1] != JSVAL_HOLE); - return JS_TRUE; + JS_ASSERT_IF(count != 0, !JSARRAY_TYPEDISPATCH(obj, + hasHole(obj, newlen-1))); + return success; } jsval* end = vector + count; while (vector != end && start < MAXINDEX) { if (!JS_CHECK_OPERATION_LIMIT(cx) || !SetArrayElement(cx, obj, start++, *vector++)) { return JS_FALSE; } @@ -1691,22 +2000,27 @@ InitArrayObject(JSContext *cx, JSObject obj->fslots[JSSLOT_ARRAY_LENGTH] = length; if (vector) { if (!EnsureCapacity(cx, obj, length)) return JS_FALSE; jsuint count = length; if (!holey) { - memcpy(obj->dslots, vector, length * sizeof (jsval)); + if (!JSARRAY_TYPEDISPATCH(obj, copyFromVector(cx, obj, 0, + length, vector))) { + return JS_FALSE; + } } else { for (jsuint i = 0; i < length; i++) { if (vector[i] == JSVAL_HOLE) --count; - obj->dslots[i] = vector[i]; + if (!JSARRAY_TYPEDISPATCH(obj, storeValue(cx, obj, i, + vector[i]))) + return JS_FALSE; } } obj->fslots[JSSLOT_ARRAY_COUNT] = count; } else { obj->fslots[JSSLOT_ARRAY_COUNT] = 0; } return JS_TRUE; } @@ -2337,19 +2651,20 @@ array_push1_dense(JSContext* cx, JSObjec return JS_FALSE; return array_push_slowly(cx, obj, 1, &v, rval); } if (!EnsureCapacity(cx, obj, length + 1)) return JS_FALSE; obj->fslots[JSSLOT_ARRAY_LENGTH] = length + 1; - JS_ASSERT(obj->dslots[length] == JSVAL_HOLE); + JS_ASSERT(JSARRAY_TYPEDISPATCH(obj, hasHole(obj, length))); obj->fslots[JSSLOT_ARRAY_COUNT]++; - obj->dslots[length] = v; + if (!JSARRAY_TYPEDISPATCH(obj, storeValue(cx, obj, length, v))) + return JS_FALSE; return IndexToValue(cx, obj->fslots[JSSLOT_ARRAY_LENGTH], rval); } JSBool JS_FASTCALL js_ArrayCompPush(JSContext *cx, JSObject *obj, jsval v) { JS_ASSERT(OBJ_IS_DENSE_ARRAY(cx, obj)); uint32_t length = (uint32_t) obj->fslots[JSSLOT_ARRAY_LENGTH]; @@ -2362,18 +2677,17 @@ js_ArrayCompPush(JSContext *cx, JSObject return JS_FALSE; } if (!EnsureCapacity(cx, obj, length + 1)) return JS_FALSE; } obj->fslots[JSSLOT_ARRAY_LENGTH] = length + 1; obj->fslots[JSSLOT_ARRAY_COUNT]++; - obj->dslots[length] = v; - return JS_TRUE; + return JSARRAY_TYPEDISPATCH(obj, storeValue(cx, obj, length, v)); } JS_DEFINE_CALLINFO_3(extern, BOOL, js_ArrayCompPush, CONTEXT, OBJECT, JSVAL, 0, 0) #ifdef JS_TRACER static jsval FASTCALL Array_p_push1(JSContext* cx, JSObject* obj, jsval v) { JSAutoTempValueRooter tvr(cx, v); @@ -2752,17 +3066,17 @@ array_concat(JSContext *cx, uintN argc, JSTempValueRooter tvr; /* Treat our |this| object as the first argument; see ECMA 15.4.4.4. */ argv = JS_ARGV(cx, vp) - 1; JS_ASSERT(JS_THIS_OBJECT(cx, vp) == JSVAL_TO_OBJECT(argv[0])); /* Create a new Array object and root it using *vp. */ aobj = JS_THIS_OBJECT(cx, vp); - if (OBJ_IS_DENSE_ARRAY(cx, aobj)) { + if (OBJ_IS_DENSE_ARRAY(cx, aobj) && !OBJ_IS_SPECIALIZED_ARRAY(aobj)) { /* * 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. */ @@ -2894,17 +3208,18 @@ array_slice(JSContext *cx, uintN argc, j end = (jsuint)d; } } if (begin > end) begin = end; if (OBJ_IS_DENSE_ARRAY(cx, obj) && end <= js_DenseArrayCapacity(obj) && - !js_PrototypeHasIndexedProperties(cx, obj)) { + !js_PrototypeHasIndexedProperties(cx, obj) && + !OBJ_IS_SPECIALIZED_ARRAY(obj)) { nobj = js_NewArrayObject(cx, end - begin, obj->dslots + begin, obj->fslots[JSSLOT_ARRAY_COUNT] != obj->fslots[JSSLOT_ARRAY_LENGTH]); if (!nobj) return JS_FALSE; *vp = OBJECT_TO_JSVAL(nobj); return JS_TRUE; } @@ -3326,16 +3641,18 @@ js_Array(JSContext *cx, JSObject *obj, u length = 1; vector = argv; } else { length = ValueIsLength(cx, &argv[0]); if (JSVAL_IS_NULL(argv[0])) return JS_FALSE; vector = NULL; } + if (OBJ_IS_SPECIALIZED_ARRAY(obj)) + js_SetSpecArrayType(obj, JSAS_INT); return InitArrayObject(cx, obj, length, vector); } JS_STATIC_ASSERT(JSSLOT_PRIVATE == JSSLOT_ARRAY_LENGTH); JS_STATIC_ASSERT(JSSLOT_ARRAY_LENGTH + 1 == JSSLOT_ARRAY_COUNT); #ifdef JS_TRACER @@ -3352,36 +3669,43 @@ js_NewEmptyArray(JSContext* cx, JSObject /* Initialize all fields of JSObject. */ obj->map = const_cast(&SharedArrayMap); obj->classword = jsuword(&js_ArrayClass); obj->fslots[JSSLOT_PROTO] = OBJECT_TO_JSVAL(proto); obj->fslots[JSSLOT_PARENT] = proto->fslots[JSSLOT_PARENT]; obj->fslots[JSSLOT_ARRAY_LENGTH] = 0; obj->fslots[JSSLOT_ARRAY_COUNT] = 0; - for (unsigned i = JSSLOT_ARRAY_COUNT + 1; i != JS_INITIAL_NSLOTS; ++i) + + js_SetSpecArrayType(obj, JSAS_INT); + for (unsigned i = JSSLOT_ARRAY_SPEC + 1; i != JS_INITIAL_NSLOTS; ++i) obj->fslots[i] = JSVAL_VOID; obj->dslots = NULL; return obj; } JS_DEFINE_CALLINFO_2(extern, OBJECT, js_NewEmptyArray, CONTEXT, OBJECT, 0, 0) JSObject* FASTCALL -js_NewUninitializedArray(JSContext* cx, JSObject* proto, uint32 len) +js_NewUninitializedArray(JSContext* cx, JSObject* proto, uint32 len, uint32 type) { JS_ASSERT(JS_ON_TRACE(cx)); JSObject* obj = js_NewEmptyArray(cx, proto); if (!obj) return NULL; obj->fslots[JSSLOT_ARRAY_LENGTH] = len; if (!ResizeSlots(cx, obj, 0, JS_MAX(len, ARRAY_CAPACITY_MIN))) return NULL; + if (1 == type && len) + ArraySpec::promoteTo(cx, obj); + else if (JSAS_DOUBLE == (type >> 1)) { + ArraySpec::promoteTo(cx, obj); + } return obj; } -JS_DEFINE_CALLINFO_3(extern, OBJECT, js_NewUninitializedArray, CONTEXT, OBJECT, UINT32, 0, 0) +JS_DEFINE_CALLINFO_4(extern, OBJECT, js_NewUninitializedArray, CONTEXT, OBJECT, UINT32, UINT32, 0, 0) #endif /* JS_TRACER */ JSObject * js_InitArrayClass(JSContext *cx, JSObject *obj) { JSObject *proto; @@ -3405,16 +3729,18 @@ js_NewArrayObject(JSContext *cx, jsuint { JSTempValueRooter tvr; JSObject *obj; obj = js_NewObject(cx, &js_ArrayClass, NULL, NULL, 0); if (!obj) return NULL; + js_SetSpecArrayType(obj, JSAS_INT); + JS_PUSH_TEMP_ROOT_OBJECT(cx, obj, &tvr); if (!InitArrayObject(cx, obj, length, vector, holey)) obj = NULL; JS_POP_TEMP_ROOT(cx, &tvr); /* Set/clear newborn root, in case we lost it. */ cx->weakRoots.newborn[GCX_OBJECT] = obj; return obj; @@ -3465,33 +3791,36 @@ js_ArrayInfo(JSContext *cx, JSObject *ob #endif JS_FRIEND_API(JSBool) js_CoerceArrayToCanvasImageData(JSObject *obj, jsuint offset, jsuint count, JSUint8 *dest) { uint32 length; - if (!obj || !js_IsDenseArray(obj)) + if (!obj || !OBJ_IS_SPECIALIZED_ARRAY(obj)) return JS_FALSE; length = obj->fslots[JSSLOT_ARRAY_LENGTH]; if (length < offset + count) return JS_FALSE; + JSArraySpecialization spec = js_SpecArrayType(obj); + JSUint8 *dp = dest; - for (uintN i = offset; i < offset+count; i++) { - jsval v = obj->dslots[i]; - if (JSVAL_IS_INT(v)) { - jsint vi = JSVAL_TO_INT(v); + if (spec == JSAS_INT) { + for (uintN i = offset; i < offset+count; i++) { + jsint vi = ArraySpec::getRawValue(obj, i); if (jsuint(vi) > 255) vi = (vi < 0) ? 0 : 255; *dp++ = JSUint8(vi); - } else if (JSVAL_IS_DOUBLE(v)) { - jsdouble vd = *JSVAL_TO_DOUBLE(v); + } + } else if (spec == JSAS_DOUBLE) { + for (uintN i = offset; i < offset+count; i++) { + jsdouble vd = ArraySpec::getRawValue(obj, i); if (!(vd >= 0)) /* Not < so that NaN coerces to 0 */ *dp++ = 0; else if (vd > 255) *dp++ = 255; else { jsdouble toTruncate = vd + 0.5; JSUint8 val = JSUint8(toTruncate); @@ -3509,15 +3838,26 @@ js_CoerceArrayToCanvasImageData(JSObject * ones bit should do the trick to get us the value we * want. */ *dp++ = (val & ~1); } else { *dp++ = val; } } - } else { - return JS_FALSE; } + } else { + // Not something we know how to handle + return JS_FALSE; } return JS_TRUE; } + +template<> +const nanojit::CallInfo * +ArraySpec::setelem_ci = &js_Array_dense_setelem_ci; +template<> +const nanojit::CallInfo * +ArraySpec::setelem_ci = &js_Array_dense_setelem_d_ci; +template<> +const nanojit::CallInfo * +ArraySpec::setelem_ci = &js_Array_dense_setelem_i_ci; diff --git a/js/src/jsarray.h b/js/src/jsarray.h --- a/js/src/jsarray.h +++ b/js/src/jsarray.h @@ -1,9 +1,10 @@ /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set sw=4 ts=8 et tw=78: * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ @@ -51,22 +52,24 @@ JS_BEGIN_EXTERN_C #define ARRAY_CAPACITY_MIN 7 /* Generous sanity-bound on length (in elements) of array initialiser. */ #define ARRAY_INIT_LIMIT JS_BIT(24) extern JSBool js_IdIsIndex(jsval id, jsuint *indexp); -extern JSClass js_ArrayClass, js_SlowArrayClass; +extern JSClass js_ArrayClass, js_DenseArrayClass, js_SlowArrayClass; + +#define OBJ_IS_SPECIALIZED_ARRAY(obj) (OBJ_GET_CLASS(BOGUS_CX, obj) == &js_ArrayClass) static JS_INLINE JSBool js_IsDenseArray(JSObject *obj) { - return STOBJ_GET_CLASS(obj) == &js_ArrayClass; + return OBJ_IS_SPECIALIZED_ARRAY(obj) || STOBJ_GET_CLASS(obj) == &js_DenseArrayClass; } #define OBJ_IS_DENSE_ARRAY(cx, obj) js_IsDenseArray(obj) #define OBJ_IS_ARRAY(cx,obj) (OBJ_IS_DENSE_ARRAY(cx, obj) || \ OBJ_GET_CLASS(cx, obj) == &js_SlowArrayClass) /* @@ -105,17 +108,37 @@ js_NewArrayObject(JSContext *cx, jsuint extern JSObject * js_NewSlowArrayObject(JSContext *cx); extern JSBool js_MakeArraySlow(JSContext *cx, JSObject *obj); #define JSSLOT_ARRAY_LENGTH JSSLOT_PRIVATE #define JSSLOT_ARRAY_COUNT (JSSLOT_ARRAY_LENGTH + 1) -#define JSSLOT_ARRAY_UNUSED (JSSLOT_ARRAY_COUNT + 1) +#define JSSLOT_ARRAY_SPEC (JSSLOT_ARRAY_COUNT + 1) + +typedef enum JSArraySpecialization { + JSAS_INT = 0, + JSAS_DOUBLE = 1 +} JSArraySpecialization; + +static JS_INLINE JSArraySpecialization +js_SpecArrayType(JSObject *obj) +{ + JS_ASSERT(OBJ_IS_SPECIALIZED_ARRAY(obj)); + JSArraySpecialization spec = static_cast(obj->fslots[JSSLOT_ARRAY_SPEC]); + JS_ASSERT(spec == JSAS_INT || spec == JSAS_DOUBLE); + return spec; +} + +static JS_INLINE void +js_SetSpecArrayType(JSObject *obj, JSArraySpecialization spec) { + JS_ASSERT(OBJ_IS_SPECIALIZED_ARRAY(obj)); + obj->fslots[JSSLOT_ARRAY_SPEC] = static_cast(spec); +} static JS_INLINE uint32 js_DenseArrayCapacity(JSObject *obj) { JS_ASSERT(js_IsDenseArray(obj)); return obj->dslots ? (uint32) obj->dslots[-1] : 0; } @@ -199,11 +222,21 @@ js_PrototypeHasIndexedProperties(JSConte /* * Utility to access the value from the id returned by array_lookupProperty. */ JSBool js_GetDenseArrayElementValue(JSContext *cx, JSObject *obj, JSProperty *prop, jsval *vp); +/* + * Utility for jsobj.cpp/js_ReallocSlots to "do-the-right-thing" for + * type specialized arrays. + */ +JSBool +js_ReallocArraySlots(JSContext *cx, JSObject *obj, uint32 nslots, + JSBool exactAllocation); + JS_END_EXTERN_C +#include "jsarraytemplates.h" + #endif /* jsarray_h___ */ diff --git a/js/src/jsarraytemplates.h b/js/src/jsarraytemplates.h new file mode 100644 --- /dev/null +++ b/js/src/jsarraytemplates.h @@ -0,0 +1,278 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set sw=4 ts=8 et tw=78: + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla Communicator client code, released + * March 31, 1998. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either of the GNU General Public License Version 2 or later (the "GPL"), + * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#ifndef jsarraytemplates_h___ +#define jsarraytemplates_h___ + +#include "jsnum.h" /* for JSDOUBLE_IS_INT */ +#include "jsbool.h" /* for JSVAL_HOLE */ + +#ifdef JS_TRACER +#include "nanojit/nanojit.h" +// hack: json.cpp has a function named JO. nanojit defines a JO macro. This is +// a bandaid. Ditto for JA. +#undef JO +#undef JA +#endif + +#define JSARRAY_SPECDISPATCH(obj, expr) \ + (js_SpecArrayType(obj) == JSAS_DOUBLE \ + ? ArraySpec::expr \ + : ArraySpec::expr) + +#define JSARRAY_TYPEDISPATCH(obj, expr) \ + (OBJ_IS_SPECIALIZED_ARRAY(obj) \ + ? (js_SpecArrayType(obj) == JSAS_DOUBLE \ + ? ArraySpec::expr \ + : ArraySpec::expr) \ + : ArraySpec::expr) + +template +class ArraySpecConversion +{ +public: + // No implementation provided for these, see specializations + static JSBool copy(JSContext *cx, TargetType *tSlots, SourceType *sSlots, + uint32 size); + static void updateObject(JSObject *obj); +}; + +// Promotion to dense array +template +class ArraySpecConversion +{ +public: + static JSBool copy(JSContext *cx, jsval *tSlots, SourceType *sSlots, + uint32 size); + static void updateObject(JSObject *obj); +}; + +template +class ArraySpec +{ +public: + static size_t value_size; + static const T hole_value; + +#ifdef JS_TRACER + static nanojit::LOpcode load_opcode; +#endif + + static JSBool toJSVal(JSContext *cx, const T &val, jsval *rval); + static JSBool fromJSVal(jsval v, T* val); + + static JSBool JS_ALWAYS_INLINE hasHole(JSObject *obj, jsuint index) { + const T& val = reinterpret_cast(obj->dslots)[index]; + return hole_value == val; + } + + // void* to play nice with JSARRAY_TYPEDISPATCH + static void *getSlotLocation(JSObject *obj, jsuint index) { + return reinterpret_cast(&getRawValue(obj, index)); + } + + static T& getRawValue(JSObject *obj, jsuint index) { + return reinterpret_cast(obj->dslots)[index]; + } + + static JSBool JS_ALWAYS_INLINE + getValue(JSContext *cx, JSObject *obj, jsuint index, jsval *vp) { + T& value = getRawValue(obj, index); + if (value != hole_value) { + return toJSVal(cx, value, vp); + } else { + *vp = JSVAL_HOLE; + return JS_TRUE; + } + } + + static JSBool JS_ALWAYS_INLINE + storeValue(JSContext *cx, JSObject *obj, jsuint idx, jsval v) { + if (v != JSVAL_HOLE) { + if (!fromJSVal(v, &getRawValue(obj, idx))) { + // Promote + return promoteToHoldValue(cx, obj, idx, v); + } + } else { + getRawValue(obj,idx) = hole_value; + } + return JS_TRUE; + } + + static JSBool JS_ALWAYS_INLINE + storeValue(JSContext *cx, JSObject *obj, jsuint idx, jsval v, + JSBool *promoted) { + JSBool cannotInsertValue = !fromJSVal(v, &getRawValue(obj, idx)); + *promoted = cannotInsertValue; + return cannotInsertValue + ? promoteToHoldValue(cx, obj, idx, v) + : JS_TRUE; + } + + + static void fillSlotsWithHole(jsval *slots, uint32 base, uint32 end) { + T* tslots = reinterpret_cast(slots); + for (uint32 i = base; i < end; i++) { + tslots[i] = hole_value; + } + } + + static void storeRawValue(JSObject *obj, jsuint idx, const T& v) { + getRawValue(obj, idx) = v; + } + + // Promotes if necessary (if v is a hole) + static JSBool storeNativeValue(JSContext *cx, JSObject *obj, + jsuint idx, const T& v); + + static void storeHole(JSObject *obj, jsuint idx) { + getRawValue(obj, idx) = hole_value; + } + + // Assumes the range is safe + static JSBool + copyFromVector(JSContext *cx, JSObject *obj, jsuint start, + jsuint count, jsval *vector); + + // Public for js_MakeArraySlow and tracer's array mismatch handling + template + static JSBool promoteTo(JSContext *cx, JSObject *obj); + +#ifdef JS_TRACER + static JSBool FASTCALL setelem(JSContext* cx, JSObject* obj, jsint i, T v); + static const nanojit::CallInfo *setelem_ci; +#endif + +private: + static JSBool + promoteToHoldValue(JSContext *cx, JSObject *obj, jsuint idx, jsval v); +}; + +template <> +JSBool JS_ALWAYS_INLINE +ArraySpec::toJSVal(JSContext *cx, const uint32 &i, jsval *rval) { + if (JS_LIKELY(INT_FITS_IN_JSVAL(i))) { + *rval = INT_TO_JSVAL(i); + return JS_TRUE; + } + return JS_NewNumberValue(cx, (jsdouble)i, rval); +} + +template <> +JSBool JS_ALWAYS_INLINE +ArraySpec::toJSVal(JSContext *cx, const jsdouble &d, jsval *rval) { + int32 i; + if (JSDOUBLE_IS_INT(d, i) && INT_FITS_IN_JSVAL(i)) { + *rval = INT_TO_JSVAL(i); + return JS_TRUE; + } + return JS_NewNumberValue(cx, d, rval); +} + +template <> +JSBool JS_ALWAYS_INLINE +ArraySpec::toJSVal(JSContext *cx, const jsval &val, jsval *rval) { + *rval = val; + return JS_TRUE; +} + +template <> +JSBool JS_ALWAYS_INLINE +ArraySpec::fromJSVal(jsval v, uint32 *val) { + JSBool ret = JSVAL_IS_INT(v); + if (ret) + *val = JSVAL_TO_INT(v); + return ret; +} + +template <> +JSBool JS_ALWAYS_INLINE +ArraySpec::fromJSVal(jsval v, jsdouble *val) { + if (JSVAL_IS_INT(v)) + *val = static_cast(JSVAL_TO_INT(v)); + else if (JSVAL_IS_DOUBLE(v)) + *val = *JSVAL_TO_DOUBLE(v); + else + return JS_FALSE; + return JS_TRUE; +} + +template <> +JSBool JS_ALWAYS_INLINE +ArraySpec::fromJSVal(jsval v, jsval *val) { + *val = v; + return JS_TRUE; +} + +template <> +JSBool +ArraySpec::promoteToHoldValue(JSContext *cx, JSObject *obj, jsuint idx, + jsval v); + +template <> +JSBool +ArraySpec::copyFromVector(JSContext *cx, JSObject *obj, jsuint start, + jsuint count, jsval *vector); + +#ifdef JS_TRACER +template <> +nanojit::LOpcode +ArraySpec::load_opcode = nanojit::LIR_ldp; + +template <> +nanojit::LOpcode +ArraySpec::load_opcode = nanojit::LIR_ld; + +template <> +nanojit::LOpcode +ArraySpec::load_opcode = nanojit::LIR_ldq; +#endif + +template <> +class ArraySpecConversion +{ +public: + static JSBool copy(JSContext *cx, jsdouble *tSlots, uint32 *sSlots, + uint32 size); + + static void updateObject(JSObject *obj); +}; + +#endif /* jsarraytemplates_h___ */ diff --git a/js/src/jsinterp.cpp b/js/src/jsinterp.cpp --- a/js/src/jsinterp.cpp +++ b/js/src/jsinterp.cpp @@ -4816,17 +4816,24 @@ js_Interpret(JSContext *cx) if (JSVAL_IS_INT(rval)) { if (OBJ_IS_DENSE_ARRAY(cx, obj)) { jsuint length; length = js_DenseArrayCapacity(obj); i = JSVAL_TO_INT(rval); if ((jsuint)i < length && i < obj->fslots[JSSLOT_ARRAY_LENGTH]) { - rval = obj->dslots[i]; + if (OBJ_IS_SPECIALIZED_ARRAY(obj)) { + // This will write JSVAL_HOLE for hole cases + if (!JSARRAY_SPECDISPATCH(obj, + getValue(cx, obj, i, &rval))) + goto error; + } else { + rval = obj->dslots[i]; + } if (rval != JSVAL_HOLE) goto end_getelem; /* Reload rval from the stack in the rare hole case. */ rval = FETCH_OPND(-1); } } id = INT_JSVAL_TO_JSID(rval); @@ -4864,24 +4871,37 @@ js_Interpret(JSContext *cx) FETCH_ELEMENT_ID(obj, -2, id); do { if (OBJ_IS_DENSE_ARRAY(cx, obj) && JSID_IS_INT(id)) { jsuint length; length = js_DenseArrayCapacity(obj); i = JSID_TO_INT(id); if ((jsuint)i < length) { - if (obj->dslots[i] == JSVAL_HOLE) { - if (js_PrototypeHasIndexedProperties(cx, obj)) - break; - if (i >= obj->fslots[JSSLOT_ARRAY_LENGTH]) - obj->fslots[JSSLOT_ARRAY_LENGTH] = i + 1; - obj->fslots[JSSLOT_ARRAY_COUNT]++; - } - obj->dslots[i] = rval; + if (!OBJ_IS_SPECIALIZED_ARRAY(obj)) { + if (obj->dslots[i] == JSVAL_HOLE) { + if (js_PrototypeHasIndexedProperties(cx, obj)) + break; + if (i >= obj->fslots[JSSLOT_ARRAY_LENGTH]) + obj->fslots[JSSLOT_ARRAY_LENGTH] = i + 1; + obj->fslots[JSSLOT_ARRAY_COUNT]++; + } + obj->dslots[i] = rval; + } else { + if (JSARRAY_SPECDISPATCH(obj, hasHole(obj, i))) { + if (js_PrototypeHasIndexedProperties(cx, obj)) + break; + if (i >= obj->fslots[JSSLOT_ARRAY_LENGTH]) + obj->fslots[JSSLOT_ARRAY_LENGTH] = i + 1; + obj->fslots[JSSLOT_ARRAY_COUNT]++; + } + if (!JSARRAY_SPECDISPATCH(obj, + storeValue(cx, obj, i, rval))) + goto error; + } goto end_setelem; } } } while (0); if (!OBJ_SET_PROPERTY(cx, obj, id, &rval)) goto error; end_setelem: END_SET_CASE_STORE_RVAL(JSOP_SETELEM, 3) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2868,16 +2868,19 @@ FreeSlots(JSContext *cx, JSObject *obj) JSBool js_ReallocSlots(JSContext *cx, JSObject *obj, uint32 nslots, JSBool exactAllocation) { jsval *old, *slots; uint32 oslots, nwords, owords, log, i; + if (OBJ_IS_DENSE_ARRAY(cx, obj)) { + return js_ReallocArraySlots(cx, obj, nslots, exactAllocation); + } /* * Minimal number of dynamic slots to allocate. */ #define MIN_DYNAMIC_WORDS 4 /* * The limit to switch to linear allocation strategy from the power of 2 * growth no to waste too much memory. diff --git a/js/src/jstracer.cpp b/js/src/jstracer.cpp --- a/js/src/jstracer.cpp +++ b/js/src/jstracer.cpp @@ -248,16 +248,17 @@ js_InitJITStatsClass(JSContext *cx, JSOb } #define AUDIT(x) (jitstats.x++) #else #define AUDIT(x) ((void)0) #endif /* JS_JIT_SPEW */ #define INS_CONST(c) addName(lir->insImm(c), #c) +#define INS_CONSTF(c) addName(lir->insImmf(c), #c) #define INS_CONSTPTR(p) addName(lir->insImmPtr(p), #p) #define INS_CONSTFUNPTR(p) addName(lir->insImmPtr(JS_FUNC_TO_DATA_PTR(void*, p)), #p) #define INS_CONSTWORD(v) addName(lir->insImmPtr((void *) v), #v) using namespace avmplus; using namespace nanojit; static GC gc = GC(); @@ -5740,23 +5741,23 @@ TraceRecorder::alu(LOpcode v, jsdouble v LIns* TraceRecorder::f2i(LIns* f) { return lir->insCall(&js_DoubleToInt32_ci, &f); } JS_REQUIRES_STACK LIns* -TraceRecorder::makeNumberInt32(LIns* f) +TraceRecorder::makeNumberInt32(LIns* f, ExitType exitType) { JS_ASSERT(f->isQuad()); LIns* x; if (!isPromote(f)) { x = f2i(f); - guard(true, lir->ins2(LIR_feq, f, lir->ins1(LIR_i2f, x)), MISMATCH_EXIT); + guard(true, lir->ins2(LIR_feq, f, lir->ins1(LIR_i2f, x)), exitType); } else { x = ::demote(lir, f); } return x; } JS_REQUIRES_STACK LIns* TraceRecorder::stringify(jsval& v) @@ -5992,30 +5993,53 @@ TraceRecorder::incProp(jsint incr, bool return JSRS_CONTINUE; } JS_REQUIRES_STACK JSRecordingStatus TraceRecorder::incElem(jsint incr, bool pre) { jsval& r = stackval(-1); jsval& l = stackval(-2); + // Not really a jsval* in the case of a specialized array jsval* vp; LIns* v_ins; LIns* addr_ins; + ExitType exitType = OBJ_IS_SPECIALIZED_ARRAY(JSVAL_TO_OBJECT(l)) + ? BRANCH_EXIT + : MISMATCH_EXIT; if (!JSVAL_IS_OBJECT(l) || !JSVAL_IS_INT(r) || - !guardDenseArray(JSVAL_TO_OBJECT(l), get(&l))) { + !guardDenseArray(JSVAL_TO_OBJECT(l), get(&l), exitType)) { return JSRS_STOP; } CHECK_STATUS(denseArrayElement(l, r, vp, v_ins, addr_ins)); if (!addr_ins) // if we read a hole, abort return JSRS_STOP; - CHECK_STATUS(inc(*vp, v_ins, incr, pre)); - box_jsval(*vp, v_ins); + + if (JSVAL_IS_OBJECT(l) && + OBJ_IS_SPECIALIZED_ARRAY(JSVAL_TO_OBJECT(l))) { + JSObject *obj = JSVAL_TO_OBJECT(l); + JSArraySpecialization spec = js_SpecArrayType(obj); + jsdouble d = JSAS_INT == spec + ? static_cast(*reinterpret_cast(vp)) + : *reinterpret_cast(vp); + jsval v; + if (!JS_NewNumberValue(cx, d, &v)) + return JSRS_ERROR; + // Garbage collection won't happen during this call and inc doesn't + // store it away somewhere so we don't need to root + CHECK_STATUS(inc(v, v_ins, incr, pre)); + // demote to int if needed + if (isi2f(v_ins) && spec == JSAS_INT) + v_ins = iu2fArg(v_ins); + } else { + CHECK_STATUS(inc(*vp, v_ins, incr, pre)); + box_jsval(*vp, v_ins); + } lir->insStorei(v_ins, addr_ins, 0); return JSRS_CONTINUE; } static bool evalCmp(LOpcode op, double result) { bool cond; @@ -6921,19 +6945,49 @@ TraceRecorder::guardClass(JSObject* obj, char namebuf[32]; JS_snprintf(namebuf, sizeof namebuf, "guard(class is %s)", clasp->name); guard(cond, addName(lir->ins2(LIR_eq, class_ins, INS_CONSTPTR(clasp)), namebuf), exit); return cond; } JS_REQUIRES_STACK bool -TraceRecorder::guardDenseArray(JSObject* obj, LIns* obj_ins, ExitType exitType) -{ - return guardClass(obj, obj_ins, &js_ArrayClass, snapshot(exitType)); +TraceRecorder::guardDenseArray(JSObject* obj, LIns* obj_ins, ExitType exitType, JSBool denseOrSpec) +{ + if (denseOrSpec) { + // if (class not is DenseArray) + // guard(class is Array) + LIns* class_ins = lir->insLoad(LIR_ldp, obj_ins, offsetof(JSObject, classword)); + class_ins = lir->ins2(LIR_piand, class_ins, + lir->insImm(~JSSLOT_CLASS_MASK_BITS)); + LIns *denseCheck = lir->ins2(LIR_eq, class_ins, + INS_CONSTPTR(&js_DenseArrayClass)); + LIns *specCheck = lir->ins2(LIR_eq, class_ins, + INS_CONSTPTR(&js_ArrayClass)); + guard(true, lir->ins2(LIR_or, denseCheck, specCheck), exitType); + } else { + bool isSpec = OBJ_IS_SPECIALIZED_ARRAY(obj); + JSClass *cls = isSpec + ? &js_ArrayClass + : &js_DenseArrayClass; + if (!guardClass(obj, obj_ins, cls, snapshot(exitType))) + return false; + if (isSpec) { + JSArraySpecialization spec = js_SpecArrayType(obj); + LIns *fslot_ins = stobj_get_fslot(obj_ins, JSSLOT_ARRAY_SPEC); + // To be helpful, include names + LIns *expectedSpec = spec == JSAS_INT + ? INS_CONST(JSAS_INT) + : INS_CONST(JSAS_DOUBLE); + guard(true, + lir->ins2(LIR_eq, expectedSpec, fslot_ins), + snapshot(exitType)); + } + } + return true; } JS_REQUIRES_STACK JSRecordingStatus TraceRecorder::guardPrototypeHasNoIndexedProperties(JSObject* obj, LIns* obj_ins, ExitType exitType) { /* * Guard that no object along the prototype chain has any indexed properties which * might become visible through holes in the array. @@ -7562,18 +7616,18 @@ TraceRecorder::newArray(JSObject* ctor, guard(false, lir->ins_eq0(arr_ins), OOM_EXIT); if (argc == 1) { // array_ins.fslots[JSSLOT_ARRAY_LENGTH] = length lir->insStorei(f2i(get(argv)), // FIXME: is this 64-bit safe? arr_ins, offsetof(JSObject, fslots) + JSSLOT_ARRAY_LENGTH * sizeof(jsval)); } } else { - // arr_ins = js_NewUninitializedArray(cx, Array.prototype, argc) - LIns *args[] = { INS_CONST(argc), proto_ins, cx_ins }; + // arr_ins = js_NewUninitializedArray(cx, Array.prototype, argc, 1) + LIns *args[] = { lir->insImm(1), INS_CONST(argc), proto_ins, cx_ins }; arr_ins = lir->insCall(&js_NewUninitializedArray_ci, args); guard(false, lir->ins_eq0(arr_ins), OOM_EXIT); // arr->dslots[i] = box_jsval(vp[i]); for i in 0..argc LIns *dslots_ins = NULL; for (uint32 i = 0; i < argc && !lirbuf->outOMem(); i++) { LIns *elt_ins = get(argv + i); box_jsval(argv[i], elt_ins); @@ -8345,17 +8399,20 @@ TraceRecorder::record_JSOP_GETELEM() // The object is not guaranteed to be a dense array at this point, so it might be the // global object, which we have to guard against. CHECK_STATUS(guardNotGlobalObject(obj, obj_ins)); return call_imacro(call ? callelem_imacros.callprop : getelem_imacros.getprop); } - if (!guardDenseArray(obj, obj_ins, BRANCH_EXIT)) { + ExitType exitType = OBJ_IS_SPECIALIZED_ARRAY(obj) + ? BRANCH_EXIT + : MISMATCH_EXIT; + if (!guardDenseArray(obj, obj_ins, exitType)) { CHECK_STATUS(guardNotGlobalObject(obj, obj_ins)); return call_imacro(call ? callelem_imacros.callelem : getelem_imacros.getelem); } // Fast path for dense arrays accessed with a integer index. jsval* vp; LIns* addr_ins; @@ -8477,30 +8534,43 @@ TraceRecorder::record_JSOP_SETELEM() CHECK_STATUS(guardNotGlobalObject(obj, obj_ins)); return call_imacro((*cx->fp->regs->pc == JSOP_INITELEM) ? initelem_imacros.initelem : setelem_imacros.setelem); } // Make sure the array is actually dense. - if (!guardDenseArray(obj, obj_ins, BRANCH_EXIT)) + if (!guardDenseArray(obj, obj_ins, MISMATCH_EXIT, JS_TRUE)) return JSRS_STOP; // Fast path for dense arrays accessed with a non-negative integer index. In case the trace // calculated the index using the FPU, force it to be an integer. idx_ins = makeNumberInt32(idx_ins); // Box the value so we can use one builtin instead of having to add one builtin for every // storage type. LIns* boxed_v_ins = v_ins; - box_jsval(v, boxed_v_ins); - + const CallInfo *setelem_ci = NULL; + if (isNumber(v)) { + setelem_ci = ArraySpec::setelem_ci; + if (v_ins->isQuad()) { + if (isPromote(v_ins)) { + boxed_v_ins = ::demote(lir, v_ins); + } else { + // This will try to do conversion to double if possible + setelem_ci = ArraySpec::setelem_ci; + } + } + } else { + setelem_ci = ArraySpec::setelem_ci; + box_jsval(v, boxed_v_ins); + } LIns* args[] = { boxed_v_ins, idx_ins, obj_ins, cx_ins }; - LIns* res_ins = lir->insCall(&js_Array_dense_setelem_ci, args); + LIns* res_ins = lir->insCall(setelem_ci, args); guard(false, lir->ins_eq0(res_ins), MISMATCH_EXIT); jsbytecode* pc = cx->fp->regs->pc; if (*pc == JSOP_SETELEM && pc[JSOP_SETELEM_LENGTH] != JSOP_POP) set(&lval, v_ins); return JSRS_CONTINUE; } @@ -9244,21 +9314,39 @@ TraceRecorder::denseArrayElement(jsval& /* Guard array capacity */ guard(true, lir->ins2(LIR_ult, idx_ins, lir->insLoad(LIR_ldp, dslots_ins, 0 - (int)sizeof(jsval))), exit); /* Load the value and guard on its type to unbox it. */ - vp = &obj->dslots[jsuint(idx)]; + vp = reinterpret_cast(JSARRAY_TYPEDISPATCH(obj, getSlotLocation(obj, idx))); addr_ins = lir->ins2(LIR_piadd, dslots_ins, - lir->ins2i(LIR_pilsh, idx_ins, (sizeof(jsval) == 4) ? 2 : 3)); - v_ins = lir->insLoad(LIR_ldp, addr_ins, 0); - unbox_jsval(*vp, v_ins, exit); + lir->ins2i(LIR_pilsh, idx_ins, + (JSARRAY_TYPEDISPATCH(obj, value_size) == 4) ? 2 : 3)); + v_ins = lir->insLoad(JSARRAY_TYPEDISPATCH(obj, load_opcode), addr_ins, 0); + if (OBJ_IS_SPECIALIZED_ARRAY(obj)) { + JSArraySpecialization spec = js_SpecArrayType(obj); + LOpcode eqOp; + LIns *hole_value_ins; + if (JSAS_INT == spec) { + eqOp = LIR_eq; + hole_value_ins = INS_CONST(ArraySpec::hole_value); + } else { + eqOp = LIR_feq; + hole_value_ins = INS_CONSTF(ArraySpec::hole_value); + } + guard(false, lir->ins2(eqOp, v_ins, hole_value_ins), exit); + if (JSAS_INT == spec) + v_ins = lir->ins1(LIR_i2f, v_ins); + return JSRS_CONTINUE; + } else { + unbox_jsval(*vp, v_ins, exit); + } if (JSVAL_TAG(*vp) == JSVAL_BOOLEAN) { /* * If we read a hole from the array, convert it to undefined and guard that there * are no indexed properties along the prototype chain. */ LIns* br = lir->insBranch(LIR_jf, lir->ins2i(LIR_eq, v_ins, JSVAL_TO_PSEUDO_BOOLEAN(JSVAL_HOLE)), @@ -10876,30 +10964,70 @@ JS_REQUIRES_STACK JSRecordingStatus TraceRecorder::record_JSOP_NEWARRAY() { LIns *proto_ins; CHECK_STATUS(getClassPrototype(JSProto_Array, proto_ins)); uint32 len = GET_UINT16(cx->fp->regs->pc); cx->fp->assertValidStackDepth(len); - LIns* args[] = { lir->insImm(len), proto_ins, cx_ins }; + // Check for all double or all int arrays + int type = JSAS_INT << 1; + + for (uint32 i = 0; i < len; i++) { + jsval& v = stackval(int(i) - int(len)); + LIns* elt_ins = get(&v); + if (!JSVAL_IS_NUMBER(v) && !JSVAL_IS_INT(v)) { + type = 1; + break; + } + if (elt_ins->isQuad() && !isPromote(elt_ins)) { + type = JSAS_DOUBLE << 1; + continue; + } + } + + if (type != 1) { + debug_only_v(printf("newarray can create a spec array of %d (len = %d)\n", type >> 1, len)); + } + + // Temporarily treat all integer arrays as double arrays + if ((JSAS_INT << 1) == type) + type = JSAS_DOUBLE << 1; + + LIns* args[] = { lir->insImm(type), lir->insImm(len), proto_ins, cx_ins }; LIns* v_ins = lir->insCall(&js_NewUninitializedArray_ci, args); guard(false, lir->ins_eq0(v_ins), OOM_EXIT); LIns* dslots_ins = NULL; uint32 count = 0; for (uint32 i = 0; i < len; i++) { jsval& v = stackval(int(i) - int(len)); if (v != JSVAL_HOLE) count++; LIns* elt_ins = get(&v); - box_jsval(v, elt_ins); - stobj_set_dslot(v_ins, i, dslots_ins, elt_ins, "set_array_elt"); - } + if (1 == type) { + box_jsval(v, elt_ins); + stobj_set_dslot(v_ins, i, dslots_ins, elt_ins, "set_array_elt"); + } else { + if (!dslots_ins) + dslots_ins = lir->insLoad(LIR_ldp, v_ins, offsetof(JSObject, dslots)); + if (!elt_ins->isQuad()) { + if (elt_ins->isconst()) { + elt_ins = + lir->insImmf(static_cast(static_cast(elt_ins->imm64()))); + } else if (JSVAL_IS_INT(v)) { + elt_ins = lir->ins1(LIR_i2f, elt_ins); + } + } + // Don't have to worry about holes for doubles + lir->insStorei(elt_ins, dslots_ins, i * sizeof(jsdouble)); + } + } + if (count > 0) stobj_set_fslot(v_ins, JSSLOT_ARRAY_COUNT, INS_CONST(count), "set_array_count"); stack(-int(len), v_ins); return JSRS_CONTINUE; } diff --git a/js/src/jstracer.h b/js/src/jstracer.h --- a/js/src/jstracer.h +++ b/js/src/jstracer.h @@ -231,17 +231,18 @@ enum ExitType { /* * A specialization of MISMATCH_EXIT to handle allocation failures. */ OOM_EXIT, OVERFLOW_EXIT, UNSTABLE_LOOP_EXIT, TIMEOUT_EXIT, DEEP_BAIL_EXIT, - STATUS_EXIT + STATUS_EXIT, + ARRAY_MISMATCH_EXIT }; struct VMSideExit : public nanojit::SideExit { JSObject* block; jsbytecode* pc; jsbytecode* imacpc; intptr_t sp_adj; @@ -536,17 +537,17 @@ class TraceRecorder : public avmplus::GC JS_REQUIRES_STACK void var(unsigned n, nanojit::LIns* i); JS_REQUIRES_STACK nanojit::LIns* upvar(JSScript* script, JSUpvarArray* uva, uintN index, jsval& v); JS_REQUIRES_STACK nanojit::LIns* stack(int n); JS_REQUIRES_STACK void stack(int n, nanojit::LIns* i); JS_REQUIRES_STACK nanojit::LIns* alu(nanojit::LOpcode op, jsdouble v0, jsdouble v1, nanojit::LIns* s0, nanojit::LIns* s1); nanojit::LIns* f2i(nanojit::LIns* f); - JS_REQUIRES_STACK nanojit::LIns* makeNumberInt32(nanojit::LIns* f); + JS_REQUIRES_STACK nanojit::LIns* makeNumberInt32(nanojit::LIns* f, ExitType exitType = MISMATCH_EXIT); JS_REQUIRES_STACK nanojit::LIns* stringify(jsval& v); JS_REQUIRES_STACK JSRecordingStatus call_imacro(jsbytecode* imacro); JS_REQUIRES_STACK JSRecordingStatus ifop(); JS_REQUIRES_STACK JSRecordingStatus switchop(); #ifdef NANOJIT_IA32 JS_REQUIRES_STACK nanojit::LIns* tableswitch(); @@ -608,17 +609,18 @@ class TraceRecorder : public avmplus::GC JS_REQUIRES_STACK JSRecordingStatus getProp(jsval& v); JS_REQUIRES_STACK JSRecordingStatus getThis(nanojit::LIns*& this_ins); JS_REQUIRES_STACK void box_jsval(jsval v, nanojit::LIns*& v_ins); JS_REQUIRES_STACK void unbox_jsval(jsval v, nanojit::LIns*& v_ins, VMSideExit* exit); JS_REQUIRES_STACK bool guardClass(JSObject* obj, nanojit::LIns* obj_ins, JSClass* clasp, VMSideExit* exit); JS_REQUIRES_STACK bool guardDenseArray(JSObject* obj, nanojit::LIns* obj_ins, - ExitType exitType = MISMATCH_EXIT); + ExitType exitType = MISMATCH_EXIT, + JSBool denseOrSpec = JS_FALSE); JS_REQUIRES_STACK JSRecordingStatus guardPrototypeHasNoIndexedProperties(JSObject* obj, nanojit::LIns* obj_ins, ExitType exitType); JS_REQUIRES_STACK JSRecordingStatus guardNotGlobalObject(JSObject* obj, nanojit::LIns* obj_ins); void clearFrameSlotsFromCache(); JS_REQUIRES_STACK JSRecordingStatus guardCallee(jsval& callee); JS_REQUIRES_STACK JSRecordingStatus getClassPrototype(JSObject* ctor,