[builtins] Port TypedArrayConstructByArrayLike to CodeStubAssembler.

This helper is used directly when constructing from an object with
a length, as well as by ConstructByIterable and ByTypedArray.

BUG=v8:5977

Change-Id: I18a4829c2a22a6099cf3b0824ea1f698bfbf1917
Reviewed-on: https://chromium-review.googlesource.com/456707
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Reviewed-by: Franziska Hinkelmann <franzih@chromium.org>
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44116}
This commit is contained in:
Peter Marshall 2017-03-24 16:02:56 +01:00 committed by Commit Bot
parent 7e08a77deb
commit 14e01da1cf
11 changed files with 257 additions and 78 deletions

View File

@ -2648,26 +2648,30 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Handle<JSFunction> typed_array_initialize = SimpleCreateFunction(
isolate, factory->NewStringFromAsciiChecked("typedArrayInitialize"),
Builtins::kTypedArrayInitialize, 6, false);
InstallWithIntrinsicDefaultProto(isolate, typed_array_initialize,
Context::TYPED_ARRAY_INITIALIZE_INDEX);
native_context()->set_typed_array_initialize(*typed_array_initialize);
// %typed_array_construct_by_length
Handle<JSFunction> construct_by_length = SimpleCreateFunction(
isolate,
factory->NewStringFromAsciiChecked("typedArrayConstructByLength"),
Builtins::kTypedArrayConstructByLength, 3, false);
InstallWithIntrinsicDefaultProto(
isolate, construct_by_length,
Context::TYPED_ARRAY_CONSTRUCT_BY_LENGTH_INDEX);
native_context()->set_typed_array_construct_by_length(*construct_by_length);
// %typed_array_construct_by_array_buffer
Handle<JSFunction> construct_by_buffer = SimpleCreateFunction(
isolate,
factory->NewStringFromAsciiChecked("typedArrayConstructByArrayBuffer"),
Builtins::kTypedArrayConstructByArrayBuffer, 5, false);
InstallWithIntrinsicDefaultProto(
isolate, construct_by_buffer,
Context::TYPED_ARRAY_CONSTRUCT_BY_ARRAY_BUFFER_INDEX);
native_context()->set_typed_array_construct_by_array_buffer(
*construct_by_buffer);
// %typed_array_construct_by_array_like
Handle<JSFunction> construct_by_array_like = SimpleCreateFunction(
isolate,
factory->NewStringFromAsciiChecked("typedArrayConstructByArrayLike"),
Builtins::kTypedArrayConstructByArrayLike, 4, false);
native_context()->set_typed_array_construct_by_array_like(
*construct_by_array_like);
}
{ // -- D a t a V i e w

View File

@ -278,7 +278,6 @@ void TypedArrayBuiltinsAssembler::DoInitialize(Node* const holder, Node* length,
}
Bind(&done);
Return(UndefinedConstant());
}
TF_BUILTIN(TypedArrayInitialize, TypedArrayBuiltinsAssembler) {
@ -292,6 +291,7 @@ TF_BUILTIN(TypedArrayInitialize, TypedArrayBuiltinsAssembler) {
DoInitialize(holder, length, maybe_buffer, byte_offset, byte_length,
initialize, context);
Return(UndefinedConstant());
}
// ES6 #sec-typedarray-length
@ -337,6 +337,7 @@ TF_BUILTIN(TypedArrayConstructByLength, TypedArrayBuiltinsAssembler) {
{
DoInitialize(holder, length, maybe_buffer.value(), byte_offset, byte_length,
initialize, context);
Return(UndefinedConstant());
}
Bind(&invalid_length);
@ -448,6 +449,7 @@ TF_BUILTIN(TypedArrayConstructByArrayBuffer, TypedArrayBuiltinsAssembler) {
DoInitialize(holder, new_length, buffer, offset.value(),
new_byte_length.value(), initialize, context);
Return(UndefinedConstant());
}
Bind(&invalid_offset_error);
@ -487,6 +489,49 @@ TF_BUILTIN(TypedArrayConstructByArrayBuffer, TypedArrayBuiltinsAssembler) {
}
}
TF_BUILTIN(TypedArrayConstructByArrayLike, TypedArrayBuiltinsAssembler) {
Node* const holder = Parameter(Descriptor::kHolder);
Node* const array_like = Parameter(Descriptor::kArrayLike);
Node* length = Parameter(Descriptor::kLength);
Node* const element_size = Parameter(Descriptor::kElementSize);
CSA_ASSERT(this, TaggedIsSmi(element_size));
Node* const context = Parameter(Descriptor::kContext);
Label call_init(this), call_runtime(this), invalid_length(this);
// The caller has looked up length on array_like, which is observable.
length = ToSmiLength(length, context, &invalid_length);
// For byte_length < typed_array_max_size_in_heap, we allocate the buffer on
// the heap. Otherwise we allocate it externally and attach it.
Node* byte_length = SmiMul(length, element_size);
GotoIf(TaggedIsNotSmi(byte_length), &call_runtime);
Branch(SmiLessThanOrEqual(byte_length,
SmiConstant(FLAG_typed_array_max_size_in_heap)),
&call_init, &call_runtime);
Bind(&call_init);
{
DoInitialize(holder, length, NullConstant(), SmiConstant(0), byte_length,
BooleanConstant(false), context);
Return(CallRuntime(Runtime::kTypedArrayCopyElements, context, holder,
array_like, length));
}
Bind(&call_runtime);
{
Return(CallRuntime(Runtime::kTypedArrayInitializeFromArrayLike, context,
holder, array_like, length));
}
Bind(&invalid_length);
{
CallRuntime(Runtime::kThrowRangeError, context,
SmiConstant(MessageTemplate::kInvalidTypedArrayLength));
Unreachable();
}
}
void TypedArrayBuiltinsAssembler::GenerateTypedArrayPrototypeGetter(
Node* context, Node* receiver, const char* method_name, int object_offset) {
// Check if the {receiver} is actually a JSTypedArray.

View File

@ -888,6 +888,8 @@ class Isolate;
/* ES6 #sec-typedarray-buffer-byteoffset-length */ \
TFJ(TypedArrayConstructByArrayBuffer, 5, kHolder, kBuffer, kByteOffset, \
kLength, kElementSize) \
TFJ(TypedArrayConstructByArrayLike, 4, kHolder, kArrayLike, kLength, \
kElementSize) \
/* ES6 #sec-typedarray-length */ \
TFJ(TypedArrayConstructByLength, 3, kHolder, kLength, kElementSize) \
TFJ(TypedArrayInitialize, 6, kHolder, kLength, kBuffer, kByteOffset, \

View File

@ -4149,12 +4149,12 @@ Node* CodeStubAssembler::JSReceiverToPrimitive(Node* context, Node* input) {
Node* CodeStubAssembler::ToSmiIndex(Node* const input, Node* const context,
Label* range_error) {
Variable result(this, MachineRepresentation::kTagged, input);
Label check_undefined(this), undefined(this), defined(this),
Label check_undefined(this), return_zero(this), defined(this),
negative_check(this), done(this);
Branch(TaggedIsSmi(result.value()), &negative_check, &check_undefined);
Bind(&check_undefined);
Branch(IsUndefined(result.value()), &undefined, &defined);
Branch(IsUndefined(result.value()), &return_zero, &defined);
Bind(&defined);
result.Bind(ToInteger(context, result.value(),
@ -4164,10 +4164,33 @@ Node* CodeStubAssembler::ToSmiIndex(Node* const input, Node* const context,
Goto(&negative_check);
Bind(&negative_check);
GotoIf(SmiLessThan(result.value(), SmiConstant(0)), range_error);
Branch(SmiLessThan(result.value(), SmiConstant(0)), range_error, &done);
Bind(&return_zero);
result.Bind(SmiConstant(0));
Goto(&done);
Bind(&undefined);
Bind(&done);
return result.value();
}
Node* CodeStubAssembler::ToSmiLength(Node* input, Node* const context,
Label* range_error) {
Variable result(this, MachineRepresentation::kTagged, input);
Label to_integer(this), negative_check(this), return_zero(this), done(this);
Branch(TaggedIsSmi(result.value()), &negative_check, &to_integer);
Bind(&to_integer);
result.Bind(ToInteger(context, result.value(),
CodeStubAssembler::kTruncateMinusZero));
GotoIfNot(TaggedIsSmi(result.value()), range_error);
CSA_ASSERT(this, TaggedIsSmi(result.value()));
Goto(&negative_check);
Bind(&negative_check);
Branch(SmiLessThan(result.value(), SmiConstant(0)), &return_zero, &done);
Bind(&return_zero);
result.Bind(SmiConstant(0));
Goto(&done);

View File

@ -799,6 +799,9 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
// ES6 7.1.17 ToIndex, but jumps to range_error if the result is not a Smi.
Node* ToSmiIndex(Node* const input, Node* const context, Label* range_error);
// ES6 7.1.15 ToLength, but jumps to range_error if the result is not a Smi.
Node* ToSmiLength(Node* input, Node* const context, Label* range_error);
// Convert any object to an Integer.
Node* ToInteger(Node* context, Node* input,
ToIntegerTruncationMode mode = kNoTruncation);

View File

@ -70,6 +70,8 @@ enum ContextLookupFlags {
V(SPREAD_ITERABLE_INDEX, JSFunction, spread_iterable) \
V(TYPED_ARRAY_CONSTRUCT_BY_ARRAY_BUFFER_INDEX, JSFunction, \
typed_array_construct_by_array_buffer) \
V(TYPED_ARRAY_CONSTRUCT_BY_ARRAY_LIKE_INDEX, JSFunction, \
typed_array_construct_by_array_like) \
V(TYPED_ARRAY_CONSTRUCT_BY_LENGTH_INDEX, JSFunction, \
typed_array_construct_by_length) \
V(TYPED_ARRAY_INITIALIZE_INDEX, JSFunction, typed_array_initialize) \

View File

@ -115,29 +115,6 @@ function TypedArraySpeciesCreate(exemplar, arg0, arg1, arg2, conservative) {
}
macro TYPED_ARRAY_CONSTRUCTOR(ARRAY_ID, NAME, ELEMENT_SIZE)
function NAMEConstructByArrayLike(obj, arrayLike, length) {
var l = ToPositiveInteger(length, kInvalidTypedArrayLength);
if (l > %_MaxSmi()) {
throw %make_range_error(kInvalidTypedArrayLength);
}
var initialized = false;
var byteLength = l * ELEMENT_SIZE;
if (byteLength <= %_TypedArrayMaxSizeInHeap()) {
%typed_array_initialize(obj, l, null, 0, byteLength, false);
} else {
initialized =
%TypedArrayInitializeFromArrayLike(obj, ARRAY_ID, arrayLike, l);
}
if (!initialized) {
for (var i = 0; i < l; i++) {
// It is crucial that we let any execptions from arrayLike[i]
// propagate outside the function.
obj[i] = arrayLike[i];
}
}
}
function NAMEConstructByIterable(obj, iterable, iteratorFn) {
var list = new InternalArray();
// Reading the Symbol.iterator property of iterable twice would be
@ -155,7 +132,7 @@ function NAMEConstructByIterable(obj, iterable, iteratorFn) {
for (var value of newIterable) {
list.push(value);
}
NAMEConstructByArrayLike(obj, list, list.length);
%typed_array_construct_by_array_like(obj, list, list.length, ELEMENT_SIZE);
}
// ES#sec-typedarray-typedarray TypedArray ( typedArray )
@ -165,7 +142,7 @@ function NAMEConstructByTypedArray(obj, typedArray) {
var length = %_TypedArrayGetLength(typedArray);
var byteLength = %_ArrayBufferViewGetByteLength(typedArray);
var newByteLength = length * ELEMENT_SIZE;
NAMEConstructByArrayLike(obj, typedArray, length);
%typed_array_construct_by_array_like(obj, typedArray, length, ELEMENT_SIZE);
var bufferConstructor = SpeciesConstructor(srcData, GlobalArrayBuffer);
var prototype = bufferConstructor.prototype;
// TODO(littledan): Use the right prototype based on bufferConstructor's realm
@ -178,13 +155,14 @@ function NAMEConstructor(arg1, arg2, arg3) {
if (!IS_UNDEFINED(new.target)) {
if (IS_ARRAYBUFFER(arg1) || IS_SHAREDARRAYBUFFER(arg1)) {
%typed_array_construct_by_array_buffer(
this, arg1, arg2, arg3, ELEMENT_SIZE);
this, arg1, arg2, arg3, ELEMENT_SIZE);
} else if (IS_TYPEDARRAY(arg1)) {
NAMEConstructByTypedArray(this, arg1);
} else if (IS_RECEIVER(arg1)) {
var iteratorFn = arg1[iteratorSymbol];
if (IS_UNDEFINED(iteratorFn)) {
NAMEConstructByArrayLike(this, arg1, arg1.length);
%typed_array_construct_by_array_like(
this, arg1, arg1.length, ELEMENT_SIZE);
} else {
NAMEConstructByIterable(this, arg1, iteratorFn);
}

View File

@ -5,6 +5,7 @@
#include "src/runtime/runtime-utils.h"
#include "src/arguments.h"
#include "src/elements.h"
#include "src/factory.h"
#include "src/messages.h"
#include "src/objects-inl.h"
@ -41,6 +42,41 @@ 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);
}
void Runtime::ArrayIdToTypeAndSize(int arrayId, ExternalArrayType* array_type,
ElementsKind* fixed_elements_kind,
@ -76,29 +112,20 @@ const char* Runtime::ElementsKindToType(ElementsKind fixed_elements_kind) {
}
}
// Initializes a typed array from an array-like object.
// If an array-like object happens to be a typed array of the same type,
// initializes backing store using memove.
//
// Returns true if backing store was initialized or false otherwise.
// Initializes a typed array from an array-like object, and its backing store as
// well.
RUNTIME_FUNCTION(Runtime_TypedArrayInitializeFromArrayLike) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
DCHECK_EQ(3, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, holder, 0);
CONVERT_SMI_ARG_CHECKED(arrayId, 1);
CONVERT_ARG_HANDLE_CHECKED(Object, source, 2);
CONVERT_NUMBER_ARG_HANDLE_CHECKED(length_obj, 3);
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, source, 1);
CONVERT_NUMBER_ARG_HANDLE_CHECKED(length_obj, 2);
CHECK(arrayId >= Runtime::ARRAY_ID_FIRST &&
arrayId <= Runtime::ARRAY_ID_LAST);
ExternalArrayType array_type = kExternalInt8Array; // Bogus initialization.
size_t element_size = 1; // Bogus initialization.
ElementsKind fixed_elements_kind = INT8_ELEMENTS; // Bogus initialization.
Runtime::ArrayIdToTypeAndSize(arrayId, &array_type, &fixed_elements_kind,
&element_size);
CHECK(holder->map()->elements_kind() == fixed_elements_kind);
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;
@ -122,22 +149,6 @@ RUNTIME_FUNCTION(Runtime_TypedArrayInitializeFromArrayLike) {
holder->SetEmbedderField(i, Smi::kZero);
}
// NOTE: not initializing backing store.
// We assume that the caller of this function will initialize holder
// with the loop
// for(i = 0; i < length; i++) { holder[i] = source[i]; }
// We assume that the caller of this function is always a typed array
// constructor.
// If source is a typed array, this loop will always run to completion,
// so we are sure that the backing store will be initialized.
// Otherwise, the indexing operation might throw, so the loop will not
// run to completion and the typed array might remain partly initialized.
// However we further assume that the caller of this function is a typed array
// constructor, and the exception will propagate out of the constructor,
// therefore uninitialized memory will not be accessible by a user program.
//
// TODO(dslomov): revise this once we support subclassing.
if (!JSArrayBuffer::SetupAllocatingData(buffer, isolate, byte_length,
false)) {
THROW_NEW_ERROR_RETURN_FAILURE(
@ -158,6 +169,9 @@ RUNTIME_FUNCTION(Runtime_TypedArrayInitializeFromArrayLike) {
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));
@ -170,8 +184,7 @@ RUNTIME_FUNCTION(Runtime_TypedArrayInitializeFromArrayLike) {
return isolate->heap()->true_value();
}
}
return isolate->heap()->false_value();
return CopyElements(isolate, holder, source, length);
}

View File

@ -618,7 +618,8 @@ namespace internal {
#define FOR_EACH_INTRINSIC_TYPEDARRAY(F) \
F(ArrayBufferGetByteLength, 1, 1) \
F(ArrayBufferNeuter, 1, 1) \
F(TypedArrayInitializeFromArrayLike, 4, 1) \
F(TypedArrayCopyElements, 3, 1) \
F(TypedArrayInitializeFromArrayLike, 3, 1) \
F(ArrayBufferViewGetByteLength, 1, 1) \
F(ArrayBufferViewGetByteOffset, 1, 1) \
F(TypedArrayGetLength, 1, 1) \

View File

@ -0,0 +1,87 @@
// 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 TestConstructSmallObject(constr) {
var myObject = { 0: 5, 1: 6, length: 2 };
arr = new constr(myObject);
assertEquals(2, arr.length);
assertEquals(5, arr[0]);
assertEquals(6, arr[1]);
};
function TestConstructLargeObject(constr) {
var myObject = {};
const n = 128;
for (var i = 0; i < n; i++) {
myObject[i] = i;
}
myObject.length = n;
arr = new constr(myObject);
assertEquals(n, arr.length);
for (var i = 0; i < n; i++) {
assertEquals(i, arr[i]);
}
}
function TestConstructFromArray(constr) {
var n = 64;
var jsArray = [];
for (var i = 0; i < n; i++) {
jsArray[i] = i;
}
var arr = new constr(jsArray);
assertEquals(n, arr.length);
for (var i = 0; i < n; i++) {
assertEquals(i, arr[i]);
}
}
function TestConstructFromTypedArray(constr) {
var n = 64;
var ta = new constr(n);
for (var i = 0; i < ta.length; i++) {
ta[i] = i;
}
var arr = new constr(ta);
assertEquals(n, arr.length);
for (var i = 0; i < n; i++) {
assertEquals(i, arr[i]);
}
}
function TestLengthIsMaxSmi(constr) {
var myObject = { 0: 5, 1: 6, length: %_MaxSmi() + 1 };
assertThrows(function() {
new constr(myObject);
}, RangeError);
}
Test(TestConstructSmallObject);
Test(TestConstructLargeObject);
Test(TestConstructFromArray);
Test(TestConstructFromTypedArray);
Test(TestLengthIsMaxSmi);
function Test(func) {
func(Uint8Array);
func(Int8Array);
func(Uint16Array);
func(Int16Array);
func(Uint32Array);
func(Int32Array);
func(Float32Array);
func(Float64Array);
func(Uint8ClampedArray);
}

View File

@ -58,3 +58,24 @@
new Uint8Array(buffer, -1);
}, RangeError);
})();
(function TestByArrayLikeObservableOrdering() {
var expected = [
'proxy.Symbol(Symbol.iterator)', 'proxy.length', 'proxy.0', 'proxy.1',
'proxy.2'
];
var actual = [];
var a = [1, 2, 3];
var proxy = new Proxy(a, {
get: function(target, name) {
actual.push("proxy." + name.toString());
if (name === Symbol.iterator) return undefined;
return target[name];
}
});
var arr = new Uint8Array(proxy);
assertEquals(a.length, arr.length);
assertEquals(expected, actual);
})();