Bug 1087963 - Optimize Array.prototype.slice on sparse arrays. r=bhackett

--HG--
extra : rebase_source : df595a105839a5a0ba257303bf67ac7e7784fb35
This commit is contained in:
Jan de Mooij 2014-10-23 19:07:39 +02:00
Родитель 39a7d91875
Коммит 079038b58c
2 изменённых файлов: 159 добавлений и 7 удалений

Просмотреть файл

@ -0,0 +1,12 @@
// Indexed getters can add new properties that slice should not ignore.
var arr = [];
Object.defineProperty(arr, 10000, {get: function() {
arr[10001] = 4;
return 3;
}});
arr[10010] = 6;
var res = arr.slice(8000);
assertEq(res[2000], 3);
assertEq(res[2001], 4);
assertEq(res[2010], 6);

Просмотреть файл

@ -2715,6 +2715,136 @@ js::array_concat(JSContext *cx, unsigned argc, Value *vp)
return SetLengthProperty(cx, narr, length);
}
struct SortComparatorIndexes
{
bool operator()(uint32_t a, uint32_t b, bool *lessOrEqualp) {
*lessOrEqualp = (a <= b);
return true;
}
};
// Returns all indexed properties in the range [begin, end) found on |obj| or
// its proto chain. This function does not handle proxies, objects with
// resolve/lookupGeneric hooks or indexed getters, as those can introduce
// new properties. In those cases, *success is set to |false|.
static bool
GetIndexedPropertiesInRange(JSContext *cx, HandleObject obj, uint32_t begin, uint32_t end,
Vector<uint32_t> &indexes, bool *success)
{
*success = false;
// First, look for proxies or class hooks that can introduce extra
// properties.
JSObject *pobj = obj;
do {
if (!pobj->isNative() ||
pobj->getClass()->resolve != JS_ResolveStub ||
pobj->getOps()->lookupGeneric)
{
return true;
}
} while ((pobj = pobj->getProto()));
// Collect indexed property names.
pobj = obj;
do {
// Append dense elements.
NativeObject *nativeObj = &pobj->as<NativeObject>();
uint32_t initLen = nativeObj->getDenseInitializedLength();
for (uint32_t i = begin; i < initLen && i < end; i++) {
if (nativeObj->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE))
continue;
if (!indexes.append(i))
return false;
}
// Append typed array elements.
if (IsAnyTypedArray(pobj)) {
uint32_t len = AnyTypedArrayLength(pobj);
for (uint32_t i = begin; i < len && i < end; i++) {
if (!indexes.append(i))
return false;
}
}
// Append sparse elements.
if (pobj->isIndexed()) {
Shape::Range<NoGC> r(pobj->lastProperty());
for (; !r.empty(); r.popFront()) {
Shape &shape = r.front();
jsid id = shape.propid();
if (!JSID_IS_INT(id))
continue;
uint32_t i = uint32_t(JSID_TO_INT(id));
if (!(begin <= i && i < end))
continue;
// Watch out for getters, they can add new properties.
if (!shape.hasDefaultGetter())
return true;
if (!indexes.append(i))
return false;
}
}
} while ((pobj = pobj->getProto()));
// Sort the indexes.
Vector<uint32_t> tmp(cx);
size_t n = indexes.length();
if (!tmp.resize(n))
return false;
if (!MergeSort(indexes.begin(), n, tmp.begin(), SortComparatorIndexes()))
return false;
// Remove duplicates.
if (!indexes.empty()) {
uint32_t last = 0;
for (size_t i = 1, len = indexes.length(); i < len; i++) {
uint32_t elem = indexes[i];
if (indexes[last] != elem) {
last++;
indexes[last] = elem;
}
}
if (!indexes.resize(last + 1))
return false;
}
*success = true;
return true;
}
static bool
SliceSparse(JSContext *cx, HandleObject obj, uint32_t begin, uint32_t end, HandleObject result)
{
MOZ_ASSERT(begin <= end);
Vector<uint32_t> indexes(cx);
bool success;
if (!GetIndexedPropertiesInRange(cx, obj, begin, end, indexes, &success))
return false;
if (!success)
return SliceSlowly(cx, obj, obj, begin, end, result);
RootedValue value(cx);
for (size_t i = 0, len = indexes.length(); i < len; i++) {
uint32_t index = indexes[i];
MOZ_ASSERT(begin <= index && index < end);
bool hole;
if (!GetElement(cx, obj, obj, index, &hole, &value))
return false;
if (!hole && !JSObject::defineElement(cx, result, index - begin, value))
return false;
}
return true;
}
bool
js::array_slice(JSContext *cx, unsigned argc, Value *vp)
{
@ -2761,12 +2891,13 @@ js::array_slice(JSContext *cx, unsigned argc, Value *vp)
begin = end;
Rooted<ArrayObject*> narr(cx);
narr = NewDenseFullyAllocatedArray(cx, end - begin);
if (!narr)
return false;
TryReuseArrayType(obj, narr);
if (obj->is<ArrayObject>() && !ObjectMayHaveExtraIndexedProperties(obj)) {
narr = NewDenseFullyAllocatedArray(cx, end - begin);
if (!narr)
return false;
TryReuseArrayType(obj, narr);
ArrayObject *aobj = &obj->as<ArrayObject>();
if (aobj->getDenseInitializedLength() > begin) {
uint32_t numSourceElements = aobj->getDenseInitializedLength() - begin;
@ -2778,6 +2909,11 @@ js::array_slice(JSContext *cx, unsigned argc, Value *vp)
return true;
}
narr = NewDensePartlyAllocatedArray(cx, end - begin);
if (!narr)
return false;
TryReuseArrayType(obj, narr);
if (js::SliceOp op = obj->getOps()->slice) {
// Ensure that we have dense elements, so that DOM can use js::UnsafeDefineElement.
NativeObject::EnsureDenseResult result = narr->ensureDenseElements(cx, 0, end - begin);
@ -2796,9 +2932,13 @@ js::array_slice(JSContext *cx, unsigned argc, Value *vp)
MOZ_ASSERT(result == NativeObject::ED_SPARSE);
}
if (!SliceSlowly(cx, obj, obj, begin, end, narr))
return false;
if (obj->isNative() && obj->isIndexed() && end - begin > 1000) {
if (!SliceSparse(cx, obj, begin, end, narr))
return false;
} else {
if (!SliceSlowly(cx, obj, obj, begin, end, narr))
return false;
}
args.rval().setObject(*narr);
return true;