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

[builtins] Copy array contents using JS in ConstructByArrayLike.

The last CL https://chromium-review.googlesource.com/c/456707/ caused
some pretty heavy performance regressions. After experimenting, it
seems the easiest and most straight-forward way to copy the elements
into the new typed array is to do it in JS.

Adds a fast path for typed arrays, where the source typed array has
the same elements kind, in which case we can just copy the backing
store using memcpy.

This CL also removes regression test 319120 which is from a pwn2own
vulnerability. The old code path enforced a maximum byte_length
that was too low, which this change removes. The length property of
the typed array must be a Smi, but the byte_length, which can be up
to 8x larger than length for a Float64Array, can be a heap number.

We can also re-use some of the logic from ConstructByLength when
deciding whether to allocate the buffer on- or off-heap, so that
is factored out into InitializeBasedOnLength. We can also re-use
the DoInitialize helper instead of calling into the runtime,
meaning we can remove InitializeFromArrayLike.

BUG=v8:5977,chromium:705503,chromium:705394

Change-Id: I63372652091d4bdf3a9491acef9b4e3ac793a755
Reviewed-on: https://chromium-review.googlesource.com/459621Reviewed-by: 's avatarBenedikt Meurer <bmeurer@chromium.org>
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44301}
parent 42f285fc
......@@ -1545,8 +1545,17 @@ ExternalReference ExternalReference::libc_memchr_function(Isolate* isolate) {
return ExternalReference(Redirect(isolate, FUNCTION_ADDR(libc_memchr)));
}
void* libc_memset(void* string, int character, size_t n) {
return memset(string, character, n);
void* libc_memcpy(void* dest, const void* src, size_t n) {
return memcpy(dest, src, n);
}
ExternalReference ExternalReference::libc_memcpy_function(Isolate* isolate) {
return ExternalReference(Redirect(isolate, FUNCTION_ADDR(libc_memcpy)));
}
void* libc_memset(void* dest, int byte, size_t n) {
DCHECK_EQ(static_cast<char>(byte), byte);
return memset(dest, byte, n);
}
ExternalReference ExternalReference::libc_memset_function(Isolate* isolate) {
......
......@@ -989,6 +989,7 @@ class ExternalReference BASE_EMBEDDED {
static ExternalReference ieee754_tanh_function(Isolate* isolate);
static ExternalReference libc_memchr_function(Isolate* isolate);
static ExternalReference libc_memcpy_function(Isolate* isolate);
static ExternalReference libc_memset_function(Isolate* isolate);
static ExternalReference page_flags(Page* page);
......@@ -1085,7 +1086,6 @@ V8_EXPORT_PRIVATE std::ostream& operator<<(std::ostream&, ExternalReference);
// -----------------------------------------------------------------------------
// Utility functions
void* libc_memchr(void* string, int character, size_t search_length);
inline int NumberOfBitsSet(uint32_t x) {
unsigned int num_bits_set;
......
This diff is collapsed.
......@@ -2741,7 +2741,7 @@ Node* CodeStubAssembler::ToThisString(Node* context, Node* value,
return var_value.value();
}
Node* CodeStubAssembler::ChangeNumberToFloat64(compiler::Node* value) {
Node* CodeStubAssembler::ChangeNumberToFloat64(Node* value) {
Variable result(this, MachineRepresentation::kFloat64);
Label smi(this);
Label done(this, &result);
......@@ -2760,6 +2760,23 @@ Node* CodeStubAssembler::ChangeNumberToFloat64(compiler::Node* value) {
return result.value();
}
Node* CodeStubAssembler::ChangeNumberToIntPtr(Node* value) {
Variable result(this, MachineType::PointerRepresentation());
Label smi(this), done(this, &result);
GotoIf(TaggedIsSmi(value), &smi);
CSA_ASSERT(this, IsHeapNumber(value));
result.Bind(ChangeFloat64ToUintPtr(LoadHeapNumberValue(value)));
Goto(&done);
Bind(&smi);
result.Bind(SmiToWord(value));
Goto(&done);
Bind(&done);
return result.value();
}
Node* CodeStubAssembler::ToThisValue(Node* context, Node* value,
PrimitiveType primitive_type,
char const* method_name) {
......@@ -3056,6 +3073,19 @@ Node* CodeStubAssembler::IsJSFunction(Node* object) {
return HasInstanceType(object, JS_FUNCTION_TYPE);
}
Node* CodeStubAssembler::IsJSTypedArray(Node* object) {
return HasInstanceType(object, JS_TYPED_ARRAY_TYPE);
}
Node* CodeStubAssembler::IsFixedTypedArray(Node* object) {
Node* instance_type = LoadInstanceType(object);
return Word32And(
Int32GreaterThanOrEqual(instance_type,
Int32Constant(FIRST_FIXED_TYPED_ARRAY_TYPE)),
Int32LessThanOrEqual(instance_type,
Int32Constant(LAST_FIXED_TYPED_ARRAY_TYPE)));
}
Node* CodeStubAssembler::StringCharCodeAt(Node* string, Node* index,
ParameterMode parameter_mode) {
CSA_ASSERT(this, IsString(string));
......
......@@ -676,6 +676,7 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* ChangeInt32ToTagged(Node* value);
Node* ChangeUint32ToTagged(Node* value);
Node* ChangeNumberToFloat64(Node* value);
Node* ChangeNumberToIntPtr(Node* value);
// Type conversions.
// Throws a TypeError for {method_name} if {value} is not coercible to Object,
......@@ -731,6 +732,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
Node* IsUnseededNumberDictionary(Node* object);
Node* IsConstructorMap(Node* map);
Node* IsJSFunction(Node* object);
Node* IsJSTypedArray(Node* object);
Node* IsFixedTypedArray(Node* object);
// ElementsKind helpers:
Node* IsFastElementsKind(Node* elements_kind);
......
......@@ -75,6 +75,8 @@ 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) \
......
......@@ -234,6 +234,8 @@ void ExternalReferenceTable::AddReferences(Isolate* isolate) {
"wasm::call_trap_callback_for_testing");
Add(ExternalReference::libc_memchr_function(isolate).address(),
"libc_memchr");
Add(ExternalReference::libc_memcpy_function(isolate).address(),
"libc_memcpy");
Add(ExternalReference::libc_memset_function(isolate).address(),
"libc_memset");
Add(ExternalReference::log_enter_external_function(isolate).address(),
......
......@@ -237,6 +237,9 @@ 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;
......
......@@ -4379,6 +4379,17 @@ class FixedTypedArrayBase: public FixedArrayBase {
static const int kDataOffset = kHeaderSize;
static const int kMaxElementSize = 8;
#ifdef V8_HOST_ARCH_32_BIT
static const size_t kMaxByteLength = std::numeric_limits<size_t>::max();
#else
static const size_t kMaxByteLength =
static_cast<size_t>(Smi::kMaxValue) * kMaxElementSize;
#endif // V8_HOST_ARCH_32_BIT
static const size_t kMaxLength = Smi::kMaxValue;
class BodyDescriptor;
inline int size();
......@@ -4426,17 +4437,18 @@ class FixedTypedArray: public FixedTypedArrayBase {
DISALLOW_IMPLICIT_CONSTRUCTORS(FixedTypedArray);
};
#define FIXED_TYPED_ARRAY_TRAITS(Type, type, TYPE, elementType, size) \
class Type##ArrayTraits { \
public: /* NOLINT */ \
typedef elementType ElementType; \
static const InstanceType kInstanceType = FIXED_##TYPE##_ARRAY_TYPE; \
static const char* Designator() { return #type " array"; } \
static inline Handle<Object> ToHandle(Isolate* isolate, \
elementType scalar); \
static inline elementType defaultValue(); \
}; \
\
#define FIXED_TYPED_ARRAY_TRAITS(Type, type, TYPE, elementType, size) \
STATIC_ASSERT(size <= FixedTypedArrayBase::kMaxElementSize); \
class Type##ArrayTraits { \
public: /* NOLINT */ \
typedef elementType ElementType; \
static const InstanceType kInstanceType = FIXED_##TYPE##_ARRAY_TYPE; \
static const char* Designator() { return #type " array"; } \
static inline Handle<Object> ToHandle(Isolate* isolate, \
elementType scalar); \
static inline elementType defaultValue(); \
}; \
\
typedef FixedTypedArray<Type##ArrayTraits> Fixed##Type##Array;
TYPED_ARRAYS(FIXED_TYPED_ARRAY_TRAITS)
......
......@@ -42,118 +42,6 @@ RUNTIME_FUNCTION(Runtime_ArrayBufferNeuter) {
return isolate->heap()->undefined_value();
}
namespace {
Object* CopyElements(Isolate* isolate, Handle<JSTypedArray> holder,
Handle<JSReceiver> source, size_t length) {
ElementsAccessor* holder_accessor = holder->GetElementsAccessor();
for (uint32_t i = 0; i < length; i++) {
LookupIterator get_it(isolate, source, i);
Handle<Object> element;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, element,
Object::GetProperty(&get_it));
// Convert the incoming value to a number for storing into typed arrays.
if (!element->IsNumber() && !element->IsUndefined(isolate)) {
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, element,
Object::ToNumber(element));
}
holder_accessor->Set(holder, i, *element);
}
return isolate->heap()->undefined_value();
}
} // namespace
RUNTIME_FUNCTION(Runtime_TypedArrayCopyElements) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, holder, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, source, 1);
CONVERT_NUMBER_ARG_HANDLE_CHECKED(length_obj, 2);
size_t length;
CHECK(TryNumberToSize(*length_obj, &length));
return CopyElements(isolate, holder, source, length);
}
// Initializes a typed array from an array-like object, and its backing store as
// well.
RUNTIME_FUNCTION(Runtime_TypedArrayInitializeFromArrayLike) {
HandleScope scope(isolate);
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, holder, 0);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, source, 1);
CONVERT_NUMBER_ARG_HANDLE_CHECKED(length_obj, 2);
ElementsKind fixed_elements_kind = holder->map()->elements_kind();
ExternalArrayType array_type =
isolate->factory()->GetArrayTypeFromElementsKind(fixed_elements_kind);
size_t element_size =
isolate->factory()->GetExternalArrayElementSize(array_type);
Handle<JSArrayBuffer> buffer = isolate->factory()->NewJSArrayBuffer();
size_t length = 0;
if (source->IsJSTypedArray() &&
JSTypedArray::cast(*source)->type() == array_type) {
length = JSTypedArray::cast(*source)->length_value();
} else {
CHECK(TryNumberToSize(*length_obj, &length));
}
if ((length > static_cast<unsigned>(Smi::kMaxValue)) ||
(length > (kMaxInt / element_size))) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kInvalidTypedArrayLength));
}
size_t byte_length = length * element_size;
DCHECK_EQ(v8::ArrayBufferView::kEmbedderFieldCount,
holder->GetEmbedderFieldCount());
for (int i = 0; i < v8::ArrayBufferView::kEmbedderFieldCount; i++) {
holder->SetEmbedderField(i, Smi::kZero);
}
if (!JSArrayBuffer::SetupAllocatingData(buffer, isolate, byte_length,
false)) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate, NewRangeError(MessageTemplate::kInvalidArrayBufferLength));
}
holder->set_buffer(*buffer);
holder->set_byte_offset(Smi::kZero);
Handle<Object> byte_length_obj(
isolate->factory()->NewNumberFromSize(byte_length));
holder->set_byte_length(*byte_length_obj);
length_obj = isolate->factory()->NewNumberFromSize(length);
holder->set_length(*length_obj);
Handle<FixedTypedArrayBase> elements =
isolate->factory()->NewFixedTypedArrayWithExternalPointer(
static_cast<int>(length), array_type,
static_cast<uint8_t*>(buffer->backing_store()));
holder->set_elements(*elements);
// Initialize the backing store. We can use a special path for typed arrays of
// the same type, but we need to make sure everything is properly observable
// for other types.
if (source->IsJSTypedArray()) {
Handle<JSTypedArray> typed_array(JSTypedArray::cast(*source));
if (typed_array->type() == holder->type()) {
uint8_t* backing_store =
static_cast<uint8_t*>(typed_array->GetBuffer()->backing_store());
size_t source_byte_offset = NumberToSize(typed_array->byte_offset());
memcpy(buffer->backing_store(), backing_store + source_byte_offset,
byte_length);
return isolate->heap()->true_value();
}
}
return CopyElements(isolate, holder, source, length);
}
#define BUFFER_VIEW_GETTER(Type, getter, accessor) \
RUNTIME_FUNCTION(Runtime_##Type##Get##getter) { \
HandleScope scope(isolate); \
......
......@@ -617,8 +617,6 @@ namespace internal {
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
F(ArrayBufferGetByteLength, 1, 1) \
F(ArrayBufferNeuter, 1, 1) \
F(TypedArrayCopyElements, 3, 1) \
F(TypedArrayInitializeFromArrayLike, 3, 1) \
F(ArrayBufferViewGetByteLength, 1, 1) \
F(ArrayBufferViewGetByteOffset, 1, 1) \
F(TypedArrayGetLength, 1, 1) \
......
......@@ -68,11 +68,39 @@ function TestLengthIsMaxSmi(constr) {
}, RangeError);
}
function TestOffsetIsUsedRunner(constr, n) {
var buffer = new ArrayBuffer(constr.BYTES_PER_ELEMENT * n);
var whole_ta = new constr(buffer);
assertEquals(n, whole_ta.length);
for (var i = 0; i < whole_ta.length; i++) {
whole_ta[i] = i;
}
var half_ta = new constr(buffer, constr.BYTES_PER_ELEMENT * n / 2);
assertEquals(n / 2, half_ta.length);
var arr = new constr(half_ta);
assertEquals(n / 2, arr.length);
for (var i = 0; i < arr.length; i++) {
assertEquals(n / 2 + i, arr[i]);
}
}
function TestOffsetIsUsed(constr, n) {
TestOffsetIsUsedRunner(constr, 4);
TestOffsetIsUsedRunner(constr, 16);
TestOffsetIsUsedRunner(constr, 32);
TestOffsetIsUsedRunner(constr, 128);
}
Test(TestConstructSmallObject);
Test(TestConstructLargeObject);
Test(TestConstructFromArray);
Test(TestConstructFromTypedArray);
Test(TestLengthIsMaxSmi);
Test(TestOffsetIsUsed);
function Test(func) {
func(Uint8Array);
......
// Copyright 2013 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.
assertThrows('x = new Float64Array({length: 0x24924925})');
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