Commit dc302c74 authored by caitp's avatar caitp Committed by Commit bot

Reland [typedarrays] move %TypedArray%.prototype.copyWithin to C++

- Removes shared InnerArrayCopyWithin JS builtin from src/js/array.js
- Implements %TypedArray%.prototype.copyWithin as a C++ builtin, which
relies on std::memmove rather than accessing individual eleements.
- Fixes the case where copyWithin is invoked on a TypedArray with a
detached buffer.
- Add tests to ensure that +/-Infinity (for all 3 parameters) is handled
  correctly by the
algorithm

The C++ version gets through the benchmark more than 25000 times as
quickly as the JS implementation.

BUG=v8:5925, v8:5929, v8:4648
R=cbruni@chromium.org, adamk@chromium.org, littledan@chromium.org

Review-Url: https://codereview.chromium.org/2697593002
Cr-Commit-Position: refs/heads/master@{#43213}
parent 70791d0c
......@@ -2483,6 +2483,10 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
values->shared()->set_builtin_function_id(kTypedArrayValues);
JSObject::AddProperty(prototype, factory->iterator_symbol(), values,
DONT_ENUM);
// TODO(caitp): alphasort accessors/methods
SimpleInstallFunction(prototype, "copyWithin",
Builtins::kTypedArrayPrototypeCopyWithin, 2, false);
}
{ // -- T y p e d A r r a y s
......
......@@ -167,5 +167,104 @@ void Builtins::Generate_TypedArrayPrototypeKeys(
state, "%TypedArray%.prototype.keys()");
}
namespace {
MaybeHandle<JSTypedArray> ValidateTypedArray(Isolate* isolate,
Handle<Object> receiver,
const char* method_name) {
if (V8_UNLIKELY(!receiver->IsJSTypedArray())) {
const MessageTemplate::Template message = MessageTemplate::kNotTypedArray;
THROW_NEW_ERROR(isolate, NewTypeError(message), JSTypedArray);
}
// TODO(caitp): throw if array.[[ViewedArrayBuffer]] is neutered (per v8:4648)
return Handle<JSTypedArray>::cast(receiver);
}
int64_t CapRelativeIndex(Handle<Object> num, int64_t minimum, int64_t maximum) {
int64_t relative;
if (V8_LIKELY(num->IsSmi())) {
relative = Smi::cast(*num)->value();
} else {
DCHECK(num->IsHeapNumber());
double fp = HeapNumber::cast(*num)->value();
if (V8_UNLIKELY(!std::isfinite(fp))) {
// +Infinity / -Infinity
DCHECK(!std::isnan(fp));
return fp < 0 ? minimum : maximum;
}
relative = static_cast<int64_t>(fp);
}
return relative < 0 ? std::max<int64_t>(relative + maximum, minimum)
: std::min<int64_t>(relative, maximum);
}
} // namespace
BUILTIN(TypedArrayPrototypeCopyWithin) {
HandleScope scope(isolate);
Handle<JSTypedArray> array;
const char* method = "%TypedArray%.prototype.copyWithin";
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, array, ValidateTypedArray(isolate, args.receiver(), method));
if (V8_UNLIKELY(array->WasNeutered())) return *array;
int64_t len = array->length_value();
int64_t to = 0;
int64_t from = 0;
int64_t final = len;
if (V8_LIKELY(args.length() > 1)) {
Handle<Object> num;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, num, Object::ToInteger(isolate, args.at<Object>(1)));
to = CapRelativeIndex(num, 0, len);
if (args.length() > 2) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
isolate, num, Object::ToInteger(isolate, args.at<Object>(2)));
from = CapRelativeIndex(num, 0, len);
Handle<Object> end = args.atOrUndefined(isolate, 3);
if (!end->IsUndefined(isolate)) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, num,
Object::ToInteger(isolate, end));
final = CapRelativeIndex(num, 0, len);
}
}
}
int64_t count = std::min<int64_t>(final - from, len - to);
if (count <= 0) return *array;
// TypedArray buffer may have been transferred/detached during parameter
// processing above. Return early in this case, to prevent potential UAF error
// TODO(caitp): throw here, as though the full algorithm were performed (the
// throw would have come from ecma262/#sec-integerindexedelementget)
// (see )
if (V8_UNLIKELY(array->WasNeutered())) return *array;
// Ensure processed indexes are within array bounds
DCHECK_GE(from, 0);
DCHECK_LT(from, len);
DCHECK_GE(to, 0);
DCHECK_LT(to, len);
DCHECK_GE(len - count, 0);
Handle<FixedTypedArrayBase> elements(
FixedTypedArrayBase::cast(array->elements()));
size_t element_size = array->element_size();
to = to * element_size;
from = from * element_size;
count = count * element_size;
uint8_t* data = static_cast<uint8_t*>(elements->DataPtr());
std::memmove(data + to, data + from, count);
return *array;
}
} // namespace internal
} // namespace v8
......@@ -806,7 +806,9 @@ class Isolate;
/* ES6 #sec-%typedarray%.prototype.keys */ \
TFJ(TypedArrayPrototypeKeys, 0) \
/* ES6 #sec-%typedarray%.prototype.values */ \
TFJ(TypedArrayPrototypeValues, 0)
TFJ(TypedArrayPrototypeValues, 0) \
/* ES6 #sec-%typedarray%.prototype.copywithin */ \
CPP(TypedArrayPrototypeCopyWithin)
#define IGNORE_BUILTIN(...)
......
......@@ -1270,7 +1270,14 @@ function ArrayReduceRight(callback, current) {
}
function InnerArrayCopyWithin(target, start, end, array, length) {
// ES#sec-array.prototype.copywithin
// (Array.prototype.copyWithin ( target, start [ , end ] )
function ArrayCopyWithin(target, start, end) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.copyWithin");
var array = TO_OBJECT(this);
var length = TO_LENGTH(array.length);
target = TO_INTEGER(target);
var to;
if (target < 0) {
......@@ -1318,17 +1325,6 @@ function InnerArrayCopyWithin(target, start, end, array, length) {
}
// ES6 draft 03-17-15, section 22.1.3.3
function ArrayCopyWithin(target, start, end) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.copyWithin");
var array = TO_OBJECT(this);
var length = TO_LENGTH(array.length);
return InnerArrayCopyWithin(target, start, end, array, length);
}
function InnerArrayFind(predicate, thisArg, array, length) {
if (!IS_CALLABLE(predicate)) {
throw %make_type_error(kCalledNonCallable, predicate);
......@@ -1625,7 +1621,6 @@ utils.Export(function(to) {
to.ArrayPush = ArrayPush;
to.ArrayToString = ArrayToString;
to.ArrayValues = IteratorFunctions.values,
to.InnerArrayCopyWithin = InnerArrayCopyWithin;
to.InnerArrayEvery = InnerArrayEvery;
to.InnerArrayFill = InnerArrayFill;
to.InnerArrayFilter = InnerArrayFilter;
......
......@@ -20,7 +20,6 @@ var GlobalArray = global.Array;
var GlobalArrayBuffer = global.ArrayBuffer;
var GlobalArrayBufferPrototype = GlobalArrayBuffer.prototype;
var GlobalObject = global.Object;
var InnerArrayCopyWithin;
var InnerArrayEvery;
var InnerArrayFill;
var InnerArrayFilter;
......@@ -68,7 +67,6 @@ utils.Import(function(from) {
ArrayValues = from.ArrayValues;
GetIterator = from.GetIterator;
GetMethod = from.GetMethod;
InnerArrayCopyWithin = from.InnerArrayCopyWithin;
InnerArrayEvery = from.InnerArrayEvery;
InnerArrayFill = from.InnerArrayFill;
InnerArrayFilter = from.InnerArrayFilter;
......@@ -437,17 +435,6 @@ function TypedArrayGetToStringTag() {
}
function TypedArrayCopyWithin(target, start, end) {
if (!IS_TYPEDARRAY(this)) throw %make_type_error(kNotTypedArray);
var length = %_TypedArrayGetLength(this);
// TODO(littledan): Replace with a memcpy for better performance
return InnerArrayCopyWithin(target, start, end, this, length);
}
%FunctionSetLength(TypedArrayCopyWithin, 2);
// ES6 draft 05-05-15, section 22.2.3.7
function TypedArrayEvery(f, receiver) {
if (!IS_TYPEDARRAY(this)) throw %make_type_error(kNotTypedArray);
......@@ -857,7 +844,6 @@ utils.InstallGetter(GlobalTypedArray.prototype, toStringTagSymbol,
utils.InstallFunctions(GlobalTypedArray.prototype, DONT_ENUM, [
"subarray", TypedArraySubArray,
"set", TypedArraySet,
"copyWithin", TypedArrayCopyWithin,
"every", TypedArrayEvery,
"fill", TypedArrayFill,
"filter", TypedArrayFilter,
......
// 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
var buf = new ArrayBuffer(0x10000);
var arr = new Uint8Array(buf).fill(55);
var tmp = {};
tmp[Symbol.toPrimitive] = function () {
%ArrayBufferNeuter(arr.buffer);
return 50;
}
arr.copyWithin(tmp);
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
var typedArrayConstructors = [
Uint8Array,
Int8Array,
......@@ -171,3 +173,75 @@ CheckEachTypedArray(function copyWithinNullEnd(constructor) {
assertArrayEquals([1, 2, 3, 4, 5],
new constructor([1, 2, 3, 4, 5]).copyWithin(0, 3, null));
});
CheckEachTypedArray(function copyWithinMinusInfinityTarget(constructor) {
var arr = new constructor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
var expected = [6, 7, 8, 9, 10, 6, 7, 8, 9, 10];
assertArrayEquals(expected, arr.copyWithin(-Infinity, 5));
assertEquals(10, arr.length);
});
CheckEachTypedArray(function copyWithinPositiveInfinityTarget(constructor) {
var arr = new constructor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
var expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
assertArrayEquals(expected, arr.copyWithin(+Infinity, 5));
assertEquals(10, arr.length);
});
CheckEachTypedArray(function copyWithinMinusInfinityStart(constructor) {
var arr = new constructor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
var expected = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
assertArrayEquals(expected, arr.copyWithin(5, -Infinity));
assertEquals(10, arr.length);
});
CheckEachTypedArray(function copyWithinPositiveInfinityStart(constructor) {
var arr = new constructor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
var expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
assertArrayEquals(expected, arr.copyWithin(5, +Infinity));
assertEquals(10, arr.length);
});
CheckEachTypedArray(function copyWithinMinusInfinityEnd(constructor) {
var arr = new constructor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
var expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
assertArrayEquals(expected, arr.copyWithin(5, 0, -Infinity));
assertEquals(10, arr.length);
});
CheckEachTypedArray(function copyWithinPositiveInfinityEnd(constructor) {
var arr = new constructor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
var expected = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5];
assertArrayEquals(expected, arr.copyWithin(5, 0, +Infinity));
assertEquals(10, arr.length);
});
CheckEachTypedArray(function parametersNotCalledIfDetached(constructor) {
var tmp = {
[Symbol.toPrimitive]() {
assertUnreachable("Parameter should not be processed when " +
"array.[[ViewedArrayBuffer]] is neutered.");
return 0;
}
};
var array = new constructor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
%ArrayBufferNeuter(array.buffer);
// TODO(caitp): this should throw due to being invoked on a TypedArray with a
// detached buffer (per v8:4648).
array.copyWithin(tmp, tmp, tmp);
assertEquals(0, array.length, "array.[[ViewedArrayBuffer]] is detached");
});
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