Commit 356e9246 authored by Peter Marshall's avatar Peter Marshall Committed by Commit Bot

[builtins] Use the ElementsAccessor to copy TypedArrays.

This includes a fastpath in the ElementsAccessor for the source
array being a JSArray with FastSmi or FastDouble packed kinds. This
is probably a pretty common usage, where an array is passed in as
a way of initializing the TypedArray at creation (as there is not other
syntax to do this). e.g. new Float64Array([1.0, 1.0, 1.0]) for some
sort of vector application.

BUG= v8:5977

Change-Id: Ice4ad9fc29f56b1c4b0b30736a1330efdc289003
Reviewed-on: https://chromium-review.googlesource.com/465126Reviewed-by: 's avatarCamillo Bruni <cbruni@chromium.org>
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44722}
parent 3e721358
......@@ -568,15 +568,12 @@ TF_BUILTIN(TypedArrayConstructByArrayLike, TypedArrayBuiltinsAssembler) {
Node* source_kind = LoadMapElementsKind(LoadMap(array_like));
GotoIf(Word32Equal(holder_kind, source_kind), &fast_copy);
// Call to JS to copy the contents of the array in.
Callable callable = CodeFactory::Call(isolate());
Node* copy_array_contents = LoadContextElement(
LoadNativeContext(context), Context::TYPED_ARRAY_SET_FROM_ARRAY_LIKE);
CallJS(callable, context, copy_array_contents, UndefinedConstant(), holder,
array_like, length, SmiConstant(0));
// Copy using the elements accessor.
CallRuntime(Runtime::kTypedArrayCopyElements, context, holder, array_like,
length);
Return(UndefinedConstant());
BIND(&fast_copy);
Bind(&fast_copy);
{
Node* holder_data_ptr = LoadDataPtr(holder);
Node* source_data_ptr = LoadDataPtr(array_like);
......
......@@ -74,8 +74,6 @@ enum ContextLookupFlags {
V(TYPED_ARRAY_CONSTRUCT_BY_LENGTH_INDEX, JSFunction, \
typed_array_construct_by_length) \
V(TYPED_ARRAY_INITIALIZE_INDEX, JSFunction, typed_array_initialize) \
V(TYPED_ARRAY_SET_FROM_ARRAY_LIKE, JSFunction, \
typed_array_set_from_array_like) \
V(MATH_FLOOR_INDEX, JSFunction, math_floor) \
V(MATH_POW_INDEX, JSFunction, math_pow) \
V(NEW_PROMISE_CAPABILITY_INDEX, JSFunction, new_promise_capability) \
......
......@@ -1002,6 +1002,18 @@ class ElementsAccessorBase : public ElementsAccessor {
kPackedSizeNotKnown, size);
}
Object* CopyElements(Handle<JSReceiver> source, Handle<JSObject> destination,
size_t length) final {
return Subclass::CopyElementsHandleImpl(source, destination, length);
}
static Object* CopyElementsHandleImpl(Handle<JSReceiver> source,
Handle<JSObject> destination,
size_t length) {
UNREACHABLE();
return *source;
}
Handle<SeededNumberDictionary> Normalize(Handle<JSObject> object) final {
return Subclass::NormalizeImpl(object, handle(object->elements()));
}
......@@ -2865,10 +2877,10 @@ class TypedElementsAccessor
ctype value;
if (obj_value->IsSmi()) {
value = BackingStore::from_int(Smi::cast(*obj_value)->value());
value = BackingStore::from(Smi::cast(*obj_value)->value());
} else {
DCHECK(obj_value->IsHeapNumber());
value = BackingStore::from_double(HeapNumber::cast(*obj_value)->value());
value = BackingStore::from(HeapNumber::cast(*obj_value)->value());
}
// Ensure indexes are within array bounds
......@@ -3088,6 +3100,164 @@ class TypedElementsAccessor
}
return result_array;
}
static bool HasSimpleRepresentation(InstanceType type) {
return !(type == FIXED_FLOAT32_ARRAY_TYPE ||
type == FIXED_FLOAT64_ARRAY_TYPE ||
type == FIXED_UINT8_CLAMPED_ARRAY_TYPE);
}
template <typename SourceTraits>
static void CopyBetweenBackingStores(FixedTypedArrayBase* source,
BackingStore* dest, size_t length) {
FixedTypedArray<SourceTraits>* source_fta =
FixedTypedArray<SourceTraits>::cast(source);
for (uint32_t i = 0; i < length; i++) {
typename SourceTraits::ElementType elem = source_fta->get_scalar(i);
dest->set(i, dest->from(elem));
}
}
static void CopyElementsHandleFromTypedArray(Handle<JSTypedArray> source,
Handle<JSTypedArray> destination,
size_t length) {
// The source is a typed array, so we know we don't need to do ToNumber
// side-effects, as the source elements will always be a number or
// undefined.
DisallowHeapAllocation no_gc;
Handle<FixedTypedArrayBase> source_elements(
FixedTypedArrayBase::cast(source->elements()));
Handle<BackingStore> destination_elements(
BackingStore::cast(destination->elements()));
DCHECK_EQ(source->length(), destination->length());
DCHECK(source->length()->IsSmi());
DCHECK_EQ(Smi::FromInt(static_cast<int>(length)), source->length());
InstanceType source_type = source_elements->map()->instance_type();
InstanceType destination_type =
destination_elements->map()->instance_type();
bool same_type = source_type == destination_type;
bool same_size = FixedTypedArrayBase::ElementSize(source_type) ==
FixedTypedArrayBase::ElementSize(destination_type);
bool both_are_simple = HasSimpleRepresentation(source_type) &&
HasSimpleRepresentation(destination_type);
// We can simply copy the backing store if the types are the same, or if
// we are converting e.g. Uint8 <-> Int8, as the binary representation
// will be the same. This is not the case for floats or clamped Uint8,
// which have special conversion operations.
if (same_type || (same_size && both_are_simple)) {
// We assume the source and destination don't overlap. This is always true
// for newly allocated TypedArrays.
CHECK_NE(source->buffer(), destination->buffer());
int element_size = FixedTypedArrayBase::ElementSize(source_type);
uint8_t* source_data = static_cast<uint8_t*>(source_elements->DataPtr());
uint8_t* destination_data =
static_cast<uint8_t*>(destination_elements->DataPtr());
std::memcpy(destination_data, source_data, length * element_size);
} else {
// We use scalar accessors below to avoid boxing/unboxing, so there are
// no allocations.
switch (source->GetElementsKind()) {
#define TYPED_ARRAY_CASE(Type, type, TYPE, ctype, size) \
case TYPE##_ELEMENTS: \
CopyBetweenBackingStores<Type##ArrayTraits>( \
*source_elements, *destination_elements, length); \
break;
TYPED_ARRAYS(TYPED_ARRAY_CASE)
default:
UNREACHABLE();
break;
}
#undef TYPED_ARRAY_CASE
}
}
static bool TryCopyElementsHandleFastNumber(Handle<JSArray> source,
Handle<JSTypedArray> destination,
size_t length) {
DisallowHeapAllocation no_gc;
ElementsKind kind = source->GetElementsKind();
BackingStore* dest = BackingStore::cast(destination->elements());
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++) {
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.
FixedDoubleArray* source_store =
FixedDoubleArray::cast(source->elements());
for (uint32_t i = 0; i < length; i++) {
// Use the from_double conversion for this specific TypedArray type,
// rather than relying on C++ to convert elem.
double elem = source_store->get_scalar(i);
dest->set(i, dest->from(elem));
}
return true;
}
return false;
}
static Object* CopyElementsHandleSlow(Handle<JSReceiver> source,
Handle<JSTypedArray> destination,
size_t length) {
Isolate* isolate = source->GetIsolate();
Handle<BackingStore> destination_elements(
BackingStore::cast(destination->elements()));
for (uint32_t i = 0; i < length; i++) {
LookupIterator it(isolate, source, i, source);
Handle<Object> elem;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, elem,
Object::GetProperty(&it));
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, elem, Object::ToNumber(elem));
// We don't need to check for buffer neutering here, because the
// source cannot be a TypedArray.
// The spec says we store the length, then get each element, so we don't
// need to check changes to length.
destination_elements->SetValue(i, *elem);
}
return Smi::kZero;
}
static Object* CopyElementsHandleImpl(Handle<JSReceiver> source,
Handle<JSObject> destination,
size_t length) {
Handle<JSTypedArray> destination_ta =
Handle<JSTypedArray>::cast(destination);
// All conversions from TypedArrays can be done without allocation.
if (source->IsJSTypedArray()) {
Handle<JSTypedArray> source_ta = Handle<JSTypedArray>::cast(source);
CopyElementsHandleFromTypedArray(source_ta, destination_ta, length);
return Smi::kZero;
}
// Fast cases for packed numbers kinds where we don't need to allocate.
if (source->IsJSArray()) {
Handle<JSArray> source_array = Handle<JSArray>::cast(source);
if (TryCopyElementsHandleFastNumber(source_array, destination_ta,
length)) {
return Smi::kZero;
}
}
// Final generic case that handles prototype chain lookups, getters, proxies
// and observable side effects via valueOf, etc.
return CopyElementsHandleSlow(source, destination_ta, length);
}
};
#define FIXED_ELEMENTS_ACCESSOR(Type, type, TYPE, ctype, size) \
......
......@@ -188,6 +188,9 @@ class ElementsAccessor {
ElementsKind source_kind,
Handle<FixedArrayBase> destination, int size) = 0;
virtual Object* CopyElements(Handle<JSReceiver> source,
Handle<JSObject> destination, size_t length) = 0;
virtual Handle<FixedArray> CreateListFromArray(Isolate* isolate,
Handle<JSArray> array) = 0;
......
......@@ -249,9 +249,6 @@ function TypedArraySetFromArrayLike(target, source, sourceLength, offset) {
}
}
%InstallToContext([
'typed_array_set_from_array_like', TypedArraySetFromArrayLike]);
function TypedArraySetFromOverlappingTypedArray(target, source, offset) {
var sourceElementSize = source.BYTES_PER_ELEMENT;
var targetElementSize = target.BYTES_PER_ELEMENT;
......
......@@ -4195,45 +4195,51 @@ void FixedTypedArray<Traits>::set(int index, ElementType value) {
ptr[index] = value;
}
template <class Traits>
typename Traits::ElementType FixedTypedArray<Traits>::from_int(int value) {
typename Traits::ElementType FixedTypedArray<Traits>::from(int value) {
return static_cast<ElementType>(value);
}
template <> inline
uint8_t FixedTypedArray<Uint8ClampedArrayTraits>::from_int(int value) {
template <>
inline uint8_t FixedTypedArray<Uint8ClampedArrayTraits>::from(int value) {
if (value < 0) return 0;
if (value > 0xFF) return 0xFF;
return static_cast<uint8_t>(value);
}
template <class Traits>
typename Traits::ElementType FixedTypedArray<Traits>::from(uint32_t value) {
return static_cast<ElementType>(value);
}
template <>
inline uint8_t FixedTypedArray<Uint8ClampedArrayTraits>::from(uint32_t value) {
// We need this special case for Uint32 -> Uint8Clamped, because the highest
// Uint32 values will be negative as an int, clamping to 0, rather than 255.
if (value > 0xFF) return 0xFF;
return static_cast<uint8_t>(value);
}
template <class Traits>
typename Traits::ElementType FixedTypedArray<Traits>::from_double(
double value) {
typename Traits::ElementType FixedTypedArray<Traits>::from(double value) {
return static_cast<ElementType>(DoubleToInt32(value));
}
template<> inline
uint8_t FixedTypedArray<Uint8ClampedArrayTraits>::from_double(double value) {
template <>
inline uint8_t FixedTypedArray<Uint8ClampedArrayTraits>::from(double value) {
// Handle NaNs and less than zero values which clamp to zero.
if (!(value > 0)) return 0;
if (value > 0xFF) return 0xFF;
return static_cast<uint8_t>(lrint(value));
}
template<> inline
float FixedTypedArray<Float32ArrayTraits>::from_double(double value) {
template <>
inline float FixedTypedArray<Float32ArrayTraits>::from(double value) {
return static_cast<float>(value);
}
template<> inline
double FixedTypedArray<Float64ArrayTraits>::from_double(double value) {
template <>
inline double FixedTypedArray<Float64ArrayTraits>::from(double value) {
return value;
}
......@@ -4249,10 +4255,10 @@ void FixedTypedArray<Traits>::SetValue(uint32_t index, Object* value) {
ElementType cast_value = Traits::defaultValue();
if (value->IsSmi()) {
int int_value = Smi::cast(value)->value();
cast_value = from_int(int_value);
cast_value = from(int_value);
} else if (value->IsHeapNumber()) {
double double_value = HeapNumber::cast(value)->value();
cast_value = from_double(double_value);
cast_value = from(double_value);
} else {
// Clamp undefined to the default value. All other types have been
// converted to a number type further up in the call chain.
......
......@@ -3464,6 +3464,7 @@ class FixedTypedArrayBase: public FixedArrayBase {
static inline int TypedArraySize(InstanceType type, int length);
inline int TypedArraySize(InstanceType type);
static inline int ElementSize(InstanceType type);
// Use with care: returns raw pointer into heap.
inline void* DataPtr();
......@@ -3471,8 +3472,6 @@ class FixedTypedArrayBase: public FixedArrayBase {
inline int DataSize();
private:
static inline int ElementSize(InstanceType type);
inline int DataSize(InstanceType type);
DISALLOW_IMPLICIT_CONSTRUCTORS(FixedTypedArrayBase);
......@@ -3491,8 +3490,9 @@ class FixedTypedArray: public FixedTypedArrayBase {
static inline Handle<Object> get(FixedTypedArray* array, int index);
inline void set(int index, ElementType value);
static inline ElementType from_int(int value);
static inline ElementType from_double(double value);
static inline ElementType from(int value);
static inline ElementType from(uint32_t value);
static inline ElementType from(double value);
// This accessor applies the correct conversion from Smi, HeapNumber
// and undefined.
......
......@@ -50,6 +50,22 @@ RUNTIME_FUNCTION(Runtime_ArrayBufferNeuter) {
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_TypedArrayCopyElements) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, destination, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, source, 1);
CONVERT_NUMBER_ARG_HANDLE_CHECKED(length_obj, 2);
size_t length;
CHECK(TryNumberToSize(*length_obj, &length));
Handle<JSTypedArray> destination_ta = Handle<JSTypedArray>::cast(destination);
ElementsAccessor* accessor = destination_ta->GetElementsAccessor();
return accessor->CopyElements(source, destination, length);
}
#define BUFFER_VIEW_GETTER(Type, getter, accessor) \
RUNTIME_FUNCTION(Runtime_##Type##Get##getter) { \
HandleScope scope(isolate); \
......
......@@ -622,19 +622,20 @@ namespace internal {
F(WasmNumInterpretedCalls, 1, 1) \
F(RedirectToWasmInterpreter, 2, 1)
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
F(ArrayBufferGetByteLength, 1, 1) \
F(ArrayBufferNeuter, 1, 1) \
F(ArrayBufferViewGetByteLength, 1, 1) \
F(ArrayBufferViewGetByteOffset, 1, 1) \
F(TypedArrayGetLength, 1, 1) \
F(TypedArrayGetBuffer, 1, 1) \
F(TypedArraySetFastCases, 3, 1) \
F(TypedArraySortFast, 1, 1) \
F(TypedArrayMaxSizeInHeap, 0, 1) \
F(IsTypedArray, 1, 1) \
F(IsSharedTypedArray, 1, 1) \
F(IsSharedIntegerTypedArray, 1, 1) \
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
F(ArrayBufferGetByteLength, 1, 1) \
F(ArrayBufferNeuter, 1, 1) \
F(TypedArrayCopyElements, 3, 1) \
F(ArrayBufferViewGetByteLength, 1, 1) \
F(ArrayBufferViewGetByteOffset, 1, 1) \
F(TypedArrayGetLength, 1, 1) \
F(TypedArrayGetBuffer, 1, 1) \
F(TypedArraySetFastCases, 3, 1) \
F(TypedArraySortFast, 1, 1) \
F(TypedArrayMaxSizeInHeap, 0, 1) \
F(IsTypedArray, 1, 1) \
F(IsSharedTypedArray, 1, 1) \
F(IsSharedIntegerTypedArray, 1, 1) \
F(IsSharedInteger32TypedArray, 1, 1)
#define FOR_EACH_INTRINSIC_WASM(F) \
......
......@@ -50,6 +50,20 @@ function TestConstructFromArrayWithSideEffectsHoley(constr) {
assertEquals(4, ta[3]);
}
function TestConstructFromArrayNoIteratorWithGetter(constr) {
var arr = [1, 2, 3];
arr[Symbol.iterator] = undefined;
Object.defineProperty(arr, "2", {
get() {
return 22;
}
});
var ta = new constr(arr);
assertArrayEquals([1, 2, 22], ta);
}
function TestConstructFromArray(constr) {
var n = 64;
......@@ -81,6 +95,44 @@ function TestConstructFromTypedArray(constr) {
}
}
(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) {
var myObject = { 0: 5, 1: 6, length: %_MaxSmi() + 1 };
......@@ -89,6 +141,37 @@ function TestLengthIsMaxSmi(constr) {
}, RangeError);
}
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) {
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) {
var thrower = { valueOf: function() { throw new TypeError(); } };
var obj = {0: 0, 1: thrower, length: 2};
assertThrows(() => new constr(obj), TypeError);
}
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);
}
function TestOffsetIsUsedRunner(constr, n) {
var buffer = new ArrayBuffer(constr.BYTES_PER_ELEMENT * n);
......@@ -120,11 +203,21 @@ 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;
}
function Test(func) {
func(Uint8Array);
func(Int8Array);
......
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