diff --git a/src/objects-inl.h b/src/objects-inl.h index 0d83c47946..53a3183694 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -1966,6 +1966,17 @@ void DescriptorArray::Swap(int first, int second) { } +template +int HashTable::ComputeCapacity(int at_least_space_for) { + const int kMinCapacity = 32; + int capacity = RoundUpToPowerOf2(at_least_space_for * 2); + if (capacity < kMinCapacity) { + capacity = kMinCapacity; // Guarantee min capacity. + } + return capacity; +} + + template int HashTable::FindEntry(Key key) { return FindEntry(GetIsolate(), key); diff --git a/src/objects.cc b/src/objects.cc index eeca17d433..a423ae4c25 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -2899,9 +2899,12 @@ MaybeObject* JSObject::NormalizeElements() { int length = IsJSArray() ? Smi::cast(JSArray::cast(this)->length())->value() : array->length(); + int old_capacity = 0; + int used_elements = 0; + GetElementsCapacityAndUsage(&old_capacity, &used_elements); NumberDictionary* dictionary = NULL; { Object* object; - MaybeObject* maybe = NumberDictionary::Allocate(length); + MaybeObject* maybe = NumberDictionary::Allocate(used_elements); if (!maybe->ToObject(&object)) return maybe; dictionary = NumberDictionary::cast(object); } @@ -8618,7 +8621,7 @@ MaybeObject* JSObject::SetDictionaryElement(uint32_t index, } else { new_length = dictionary->max_number_key() + 1; } - MaybeObject* result = ShouldConvertToFastDoubleElements() + MaybeObject* result = CanConvertToFastDoubleElements() ? SetFastDoubleElementsCapacityAndLength(new_length, new_length) : SetFastElementsCapacityAndLength(new_length, new_length); if (result->IsFailure()) return result; @@ -9160,7 +9163,15 @@ MaybeObject* JSObject::GetExternalElement(uint32_t index) { bool JSObject::HasDenseElements() { int capacity = 0; - int number_of_elements = 0; + int used = 0; + GetElementsCapacityAndUsage(&capacity, &used); + return (capacity == 0) || (used > (capacity / 2)); +} + + +void JSObject::GetElementsCapacityAndUsage(int* capacity, int* used) { + *capacity = 0; + *used = 0; FixedArrayBase* backing_store_base = FixedArrayBase::cast(elements()); FixedArray* backing_store = NULL; @@ -9171,34 +9182,33 @@ bool JSObject::HasDenseElements() { backing_store = FixedArray::cast(backing_store_base); if (backing_store->IsDictionary()) { NumberDictionary* dictionary = NumberDictionary::cast(backing_store); - capacity = dictionary->Capacity(); - number_of_elements = dictionary->NumberOfElements(); + *capacity = dictionary->Capacity(); + *used = dictionary->NumberOfElements(); break; } // Fall through. case FAST_ELEMENTS: backing_store = FixedArray::cast(backing_store_base); - capacity = backing_store->length(); - for (int i = 0; i < capacity; ++i) { - if (!backing_store->get(i)->IsTheHole()) ++number_of_elements; + *capacity = backing_store->length(); + for (int i = 0; i < *capacity; ++i) { + if (!backing_store->get(i)->IsTheHole()) ++(*used); } break; case DICTIONARY_ELEMENTS: { NumberDictionary* dictionary = NumberDictionary::cast(FixedArray::cast(elements())); - capacity = dictionary->Capacity(); - number_of_elements = dictionary->NumberOfElements(); + *capacity = dictionary->Capacity(); + *used = dictionary->NumberOfElements(); break; } case FAST_DOUBLE_ELEMENTS: { FixedDoubleArray* elms = FixedDoubleArray::cast(elements()); - capacity = elms->length(); - for (int i = 0; i < capacity; i++) { - if (!elms->is_the_hole(i)) number_of_elements++; + *capacity = elms->length(); + for (int i = 0; i < *capacity; i++) { + if (!elms->is_the_hole(i)) ++(*used); } break; } - case EXTERNAL_PIXEL_ELEMENTS: case EXTERNAL_BYTE_ELEMENTS: case EXTERNAL_UNSIGNED_BYTE_ELEMENTS: case EXTERNAL_SHORT_ELEMENTS: @@ -9206,31 +9216,34 @@ bool JSObject::HasDenseElements() { case EXTERNAL_INT_ELEMENTS: case EXTERNAL_UNSIGNED_INT_ELEMENTS: case EXTERNAL_FLOAT_ELEMENTS: - case EXTERNAL_DOUBLE_ELEMENTS: { - return true; - } + case EXTERNAL_DOUBLE_ELEMENTS: + case EXTERNAL_PIXEL_ELEMENTS: + // External arrays are considered 100% used. + ExternalArray* external_array = ExternalArray::cast(elements()); + *capacity = external_array->length(); + *used = external_array->length(); + break; } - return (capacity == 0) || (number_of_elements > (capacity / 2)); } bool JSObject::ShouldConvertToSlowElements(int new_capacity) { - if (new_capacity <= kMaxFastElementsLength) return false; - // Keep the array in fast case if the current backing storage is - // almost filled and if the new capacity is no more than twice the - // old capacity. - int old_capacity = 0; - if (elements()->map() == GetHeap()->non_strict_arguments_elements_map()) { - FixedArray* backing_store = FixedArray::cast(elements()); - old_capacity = FixedArray::cast(backing_store->get(1))->length(); - } else if (HasFastElements()) { - old_capacity = FixedArray::cast(elements())->length(); - } else if (HasFastDoubleElements()) { - old_capacity = FixedDoubleArray::cast(elements())->length(); - } else { - UNREACHABLE(); + STATIC_ASSERT(kMaxUncheckedOldFastElementsLength <= + kMaxUncheckedFastElementsLength); + if (new_capacity <= kMaxUncheckedOldFastElementsLength || + (new_capacity <= kMaxUncheckedFastElementsLength && + GetHeap()->InNewSpace(this))) { + return false; } - return !HasDenseElements() || ((new_capacity / 2) > old_capacity); + // If the fast-case backing storage takes up roughly three times as + // much space (in machine words) as a dictionary backing storage + // would, the object should have slow elements. + int old_capacity = 0; + int used_elements = 0; + GetElementsCapacityAndUsage(&old_capacity, &used_elements); + int dictionary_size = NumberDictionary::ComputeCapacity(used_elements) * + NumberDictionary::kEntrySize; + return 3 * dictionary_size <= new_capacity; } @@ -9253,20 +9266,21 @@ bool JSObject::ShouldConvertToFastElements() { // dictionary, we cannot go back to fast case. if (dictionary->requires_slow_elements()) return false; // If the dictionary backing storage takes up roughly half as much - // space as a fast-case backing storage would the array should have - // fast elements. - uint32_t length = 0; + // space (in machine words) as a fast-case backing storage would, + // the object should have fast elements. + uint32_t array_size = 0; if (IsJSArray()) { - CHECK(JSArray::cast(this)->length()->ToArrayIndex(&length)); + CHECK(JSArray::cast(this)->length()->ToArrayIndex(&array_size)); } else { - length = dictionary->max_number_key(); + array_size = dictionary->max_number_key(); } - return static_cast(dictionary->Capacity()) >= - (length / (2 * NumberDictionary::kEntrySize)); + uint32_t dictionary_size = static_cast(dictionary->Capacity()) * + NumberDictionary::kEntrySize; + return 2 * dictionary_size >= array_size; } -bool JSObject::ShouldConvertToFastDoubleElements() { +bool JSObject::CanConvertToFastDoubleElements() { if (FLAG_unbox_double_arrays) { ASSERT(HasDictionaryElements()); NumberDictionary* dictionary = NumberDictionary::cast(elements()); @@ -10197,11 +10211,8 @@ void HashTable::IterateElements(ObjectVisitor* v) { template MaybeObject* HashTable::Allocate(int at_least_space_for, PretenureFlag pretenure) { - const int kMinCapacity = 32; - int capacity = RoundUpToPowerOf2(at_least_space_for * 2); - if (capacity < kMinCapacity) { - capacity = kMinCapacity; // Guarantee min capacity. - } else if (capacity > HashTable::kMaxCapacity) { + int capacity = ComputeCapacity(at_least_space_for); + if (capacity > HashTable::kMaxCapacity) { return Failure::OutOfMemoryException(); } diff --git a/src/objects.h b/src/objects.h index 13edfd78ba..9ef14dbdab 100644 --- a/src/objects.h +++ b/src/objects.h @@ -1654,7 +1654,7 @@ class JSObject: public JSReceiver { bool ShouldConvertToFastElements(); // Returns true if the elements of JSObject contains only values that can be // represented in a FixedDoubleArray. - bool ShouldConvertToFastDoubleElements(); + bool CanConvertToFastDoubleElements(); // Tells whether the index'th element is present. inline bool HasElement(uint32_t index); @@ -1948,8 +1948,21 @@ class JSObject: public JSReceiver { // Also maximal value of JSArray's length property. static const uint32_t kMaxElementCount = 0xffffffffu; + // Constants for heuristics controlling conversion of fast elements + // to slow elements. + + // Maximal gap that can be introduced by adding an element beyond + // the current elements length. static const uint32_t kMaxGap = 1024; - static const int kMaxFastElementsLength = 5000; + + // Maximal length of fast elements array that won't be checked for + // being dense enough on expansion. + static const int kMaxUncheckedFastElementsLength = 5000; + + // Same as above but for old arrays. This limit is more strict. We + // don't want to be wasteful with long lived objects. + static const int kMaxUncheckedOldFastElementsLength = 500; + static const int kInitialMaxFastElementArray = 100000; static const int kMaxFastProperties = 12; static const int kMaxInstanceSize = 255 * kPointerSize; @@ -2015,6 +2028,9 @@ class JSObject: public JSReceiver { // Returns true if most of the elements backing storage is used. bool HasDenseElements(); + // Gets the current elements capacity and the number of used elements. + void GetElementsCapacityAndUsage(int* capacity, int* used); + bool CanSetCallback(String* name); MUST_USE_RESULT MaybeObject* SetElementCallback( uint32_t index, @@ -2491,6 +2507,10 @@ class HashTable: public FixedArray { int at_least_space_for, PretenureFlag pretenure = NOT_TENURED); + // Computes the required capacity for a table holding the given + // number of elements. May be more than HashTable::kMaxCapacity. + static int ComputeCapacity(int at_least_space_for); + // Returns the key at entry. Object* KeyAt(int entry) { return get(EntryToIndex(entry)); } diff --git a/src/runtime.cc b/src/runtime.cc index de8c6fddc4..e193dfc977 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -1666,7 +1666,9 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_RegExpExec) { RUNTIME_FUNCTION(MaybeObject*, Runtime_RegExpConstructResult) { ASSERT(args.length() == 3); CONVERT_SMI_ARG_CHECKED(elements_count, 0); - if (elements_count > JSArray::kMaxFastElementsLength) { + if (elements_count < 0 || + elements_count > FixedArray::kMaxLength || + !Smi::IsValid(elements_count)) { return isolate->ThrowIllegalOperation(); } Object* new_object;