[typedarray] Port TypedArray.from to CSA.

Factor out IterableToList into a helper stub to save space. There are
two callers now, TypedArrayFrom and ConstructByIterable, and it is
~2.5kb so we save space by doing this.

Increase test coverage to cover more of the branching in CSA.

This is doesn't follow the control flow in the spec exactly - see the
big code comment for an explanation.

Change-Id: Ief39e93c4202cb7bf0e28a39dc6aa81b8b9c59d2
Reviewed-on: https://chromium-review.googlesource.com/908755
Commit-Queue: Peter Marshall <petermarshall@chromium.org>
Reviewed-by: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#51377}
This commit is contained in:
Peter Marshall 2018-02-19 13:32:56 +01:00 committed by Commit Bot
parent 19e65114a1
commit 81a3742a88
8 changed files with 385 additions and 125 deletions

View File

@ -2947,6 +2947,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
SimpleInstallFunction(typed_array_fun, "of", Builtins::kTypedArrayOf, 0,
false);
SimpleInstallFunction(typed_array_fun, "from", Builtins::kTypedArrayFrom, 1,
false);
// Setup %TypedArrayPrototype%.
Handle<JSObject> prototype(

View File

@ -1071,6 +1071,7 @@ namespace internal {
TFJ(SymbolPrototypeValueOf, 0) \
\
/* TypedArray */ \
TFS(IterableToList, kIterable, kIteratorFn) \
TFS(TypedArrayInitialize, kHolder, kLength, kElementSize, kInitialize) \
TFS(TypedArrayInitializeWithBuffer, kHolder, kLength, kBuffer, kElementSize, \
kByteOffset) \
@ -1140,6 +1141,8 @@ namespace internal {
SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 %TypedArray%.of */ \
TFJ(TypedArrayOf, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES6 %TypedArray%.from */ \
TFJ(TypedArrayFrom, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
\
/* Wasm */ \
ASM(WasmCompileLazy) \

View File

@ -622,68 +622,11 @@ void TypedArrayBuiltinsAssembler::ConstructByIterable(
CSA_ASSERT(this, IsCallable(iterator_fn));
Label fast_path(this), slow_path(this), done(this);
TVARIABLE(JSReceiver, array_like);
TVARIABLE(Object, initial_length);
// This is a fast-path for ignoring the iterator.
// TODO(petermarshall): Port to CSA.
Node* elided =
CallRuntime(Runtime::kIterableToListCanBeElided, context, iterable);
CSA_ASSERT(this, IsBoolean(elided));
Branch(IsTrue(elided), &fast_path, &slow_path);
BIND(&fast_path);
{
TNode<JSArray> js_array_iterable = CAST(iterable);
// This .length access is unobservable, because it being observable would
// mean that iteration has side effects, and we wouldn't reach this path.
array_like = js_array_iterable;
initial_length = LoadJSArrayLength(js_array_iterable);
Goto(&done);
}
BIND(&slow_path);
{
IteratorBuiltinsAssembler iterator_assembler(state());
// 1. Let iteratorRecord be ? GetIterator(items, method).
IteratorRecord iterator_record =
iterator_assembler.GetIterator(context, iterable, iterator_fn);
// 2. Let values be a new empty List.
GrowableFixedArray values(state());
Variable* vars[] = {values.var_array(), values.var_length(),
values.var_capacity()};
Label loop_start(this, 3, vars), loop_end(this);
Goto(&loop_start);
// 3. Let next be true.
// 4. Repeat, while next is not false
BIND(&loop_start);
{
// a. Set next to ? IteratorStep(iteratorRecord).
TNode<Object> next = CAST(
iterator_assembler.IteratorStep(context, iterator_record, &loop_end));
// b. If next is not false, then
// i. Let nextValue be ? IteratorValue(next).
TNode<Object> next_value =
CAST(iterator_assembler.IteratorValue(context, next));
// ii. Append nextValue to the end of the List values.
values.Push(next_value);
Goto(&loop_start);
}
BIND(&loop_end);
// 5. Return values.
TNode<JSArray> js_array_values = values.ToJSArray(context);
array_like = js_array_values;
initial_length = LoadJSArrayLength(js_array_values);
Goto(&done);
}
BIND(&done);
ConstructByArrayLike(context, holder, array_like.value(),
initial_length.value(), element_size);
TNode<JSArray> array_like = CAST(
CallBuiltin(Builtins::kIterableToList, context, iterable, iterator_fn));
TNode<Object> initial_length = LoadJSArrayLength(array_like);
ConstructByArrayLike(context, holder, array_like, initial_length,
element_size);
}
TF_BUILTIN(TypedArrayConstructor, TypedArrayBuiltinsAssembler) {
@ -1632,6 +1575,251 @@ TF_BUILTIN(TypedArrayOf, TypedArrayBuiltinsAssembler) {
ThrowTypeError(context, MessageTemplate::kNotConstructor, receiver);
}
TF_BUILTIN(IterableToList, TypedArrayBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<Object> iterable = CAST(Parameter(Descriptor::kIterable));
TNode<Object> iterator_fn = CAST(Parameter(Descriptor::kIteratorFn));
Label fast_path(this), slow_path(this), done(this);
TVARIABLE(JSArray, created_list);
// This is a fast-path for ignoring the iterator.
// TODO(petermarshall): Port to CSA.
Node* elided =
CallRuntime(Runtime::kIterableToListCanBeElided, context, iterable);
CSA_ASSERT(this, IsBoolean(elided));
Branch(IsTrue(elided), &fast_path, &slow_path);
BIND(&fast_path);
{
created_list = CAST(iterable);
Goto(&done);
}
BIND(&slow_path);
{
IteratorBuiltinsAssembler iterator_assembler(state());
// 1. Let iteratorRecord be ? GetIterator(items, method).
IteratorRecord iterator_record =
iterator_assembler.GetIterator(context, iterable, iterator_fn);
// 2. Let values be a new empty List.
GrowableFixedArray values(state());
Variable* vars[] = {values.var_array(), values.var_length(),
values.var_capacity()};
Label loop_start(this, 3, vars), loop_end(this);
Goto(&loop_start);
// 3. Let next be true.
// 4. Repeat, while next is not false
BIND(&loop_start);
{
// a. Set next to ? IteratorStep(iteratorRecord).
TNode<Object> next = CAST(
iterator_assembler.IteratorStep(context, iterator_record, &loop_end));
// b. If next is not false, then
// i. Let nextValue be ? IteratorValue(next).
TNode<Object> next_value =
CAST(iterator_assembler.IteratorValue(context, next));
// ii. Append nextValue to the end of the List values.
values.Push(next_value);
Goto(&loop_start);
}
BIND(&loop_end);
// 5. Return values.
TNode<JSArray> js_array_values = values.ToJSArray(context);
created_list = js_array_values;
Goto(&done);
}
BIND(&done);
Return(created_list.value());
}
// ES6 #sec-%typedarray%.from
TF_BUILTIN(TypedArrayFrom, TypedArrayBuiltinsAssembler) {
TNode<Context> context = CAST(Parameter(BuiltinDescriptor::kContext));
Label check_iterator(this), from_array_like(this), fast_path(this),
slow_path(this), create_typed_array(this),
if_not_constructor(this, Label::kDeferred),
if_map_fn_not_callable(this, Label::kDeferred),
if_iterator_fn_not_callable(this, Label::kDeferred),
unreachable(this, Label::kDeferred);
CodeStubArguments args(
this, ChangeInt32ToIntPtr(Parameter(BuiltinDescriptor::kArgumentsCount)));
TNode<Object> source = args.GetOptionalArgumentValue(0);
// 5. If thisArg is present, let T be thisArg; else let T be undefined.
TNode<Object> this_arg = args.GetOptionalArgumentValue(2);
// 1. Let C be the this value.
// 2. If IsConstructor(C) is false, throw a TypeError exception.
TNode<Object> receiver = args.GetReceiver();
GotoIf(TaggedIsSmi(receiver), &if_not_constructor);
GotoIfNot(IsConstructor(receiver), &if_not_constructor);
// 3. If mapfn is present and mapfn is not undefined, then
TNode<Object> map_fn = args.GetOptionalArgumentValue(1);
TVARIABLE(BoolT, mapping, Int32FalseConstant());
GotoIf(IsUndefined(map_fn), &check_iterator);
// a. If IsCallable(mapfn) is false, throw a TypeError exception.
// b. Let mapping be true.
// 4. Else, let mapping be false.
GotoIf(TaggedIsSmi(map_fn), &if_map_fn_not_callable);
GotoIfNot(IsCallable(map_fn), &if_map_fn_not_callable);
mapping = Int32TrueConstant();
Goto(&check_iterator);
TVARIABLE(Object, final_source);
TVARIABLE(Smi, final_length);
// We split up this builtin differently to the way it is written in the spec.
// We already have great code in the elements accessor for copying from a
// JSArray into a TypedArray, so we use that when possible. We only avoid
// calling into the elements accessor when we have a mapping function, because
// we can't handle that. Here, presence of a mapping function is the slow
// path. We also combine the two different loops in the specification
// (starting at 7.e and 13) because they are essentially identical. We also
// save on code-size this way.
BIND(&check_iterator);
{
// 6. Let usingIterator be ? GetMethod(source, @@iterator).
TNode<Object> iterator_fn =
CAST(GetMethod(context, source, isolate()->factory()->iterator_symbol(),
&from_array_like));
GotoIf(TaggedIsSmi(iterator_fn), &if_iterator_fn_not_callable);
GotoIfNot(IsCallable(iterator_fn), &if_iterator_fn_not_callable);
// We are using the iterator.
Label if_length_not_smi(this, Label::kDeferred);
// 7. If usingIterator is not undefined, then
// a. Let values be ? IterableToList(source, usingIterator).
// b. Let len be the number of elements in values.
TNode<JSArray> values = CAST(
CallBuiltin(Builtins::kIterableToList, context, source, iterator_fn));
// This is not a spec'd limit, so it doesn't particularly matter when we
// throw the range error for typed array length > MaxSmi.
TNode<Object> raw_length = LoadJSArrayLength(values);
GotoIfNot(TaggedIsSmi(raw_length), &if_length_not_smi);
final_length = CAST(raw_length);
final_source = values;
Goto(&create_typed_array);
BIND(&if_length_not_smi);
ThrowRangeError(context, MessageTemplate::kInvalidTypedArrayLength,
raw_length);
}
BIND(&from_array_like);
{
Label if_length_not_smi(this, Label::kDeferred);
final_source = CAST(source);
// 10. Let len be ? ToLength(? Get(arrayLike, "length")).
Node* raw_length =
GetProperty(context, final_source.value(), LengthStringConstant());
final_length = CAST(ToSmiLength(raw_length, context, &if_length_not_smi));
Goto(&create_typed_array);
BIND(&if_length_not_smi);
ThrowRangeError(context, MessageTemplate::kInvalidTypedArrayLength,
raw_length);
}
TVARIABLE(JSTypedArray, target_obj);
BIND(&create_typed_array);
{
// 7c/11. Let targetObj be ? TypedArrayCreate(C, «len»).
target_obj = CreateByLength(context, receiver, final_length.value(),
"%TypedArray%.from");
Branch(mapping.value(), &slow_path, &fast_path);
}
BIND(&fast_path);
{
Label done(this);
GotoIf(SmiEqual(final_length.value(), SmiConstant(0)), &done);
CallRuntime(Runtime::kTypedArrayCopyElements, context, target_obj.value(),
final_source.value(), final_length.value());
Goto(&done);
BIND(&done);
args.PopAndReturn(target_obj.value());
}
BIND(&slow_path);
TNode<Word32T> elements_kind = LoadElementsKind(target_obj.value());
// 7e/13 : Copy the elements
BuildFastLoop(
SmiConstant(0), final_length.value(),
[&](Node* index) {
TNode<String> const index_string = NumberToString(CAST(index));
TNode<Object> const k_value =
CAST(GetProperty(context, final_source.value(), index_string));
TNode<Object> const mapped_value =
CAST(CallJS(CodeFactory::Call(isolate()), context, map_fn, this_arg,
k_value, index));
TNode<Number> const number_value =
ToNumber_Inline(context, mapped_value);
// map_fn or ToNumber could have executed JavaScript code, but
// they could not have accessed the new typed array.
DebugSanityCheckTypedArrayIndex(target_obj.value(), index);
DispatchTypedArrayByElementsKind(
elements_kind,
[&](ElementsKind kind, int size, int typed_array_fun_index) {
if (kind == BIGINT64_ELEMENTS || kind == BIGUINT64_ELEMENTS) {
// TODO(jkummerow): Add inline support.
CallRuntime(Runtime::kSetProperty, context, target_obj.value(),
index, number_value,
SmiConstant(LanguageMode::kSloppy));
} else {
// Since we can guarantee that "number_value" is Number type,
// PrepareValueForWriteToTypedArray cannot bail out.
Node* const final_value = PrepareValueForWriteToTypedArray(
number_value, kind, &unreachable);
// GC may move backing store in map_fn, thus load backing
// store in each iteration of this loop.
TNode<IntPtrT> const backing_store =
UncheckedCast<IntPtrT>(LoadDataPtr(target_obj.value()));
StoreElement(backing_store, kind, index, final_value,
SMI_PARAMETERS);
}
});
},
1, ParameterMode::SMI_PARAMETERS, IndexAdvanceMode::kPost);
args.PopAndReturn(target_obj.value());
BIND(&if_not_constructor);
ThrowTypeError(context, MessageTemplate::kNotConstructor, receiver);
BIND(&if_map_fn_not_callable);
ThrowTypeError(context, MessageTemplate::kCalledNonCallable, map_fn);
BIND(&if_iterator_fn_not_callable);
ThrowTypeError(context, MessageTemplate::kIteratorSymbolNonCallable);
BIND(&unreachable);
Unreachable();
}
// ES %TypedArray%.prototype.filter
TF_BUILTIN(TypedArrayPrototypeFilter, TypedArrayBuiltinsAssembler) {
const char* method_name = "%TypedArray%.prototype.filter";

View File

@ -956,13 +956,13 @@ Node* CodeAssembler::Projection(int index, Node* value) {
void CodeAssembler::GotoIfException(Node* node, Label* if_exception,
Variable* exception_var) {
DCHECK(!node->op()->HasProperty(Operator::kNoThrow));
if (if_exception == nullptr) {
// If no handler is supplied, don't add continuations
return;
}
DCHECK(!node->op()->HasProperty(Operator::kNoThrow));
Label success(this), exception(this, Label::kDeferred);
success.MergeVariables();
exception.MergeVariables();

View File

@ -3504,8 +3504,12 @@ class TypedElementsAccessor
isolate, NewTypeError(MessageTemplate::kBigIntToNumber));
}
}
CopyElementsFromTypedArray(*source_ta, *destination_ta, length, offset);
return *isolate->factory()->undefined_value();
// If we have to copy more elements than we have in the source, we need to
// do special handling and conversion; that happens in the slow case.
if (length + offset <= source_ta->length_value()) {
CopyElementsFromTypedArray(*source_ta, *destination_ta, length, offset);
return *isolate->factory()->undefined_value();
}
}
// Fast cases for packed numbers kinds where we don't need to allocate.

View File

@ -124,57 +124,6 @@ DEFINE_METHOD(
}
);
// ES#sec-iterabletoarraylike Runtime Semantics: IterableToArrayLike( items )
function IterableToArrayLike(items) {
var iterable = GetMethod(items, iteratorSymbol);
if (!IS_UNDEFINED(iterable)) {
var internal_array = new InternalArray();
var i = 0;
for (var value of
{ [iteratorSymbol]() { return GetIterator(items, iterable) } }) {
internal_array[i] = value;
i++;
}
var array = [];
%MoveArrayContents(internal_array, array);
return array;
}
return TO_OBJECT(items);
}
// ES#sec-%typedarray%.from
// %TypedArray%.from ( source [ , mapfn [ , thisArg ] ] )
DEFINE_METHOD_LEN(
GlobalTypedArray,
'from'(source, mapfn, thisArg) {
if (!%IsConstructor(this)) throw %make_type_error(kNotConstructor, this);
var mapping;
if (!IS_UNDEFINED(mapfn)) {
if (!IS_CALLABLE(mapfn)) throw %make_type_error(kCalledNonCallable, this);
mapping = true;
} else {
mapping = false;
}
var arrayLike = IterableToArrayLike(source);
var length = TO_LENGTH(arrayLike.length);
var targetObject = TypedArrayCreate(this, length);
var value, mappedValue;
for (var i = 0; i < length; i++) {
value = arrayLike[i];
if (mapping) {
mappedValue = %_Call(mapfn, thisArg, value, i);
} else {
mappedValue = value;
}
targetObject[i] = mappedValue;
}
return targetObject;
},
1 /* Set function length. */
);
// TODO(bmeurer): Migrate this to a proper builtin.
function TypedArrayConstructor() {
throw %make_type_error(kConstructAbstractClass, "TypedArray");

View File

@ -1231,7 +1231,7 @@ RUNTIME_FUNCTION(Runtime_CreateDataProperty) {
RUNTIME_FUNCTION(Runtime_IterableToListCanBeElided) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSReceiver, obj, 0);
CONVERT_ARG_HANDLE_CHECKED(HeapObject, obj, 0);
if (!obj->IsJSObject()) return isolate->heap()->ToBoolean(false);

View File

@ -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,
@ -14,19 +16,65 @@ var typedArrayConstructors = [
Float64Array
];
function defaultValue(constr) {
if (constr == Float32Array || constr == Float64Array) return NaN;
return 0;
}
function assertArrayLikeEquals(value, expected, type) {
assertEquals(value.__proto__, type.prototype);
assertEquals(expected.length, value.length);
for (var i = 0; i < value.length; ++i) {
assertEquals(expected[i], value[i]);
}
}
(function() {
var source = [-0, 0, 2**40, 2**41, 2**42];
var arr = Float64Array.from(source);
assertArrayLikeEquals(arr, source, Float64Array);
arr = Float32Array.from(source);
assertArrayLikeEquals(arr, source, Float32Array);
})();
(function() {
var source = [-0, 0, , 2**42];
var expected = [-0, 0, NaN, 2**42];
var arr = Float64Array.from(source);
assertArrayLikeEquals(arr, expected, Float64Array);
arr = Float32Array.from(source);
assertArrayLikeEquals(arr, expected, Float32Array);
})();
(function() {
var source = {0: -0, 1: 0, 2: 2**40, 3: 2**41, 4: 2**42, length: 5};
var expected = [-0, 0, 2**40, 2**41, 2**42];
var arr = Float64Array.from(source);
assertArrayLikeEquals(arr, expected, Float64Array);
arr = Float32Array.from(source);
assertArrayLikeEquals(arr, expected, Float32Array);
})();
(function() {
var source = [-0, 0, , 2**42];
Object.getPrototypeOf(source)[2] = 27;
var expected = [-0, 0, 27, 2**42];
var arr = Float64Array.from(source);
assertArrayLikeEquals(arr, expected, Float64Array);
arr = Float32Array.from(source);
assertArrayLikeEquals(arr, expected, Float32Array);
})();
for (var constructor of typedArrayConstructors) {
assertEquals(1, constructor.from.length);
// TypedArray.from only callable on this subclassing %TypedArray%
assertThrows(function () {constructor.from.call(Array, [])}, TypeError);
function assertArrayLikeEquals(value, expected, type) {
assertEquals(value.__proto__, type.prototype);
assertEquals(expected.length, value.length);
for (var i = 0; i < value.length; ++i) {
assertEquals(expected[i], value[i]);
}
}
// Assert that calling mapfn with / without thisArg in sloppy and strict modes
// works as expected.
@ -47,6 +95,14 @@ for (var constructor of typedArrayConstructors) {
assertThrows(function() {constructor.from.call(1, [])}, TypeError);
assertThrows(function() {constructor.from.call(undefined, [])}, TypeError);
// Use a map function that returns non-numbers.
function mapper(value, index) {
return String.fromCharCode(value);
}
var d = defaultValue(constructor);
assertArrayLikeEquals(
constructor.from([72, 69, 89], mapper), [d, d, d], constructor);
// Converting from various other types, demonstrating that it can
// operate on array-like objects as well as iterables.
// TODO(littledan): constructors should have similar flexibility.
@ -72,12 +128,50 @@ for (var constructor of typedArrayConstructors) {
assertArrayLikeEquals(constructor.from(generator()),
[4, 5, 6], constructor);
// Check mapper is used for non-iterator case.
function mapper2(value, index) {
return value + 1;
}
var array_like = {
0: 1,
1: 2,
2: 3,
length: 3
};
assertArrayLikeEquals(constructor.from(array_like, mapper2),
[2, 3, 4], constructor);
// With a smi source. Step 10 will set len = 0.
var source = 1;
assertArrayLikeEquals(constructor.from(source), [], constructor);
assertThrows(function() { constructor.from(null); }, TypeError);
assertThrows(function() { constructor.from(undefined); }, TypeError);
assertThrows(function() { constructor.from([], null); }, TypeError);
assertThrows(function() { constructor.from([], 'noncallable'); },
TypeError);
source = [1, 2, 3];
source[Symbol.iterator] = undefined;
assertArrayLikeEquals(constructor.from(source), source, constructor);
source = [{ valueOf: function(){ return 42; }}];
source[Symbol.iterator] = undefined;
assertArrayLikeEquals(constructor.from(source), [42], constructor);
source = [1, 2, 3];
var proxy = new Proxy(source, {});
assertArrayLikeEquals(constructor.from(proxy), source, constructor);
proxy = new Proxy(source, {
get: function(target, name) {
if (name === Symbol.iterator) return target[name];
if (name === "length") return 3;
return target[name] + 1;
}
});
assertArrayLikeEquals(constructor.from(proxy), [2, 3, 4], constructor);
var nullIterator = {};
nullIterator[Symbol.iterator] = null;
assertArrayLikeEquals(constructor.from(nullIterator), [],
@ -90,6 +184,26 @@ for (var constructor of typedArrayConstructors) {
assertThrows(function() { constructor.from([], null); }, TypeError);
d = defaultValue(constructor);
let ta1 = new constructor(3).fill(1);
Object.defineProperty(ta1, "length", {get: function() {
return 6;
}});
delete ta1[Symbol.iterator];
delete ta1.__proto__[Symbol.iterator];
delete ta1.__proto__.__proto__[Symbol.iterator];
assertArrayLikeEquals(constructor.from(ta1), [1, 1, 1, d, d, d], constructor);
let ta2 = new constructor(3).fill(1);
Object.defineProperty(ta2, "length", {get: function() {
%ArrayBufferNeuter(ta2.buffer);
return 6;
}});
assertArrayLikeEquals(constructor.from(ta2), [d, d, d, d, d, d], constructor);
var o1 = {0: 0, 1: 1, 2: 2, length: 6};
assertArrayLikeEquals(constructor.from(o1), [0, 1, 2, d, d, d], constructor);
// Ensure iterator is only accessed once, and only invoked once
var called = 0;
var arr = [1, 2, 3];