Implement IdentityMap<V>, a robust, GC-safe object-identity HashMap.
authortitzer <titzer@chromium.org>
Wed, 6 May 2015 12:40:21 +0000 (05:40 -0700)
committerCommit bot <commit-bot@chromium.org>
Wed, 6 May 2015 12:40:29 +0000 (12:40 +0000)
R=hpayer@chromium.org, erikcorry@chromium.org
BUG=

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

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

BUILD.gn
src/heap/heap.cc
src/heap/heap.h
src/heap/identity-map.cc [new file with mode: 0644]
src/heap/identity-map.h [new file with mode: 0644]
src/objects.h
test/cctest/cctest.gyp
test/cctest/test-identity-map.cc [new file with mode: 0644]
tools/gyp/v8.gyp

index 9b52661..a26f708 100644 (file)
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -783,6 +783,8 @@ source_set("v8_base") {
     "src/heap/heap-inl.h",
     "src/heap/heap.cc",
     "src/heap/heap.h",
+    "src/heap/identity-map.cc",
+    "src/heap/identity-map.h",
     "src/heap/incremental-marking.cc",
     "src/heap/incremental-marking.h",
     "src/heap/mark-compact-inl.h",
index e5095c8..6a1aa33 100644 (file)
@@ -54,6 +54,13 @@ namespace v8 {
 namespace internal {
 
 
+struct Heap::StrongRootsList {
+  Object** start;
+  Object** end;
+  StrongRootsList* next;
+};
+
+
 Heap::Heap()
     : amount_of_external_allocated_memory_(0),
       amount_of_external_allocated_memory_at_last_global_gc_(0),
@@ -141,7 +148,8 @@ Heap::Heap()
       chunks_queued_for_free_(NULL),
       gc_callbacks_depth_(0),
       deserialization_complete_(false),
-      concurrent_sweeping_enabled_(false) {
+      concurrent_sweeping_enabled_(false),
+      strong_roots_list_(NULL) {
 // Allow build-time customization of the max semispace size. Building
 // V8 with snapshots and a non-default max semispace size is much
 // easier if you can define it as part of the build environment.
@@ -5027,6 +5035,12 @@ void Heap::IterateStrongRoots(ObjectVisitor* v, VisitMode mode) {
   isolate_->thread_manager()->Iterate(v);
   v->Synchronize(VisitorSynchronization::kThreadManager);
 
+  // Iterate over other strong roots (currently only identity maps).
+  for (StrongRootsList* list = strong_roots_list_; list; list = list->next) {
+    v->VisitPointers(list->start, list->end);
+  }
+  v->Synchronize(VisitorSynchronization::kStrongRoots);
+
   // Iterate over the pointers the Serialization/Deserialization code is
   // holding.
   // During garbage collection this keeps the partial snapshot cache alive.
@@ -5541,6 +5555,13 @@ void Heap::TearDown() {
   store_buffer()->TearDown();
 
   isolate_->memory_allocator()->TearDown();
+
+  StrongRootsList* next = NULL;
+  for (StrongRootsList* list = strong_roots_list_; list; list = next) {
+    next = list->next;
+    delete list;
+  }
+  strong_roots_list_ = NULL;
 }
 
 
@@ -6439,5 +6460,34 @@ void Heap::CheckpointObjectStats() {
   MemCopy(object_sizes_last_time_, object_sizes_, sizeof(object_sizes_));
   ClearObjectStats();
 }
+
+
+void Heap::RegisterStrongRoots(Object** start, Object** end) {
+  StrongRootsList* list = new StrongRootsList();
+  list->next = strong_roots_list_;
+  list->start = start;
+  list->end = end;
+  strong_roots_list_ = list;
+}
+
+
+void Heap::UnregisterStrongRoots(Object** start) {
+  StrongRootsList* prev = NULL;
+  StrongRootsList* list = strong_roots_list_;
+  while (list != nullptr) {
+    StrongRootsList* next = list->next;
+    if (list->start == start) {
+      if (prev) {
+        prev->next = next;
+      } else {
+        strong_roots_list_ = next;
+      }
+      delete list;
+    } else {
+      prev = list;
+    }
+    list = next;
+  }
+}
 }
 }  // namespace v8::internal
index 71ee26e..689f14e 100644 (file)
@@ -1446,21 +1446,42 @@ class Heap {
   void TraceObjectStat(const char* name, int count, int size, double time);
   void CheckpointObjectStats();
 
-  // We don't use a LockGuard here since we want to lock the heap
-  // only when FLAG_concurrent_recompilation is true.
+  void RegisterStrongRoots(Object** start, Object** end);
+  void UnregisterStrongRoots(Object** start);
+
+  // Taking this lock prevents the GC from entering a phase that relocates
+  // object references.
   class RelocationLock {
    public:
     explicit RelocationLock(Heap* heap) : heap_(heap) {
       heap_->relocation_mutex_.Lock();
     }
 
-
     ~RelocationLock() { heap_->relocation_mutex_.Unlock(); }
 
    private:
     Heap* heap_;
   };
 
+  // An optional version of the above lock that can be used for some critical
+  // sections on the mutator thread; only safe since the GC currently does not
+  // do concurrent compaction.
+  class OptionalRelocationLock {
+   public:
+    OptionalRelocationLock(Heap* heap, bool concurrent)
+        : heap_(heap), concurrent_(concurrent) {
+      if (concurrent_) heap_->relocation_mutex_.Lock();
+    }
+
+    ~OptionalRelocationLock() {
+      if (concurrent_) heap_->relocation_mutex_.Unlock();
+    }
+
+   private:
+    Heap* heap_;
+    bool concurrent_;
+  };
+
   void AddWeakObjectToCodeDependency(Handle<HeapObject> obj,
                                      Handle<DependentCode> dep);
 
@@ -2154,6 +2175,9 @@ class Heap {
   std::map<void*, size_t> live_array_buffers_;
   std::map<void*, size_t> not_yet_discovered_array_buffers_;
 
+  struct StrongRootsList;
+  StrongRootsList* strong_roots_list_;
+
   friend class AlwaysAllocateScope;
   friend class Deserializer;
   friend class Factory;
diff --git a/src/heap/identity-map.cc b/src/heap/identity-map.cc
new file mode 100644 (file)
index 0000000..e968989
--- /dev/null
@@ -0,0 +1,191 @@
+// 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.
+
+#include "src/v8.h"
+
+#include "src/heap/heap.h"
+#include "src/heap/identity-map.h"
+
+namespace v8 {
+namespace internal {
+
+static const int kInitialIdentityMapSize = 4;
+static const int kResizeFactor = 4;
+
+IdentityMapBase::~IdentityMapBase() {
+  if (keys_) {
+    Heap::OptionalRelocationLock lock(heap_, concurrent_);
+    heap_->UnregisterStrongRoots(keys_);
+  }
+}
+
+
+IdentityMapBase::RawEntry IdentityMapBase::Lookup(Handle<Object> key) {
+  AllowHandleDereference for_lookup;
+  int index = LookupIndex(*key);
+  return index >= 0 ? &values_[index] : nullptr;
+}
+
+
+IdentityMapBase::RawEntry IdentityMapBase::Insert(Handle<Object> key) {
+  AllowHandleDereference for_lookup;
+  int index = InsertIndex(*key);
+  DCHECK_GE(index, 0);
+  return &values_[index];
+}
+
+
+int IdentityMapBase::Hash(Object* address) {
+  uintptr_t raw_address = reinterpret_cast<uintptr_t>(address);
+  CHECK_NE(0, raw_address);  // Cannot store Smi 0 as a key here, sorry.
+  // Xor some of the upper bits, since the lower 2 or 3 are usually aligned.
+  return static_cast<int>((raw_address >> 11) ^ raw_address);
+}
+
+
+int IdentityMapBase::LookupIndex(Object* address) {
+  int start = Hash(address) & mask_;
+  for (int index = start; index < size_; index++) {
+    if (keys_[index] == address) return index;  // Found.
+    if (keys_[index] == nullptr) return -1;     // Not found.
+  }
+  for (int index = 0; index < start; index++) {
+    if (keys_[index] == address) return index;  // Found.
+    if (keys_[index] == nullptr) return -1;     // Not found.
+  }
+  return -1;
+}
+
+
+int IdentityMapBase::InsertIndex(Object* address) {
+  while (true) {
+    int start = Hash(address) & mask_;
+    int limit = size_ / 2;
+    // Search up to {limit} entries.
+    for (int index = start; --limit > 0; index = (index + 1) & mask_) {
+      if (keys_[index] == address) return index;  // Found.
+      if (keys_[index] == nullptr) {              // Free entry.
+        keys_[index] = address;
+        return index;
+      }
+    }
+    Resize();  // Should only have to resize once, since we grow 4x.
+  }
+  UNREACHABLE();
+  return -1;
+}
+
+
+// Searches this map for the given key using the object's address
+// as the identity, returning:
+//    found => a pointer to the storage location for the value
+//    not found => a pointer to a new storage location for the value
+IdentityMapBase::RawEntry IdentityMapBase::GetEntry(Handle<Object> key) {
+  Heap::OptionalRelocationLock lock(heap_, concurrent_);
+  RawEntry result;
+  if (size_ == 0) {
+    // Allocate the initial storage for keys and values.
+    size_ = kInitialIdentityMapSize;
+    mask_ = kInitialIdentityMapSize - 1;
+    gc_counter_ = heap_->gc_count();
+
+    keys_ = zone_->NewArray<Object*>(size_);
+    memset(keys_, 0, sizeof(Object*) * size_);
+    values_ = zone_->NewArray<void*>(size_);
+    memset(values_, 0, sizeof(void*) * size_);
+
+    heap_->RegisterStrongRoots(keys_, keys_ + size_);
+    result = Insert(key);
+  } else {
+    // Perform an optimistic lookup.
+    result = Lookup(key);
+    if (result == nullptr) {
+      // Miss; rehash if there was a GC, then insert.
+      if (gc_counter_ != heap_->gc_count()) Rehash();
+      result = Insert(key);
+    }
+  }
+  return result;
+}
+
+
+// Searches this map for the given key using the object's address
+// as the identity, returning:
+//    found => a pointer to the storage location for the value
+//    not found => {nullptr}
+IdentityMapBase::RawEntry IdentityMapBase::FindEntry(Handle<Object> key) {
+  if (size_ == 0) return nullptr;
+
+  Heap::OptionalRelocationLock lock(heap_, concurrent_);
+  RawEntry result = Lookup(key);
+  if (result == nullptr && gc_counter_ != heap_->gc_count()) {
+    Rehash();  // Rehash is expensive, so only do it in case of a miss.
+    result = Lookup(key);
+  }
+  return result;
+}
+
+
+void IdentityMapBase::Rehash() {
+  // Record the current GC counter.
+  gc_counter_ = heap_->gc_count();
+  // Assume that most objects won't be moved.
+  ZoneVector<std::pair<Object*, void*>> reinsert(zone_);
+  // Search the table looking for keys that wouldn't be found with their
+  // current hashcode and evacuate them.
+  int last_empty = -1;
+  for (int i = 0; i < size_; i++) {
+    if (keys_[i] == nullptr) {
+      last_empty = i;
+    } else {
+      int pos = Hash(keys_[i]) & mask_;
+      if (pos <= last_empty || pos > i) {
+        // Evacuate an entry that is in the wrong place.
+        reinsert.push_back(std::pair<Object*, void*>(keys_[i], values_[i]));
+        keys_[i] = nullptr;
+        values_[i] = nullptr;
+        last_empty = i;
+      }
+    }
+  }
+  // Reinsert all the key/value pairs that were in the wrong place.
+  for (auto pair : reinsert) {
+    int index = InsertIndex(pair.first);
+    DCHECK_GE(index, 0);
+    DCHECK_NULL(values_[index]);
+    values_[index] = pair.second;
+  }
+}
+
+
+void IdentityMapBase::Resize() {
+  // Grow the internal storage and reinsert all the key/value pairs.
+  int old_size = size_;
+  Object** old_keys = keys_;
+  void** old_values = values_;
+
+  size_ = size_ * kResizeFactor;
+  mask_ = size_ - 1;
+  gc_counter_ = heap_->gc_count();
+
+  CHECK_LE(size_, (1024 * 1024 * 16));  // that would be extreme...
+
+  keys_ = zone_->NewArray<Object*>(size_);
+  memset(keys_, 0, sizeof(Object*) * size_);
+  values_ = zone_->NewArray<void*>(size_);
+  memset(values_, 0, sizeof(void*) * size_);
+
+  for (int i = 0; i < old_size; i++) {
+    if (old_keys[i] == nullptr) continue;
+    int index = InsertIndex(old_keys[i]);
+    DCHECK_GE(index, 0);
+    values_[index] = old_values[i];
+  }
+
+  // Unregister old keys and register new keys.
+  heap_->UnregisterStrongRoots(old_keys);
+  heap_->RegisterStrongRoots(keys_, keys_ + size_);
+}
+}
+}  // namespace v8::internal
diff --git a/src/heap/identity-map.h b/src/heap/identity-map.h
new file mode 100644 (file)
index 0000000..5c754bc
--- /dev/null
@@ -0,0 +1,96 @@
+// 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.
+
+#ifndef V8_HEAP_IDENTITY_MAP_H_
+#define V8_HEAP_IDENTITY_MAP_H_
+
+#include "src/handles.h"
+
+namespace v8 {
+namespace internal {
+
+class Heap;
+
+// Base class of identity maps contains shared code for all template
+// instantions.
+class IdentityMapBase {
+ public:
+  // Enable or disable concurrent mode for this map. Concurrent mode implies
+  // taking the heap's relocation lock during most operations.
+  void SetConcurrent(bool concurrent) { concurrent_ = concurrent; }
+
+ protected:
+  // Allow Tester to access internals, including changing the address of objects
+  // within the {keys_} array in order to simulate a moving GC.
+  friend class IdentityMapTester;
+
+  typedef void** RawEntry;
+
+  IdentityMapBase(Heap* heap, Zone* zone)
+      : heap_(heap),
+        zone_(zone),
+        concurrent_(false),
+        gc_counter_(-1),
+        size_(0),
+        mask_(0),
+        keys_(nullptr),
+        values_(nullptr) {}
+  ~IdentityMapBase();
+
+  RawEntry GetEntry(Handle<Object> key);
+  RawEntry FindEntry(Handle<Object> key);
+
+ private:
+  // Internal implementation should not be called directly by subclasses.
+  int LookupIndex(Object* address);
+  int InsertIndex(Object* address);
+  void Rehash();
+  void Resize();
+  RawEntry Lookup(Handle<Object> key);
+  RawEntry Insert(Handle<Object> key);
+  int Hash(Object* address);
+
+  Heap* heap_;
+  Zone* zone_;
+  bool concurrent_;
+  int gc_counter_;
+  int size_;
+  int mask_;
+  Object** keys_;
+  void** values_;
+};
+
+// Implements an identity map from object addresses to a given value type {V}.
+// The map is robust w.r.t. garbage collection by synchronization with the
+// supplied {heap}.
+//  * Keys are treated as strong roots.
+//  * SMIs are valid keys, except SMI #0.
+//  * The value type {V} must be reinterpret_cast'able to {void*}
+//  * The value type {V} must not be a heap type.
+template <typename V>
+class IdentityMap : public IdentityMapBase {
+ public:
+  IdentityMap(Heap* heap, Zone* zone) : IdentityMapBase(heap, zone) {}
+
+  // Searches this map for the given key using the object's address
+  // as the identity, returning:
+  //    found => a pointer to the storage location for the value
+  //    not found => a pointer to a new storage location for the value
+  V* Get(Handle<Object> key) { return reinterpret_cast<V*>(GetEntry(key)); }
+
+  // Searches this map for the given key using the object's address
+  // as the identity, returning:
+  //    found => a pointer to the storage location for the value
+  //    not found => {nullptr}
+  V* Find(Handle<Object> key) { return reinterpret_cast<V*>(FindEntry(key)); }
+
+  // Set the value for the given key.
+  void Set(Handle<Object> key, V value) {
+    *(reinterpret_cast<V*>(GetEntry(key))) = value;
+  }
+};
+}
+}  // namespace v8::internal
+
+#endif  // V8_HEAP_IDENTITY_MAP_H_
index fa7ffcb..f56bdb2 100644 (file)
@@ -10981,22 +10981,23 @@ class BreakPointInfo: public Struct {
 #undef DECLARE_CAST
 #undef DECLARE_VERIFIER
 
-#define VISITOR_SYNCHRONIZATION_TAGS_LIST(V)                            \
-  V(kStringTable, "string_table", "(Internalized strings)")             \
+#define VISITOR_SYNCHRONIZATION_TAGS_LIST(V)                               \
+  V(kStringTable, "string_table", "(Internalized strings)")                \
   V(kExternalStringsTable, "external_strings_table", "(External strings)") \
-  V(kStrongRootList, "strong_root_list", "(Strong roots)")              \
-  V(kSmiRootList, "smi_root_list", "(Smi roots)")                       \
-  V(kInternalizedString, "internalized_string", "(Internal string)")    \
-  V(kBootstrapper, "bootstrapper", "(Bootstrapper)")                    \
-  V(kTop, "top", "(Isolate)")                                           \
-  V(kRelocatable, "relocatable", "(Relocatable)")                       \
-  V(kDebug, "debug", "(Debugger)")                                      \
-  V(kCompilationCache, "compilationcache", "(Compilation cache)")       \
-  V(kHandleScope, "handlescope", "(Handle scope)")                      \
-  V(kBuiltins, "builtins", "(Builtins)")                                \
-  V(kGlobalHandles, "globalhandles", "(Global handles)")                \
-  V(kEternalHandles, "eternalhandles", "(Eternal handles)")             \
-  V(kThreadManager, "threadmanager", "(Thread manager)")                \
+  V(kStrongRootList, "strong_root_list", "(Strong roots)")                 \
+  V(kSmiRootList, "smi_root_list", "(Smi roots)")                          \
+  V(kInternalizedString, "internalized_string", "(Internal string)")       \
+  V(kBootstrapper, "bootstrapper", "(Bootstrapper)")                       \
+  V(kTop, "top", "(Isolate)")                                              \
+  V(kRelocatable, "relocatable", "(Relocatable)")                          \
+  V(kDebug, "debug", "(Debugger)")                                         \
+  V(kCompilationCache, "compilationcache", "(Compilation cache)")          \
+  V(kHandleScope, "handlescope", "(Handle scope)")                         \
+  V(kBuiltins, "builtins", "(Builtins)")                                   \
+  V(kGlobalHandles, "globalhandles", "(Global handles)")                   \
+  V(kEternalHandles, "eternalhandles", "(Eternal handles)")                \
+  V(kThreadManager, "threadmanager", "(Thread manager)")                   \
+  V(kStrongRoots, "strong roots", "(Strong roots)")                        \
   V(kExtensions, "Extensions", "(Extensions)")
 
 class VisitorSynchronization : public AllStatic {
index 9b71ace..276ebd2 100644 (file)
         'test-heap.cc',
         'test-heap-profiler.cc',
         'test-hydrogen-types.cc',
+        'test-identity-map.cc',
         'test-list.cc',
         'test-liveedit.cc',
         'test-lockers.cc',
diff --git a/test/cctest/test-identity-map.cc b/test/cctest/test-identity-map.cc
new file mode 100644 (file)
index 0000000..787fda7
--- /dev/null
@@ -0,0 +1,339 @@
+// 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.
+
+#include "src/v8.h"
+
+#include "src/heap/identity-map.h"
+#include "src/zone.h"
+
+#include "test/cctest/cctest.h"
+
+namespace v8 {
+namespace internal {
+
+// Helper for testing. A "friend" of the IdentityMapBase class, it is able to
+// "move" objects to simulate GC for testing the internals of the map.
+class IdentityMapTester : public HandleAndZoneScope {
+ public:
+  IdentityMap<void*> map;
+
+  IdentityMapTester() : map(heap(), main_zone()) {}
+
+  Heap* heap() { return isolate()->heap(); }
+  Isolate* isolate() { return main_isolate(); }
+
+  void TestGetFind(Handle<Object> key1, void* val1, Handle<Object> key2,
+                   void* val2) {
+    CHECK_NULL(map.Find(key1));
+    CHECK_NULL(map.Find(key2));
+
+    // Set {key1} the first time.
+    void** entry = map.Get(key1);
+    CHECK_NOT_NULL(entry);
+    *entry = val1;
+
+    for (int i = 0; i < 3; i++) {  // Get and find {key1} K times.
+      {
+        void** nentry = map.Get(key1);
+        CHECK_EQ(entry, nentry);
+        CHECK_EQ(val1, *nentry);
+        CHECK_NULL(map.Find(key2));
+      }
+      {
+        void** nentry = map.Find(key1);
+        CHECK_EQ(entry, nentry);
+        CHECK_EQ(val1, *nentry);
+        CHECK_NULL(map.Find(key2));
+      }
+    }
+
+    // Set {key2} the first time.
+    void** entry2 = map.Get(key2);
+    CHECK_NOT_NULL(entry2);
+    *entry2 = val2;
+
+    for (int i = 0; i < 3; i++) {  // Get and find {key1} and {key2} K times.
+      {
+        void** nentry = map.Get(key2);
+        CHECK_EQ(entry2, nentry);
+        CHECK_EQ(val2, *nentry);
+      }
+      {
+        void** nentry = map.Find(key2);
+        CHECK_EQ(entry2, nentry);
+        CHECK_EQ(val2, *nentry);
+      }
+      {
+        void** nentry = map.Find(key1);
+        CHECK_EQ(val1, *nentry);
+      }
+    }
+  }
+
+  Handle<Smi> smi(int value) {
+    return Handle<Smi>(Smi::FromInt(value), isolate());
+  }
+
+  Handle<Object> num(double value) {
+    return isolate()->factory()->NewNumber(value);
+  }
+
+  void SimulateGCByIncrementingSmisBy(int shift) {
+    for (int i = 0; i < map.size_; i++) {
+      if (map.keys_[i]->IsSmi()) {
+        map.keys_[i] = Smi::FromInt(Smi::cast(map.keys_[i])->value() + shift);
+      }
+    }
+    map.gc_counter_ = -1;
+  }
+
+  void CheckFind(Handle<Object> key, void* value) {
+    void** entry = map.Find(key);
+    CHECK_NOT_NULL(entry);
+    CHECK_EQ(value, *entry);
+  }
+
+  void CheckGet(Handle<Object> key, void* value) {
+    void** entry = map.Get(key);
+    CHECK_NOT_NULL(entry);
+    CHECK_EQ(value, *entry);
+  }
+
+  void PrintMap() {
+    PrintF("{\n");
+    for (int i = 0; i < map.size_; i++) {
+      PrintF("  %3d: %p => %p\n", i, reinterpret_cast<void*>(map.keys_[i]),
+             reinterpret_cast<void*>(map.values_[i]));
+    }
+    PrintF("}\n");
+  }
+
+  void Resize() { map.Resize(); }
+
+  void Rehash() { map.Rehash(); }
+};
+
+
+TEST(Find_smi_not_found) {
+  IdentityMapTester t;
+  for (int i = 0; i < 100; i++) {
+    CHECK_NULL(t.map.Find(t.smi(i)));
+  }
+}
+
+
+TEST(Find_num_not_found) {
+  IdentityMapTester t;
+  for (int i = 0; i < 100; i++) {
+    CHECK_NULL(t.map.Find(t.num(i + 0.2)));
+  }
+}
+
+
+TEST(GetFind_smi_13) {
+  IdentityMapTester t;
+  t.TestGetFind(t.smi(13), t.isolate(), t.smi(17), t.heap());
+}
+
+
+TEST(GetFind_num_13) {
+  IdentityMapTester t;
+  t.TestGetFind(t.num(13.1), t.isolate(), t.num(17.1), t.heap());
+}
+
+
+TEST(GetFind_smi_17m) {
+  const int kInterval = 17;
+  const int kShift = 1099;
+  IdentityMapTester t;
+
+  for (int i = 1; i < 100; i += kInterval) {
+    t.map.Set(t.smi(i), reinterpret_cast<void*>(i + kShift));
+  }
+
+  for (int i = 1; i < 100; i += kInterval) {
+    t.CheckFind(t.smi(i), reinterpret_cast<void*>(i + kShift));
+  }
+
+  for (int i = 1; i < 100; i += kInterval) {
+    t.CheckGet(t.smi(i), reinterpret_cast<void*>(i + kShift));
+  }
+
+  for (int i = 1; i < 100; i++) {
+    void** entry = t.map.Find(t.smi(i));
+    if ((i % kInterval) != 1) {
+      CHECK_NULL(entry);
+    } else {
+      CHECK_NOT_NULL(entry);
+      CHECK_EQ(reinterpret_cast<void*>(i + kShift), *entry);
+    }
+  }
+}
+
+
+TEST(GetFind_num_1000) {
+  const int kPrime = 137;
+  IdentityMapTester t;
+  int val1;
+  int val2;
+
+  for (int i = 1; i < 1000; i++) {
+    t.TestGetFind(t.smi(i * kPrime), &val1, t.smi(i * kPrime + 1), &val2);
+  }
+}
+
+
+TEST(GetFind_smi_gc) {
+  const int kKey = 33;
+  const int kShift = 1211;
+  IdentityMapTester t;
+
+  t.map.Set(t.smi(kKey), &t);
+  t.SimulateGCByIncrementingSmisBy(kShift);
+  t.CheckFind(t.smi(kKey + kShift), &t);
+  t.CheckGet(t.smi(kKey + kShift), &t);
+}
+
+
+TEST(GetFind_smi_gc2) {
+  int kKey1 = 1;
+  int kKey2 = 33;
+  const int kShift = 1211;
+  IdentityMapTester t;
+
+  t.map.Set(t.smi(kKey1), &kKey1);
+  t.map.Set(t.smi(kKey2), &kKey2);
+  t.SimulateGCByIncrementingSmisBy(kShift);
+  t.CheckFind(t.smi(kKey1 + kShift), &kKey1);
+  t.CheckGet(t.smi(kKey1 + kShift), &kKey1);
+  t.CheckFind(t.smi(kKey2 + kShift), &kKey2);
+  t.CheckGet(t.smi(kKey2 + kShift), &kKey2);
+}
+
+
+TEST(GetFind_smi_gc_n) {
+  const int kShift = 12011;
+  IdentityMapTester t;
+  int keys[12] = {1,      2,      7,      8,      15,      23,
+                  1 + 32, 2 + 32, 7 + 32, 8 + 32, 15 + 32, 23 + 32};
+  // Initialize the map first.
+  for (size_t i = 0; i < arraysize(keys); i += 2) {
+    t.TestGetFind(t.smi(keys[i]), &keys[i], t.smi(keys[i + 1]), &keys[i + 1]);
+  }
+  // Check the above initialization.
+  for (size_t i = 0; i < arraysize(keys); i++) {
+    t.CheckFind(t.smi(keys[i]), &keys[i]);
+  }
+  // Simulate a GC by "moving" the smis in the internal keys array.
+  t.SimulateGCByIncrementingSmisBy(kShift);
+  // Check that searching for the incremented smis finds the same values.
+  for (size_t i = 0; i < arraysize(keys); i++) {
+    t.CheckFind(t.smi(keys[i] + kShift), &keys[i]);
+  }
+  // Check that searching for the incremented smis gets the same values.
+  for (size_t i = 0; i < arraysize(keys); i++) {
+    t.CheckGet(t.smi(keys[i] + kShift), &keys[i]);
+  }
+}
+
+
+TEST(GetFind_smi_num_gc_n) {
+  const int kShift = 12019;
+  IdentityMapTester t;
+  int smi_keys[] = {1, 2, 7, 15, 23};
+  Handle<Object> num_keys[] = {t.num(1.1), t.num(2.2), t.num(3.3), t.num(4.4),
+                               t.num(5.5), t.num(6.6), t.num(7.7), t.num(8.8),
+                               t.num(9.9), t.num(10.1)};
+  // Initialize the map first.
+  for (size_t i = 0; i < arraysize(smi_keys); i++) {
+    t.map.Set(t.smi(smi_keys[i]), &smi_keys[i]);
+  }
+  for (size_t i = 0; i < arraysize(num_keys); i++) {
+    t.map.Set(num_keys[i], &num_keys[i]);
+  }
+  // Check the above initialization.
+  for (size_t i = 0; i < arraysize(smi_keys); i++) {
+    t.CheckFind(t.smi(smi_keys[i]), &smi_keys[i]);
+  }
+  for (size_t i = 0; i < arraysize(num_keys); i++) {
+    t.CheckFind(num_keys[i], &num_keys[i]);
+  }
+
+  // Simulate a GC by moving SMIs.
+  // Ironically the SMIs "move", but the heap numbers don't!
+  t.SimulateGCByIncrementingSmisBy(kShift);
+
+  // Check that searching for the incremented smis finds the same values.
+  for (size_t i = 0; i < arraysize(smi_keys); i++) {
+    t.CheckFind(t.smi(smi_keys[i] + kShift), &smi_keys[i]);
+    t.CheckGet(t.smi(smi_keys[i] + kShift), &smi_keys[i]);
+  }
+
+  // Check that searching for the numbers finds the same values.
+  for (size_t i = 0; i < arraysize(num_keys); i++) {
+    t.CheckFind(num_keys[i], &num_keys[i]);
+    t.CheckGet(num_keys[i], &num_keys[i]);
+  }
+}
+
+
+void CollisionTest(int stride, bool rehash = false, bool resize = false) {
+  for (int load = 15; load <= 120; load = load * 2) {
+    IdentityMapTester t;
+
+    {  // Add entries to the map.
+      HandleScope scope(t.isolate());
+      int next = 1;
+      for (int i = 0; i < load; i++) {
+        t.map.Set(t.smi(next), reinterpret_cast<void*>(next));
+        t.CheckFind(t.smi(next), reinterpret_cast<void*>(next));
+        next = next + stride;
+      }
+    }
+    if (resize) t.Resize();  // Explicit resize (internal method).
+    if (rehash) t.Rehash();  // Explicit rehash (internal method).
+    {                        // Check find and get.
+      HandleScope scope(t.isolate());
+      int next = 1;
+      for (int i = 0; i < load; i++) {
+        t.CheckFind(t.smi(next), reinterpret_cast<void*>(next));
+        t.CheckGet(t.smi(next), reinterpret_cast<void*>(next));
+        next = next + stride;
+      }
+    }
+  }
+}
+
+
+TEST(Collisions_1) { CollisionTest(1); }
+TEST(Collisions_2) { CollisionTest(2); }
+TEST(Collisions_3) { CollisionTest(3); }
+TEST(Collisions_5) { CollisionTest(5); }
+TEST(Collisions_7) { CollisionTest(7); }
+TEST(Resize) { CollisionTest(9, false, true); }
+TEST(Rehash) { CollisionTest(11, true, false); }
+
+
+TEST(ExplicitGC) {
+  IdentityMapTester t;
+  Handle<Object> num_keys[] = {t.num(2.1), t.num(2.4), t.num(3.3), t.num(4.3),
+                               t.num(7.5), t.num(6.4), t.num(7.3), t.num(8.3),
+                               t.num(8.9), t.num(10.4)};
+
+  // Insert some objects that should be in new space.
+  for (size_t i = 0; i < arraysize(num_keys); i++) {
+    t.map.Set(num_keys[i], &num_keys[i]);
+  }
+
+  // Do an explicit, real GC.
+  t.heap()->CollectGarbage(i::NEW_SPACE);
+
+  // Check that searching for the numbers finds the same values.
+  for (size_t i = 0; i < arraysize(num_keys); i++) {
+    t.CheckFind(num_keys[i], &num_keys[i]);
+    t.CheckGet(num_keys[i], &num_keys[i]);
+  }
+}
+}
+}
index 288c294..b3db3cf 100644 (file)
         '../../src/heap/heap-inl.h',
         '../../src/heap/heap.cc',
         '../../src/heap/heap.h',
+        '../../src/heap/identity-map.cc',
+        '../../src/heap/identity-map.h',
         '../../src/heap/incremental-marking-inl.h',
         '../../src/heap/incremental-marking.cc',
         '../../src/heap/incremental-marking.h',