Commit 837fec91 authored by Simon Zünd's avatar Simon Zünd Committed by Commit Bot

[array] Replace JS Array.p.reverse with a Torque implementation

This CL adds a baseline implementation for Array.p.reverse in Torque,
as well as fastpaths for PACKED elements kinds.

Support for sparse JSArrays was removed.

R=jgruber@chromium.org, petermarshall@chromium.org

Bug: v8:7624
Cq-Include-Trybots: luci.v8.try:v8_linux_noi18n_rel_ng
Change-Id: I12900fbbb44746f1c5d36b78be826e14b88b4f69
Reviewed-on: https://chromium-review.googlesource.com/1185600
Commit-Queue: Simon Zünd <szuend@google.com>
Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#55369}
parent 8093b4f3
......@@ -887,6 +887,7 @@ torque_files = [
"src/builtins/array.tq",
"src/builtins/array-copywithin.tq",
"src/builtins/array-foreach.tq",
"src/builtins/array-reverse.tq",
"src/builtins/typed-array.tq",
"src/builtins/data-view.tq",
"test/torque/test-torque.tq",
......
......@@ -1735,6 +1735,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
0, false);
SimpleInstallFunction(isolate_, proto, "push",
Builtins::kArrayPrototypePush, 1, false);
SimpleInstallFunction(isolate_, proto, "reverse",
Builtins::kArrayPrototypeReverse, 0, false);
SimpleInstallFunction(isolate_, proto, "shift",
Builtins::kArrayPrototypeShift, 0, false);
SimpleInstallFunction(isolate_, proto, "unshift", Builtins::kArrayUnshift,
......
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module array {
macro LoadElement<ElementsAccessor : type, T : type>(
elements: FixedArrayBase, index: Smi): T;
LoadElement<FastPackedSmiElements, Smi>(
elements: FixedArrayBase, index: Smi): Smi {
const elems: FixedArray = unsafe_cast<FixedArray>(elements);
return unsafe_cast<Smi>(elems[index]);
}
LoadElement<FastPackedObjectElements, Object>(
elements: FixedArrayBase, index: Smi): Object {
const elems: FixedArray = unsafe_cast<FixedArray>(elements);
return elems[index];
}
LoadElement<FastPackedDoubleElements, float64>(
elements: FixedArrayBase, index: Smi): float64 {
try {
const elems: FixedDoubleArray = unsafe_cast<FixedDoubleArray>(elements);
return LoadDoubleWithHoleCheck(elems, index) otherwise Hole;
}
label Hole {
// This macro is only used for PACKED_DOUBLE, loading the hole should
// be impossible.
unreachable;
}
}
macro StoreElement<ElementsAccessor : type, T : type>(
elements: FixedArrayBase, index: Smi, value: T);
StoreElement<FastPackedSmiElements, Smi>(
elements: FixedArrayBase, index: Smi, value: Smi) {
const elems: FixedArray = unsafe_cast<FixedArray>(elements);
StoreFixedArrayElementSmi(elems, index, value, SKIP_WRITE_BARRIER);
}
StoreElement<FastPackedObjectElements, Object>(
elements: FixedArrayBase, index: Smi, value: Object) {
const elems: FixedArray = unsafe_cast<FixedArray>(elements);
elems[index] = value;
}
StoreElement<FastPackedDoubleElements, float64>(
elements: FixedArrayBase, index: Smi, value: float64) {
const elems: FixedDoubleArray = unsafe_cast<FixedDoubleArray>(elements);
assert(value == Float64SilenceNaN(value));
StoreFixedDoubleArrayElementWithSmiIndex(elems, index, value);
}
// Fast-path for all PACKED_* elements kinds. These do not need to check
// whether a property is present, so we can simply swap them using fast
// FixedArray loads/stores.
macro FastPackedArrayReverse<Accessor : type, T : type>(
elements: FixedArrayBase, length: Smi) {
let lower: Smi = 0;
let upper: Smi = length - 1;
while (lower < upper) {
const lower_value: T = LoadElement<Accessor, T>(elements, lower);
const upper_value: T = LoadElement<Accessor, T>(elements, upper);
StoreElement<Accessor, T>(elements, lower, upper_value);
StoreElement<Accessor, T>(elements, upper, lower_value);
++lower;
--upper;
}
}
macro GenericArrayReverse(context: Context, receiver: Object): Object {
// 1. Let O be ? ToObject(this value).
const object: JSReceiver = ToObject_Inline(context, receiver);
// 2. Let len be ? ToLength(? Get(O, "length")).
const length: Number = GetLengthProperty(context, object);
// 3. Let middle be floor(len / 2).
// 4. Let lower be 0.
// 5. Repeat, while lower != middle.
// a. Let upper be len - lower - 1.
// Instead of calculating the middle value, we simply initialize upper
// with len - 1 and decrement it after each iteration.
let lower: Number = 0;
let upper: Number = length - 1;
while (lower < upper) {
let lower_value: Object = Undefined;
let upper_value: Object = Undefined;
// b. Let upperP be ! ToString(upper).
// c. Let lowerP be ! ToString(lower).
// d. Let lowerExists be ? HasProperty(O, lowerP).
const lower_exists: Boolean = HasProperty(context, object, lower);
// e. If lowerExists is true, then.
if (lower_exists == True) {
// i. Let lowerValue be ? Get(O, lowerP).
lower_value = GetProperty(context, object, lower);
}
// f. Let upperExists be ? HasProperty(O, upperP).
const upper_exists: Boolean = HasProperty(context, object, upper);
// g. If upperExists is true, then.
if (upper_exists == True) {
// i. Let upperValue be ? Get(O, upperP).
upper_value = GetProperty(context, object, upper);
}
// h. If lowerExists is true and upperExists is true, then
if (lower_exists == True && upper_exists == True) {
// i. Perform ? Set(O, lowerP, upperValue, true).
SetProperty(context, object, lower, upper_value);
// ii. Perform ? Set(O, upperP, lowerValue, true).
SetProperty(context, object, upper, lower_value);
} else if (lower_exists == False && upper_exists == True) {
// i. Perform ? Set(O, lowerP, upperValue, true).
SetProperty(context, object, lower, upper_value);
// ii. Perform ? DeletePropertyOrThrow(O, upperP).
DeleteProperty(context, object, upper, kStrict);
} else if (lower_exists == True && upper_exists == False) {
// i. Perform ? DeletePropertyOrThrow(O, lowerP).
DeleteProperty(context, object, lower, kStrict);
// ii. Perform ? Set(O, upperP, lowerValue, true).
SetProperty(context, object, upper, lower_value);
}
// l. Increase lower by 1.
++lower;
--upper;
}
// 6. Return O.
return object;
}
macro TryFastPackedArrayReverse(receiver: Object) labels Slow {
const array: JSArray = cast<JSArray>(receiver) otherwise Slow;
const map: Map = array.map;
if (!IsExtensibleMap(map)) goto Slow;
if (array.elements.map == kCOWMap) goto Slow;
const kind: ElementsKind = map.elements_kind;
if (kind == PACKED_SMI_ELEMENTS) {
FastPackedArrayReverse<FastPackedSmiElements, Smi>(
array.elements, array.length_fast);
} else if (kind == PACKED_ELEMENTS) {
FastPackedArrayReverse<FastPackedObjectElements, Object>(
array.elements, array.length_fast);
} else if (kind == PACKED_DOUBLE_ELEMENTS) {
FastPackedArrayReverse<FastPackedDoubleElements, float64>(
array.elements, array.length_fast);
} else {
goto Slow;
}
}
// https://tc39.github.io/ecma262/#sec-array.prototype.reverse
javascript builtin ArrayPrototypeReverse(
context: Context, receiver: Object, ...arguments): Object {
try {
TryFastPackedArrayReverse(receiver) otherwise Baseline;
return receiver;
}
label Baseline {
return GenericArrayReverse(context, receiver);
}
}
}
......@@ -3,6 +3,17 @@
// found in the LICENSE file.
module array {
// Naming convention from elements.cc. We have a similar intent but implement
// fastpaths using generics instead of using a class hierarchy for elements
// kinds specific implementations.
type GenericElementsAccessor;
type FastPackedSmiElements;
type FastPackedObjectElements;
type FastPackedDoubleElements;
type FastSmiOrObjectElements;
type FastDoubleElements;
type DictionaryElements;
macro GetLengthProperty(context: Context, o: Object): Number {
if (BranchIfFastJSArray(o, context)) {
let a: JSArray = unsafe_cast<JSArray>(o);
......
......@@ -817,6 +817,7 @@ DebugInfo::SideEffectState BuiltinGetSideEffectState(Builtins::Name id) {
case Builtins::kArrayIteratorPrototypeNext:
case Builtins::kArrayPrototypePop:
case Builtins::kArrayPrototypePush:
case Builtins::kArrayPrototypeReverse:
case Builtins::kArrayPrototypeShift:
case Builtins::kArraySplice:
case Builtins::kArrayUnshift:
......
......@@ -408,106 +408,6 @@ DEFINE_METHOD(
);
// For implementing reverse() on large, sparse arrays.
function SparseReverse(array, len) {
var keys = GetSortedArrayKeys(array, %GetArrayKeys(array, len));
var high_counter = keys.length - 1;
var low_counter = 0;
while (low_counter <= high_counter) {
var i = keys[low_counter];
var j = keys[high_counter];
var j_complement = len - j - 1;
var low, high;
if (j_complement <= i) {
high = j;
while (keys[--high_counter] == j) { }
low = j_complement;
}
if (j_complement >= i) {
low = i;
while (keys[++low_counter] == i) { }
high = len - i - 1;
}
var current_i = array[low];
if (!IS_UNDEFINED(current_i) || low in array) {
var current_j = array[high];
if (!IS_UNDEFINED(current_j) || high in array) {
array[low] = current_j;
array[high] = current_i;
} else {
array[high] = current_i;
delete array[low];
}
} else {
var current_j = array[high];
if (!IS_UNDEFINED(current_j) || high in array) {
array[low] = current_j;
delete array[high];
}
}
}
}
function PackedArrayReverse(array, len) {
var j = len - 1;
for (var i = 0; i < j; i++, j--) {
var current_i = array[i];
var current_j = array[j];
array[i] = current_j;
array[j] = current_i;
}
return array;
}
function GenericArrayReverse(array, len) {
var j = len - 1;
for (var i = 0; i < j; i++, j--) {
if (i in array) {
var current_i = array[i];
if (j in array) {
var current_j = array[j];
array[i] = current_j;
array[j] = current_i;
} else {
array[j] = current_i;
delete array[i];
}
} else {
if (j in array) {
var current_j = array[j];
array[i] = current_j;
delete array[j];
}
}
}
return array;
}
DEFINE_METHOD(
GlobalArray.prototype,
reverse() {
var array = TO_OBJECT(this);
var len = TO_LENGTH(array.length);
var isArray = IS_ARRAY(array);
if (UseSparseVariant(array, len, isArray, len)) {
%NormalizeElements(array);
SparseReverse(array, len);
return array;
} else if (isArray && %_HasFastPackedElements(array)) {
return PackedArrayReverse(array, len);
} else {
return GenericArrayReverse(array, len);
}
}
);
function ArrayShiftFallback() {
var array = TO_OBJECT(this);
var len = TO_LENGTH(array.length);
......
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
assertArrayEquals([], [].reverse());
assertArrayEquals([8, 6, 4, 2], [2, 4, 6, 8].reverse());
assertArrayEquals([0.8, 0.6, 0.4], [0.4, 0.6, 0.8].reverse());
assertArrayEquals(["str4", "str3", "str2"], ["str2", "str3", "str4"].reverse());
assertArrayEquals([4,3,,1], [1,,3,4].reverse());
assertArrayEquals([4,,2,1], [1,2,,4].reverse());
assertArrayEquals([5,,3,,1], [1,,3,,5].reverse());
function TestReverseWithObject() {
let obj = { length: 5 };
obj[0] = "foo";
obj[3] = "bar";
Array.prototype.reverse.call(obj);
assertArrayEquals([,"bar",,,"foo"], obj);
}
TestReverseWithObject();
function TestReverseWithPrototypeChain() {
let proto = { 0: "foo", 19: "bar" };
let obj = { length: 20, 5: "foobar", __proto__: proto };
Array.prototype.reverse.call(obj);
assertEquals("bar", obj[0]);
assertEquals("foobar", obj[14]);
assertEquals("foo", obj[19]);
}
TestReverseWithPrototypeChain();
function TestReverseWithTypedArrays() {
const constructors = [
Uint8Array,
Int8Array,
Uint16Array,
Int16Array,
Uint32Array,
Int32Array,
Uint8ClampedArray,
Float32Array,
Float64Array
];
for (const constructor of constructors) {
const array_odd = new constructor([1, 2, 3]);
Array.prototype.reverse.call(array_odd);
assertArrayEquals([3, 2, 1], array_odd, constructor);
const array_even = new constructor([1, 2, 3, 4]);
Array.prototype.reverse.call(array_even);
assertArrayEquals([4, 3, 2, 1], array_even, constructor);
// Array.prototype.reverse respects shadowing length on TypedArrays.
const array = new constructor([1, 2, 3, 4]);
Object.defineProperty(array, 'length', {value: 2});
Array.prototype.reverse.call(array);
assertArrayEquals([2, 1], array, constructor);
const array_shadowed_length = new constructor([1, 2]);
Object.defineProperty(array_shadowed_length, 'length', {value: 5});
assertThrows(() => Array.prototype.reverse.call(array_shadowed_length));
}
}
TestReverseWithTypedArrays();
// Copyright 2010 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Regression test for http://code.google.com/p/v8/issues/detail?id=685.
//
// Test that keyed load IC generic stub uses unsigned comparison for
// for the length field of arrays.
//
// The test passes if it does not crash.
function test() {
var N = 0xFFFFFFFF;
var a = [];
a[N - 1] = 0;
a[N - 2] = 1;
a.reverse();
}
test();
// Copyright 2008 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/**
* @fileoverview Test reverse on small * and large arrays.
*/
var VERYLARGE = 4000000000;
// Nicer for firefox 1.5. Unless you uncomment the following line,
// smjs will appear to hang on this file.
//var VERYLARGE = 40000;
// Simple test of reverse on sparse array.
var a = [];
a.length = 2000;
a[15] = 'a';
a[30] = 'b';
Array.prototype[30] = 'B'; // Should be hidden by a[30].
a[40] = 'c';
a[50] = 'deleted';
delete a[50]; // Should leave no trace once deleted.
a[1959] = 'd'; // Swapped with a[40] when reversing.
a[1999] = 'e';
assertEquals("abcde", a.join(''));
a.reverse();
delete Array.prototype[30];
assertEquals("edcba", a.join(''));
var seed = 43;
// CONG pseudo random number generator. Used for fuzzing the sparse array
// reverse code.
function DoOrDont() {
seed = (69069 * seed + 1234567) % 0x100000000;
return (seed & 0x100000) != 0;
}
var sizes = [140, 40000, VERYLARGE];
var poses = [0, 10, 50, 69];
// Fuzzing test of reverse on sparse array.
for (var iterations = 0; iterations < 20; iterations++) {
for (var size_pos = 0; size_pos < sizes.length; size_pos++) {
var size = sizes[size_pos];
var to_delete = [];
var a;
// Make sure we test both array-backed and hash-table backed
// arrays.
if (size < 1000) {
a = new Array(size);
} else {
a = new Array();
a.length = size;
}
var expected = '';
var expected_reversed = '';
for (var pos_pos = 0; pos_pos < poses.length; pos_pos++) {
var pos = poses[pos_pos];
var letter = String.fromCharCode(97 + pos_pos);
if (DoOrDont()) {
a[pos] = letter;
expected += letter;
expected_reversed = letter + expected_reversed;
} else if (DoOrDont()) {
Array.prototype[pos] = letter;
expected += letter;
expected_reversed = letter + expected_reversed;
to_delete.push(pos);
}
}
var expected2 = '';
var expected_reversed2 = '';
for (var pos_pos = poses.length - 1; pos_pos >= 0; pos_pos--) {
var letter = String.fromCharCode(110 + pos_pos);
var pos = size - poses[pos_pos] - 1;
if (DoOrDont()) {
a[pos] = letter;
expected2 += letter;
expected_reversed2 = letter + expected_reversed2;
} else if (DoOrDont()) {
Array.prototype[pos] = letter;
expected2 += letter;
expected_reversed2 = letter + expected_reversed2;
to_delete.push(pos);
}
}
assertEquals(expected + expected2, a.join(''), 'join' + size);
a.reverse();
while (to_delete.length != 0) {
var pos = to_delete.pop();
delete(Array.prototype[pos]);
}
assertEquals(expected_reversed2 + expected_reversed, a.join(''), 'reverse then join' + size);
}
}
......@@ -517,7 +517,6 @@
# https://bugs.chromium.org/p/v8/issues/detail?id=6538
'built-ins/Array/prototype/unshift/throws-if-integer-limit-exceeded': [SKIP],
'built-ins/Array/prototype/reverse/length-exceeding-integer-limit-with-proxy': [FAIL],
'built-ins/Array/prototype/splice/create-species-length-exceeding-integer-limit': [FAIL],
'built-ins/Array/prototype/splice/throws-if-integer-limit-exceeded': [SKIP],
......
......@@ -14,9 +14,6 @@
// https://github.com/python/cpython/blob/master/Objects/listsort.txt
module array {
// Naming convention from elements.cc. We have a similar intent but implement
// fastpaths using generics instead of using a class hierarchy for elements
// kinds specific implementations.
// All accessors bail to the GenericElementsAccessor if assumptions checked
// by "CanUseSameAccessor<>" are violated:
// Generic <- FastPackedSmi
......@@ -27,12 +24,6 @@ module array {
// The only exception is TempArrayElements, since it does not describe the
// "elements" of the receiver, but instead is used as an "adaptor" so
// GallopLeft/GallopRight can be reused with the temporary array.
type GenericElementsAccessor;
type FastPackedSmiElements;
type FastSmiOrObjectElements;
type FastDoubleElements;
type DictionaryElements;
const kGenericElementsAccessorId: Smi = 0;
const kFastElementsAccessorId: Smi = 1;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment