Lazily register prototype users
authorjkummerow <jkummerow@chromium.org>
Fri, 24 Apr 2015 12:51:55 +0000 (05:51 -0700)
committerCommit bot <commit-bot@chromium.org>
Fri, 24 Apr 2015 12:51:37 +0000 (12:51 +0000)
when handing out validity cells to handles; because invalidating said cells is the only time we'll need the user registrations.
Along the way, fix a corner case in WeakFixedArray, which can now be empty after the recently introduced compaction support.

Review URL: https://codereview.chromium.org/1104813004

Cr-Commit-Position: refs/heads/master@{#28047}

src/objects.cc
src/objects.h
test/cctest/test-heap.cc
test/mjsunit/prototype-changes.js [new file with mode: 0644]

index 4909c95..7e7fe22 100644 (file)
@@ -1936,7 +1936,7 @@ void JSObject::MigrateToMap(Handle<JSObject> object, Handle<Map> new_map,
     // Slow-to-slow migration is trivial.
     object->set_map(*new_map);
   }
-  if (old_map->is_prototype_map()) {
+  if (old_map->is_prototype_map() && FLAG_track_prototype_users) {
     DCHECK(new_map->is_prototype_map());
     DCHECK(object->map() == *new_map);
     new_map->set_prototype_info(old_map->prototype_info());
@@ -1947,6 +1947,20 @@ void JSObject::MigrateToMap(Handle<JSObject> object, Handle<Map> new_map,
              reinterpret_cast<void*>(*old_map),
              reinterpret_cast<void*>(*new_map));
     }
+    // If the map was registered with its prototype before, ensure that it
+    // registers with its new prototype now. This preserves the invariant that
+    // when a map on a prototype chain is registered with its prototype, then
+    // all prototypes further up the chain are also registered with their
+    // respective prototypes.
+    Object* maybe_old_prototype = old_map->prototype();
+    if (maybe_old_prototype->IsJSObject()) {
+      Handle<JSObject> old_prototype(JSObject::cast(maybe_old_prototype));
+      bool was_registered =
+          JSObject::UnregisterPrototypeUser(old_prototype, old_map);
+      if (was_registered) {
+        JSObject::LazyRegisterPrototypeUser(new_map, new_map->GetIsolate());
+      }
+    }
   }
 }
 
@@ -7086,12 +7100,6 @@ void Map::ConnectTransition(Handle<Map> parent, Handle<Map> child,
 #endif
   } else {
     TransitionArray::Insert(parent, name, child, flag);
-    if (child->prototype()->IsJSObject()) {
-      Handle<JSObject> proto(JSObject::cast(child->prototype()));
-      if (!ShouldRegisterAsPrototypeUser(child, proto)) {
-        JSObject::UnregisterPrototypeUser(proto, child);
-      }
-    }
 #if TRACE_MAPS
     Map::TraceTransition("Transition", *parent, *child, *name);
 #endif
@@ -8256,15 +8264,18 @@ void WeakFixedArray::Set(Handle<WeakFixedArray> array, int index,
 // static
 Handle<WeakFixedArray> WeakFixedArray::Add(
     Handle<Object> maybe_array, Handle<HeapObject> value,
-    SearchForDuplicates search_for_duplicates) {
+    SearchForDuplicates search_for_duplicates, bool* was_present) {
   Handle<WeakFixedArray> array =
       (maybe_array.is_null() || !maybe_array->IsWeakFixedArray())
           ? Allocate(value->GetIsolate(), 1, Handle<WeakFixedArray>::null())
           : Handle<WeakFixedArray>::cast(maybe_array);
-
+  if (was_present != NULL) *was_present = false;
   if (search_for_duplicates == kAddIfNotFound) {
     for (int i = 0; i < array->Length(); ++i) {
-      if (array->Get(i) == *value) return array;
+      if (array->Get(i) == *value) {
+        if (was_present != NULL) *was_present = true;
+        return array;
+      }
     }
 #if 0  // Enable this if you want to check your search_for_duplicates flags.
   } else {
@@ -8277,20 +8288,23 @@ Handle<WeakFixedArray> WeakFixedArray::Add(
   // Try to store the new entry if there's room. Optimize for consecutive
   // accesses.
   int first_index = array->last_used_index();
-  for (int i = first_index;;) {
-    if (array->IsEmptySlot((i))) {
-      WeakFixedArray::Set(array, i, value);
-      return array;
-    }
-    if (FLAG_trace_weak_arrays) {
-      PrintF("[WeakFixedArray: searching for free slot]\n");
+  if (array->Length() > 0) {
+    for (int i = first_index;;) {
+      if (array->IsEmptySlot((i))) {
+        WeakFixedArray::Set(array, i, value);
+        return array;
+      }
+      if (FLAG_trace_weak_arrays) {
+        PrintF("[WeakFixedArray: searching for free slot]\n");
+      }
+      i = (i + 1) % array->Length();
+      if (i == first_index) break;
     }
-    i = (i + 1) % array->Length();
-    if (i == first_index) break;
   }
 
   // No usable slot found, grow the array.
-  int new_length = array->Length() + (array->Length() >> 1) + 4;
+  int new_length =
+      array->Length() == 0 ? 1 : array->Length() + (array->Length() >> 1) + 4;
   Handle<WeakFixedArray> new_array =
       Allocate(array->GetIsolate(), new_length, array);
   if (FLAG_trace_weak_arrays) {
@@ -8315,7 +8329,8 @@ void WeakFixedArray::Compact() {
 }
 
 
-void WeakFixedArray::Remove(Handle<HeapObject> value) {
+bool WeakFixedArray::Remove(Handle<HeapObject> value) {
+  if (Length() == 0) return false;
   // Optimize for the most recently added element to be removed again.
   int first_index = last_used_index();
   for (int i = first_index;;) {
@@ -8323,11 +8338,12 @@ void WeakFixedArray::Remove(Handle<HeapObject> value) {
       clear(i);
       // Users of WeakFixedArray should make sure that there are no duplicates,
       // they can use Add(..., kAddIfNotFound) if necessary.
-      return;
+      return true;
     }
     i = (i + 1) % Length();
-    if (i == first_index) break;
+    if (i == first_index) return false;
   }
+  UNREACHABLE();
 }
 
 
@@ -10060,36 +10076,55 @@ void JSObject::ReoptimizeIfPrototype(Handle<JSObject> object) {
 
 
 // static
-void JSObject::RegisterPrototypeUser(Handle<JSObject> prototype,
-                                     Handle<HeapObject> user) {
+void JSObject::LazyRegisterPrototypeUser(Handle<Map> user, Isolate* isolate) {
   DCHECK(FLAG_track_prototype_users);
-  Isolate* isolate = prototype->GetIsolate();
-  if (prototype->IsJSGlobalProxy()) {
-    PrototypeIterator iter(isolate, prototype);
-    prototype = Handle<JSObject>::cast(PrototypeIterator::GetCurrent(iter));
-  }
-  Handle<PrototypeInfo> proto_info;
-  Object* maybe_proto_info = prototype->map()->prototype_info();
-  if (maybe_proto_info->IsPrototypeInfo()) {
-    proto_info = handle(PrototypeInfo::cast(maybe_proto_info), isolate);
-  } else {
-    proto_info = isolate->factory()->NewPrototypeInfo();
-    prototype->map()->set_prototype_info(*proto_info);
+  // Contract: In line with InvalidatePrototypeChains()'s requirements,
+  // leaf maps don't need to register as users, only prototypes do.
+  DCHECK(user->is_prototype_map());
+
+  Handle<Map> current_user = user;
+  for (PrototypeIterator iter(user); !iter.IsAtEnd(); iter.Advance()) {
+    Handle<Object> maybe_proto = PrototypeIterator::GetCurrent(iter);
+    if (maybe_proto->IsJSGlobalProxy()) continue;
+    // Proxies on the prototype chain are not supported.
+    if (maybe_proto->IsJSProxy()) return;
+    Handle<JSObject> proto = Handle<JSObject>::cast(maybe_proto);
+    bool just_registered =
+        RegisterPrototypeUserIfNotRegistered(proto, current_user, isolate);
+    // Walk up the prototype chain as far as links haven't been registered yet.
+    if (!just_registered) break;
+    current_user = handle(proto->map(), isolate);
   }
+}
+
+
+// Returns true if the user was not yet registered.
+// static
+bool JSObject::RegisterPrototypeUserIfNotRegistered(Handle<JSObject> prototype,
+                                                    Handle<HeapObject> user,
+                                                    Isolate* isolate) {
+  Handle<PrototypeInfo> proto_info =
+      Map::GetOrCreatePrototypeInfo(prototype, isolate);
   Handle<Object> maybe_registry(proto_info->prototype_users(), isolate);
-  Handle<WeakFixedArray> new_array = WeakFixedArray::Add(maybe_registry, user);
+  bool was_present = false;
+  Handle<WeakFixedArray> new_array = WeakFixedArray::Add(
+      maybe_registry, user, WeakFixedArray::kAddIfNotFound, &was_present);
   if (!maybe_registry.is_identical_to(new_array)) {
     proto_info->set_prototype_users(*new_array);
   }
-  if (FLAG_trace_prototype_users) {
-    PrintF("Registering %p as a user of prototype %p.\n",
-           reinterpret_cast<void*>(*user), reinterpret_cast<void*>(*prototype));
+  if (FLAG_trace_prototype_users && !was_present) {
+    PrintF("Registering %p as a user of prototype %p (map=%p).\n",
+           reinterpret_cast<void*>(*user), reinterpret_cast<void*>(*prototype),
+           reinterpret_cast<void*>(prototype->map()));
   }
+  return !was_present;
 }
 
 
+// Can be called regardless of whether |user| was actually registered with
+// |prototype|. Returns true when there was a registration.
 // static
-void JSObject::UnregisterPrototypeUser(Handle<JSObject> prototype,
+bool JSObject::UnregisterPrototypeUser(Handle<JSObject> prototype,
                                        Handle<HeapObject> user) {
   Isolate* isolate = prototype->GetIsolate();
   if (prototype->IsJSGlobalProxy()) {
@@ -10098,21 +10133,26 @@ void JSObject::UnregisterPrototypeUser(Handle<JSObject> prototype,
   }
   DCHECK(prototype->map()->is_prototype_map());
   Object* maybe_proto_info = prototype->map()->prototype_info();
-  if (!maybe_proto_info->IsPrototypeInfo()) return;
+  if (!maybe_proto_info->IsPrototypeInfo()) return false;
   Handle<PrototypeInfo> proto_info(PrototypeInfo::cast(maybe_proto_info),
                                    isolate);
   Object* maybe_registry = proto_info->prototype_users();
-  if (!maybe_registry->IsWeakFixedArray()) return;
-  WeakFixedArray::cast(maybe_registry)->Remove(user);
-  if (FLAG_trace_prototype_users) {
+  if (!maybe_registry->IsWeakFixedArray()) return false;
+  bool result = WeakFixedArray::cast(maybe_registry)->Remove(user);
+  if (FLAG_trace_prototype_users && result) {
     PrintF("Unregistering %p as a user of prototype %p.\n",
            reinterpret_cast<void*>(*user), reinterpret_cast<void*>(*prototype));
   }
+  return result;
 }
 
 
 static void InvalidatePrototypeChainsInternal(Map* map) {
   if (!map->is_prototype_map()) return;
+  if (FLAG_trace_prototype_users) {
+    PrintF("Invalidating prototype map %p 's cell\n",
+           reinterpret_cast<void*>(map));
+  }
   Object* maybe_proto_info = map->prototype_info();
   if (!maybe_proto_info->IsPrototypeInfo()) return;
   PrototypeInfo* proto_info = PrototypeInfo::cast(maybe_proto_info);
@@ -10152,6 +10192,19 @@ void JSObject::InvalidatePrototypeChains(Map* map) {
 
 
 // static
+Handle<PrototypeInfo> Map::GetOrCreatePrototypeInfo(Handle<JSObject> prototype,
+                                                    Isolate* isolate) {
+  Object* maybe_proto_info = prototype->map()->prototype_info();
+  if (maybe_proto_info->IsPrototypeInfo()) {
+    return handle(PrototypeInfo::cast(maybe_proto_info), isolate);
+  }
+  Handle<PrototypeInfo> proto_info = isolate->factory()->NewPrototypeInfo();
+  prototype->map()->set_prototype_info(*proto_info);
+  return proto_info;
+}
+
+
+// static
 Handle<Cell> Map::GetOrCreatePrototypeChainValidityCell(Handle<Map> map,
                                                         Isolate* isolate) {
   Handle<Object> maybe_prototype(map->prototype(), isolate);
@@ -10161,14 +10214,18 @@ Handle<Cell> Map::GetOrCreatePrototypeChainValidityCell(Handle<Map> map,
     PrototypeIterator iter(isolate, prototype);
     prototype = Handle<JSObject>::cast(PrototypeIterator::GetCurrent(iter));
   }
-  Handle<PrototypeInfo> proto_info(
-      PrototypeInfo::cast(prototype->map()->prototype_info()), isolate);
+  // Ensure the prototype is registered with its own prototypes so its cell
+  // will be invalidated when necessary.
+  JSObject::LazyRegisterPrototypeUser(handle(prototype->map(), isolate),
+                                      isolate);
+  Handle<PrototypeInfo> proto_info =
+      GetOrCreatePrototypeInfo(prototype, isolate);
   Object* maybe_cell = proto_info->validity_cell();
   // Return existing cell if it's still valid.
   if (maybe_cell->IsCell()) {
     Handle<Cell> cell(Cell::cast(maybe_cell), isolate);
     if (cell->value() == Smi::FromInt(Map::kPrototypeChainValid)) {
-      return handle(Cell::cast(maybe_cell), isolate);
+      return cell;
     }
   }
   // Otherwise create a new cell.
@@ -10179,18 +10236,12 @@ Handle<Cell> Map::GetOrCreatePrototypeChainValidityCell(Handle<Map> map,
 }
 
 
+// static
 void Map::SetPrototype(Handle<Map> map, Handle<Object> prototype,
                        PrototypeOptimizationMode proto_mode) {
-  if (map->prototype()->IsJSObject() && FLAG_track_prototype_users) {
-    Handle<JSObject> old_prototype(JSObject::cast(map->prototype()));
-    JSObject::UnregisterPrototypeUser(old_prototype, map);
-  }
   if (prototype->IsJSObject()) {
     Handle<JSObject> prototype_jsobj = Handle<JSObject>::cast(prototype);
     JSObject::OptimizeAsPrototype(prototype_jsobj, proto_mode);
-    if (ShouldRegisterAsPrototypeUser(map, prototype_jsobj)) {
-      JSObject::RegisterPrototypeUser(prototype_jsobj, map);
-    }
   }
   WriteBarrierMode wb_mode =
       prototype->IsNull() ? SKIP_WRITE_BARRIER : UPDATE_WRITE_BARRIER;
@@ -10198,18 +10249,6 @@ void Map::SetPrototype(Handle<Map> map, Handle<Object> prototype,
 }
 
 
-// static
-bool Map::ShouldRegisterAsPrototypeUser(Handle<Map> map,
-                                        Handle<JSObject> prototype) {
-  if (!FLAG_track_prototype_users) return false;
-  if (map->is_prototype_map()) return true;
-  Object* back = map->GetBackPointer();
-  if (!back->IsMap()) return true;
-  if (Map::cast(back)->prototype() != *prototype) return true;
-  return false;
-}
-
-
 Handle<Object> CacheInitialJSArrayMaps(
     Handle<Context> native_context, Handle<Map> initial_map) {
   // Replace all of the cached initial array maps in the native context with
index bfec772..efa0cae 100644 (file)
@@ -1841,9 +1841,11 @@ class JSObject: public JSReceiver {
   static void OptimizeAsPrototype(Handle<JSObject> object,
                                   PrototypeOptimizationMode mode);
   static void ReoptimizeIfPrototype(Handle<JSObject> object);
-  static void RegisterPrototypeUser(Handle<JSObject> prototype,
-                                    Handle<HeapObject> user);
-  static void UnregisterPrototypeUser(Handle<JSObject> prototype,
+  static void LazyRegisterPrototypeUser(Handle<Map> user, Isolate* isolate);
+  static bool RegisterPrototypeUserIfNotRegistered(Handle<JSObject> prototype,
+                                                   Handle<HeapObject> user,
+                                                   Isolate* isolate);
+  static bool UnregisterPrototypeUser(Handle<JSObject> prototype,
                                       Handle<HeapObject> user);
   static void InvalidatePrototypeChains(Map* map);
 
@@ -2621,9 +2623,11 @@ class WeakFixedArray : public FixedArray {
   // If |maybe_array| is not a WeakFixedArray, a fresh one will be allocated.
   static Handle<WeakFixedArray> Add(
       Handle<Object> maybe_array, Handle<HeapObject> value,
-      SearchForDuplicates search_for_duplicates = kAlwaysAdd);
+      SearchForDuplicates search_for_duplicates = kAlwaysAdd,
+      bool* was_present = NULL);
 
-  void Remove(Handle<HeapObject> value);
+  // Returns true if an entry was found and removed.
+  bool Remove(Handle<HeapObject> value);
 
   void Compact();
 
@@ -5844,6 +5848,9 @@ class DependentCode: public FixedArray {
 };
 
 
+class PrototypeInfo;
+
+
 // All heap objects have a Map that describes their structure.
 //  A Map contains information about:
 //  - Size information about the object
@@ -6051,6 +6058,10 @@ class Map: public HeapObject {
   // [prototype_info]: Per-prototype metadata. Aliased with transitions
   // (which prototype maps don't have).
   DECL_ACCESSORS(prototype_info, Object)
+  // PrototypeInfo is created lazily using this helper (which installs it on
+  // the given prototype's map).
+  static Handle<PrototypeInfo> GetOrCreatePrototypeInfo(
+      Handle<JSObject> prototype, Isolate* isolate);
 
   // [prototype chain validity cell]: Associated with a prototype object,
   // stored in that object's map's PrototypeInfo, indicates that prototype
@@ -6123,9 +6134,6 @@ class Map: public HeapObject {
   static void SetPrototype(
       Handle<Map> map, Handle<Object> prototype,
       PrototypeOptimizationMode proto_mode = FAST_PROTOTYPE);
-  static bool ShouldRegisterAsPrototypeUser(Handle<Map> map,
-                                            Handle<JSObject> prototype);
-  bool CanUseOptimizationsBasedOnPrototypeRegistry();
 
   // [constructor]: points back to the function responsible for this map.
   // The field overlaps with the back pointer. All maps in a transition tree
index 21fa4ce..b8d7676 100644 (file)
@@ -5367,3 +5367,15 @@ TEST(Regress472513) {
   TestRightTrimFixedTypedArray(i::kExternalUint16Array, 8 - 1, 3);
   TestRightTrimFixedTypedArray(i::kExternalUint32Array, 4, 3);
 }
+
+
+TEST(WeakFixedArray) {
+  CcTest::InitializeVM();
+  v8::HandleScope scope(CcTest::isolate());
+
+  Handle<HeapNumber> number = CcTest::i_isolate()->factory()->NewHeapNumber(1);
+  Handle<WeakFixedArray> array = WeakFixedArray::Add(Handle<Object>(), number);
+  array->Remove(number);
+  array->Compact();
+  WeakFixedArray::Add(array, number);
+}
diff --git a/test/mjsunit/prototype-changes.js b/test/mjsunit/prototype-changes.js
new file mode 100644 (file)
index 0000000..e7fcc7e
--- /dev/null
@@ -0,0 +1,56 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Flags: --allow-natives-syntax
+
+function A() {
+  this.a = "a";
+}
+var a = new A();
+
+function B() {
+  this.b = "b";
+}
+B.prototype = a;
+
+function C() {
+  this.c = "c";
+}
+C.prototype = new B();
+
+var c = new C();
+
+function f(expected) {
+  var result = c.z;
+  assertEquals(expected, result);
+}
+f(undefined);
+f(undefined);
+%OptimizeFunctionOnNextCall(f);
+f(undefined);
+a.z = "z";
+f("z");
+f("z");
+
+// Test updating .__proto__ pointers.
+var p1 = {foo: 1.5};
+var p2 = {}; p2.__proto__ = p1;
+var p3 = {}; p3.__proto__ = p2;
+var o = {}; o.__proto__ = p3;
+
+for (var i = 0; i < 2; i++) o.foo;  // Force registration.
+
+var p1a = {foo: 1.7};
+p2.__proto__ = p1a;
+
+function g(o, expected) {
+  var result = o.foo;
+  assertEquals(expected, result);
+}
+
+g(o, 1.7);
+g(o, 1.7);
+g(o, 1.7);
+Object.defineProperty(p1a, "foo", {get: function() { return "foo"}});
+g(o, "foo");