Commit e855e514 authored by Peter Marshall's avatar Peter Marshall Committed by Commit Bot

[builtins] Add a fast path to construct TypedArrays from holey arrays.

For holey Smi and double source arrays, we would go to the general
case, which is much slower than before. We already check that there
are no prototype chain changes in IterableToListCanBeElided, and
there is no JS-code run between that check and the copying of the
elements, so we can safely check for the hole and convert it to
undefined, which is then converted to 0/NaN appropriately for the
given TypedArray.

Bug: chromium:713570,chromium:711275
Change-Id: I5b21c915907d71eebb73b7b1eea8eb58b4a5436d
Reviewed-on: https://chromium-review.googlesource.com/485520
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44899}
parent 397ebb76
......@@ -3177,15 +3177,41 @@ class TypedElementsAccessor
}
}
static bool HoleyPrototypeLookupRequired(Isolate* isolate,
Handle<JSArray> source) {
Object* source_proto = source->map()->prototype();
// Null prototypes are OK - we don't need to do prototype chain lookups on
// them.
if (source_proto->IsNull(isolate)) return false;
if (source_proto->IsJSProxy()) return true;
DCHECK(source_proto->IsJSObject());
if (!isolate->is_initial_array_prototype(JSObject::cast(source_proto))) {
return true;
}
return !isolate->IsFastArrayConstructorPrototypeChainIntact();
}
static bool TryCopyElementsHandleFastNumber(Handle<JSArray> source,
Handle<JSTypedArray> destination,
size_t length) {
Isolate* isolate = source->GetIsolate();
DisallowHeapAllocation no_gc;
DisallowJavascriptExecution no_js(isolate);
ElementsKind kind = source->GetElementsKind();
BackingStore* dest = BackingStore::cast(destination->elements());
// When we find the hole, we normally have to look up the element on the
// prototype chain, which is not handled here and we return false instead.
// When the array has the original array prototype, and that prototype has
// not been changed in a way that would affect lookups, we can just convert
// the hole into undefined.
if (HoleyPrototypeLookupRequired(isolate, source)) return false;
Object* undefined = isolate->heap()->undefined_value();
// Fastpath for packed Smi kind.
if (kind == FAST_SMI_ELEMENTS) {
// Fastpath for packed Smi kind.
FixedArray* source_store = FixedArray::cast(source->elements());
for (uint32_t i = 0; i < length; i++) {
......@@ -3195,6 +3221,19 @@ class TypedElementsAccessor
dest->set(i, dest->from(int_value));
}
return true;
} else if (kind == FAST_HOLEY_SMI_ELEMENTS) {
FixedArray* source_store = FixedArray::cast(source->elements());
for (uint32_t i = 0; i < length; i++) {
if (source_store->is_the_hole(isolate, i)) {
dest->SetValue(i, undefined);
} else {
Object* elem = source_store->get(i);
DCHECK(elem->IsSmi());
int int_value = Smi::cast(elem)->value();
dest->set(i, dest->from(int_value));
}
}
return true;
} else if (kind == FAST_DOUBLE_ELEMENTS) {
// Fastpath for packed double kind. We avoid boxing and then immediately
// unboxing the double here by using get_scalar.
......@@ -3208,6 +3247,18 @@ class TypedElementsAccessor
dest->set(i, dest->from(elem));
}
return true;
} else if (kind == FAST_HOLEY_DOUBLE_ELEMENTS) {
FixedDoubleArray* source_store =
FixedDoubleArray::cast(source->elements());
for (uint32_t i = 0; i < length; i++) {
if (source_store->is_the_hole(i)) {
dest->SetValue(i, undefined);
} else {
double elem = source_store->get_scalar(i);
dest->set(i, dest->from(elem));
}
}
return true;
}
return false;
}
......
// Copyright 2017 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.
// Flags: --allow-natives-syntax
(function() {
var arr = [0, 1, , 3];
Array.prototype[2] = 2;
var constructors = [
Uint8Array,
Int8Array,
Uint16Array,
Int16Array,
Uint32Array,
Int32Array,
Float32Array,
Float64Array,
Uint8ClampedArray
];
for (var constr of constructors) {
var ta = new constr(arr);
assertArrayEquals([0, 1, 2, 3], ta);
}
})();
(function testTypedArrayConstructByArrayLikeInvalidArrayProtector() {
Array.prototype[2] = undefined;
load("test/mjsunit/es6/typedarray-construct-by-array-like.js");
})();
......@@ -4,7 +4,11 @@
// Flags: --allow-natives-syntax
function TestConstructSmallObject(constr) {
var tests = [];
// Tests that will be called with each TypedArray constructor.
tests.push(function TestConstructSmallObject(constr) {
var myObject = { 0: 5, 1: 6, length: 2 };
arr = new constr(myObject);
......@@ -12,9 +16,9 @@ function TestConstructSmallObject(constr) {
assertEquals(2, arr.length);
assertEquals(5, arr[0]);
assertEquals(6, arr[1]);
};
});
function TestConstructLargeObject(constr) {
tests.push(function TestConstructLargeObject(constr) {
var myObject = {};
const n = 128;
for (var i = 0; i < n; i++) {
......@@ -28,18 +32,18 @@ function TestConstructLargeObject(constr) {
for (var i = 0; i < n; i++) {
assertEquals(i, arr[i]);
}
}
});
function TestConstructFromArrayWithSideEffects(constr) {
tests.push(function TestConstructFromArrayWithSideEffects(constr) {
var arr = [{ valueOf() { arr[1] = 20; return 1; }}, 2];
var ta = new constr(arr);
assertEquals(1, ta[0]);
assertEquals(2, ta[1]);
}
});
function TestConstructFromArrayWithSideEffectsHoley(constr) {
tests.push(function TestConstructFromArrayWithSideEffectsHoley(constr) {
var arr = [{ valueOf() { arr[1] = 20; return 1; }}, 2, , 4];
var ta = new constr(arr);
......@@ -48,9 +52,60 @@ function TestConstructFromArrayWithSideEffectsHoley(constr) {
assertEquals(2, ta[1]);
// ta[2] will be the default value, but we aren't testing that here.
assertEquals(4, ta[3]);
}
});
tests.push(function TestConstructFromArrayHoleySmi(constr) {
var arr = [0, 1, , 3];
var ta = new constr(arr);
assertArrayEquals([0, 1, defaultValue(constr), 3], ta);
});
tests.push(function TestConstructFromArrayHoleyDouble(constr) {
var arr = [0.0, 1.0, , 3.0];
var ta = new constr(arr);
assertArrayEquals([0, 1, defaultValue(constr), 3], ta);
});
tests.push(function TestConstructFromArrayHoleySmiWithOtherPrototype(constr) {
var arr = [0, 1, , 3];
Object.setPrototypeOf(arr, { 2: 2 });
var ta = new constr(arr);
function TestConstructFromArrayNoIteratorWithGetter(constr) {
assertArrayEquals([0, 1, 2, 3], ta);
});
tests.push(function TestConstructFromArrayWithProxyPrototype(constr) {
var arr = [0, 1, , 3];
var proxy = new Proxy([], {
get: function(target, name) {
if (name === Symbol.iterator) return undefined;
if (name == 2) return 2;
return target[name];
}
});
Object.setPrototypeOf(arr, proxy);
var ta = new constr(arr);
assertArrayEquals([0, 1, 2, 3], ta);
});
tests.push(function TestConstructFromArrayHoleySmiWithSubclass(constr) {
class SubArray extends Array {}
var arr = new SubArray(0, 1);
arr[3] = 3;
var ta = new constr(arr);
assertArrayEquals([0, 1, defaultValue(constr), 3], ta);
});
tests.push(function TestConstructFromArrayNoIteratorWithGetter(constr) {
var arr = [1, 2, 3];
arr[Symbol.iterator] = undefined;
......@@ -63,9 +118,9 @@ function TestConstructFromArrayNoIteratorWithGetter(constr) {
var ta = new constr(arr);
assertArrayEquals([1, 2, 22], ta);
}
});
function TestConstructFromArray(constr) {
tests.push(function TestConstructFromArray(constr) {
var n = 64;
var jsArray = [];
for (var i = 0; i < n; i++) {
......@@ -78,9 +133,9 @@ function TestConstructFromArray(constr) {
for (var i = 0; i < n; i++) {
assertEquals(i, arr[i]);
}
}
});
function TestConstructFromTypedArray(constr) {
tests.push(function TestConstructFromTypedArray(constr) {
var n = 64;
var ta = new constr(n);
for (var i = 0; i < ta.length; i++) {
......@@ -93,84 +148,55 @@ function TestConstructFromTypedArray(constr) {
for (var i = 0; i < n; i++) {
assertEquals(i, arr[i]);
}
}
(function TestUint8ClampedIsNotBitCopied() {
var arr = new Int8Array([-1.0, 0, 1.1, 255, 256]);
assertArrayEquals([-1, 0, 1, -1, 0], arr);
var expected = new Uint8ClampedArray([0, 0, 1, 0, 0]);
var converted = new Uint8ClampedArray(arr);
assertArrayEquals([0, 0, 1, 0, 0], converted);
})();
(function TestInt8ArrayCopying() {
var source = new Uint8Array([0, 1, 127, 128, 255, 256]);
assertArrayEquals([0, 1, 127, 128, 255, 0], source);
var converted = new Int8Array(source);
assertArrayEquals([0, 1, 127, -128, -1, 0], converted);
})();
(function TestInt16ArrayCopying() {
var source = new Uint16Array([0, 1, 32767, 32768, 65535, 65536]);
assertArrayEquals([0, 1, 32767, 32768, 65535, 0], source);
var converted = new Int16Array(source);
assertArrayEquals([0, 1, 32767, -32768, -1, 0], converted);
})();
(function TestInt32ArrayCopying() {
var source =
new Uint32Array([0, 1, 2147483647, 2147483648, 4294967295, 4294967296]);
assertArrayEquals([0, 1, 2147483647, 2147483648, 4294967295, 0], source);
});
var converted = new Int32Array(source);
assertArrayEquals([0, 1, 2147483647, -2147483648, -1, 0], converted);
})();
function TestLengthIsMaxSmi(constr) {
tests.push(function TestLengthIsMaxSmi(constr) {
var myObject = { 0: 5, 1: 6, length: %_MaxSmi() + 1 };
assertThrows(function() {
new constr(myObject);
}, RangeError);
}
});
function TestProxyHoleConverted(constr) {
tests.push(function TestProxyHoleConverted(constr) {
var source = {0: 0, 2: 2, length: 3};
var proxy = new Proxy(source, {});
var converted = new constr(proxy);
assertArrayEquals([0, defaultValue(constr), 2], converted);
}
});
function TestProxyToObjectValueOfCalled(constr) {
tests.push(function TestProxyToObjectValueOfCalled(constr) {
var thrower = { valueOf: function() { throw new TypeError(); } };
var source = {0: 0, 1: thrower, length: 2};
var proxy = new Proxy(source, {});
assertThrows(() => new constr(proxy), TypeError);
}
});
function TestObjectValueOfCalled(constr) {
tests.push(function TestObjectValueOfCalled(constr) {
var thrower = { valueOf: function() { throw new TypeError(); } };
var obj = {0: 0, 1: thrower, length: 2};
assertThrows(() => new constr(obj), TypeError);
}
});
function TestSmiPackedArray(constr) {
tests.push(function TestSmiPackedArray(constr) {
var ta = new constr([1, 2, 3, 4, 127]);
assertEquals(5 * constr.BYTES_PER_ELEMENT, ta.byteLength);
assertArrayEquals([1, 2, 3, 4, 127], ta);
}
});
tests.push(function TestOffsetIsUsed(constr) {
TestOffsetIsUsedRunner(constr, 4);
TestOffsetIsUsedRunner(constr, 16);
TestOffsetIsUsedRunner(constr, 32);
TestOffsetIsUsedRunner(constr, 128);
});
// Helpers for above tests.
function TestOffsetIsUsedRunner(constr, n) {
var buffer = new ArrayBuffer(constr.BYTES_PER_ELEMENT * n);
......@@ -192,32 +218,14 @@ function TestOffsetIsUsedRunner(constr, n) {
}
}
function TestOffsetIsUsed(constr, n) {
TestOffsetIsUsedRunner(constr, 4);
TestOffsetIsUsedRunner(constr, 16);
TestOffsetIsUsedRunner(constr, 32);
TestOffsetIsUsedRunner(constr, 128);
}
Test(TestConstructSmallObject);
Test(TestConstructLargeObject);
Test(TestConstructFromArrayWithSideEffects);
Test(TestConstructFromArrayWithSideEffectsHoley);
Test(TestConstructFromArrayNoIteratorWithGetter);
Test(TestConstructFromArray);
Test(TestConstructFromTypedArray);
Test(TestLengthIsMaxSmi);
Test(TestProxyHoleConverted);
Test(TestProxyToObjectValueOfCalled);
Test(TestObjectValueOfCalled);
Test(TestSmiPackedArray);
Test(TestOffsetIsUsed);
function defaultValue(constr) {
if (constr == Float32Array || constr == Float64Array) return NaN;
return 0;
}
tests.forEach(f => Test(f));
function Test(func) {
func(Uint8Array);
func(Int8Array);
......@@ -229,3 +237,43 @@ function Test(func) {
func(Float64Array);
func(Uint8ClampedArray);
}
// Other, standalone tests.
(function TestUint8ClampedIsNotBitCopied() {
var arr = new Int8Array([-1.0, 0, 1.1, 255, 256]);
assertArrayEquals([-1, 0, 1, -1, 0], arr);
var expected = new Uint8ClampedArray([0, 0, 1, 0, 0]);
var converted = new Uint8ClampedArray(arr);
assertArrayEquals([0, 0, 1, 0, 0], converted);
})();
(function TestInt8ArrayCopying() {
var source = new Uint8Array([0, 1, 127, 128, 255, 256]);
assertArrayEquals([0, 1, 127, 128, 255, 0], source);
var converted = new Int8Array(source);
assertArrayEquals([0, 1, 127, -128, -1, 0], converted);
})();
(function TestInt16ArrayCopying() {
var source = new Uint16Array([0, 1, 32767, 32768, 65535, 65536]);
assertArrayEquals([0, 1, 32767, 32768, 65535, 0], source);
var converted = new Int16Array(source);
assertArrayEquals([0, 1, 32767, -32768, -1, 0], converted);
})();
(function TestInt32ArrayCopying() {
var source =
new Uint32Array([0, 1, 2147483647, 2147483648, 4294967295, 4294967296]);
assertArrayEquals([0, 1, 2147483647, 2147483648, 4294967295, 0], source);
var converted = new Int32Array(source);
assertArrayEquals([0, 1, 2147483647, -2147483648, -1, 0], converted);
})();
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