Index: src/objects.cc |
diff --git a/src/objects.cc b/src/objects.cc |
index a642df0d381d101ac6daa99ec5ec1ab24020683e..28007d73f5e1ce27867312a38e713ef5d6d9cc8a 100644 |
--- a/src/objects.cc |
+++ b/src/objects.cc |
@@ -6590,11 +6590,123 @@ Handle<FixedArray> JSObject::GetEnumPropertyKeys(Handle<JSObject> object, |
} |
+Handle<FixedArray> KeyAccumulator::GetKeys() { |
+ if (length_ == 0) { |
+ return isolate_->factory()->empty_fixed_array(); |
+ } |
+ if (set_.is_null()) { |
+ keys_->Shrink(length_); |
+ return keys_; |
+ } |
+ // copy over results from set_ |
+ Handle<FixedArray> result = isolate_->factory()->NewFixedArray(length_); |
+ for (int i = 0; i < length_; i++) { |
+ result->set(i, set_->KeyAt(i)); |
+ } |
+ return result; |
+} |
+ |
+ |
+void KeyAccumulator::AddKey(Handle<Object> key, int check_limit) { |
+#ifdef ENABLE_SLOW_DCHECKS |
+ if (FLAG_enable_slow_asserts) { |
+ DCHECK(key->IsNumber() || key->IsName()); |
+ } |
+#endif |
+ if (!set_.is_null()) { |
+ set_ = OrderedHashSet::Add(set_, key); |
+ length_ = set_->NumberOfElements(); |
+ return; |
+ } |
+ // check if we already have the key in the case we are still using |
+ // the keys_ FixedArray |
+ check_limit = Min(check_limit, length_); |
+ for (int i = 0; i < check_limit; i++) { |
+ Object* current = keys_->get(i); |
+ if (current->KeyEquals(*key)) return; |
+ } |
+ EnsureCapacity(length_); |
+ keys_->set(length_, *key); |
+ length_++; |
+} |
+ |
+ |
+void KeyAccumulator::AddKeys(Handle<FixedArray> array, |
+ FixedArray::KeyFilter filter) { |
+ int add_length = array->length(); |
+ if (add_length == 0) return; |
+ if (keys_.is_null() && filter == FixedArray::ALL_KEYS) { |
+ keys_ = array; |
+ length_ = keys_->length(); |
+ return; |
+ } |
+ PrepareForComparisons(add_length); |
+ int previous_key_count = length_; |
+ for (int i = 0; i < add_length; i++) { |
+ Handle<Object> current(array->get(i), isolate_); |
+ if (filter == FixedArray::NON_SYMBOL_KEYS && current->IsSymbol()) continue; |
+ AddKey(current, previous_key_count); |
+ } |
+} |
+ |
+ |
+void KeyAccumulator::AddKeys(Handle<JSObject> array_like, |
+ FixedArray::KeyFilter filter) { |
+ DCHECK(array_like->IsJSArray() || array_like->HasSloppyArgumentsElements()); |
+ ElementsAccessor* accessor = array_like->GetElementsAccessor(); |
+ accessor->AddElementsToKeyAccumulator(array_like, this, filter); |
+} |
+ |
+ |
+void KeyAccumulator::PrepareForComparisons(int count) { |
+ // Depending on how many comparisons we do we should switch to the |
+ // hash-table-based checks which have a one-time overhead for |
+ // initializing but O(1) for HasKey checks. |
+ if (!set_.is_null()) return; |
+ // This limit was obtained through evaluation of a microbench. |
+ if (length_ * count < 50) return; |
+ set_ = OrderedHashSet::Allocate(isolate_, length_); |
+ for (int i = 0; i < length_; i++) { |
+ Handle<Object> value(keys_->get(i), isolate_); |
+ set_ = OrderedHashSet::Add(set_, value); |
+ } |
+} |
+ |
+ |
+void KeyAccumulator::EnsureCapacity(int capacity) { |
+ if (keys_.is_null() || keys_->length() <= capacity) { |
+ Grow(); |
+ } |
+} |
+ |
+ |
+void KeyAccumulator::Grow() { |
+ // The OrderedHashSet handles growing by itself. |
+ if (!set_.is_null()) return; |
+ // Otherwise, grow the internal keys_ FixedArray |
+ int capacity = keys_.is_null() ? 16 : keys_->length() * 2 + 16; |
+ Handle<FixedArray> new_keys = isolate_->factory()->NewFixedArray(capacity); |
+ if (keys_.is_null()) { |
+ keys_ = new_keys; |
+ return; |
+ } |
+ int buffer_length = keys_->length(); |
+ { |
+ DisallowHeapAllocation no_gc; |
+ WriteBarrierMode mode = new_keys->GetWriteBarrierMode(no_gc); |
+ for (int i = 0; i < buffer_length; i++) { |
+ new_keys->set(i, keys_->get(i), mode); |
+ } |
+ } |
+ keys_ = new_keys; |
+} |
+ |
+ |
MaybeHandle<FixedArray> JSReceiver::GetKeys(Handle<JSReceiver> object, |
KeyCollectionType type) { |
USE(ContainsOnlyValidKeys); |
Isolate* isolate = object->GetIsolate(); |
- Handle<FixedArray> content = isolate->factory()->empty_fixed_array(); |
+ KeyAccumulator accumulator(isolate); |
Handle<JSFunction> arguments_function( |
JSFunction::cast(isolate->sloppy_arguments_map()->GetConstructor())); |
@@ -6617,11 +6729,7 @@ MaybeHandle<FixedArray> JSReceiver::GetKeys(Handle<JSReceiver> object, |
arraysize(args), |
args), |
FixedArray); |
- ASSIGN_RETURN_ON_EXCEPTION( |
- isolate, content, |
- FixedArray::AddKeysFromArrayLike( |
- content, Handle<JSObject>::cast(names)), |
- FixedArray); |
+ accumulator.AddKeys(Handle<JSObject>::cast(names), FixedArray::ALL_KEYS); |
break; |
} |
@@ -6640,23 +6748,17 @@ MaybeHandle<FixedArray> JSReceiver::GetKeys(Handle<JSReceiver> object, |
Handle<FixedArray> element_keys = |
isolate->factory()->NewFixedArray(current->NumberOfEnumElements()); |
current->GetEnumElementKeys(*element_keys); |
- ASSIGN_RETURN_ON_EXCEPTION( |
- isolate, content, |
- FixedArray::UnionOfKeys(content, element_keys), |
- FixedArray); |
- DCHECK(ContainsOnlyValidKeys(content)); |
+ accumulator.AddKeys(element_keys, FixedArray::ALL_KEYS); |
+ DCHECK(ContainsOnlyValidKeys(accumulator.GetKeys())); |
// Add the element keys from the interceptor. |
if (current->HasIndexedInterceptor()) { |
Handle<JSObject> result; |
if (JSObject::GetKeysForIndexedInterceptor( |
current, object).ToHandle(&result)) { |
- ASSIGN_RETURN_ON_EXCEPTION( |
- isolate, content, |
- FixedArray::AddKeysFromArrayLike(content, result), |
- FixedArray); |
+ accumulator.AddKeys(result, FixedArray::ALL_KEYS); |
} |
- DCHECK(ContainsOnlyValidKeys(content)); |
+ DCHECK(ContainsOnlyValidKeys(accumulator.GetKeys())); |
} |
// We can cache the computed property keys if access checks are |
@@ -6674,27 +6776,26 @@ MaybeHandle<FixedArray> JSReceiver::GetKeys(Handle<JSReceiver> object, |
!current->IsJSValue() && !current->IsAccessCheckNeeded() && |
!current->HasNamedInterceptor() && !current->HasIndexedInterceptor()); |
// Compute the property keys and cache them if possible. |
- ASSIGN_RETURN_ON_EXCEPTION( |
- isolate, content, |
- FixedArray::UnionOfKeys( |
- content, JSObject::GetEnumPropertyKeys(current, cache_enum_keys)), |
- FixedArray); |
- DCHECK(ContainsOnlyValidKeys(content)); |
+ |
+ Handle<FixedArray> enum_keys = |
+ JSObject::GetEnumPropertyKeys(current, cache_enum_keys); |
+ accumulator.AddKeys(enum_keys, FixedArray::ALL_KEYS); |
+ DCHECK(ContainsOnlyValidKeys(accumulator.GetKeys())); |
// Add the non-symbol property keys from the interceptor. |
if (current->HasNamedInterceptor()) { |
Handle<JSObject> result; |
if (JSObject::GetKeysForNamedInterceptor( |
current, object).ToHandle(&result)) { |
- ASSIGN_RETURN_ON_EXCEPTION( |
- isolate, content, FixedArray::AddKeysFromArrayLike( |
- content, result, FixedArray::NON_SYMBOL_KEYS), |
- FixedArray); |
+ accumulator.AddKeys(result, FixedArray::NON_SYMBOL_KEYS); |
} |
- DCHECK(ContainsOnlyValidKeys(content)); |
+ DCHECK(ContainsOnlyValidKeys(accumulator.GetKeys())); |
} |
} |
- return content; |
+ |
+ Handle<FixedArray> keys = accumulator.GetKeys(); |
+ DCHECK(ContainsOnlyValidKeys(keys)); |
+ return keys; |
} |
@@ -8195,53 +8296,6 @@ void FixedArray::Shrink(int new_length) { |
} |
-MaybeHandle<FixedArray> FixedArray::AddKeysFromArrayLike( |
- Handle<FixedArray> content, Handle<JSObject> array, KeyFilter filter) { |
- DCHECK(array->IsJSArray() || array->HasSloppyArgumentsElements()); |
- ElementsAccessor* accessor = array->GetElementsAccessor(); |
- Handle<FixedArray> result = |
- accessor->AddElementsToFixedArray(array, content, filter); |
- |
-#ifdef ENABLE_SLOW_DCHECKS |
- if (FLAG_enable_slow_asserts) { |
- DisallowHeapAllocation no_allocation; |
- for (int i = 0; i < result->length(); i++) { |
- Object* current = result->get(i); |
- DCHECK(current->IsNumber() || current->IsName()); |
- } |
- } |
-#endif |
- return result; |
-} |
- |
- |
-MaybeHandle<FixedArray> FixedArray::UnionOfKeys(Handle<FixedArray> first, |
- Handle<FixedArray> second) { |
- if (second->length() == 0) return first; |
- if (first->length() == 0) return second; |
- Isolate* isolate = first->GetIsolate(); |
- Handle<FixedArray> result = |
- isolate->factory()->NewFixedArray(first->length() + second->length()); |
- for (int i = 0; i < first->length(); i++) { |
- result->set(i, first->get(i)); |
- } |
- int pos = first->length(); |
- for (int j = 0; j < second->length(); j++) { |
- Object* current = second->get(j); |
- int i; |
- for (i = 0; i < first->length(); i++) { |
- if (current->KeyEquals(first->get(i))) break; |
- } |
- if (i == first->length()) { |
- result->set(pos++, current); |
- } |
- } |
- |
- result->Shrink(pos); |
- return result; |
-} |
- |
- |
void FixedArray::CopyTo(int pos, FixedArray* dest, int dest_pos, int len) { |
DisallowHeapAllocation no_gc; |
WriteBarrierMode mode = dest->GetWriteBarrierMode(no_gc); |
@@ -15564,6 +15618,48 @@ Handle<Derived> OrderedHashTable<Derived, Iterator, entrysize>::Clear( |
return new_table; |
} |
+template <class Derived, class Iterator, int entrysize> |
+bool OrderedHashTable<Derived, Iterator, entrysize>::HasKey( |
+ Handle<Derived> table, Handle<Object> key) { |
+ int entry = table->KeyToFirstEntry(*key); |
+ // Walk the chain in the bucket to find the key. |
+ while (entry != kNotFound) { |
+ Object* candidate_key = table->KeyAt(entry); |
+ if (candidate_key->SameValueZero(*key)) return true; |
+ entry = table->NextChainEntry(entry); |
+ } |
+ return false; |
+} |
+ |
+ |
+Handle<OrderedHashSet> OrderedHashSet::Add(Handle<OrderedHashSet> table, |
+ Handle<Object> key) { |
+ int hash = Object::GetOrCreateHash(table->GetIsolate(), key)->value(); |
+ int entry = table->HashToEntry(hash); |
+ // Walk the chain of the bucket and try finding the key. |
+ while (entry != kNotFound) { |
+ Object* candidate_key = table->KeyAt(entry); |
+ // Do not add if we have the key already |
+ if (candidate_key->SameValueZero(*key)) return table; |
+ entry = table->NextChainEntry(entry); |
+ } |
+ |
+ table = OrderedHashSet::EnsureGrowable(table); |
+ // Read the existing bucket values. |
+ int bucket = table->HashToBucket(hash); |
+ int previous_entry = table->HashToEntry(hash); |
+ int nof = table->NumberOfElements(); |
+ // Insert a new entry at the end, |
+ int new_entry = nof + table->NumberOfDeletedElements(); |
+ int new_index = table->EntryToIndex(new_entry); |
+ table->set(new_index, *key); |
+ table->set(new_index + kChainOffset, Smi::FromInt(previous_entry)); |
+ // and point the bucket to the new entry. |
+ table->set(kHashTableStartIndex + bucket, Smi::FromInt(new_entry)); |
+ table->SetNumberOfElements(nof + 1); |
+ return table; |
+} |
+ |
template<class Derived, class Iterator, int entrysize> |
Handle<Derived> OrderedHashTable<Derived, Iterator, entrysize>::Rehash( |
@@ -15626,6 +15722,9 @@ template Handle<OrderedHashSet> |
OrderedHashTable<OrderedHashSet, JSSetIterator, 1>::Clear( |
Handle<OrderedHashSet> table); |
+template bool OrderedHashTable<OrderedHashSet, JSSetIterator, 1>::HasKey( |
+ Handle<OrderedHashSet> table, Handle<Object> key); |
+ |
template Handle<OrderedHashMap> |
OrderedHashTable<OrderedHashMap, JSMapIterator, 2>::Allocate( |
@@ -15643,6 +15742,9 @@ template Handle<OrderedHashMap> |
OrderedHashTable<OrderedHashMap, JSMapIterator, 2>::Clear( |
Handle<OrderedHashMap> table); |
+template bool OrderedHashTable<OrderedHashMap, JSMapIterator, 2>::HasKey( |
+ Handle<OrderedHashMap> table, Handle<Object> key); |
+ |
template<class Derived, class TableType> |
void OrderedHashTableIterator<Derived, TableType>::Transition() { |