Commit 8e9eebeb authored by adamk@chromium.org's avatar adamk@chromium.org

OrderedHashTable implementation with Set and Map interfaces

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
parent 9e5757ab
...@@ -109,6 +109,16 @@ Handle<ObjectHashSet> Factory::NewObjectHashSet(int at_least_space_for) { ...@@ -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( Handle<ObjectHashTable> Factory::NewObjectHashTable(
int at_least_space_for, int at_least_space_for,
MinimumCapacity capacity_option) { MinimumCapacity capacity_option) {
......
...@@ -57,6 +57,9 @@ class Factory V8_FINAL { ...@@ -57,6 +57,9 @@ class Factory V8_FINAL {
int at_least_space_for, int at_least_space_for,
MinimumCapacity capacity_option = USE_DEFAULT_MINIMUM_CAPACITY); MinimumCapacity capacity_option = USE_DEFAULT_MINIMUM_CAPACITY);
Handle<OrderedHashSet> NewOrderedHashSet();
Handle<OrderedHashMap> NewOrderedHashMap();
Handle<WeakHashTable> NewWeakHashTable(int at_least_space_for); Handle<WeakHashTable> NewWeakHashTable(int at_least_space_for);
Handle<DescriptorArray> NewDescriptorArray(int number_of_descriptors, Handle<DescriptorArray> NewDescriptorArray(int number_of_descriptors,
......
...@@ -15902,6 +15902,197 @@ void WeakHashTable::AddEntry(int entry, Object* key, Object* value) { ...@@ -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( DeclaredAccessorDescriptorIterator::DeclaredAccessorDescriptorIterator(
DeclaredAccessorDescriptor* descriptor) DeclaredAccessorDescriptor* descriptor)
: array_(descriptor->serialized_data()->GetDataStartAddress()), : array_(descriptor->serialized_data()->GetDataStartAddress()),
......
...@@ -92,6 +92,9 @@ ...@@ -92,6 +92,9 @@
// - CompilationCacheTable // - CompilationCacheTable
// - CodeCacheHashTable // - CodeCacheHashTable
// - MapCache // - MapCache
// - OrderedHashTable
// - OrderedHashSet
// - OrderedHashMap
// - Context // - Context
// - JSFunctionResultCache // - JSFunctionResultCache
// - ScopeInfo // - ScopeInfo
...@@ -4266,6 +4269,163 @@ class ObjectHashTable: public HashTable<ObjectHashTableShape<2>, Object*> { ...@@ -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> template <int entrysize>
class WeakHashTableShape : public BaseShape<Object*> { class WeakHashTableShape : public BaseShape<Object*> {
public: public:
......
...@@ -94,6 +94,7 @@ ...@@ -94,6 +94,7 @@
'test-mementos.cc', 'test-mementos.cc',
'test-mutex.cc', 'test-mutex.cc',
'test-object-observe.cc', 'test-object-observe.cc',
'test-ordered-hash-table.cc',
'test-parsing.cc', 'test-parsing.cc',
'test-platform.cc', 'test-platform.cc',
'test-platform-tls.cc', 'test-platform-tls.cc',
......
...@@ -38,16 +38,17 @@ ...@@ -38,16 +38,17 @@
using namespace v8::internal; using namespace v8::internal;
namespace {
TEST(ObjectHashTable) {
LocalContext context; template<typename HashMap>
static void TestHashMap(Handle<HashMap> table) {
Isolate* isolate = CcTest::i_isolate(); Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory(); Factory* factory = isolate->factory();
v8::HandleScope scope(context->GetIsolate());
Handle<ObjectHashTable> table = factory->NewObjectHashTable(23);
Handle<JSObject> a = factory->NewJSArray(7); Handle<JSObject> a = factory->NewJSArray(7);
Handle<JSObject> b = factory->NewJSArray(11); 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->NumberOfElements(), 1);
CHECK_EQ(table->Lookup(*a), *b); 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());
...@@ -59,14 +60,13 @@ TEST(ObjectHashTable) { ...@@ -59,14 +60,13 @@ TEST(ObjectHashTable) {
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. // 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_EQ(table->NumberOfElements(), 1);
CHECK_NE(table->Lookup(*a), *b); CHECK_NE(table->Lookup(*a), *b);
// Keys mapped to the hole should be removed permanently. // 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->NumberOfElements(), 0);
CHECK_EQ(table->NumberOfDeletedElements(), 1);
CHECK_EQ(table->Lookup(*a), CcTest::heap()->the_hole_value()); CHECK_EQ(table->Lookup(*a), CcTest::heap()->the_hole_value());
// Keys should map back to their respective values and also should get // Keys should map back to their respective values and also should get
...@@ -74,9 +74,9 @@ TEST(ObjectHashTable) { ...@@ -74,9 +74,9 @@ TEST(ObjectHashTable) {
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
Handle<JSReceiver> key = factory->NewJSArray(7); Handle<JSReceiver> key = factory->NewJSArray(7);
Handle<JSObject> value = factory->NewJSArray(11); 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_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_EQ(table->Lookup(*key), *value);
CHECK(key->GetIdentityHash()->IsSmi()); CHECK(key->GetIdentityHash()->IsSmi());
} }
...@@ -86,7 +86,7 @@ TEST(ObjectHashTable) { ...@@ -86,7 +86,7 @@ TEST(ObjectHashTable) {
for (int i = 0; i < 100; i++) { for (int i = 0; i < 100; i++) {
Handle<JSReceiver> key = factory->NewJSArray(7); Handle<JSReceiver> key = factory->NewJSArray(7);
CHECK(JSReceiver::GetOrCreateIdentityHash(key)->IsSmi()); 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_EQ(table->Lookup(*key), CcTest::heap()->the_hole_value());
CHECK(key->GetIdentityHash()->IsSmi()); CHECK(key->GetIdentityHash()->IsSmi());
} }
...@@ -102,6 +102,15 @@ TEST(ObjectHashTable) { ...@@ -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 { class ObjectHashTableTest: public ObjectHashTable {
public: public:
void insert(int entry, int key, int value) { void insert(int entry, int key, int value) {
...@@ -154,13 +163,11 @@ TEST(HashTableRehash) { ...@@ -154,13 +163,11 @@ TEST(HashTableRehash) {
#ifdef DEBUG #ifdef DEBUG
TEST(ObjectHashSetCausesGC) { template<class HashSet>
i::FLAG_stress_compaction = false; static void TestHashSetCausesGC(Handle<HashSet> table) {
LocalContext context;
Isolate* isolate = CcTest::i_isolate(); Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory(); Factory* factory = isolate->factory();
v8::HandleScope scope(context->GetIsolate());
Handle<ObjectHashSet> table = factory->NewObjectHashSet(1);
Handle<JSObject> key = factory->NewJSArray(0); Handle<JSObject> key = factory->NewJSArray(0);
v8::Handle<v8::Object> key_obj = v8::Utils::ToLocal(key); v8::Handle<v8::Object> key_obj = v8::Utils::ToLocal(key);
...@@ -180,24 +187,32 @@ TEST(ObjectHashSetCausesGC) { ...@@ -180,24 +187,32 @@ TEST(ObjectHashSetCausesGC) {
CHECK(gc_count == isolate->heap()->gc_count()); CHECK(gc_count == isolate->heap()->gc_count());
// Calling Remove() will not cause GC in this case. // 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()); CHECK(gc_count == isolate->heap()->gc_count());
// Calling Add() should cause GC. // Calling Add() should cause GC.
table = ObjectHashSet::Add(table, key); table = HashSet::Add(table, key);
CHECK(gc_count < isolate->heap()->gc_count()); CHECK(gc_count < isolate->heap()->gc_count());
} }
#endif
#ifdef DEBUG TEST(ObjectHashSetCausesGC) {
TEST(ObjectHashTableCausesGC) {
i::FLAG_stress_compaction = false; i::FLAG_stress_compaction = false;
LocalContext context; 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(); Isolate* isolate = CcTest::i_isolate();
Factory* factory = isolate->factory(); Factory* factory = isolate->factory();
v8::HandleScope scope(context->GetIsolate());
Handle<ObjectHashTable> table = factory->NewObjectHashTable(1);
Handle<JSObject> key = factory->NewJSArray(0); Handle<JSObject> key = factory->NewJSArray(0);
v8::Handle<v8::Object> key_obj = v8::Utils::ToLocal(key); v8::Handle<v8::Object> key_obj = v8::Utils::ToLocal(key);
...@@ -216,7 +231,20 @@ TEST(ObjectHashTableCausesGC) { ...@@ -216,7 +231,20 @@ TEST(ObjectHashTableCausesGC) {
// Calling Put() should request GC by returning a failure. // Calling Put() should request GC by returning a failure.
int gc_count = isolate->heap()->gc_count(); int gc_count = isolate->heap()->gc_count();
ObjectHashTable::Put(table, key, key); HashMap::Put(table, key, key);
CHECK(gc_count < isolate->heap()->gc_count()); 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 #endif
}
// 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());
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment