OrderedHashTable implementation with Set and Map interfaces
authoradamk@chromium.org <adamk@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 4 Apr 2014 20:41:57 +0000 (20:41 +0000)
committeradamk@chromium.org <adamk@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Fri, 4 Apr 2014 20:41:57 +0000 (20:41 +0000)
OrderedHashTable is an insertion-ordered HashTable based on
Jason Orendorff's writeup of a data structure attributed to Tyler Close:
https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables

It is intended as the new backing store for JSSet/JSMap, as ES6 requires
insertion-order-based iteration. Note, however, that in the interest of
keeping the initial check-in small this patch does not yet include any
iteration support.

This change also doesn't yet touch any existing behavior, but in
a branch I've verified that these structures pass the existing
JSSet/JSMap mjsunit tests.

BUG=v8:1793
LOG=N
R=mstarzinger@chromium.org

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@20522 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/factory.cc
src/factory.h
src/objects.cc
src/objects.h
test/cctest/cctest.gyp
test/cctest/test-dictionary.cc
test/cctest/test-ordered-hash-table.cc [new file with mode: 0644]

index 7b8fa38fb7c2d27956506d697c5e4e0af5a4c709..08c3a8bd5703d391fc815aa1445e621124318829 100644 (file)
@@ -109,6 +109,16 @@ Handle<ObjectHashSet> Factory::NewObjectHashSet(int at_least_space_for) {
 }
 
 
+Handle<OrderedHashSet> Factory::NewOrderedHashSet() {
+  return OrderedHashSet::Allocate(isolate(), 4);
+}
+
+
+Handle<OrderedHashMap> Factory::NewOrderedHashMap() {
+  return OrderedHashMap::Allocate(isolate(), 4);
+}
+
+
 Handle<ObjectHashTable> Factory::NewObjectHashTable(
     int at_least_space_for,
     MinimumCapacity capacity_option) {
index 9d46d02d4c0a7af890ca1905ebfb136900b24a55..c72faae7958cd376214c3b58fbca2bfa68264621 100644 (file)
@@ -57,6 +57,9 @@ class Factory V8_FINAL {
       int at_least_space_for,
       MinimumCapacity capacity_option = USE_DEFAULT_MINIMUM_CAPACITY);
 
+  Handle<OrderedHashSet> NewOrderedHashSet();
+  Handle<OrderedHashMap> NewOrderedHashMap();
+
   Handle<WeakHashTable> NewWeakHashTable(int at_least_space_for);
 
   Handle<DescriptorArray> NewDescriptorArray(int number_of_descriptors,
index ce0aa6d92e28c2d1c4751d0f17c7a928eb6423be..ae24cd6b45b6166b6262137d7d7473f28a81e330 100644 (file)
@@ -15902,6 +15902,197 @@ void WeakHashTable::AddEntry(int entry, Object* key, Object* value) {
 }
 
 
+template<class Derived, int entrysize>
+Handle<Derived> OrderedHashTable<Derived, entrysize>::Allocate(
+    Isolate* isolate, int capacity, PretenureFlag pretenure) {
+  // Capacity must be a power of two, since we depend on being able
+  // to divide and multiple by 2 (kLoadFactor) to derive capacity
+  // from number of buckets. If we decide to change kLoadFactor
+  // to something other than 2, capacity should be stored as another
+  // field of this object.
+  const int kMinCapacity = 4;
+  capacity = RoundUpToPowerOf2(Max(kMinCapacity, capacity));
+  if (capacity > kMaxCapacity) {
+    v8::internal::Heap::FatalProcessOutOfMemory("invalid table size", true);
+  }
+  int num_buckets = capacity / kLoadFactor;
+  Handle<Derived> table =
+      Handle<Derived>::cast(
+          isolate->factory()->NewFixedArray(
+              kHashTableStartIndex + num_buckets + (capacity * kEntrySize),
+              pretenure));
+  for (int i = 0; i < num_buckets; ++i) {
+    table->set(kHashTableStartIndex + i, Smi::FromInt(kNotFound));
+  }
+  table->SetNumberOfBuckets(num_buckets);
+  table->SetNumberOfElements(0);
+  table->SetNumberOfDeletedElements(0);
+  return table;
+}
+
+
+template<class Derived, int entrysize>
+Handle<Derived> OrderedHashTable<Derived, entrysize>::EnsureGrowable(
+    Handle<Derived> table) {
+  int nof = table->NumberOfElements();
+  int nod = table->NumberOfDeletedElements();
+  int capacity = table->Capacity();
+  if ((nof + nod) < capacity) return table;
+  // Don't need to grow if we can simply clear out deleted entries instead.
+  // Note that we can't compact in place, though, so we always allocate
+  // a new table.
+  return Rehash(table, (nod < (capacity >> 1)) ? capacity << 1 : capacity);
+}
+
+
+template<class Derived, int entrysize>
+Handle<Derived> OrderedHashTable<Derived, entrysize>::Shrink(
+    Handle<Derived> table) {
+  int nof = table->NumberOfElements();
+  int capacity = table->Capacity();
+  if (nof > (capacity >> 2)) return table;
+  return Rehash(table, capacity / 2);
+}
+
+
+template<class Derived, int entrysize>
+Handle<Derived> OrderedHashTable<Derived, entrysize>::Rehash(
+    Handle<Derived> table, int new_capacity) {
+  Handle<Derived> new_table =
+      Allocate(table->GetIsolate(),
+               new_capacity,
+               table->GetHeap()->InNewSpace(*table) ? NOT_TENURED : TENURED);
+  int nof = table->NumberOfElements();
+  int nod = table->NumberOfDeletedElements();
+  int new_buckets = new_table->NumberOfBuckets();
+  int new_entry = 0;
+  for (int old_entry = 0; old_entry < (nof + nod); ++old_entry) {
+    Object* key = table->KeyAt(old_entry);
+    if (key->IsTheHole()) continue;
+    Object* hash = key->GetHash();
+    int bucket = Smi::cast(hash)->value() & (new_buckets - 1);
+    Object* chain_entry = new_table->get(kHashTableStartIndex + bucket);
+    new_table->set(kHashTableStartIndex + bucket, Smi::FromInt(new_entry));
+    int new_index = new_table->EntryToIndex(new_entry);
+    int old_index = table->EntryToIndex(old_entry);
+    for (int i = 0; i < entrysize; ++i) {
+      Object* value = table->get(old_index + i);
+      new_table->set(new_index + i, value);
+    }
+    new_table->set(new_index + kChainOffset, chain_entry);
+    ++new_entry;
+  }
+  new_table->SetNumberOfElements(nof);
+  return new_table;
+}
+
+
+template<class Derived, int entrysize>
+int OrderedHashTable<Derived, entrysize>::FindEntry(Object* key) {
+  ASSERT(!key->IsTheHole());
+  Object* hash = key->GetHash();
+  if (hash->IsUndefined()) return kNotFound;
+  for (int entry = HashToEntry(Smi::cast(hash)->value());
+       entry != kNotFound;
+       entry = ChainAt(entry)) {
+    Object* candidate = KeyAt(entry);
+    if (candidate->SameValue(key))
+      return entry;
+  }
+  return kNotFound;
+}
+
+
+template<class Derived, int entrysize>
+int OrderedHashTable<Derived, entrysize>::AddEntry(int hash) {
+  int entry = NumberOfElements() + NumberOfDeletedElements();
+  int bucket = HashToBucket(hash);
+  int index = EntryToIndex(entry);
+  Object* chain_entry = get(kHashTableStartIndex + bucket);
+  set(kHashTableStartIndex + bucket, Smi::FromInt(entry));
+  set(index + kChainOffset, chain_entry);
+  SetNumberOfElements(NumberOfElements() + 1);
+  return index;
+}
+
+
+template<class Derived, int entrysize>
+void OrderedHashTable<Derived, entrysize>::RemoveEntry(int entry) {
+  int index = EntryToIndex(entry);
+  for (int i = 0; i < entrysize; ++i) {
+    set_the_hole(index + i);
+  }
+  SetNumberOfElements(NumberOfElements() - 1);
+  SetNumberOfDeletedElements(NumberOfDeletedElements() + 1);
+}
+
+
+template class OrderedHashTable<OrderedHashSet, 1>;
+template class OrderedHashTable<OrderedHashMap, 2>;
+
+
+bool OrderedHashSet::Contains(Object* key) {
+  return FindEntry(key) != kNotFound;
+}
+
+
+Handle<OrderedHashSet> OrderedHashSet::Add(Handle<OrderedHashSet> table,
+                                           Handle<Object> key) {
+  if (table->FindEntry(*key) != kNotFound) return table;
+
+  table = EnsureGrowable(table);
+
+  Handle<Object> hash = GetOrCreateHash(key, table->GetIsolate());
+  int index = table->AddEntry(Smi::cast(*hash)->value());
+  table->set(index, *key);
+  return table;
+}
+
+
+Handle<OrderedHashSet> OrderedHashSet::Remove(Handle<OrderedHashSet> table,
+                                              Handle<Object> key) {
+  int entry = table->FindEntry(*key);
+  if (entry == kNotFound) return table;
+  table->RemoveEntry(entry);
+  // TODO(adamk): Don't shrink if we're being iterated over
+  return Shrink(table);
+}
+
+
+Object* OrderedHashMap::Lookup(Object* key) {
+  int entry = FindEntry(key);
+  if (entry == kNotFound) return GetHeap()->the_hole_value();
+  return ValueAt(entry);
+}
+
+
+Handle<OrderedHashMap> OrderedHashMap::Put(Handle<OrderedHashMap> table,
+                                           Handle<Object> key,
+                                           Handle<Object> value) {
+  int entry = table->FindEntry(*key);
+
+  if (value->IsTheHole()) {
+    if (entry == kNotFound) return table;
+    table->RemoveEntry(entry);
+    // TODO(adamk): Only shrink if not iterating
+    return Shrink(table);
+  }
+
+  if (entry != kNotFound) {
+    table->set(table->EntryToIndex(entry) + kValueOffset, *value);
+    return table;
+  }
+
+  table = EnsureGrowable(table);
+
+  Handle<Object> hash = GetOrCreateHash(key, table->GetIsolate());
+  int index = table->AddEntry(Smi::cast(*hash)->value());
+  table->set(index, *key);
+  table->set(index + kValueOffset, *value);
+  return table;
+}
+
+
 DeclaredAccessorDescriptorIterator::DeclaredAccessorDescriptorIterator(
     DeclaredAccessorDescriptor* descriptor)
     : array_(descriptor->serialized_data()->GetDataStartAddress()),
index 948246f2e41beb1d4f8cab114013707ce436c80a..ff9f45721f1498a181015755bb9759b670f53aa1 100644 (file)
@@ -92,6 +92,9 @@
 //             - CompilationCacheTable
 //             - CodeCacheHashTable
 //             - MapCache
+//           - OrderedHashTable
+//             - OrderedHashSet
+//             - OrderedHashMap
 //           - Context
 //           - JSFunctionResultCache
 //           - ScopeInfo
@@ -4266,6 +4269,163 @@ class ObjectHashTable: public HashTable<ObjectHashTableShape<2>, Object*> {
 };
 
 
+// OrderedHashTable is a HashTable with Object keys that preserves
+// insertion order. There are Map and Set interfaces (OrderedHashMap
+// and OrderedHashTable, below). It is meant to be used by JSMap/JSSet.
+//
+// Only Object* keys are supported, with Object::SameValue() used as the
+// equality operator and Object::GetHash() for the hash function.
+//
+// Based on the "Deterministic Hash Table" as described by Jason Orendorff at
+// https://wiki.mozilla.org/User:Jorend/Deterministic_hash_tables
+// Originally attributed to Tyler Close.
+//
+// Memory layout:
+//   [0]: bucket count
+//   [1]: element count
+//   [2]: deleted element count
+//   [3..(NumberOfBuckets() - 1)]: "hash table", where each item is an offset
+//                                 into the data table (see below) where the
+//                                 first item in this bucket is stored.
+//   [3 + NumberOfBuckets()..length]: "data table", an array of length
+//                            Capacity() * kEntrySize, where the first entrysize
+//                            items are handled by the derived class and the
+//                            item at kChainOffset is another entry into the
+//                            data table indicating the next entry in this hash
+//                            bucket.
+template<class Derived, int entrysize>
+class OrderedHashTable: public FixedArray {
+ public:
+  // Returns an OrderedHashTable with a capacity of at least |capacity|.
+  static Handle<Derived> Allocate(
+      Isolate* isolate, int capacity, PretenureFlag pretenure = NOT_TENURED);
+
+  // Returns an OrderedHashTable (possibly |table|) with enough space
+  // to add at least one new element, or returns a Failure if a GC occurs.
+  static Handle<Derived> EnsureGrowable(Handle<Derived> table);
+
+  // Returns an OrderedHashTable (possibly |table|) that's shrunken
+  // if possible.
+  static Handle<Derived> Shrink(Handle<Derived> table);
+
+  // Returns kNotFound if the key isn't present.
+  int FindEntry(Object* key);
+
+  int NumberOfElements() {
+    return Smi::cast(get(kNumberOfElementsIndex))->value();
+  }
+
+  int NumberOfDeletedElements() {
+    return Smi::cast(get(kNumberOfDeletedElementsIndex))->value();
+  }
+
+  int NumberOfBuckets() {
+    return Smi::cast(get(kNumberOfBucketsIndex))->value();
+  }
+
+  // Returns the index into the data table where the new entry
+  // should be placed. The table is assumed to have enough space
+  // for a new entry.
+  int AddEntry(int hash);
+
+  // Removes the entry, and puts the_hole in entrysize pointers
+  // (leaving the hash table chain intact).
+  void RemoveEntry(int entry);
+
+  // Returns an index into |this| for the given entry.
+  int EntryToIndex(int entry) {
+    return kHashTableStartIndex + NumberOfBuckets() + (entry * kEntrySize);
+  }
+
+  static const int kNotFound = -1;
+
+ private:
+  static Handle<Derived> Rehash(Handle<Derived> table, int new_capacity);
+
+  void SetNumberOfBuckets(int num) {
+    set(kNumberOfBucketsIndex, Smi::FromInt(num));
+  }
+
+  void SetNumberOfElements(int num) {
+    set(kNumberOfElementsIndex, Smi::FromInt(num));
+  }
+
+  void SetNumberOfDeletedElements(int num) {
+    set(kNumberOfDeletedElementsIndex, Smi::FromInt(num));
+  }
+
+  int Capacity() {
+    return NumberOfBuckets() * kLoadFactor;
+  }
+
+  Object* KeyAt(int entry) { return get(EntryToIndex(entry)); }
+
+  // Returns the next entry for the given entry.
+  int ChainAt(int entry) {
+    return Smi::cast(get(EntryToIndex(entry) + kChainOffset))->value();
+  }
+
+  int HashToBucket(int hash) {
+    return hash & (NumberOfBuckets() - 1);
+  }
+
+  int HashToEntry(int hash) {
+    int bucket = HashToBucket(hash);
+    return Smi::cast(get(kHashTableStartIndex + bucket))->value();
+  }
+
+  static const int kNumberOfBucketsIndex = 0;
+  static const int kNumberOfElementsIndex = kNumberOfBucketsIndex + 1;
+  static const int kNumberOfDeletedElementsIndex = kNumberOfElementsIndex + 1;
+  static const int kHashTableStartIndex = kNumberOfDeletedElementsIndex + 1;
+
+  static const int kEntrySize = entrysize + 1;
+  static const int kChainOffset = entrysize;
+
+  static const int kLoadFactor = 2;
+  static const int kMaxCapacity =
+      (FixedArray::kMaxLength - kHashTableStartIndex)
+      / (1 + (kEntrySize * kLoadFactor));
+};
+
+
+class OrderedHashSet: public OrderedHashTable<OrderedHashSet, 1> {
+ public:
+  static OrderedHashSet* cast(Object* obj) {
+    ASSERT(obj->IsFixedArray());  // TODO(adamk): Make a map for this
+    return reinterpret_cast<OrderedHashSet*>(obj);
+  }
+
+  bool Contains(Object* key);
+  static Handle<OrderedHashSet> Add(
+      Handle<OrderedHashSet> table, Handle<Object> key);
+  static Handle<OrderedHashSet> Remove(
+      Handle<OrderedHashSet> table, Handle<Object> key);
+};
+
+
+class OrderedHashMap: public OrderedHashTable<OrderedHashMap, 2> {
+ public:
+  static OrderedHashMap* cast(Object* obj) {
+    ASSERT(obj->IsFixedArray());  // TODO(adamk): Make a map for this
+    return reinterpret_cast<OrderedHashMap*>(obj);
+  }
+
+  Object* Lookup(Object* key);
+  static Handle<OrderedHashMap> Put(
+      Handle<OrderedHashMap> table,
+      Handle<Object> key,
+      Handle<Object> value);
+
+ private:
+  Object* ValueAt(int entry) {
+    return get(EntryToIndex(entry) + kValueOffset);
+  }
+
+  static const int kValueOffset = 1;
+};
+
+
 template <int entrysize>
 class WeakHashTableShape : public BaseShape<Object*> {
  public:
index ec5b08dd292b6778d12fb4db768d74bc876af774..eba0580ca58ce31c91c588707f217359e5698dda 100644 (file)
@@ -94,6 +94,7 @@
         'test-mementos.cc',
         'test-mutex.cc',
         'test-object-observe.cc',
+        'test-ordered-hash-table.cc',
         'test-parsing.cc',
         'test-platform.cc',
         'test-platform-tls.cc',
index 6e62a2243c26eed0e00222abc2e44930be05a145..1de1cdb122321896f356c2abbd94e81dbd704817 100644 (file)
 
 using namespace v8::internal;
 
+namespace {
 
-TEST(ObjectHashTable) {
-  LocalContext context;
+
+template<typename HashMap>
+static void TestHashMap(Handle<HashMap> table) {
   Isolate* isolate = CcTest::i_isolate();
   Factory* factory = isolate->factory();
-  v8::HandleScope scope(context->GetIsolate());
-  Handle<ObjectHashTable> table = factory->NewObjectHashTable(23);
+
   Handle<JSObject> a = factory->NewJSArray(7);
   Handle<JSObject> b = factory->NewJSArray(11);
-  table = ObjectHashTable::Put(table, a, b);
+  table = HashMap::Put(table, a, b);
   CHECK_EQ(table->NumberOfElements(), 1);
   CHECK_EQ(table->Lookup(*a), *b);
   CHECK_EQ(table->Lookup(*b), CcTest::heap()->the_hole_value());
@@ -59,14 +60,13 @@ TEST(ObjectHashTable) {
   CHECK_EQ(table->Lookup(*b), CcTest::heap()->the_hole_value());
 
   // Keys that are overwritten should not change number of elements.
-  table = ObjectHashTable::Put(table, a, factory->NewJSArray(13));
+  table = HashMap::Put(table, a, factory->NewJSArray(13));
   CHECK_EQ(table->NumberOfElements(), 1);
   CHECK_NE(table->Lookup(*a), *b);
 
   // Keys mapped to the hole should be removed permanently.
-  table = ObjectHashTable::Put(table, a, factory->the_hole_value());
+  table = HashMap::Put(table, a, factory->the_hole_value());
   CHECK_EQ(table->NumberOfElements(), 0);
-  CHECK_EQ(table->NumberOfDeletedElements(), 1);
   CHECK_EQ(table->Lookup(*a), CcTest::heap()->the_hole_value());
 
   // Keys should map back to their respective values and also should get
@@ -74,9 +74,9 @@ TEST(ObjectHashTable) {
   for (int i = 0; i < 100; i++) {
     Handle<JSReceiver> key = factory->NewJSArray(7);
     Handle<JSObject> value = factory->NewJSArray(11);
-    table = ObjectHashTable::Put(table, key, value);
+    table = HashMap::Put(table, key, value);
     CHECK_EQ(table->NumberOfElements(), i + 1);
-    CHECK_NE(table->FindEntry(*key), ObjectHashTable::kNotFound);
+    CHECK_NE(table->FindEntry(*key), HashMap::kNotFound);
     CHECK_EQ(table->Lookup(*key), *value);
     CHECK(key->GetIdentityHash()->IsSmi());
   }
@@ -86,7 +86,7 @@ TEST(ObjectHashTable) {
   for (int i = 0; i < 100; i++) {
     Handle<JSReceiver> key = factory->NewJSArray(7);
     CHECK(JSReceiver::GetOrCreateIdentityHash(key)->IsSmi());
-    CHECK_EQ(table->FindEntry(*key), ObjectHashTable::kNotFound);
+    CHECK_EQ(table->FindEntry(*key), HashMap::kNotFound);
     CHECK_EQ(table->Lookup(*key), CcTest::heap()->the_hole_value());
     CHECK(key->GetIdentityHash()->IsSmi());
   }
@@ -102,6 +102,15 @@ TEST(ObjectHashTable) {
 }
 
 
+TEST(HashMap) {
+  LocalContext context;
+  v8::HandleScope scope(context->GetIsolate());
+  Isolate* isolate = CcTest::i_isolate();
+  TestHashMap(isolate->factory()->NewObjectHashTable(23));
+  TestHashMap(isolate->factory()->NewOrderedHashMap());
+}
+
+
 class ObjectHashTableTest: public ObjectHashTable {
  public:
   void insert(int entry, int key, int value) {
@@ -154,13 +163,11 @@ TEST(HashTableRehash) {
 
 
 #ifdef DEBUG
-TEST(ObjectHashSetCausesGC) {
-  i::FLAG_stress_compaction = false;
-  LocalContext context;
+template<class HashSet>
+static void TestHashSetCausesGC(Handle<HashSet> table) {
   Isolate* isolate = CcTest::i_isolate();
   Factory* factory = isolate->factory();
-  v8::HandleScope scope(context->GetIsolate());
-  Handle<ObjectHashSet> table = factory->NewObjectHashSet(1);
+
   Handle<JSObject> key = factory->NewJSArray(0);
   v8::Handle<v8::Object> key_obj = v8::Utils::ToLocal(key);
 
@@ -180,24 +187,32 @@ TEST(ObjectHashSetCausesGC) {
   CHECK(gc_count == isolate->heap()->gc_count());
 
   // Calling Remove() will not cause GC in this case.
-  table = ObjectHashSet::Remove(table, key);
+  table = HashSet::Remove(table, key);
   CHECK(gc_count == isolate->heap()->gc_count());
 
   // Calling Add() should cause GC.
-  table = ObjectHashSet::Add(table, key);
+  table = HashSet::Add(table, key);
   CHECK(gc_count < isolate->heap()->gc_count());
 }
-#endif
 
 
-#ifdef DEBUG
-TEST(ObjectHashTableCausesGC) {
+TEST(ObjectHashSetCausesGC) {
   i::FLAG_stress_compaction = false;
   LocalContext context;
+  v8::HandleScope scope(context->GetIsolate());
+  Isolate* isolate = CcTest::i_isolate();
+  TestHashSetCausesGC(isolate->factory()->NewObjectHashSet(1));
+  TestHashSetCausesGC(isolate->factory()->NewOrderedHashSet());
+}
+#endif
+
+
+#ifdef DEBUG
+template<class HashMap>
+static void TestHashMapCausesGC(Handle<HashMap> table) {
   Isolate* isolate = CcTest::i_isolate();
   Factory* factory = isolate->factory();
-  v8::HandleScope scope(context->GetIsolate());
-  Handle<ObjectHashTable> table = factory->NewObjectHashTable(1);
+
   Handle<JSObject> key = factory->NewJSArray(0);
   v8::Handle<v8::Object> key_obj = v8::Utils::ToLocal(key);
 
@@ -216,7 +231,20 @@ TEST(ObjectHashTableCausesGC) {
 
   // Calling Put() should request GC by returning a failure.
   int gc_count = isolate->heap()->gc_count();
-  ObjectHashTable::Put(table, key, key);
+  HashMap::Put(table, key, key);
   CHECK(gc_count < isolate->heap()->gc_count());
 }
+
+
+TEST(ObjectHashTableCausesGC) {
+  i::FLAG_stress_compaction = false;
+  LocalContext context;
+  v8::HandleScope scope(context->GetIsolate());
+  Isolate* isolate = CcTest::i_isolate();
+  TestHashMapCausesGC(isolate->factory()->NewObjectHashTable(1));
+  TestHashMapCausesGC(isolate->factory()->NewOrderedHashMap());
+}
 #endif
+
+
+}
diff --git a/test/cctest/test-ordered-hash-table.cc b/test/cctest/test-ordered-hash-table.cc
new file mode 100644 (file)
index 0000000..f6ebc16
--- /dev/null
@@ -0,0 +1,158 @@
+// Copyright 2014 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+//       notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+//       copyright notice, this list of conditions and the following
+//       disclaimer in the documentation and/or other materials provided
+//       with the distribution.
+//     * Neither the name of Google Inc. nor the names of its
+//       contributors may be used to endorse or promote products derived
+//       from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <stdlib.h>
+
+#include "v8.h"
+
+#include "cctest.h"
+#include "factory.h"
+
+namespace {
+
+using namespace v8::internal;
+
+TEST(Set) {
+  LocalContext context;
+  Isolate* isolate = CcTest::i_isolate();
+  Factory* factory = isolate->factory();
+  HandleScope scope(isolate);
+  Handle<OrderedHashSet> ordered_set = factory->NewOrderedHashSet();
+  CHECK_EQ(2, ordered_set->NumberOfBuckets());
+  CHECK_EQ(0, ordered_set->NumberOfElements());
+  CHECK_EQ(0, ordered_set->NumberOfDeletedElements());
+
+  Handle<Map> map = factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
+  Handle<JSObject> obj = factory->NewJSObjectFromMap(map);
+  CHECK(!ordered_set->Contains(*obj));
+  ordered_set = OrderedHashSet::Add(ordered_set, obj);
+  CHECK_EQ(1, ordered_set->NumberOfElements());
+  CHECK(ordered_set->Contains(*obj));
+  ordered_set = OrderedHashSet::Remove(ordered_set, obj);
+  CHECK_EQ(0, ordered_set->NumberOfElements());
+  CHECK(!ordered_set->Contains(*obj));
+
+  // Test for collisions/chaining
+  Handle<JSObject> obj1 = factory->NewJSObjectFromMap(map);
+  ordered_set = OrderedHashSet::Add(ordered_set, obj1);
+  Handle<JSObject> obj2 = factory->NewJSObjectFromMap(map);
+  ordered_set = OrderedHashSet::Add(ordered_set, obj2);
+  Handle<JSObject> obj3 = factory->NewJSObjectFromMap(map);
+  ordered_set = OrderedHashSet::Add(ordered_set, obj3);
+  CHECK_EQ(3, ordered_set->NumberOfElements());
+  CHECK(ordered_set->Contains(*obj1));
+  CHECK(ordered_set->Contains(*obj2));
+  CHECK(ordered_set->Contains(*obj3));
+
+  // Test growth
+  ordered_set = OrderedHashSet::Add(ordered_set, obj);
+  Handle<JSObject> obj4 = factory->NewJSObjectFromMap(map);
+  ordered_set = OrderedHashSet::Add(ordered_set, obj4);
+  CHECK(ordered_set->Contains(*obj));
+  CHECK(ordered_set->Contains(*obj1));
+  CHECK(ordered_set->Contains(*obj2));
+  CHECK(ordered_set->Contains(*obj3));
+  CHECK(ordered_set->Contains(*obj4));
+  CHECK_EQ(5, ordered_set->NumberOfElements());
+  CHECK_EQ(0, ordered_set->NumberOfDeletedElements());
+  CHECK_EQ(4, ordered_set->NumberOfBuckets());
+
+  // Test shrinking
+  ordered_set = OrderedHashSet::Remove(ordered_set, obj);
+  ordered_set = OrderedHashSet::Remove(ordered_set, obj1);
+  ordered_set = OrderedHashSet::Remove(ordered_set, obj2);
+  ordered_set = OrderedHashSet::Remove(ordered_set, obj3);
+  CHECK_EQ(1, ordered_set->NumberOfElements());
+  CHECK_EQ(2, ordered_set->NumberOfBuckets());
+}
+
+
+TEST(Map) {
+  LocalContext context;
+  Isolate* isolate = CcTest::i_isolate();
+  Factory* factory = isolate->factory();
+  HandleScope scope(isolate);
+  Handle<OrderedHashMap> ordered_map = factory->NewOrderedHashMap();
+  CHECK_EQ(2, ordered_map->NumberOfBuckets());
+  CHECK_EQ(0, ordered_map->NumberOfElements());
+  CHECK_EQ(0, ordered_map->NumberOfDeletedElements());
+
+  Handle<Map> map = factory->NewMap(JS_OBJECT_TYPE, JSObject::kHeaderSize);
+  Handle<JSObject> obj = factory->NewJSObjectFromMap(map);
+  Handle<JSObject> val = factory->NewJSObjectFromMap(map);
+  CHECK(ordered_map->Lookup(*obj)->IsTheHole());
+  ordered_map = OrderedHashMap::Put(ordered_map, obj, val);
+  CHECK_EQ(1, ordered_map->NumberOfElements());
+  CHECK(ordered_map->Lookup(*obj)->SameValue(*val));
+  ordered_map = OrderedHashMap::Put(
+      ordered_map, obj, factory->the_hole_value());
+  CHECK_EQ(0, ordered_map->NumberOfElements());
+  CHECK(ordered_map->Lookup(*obj)->IsTheHole());
+
+  // Test for collisions/chaining
+  Handle<JSObject> obj1 = factory->NewJSObjectFromMap(map);
+  Handle<JSObject> obj2 = factory->NewJSObjectFromMap(map);
+  Handle<JSObject> obj3 = factory->NewJSObjectFromMap(map);
+  Handle<JSObject> val1 = factory->NewJSObjectFromMap(map);
+  Handle<JSObject> val2 = factory->NewJSObjectFromMap(map);
+  Handle<JSObject> val3 = factory->NewJSObjectFromMap(map);
+  ordered_map = OrderedHashMap::Put(ordered_map, obj1, val1);
+  ordered_map = OrderedHashMap::Put(ordered_map, obj2, val2);
+  ordered_map = OrderedHashMap::Put(ordered_map, obj3, val3);
+  CHECK_EQ(3, ordered_map->NumberOfElements());
+  CHECK(ordered_map->Lookup(*obj1)->SameValue(*val1));
+  CHECK(ordered_map->Lookup(*obj2)->SameValue(*val2));
+  CHECK(ordered_map->Lookup(*obj3)->SameValue(*val3));
+
+  // Test growth
+  ordered_map = OrderedHashMap::Put(ordered_map, obj, val);
+  Handle<JSObject> obj4 = factory->NewJSObjectFromMap(map);
+  Handle<JSObject> val4 = factory->NewJSObjectFromMap(map);
+  ordered_map = OrderedHashMap::Put(ordered_map, obj4, val4);
+  CHECK(ordered_map->Lookup(*obj)->SameValue(*val));
+  CHECK(ordered_map->Lookup(*obj1)->SameValue(*val1));
+  CHECK(ordered_map->Lookup(*obj2)->SameValue(*val2));
+  CHECK(ordered_map->Lookup(*obj3)->SameValue(*val3));
+  CHECK(ordered_map->Lookup(*obj4)->SameValue(*val4));
+  CHECK_EQ(5, ordered_map->NumberOfElements());
+  CHECK_EQ(4, ordered_map->NumberOfBuckets());
+
+  // Test shrinking
+  ordered_map = OrderedHashMap::Put(
+      ordered_map, obj, factory->the_hole_value());
+  ordered_map = OrderedHashMap::Put(
+      ordered_map, obj1, factory->the_hole_value());
+  ordered_map = OrderedHashMap::Put(
+      ordered_map, obj2, factory->the_hole_value());
+  ordered_map = OrderedHashMap::Put(
+      ordered_map, obj3, factory->the_hole_value());
+  CHECK_EQ(1, ordered_map->NumberOfElements());
+  CHECK_EQ(2, ordered_map->NumberOfBuckets());
+}
+
+
+}