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}
This commit is contained in:
parent
70791d0cc3
commit
dc302c74be
@ -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,
|
||||
|
14
test/mjsunit/es6/regress/regress-5929-1.js
Normal file
14
test/mjsunit/es6/regress/regress-5929-1.js
Normal file
@ -0,0 +1,14 @@
|
||||
// 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");
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user