Bug 1375505 part 8 - Use iterator cache also for objects with the hasUncacheableProto flag. r=anba
authorJan de Mooij <jdemooij@mozilla.com>
Wed, 12 Jul 2017 19:37:43 +0200
changeset 607732 77447df746ccdb06887713577d439775d9ff3c2f
parent 607731 ea3f1df8382342cc525bf7b650ac8bb2a8daa30f
child 607733 e4bd519bfbfa9e3e9ae4a18bb63c4f0a57015715
push id68095
push userbmo:rbarker@mozilla.com
push dateWed, 12 Jul 2017 20:01:47 +0000
reviewersanba
bugs1375505
milestone56.0a1
Bug 1375505 part 8 - Use iterator cache also for objects with the hasUncacheableProto flag. r=anba
js/src/jit-test/tests/basic/iter-cache-null-proto.js
js/src/jsiter.cpp
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/iter-cache-null-proto.js
@@ -0,0 +1,11 @@
+function f() {
+    var o = {x: 0};
+    for (var i = 0; i < 20; i++) {
+        if ((i % 4) === 0)
+            Object.setPrototypeOf(o, null);
+        else
+            Object.setPrototypeOf(o, Object.prototype);
+        for (var x in o) {}
+    }
+}
+f();
--- a/js/src/jsiter.cpp
+++ b/js/src/jsiter.cpp
@@ -860,16 +860,34 @@ CanCompareIterableObjectToCache(JSObject
     if (obj->is<UnboxedPlainObject>()) {
         if (UnboxedExpandoObject* expando = obj->as<UnboxedPlainObject>().maybeExpando())
             return expando->hasEmptyElements();
         return true;
     }
     return false;
 }
 
+static MOZ_ALWAYS_INLINE void
+UpdateLastCachedNativeIterator(JSContext* cx, JSObject* obj, PropertyIteratorObject* iterobj)
+{
+    // lastCachedNativeIterator is only used when there's a single object on
+    // the prototype chain, to simplify JIT code.
+    if (iterobj->getNativeIterator()->guard_length != 2)
+        return;
+
+    // Both GetIterator and JIT code assume the receiver has a non-null proto,
+    // so we have to make sure a Shape change is triggered when the proto
+    // changes. Note that this does not apply to the object on the proto chain
+    // because we always check it has a null proto.
+    if (obj->hasUncacheableProto())
+        return;
+
+    cx->compartment()->lastCachedNativeIterator = iterobj;
+}
+
 using ReceiverGuardVector = Vector<ReceiverGuard, 8>;
 
 static MOZ_ALWAYS_INLINE PropertyIteratorObject*
 LookupInIteratorCache(JSContext* cx, JSObject* obj, ReceiverGuardVector& guards, uint32_t* keyArg)
 {
     MOZ_ASSERT(guards.empty());
 
     // The iterator object for JSITER_ENUMERATE never escapes, so we don't
@@ -912,18 +930,17 @@ LookupInIteratorCache(JSContext* cx, JSO
         iterobj->compartment() != cx->compartment())
     {
         return nullptr;
     }
 
     UpdateNativeIterator(ni, obj);
     RegisterEnumerator(cx, iterobj, ni);
 
-    if (guards.length() == 2)
-        cx->compartment()->lastCachedNativeIterator = iterobj;
+    UpdateLastCachedNativeIterator(cx, obj, iterobj);
 
     return iterobj;
 }
 
 static bool
 CanStoreInIteratorCache(JSContext* cx, JSObject* obj)
 {
     do {
@@ -933,19 +950,16 @@ CanStoreInIteratorCache(JSContext* cx, J
             // Typed arrays have indexed properties not captured by the Shape guard.
             // Enumerate hooks may add extra properties.
             const Class* clasp = obj->getClass();
             if (MOZ_UNLIKELY(IsTypedArrayClass(clasp)))
                 return false;
             if (MOZ_UNLIKELY(clasp->getNewEnumerate() || clasp->getEnumerate()))
                 return false;
 
-            if (obj->hasUncacheableProto())
-                return false;
-
             if (MOZ_UNLIKELY(obj->as<NativeObject>().containsPure(cx->names().iteratorIntrinsic)))
                 return false;
         } else {
             MOZ_ASSERT(obj->is<UnboxedPlainObject>());
         }
 
         obj = obj->staticPrototype();
     } while (obj);
@@ -956,18 +970,17 @@ CanStoreInIteratorCache(JSContext* cx, J
 static void
 StoreInIteratorCache(JSContext* cx, JSObject* obj, uint32_t key, PropertyIteratorObject* iterobj)
 {
     MOZ_ASSERT(CanStoreInIteratorCache(cx, obj));
     MOZ_ASSERT(iterobj->getNativeIterator()->guard_length > 0);
 
     cx->caches().nativeIterCache.set(key, iterobj);
 
-    if (iterobj->getNativeIterator()->guard_length == 2)
-        cx->compartment()->lastCachedNativeIterator = iterobj;
+    UpdateLastCachedNativeIterator(cx, obj, iterobj);
 }
 
 JSObject*
 js::GetIterator(JSContext* cx, HandleObject obj, unsigned flags)
 {
     ReceiverGuardVector guards(cx);
     uint32_t key = 0;
     if (flags == JSITER_ENUMERATE) {