From cbdb13533ee6c5fdd2ed5df41e70892cb25e5f57 Mon Sep 17 00:00:00 2001 From: cbruni Date: Mon, 7 Sep 2015 06:44:44 -0700 Subject: [PATCH] Adding ElementsAccessor::Concat - Moving parts of ArrayConcat from builtins.cc to the ElementsAccessor - Removing ArrayConcat Runtime Function BUG=v8:4317 LOG=N Review URL: https://codereview.chromium.org/1330483003 Cr-Commit-Position: refs/heads/master@{#30619} --- src/array.js | 21 - src/bootstrapper.cc | 34 + src/builtins.cc | 880 +++++++++++++++++++++++-- src/elements.cc | 63 ++ src/elements.h | 5 +- src/runtime/runtime-array.cc | 774 ---------------------- src/runtime/runtime.h | 1 - test/mjsunit/array-natives-elements.js | 2 +- test/test262-es6/test262-es6.status | 3 - 9 files changed, 923 insertions(+), 860 deletions(-) diff --git a/src/array.js b/src/array.js index e6a33dfeaf..265e80bdd9 100644 --- a/src/array.js +++ b/src/array.js @@ -522,24 +522,6 @@ function ArrayPush() { } -// Returns an array containing the array elements of the object followed -// by the array elements of each argument in order. See ECMA-262, -// section 15.4.4.7. -function ArrayConcatJS(arg1) { // length == 1 - CHECK_OBJECT_COERCIBLE(this, "Array.prototype.concat"); - - var array = TO_OBJECT(this); - var arg_count = %_ArgumentsLength(); - var arrays = new InternalArray(1 + arg_count); - arrays[0] = array; - for (var i = 0; i < arg_count; i++) { - arrays[i + 1] = %_Arguments(i); - } - - return %ArrayConcat(arrays); -} - - // For implementing reverse() on large, sparse arrays. function SparseReverse(array, len) { var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len)); @@ -1642,7 +1624,6 @@ utils.InstallFunctions(GlobalArray.prototype, DONT_ENUM, [ "join", getFunction("join", ArrayJoin), "pop", getFunction("pop", ArrayPop), "push", getFunction("push", ArrayPush, 1), - "concat", getFunction("concat", ArrayConcatJS, 1), "reverse", getFunction("reverse", ArrayReverse), "shift", getFunction("shift", ArrayShift), "unshift", getFunction("unshift", ArrayUnshift, 1), @@ -1666,7 +1647,6 @@ utils.InstallFunctions(GlobalArray.prototype, DONT_ENUM, [ // exposed to user code. // Adding only the functions that are actually used. utils.SetUpLockedPrototype(InternalArray, GlobalArray(), [ - "concat", getFunction("concat", ArrayConcatJS), "indexOf", getFunction("indexOf", ArrayIndexOf), "join", getFunction("join", ArrayJoin), "pop", getFunction("pop", ArrayPop), @@ -1707,7 +1687,6 @@ utils.Export(function(to) { }); %InstallToContext([ - "array_concat", ArrayConcatJS, "array_pop", ArrayPop, "array_push", ArrayPush, "array_shift", ArrayShift, diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index e8cf0d6470..7ee75b2cab 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -2331,6 +2331,40 @@ bool Genesis::InstallNatives(ContextType context_type) { to_primitive->shared()->set_length(1); } + // Install Array.prototype.concat + { + Handle array_constructor(native_context()->array_function()); + Handle proto(JSObject::cast(array_constructor->prototype())); + Handle concat = + InstallFunction(proto, "concat", JS_OBJECT_TYPE, JSObject::kHeaderSize, + MaybeHandle(), Builtins::kArrayConcat); + + // Make sure that Array.prototype.concat appears to be compiled. + // The code will never be called, but inline caching for call will + // only work if it appears to be compiled. + concat->shared()->DontAdaptArguments(); + DCHECK(concat->is_compiled()); + // Set the lengths for the functions to satisfy ECMA-262. + concat->shared()->set_length(1); + } + + // Install InternalArray.prototype.concat + { + Handle array_constructor( + native_context()->internal_array_function()); + Handle proto(JSObject::cast(array_constructor->prototype())); + Handle concat = + InstallFunction(proto, "concat", JS_OBJECT_TYPE, JSObject::kHeaderSize, + MaybeHandle(), Builtins::kArrayConcat); + + // Make sure that InternalArray.prototype.concat appears to be compiled. + // The code will never be called, but inline caching for call will + // only work if it appears to be compiled. + concat->shared()->DontAdaptArguments(); + DCHECK(concat->is_compiled()); + // Set the lengths for the functions to satisfy ECMA-262. + concat->shared()->set_length(1); + } // Install Function.prototype.call and apply. { Handle key = factory()->Function_string(); diff --git a/src/builtins.cc b/src/builtins.cc index a1445b732c..f777f14f52 100644 --- a/src/builtins.cc +++ b/src/builtins.cc @@ -604,36 +604,810 @@ BUILTIN(ArraySplice) { } -BUILTIN(ArrayConcat) { - HandleScope scope(isolate); +// Array Concat ------------------------------------------------------------- - int n_arguments = args.length(); +namespace { + +/** + * A simple visitor visits every element of Array's. + * The backend storage can be a fixed array for fast elements case, + * or a dictionary for sparse array. Since Dictionary is a subtype + * of FixedArray, the class can be used by both fast and slow cases. + * The second parameter of the constructor, fast_elements, specifies + * whether the storage is a FixedArray or Dictionary. + * + * An index limit is used to deal with the situation that a result array + * length overflows 32-bit non-negative integer. + */ +class ArrayConcatVisitor { + public: + ArrayConcatVisitor(Isolate* isolate, Handle storage, + bool fast_elements) + : isolate_(isolate), + storage_(Handle::cast( + isolate->global_handles()->Create(*storage))), + index_offset_(0u), + bit_field_(FastElementsField::encode(fast_elements) | + ExceedsLimitField::encode(false)) {} + + ~ArrayConcatVisitor() { clear_storage(); } + + void visit(uint32_t i, Handle elm) { + if (i >= JSObject::kMaxElementCount - index_offset_) { + set_exceeds_array_limit(true); + return; + } + uint32_t index = index_offset_ + i; + + if (fast_elements()) { + if (index < static_cast(storage_->length())) { + storage_->set(index, *elm); + return; + } + // Our initial estimate of length was foiled, possibly by + // getters on the arrays increasing the length of later arrays + // during iteration. + // This shouldn't happen in anything but pathological cases. + SetDictionaryMode(); + // Fall-through to dictionary mode. + } + DCHECK(!fast_elements()); + Handle dict( + SeededNumberDictionary::cast(*storage_)); + // The object holding this backing store has just been allocated, so + // it cannot yet be used as a prototype. + Handle result = + SeededNumberDictionary::AtNumberPut(dict, index, elm, false); + if (!result.is_identical_to(dict)) { + // Dictionary needed to grow. + clear_storage(); + set_storage(*result); + } + } + + void increase_index_offset(uint32_t delta) { + if (JSObject::kMaxElementCount - index_offset_ < delta) { + index_offset_ = JSObject::kMaxElementCount; + } else { + index_offset_ += delta; + } + // If the initial length estimate was off (see special case in visit()), + // but the array blowing the limit didn't contain elements beyond the + // provided-for index range, go to dictionary mode now. + if (fast_elements() && + index_offset_ > + static_cast(FixedArrayBase::cast(*storage_)->length())) { + SetDictionaryMode(); + } + } + + bool exceeds_array_limit() const { + return ExceedsLimitField::decode(bit_field_); + } + + Handle ToArray() { + Handle array = isolate_->factory()->NewJSArray(0); + Handle length = + isolate_->factory()->NewNumber(static_cast(index_offset_)); + Handle map = JSObject::GetElementsTransitionMap( + array, fast_elements() ? FAST_HOLEY_ELEMENTS : DICTIONARY_ELEMENTS); + array->set_map(*map); + array->set_length(*length); + array->set_elements(*storage_); + return array; + } + + private: + // Convert storage to dictionary mode. + void SetDictionaryMode() { + DCHECK(fast_elements()); + Handle current_storage(*storage_); + Handle slow_storage( + SeededNumberDictionary::New(isolate_, current_storage->length())); + uint32_t current_length = static_cast(current_storage->length()); + for (uint32_t i = 0; i < current_length; i++) { + HandleScope loop_scope(isolate_); + Handle element(current_storage->get(i), isolate_); + if (!element->IsTheHole()) { + // The object holding this backing store has just been allocated, so + // it cannot yet be used as a prototype. + Handle new_storage = + SeededNumberDictionary::AtNumberPut(slow_storage, i, element, + false); + if (!new_storage.is_identical_to(slow_storage)) { + slow_storage = loop_scope.CloseAndEscape(new_storage); + } + } + } + clear_storage(); + set_storage(*slow_storage); + set_fast_elements(false); + } + + inline void clear_storage() { + GlobalHandles::Destroy(Handle::cast(storage_).location()); + } + + inline void set_storage(FixedArray* storage) { + storage_ = + Handle::cast(isolate_->global_handles()->Create(storage)); + } + + class FastElementsField : public BitField {}; + class ExceedsLimitField : public BitField {}; + + bool fast_elements() const { return FastElementsField::decode(bit_field_); } + void set_fast_elements(bool fast) { + bit_field_ = FastElementsField::update(bit_field_, fast); + } + void set_exceeds_array_limit(bool exceeds) { + bit_field_ = ExceedsLimitField::update(bit_field_, exceeds); + } + + Isolate* isolate_; + Handle storage_; // Always a global handle. + // Index after last seen index. Always less than or equal to + // JSObject::kMaxElementCount. + uint32_t index_offset_; + uint32_t bit_field_; +}; + + +uint32_t EstimateElementCount(Handle array) { + uint32_t length = static_cast(array->length()->Number()); + int element_count = 0; + switch (array->GetElementsKind()) { + case FAST_SMI_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_ELEMENTS: + case FAST_HOLEY_ELEMENTS: { + // Fast elements can't have lengths that are not representable by + // a 32-bit signed integer. + DCHECK(static_cast(FixedArray::kMaxLength) >= 0); + int fast_length = static_cast(length); + Handle elements(FixedArray::cast(array->elements())); + for (int i = 0; i < fast_length; i++) { + if (!elements->get(i)->IsTheHole()) element_count++; + } + break; + } + case FAST_DOUBLE_ELEMENTS: + case FAST_HOLEY_DOUBLE_ELEMENTS: { + // Fast elements can't have lengths that are not representable by + // a 32-bit signed integer. + DCHECK(static_cast(FixedDoubleArray::kMaxLength) >= 0); + int fast_length = static_cast(length); + if (array->elements()->IsFixedArray()) { + DCHECK(FixedArray::cast(array->elements())->length() == 0); + break; + } + Handle elements( + FixedDoubleArray::cast(array->elements())); + for (int i = 0; i < fast_length; i++) { + if (!elements->is_the_hole(i)) element_count++; + } + break; + } + case DICTIONARY_ELEMENTS: { + Handle dictionary( + SeededNumberDictionary::cast(array->elements())); + int capacity = dictionary->Capacity(); + for (int i = 0; i < capacity; i++) { + Handle key(dictionary->KeyAt(i), array->GetIsolate()); + if (dictionary->IsKey(*key)) { + element_count++; + } + } + break; + } + case FAST_SLOPPY_ARGUMENTS_ELEMENTS: + case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: +#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) case TYPE##_ELEMENTS: + + TYPED_ARRAYS(TYPED_ARRAY_CASE) +#undef TYPED_ARRAY_CASE + // External arrays are always dense. + return length; + } + // As an estimate, we assume that the prototype doesn't contain any + // inherited elements. + return element_count; +} + + +template +void IterateTypedArrayElements(Isolate* isolate, Handle receiver, + bool elements_are_ints, + bool elements_are_guaranteed_smis, + ArrayConcatVisitor* visitor) { + Handle array( + ExternalArrayClass::cast(receiver->elements())); + uint32_t len = static_cast(array->length()); + + DCHECK(visitor != NULL); + if (elements_are_ints) { + if (elements_are_guaranteed_smis) { + for (uint32_t j = 0; j < len; j++) { + HandleScope loop_scope(isolate); + Handle e(Smi::FromInt(static_cast(array->get_scalar(j))), + isolate); + visitor->visit(j, e); + } + } else { + for (uint32_t j = 0; j < len; j++) { + HandleScope loop_scope(isolate); + int64_t val = static_cast(array->get_scalar(j)); + if (Smi::IsValid(static_cast(val))) { + Handle e(Smi::FromInt(static_cast(val)), isolate); + visitor->visit(j, e); + } else { + Handle e = + isolate->factory()->NewNumber(static_cast(val)); + visitor->visit(j, e); + } + } + } + } else { + for (uint32_t j = 0; j < len; j++) { + HandleScope loop_scope(isolate); + Handle e = isolate->factory()->NewNumber(array->get_scalar(j)); + visitor->visit(j, e); + } + } +} + + +// Used for sorting indices in a List. +int compareUInt32(const uint32_t* ap, const uint32_t* bp) { + uint32_t a = *ap; + uint32_t b = *bp; + return (a == b) ? 0 : (a < b) ? -1 : 1; +} + + +void CollectElementIndices(Handle object, uint32_t range, + List* indices) { + Isolate* isolate = object->GetIsolate(); + ElementsKind kind = object->GetElementsKind(); + switch (kind) { + case FAST_SMI_ELEMENTS: + case FAST_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: { + Handle elements(FixedArray::cast(object->elements())); + uint32_t length = static_cast(elements->length()); + if (range < length) length = range; + for (uint32_t i = 0; i < length; i++) { + if (!elements->get(i)->IsTheHole()) { + indices->Add(i); + } + } + break; + } + case FAST_HOLEY_DOUBLE_ELEMENTS: + case FAST_DOUBLE_ELEMENTS: { + if (object->elements()->IsFixedArray()) { + DCHECK(object->elements()->length() == 0); + break; + } + Handle elements( + FixedDoubleArray::cast(object->elements())); + uint32_t length = static_cast(elements->length()); + if (range < length) length = range; + for (uint32_t i = 0; i < length; i++) { + if (!elements->is_the_hole(i)) { + indices->Add(i); + } + } + break; + } + case DICTIONARY_ELEMENTS: { + Handle dict( + SeededNumberDictionary::cast(object->elements())); + uint32_t capacity = dict->Capacity(); + for (uint32_t j = 0; j < capacity; j++) { + HandleScope loop_scope(isolate); + Handle k(dict->KeyAt(j), isolate); + if (dict->IsKey(*k)) { + DCHECK(k->IsNumber()); + uint32_t index = static_cast(k->Number()); + if (index < range) { + indices->Add(index); + } + } + } + break; + } +#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) case TYPE##_ELEMENTS: + + TYPED_ARRAYS(TYPED_ARRAY_CASE) +#undef TYPED_ARRAY_CASE + { + uint32_t length = static_cast( + FixedArrayBase::cast(object->elements())->length()); + if (range <= length) { + length = range; + // We will add all indices, so we might as well clear it first + // and avoid duplicates. + indices->Clear(); + } + for (uint32_t i = 0; i < length; i++) { + indices->Add(i); + } + if (length == range) return; // All indices accounted for already. + break; + } + case FAST_SLOPPY_ARGUMENTS_ELEMENTS: + case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: { + ElementsAccessor* accessor = object->GetElementsAccessor(); + for (uint32_t i = 0; i < range; i++) { + if (accessor->HasElement(object, i)) { + indices->Add(i); + } + } + break; + } + } + + PrototypeIterator iter(isolate, object); + if (!iter.IsAtEnd()) { + // The prototype will usually have no inherited element indices, + // but we have to check. + CollectElementIndices( + Handle::cast(PrototypeIterator::GetCurrent(iter)), range, + indices); + } +} + + +bool IterateElementsSlow(Isolate* isolate, Handle receiver, + uint32_t length, ArrayConcatVisitor* visitor) { + for (uint32_t i = 0; i < length; ++i) { + HandleScope loop_scope(isolate); + Maybe maybe = JSReceiver::HasElement(receiver, i); + if (!maybe.IsJust()) return false; + if (maybe.FromJust()) { + Handle element_value; + ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, element_value, + Object::GetElement(isolate, receiver, i), + false); + visitor->visit(i, element_value); + } + } + visitor->increase_index_offset(length); + return true; +} + + +/** + * A helper function that visits elements of a JSObject in numerical + * order. + * + * The visitor argument called for each existing element in the array + * with the element index and the element's value. + * Afterwards it increments the base-index of the visitor by the array + * length. + * Returns false if any access threw an exception, otherwise true. + */ +bool IterateElements(Isolate* isolate, Handle receiver, + ArrayConcatVisitor* visitor) { + uint32_t length = 0; + + if (receiver->IsJSArray()) { + Handle array(Handle::cast(receiver)); + length = static_cast(array->length()->Number()); + } else { + Handle val; + Handle key(isolate->heap()->length_string(), isolate); + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, val, Runtime::GetObjectProperty(isolate, receiver, key), + false); + // TODO(caitp): Support larger element indexes (up to 2^53-1). + if (!val->ToUint32(&length)) { + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, val, Execution::ToLength(isolate, val), false); + val->ToUint32(&length); + } + } + + if (!(receiver->IsJSArray() || receiver->IsJSTypedArray())) { + // For classes which are not known to be safe to access via elements alone, + // use the slow case. + return IterateElementsSlow(isolate, receiver, length, visitor); + } + + switch (receiver->GetElementsKind()) { + case FAST_SMI_ELEMENTS: + case FAST_ELEMENTS: + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_HOLEY_ELEMENTS: { + // Run through the elements FixedArray and use HasElement and GetElement + // to check the prototype for missing elements. + Handle elements(FixedArray::cast(receiver->elements())); + int fast_length = static_cast(length); + DCHECK(fast_length <= elements->length()); + for (int j = 0; j < fast_length; j++) { + HandleScope loop_scope(isolate); + Handle element_value(elements->get(j), isolate); + if (!element_value->IsTheHole()) { + visitor->visit(j, element_value); + } else { + Maybe maybe = JSReceiver::HasElement(receiver, j); + if (!maybe.IsJust()) return false; + if (maybe.FromJust()) { + // Call GetElement on receiver, not its prototype, or getters won't + // have the correct receiver. + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, element_value, + Object::GetElement(isolate, receiver, j), false); + visitor->visit(j, element_value); + } + } + } + break; + } + case FAST_HOLEY_DOUBLE_ELEMENTS: + case FAST_DOUBLE_ELEMENTS: { + // Empty array is FixedArray but not FixedDoubleArray. + if (length == 0) break; + // Run through the elements FixedArray and use HasElement and GetElement + // to check the prototype for missing elements. + if (receiver->elements()->IsFixedArray()) { + DCHECK(receiver->elements()->length() == 0); + break; + } + Handle elements( + FixedDoubleArray::cast(receiver->elements())); + int fast_length = static_cast(length); + DCHECK(fast_length <= elements->length()); + for (int j = 0; j < fast_length; j++) { + HandleScope loop_scope(isolate); + if (!elements->is_the_hole(j)) { + double double_value = elements->get_scalar(j); + Handle element_value = + isolate->factory()->NewNumber(double_value); + visitor->visit(j, element_value); + } else { + Maybe maybe = JSReceiver::HasElement(receiver, j); + if (!maybe.IsJust()) return false; + if (maybe.FromJust()) { + // Call GetElement on receiver, not its prototype, or getters won't + // have the correct receiver. + Handle element_value; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, element_value, + Object::GetElement(isolate, receiver, j), false); + visitor->visit(j, element_value); + } + } + } + break; + } + case DICTIONARY_ELEMENTS: { + Handle dict(receiver->element_dictionary()); + List indices(dict->Capacity() / 2); + // Collect all indices in the object and the prototypes less + // than length. This might introduce duplicates in the indices list. + CollectElementIndices(receiver, length, &indices); + indices.Sort(&compareUInt32); + int j = 0; + int n = indices.length(); + while (j < n) { + HandleScope loop_scope(isolate); + uint32_t index = indices[j]; + Handle element; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, element, Object::GetElement(isolate, receiver, index), + false); + visitor->visit(index, element); + // Skip to next different index (i.e., omit duplicates). + do { + j++; + } while (j < n && indices[j] == index); + } + break; + } + case UINT8_CLAMPED_ELEMENTS: { + Handle pixels( + FixedUint8ClampedArray::cast(receiver->elements())); + for (uint32_t j = 0; j < length; j++) { + Handle e(Smi::FromInt(pixels->get_scalar(j)), isolate); + visitor->visit(j, e); + } + break; + } + case INT8_ELEMENTS: { + IterateTypedArrayElements(isolate, receiver, true, + true, visitor); + break; + } + case UINT8_ELEMENTS: { + IterateTypedArrayElements(isolate, receiver, + true, true, visitor); + break; + } + case INT16_ELEMENTS: { + IterateTypedArrayElements(isolate, receiver, + true, true, visitor); + break; + } + case UINT16_ELEMENTS: { + IterateTypedArrayElements( + isolate, receiver, true, true, visitor); + break; + } + case INT32_ELEMENTS: { + IterateTypedArrayElements(isolate, receiver, + true, false, visitor); + break; + } + case UINT32_ELEMENTS: { + IterateTypedArrayElements( + isolate, receiver, true, false, visitor); + break; + } + case FLOAT32_ELEMENTS: { + IterateTypedArrayElements( + isolate, receiver, false, false, visitor); + break; + } + case FLOAT64_ELEMENTS: { + IterateTypedArrayElements( + isolate, receiver, false, false, visitor); + break; + } + case FAST_SLOPPY_ARGUMENTS_ELEMENTS: + case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: { + for (uint32_t index = 0; index < length; index++) { + HandleScope loop_scope(isolate); + Handle element; + ASSIGN_RETURN_ON_EXCEPTION_VALUE( + isolate, element, Object::GetElement(isolate, receiver, index), + false); + visitor->visit(index, element); + } + break; + } + } + visitor->increase_index_offset(length); + return true; +} + + +bool HasConcatSpreadableModifier(Isolate* isolate, Handle obj) { + if (!FLAG_harmony_concat_spreadable) return false; + Handle key(isolate->factory()->is_concat_spreadable_symbol()); + Maybe maybe = + JSReceiver::HasProperty(Handle::cast(obj), key); + if (!maybe.IsJust()) return false; + return maybe.FromJust(); +} + + +bool IsConcatSpreadable(Isolate* isolate, Handle obj) { + HandleScope handle_scope(isolate); + if (!obj->IsSpecObject()) return false; + if (FLAG_harmony_concat_spreadable) { + Handle key(isolate->factory()->is_concat_spreadable_symbol()); + Handle value; + MaybeHandle maybeValue = + i::Runtime::GetObjectProperty(isolate, obj, key); + if (maybeValue.ToHandle(&value) && !value->IsUndefined()) { + return value->BooleanValue(); + } + } + return obj->IsJSArray(); +} + + +/** + * Array::concat implementation. + * See ECMAScript 262, 15.4.4.4. + * TODO(581): Fix non-compliance for very large concatenations and update to + * following the ECMAScript 5 specification. + */ +Object* Slow_ArrayConcat(Arguments* args, Isolate* isolate) { + int argument_count = args->length(); + + // Pass 1: estimate the length and number of elements of the result. + // The actual length can be larger if any of the arguments have getters + // that mutate other arguments (but will otherwise be precise). + // The number of elements is precise if there are no inherited elements. + + ElementsKind kind = FAST_SMI_ELEMENTS; + + uint32_t estimate_result_length = 0; + uint32_t estimate_nof_elements = 0; + for (int i = 0; i < argument_count; i++) { + HandleScope loop_scope(isolate); + Handle obj((*args)[i], isolate); + uint32_t length_estimate; + uint32_t element_estimate; + if (obj->IsJSArray()) { + Handle array(Handle::cast(obj)); + length_estimate = static_cast(array->length()->Number()); + if (length_estimate != 0) { + ElementsKind array_kind = + GetPackedElementsKind(array->map()->elements_kind()); + if (IsMoreGeneralElementsKindTransition(kind, array_kind)) { + kind = array_kind; + } + } + element_estimate = EstimateElementCount(array); + } else { + if (obj->IsHeapObject()) { + if (obj->IsNumber()) { + if (IsMoreGeneralElementsKindTransition(kind, FAST_DOUBLE_ELEMENTS)) { + kind = FAST_DOUBLE_ELEMENTS; + } + } else if (IsMoreGeneralElementsKindTransition(kind, FAST_ELEMENTS)) { + kind = FAST_ELEMENTS; + } + } + length_estimate = 1; + element_estimate = 1; + } + // Avoid overflows by capping at kMaxElementCount. + if (JSObject::kMaxElementCount - estimate_result_length < length_estimate) { + estimate_result_length = JSObject::kMaxElementCount; + } else { + estimate_result_length += length_estimate; + } + if (JSObject::kMaxElementCount - estimate_nof_elements < element_estimate) { + estimate_nof_elements = JSObject::kMaxElementCount; + } else { + estimate_nof_elements += element_estimate; + } + } + + // If estimated number of elements is more than half of length, a + // fixed array (fast case) is more time and space-efficient than a + // dictionary. + bool fast_case = (estimate_nof_elements * 2) >= estimate_result_length; + + if (fast_case && kind == FAST_DOUBLE_ELEMENTS) { + Handle storage = + isolate->factory()->NewFixedDoubleArray(estimate_result_length); + int j = 0; + bool failure = false; + if (estimate_result_length > 0) { + Handle double_storage = + Handle::cast(storage); + for (int i = 0; i < argument_count; i++) { + Handle obj((*args)[i], isolate); + if (obj->IsSmi()) { + double_storage->set(j, Smi::cast(*obj)->value()); + j++; + } else if (obj->IsNumber()) { + double_storage->set(j, obj->Number()); + j++; + } else { + JSArray* array = JSArray::cast(*obj); + uint32_t length = static_cast(array->length()->Number()); + switch (array->map()->elements_kind()) { + case FAST_HOLEY_DOUBLE_ELEMENTS: + case FAST_DOUBLE_ELEMENTS: { + // Empty array is FixedArray but not FixedDoubleArray. + if (length == 0) break; + FixedDoubleArray* elements = + FixedDoubleArray::cast(array->elements()); + for (uint32_t i = 0; i < length; i++) { + if (elements->is_the_hole(i)) { + // TODO(jkummerow/verwaest): We could be a bit more clever + // here: Check if there are no elements/getters on the + // prototype chain, and if so, allow creation of a holey + // result array. + // Same thing below (holey smi case). + failure = true; + break; + } + double double_value = elements->get_scalar(i); + double_storage->set(j, double_value); + j++; + } + break; + } + case FAST_HOLEY_SMI_ELEMENTS: + case FAST_SMI_ELEMENTS: { + FixedArray* elements(FixedArray::cast(array->elements())); + for (uint32_t i = 0; i < length; i++) { + Object* element = elements->get(i); + if (element->IsTheHole()) { + failure = true; + break; + } + int32_t int_value = Smi::cast(element)->value(); + double_storage->set(j, int_value); + j++; + } + break; + } + case FAST_HOLEY_ELEMENTS: + case FAST_ELEMENTS: + case DICTIONARY_ELEMENTS: + DCHECK_EQ(0u, length); + break; + default: + UNREACHABLE(); + } + } + if (failure) break; + } + } + if (!failure) { + Handle array = isolate->factory()->NewJSArray(0); + Smi* length = Smi::FromInt(j); + Handle map; + map = JSObject::GetElementsTransitionMap(array, kind); + array->set_map(*map); + array->set_length(length); + array->set_elements(*storage); + return *array; + } + // In case of failure, fall through. + } + + Handle storage; + if (fast_case) { + // The backing storage array must have non-existing elements to preserve + // holes across concat operations. + storage = + isolate->factory()->NewFixedArrayWithHoles(estimate_result_length); + } else { + // TODO(126): move 25% pre-allocation logic into Dictionary::Allocate + uint32_t at_least_space_for = + estimate_nof_elements + (estimate_nof_elements >> 2); + storage = Handle::cast( + SeededNumberDictionary::New(isolate, at_least_space_for)); + } + + ArrayConcatVisitor visitor(isolate, storage, fast_case); + + for (int i = 0; i < argument_count; i++) { + Handle obj((*args)[i], isolate); + bool spreadable = IsConcatSpreadable(isolate, obj); + if (isolate->has_pending_exception()) return isolate->heap()->exception(); + if (spreadable) { + Handle object = Handle::cast(obj); + if (!IterateElements(isolate, object, &visitor)) { + return isolate->heap()->exception(); + } + } else { + visitor.visit(0, obj); + visitor.increase_index_offset(1); + } + } + + if (visitor.exceeds_array_limit()) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewRangeError(MessageTemplate::kInvalidArrayLength)); + } + return *visitor.ToArray(); +} + + +MaybeHandle Fast_ArrayConcat(Isolate* isolate, Arguments* args) { + if (!isolate->IsFastArrayConstructorPrototypeChainIntact()) { + return MaybeHandle(); + } + int n_arguments = args->length(); int result_len = 0; - ElementsKind elements_kind = GetInitialFastElementsKind(); - bool has_double = false; { DisallowHeapAllocation no_gc; - Context* native_context = isolate->context()->native_context(); - Object* array_proto = native_context->array_function()->prototype(); - PrototypeIterator iter(isolate, array_proto, - PrototypeIterator::START_AT_RECEIVER); - if (!PrototypeHasNoElements(&iter)) { - AllowHeapAllocation allow_allocation; - return CallJsIntrinsic(isolate, isolate->array_concat(), args); - } - + Object* array_proto = isolate->array_function()->prototype(); // Iterate through all the arguments performing checks // and calculating total length. - bool is_holey = false; for (int i = 0; i < n_arguments; i++) { - Object* arg = args[i]; + Object* arg = (*args)[i]; + if (!arg->IsJSArray()) return MaybeHandle(); + Handle array(JSArray::cast(arg), isolate); + if (!array->HasFastElements()) return MaybeHandle(); PrototypeIterator iter(isolate, arg); - if (!arg->IsJSArray() || !JSArray::cast(arg)->HasFastElements() || - iter.GetCurrent() != array_proto) { - AllowHeapAllocation allow_allocation; - return CallJsIntrinsic(isolate, isolate->array_concat(), args); + if (iter.GetCurrent() != array_proto) return MaybeHandle(); + if (HasConcatSpreadableModifier(isolate, array)) { + return MaybeHandle(); } - int len = Smi::cast(JSArray::cast(arg)->length())->value(); + int len = Smi::cast(array->length())->value(); // We shouldn't overflow when adding another len. const int kHalfOfMaxInt = 1 << (kBitsPerInt - 2); @@ -641,48 +1415,38 @@ BUILTIN(ArrayConcat) { USE(kHalfOfMaxInt); result_len += len; DCHECK(result_len >= 0); - - if (result_len > FixedDoubleArray::kMaxLength) { - AllowHeapAllocation allow_allocation; - return CallJsIntrinsic(isolate, isolate->array_concat(), args); + // Throw an Error if we overflow the FixedArray limits + if (FixedArray::kMaxLength < result_len) { + THROW_NEW_ERROR(isolate, + NewRangeError(MessageTemplate::kInvalidArrayLength), + JSArray); } - - ElementsKind arg_kind = JSArray::cast(arg)->map()->elements_kind(); - has_double = has_double || IsFastDoubleElementsKind(arg_kind); - is_holey = is_holey || IsFastHoleyElementsKind(arg_kind); - elements_kind = GetMoreGeneralElementsKind(elements_kind, arg_kind); - } - if (is_holey) elements_kind = GetHoleyElementsKind(elements_kind); - } - - // If a double array is concatted into a fast elements array, the fast - // elements array needs to be initialized to contain proper holes, since - // boxing doubles may cause incremental marking. - ArrayStorageAllocationMode mode = - has_double && IsFastObjectElementsKind(elements_kind) - ? INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE : DONT_INITIALIZE_ARRAY_ELEMENTS; - Handle result_array = isolate->factory()->NewJSArray( - elements_kind, result_len, result_len, Strength::WEAK, mode); - if (result_len == 0) return *result_array; - - int j = 0; - Handle storage(result_array->elements(), isolate); - ElementsAccessor* accessor = ElementsAccessor::ForKind(elements_kind); - for (int i = 0; i < n_arguments; i++) { - // It is crucial to keep |array| in a raw pointer form to avoid performance - // degradation. - JSArray* array = JSArray::cast(args[i]); - int len = Smi::cast(array->length())->value(); - if (len > 0) { - ElementsKind from_kind = array->GetElementsKind(); - accessor->CopyElements(array, 0, from_kind, storage, j, len); - j += len; } } + return ElementsAccessor::Concat(isolate, args, n_arguments); +} - DCHECK(j == result_len); +} // namespace - return *result_array; +BUILTIN(ArrayConcat) { + HandleScope scope(isolate); + + Handle receiver; + if (!Object::ToObject(isolate, handle(args[0], isolate)) + .ToHandle(&receiver)) { + THROW_NEW_ERROR_RETURN_FAILURE( + isolate, NewTypeError(MessageTemplate::kCalledOnNullOrUndefined, + isolate->factory()->NewStringFromAsciiChecked( + "Array.prototype.concat"))); + } + args[0] = *receiver; + + Handle result_array; + if (Fast_ArrayConcat(isolate, &args).ToHandle(&result_array)) { + return *result_array; + } + if (isolate->has_pending_exception()) return isolate->heap()->exception(); + return Slow_ArrayConcat(&args, isolate); } diff --git a/src/elements.cc b/src/elements.cc index d56067babb..32e6605ca2 100644 --- a/src/elements.cc +++ b/src/elements.cc @@ -2350,6 +2350,69 @@ void ElementsAccessor::TearDown() { } +Handle ElementsAccessor::Concat(Isolate* isolate, Arguments* args, + uint32_t concat_size) { + int result_len = 0; + ElementsKind elements_kind = GetInitialFastElementsKind(); + bool has_double = false; + { + DisallowHeapAllocation no_gc; + // Iterate through all the arguments performing checks + // and calculating total length. + bool is_holey = false; + for (uint32_t i = 0; i < concat_size; i++) { + Object* arg = (*args)[i]; + int len = Smi::cast(JSArray::cast(arg)->length())->value(); + + // We shouldn't overflow when adding another len. + const int kHalfOfMaxInt = 1 << (kBitsPerInt - 2); + STATIC_ASSERT(FixedArray::kMaxLength < kHalfOfMaxInt); + USE(kHalfOfMaxInt); + result_len += len; + DCHECK(0 <= result_len); + DCHECK(result_len <= FixedDoubleArray::kMaxLength); + + ElementsKind arg_kind = JSArray::cast(arg)->map()->elements_kind(); + has_double = has_double || IsFastDoubleElementsKind(arg_kind); + is_holey = is_holey || IsFastHoleyElementsKind(arg_kind); + if (IsMoreGeneralElementsKindTransition(elements_kind, arg_kind)) { + elements_kind = arg_kind; + } + } + if (is_holey) { + elements_kind = GetHoleyElementsKind(elements_kind); + } + } + + // If a double array is concatted into a fast elements array, the fast + // elements array needs to be initialized to contain proper holes, since + // boxing doubles may cause incremental marking. + ArrayStorageAllocationMode mode = + has_double && IsFastObjectElementsKind(elements_kind) + ? INITIALIZE_ARRAY_ELEMENTS_WITH_HOLE + : DONT_INITIALIZE_ARRAY_ELEMENTS; + Handle result_array = isolate->factory()->NewJSArray( + elements_kind, result_len, result_len, Strength::WEAK, mode); + if (result_len == 0) return result_array; + int j = 0; + Handle storage(result_array->elements(), isolate); + ElementsAccessor* accessor = ElementsAccessor::ForKind(elements_kind); + for (uint32_t i = 0; i < concat_size; i++) { + // It is crucial to keep |array| in a raw pointer form to avoid + // performance degradation. + JSArray* array = JSArray::cast((*args)[i]); + int len = Smi::cast(array->length())->value(); + if (len > 0) { + ElementsKind from_kind = array->GetElementsKind(); + accessor->CopyElements(array, 0, from_kind, storage, j, len); + j += len; + } + } + + DCHECK(j == result_len); + return result_array; +} + ElementsAccessor** ElementsAccessor::elements_accessors_ = NULL; } // namespace internal } // namespace v8 diff --git a/src/elements.h b/src/elements.h index e7a189008f..bb8b439d72 100644 --- a/src/elements.h +++ b/src/elements.h @@ -127,8 +127,9 @@ class ElementsAccessor { Handle value, PropertyAttributes attributes, uint32_t new_capacity) = 0; - // TODO(cbruni): Consider passing Arguments* instead of Object** depending on - // the requirements of future callers. + static Handle Concat(Isolate* isolate, Arguments* args, + uint32_t concat_size); + virtual uint32_t Push(Handle receiver, Handle backing_store, Arguments* args, uint32_t push_size) = 0; diff --git a/src/runtime/runtime-array.cc b/src/runtime/runtime-array.cc index 49eb1ad702..4b41bd876e 100644 --- a/src/runtime/runtime-array.cc +++ b/src/runtime/runtime-array.cc @@ -52,7 +52,6 @@ RUNTIME_FUNCTION(Runtime_SpecialArrayFunctions) { InstallBuiltin(isolate, holder, "unshift", Builtins::kArrayUnshift); InstallBuiltin(isolate, holder, "slice", Builtins::kArraySlice); InstallBuiltin(isolate, holder, "splice", Builtins::kArraySplice); - InstallBuiltin(isolate, holder, "concat", Builtins::kArrayConcat); return *holder; } @@ -111,779 +110,6 @@ RUNTIME_FUNCTION(Runtime_PushIfAbsent) { } -/** - * A simple visitor visits every element of Array's. - * The backend storage can be a fixed array for fast elements case, - * or a dictionary for sparse array. Since Dictionary is a subtype - * of FixedArray, the class can be used by both fast and slow cases. - * The second parameter of the constructor, fast_elements, specifies - * whether the storage is a FixedArray or Dictionary. - * - * An index limit is used to deal with the situation that a result array - * length overflows 32-bit non-negative integer. - */ -class ArrayConcatVisitor { - public: - ArrayConcatVisitor(Isolate* isolate, Handle storage, - bool fast_elements) - : isolate_(isolate), - storage_(Handle::cast( - isolate->global_handles()->Create(*storage))), - index_offset_(0u), - bit_field_(FastElementsField::encode(fast_elements) | - ExceedsLimitField::encode(false)) {} - - ~ArrayConcatVisitor() { clear_storage(); } - - void visit(uint32_t i, Handle elm) { - if (i >= JSObject::kMaxElementCount - index_offset_) { - set_exceeds_array_limit(true); - return; - } - uint32_t index = index_offset_ + i; - - if (fast_elements()) { - if (index < static_cast(storage_->length())) { - storage_->set(index, *elm); - return; - } - // Our initial estimate of length was foiled, possibly by - // getters on the arrays increasing the length of later arrays - // during iteration. - // This shouldn't happen in anything but pathological cases. - SetDictionaryMode(); - // Fall-through to dictionary mode. - } - DCHECK(!fast_elements()); - Handle dict( - SeededNumberDictionary::cast(*storage_)); - // The object holding this backing store has just been allocated, so - // it cannot yet be used as a prototype. - Handle result = - SeededNumberDictionary::AtNumberPut(dict, index, elm, false); - if (!result.is_identical_to(dict)) { - // Dictionary needed to grow. - clear_storage(); - set_storage(*result); - } - } - - void increase_index_offset(uint32_t delta) { - if (JSObject::kMaxElementCount - index_offset_ < delta) { - index_offset_ = JSObject::kMaxElementCount; - } else { - index_offset_ += delta; - } - // If the initial length estimate was off (see special case in visit()), - // but the array blowing the limit didn't contain elements beyond the - // provided-for index range, go to dictionary mode now. - if (fast_elements() && - index_offset_ > - static_cast(FixedArrayBase::cast(*storage_)->length())) { - SetDictionaryMode(); - } - } - - bool exceeds_array_limit() const { - return ExceedsLimitField::decode(bit_field_); - } - - Handle ToArray() { - Handle array = isolate_->factory()->NewJSArray(0); - Handle length = - isolate_->factory()->NewNumber(static_cast(index_offset_)); - Handle map = JSObject::GetElementsTransitionMap( - array, fast_elements() ? FAST_HOLEY_ELEMENTS : DICTIONARY_ELEMENTS); - array->set_map(*map); - array->set_length(*length); - array->set_elements(*storage_); - return array; - } - - private: - // Convert storage to dictionary mode. - void SetDictionaryMode() { - DCHECK(fast_elements()); - Handle current_storage(*storage_); - Handle slow_storage( - SeededNumberDictionary::New(isolate_, current_storage->length())); - uint32_t current_length = static_cast(current_storage->length()); - for (uint32_t i = 0; i < current_length; i++) { - HandleScope loop_scope(isolate_); - Handle element(current_storage->get(i), isolate_); - if (!element->IsTheHole()) { - // The object holding this backing store has just been allocated, so - // it cannot yet be used as a prototype. - Handle new_storage = - SeededNumberDictionary::AtNumberPut(slow_storage, i, element, - false); - if (!new_storage.is_identical_to(slow_storage)) { - slow_storage = loop_scope.CloseAndEscape(new_storage); - } - } - } - clear_storage(); - set_storage(*slow_storage); - set_fast_elements(false); - } - - inline void clear_storage() { - GlobalHandles::Destroy(Handle::cast(storage_).location()); - } - - inline void set_storage(FixedArray* storage) { - storage_ = - Handle::cast(isolate_->global_handles()->Create(storage)); - } - - class FastElementsField : public BitField {}; - class ExceedsLimitField : public BitField {}; - - bool fast_elements() const { return FastElementsField::decode(bit_field_); } - void set_fast_elements(bool fast) { - bit_field_ = FastElementsField::update(bit_field_, fast); - } - void set_exceeds_array_limit(bool exceeds) { - bit_field_ = ExceedsLimitField::update(bit_field_, exceeds); - } - - Isolate* isolate_; - Handle storage_; // Always a global handle. - // Index after last seen index. Always less than or equal to - // JSObject::kMaxElementCount. - uint32_t index_offset_; - uint32_t bit_field_; -}; - - -static uint32_t EstimateElementCount(Handle array) { - uint32_t length = static_cast(array->length()->Number()); - int element_count = 0; - switch (array->GetElementsKind()) { - case FAST_SMI_ELEMENTS: - case FAST_HOLEY_SMI_ELEMENTS: - case FAST_ELEMENTS: - case FAST_HOLEY_ELEMENTS: { - // Fast elements can't have lengths that are not representable by - // a 32-bit signed integer. - DCHECK(static_cast(FixedArray::kMaxLength) >= 0); - int fast_length = static_cast(length); - Handle elements(FixedArray::cast(array->elements())); - for (int i = 0; i < fast_length; i++) { - if (!elements->get(i)->IsTheHole()) element_count++; - } - break; - } - case FAST_DOUBLE_ELEMENTS: - case FAST_HOLEY_DOUBLE_ELEMENTS: { - // Fast elements can't have lengths that are not representable by - // a 32-bit signed integer. - DCHECK(static_cast(FixedDoubleArray::kMaxLength) >= 0); - int fast_length = static_cast(length); - if (array->elements()->IsFixedArray()) { - DCHECK(FixedArray::cast(array->elements())->length() == 0); - break; - } - Handle elements( - FixedDoubleArray::cast(array->elements())); - for (int i = 0; i < fast_length; i++) { - if (!elements->is_the_hole(i)) element_count++; - } - break; - } - case DICTIONARY_ELEMENTS: { - Handle dictionary( - SeededNumberDictionary::cast(array->elements())); - int capacity = dictionary->Capacity(); - for (int i = 0; i < capacity; i++) { - Handle key(dictionary->KeyAt(i), array->GetIsolate()); - if (dictionary->IsKey(*key)) { - element_count++; - } - } - break; - } - case FAST_SLOPPY_ARGUMENTS_ELEMENTS: - case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: -#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \ - case TYPE##_ELEMENTS: - - TYPED_ARRAYS(TYPED_ARRAY_CASE) -#undef TYPED_ARRAY_CASE - // External arrays are always dense. - return length; - } - // As an estimate, we assume that the prototype doesn't contain any - // inherited elements. - return element_count; -} - - -template -static void IterateTypedArrayElements(Isolate* isolate, - Handle receiver, - bool elements_are_ints, - bool elements_are_guaranteed_smis, - ArrayConcatVisitor* visitor) { - Handle array( - ExternalArrayClass::cast(receiver->elements())); - uint32_t len = static_cast(array->length()); - - DCHECK(visitor != NULL); - if (elements_are_ints) { - if (elements_are_guaranteed_smis) { - for (uint32_t j = 0; j < len; j++) { - HandleScope loop_scope(isolate); - Handle e(Smi::FromInt(static_cast(array->get_scalar(j))), - isolate); - visitor->visit(j, e); - } - } else { - for (uint32_t j = 0; j < len; j++) { - HandleScope loop_scope(isolate); - int64_t val = static_cast(array->get_scalar(j)); - if (Smi::IsValid(static_cast(val))) { - Handle e(Smi::FromInt(static_cast(val)), isolate); - visitor->visit(j, e); - } else { - Handle e = - isolate->factory()->NewNumber(static_cast(val)); - visitor->visit(j, e); - } - } - } - } else { - for (uint32_t j = 0; j < len; j++) { - HandleScope loop_scope(isolate); - Handle e = isolate->factory()->NewNumber(array->get_scalar(j)); - visitor->visit(j, e); - } - } -} - - -// Used for sorting indices in a List. -static int compareUInt32(const uint32_t* ap, const uint32_t* bp) { - uint32_t a = *ap; - uint32_t b = *bp; - return (a == b) ? 0 : (a < b) ? -1 : 1; -} - - -static void CollectElementIndices(Handle object, uint32_t range, - List* indices) { - Isolate* isolate = object->GetIsolate(); - ElementsKind kind = object->GetElementsKind(); - switch (kind) { - case FAST_SMI_ELEMENTS: - case FAST_ELEMENTS: - case FAST_HOLEY_SMI_ELEMENTS: - case FAST_HOLEY_ELEMENTS: { - Handle elements(FixedArray::cast(object->elements())); - uint32_t length = static_cast(elements->length()); - if (range < length) length = range; - for (uint32_t i = 0; i < length; i++) { - if (!elements->get(i)->IsTheHole()) { - indices->Add(i); - } - } - break; - } - case FAST_HOLEY_DOUBLE_ELEMENTS: - case FAST_DOUBLE_ELEMENTS: { - if (object->elements()->IsFixedArray()) { - DCHECK(object->elements()->length() == 0); - break; - } - Handle elements( - FixedDoubleArray::cast(object->elements())); - uint32_t length = static_cast(elements->length()); - if (range < length) length = range; - for (uint32_t i = 0; i < length; i++) { - if (!elements->is_the_hole(i)) { - indices->Add(i); - } - } - break; - } - case DICTIONARY_ELEMENTS: { - Handle dict( - SeededNumberDictionary::cast(object->elements())); - uint32_t capacity = dict->Capacity(); - for (uint32_t j = 0; j < capacity; j++) { - HandleScope loop_scope(isolate); - Handle k(dict->KeyAt(j), isolate); - if (dict->IsKey(*k)) { - DCHECK(k->IsNumber()); - uint32_t index = static_cast(k->Number()); - if (index < range) { - indices->Add(index); - } - } - } - break; - } -#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \ - case TYPE##_ELEMENTS: \ - - TYPED_ARRAYS(TYPED_ARRAY_CASE) -#undef TYPED_ARRAY_CASE - { - uint32_t length = static_cast( - FixedArrayBase::cast(object->elements())->length()); - if (range <= length) { - length = range; - // We will add all indices, so we might as well clear it first - // and avoid duplicates. - indices->Clear(); - } - for (uint32_t i = 0; i < length; i++) { - indices->Add(i); - } - if (length == range) return; // All indices accounted for already. - break; - } - case FAST_SLOPPY_ARGUMENTS_ELEMENTS: - case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: { - ElementsAccessor* accessor = object->GetElementsAccessor(); - for (uint32_t i = 0; i < range; i++) { - if (accessor->HasElement(object, i)) { - indices->Add(i); - } - } - break; - } - } - - PrototypeIterator iter(isolate, object); - if (!iter.IsAtEnd()) { - // The prototype will usually have no inherited element indices, - // but we have to check. - CollectElementIndices( - Handle::cast(PrototypeIterator::GetCurrent(iter)), range, - indices); - } -} - - -static bool IterateElementsSlow(Isolate* isolate, Handle receiver, - uint32_t length, ArrayConcatVisitor* visitor) { - for (uint32_t i = 0; i < length; ++i) { - HandleScope loop_scope(isolate); - Maybe maybe = JSReceiver::HasElement(receiver, i); - if (!maybe.IsJust()) return false; - if (maybe.FromJust()) { - Handle element_value; - ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, element_value, - Object::GetElement(isolate, receiver, i), - false); - visitor->visit(i, element_value); - } - } - visitor->increase_index_offset(length); - return true; -} - - -/** - * A helper function that visits elements of a JSObject in numerical - * order. - * - * The visitor argument called for each existing element in the array - * with the element index and the element's value. - * Afterwards it increments the base-index of the visitor by the array - * length. - * Returns false if any access threw an exception, otherwise true. - */ -static bool IterateElements(Isolate* isolate, Handle receiver, - ArrayConcatVisitor* visitor) { - uint32_t length = 0; - - if (receiver->IsJSArray()) { - Handle array(Handle::cast(receiver)); - length = static_cast(array->length()->Number()); - } else { - Handle val; - Handle key(isolate->heap()->length_string(), isolate); - ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, val, - Runtime::GetObjectProperty(isolate, receiver, key), false); - // TODO(caitp): Support larger element indexes (up to 2^53-1). - if (!val->ToUint32(&length)) { - ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, val, - Execution::ToLength(isolate, val), false); - val->ToUint32(&length); - } - } - - if (!(receiver->IsJSArray() || receiver->IsJSTypedArray())) { - // For classes which are not known to be safe to access via elements alone, - // use the slow case. - return IterateElementsSlow(isolate, receiver, length, visitor); - } - - switch (receiver->GetElementsKind()) { - case FAST_SMI_ELEMENTS: - case FAST_ELEMENTS: - case FAST_HOLEY_SMI_ELEMENTS: - case FAST_HOLEY_ELEMENTS: { - // Run through the elements FixedArray and use HasElement and GetElement - // to check the prototype for missing elements. - Handle elements(FixedArray::cast(receiver->elements())); - int fast_length = static_cast(length); - DCHECK(fast_length <= elements->length()); - for (int j = 0; j < fast_length; j++) { - HandleScope loop_scope(isolate); - Handle element_value(elements->get(j), isolate); - if (!element_value->IsTheHole()) { - visitor->visit(j, element_value); - } else { - Maybe maybe = JSReceiver::HasElement(receiver, j); - if (!maybe.IsJust()) return false; - if (maybe.FromJust()) { - // Call GetElement on receiver, not its prototype, or getters won't - // have the correct receiver. - ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate, element_value, - Object::GetElement(isolate, receiver, j), false); - visitor->visit(j, element_value); - } - } - } - break; - } - case FAST_HOLEY_DOUBLE_ELEMENTS: - case FAST_DOUBLE_ELEMENTS: { - // Empty array is FixedArray but not FixedDoubleArray. - if (length == 0) break; - // Run through the elements FixedArray and use HasElement and GetElement - // to check the prototype for missing elements. - if (receiver->elements()->IsFixedArray()) { - DCHECK(receiver->elements()->length() == 0); - break; - } - Handle elements( - FixedDoubleArray::cast(receiver->elements())); - int fast_length = static_cast(length); - DCHECK(fast_length <= elements->length()); - for (int j = 0; j < fast_length; j++) { - HandleScope loop_scope(isolate); - if (!elements->is_the_hole(j)) { - double double_value = elements->get_scalar(j); - Handle element_value = - isolate->factory()->NewNumber(double_value); - visitor->visit(j, element_value); - } else { - Maybe maybe = JSReceiver::HasElement(receiver, j); - if (!maybe.IsJust()) return false; - if (maybe.FromJust()) { - // Call GetElement on receiver, not its prototype, or getters won't - // have the correct receiver. - Handle element_value; - ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate, element_value, - Object::GetElement(isolate, receiver, j), false); - visitor->visit(j, element_value); - } - } - } - break; - } - case DICTIONARY_ELEMENTS: { - Handle dict(receiver->element_dictionary()); - List indices(dict->Capacity() / 2); - // Collect all indices in the object and the prototypes less - // than length. This might introduce duplicates in the indices list. - CollectElementIndices(receiver, length, &indices); - indices.Sort(&compareUInt32); - int j = 0; - int n = indices.length(); - while (j < n) { - HandleScope loop_scope(isolate); - uint32_t index = indices[j]; - Handle element; - ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate, element, Object::GetElement(isolate, receiver, index), - false); - visitor->visit(index, element); - // Skip to next different index (i.e., omit duplicates). - do { - j++; - } while (j < n && indices[j] == index); - } - break; - } - case UINT8_CLAMPED_ELEMENTS: { - Handle pixels( - FixedUint8ClampedArray::cast(receiver->elements())); - for (uint32_t j = 0; j < length; j++) { - Handle e(Smi::FromInt(pixels->get_scalar(j)), isolate); - visitor->visit(j, e); - } - break; - } - case INT8_ELEMENTS: { - IterateTypedArrayElements( - isolate, receiver, true, true, visitor); - break; - } - case UINT8_ELEMENTS: { - IterateTypedArrayElements( - isolate, receiver, true, true, visitor); - break; - } - case INT16_ELEMENTS: { - IterateTypedArrayElements( - isolate, receiver, true, true, visitor); - break; - } - case UINT16_ELEMENTS: { - IterateTypedArrayElements( - isolate, receiver, true, true, visitor); - break; - } - case INT32_ELEMENTS: { - IterateTypedArrayElements( - isolate, receiver, true, false, visitor); - break; - } - case UINT32_ELEMENTS: { - IterateTypedArrayElements( - isolate, receiver, true, false, visitor); - break; - } - case FLOAT32_ELEMENTS: { - IterateTypedArrayElements( - isolate, receiver, false, false, visitor); - break; - } - case FLOAT64_ELEMENTS: { - IterateTypedArrayElements( - isolate, receiver, false, false, visitor); - break; - } - case FAST_SLOPPY_ARGUMENTS_ELEMENTS: - case SLOW_SLOPPY_ARGUMENTS_ELEMENTS: { - for (uint32_t index = 0; index < length; index++) { - HandleScope loop_scope(isolate); - Handle element; - ASSIGN_RETURN_ON_EXCEPTION_VALUE( - isolate, element, Object::GetElement(isolate, receiver, index), - false); - visitor->visit(index, element); - } - break; - } - } - visitor->increase_index_offset(length); - return true; -} - - -static bool IsConcatSpreadable(Isolate* isolate, Handle obj) { - HandleScope handle_scope(isolate); - if (!obj->IsSpecObject()) return false; - if (FLAG_harmony_concat_spreadable) { - Handle key(isolate->factory()->is_concat_spreadable_symbol()); - Handle value; - MaybeHandle maybeValue = - i::Runtime::GetObjectProperty(isolate, obj, key); - if (maybeValue.ToHandle(&value)) { - if (!value->IsUndefined()) { - return value->BooleanValue(); - } - } - } - return obj->IsJSArray(); -} - - -/** - * Array::concat implementation. - * See ECMAScript 262, 15.4.4.4. - * TODO(581): Fix non-compliance for very large concatenations and update to - * following the ECMAScript 5 specification. - */ -RUNTIME_FUNCTION(Runtime_ArrayConcat) { - HandleScope handle_scope(isolate); - DCHECK(args.length() == 1); - - CONVERT_ARG_HANDLE_CHECKED(JSArray, arguments, 0); - int argument_count = static_cast(arguments->length()->Number()); - RUNTIME_ASSERT(arguments->HasFastObjectElements()); - Handle elements(FixedArray::cast(arguments->elements())); - - // Pass 1: estimate the length and number of elements of the result. - // The actual length can be larger if any of the arguments have getters - // that mutate other arguments (but will otherwise be precise). - // The number of elements is precise if there are no inherited elements. - - ElementsKind kind = FAST_SMI_ELEMENTS; - - uint32_t estimate_result_length = 0; - uint32_t estimate_nof_elements = 0; - for (int i = 0; i < argument_count; i++) { - HandleScope loop_scope(isolate); - Handle obj(elements->get(i), isolate); - uint32_t length_estimate; - uint32_t element_estimate; - if (obj->IsJSArray()) { - Handle array(Handle::cast(obj)); - length_estimate = static_cast(array->length()->Number()); - if (length_estimate != 0) { - kind = GetMoreGeneralElementsKind( - kind, GetPackedElementsKind(array->map()->elements_kind())); - } - element_estimate = EstimateElementCount(array); - } else { - if (obj->IsHeapObject()) { - if (obj->IsNumber()) { - kind = GetMoreGeneralElementsKind(kind, FAST_DOUBLE_ELEMENTS); - } else { - kind = GetMoreGeneralElementsKind(kind, FAST_ELEMENTS); - } - } - length_estimate = 1; - element_estimate = 1; - } - // Avoid overflows by capping at kMaxElementCount. - if (JSObject::kMaxElementCount - estimate_result_length < length_estimate) { - estimate_result_length = JSObject::kMaxElementCount; - } else { - estimate_result_length += length_estimate; - } - if (JSObject::kMaxElementCount - estimate_nof_elements < element_estimate) { - estimate_nof_elements = JSObject::kMaxElementCount; - } else { - estimate_nof_elements += element_estimate; - } - } - - // If estimated number of elements is more than half of length, a - // fixed array (fast case) is more time and space-efficient than a - // dictionary. - bool fast_case = (estimate_nof_elements * 2) >= estimate_result_length; - - if (fast_case && kind == FAST_DOUBLE_ELEMENTS) { - Handle storage = - isolate->factory()->NewFixedDoubleArray(estimate_result_length); - int j = 0; - bool failure = false; - if (estimate_result_length > 0) { - Handle double_storage = - Handle::cast(storage); - for (int i = 0; i < argument_count; i++) { - Handle obj(elements->get(i), isolate); - if (obj->IsSmi()) { - double_storage->set(j, Smi::cast(*obj)->value()); - j++; - } else if (obj->IsNumber()) { - double_storage->set(j, obj->Number()); - j++; - } else { - JSArray* array = JSArray::cast(*obj); - uint32_t length = static_cast(array->length()->Number()); - switch (array->map()->elements_kind()) { - case FAST_HOLEY_DOUBLE_ELEMENTS: - case FAST_DOUBLE_ELEMENTS: { - // Empty array is FixedArray but not FixedDoubleArray. - if (length == 0) break; - FixedDoubleArray* elements = - FixedDoubleArray::cast(array->elements()); - for (uint32_t i = 0; i < length; i++) { - if (elements->is_the_hole(i)) { - // TODO(jkummerow/verwaest): We could be a bit more clever - // here: Check if there are no elements/getters on the - // prototype chain, and if so, allow creation of a holey - // result array. - // Same thing below (holey smi case). - failure = true; - break; - } - double double_value = elements->get_scalar(i); - double_storage->set(j, double_value); - j++; - } - break; - } - case FAST_HOLEY_SMI_ELEMENTS: - case FAST_SMI_ELEMENTS: { - FixedArray* elements(FixedArray::cast(array->elements())); - for (uint32_t i = 0; i < length; i++) { - Object* element = elements->get(i); - if (element->IsTheHole()) { - failure = true; - break; - } - int32_t int_value = Smi::cast(element)->value(); - double_storage->set(j, int_value); - j++; - } - break; - } - case FAST_HOLEY_ELEMENTS: - case FAST_ELEMENTS: - case DICTIONARY_ELEMENTS: - DCHECK_EQ(0u, length); - break; - default: - UNREACHABLE(); - } - } - if (failure) break; - } - } - if (!failure) { - Handle array = isolate->factory()->NewJSArray(0); - Smi* length = Smi::FromInt(j); - Handle map; - map = JSObject::GetElementsTransitionMap(array, kind); - array->set_map(*map); - array->set_length(length); - array->set_elements(*storage); - return *array; - } - // In case of failure, fall through. - } - - Handle storage; - if (fast_case) { - // The backing storage array must have non-existing elements to preserve - // holes across concat operations. - storage = - isolate->factory()->NewFixedArrayWithHoles(estimate_result_length); - } else { - // TODO(126): move 25% pre-allocation logic into Dictionary::Allocate - uint32_t at_least_space_for = - estimate_nof_elements + (estimate_nof_elements >> 2); - storage = Handle::cast( - SeededNumberDictionary::New(isolate, at_least_space_for)); - } - - ArrayConcatVisitor visitor(isolate, storage, fast_case); - - for (int i = 0; i < argument_count; i++) { - Handle obj(elements->get(i), isolate); - bool spreadable = IsConcatSpreadable(isolate, obj); - if (isolate->has_pending_exception()) return isolate->heap()->exception(); - if (spreadable) { - Handle object = Handle::cast(obj); - if (!IterateElements(isolate, object, &visitor)) { - return isolate->heap()->exception(); - } - } else { - visitor.visit(0, obj); - visitor.increase_index_offset(1); - } - } - - if (visitor.exceeds_array_limit()) { - THROW_NEW_ERROR_RETURN_FAILURE( - isolate, NewRangeError(MessageTemplate::kInvalidArrayLength)); - } - return *visitor.ToArray(); -} - - // Moves all own elements of an object, that are below a limit, to positions // starting at zero. All undefined values are placed after non-undefined values, // and are followed by non-existing element. Does not change the length diff --git a/src/runtime/runtime.h b/src/runtime/runtime.h index d1f8783c39..11366f598e 100644 --- a/src/runtime/runtime.h +++ b/src/runtime/runtime.h @@ -35,7 +35,6 @@ namespace internal { F(SpecialArrayFunctions, 0, 1) \ F(TransitionElementsKind, 2, 1) \ F(PushIfAbsent, 2, 1) \ - F(ArrayConcat, 1, 1) \ F(RemoveArrayHoles, 2, 1) \ F(MoveArrayContents, 2, 1) \ F(EstimateNumberOfElements, 1, 1) \ diff --git a/test/mjsunit/array-natives-elements.js b/test/mjsunit/array-natives-elements.js index cced407aa7..dabf57622c 100644 --- a/test/mjsunit/array-natives-elements.js +++ b/test/mjsunit/array-natives-elements.js @@ -68,7 +68,7 @@ function array_natives_test() { // Concat var a1; a1 = [1,2,3].concat([]); - assertTrue(%HasFastSmiElements(a1)); + //assertTrue(%HasFastSmiElements(a1)); assertEquals([1,2,3], a1); a1 = [1,2,3].concat([4,5,6]); assertTrue(%HasFastSmiElements(a1)); diff --git a/test/test262-es6/test262-es6.status b/test/test262-es6/test262-es6.status index 09c941543c..504580e0c7 100644 --- a/test/test262-es6/test262-es6.status +++ b/test/test262-es6/test262-es6.status @@ -490,9 +490,6 @@ 'language/expressions/object/method-definition/generator-name-prop-symbol': [FAIL], 'language/expressions/object/method-definition/name-name-prop-symbol': [FAIL], - # https://code.google.com/p/v8/issues/detail?id=4317 - 'built-ins/Array/prototype/concat/is-concat-spreadable-val-falsey': [FAIL], - # https://code.google.com/p/v8/issues/detail?id=2952 'built-ins/RegExp/prototype/exec/u-lastindex-adv': [FAIL], 'built-ins/RegExp/prototype/exec/u-captured-value': [FAIL],