1 // Copyright 2012 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 (function(global, utils) {
9 %CheckIsBootstrapping();
11 // -------------------------------------------------------------------
15 var GlobalArray = global.Array;
16 var InternalArray = utils.InternalArray;
17 var InternalPackedArray = utils.InternalPackedArray;
19 var ObjectHasOwnProperty;
25 var unscopablesSymbol = utils.ImportNow("unscopables_symbol");
27 utils.Import(function(from) {
29 MathMin = from.MathMin;
30 ObjectHasOwnProperty = from.ObjectHasOwnProperty;
31 ObjectIsFrozen = from.ObjectIsFrozen;
32 ObjectIsSealed = from.ObjectIsSealed;
33 ObjectToString = from.ObjectToString;
34 ToNumber = from.ToNumber;
35 ToString = from.ToString;
38 // -------------------------------------------------------------------
40 // Global list of arrays visited during toString, toLocaleString and
42 var visited_arrays = new InternalArray();
45 // Gets a sorted array of array keys. Useful for operations on sparse
46 // arrays. Dupes have not been removed.
47 function GetSortedArrayKeys(array, indices) {
48 var keys = new InternalArray();
49 if (IS_NUMBER(indices)) {
52 for (var i = 0; i < limit; ++i) {
54 if (!IS_UNDEFINED(e) || i in array) {
59 var length = indices.length;
60 for (var k = 0; k < length; ++k) {
62 if (!IS_UNDEFINED(key)) {
64 if (!IS_UNDEFINED(e) || key in array) {
69 keys.sort(function(a, b) { return a - b; });
75 function SparseJoinWithSeparatorJS(array, len, convert, separator) {
76 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
78 var elements = new InternalArray(keys.length * 2);
80 for (var i = 0; i < keys.length; i++) {
82 if (key != previousKey) { // keys may contain duplicates.
84 if (!IS_STRING(e)) e = convert(e);
85 elements[i * 2] = key;
86 elements[i * 2 + 1] = e;
90 return %SparseJoinWithSeparator(elements, len, separator);
94 // Optimized for sparse arrays if separator is ''.
95 function SparseJoin(array, len, convert) {
96 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
98 var keys_length = keys.length;
100 var elements = new InternalArray(keys_length);
101 var elements_length = 0;
103 for (var i = 0; i < keys_length; i++) {
105 if (key != last_key) {
107 if (!IS_STRING(e)) e = convert(e);
108 elements[elements_length++] = e;
112 return %StringBuilderConcat(elements, elements_length, '');
116 function UseSparseVariant(array, length, is_array, touched) {
117 // Only use the sparse variant on arrays that are likely to be sparse and the
118 // number of elements touched in the operation is relatively small compared to
119 // the overall size of the array.
120 if (!is_array || length < 1000 || %IsObserved(array) ||
121 %HasComplexElements(array)) {
124 if (!%_IsSmi(length)) {
127 var elements_threshold = length >> 2; // No more than 75% holes
128 var estimated_elements = %EstimateNumberOfElements(array);
129 return (estimated_elements < elements_threshold) &&
130 (touched > estimated_elements * 4);
134 function Join(array, length, separator, convert) {
135 if (length == 0) return '';
137 var is_array = IS_ARRAY(array);
140 // If the array is cyclic, return the empty string for already
142 if (!%PushIfAbsent(visited_arrays, array)) return '';
145 // Attempt to convert the elements.
147 if (UseSparseVariant(array, length, is_array, length)) {
148 %NormalizeElements(array);
149 if (separator.length == 0) {
150 return SparseJoin(array, length, convert);
152 return SparseJoinWithSeparatorJS(array, length, convert, separator);
156 // Fast case for one-element arrays.
159 if (IS_STRING(e)) return e;
163 // Construct an array for the elements.
164 var elements = new InternalArray(length);
166 // We pull the empty separator check outside the loop for speed!
167 if (separator.length == 0) {
168 var elements_length = 0;
169 for (var i = 0; i < length; i++) {
171 if (!IS_STRING(e)) e = convert(e);
172 elements[elements_length++] = e;
174 elements.length = elements_length;
175 var result = %_FastOneByteArrayJoin(elements, '');
176 if (!IS_UNDEFINED(result)) return result;
177 return %StringBuilderConcat(elements, elements_length, '');
179 // Non-empty separator case.
180 // If the first element is a number then use the heuristic that the
181 // remaining elements are also likely to be numbers.
182 if (!IS_NUMBER(array[0])) {
183 for (var i = 0; i < length; i++) {
185 if (!IS_STRING(e)) e = convert(e);
189 for (var i = 0; i < length; i++) {
192 e = %_NumberToString(e);
193 } else if (!IS_STRING(e)) {
199 var result = %_FastOneByteArrayJoin(elements, separator);
200 if (!IS_UNDEFINED(result)) return result;
202 return %StringBuilderJoin(elements, length, separator);
204 // Make sure to remove the last element of the visited array no
205 // matter what happens.
206 if (is_array) visited_arrays.length = visited_arrays.length - 1;
211 function ConvertToString(x) {
212 // Assumes x is a non-string.
213 if (IS_NUMBER(x)) return %_NumberToString(x);
214 if (IS_BOOLEAN(x)) return x ? 'true' : 'false';
215 return (IS_NULL_OR_UNDEFINED(x)) ? '' : ToString($defaultString(x));
219 function ConvertToLocaleString(e) {
220 if (IS_NULL_OR_UNDEFINED(e)) {
223 // According to ES5, section 15.4.4.3, the toLocaleString conversion
224 // must throw a TypeError if ToObject(e).toLocaleString isn't
226 var e_obj = TO_OBJECT(e);
227 return ToString(e_obj.toLocaleString());
232 // This function implements the optimized splice implementation that can use
233 // special array operations to handle sparse arrays in a sensible fashion.
234 function SparseSlice(array, start_i, del_count, len, deleted_elements) {
235 // Move deleted elements to a new array (the return value from splice).
236 var indices = %GetArrayKeys(array, start_i + del_count);
237 if (IS_NUMBER(indices)) {
239 for (var i = start_i; i < limit; ++i) {
240 var current = array[i];
241 if (!IS_UNDEFINED(current) || i in array) {
242 %AddElement(deleted_elements, i - start_i, current);
246 var length = indices.length;
247 for (var k = 0; k < length; ++k) {
248 var key = indices[k];
249 if (!IS_UNDEFINED(key)) {
250 if (key >= start_i) {
251 var current = array[key];
252 if (!IS_UNDEFINED(current) || key in array) {
253 %AddElement(deleted_elements, key - start_i, current);
262 // This function implements the optimized splice implementation that can use
263 // special array operations to handle sparse arrays in a sensible fashion.
264 function SparseMove(array, start_i, del_count, len, num_additional_args) {
265 // Bail out if no moving is necessary.
266 if (num_additional_args === del_count) return;
267 // Move data to new array.
268 var new_array = new InternalArray(
269 // Clamp array length to 2^32-1 to avoid early RangeError.
270 MathMin(len - del_count + num_additional_args, 0xffffffff));
272 var indices = %GetArrayKeys(array, len);
273 if (IS_NUMBER(indices)) {
275 for (var i = 0; i < start_i && i < limit; ++i) {
276 var current = array[i];
277 if (!IS_UNDEFINED(current) || i in array) {
278 new_array[i] = current;
281 for (var i = start_i + del_count; i < limit; ++i) {
282 var current = array[i];
283 if (!IS_UNDEFINED(current) || i in array) {
284 new_array[i - del_count + num_additional_args] = current;
288 var length = indices.length;
289 for (var k = 0; k < length; ++k) {
290 var key = indices[k];
291 if (!IS_UNDEFINED(key)) {
293 var current = array[key];
294 if (!IS_UNDEFINED(current) || key in array) {
295 new_array[key] = current;
297 } else if (key >= start_i + del_count) {
298 var current = array[key];
299 if (!IS_UNDEFINED(current) || key in array) {
300 var new_key = key - del_count + num_additional_args;
301 new_array[new_key] = current;
302 if (new_key > 0xfffffffe) {
303 big_indices = big_indices || new InternalArray();
304 big_indices.push(new_key);
311 // Move contents of new_array into this array
312 %MoveArrayContents(new_array, array);
313 // Add any moved values that aren't elements anymore.
314 if (!IS_UNDEFINED(big_indices)) {
315 var length = big_indices.length;
316 for (var i = 0; i < length; ++i) {
317 var key = big_indices[i];
318 array[key] = new_array[key];
324 // This is part of the old simple-minded splice. We are using it either
325 // because the receiver is not an array (so we have no choice) or because we
326 // know we are not deleting or moving a lot of elements.
327 function SimpleSlice(array, start_i, del_count, len, deleted_elements) {
328 var is_array = IS_ARRAY(array);
329 for (var i = 0; i < del_count; i++) {
330 var index = start_i + i;
331 if (HAS_INDEX(array, index, is_array)) {
332 var current = array[index];
333 // The spec requires [[DefineOwnProperty]] here, %AddElement is close
334 // enough (in that it ignores the prototype).
335 %AddElement(deleted_elements, i, current);
341 function SimpleMove(array, start_i, del_count, len, num_additional_args) {
342 var is_array = IS_ARRAY(array);
343 if (num_additional_args !== del_count) {
344 // Move the existing elements after the elements to be deleted
345 // to the right position in the resulting array.
346 if (num_additional_args > del_count) {
347 for (var i = len - del_count; i > start_i; i--) {
348 var from_index = i + del_count - 1;
349 var to_index = i + num_additional_args - 1;
350 if (HAS_INDEX(array, from_index, is_array)) {
351 array[to_index] = array[from_index];
353 delete array[to_index];
357 for (var i = start_i; i < len - del_count; i++) {
358 var from_index = i + del_count;
359 var to_index = i + num_additional_args;
360 if (HAS_INDEX(array, from_index, is_array)) {
361 array[to_index] = array[from_index];
363 delete array[to_index];
366 for (var i = len; i > len - del_count + num_additional_args; i--) {
374 // -------------------------------------------------------------------
377 function ArrayToString() {
380 if (IS_ARRAY(this)) {
382 if (func === ArrayJoin) {
383 return Join(this, this.length, ',', ConvertToString);
387 array = TO_OBJECT(this);
390 if (!IS_CALLABLE(func)) {
391 return %_CallFunction(array, ObjectToString);
393 return %_Call(func, array);
397 function InnerArrayToLocaleString(array, length) {
398 var len = TO_LENGTH_OR_UINT32(length);
399 if (len === 0) return "";
400 return Join(array, len, ',', ConvertToLocaleString);
404 function ArrayToLocaleString() {
405 var array = TO_OBJECT(this);
406 var arrayLen = array.length;
407 return InnerArrayToLocaleString(array, arrayLen);
411 function InnerArrayJoin(separator, array, length) {
412 if (IS_UNDEFINED(separator)) {
415 separator = TO_STRING(separator);
418 var result = %_FastOneByteArrayJoin(array, separator);
419 if (!IS_UNDEFINED(result)) return result;
421 // Fast case for one-element arrays.
424 if (IS_NULL_OR_UNDEFINED(e)) return '';
428 return Join(array, length, separator, ConvertToString);
432 function ArrayJoin(separator) {
433 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.join");
435 var array = TO_OBJECT(this);
436 var length = TO_LENGTH_OR_UINT32(array.length);
438 return InnerArrayJoin(separator, array, length);
442 function ObservedArrayPop(n) {
447 $observeBeginPerformSplice(this);
451 $observeEndPerformSplice(this);
452 $observeEnqueueSpliceRecord(this, n, [value], 0);
459 // Removes the last element from the array and returns it. See
460 // ECMA-262, section 15.4.4.6.
461 function ArrayPop() {
462 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.pop");
464 var array = TO_OBJECT(this);
465 var n = TO_LENGTH_OR_UINT32(array.length);
471 if (%IsObserved(array))
472 return ObservedArrayPop.call(array, n);
475 var value = array[n];
476 Delete(array, n, true);
482 function ObservedArrayPush() {
483 var n = TO_LENGTH_OR_UINT32(this.length);
484 var m = %_ArgumentsLength();
487 $observeBeginPerformSplice(this);
488 for (var i = 0; i < m; i++) {
489 this[i+n] = %_Arguments(i);
491 var new_length = n + m;
492 this.length = new_length;
494 $observeEndPerformSplice(this);
495 $observeEnqueueSpliceRecord(this, n, [], m);
502 // Appends the arguments to the end of the array and returns the new
503 // length of the array. See ECMA-262, section 15.4.4.7.
504 function ArrayPush() {
505 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.push");
507 if (%IsObserved(this))
508 return ObservedArrayPush.apply(this, arguments);
510 var array = TO_OBJECT(this);
511 var n = TO_LENGTH_OR_UINT32(array.length);
512 var m = %_ArgumentsLength();
514 for (var i = 0; i < m; i++) {
515 array[i+n] = %_Arguments(i);
518 var new_length = n + m;
519 array.length = new_length;
524 // For implementing reverse() on large, sparse arrays.
525 function SparseReverse(array, len) {
526 var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
527 var high_counter = keys.length - 1;
529 while (low_counter <= high_counter) {
530 var i = keys[low_counter];
531 var j = keys[high_counter];
533 var j_complement = len - j - 1;
536 if (j_complement <= i) {
538 while (keys[--high_counter] == j) { }
541 if (j_complement >= i) {
543 while (keys[++low_counter] == i) { }
547 var current_i = array[low];
548 if (!IS_UNDEFINED(current_i) || low in array) {
549 var current_j = array[high];
550 if (!IS_UNDEFINED(current_j) || high in array) {
551 array[low] = current_j;
552 array[high] = current_i;
554 array[high] = current_i;
558 var current_j = array[high];
559 if (!IS_UNDEFINED(current_j) || high in array) {
560 array[low] = current_j;
567 function PackedArrayReverse(array, len) {
569 for (var i = 0; i < j; i++, j--) {
570 var current_i = array[i];
571 var current_j = array[j];
572 array[i] = current_j;
573 array[j] = current_i;
579 function GenericArrayReverse(array, len) {
581 for (var i = 0; i < j; i++, j--) {
583 var current_i = array[i];
585 var current_j = array[j];
586 array[i] = current_j;
587 array[j] = current_i;
589 array[j] = current_i;
594 var current_j = array[j];
595 array[i] = current_j;
604 function ArrayReverse() {
605 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reverse");
607 var array = TO_OBJECT(this);
608 var len = TO_LENGTH_OR_UINT32(array.length);
609 var isArray = IS_ARRAY(array);
611 if (UseSparseVariant(array, len, isArray, len)) {
612 %NormalizeElements(array);
613 SparseReverse(array, len);
615 } else if (isArray && %_HasFastPackedElements(array)) {
616 return PackedArrayReverse(array, len);
618 return GenericArrayReverse(array, len);
623 function ObservedArrayShift(len) {
627 $observeBeginPerformSplice(this);
628 SimpleMove(this, 0, 1, len, 0);
629 this.length = len - 1;
631 $observeEndPerformSplice(this);
632 $observeEnqueueSpliceRecord(this, 0, [first], 0);
639 function ArrayShift() {
640 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.shift");
642 var array = TO_OBJECT(this);
643 var len = TO_LENGTH_OR_UINT32(array.length);
650 if (ObjectIsSealed(array)) throw MakeTypeError(kArrayFunctionsOnSealed);
652 if (%IsObserved(array))
653 return ObservedArrayShift.call(array, len);
655 var first = array[0];
657 if (UseSparseVariant(array, len, IS_ARRAY(array), len)) {
658 SparseMove(array, 0, 1, len, 0);
660 SimpleMove(array, 0, 1, len, 0);
663 array.length = len - 1;
669 function ObservedArrayUnshift() {
670 var len = TO_LENGTH_OR_UINT32(this.length);
671 var num_arguments = %_ArgumentsLength();
674 $observeBeginPerformSplice(this);
675 SimpleMove(this, 0, 0, len, num_arguments);
676 for (var i = 0; i < num_arguments; i++) {
677 this[i] = %_Arguments(i);
679 var new_length = len + num_arguments;
680 this.length = new_length;
682 $observeEndPerformSplice(this);
683 $observeEnqueueSpliceRecord(this, 0, [], num_arguments);
690 function ArrayUnshift(arg1) { // length == 1
691 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.unshift");
693 if (%IsObserved(this))
694 return ObservedArrayUnshift.apply(this, arguments);
696 var array = TO_OBJECT(this);
697 var len = TO_LENGTH_OR_UINT32(array.length);
698 var num_arguments = %_ArgumentsLength();
700 if (len > 0 && UseSparseVariant(array, len, IS_ARRAY(array), len) &&
701 !ObjectIsSealed(array)) {
702 SparseMove(array, 0, 0, len, num_arguments);
704 SimpleMove(array, 0, 0, len, num_arguments);
707 for (var i = 0; i < num_arguments; i++) {
708 array[i] = %_Arguments(i);
711 var new_length = len + num_arguments;
712 array.length = new_length;
717 function ArraySlice(start, end) {
718 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.slice");
720 var array = TO_OBJECT(this);
721 var len = TO_LENGTH_OR_UINT32(array.length);
722 var start_i = TO_INTEGER(start);
725 if (!IS_UNDEFINED(end)) end_i = TO_INTEGER(end);
729 if (start_i < 0) start_i = 0;
731 if (start_i > len) start_i = len;
736 if (end_i < 0) end_i = 0;
738 if (end_i > len) end_i = len;
743 if (end_i < start_i) return result;
745 if (UseSparseVariant(array, len, IS_ARRAY(array), end_i - start_i)) {
746 %NormalizeElements(array);
747 %NormalizeElements(result);
748 SparseSlice(array, start_i, end_i - start_i, len, result);
750 SimpleSlice(array, start_i, end_i - start_i, len, result);
753 result.length = end_i - start_i;
759 function ComputeSpliceStartIndex(start_i, len) {
762 return start_i < 0 ? 0 : start_i;
765 return start_i > len ? len : start_i;
769 function ComputeSpliceDeleteCount(delete_count, num_arguments, len, start_i) {
770 // SpiderMonkey, TraceMonkey and JSC treat the case where no delete count is
771 // given as a request to delete all the elements from the start.
772 // And it differs from the case of undefined delete count.
773 // This does not follow ECMA-262, but we do the same for
776 if (num_arguments == 1)
777 return len - start_i;
779 del_count = TO_INTEGER(delete_count);
783 if (del_count > len - start_i)
784 return len - start_i;
790 function ObservedArraySplice(start, delete_count) {
791 var num_arguments = %_ArgumentsLength();
792 var len = TO_LENGTH_OR_UINT32(this.length);
793 var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
794 var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len,
796 var deleted_elements = [];
797 deleted_elements.length = del_count;
798 var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0;
801 $observeBeginPerformSplice(this);
803 SimpleSlice(this, start_i, del_count, len, deleted_elements);
804 SimpleMove(this, start_i, del_count, len, num_elements_to_add);
806 // Insert the arguments into the resulting array in
807 // place of the deleted elements.
809 var arguments_index = 2;
810 var arguments_length = %_ArgumentsLength();
811 while (arguments_index < arguments_length) {
812 this[i++] = %_Arguments(arguments_index++);
814 this.length = len - del_count + num_elements_to_add;
817 $observeEndPerformSplice(this);
818 if (deleted_elements.length || num_elements_to_add) {
819 $observeEnqueueSpliceRecord(this,
821 deleted_elements.slice(),
822 num_elements_to_add);
826 // Return the deleted elements.
827 return deleted_elements;
831 function ArraySplice(start, delete_count) {
832 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.splice");
834 if (%IsObserved(this))
835 return ObservedArraySplice.apply(this, arguments);
837 var num_arguments = %_ArgumentsLength();
838 var array = TO_OBJECT(this);
839 var len = TO_LENGTH_OR_UINT32(array.length);
840 var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
841 var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len,
843 var deleted_elements = [];
844 deleted_elements.length = del_count;
845 var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0;
847 if (del_count != num_elements_to_add && ObjectIsSealed(array)) {
848 throw MakeTypeError(kArrayFunctionsOnSealed);
849 } else if (del_count > 0 && ObjectIsFrozen(array)) {
850 throw MakeTypeError(kArrayFunctionsOnFrozen);
853 var changed_elements = del_count;
854 if (num_elements_to_add != del_count) {
855 // If the slice needs to do a actually move elements after the insertion
856 // point, then include those in the estimate of changed elements.
857 changed_elements += len - start_i - del_count;
859 if (UseSparseVariant(array, len, IS_ARRAY(array), changed_elements)) {
860 %NormalizeElements(array);
861 %NormalizeElements(deleted_elements);
862 SparseSlice(array, start_i, del_count, len, deleted_elements);
863 SparseMove(array, start_i, del_count, len, num_elements_to_add);
865 SimpleSlice(array, start_i, del_count, len, deleted_elements);
866 SimpleMove(array, start_i, del_count, len, num_elements_to_add);
869 // Insert the arguments into the resulting array in
870 // place of the deleted elements.
872 var arguments_index = 2;
873 var arguments_length = %_ArgumentsLength();
874 while (arguments_index < arguments_length) {
875 array[i++] = %_Arguments(arguments_index++);
877 array.length = len - del_count + num_elements_to_add;
879 // Return the deleted elements.
880 return deleted_elements;
884 function InnerArraySort(array, length, comparefn) {
885 // In-place QuickSort algorithm.
886 // For short (length <= 22) arrays, insertion sort is used for efficiency.
888 if (!IS_CALLABLE(comparefn)) {
889 comparefn = function (x, y) {
890 if (x === y) return 0;
891 if (%_IsSmi(x) && %_IsSmi(y)) {
892 return %SmiLexicographicCompare(x, y);
896 if (x == y) return 0;
897 else return x < y ? -1 : 1;
900 var InsertionSort = function InsertionSort(a, from, to) {
901 for (var i = from + 1; i < to; i++) {
903 for (var j = i - 1; j >= from; j--) {
905 var order = comparefn(tmp, element);
916 var GetThirdIndex = function(a, from, to) {
917 var t_array = new InternalArray();
918 // Use both 'from' and 'to' to determine the pivot candidates.
919 var increment = 200 + ((to - from) & 15);
923 for (var i = from; i < to; i += increment) {
924 t_array[j] = [i, a[i]];
927 t_array.sort(function(a, b) {
928 return comparefn(a[1], b[1]);
930 var third_index = t_array[t_array.length >> 1][0];
934 var QuickSort = function QuickSort(a, from, to) {
937 // Insertion sort is faster for short arrays.
938 if (to - from <= 10) {
939 InsertionSort(a, from, to);
942 if (to - from > 1000) {
943 third_index = GetThirdIndex(a, from, to);
945 third_index = from + ((to - from) >> 1);
947 // Find a pivot as the median of first, last and middle element.
950 var v2 = a[third_index];
951 var c01 = comparefn(v0, v1);
953 // v1 < v0, so swap them.
958 var c02 = comparefn(v0, v2);
966 // v0 <= v1 && v0 < v2
967 var c12 = comparefn(v1, v2);
979 var low_end = from + 1; // Upper bound of elements lower than pivot.
980 var high_start = to - 1; // Lower bound of elements greater than pivot.
981 a[third_index] = a[low_end];
984 // From low_end to i are elements equal to pivot.
985 // From i to high_start are elements that haven't been compared yet.
986 partition: for (var i = low_end + 1; i < high_start; i++) {
988 var order = comparefn(element, pivot);
991 a[low_end] = element;
993 } else if (order > 0) {
996 if (high_start == i) break partition;
997 var top_elem = a[high_start];
998 order = comparefn(top_elem, pivot);
1000 a[i] = a[high_start];
1001 a[high_start] = element;
1005 a[low_end] = element;
1010 if (to - high_start < low_end - from) {
1011 QuickSort(a, high_start, to);
1014 QuickSort(a, from, low_end);
1020 // Copy elements in the range 0..length from obj's prototype chain
1021 // to obj itself, if obj has holes. Return one more than the maximal index
1022 // of a prototype property.
1023 var CopyFromPrototype = function CopyFromPrototype(obj, length) {
1025 for (var proto = %_GetPrototype(obj); proto; proto = %_GetPrototype(proto)) {
1026 var indices = %GetArrayKeys(proto, length);
1027 if (IS_NUMBER(indices)) {
1028 // It's an interval.
1029 var proto_length = indices;
1030 for (var i = 0; i < proto_length; i++) {
1031 if (!HAS_OWN_PROPERTY(obj, i) && HAS_OWN_PROPERTY(proto, i)) {
1033 if (i >= max) { max = i + 1; }
1037 for (var i = 0; i < indices.length; i++) {
1038 var index = indices[i];
1039 if (!IS_UNDEFINED(index) && !HAS_OWN_PROPERTY(obj, index)
1040 && HAS_OWN_PROPERTY(proto, index)) {
1041 obj[index] = proto[index];
1042 if (index >= max) { max = index + 1; }
1050 // Set a value of "undefined" on all indices in the range from..to
1051 // where a prototype of obj has an element. I.e., shadow all prototype
1052 // elements in that range.
1053 var ShadowPrototypeElements = function(obj, from, to) {
1054 for (var proto = %_GetPrototype(obj); proto; proto = %_GetPrototype(proto)) {
1055 var indices = %GetArrayKeys(proto, to);
1056 if (IS_NUMBER(indices)) {
1057 // It's an interval.
1058 var proto_length = indices;
1059 for (var i = from; i < proto_length; i++) {
1060 if (HAS_OWN_PROPERTY(proto, i)) {
1065 for (var i = 0; i < indices.length; i++) {
1066 var index = indices[i];
1067 if (!IS_UNDEFINED(index) && from <= index &&
1068 HAS_OWN_PROPERTY(proto, index)) {
1069 obj[index] = UNDEFINED;
1076 var SafeRemoveArrayHoles = function SafeRemoveArrayHoles(obj) {
1077 // Copy defined elements from the end to fill in all holes and undefineds
1078 // in the beginning of the array. Write undefineds and holes at the end
1079 // after loop is finished.
1080 var first_undefined = 0;
1081 var last_defined = length - 1;
1083 while (first_undefined < last_defined) {
1084 // Find first undefined element.
1085 while (first_undefined < last_defined &&
1086 !IS_UNDEFINED(obj[first_undefined])) {
1089 // Maintain the invariant num_holes = the number of holes in the original
1090 // array with indices <= first_undefined or > last_defined.
1091 if (!HAS_OWN_PROPERTY(obj, first_undefined)) {
1095 // Find last defined element.
1096 while (first_undefined < last_defined &&
1097 IS_UNDEFINED(obj[last_defined])) {
1098 if (!HAS_OWN_PROPERTY(obj, last_defined)) {
1103 if (first_undefined < last_defined) {
1104 // Fill in hole or undefined.
1105 obj[first_undefined] = obj[last_defined];
1106 obj[last_defined] = UNDEFINED;
1109 // If there were any undefineds in the entire array, first_undefined
1110 // points to one past the last defined element. Make this true if
1111 // there were no undefineds, as well, so that first_undefined == number
1112 // of defined elements.
1113 if (!IS_UNDEFINED(obj[first_undefined])) first_undefined++;
1114 // Fill in the undefineds and the holes. There may be a hole where
1115 // an undefined should be and vice versa.
1117 for (i = first_undefined; i < length - num_holes; i++) {
1120 for (i = length - num_holes; i < length; i++) {
1121 // For compatability with Webkit, do not expose elements in the prototype.
1122 if (i in %_GetPrototype(obj)) {
1129 // Return the number of defined elements.
1130 return first_undefined;
1133 if (length < 2) return array;
1135 var is_array = IS_ARRAY(array);
1136 var max_prototype_element;
1138 // For compatibility with JSC, we also sort elements inherited from
1139 // the prototype chain on non-Array objects.
1140 // We do this by copying them to this object and sorting only
1141 // own elements. This is not very efficient, but sorting with
1142 // inherited elements happens very, very rarely, if at all.
1143 // The specification allows "implementation dependent" behavior
1144 // if an element on the prototype chain has an element that
1145 // might interact with sorting.
1146 max_prototype_element = CopyFromPrototype(array, length);
1149 // %RemoveArrayHoles returns -1 if fast removal is not supported.
1150 var num_non_undefined = %RemoveArrayHoles(array, length);
1152 if (num_non_undefined == -1) {
1153 // The array is observed, or there were indexed accessors in the array.
1154 // Move array holes and undefineds to the end using a Javascript function
1155 // that is safe in the presence of accessors and is observable.
1156 num_non_undefined = SafeRemoveArrayHoles(array);
1159 QuickSort(array, 0, num_non_undefined);
1161 if (!is_array && (num_non_undefined + 1 < max_prototype_element)) {
1162 // For compatibility with JSC, we shadow any elements in the prototype
1163 // chain that has become exposed by sort moving a hole to its position.
1164 ShadowPrototypeElements(array, num_non_undefined, max_prototype_element);
1171 function ArraySort(comparefn) {
1172 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.sort");
1174 var array = TO_OBJECT(this);
1175 var length = TO_LENGTH_OR_UINT32(array.length);
1176 return InnerArraySort(array, length, comparefn);
1180 // The following functions cannot be made efficient on sparse arrays while
1181 // preserving the semantics, since the calls to the receiver function can add
1182 // or delete elements from the array.
1183 function InnerArrayFilter(f, receiver, array, length) {
1184 if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
1186 var accumulator = new InternalArray();
1187 var accumulator_length = 0;
1188 var is_array = IS_ARRAY(array);
1189 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
1190 for (var i = 0; i < length; i++) {
1191 if (HAS_INDEX(array, i, is_array)) {
1192 var element = array[i];
1193 // Prepare break slots for debugger step in.
1194 if (stepping) %DebugPrepareStepInIfStepping(f);
1195 if (%_Call(f, receiver, element, i, array)) {
1196 accumulator[accumulator_length++] = element;
1203 function ArrayFilter(f, receiver) {
1204 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.filter");
1206 // Pull out the length so that modifications to the length in the
1207 // loop will not affect the looping and side effects are visible.
1208 var array = TO_OBJECT(this);
1209 var length = TO_LENGTH_OR_UINT32(array.length);
1210 var accumulator = InnerArrayFilter(f, receiver, array, length);
1211 var result = new GlobalArray();
1212 %MoveArrayContents(accumulator, result);
1216 function InnerArrayForEach(f, receiver, array, length) {
1217 if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
1219 var is_array = IS_ARRAY(array);
1220 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
1221 for (var i = 0; i < length; i++) {
1222 if (HAS_INDEX(array, i, is_array)) {
1223 var element = array[i];
1224 // Prepare break slots for debugger step in.
1225 if (stepping) %DebugPrepareStepInIfStepping(f);
1226 %_Call(f, receiver, element, i, array);
1231 function ArrayForEach(f, receiver) {
1232 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.forEach");
1234 // Pull out the length so that modifications to the length in the
1235 // loop will not affect the looping and side effects are visible.
1236 var array = TO_OBJECT(this);
1237 var length = TO_LENGTH_OR_UINT32(array.length);
1238 InnerArrayForEach(f, receiver, array, length);
1242 function InnerArraySome(f, receiver, array, length) {
1243 if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
1245 var is_array = IS_ARRAY(array);
1246 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
1247 for (var i = 0; i < length; i++) {
1248 if (HAS_INDEX(array, i, is_array)) {
1249 var element = array[i];
1250 // Prepare break slots for debugger step in.
1251 if (stepping) %DebugPrepareStepInIfStepping(f);
1252 if (%_Call(f, receiver, element, i, array)) return true;
1259 // Executes the function once for each element present in the
1260 // array until it finds one where callback returns true.
1261 function ArraySome(f, receiver) {
1262 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.some");
1264 // Pull out the length so that modifications to the length in the
1265 // loop will not affect the looping and side effects are visible.
1266 var array = TO_OBJECT(this);
1267 var length = TO_LENGTH_OR_UINT32(array.length);
1268 return InnerArraySome(f, receiver, array, length);
1272 function InnerArrayEvery(f, receiver, array, length) {
1273 if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
1275 var is_array = IS_ARRAY(array);
1276 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
1277 for (var i = 0; i < length; i++) {
1278 if (HAS_INDEX(array, i, is_array)) {
1279 var element = array[i];
1280 // Prepare break slots for debugger step in.
1281 if (stepping) %DebugPrepareStepInIfStepping(f);
1282 if (!%_Call(f, receiver, element, i, array)) return false;
1288 function ArrayEvery(f, receiver) {
1289 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.every");
1291 // Pull out the length so that modifications to the length in the
1292 // loop will not affect the looping and side effects are visible.
1293 var array = TO_OBJECT(this);
1294 var length = TO_LENGTH_OR_UINT32(array.length);
1295 return InnerArrayEvery(f, receiver, array, length);
1299 function InnerArrayMap(f, receiver, array, length) {
1300 if (!IS_CALLABLE(f)) throw MakeTypeError(kCalledNonCallable, f);
1302 var accumulator = new InternalArray(length);
1303 var is_array = IS_ARRAY(array);
1304 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(f);
1305 for (var i = 0; i < length; i++) {
1306 if (HAS_INDEX(array, i, is_array)) {
1307 var element = array[i];
1308 // Prepare break slots for debugger step in.
1309 if (stepping) %DebugPrepareStepInIfStepping(f);
1310 accumulator[i] = %_Call(f, receiver, element, i, array);
1317 function ArrayMap(f, receiver) {
1318 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.map");
1320 // Pull out the length so that modifications to the length in the
1321 // loop will not affect the looping and side effects are visible.
1322 var array = TO_OBJECT(this);
1323 var length = TO_LENGTH_OR_UINT32(array.length);
1324 var accumulator = InnerArrayMap(f, receiver, array, length);
1325 var result = new GlobalArray();
1326 %MoveArrayContents(accumulator, result);
1331 // For .indexOf, we don't need to pass in the number of arguments
1332 // at the callsite since ToInteger(undefined) == 0; however, for
1333 // .lastIndexOf, we need to pass it, since the behavior for passing
1334 // undefined is 0 but for not including the argument is length-1.
1335 function InnerArrayIndexOf(array, element, index, length) {
1336 if (length == 0) return -1;
1337 if (IS_UNDEFINED(index)) {
1340 index = TO_INTEGER(index);
1341 // If index is negative, index from the end of the array.
1343 index = length + index;
1344 // If index is still negative, search the entire array.
1345 if (index < 0) index = 0;
1350 if (UseSparseVariant(array, length, IS_ARRAY(array), max - min)) {
1351 %NormalizeElements(array);
1352 var indices = %GetArrayKeys(array, length);
1353 if (IS_NUMBER(indices)) {
1354 // It's an interval.
1355 max = indices; // Capped by length already.
1356 // Fall through to loop below.
1358 if (indices.length == 0) return -1;
1359 // Get all the keys in sorted order.
1360 var sortedKeys = GetSortedArrayKeys(array, indices);
1361 var n = sortedKeys.length;
1363 while (i < n && sortedKeys[i] < index) i++;
1365 var key = sortedKeys[i];
1366 if (!IS_UNDEFINED(key) && array[key] === element) return key;
1372 // Lookup through the array.
1373 if (!IS_UNDEFINED(element)) {
1374 for (var i = min; i < max; i++) {
1375 if (array[i] === element) return i;
1379 // Lookup through the array.
1380 for (var i = min; i < max; i++) {
1381 if (IS_UNDEFINED(array[i]) && i in array) {
1389 function ArrayIndexOf(element, index) {
1390 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.indexOf");
1392 var length = TO_LENGTH_OR_UINT32(this.length);
1393 return InnerArrayIndexOf(this, element, index, length);
1397 function InnerArrayLastIndexOf(array, element, index, length, argumentsLength) {
1398 if (length == 0) return -1;
1399 if (argumentsLength < 2) {
1402 index = TO_INTEGER(index);
1403 // If index is negative, index from end of the array.
1404 if (index < 0) index += length;
1405 // If index is still negative, do not search the array.
1406 if (index < 0) return -1;
1407 else if (index >= length) index = length - 1;
1411 if (UseSparseVariant(array, length, IS_ARRAY(array), index)) {
1412 %NormalizeElements(array);
1413 var indices = %GetArrayKeys(array, index + 1);
1414 if (IS_NUMBER(indices)) {
1415 // It's an interval.
1416 max = indices; // Capped by index already.
1417 // Fall through to loop below.
1419 if (indices.length == 0) return -1;
1420 // Get all the keys in sorted order.
1421 var sortedKeys = GetSortedArrayKeys(array, indices);
1422 var i = sortedKeys.length - 1;
1424 var key = sortedKeys[i];
1425 if (!IS_UNDEFINED(key) && array[key] === element) return key;
1431 // Lookup through the array.
1432 if (!IS_UNDEFINED(element)) {
1433 for (var i = max; i >= min; i--) {
1434 if (array[i] === element) return i;
1438 for (var i = max; i >= min; i--) {
1439 if (IS_UNDEFINED(array[i]) && i in array) {
1447 function ArrayLastIndexOf(element, index) {
1448 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.lastIndexOf");
1450 var length = TO_LENGTH_OR_UINT32(this.length);
1451 return InnerArrayLastIndexOf(this, element, index, length,
1452 %_ArgumentsLength());
1456 function InnerArrayReduce(callback, current, array, length, argumentsLength) {
1457 if (!IS_CALLABLE(callback)) {
1458 throw MakeTypeError(kCalledNonCallable, callback);
1461 var is_array = IS_ARRAY(array);
1463 find_initial: if (argumentsLength < 2) {
1464 for (; i < length; i++) {
1465 if (HAS_INDEX(array, i, is_array)) {
1466 current = array[i++];
1470 throw MakeTypeError(kReduceNoInitial);
1473 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(callback);
1474 for (; i < length; i++) {
1475 if (HAS_INDEX(array, i, is_array)) {
1476 var element = array[i];
1477 // Prepare break slots for debugger step in.
1478 if (stepping) %DebugPrepareStepInIfStepping(callback);
1479 current = callback(current, element, i, array);
1486 function ArrayReduce(callback, current) {
1487 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduce");
1489 // Pull out the length so that modifications to the length in the
1490 // loop will not affect the looping and side effects are visible.
1491 var array = TO_OBJECT(this);
1492 var length = TO_LENGTH_OR_UINT32(array.length);
1493 return InnerArrayReduce(callback, current, array, length,
1494 %_ArgumentsLength());
1498 function InnerArrayReduceRight(callback, current, array, length,
1500 if (!IS_CALLABLE(callback)) {
1501 throw MakeTypeError(kCalledNonCallable, callback);
1504 var is_array = IS_ARRAY(array);
1506 find_initial: if (argumentsLength < 2) {
1507 for (; i >= 0; i--) {
1508 if (HAS_INDEX(array, i, is_array)) {
1509 current = array[i--];
1513 throw MakeTypeError(kReduceNoInitial);
1516 var stepping = DEBUG_IS_ACTIVE && %DebugCallbackSupportsStepping(callback);
1517 for (; i >= 0; i--) {
1518 if (HAS_INDEX(array, i, is_array)) {
1519 var element = array[i];
1520 // Prepare break slots for debugger step in.
1521 if (stepping) %DebugPrepareStepInIfStepping(callback);
1522 current = callback(current, element, i, array);
1529 function ArrayReduceRight(callback, current) {
1530 CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reduceRight");
1532 // Pull out the length so that side effects are visible before the
1533 // callback function is checked.
1534 var array = TO_OBJECT(this);
1535 var length = TO_LENGTH_OR_UINT32(array.length);
1536 return InnerArrayReduceRight(callback, current, array, length,
1537 %_ArgumentsLength());
1541 function ArrayIsArray(obj) {
1542 return IS_ARRAY(obj);
1546 // -------------------------------------------------------------------
1548 // Set up non-enumerable constructor property on the Array.prototype
1550 %AddNamedProperty(GlobalArray.prototype, "constructor", GlobalArray,
1553 // Set up unscopable properties on the Array.prototype object.
1564 %AddNamedProperty(GlobalArray.prototype, unscopablesSymbol, unscopables,
1565 DONT_ENUM | READ_ONLY);
1567 // Set up non-enumerable functions on the Array object.
1568 utils.InstallFunctions(GlobalArray, DONT_ENUM, [
1569 "isArray", ArrayIsArray
1572 var specialFunctions = %SpecialArrayFunctions();
1574 var getFunction = function(name, jsBuiltin, len) {
1576 if (specialFunctions.hasOwnProperty(name)) {
1577 f = specialFunctions[name];
1579 if (!IS_UNDEFINED(len)) {
1580 %FunctionSetLength(f, len);
1585 // Set up non-enumerable functions of the Array.prototype object and
1587 // Manipulate the length of some of the functions to meet
1588 // expectations set by ECMA-262 or Mozilla.
1589 utils.InstallFunctions(GlobalArray.prototype, DONT_ENUM, [
1590 "toString", getFunction("toString", ArrayToString),
1591 "toLocaleString", getFunction("toLocaleString", ArrayToLocaleString),
1592 "join", getFunction("join", ArrayJoin),
1593 "pop", getFunction("pop", ArrayPop),
1594 "push", getFunction("push", ArrayPush, 1),
1595 "reverse", getFunction("reverse", ArrayReverse),
1596 "shift", getFunction("shift", ArrayShift),
1597 "unshift", getFunction("unshift", ArrayUnshift, 1),
1598 "slice", getFunction("slice", ArraySlice, 2),
1599 "splice", getFunction("splice", ArraySplice, 2),
1600 "sort", getFunction("sort", ArraySort),
1601 "filter", getFunction("filter", ArrayFilter, 1),
1602 "forEach", getFunction("forEach", ArrayForEach, 1),
1603 "some", getFunction("some", ArraySome, 1),
1604 "every", getFunction("every", ArrayEvery, 1),
1605 "map", getFunction("map", ArrayMap, 1),
1606 "indexOf", getFunction("indexOf", ArrayIndexOf, 1),
1607 "lastIndexOf", getFunction("lastIndexOf", ArrayLastIndexOf, 1),
1608 "reduce", getFunction("reduce", ArrayReduce, 1),
1609 "reduceRight", getFunction("reduceRight", ArrayReduceRight, 1)
1612 %FinishArrayPrototypeSetup(GlobalArray.prototype);
1614 // The internal Array prototype doesn't need to be fancy, since it's never
1615 // exposed to user code.
1616 // Adding only the functions that are actually used.
1617 utils.SetUpLockedPrototype(InternalArray, GlobalArray(), [
1618 "indexOf", getFunction("indexOf", ArrayIndexOf),
1619 "join", getFunction("join", ArrayJoin),
1620 "pop", getFunction("pop", ArrayPop),
1621 "push", getFunction("push", ArrayPush),
1622 "shift", getFunction("shift", ArrayShift),
1623 "sort", getFunction("sort", ArraySort),
1624 "splice", getFunction("splice", ArraySplice)
1627 utils.SetUpLockedPrototype(InternalPackedArray, GlobalArray(), [
1628 "join", getFunction("join", ArrayJoin),
1629 "pop", getFunction("pop", ArrayPop),
1630 "push", getFunction("push", ArrayPush),
1631 "shift", getFunction("shift", ArrayShift)
1634 // -------------------------------------------------------------------
1637 utils.Export(function(to) {
1638 to.ArrayIndexOf = ArrayIndexOf;
1639 to.ArrayJoin = ArrayJoin;
1640 to.ArrayPush = ArrayPush;
1641 to.ArrayToString = ArrayToString;
1642 to.InnerArrayEvery = InnerArrayEvery;
1643 to.InnerArrayFilter = InnerArrayFilter;
1644 to.InnerArrayForEach = InnerArrayForEach;
1645 to.InnerArrayIndexOf = InnerArrayIndexOf;
1646 to.InnerArrayJoin = InnerArrayJoin;
1647 to.InnerArrayLastIndexOf = InnerArrayLastIndexOf;
1648 to.InnerArrayMap = InnerArrayMap;
1649 to.InnerArrayReduce = InnerArrayReduce;
1650 to.InnerArrayReduceRight = InnerArrayReduceRight;
1651 to.InnerArraySome = InnerArraySome;
1652 to.InnerArraySort = InnerArraySort;
1653 to.InnerArrayToLocaleString = InnerArrayToLocaleString;
1654 to.PackedArrayReverse = PackedArrayReverse;
1658 "array_pop", ArrayPop,
1659 "array_push", ArrayPush,
1660 "array_shift", ArrayShift,
1661 "array_splice", ArraySplice,
1662 "array_slice", ArraySlice,
1663 "array_unshift", ArrayUnshift,