Bug 1157703 - Cache iterators involving unboxed objects, r=jandem.
This commit is contained in:
@@ -62,6 +62,9 @@ NativeIterator::mark(JSTracer* trc)
|
||||
if (obj)
|
||||
TraceEdge(trc, &obj, "obj");
|
||||
|
||||
for (size_t i = 0; i < guard_length; i++)
|
||||
guard_array[i].trace(trc);
|
||||
|
||||
// The SuppressDeletedPropertyHelper loop can GC, so make sure that if the
|
||||
// GC removes any elements from the list, it won't remove this one.
|
||||
if (iterObj_)
|
||||
@@ -531,10 +534,12 @@ NewPropertyIteratorObject(JSContext* cx, unsigned flags)
|
||||
}
|
||||
|
||||
NativeIterator*
|
||||
NativeIterator::allocateIterator(JSContext* cx, uint32_t slength, const AutoIdVector& props)
|
||||
NativeIterator::allocateIterator(JSContext* cx, uint32_t numGuards, const AutoIdVector& props)
|
||||
{
|
||||
JS_STATIC_ASSERT(sizeof(ReceiverGuard) == 2 * sizeof(void*));
|
||||
|
||||
size_t plength = props.length();
|
||||
NativeIterator* ni = cx->zone()->pod_malloc_with_extra<NativeIterator, void*>(plength + slength);
|
||||
NativeIterator* ni = cx->zone()->pod_malloc_with_extra<NativeIterator, void*>(plength + numGuards * 2);
|
||||
if (!ni)
|
||||
return nullptr;
|
||||
|
||||
@@ -572,14 +577,14 @@ NativeIterator::allocateSentinel(JSContext* maybecx)
|
||||
}
|
||||
|
||||
inline void
|
||||
NativeIterator::init(JSObject* obj, JSObject* iterObj, unsigned flags, uint32_t slength, uint32_t key)
|
||||
NativeIterator::init(JSObject* obj, JSObject* iterObj, unsigned flags, uint32_t numGuards, uint32_t key)
|
||||
{
|
||||
this->obj.init(obj);
|
||||
this->iterObj_ = iterObj;
|
||||
this->flags = flags;
|
||||
this->shapes_array = (Shape**) this->props_end;
|
||||
this->shapes_length = slength;
|
||||
this->shapes_key = key;
|
||||
this->guard_array = (HeapReceiverGuard*) this->props_end;
|
||||
this->guard_length = numGuards;
|
||||
this->guard_key = key;
|
||||
}
|
||||
|
||||
static inline void
|
||||
@@ -596,7 +601,7 @@ RegisterEnumerator(JSContext* cx, PropertyIteratorObject* iterobj, NativeIterato
|
||||
|
||||
static inline bool
|
||||
VectorToKeyIterator(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVector& keys,
|
||||
uint32_t slength, uint32_t key, MutableHandleObject objp)
|
||||
uint32_t numGuards, uint32_t key, MutableHandleObject objp)
|
||||
{
|
||||
MOZ_ASSERT(!(flags & JSITER_FOREACH));
|
||||
|
||||
@@ -608,26 +613,20 @@ VectorToKeyIterator(JSContext* cx, HandleObject obj, unsigned flags, AutoIdVecto
|
||||
if (!iterobj)
|
||||
return false;
|
||||
|
||||
NativeIterator* ni = NativeIterator::allocateIterator(cx, slength, keys);
|
||||
NativeIterator* ni = NativeIterator::allocateIterator(cx, numGuards, keys);
|
||||
if (!ni)
|
||||
return false;
|
||||
ni->init(obj, iterobj, flags, slength, key);
|
||||
ni->init(obj, iterobj, flags, numGuards, key);
|
||||
|
||||
if (slength) {
|
||||
/*
|
||||
* Fill in the shape array from scratch. We can't use the array that was
|
||||
* computed for the cache lookup earlier, as constructing iterobj could
|
||||
* have triggered a shape-regenerating GC. Don't bother with regenerating
|
||||
* the shape key; if such a GC *does* occur, we can only get hits through
|
||||
* the one-slot lastNativeIterator cache.
|
||||
*/
|
||||
if (numGuards) {
|
||||
// Fill in the guard array from scratch.
|
||||
JSObject* pobj = obj;
|
||||
size_t ind = 0;
|
||||
do {
|
||||
ni->shapes_array[ind++] = pobj->as<NativeObject>().lastProperty();
|
||||
ni->guard_array[ind++].init(ReceiverGuard(pobj));
|
||||
pobj = pobj->getProto();
|
||||
} while (pobj);
|
||||
MOZ_ASSERT(ind == slength);
|
||||
MOZ_ASSERT(ind == numGuards);
|
||||
}
|
||||
|
||||
iterobj->setNativeIterator(ni);
|
||||
@@ -702,6 +701,37 @@ UpdateNativeIterator(NativeIterator* ni, JSObject* obj)
|
||||
ni->obj = obj;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
CanCompareIterableObjectToCache(JSObject* obj)
|
||||
{
|
||||
if (obj->isNative())
|
||||
return obj->as<NativeObject>().hasEmptyElements();
|
||||
if (obj->is<UnboxedPlainObject>()) {
|
||||
if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
|
||||
return expando->hasEmptyElements();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
CanCacheIterableObject(JSContext* cx, JSObject* obj)
|
||||
{
|
||||
if (!CanCompareIterableObjectToCache(obj))
|
||||
return false;
|
||||
if (obj->isNative()) {
|
||||
if (IsAnyTypedArray(obj) ||
|
||||
obj->hasUncacheableProto() ||
|
||||
obj->getOps()->enumerate ||
|
||||
obj->getClass()->enumerate ||
|
||||
obj->as<NativeObject>().containsPure(cx->names().iteratorIntrinsic))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleObject objp)
|
||||
{
|
||||
@@ -720,27 +750,21 @@ js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleOb
|
||||
return Proxy::enumerate(cx, obj, objp);
|
||||
}
|
||||
|
||||
Vector<Shape*, 8> shapes(cx);
|
||||
Vector<ReceiverGuard, 8> guards(cx);
|
||||
uint32_t key = 0;
|
||||
if (flags == JSITER_ENUMERATE) {
|
||||
/*
|
||||
* Check to see if this is the same as the most recent object which
|
||||
* was iterated over. We don't explicitly check for shapeless
|
||||
* objects here, as they are not inserted into the cache and
|
||||
* will result in a miss.
|
||||
*/
|
||||
// Check to see if this is the same as the most recent object which was
|
||||
// iterated over.
|
||||
PropertyIteratorObject* last = cx->runtime()->nativeIterCache.last;
|
||||
if (last) {
|
||||
NativeIterator* lastni = last->getNativeIterator();
|
||||
if (!(lastni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
|
||||
obj->isNative() &&
|
||||
obj->as<NativeObject>().hasEmptyElements() &&
|
||||
obj->as<NativeObject>().lastProperty() == lastni->shapes_array[0])
|
||||
CanCompareIterableObjectToCache(obj) &&
|
||||
ReceiverGuard(obj) == lastni->guard_array[0])
|
||||
{
|
||||
JSObject* proto = obj->getProto();
|
||||
if (proto->isNative() &&
|
||||
proto->as<NativeObject>().hasEmptyElements() &&
|
||||
proto->as<NativeObject>().lastProperty() == lastni->shapes_array[1] &&
|
||||
if (CanCompareIterableObjectToCache(proto) &&
|
||||
ReceiverGuard(proto) == lastni->guard_array[1] &&
|
||||
!proto->getProto())
|
||||
{
|
||||
objp.set(last);
|
||||
@@ -760,20 +784,13 @@ js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleOb
|
||||
{
|
||||
JSObject* pobj = obj;
|
||||
do {
|
||||
if (!pobj->isNative() ||
|
||||
!pobj->as<NativeObject>().hasEmptyElements() ||
|
||||
IsAnyTypedArray(pobj) ||
|
||||
pobj->hasUncacheableProto() ||
|
||||
pobj->getOps()->enumerate ||
|
||||
pobj->getClass()->enumerate ||
|
||||
pobj->as<NativeObject>().containsPure(cx->names().iteratorIntrinsic))
|
||||
{
|
||||
shapes.clear();
|
||||
if (!CanCacheIterableObject(cx, pobj)) {
|
||||
guards.clear();
|
||||
goto miss;
|
||||
}
|
||||
Shape* shape = pobj->as<NativeObject>().lastProperty();
|
||||
key = (key + (key << 16)) ^ (uintptr_t(shape) >> 3);
|
||||
if (!shapes.append(shape))
|
||||
ReceiverGuard guard(pobj);
|
||||
key = (key + (key << 16)) ^ guard.hash();
|
||||
if (!guards.append(guard))
|
||||
return false;
|
||||
pobj = pobj->getProto();
|
||||
} while (pobj);
|
||||
@@ -783,14 +800,16 @@ js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleOb
|
||||
if (iterobj) {
|
||||
NativeIterator* ni = iterobj->getNativeIterator();
|
||||
if (!(ni->flags & (JSITER_ACTIVE|JSITER_UNREUSABLE)) &&
|
||||
ni->shapes_key == key &&
|
||||
ni->shapes_length == shapes.length() &&
|
||||
Compare(ni->shapes_array, shapes.begin(), ni->shapes_length)) {
|
||||
ni->guard_key == key &&
|
||||
ni->guard_length == guards.length() &&
|
||||
Compare(reinterpret_cast<ReceiverGuard*>(ni->guard_array),
|
||||
guards.begin(), ni->guard_length))
|
||||
{
|
||||
objp.set(iterobj);
|
||||
|
||||
UpdateNativeIterator(ni, obj);
|
||||
RegisterEnumerator(cx, iterobj, ni);
|
||||
if (shapes.length() == 2)
|
||||
if (guards.length() == 2)
|
||||
cx->runtime()->nativeIterCache.last = iterobj;
|
||||
return true;
|
||||
}
|
||||
@@ -805,7 +824,7 @@ js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleOb
|
||||
|
||||
AutoIdVector keys(cx);
|
||||
if (flags & JSITER_FOREACH) {
|
||||
MOZ_ASSERT(shapes.empty());
|
||||
MOZ_ASSERT(guards.empty());
|
||||
|
||||
if (!Snapshot(cx, obj, flags, &keys))
|
||||
return false;
|
||||
@@ -814,17 +833,17 @@ js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags, MutableHandleOb
|
||||
} else {
|
||||
if (!Snapshot(cx, obj, flags, &keys))
|
||||
return false;
|
||||
if (!VectorToKeyIterator(cx, obj, flags, keys, shapes.length(), key, objp))
|
||||
if (!VectorToKeyIterator(cx, obj, flags, keys, guards.length(), key, objp))
|
||||
return false;
|
||||
}
|
||||
|
||||
PropertyIteratorObject* iterobj = &objp->as<PropertyIteratorObject>();
|
||||
|
||||
/* Cache the iterator object if possible. */
|
||||
if (shapes.length())
|
||||
if (guards.length())
|
||||
cx->runtime()->nativeIterCache.set(key, iterobj);
|
||||
|
||||
if (shapes.length() == 2)
|
||||
if (guards.length() == 2)
|
||||
cx->runtime()->nativeIterCache.last = iterobj;
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user