}
+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) {
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,
}
+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()),
// - CompilationCacheTable
// - CodeCacheHashTable
// - MapCache
+// - OrderedHashTable
+// - OrderedHashSet
+// - OrderedHashMap
// - Context
// - JSFunctionResultCache
// - ScopeInfo
};
+// 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:
'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',
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());
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
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());
}
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());
}
}
+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) {
#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);
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);
// 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
+
+
+}
--- /dev/null
+// 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());
+}
+
+
+}