Commit 53dfeb88 authored by Gus Caplan's avatar Gus Caplan Committed by Commit Bot

[Torque] port Array.from to torque

Bug: v8:9891
Change-Id: I320b5de731f1d3c03eb1b85de412e1f67196b049
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1985187Reviewed-by: 's avatarJakob Gruber <jgruber@chromium.org>
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/master@{#65654}
parent 97f6ac94
......@@ -953,6 +953,7 @@ torque_files = [
"src/builtins/array-find.tq",
"src/builtins/array-findindex.tq",
"src/builtins/array-foreach.tq",
"src/builtins/array-from.tq",
"src/builtins/array-isarray.tq",
"src/builtins/array-join.tq",
"src/builtins/array-lastindexof.tq",
......
// Copyright 2019 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.
namespace array {
// Array.from( items [, mapfn [, thisArg ] ] )
// ES #sec-array.from
transitioning javascript builtin
ArrayFrom(js-implicit context: NativeContext, receiver: JSAny)(...arguments):
JSReceiver {
// Use fast path if:
// * |items| is the only argument, and
// * the receiver is the Array function.
if (arguments.length == 1 && receiver == GetArrayFunction()) {
try {
return iterator::FastIterableToList(arguments[0]) otherwise Slow;
}
label Slow {
// fall through
}
}
const items = arguments[0];
const mapfn = arguments[1];
const thisArg = arguments[2];
// 1. Let C be the this value.
const c = receiver;
let mapping: bool;
// 2. If mapfn is undefined, let mapping be false.
if (mapfn == Undefined) {
mapping = false;
} else {
// a. If IsCallable(mapfn) is false, throw a TypeError exception.
if (!TaggedIsCallable(mapfn)) deferred {
ThrowTypeError(MessageTemplate::kCalledNonCallable, mapfn);
}
// b. Let mapping be true.
mapping = true;
}
// 4. Let usingIterator be ? GetMethod(items, @@iterator).
// 5. If usingIterator is not undefined, then
try {
const usingIterator = GetMethod(items, IteratorSymbolConstant())
otherwise IteratorIsUndefined, IteratorNotCallable;
let a: JSReceiver;
// a. If IsConstructor(C) is true, then
typeswitch (c) {
case (c: Constructor): {
// i. Let A be ? Construct(C).
a = Construct(c);
}
case (JSAny): {
// i. Let A be ? ArrayCreate(0).
a = ArrayCreate(0);
}
}
// c. Let iteratorRecord be ? GetIterator(items, sync, usingIterator).
const iteratorRecord = iterator::GetIterator(items, usingIterator);
const fastIteratorResultMap = GetIteratorResultMap();
// d. Let k be 0.
let k: Smi = 0;
// e. Repeat,
while (true) {
// i. If k ≥ 2^53-1, then
// 1. Let error be ThrowCompletion(a newly created TypeError object).
// 2. Return ? IteratorClose(iteratorRecord, error).
// The spec requires that we throw an exception if index reaches 2^53-1,
// but an empty loop would take >100 days to do this many iterations. To
// actually run for that long would require an iterator that never set
// done to true and a target array which somehow never ran out of
// memory, e.g. a proxy that discarded the values. Ignoring this case
// just means we would repeatedly call CreateDataProperty with index =
// 2^53
assert(k < kMaxSafeInteger);
// ii. Let Pk be ! ToString(k).
// iii. Let next be ? IteratorStep(iteratorRecord).
let next: JSReceiver;
try {
next = iterator::IteratorStep(iteratorRecord, fastIteratorResultMap)
otherwise NextIsFalse;
}
// iv. If next is false, then
label NextIsFalse {
// 1. Perform ? Set(A, "length", k, true).
array::SetPropertyLength(a, k);
// 2. Return A.
return a;
}
// v. Let nextValue be ? IteratorValue(next).
const nextValue = iterator::IteratorValue(next, fastIteratorResultMap);
let mappedValue: JSAny;
// vi. If mapping is true, then
if (mapping) {
// 1. Let mappedValue be Call(mapfn, thisArg, « nextValue, k »).
// 2. If mappedValue is an abrupt completion,
// return ? IteratorClose(iteratorRecord, mappedValue).
// 3. Set mappedValue to mappedValue.[[Value]].
try {
mappedValue = Call(
context, UnsafeCast<Callable>(mapfn), thisArg, nextValue, k);
} catch (e) {
iterator::IteratorCloseOnException(iteratorRecord, e);
}
} else {
mappedValue = nextValue;
}
// viii. Let defineStatus be
// CreateDataPropertyOrThrow(A, Pk, mappedValue).
// ix. If defineStatus is an abrupt completion,
// return ? IteratorClose(iteratorRecord, defineStatus).
try {
FastCreateDataProperty(a, k, mappedValue);
} catch (e) deferred {
iterator::IteratorCloseOnException(iteratorRecord, e);
}
// x. Set k to k + 1.
k += 1;
}
unreachable;
}
label IteratorIsUndefined {
// 6. NOTE: items is not an Iterable so assume it is an array-like object.
// 7. Let arrayLike be ! ToObject(items).
const arrayLike = ToObject_Inline(context, items);
// 8. Let len be ? LengthOfArrayLike(arrayLike).
const len = GetLengthProperty(arrayLike);
let a: JSReceiver;
// 9. If IsConstructor(C) is true, then
typeswitch (c) {
case (c: Constructor): {
// a. Let A be ? Construct(C, « len »).
a = Construct(c, len);
}
case (JSAny): {
// a. Let A be ? ArrayCreate(len).
a = ArrayCreate(len);
}
}
// 11. Let k be 0.
let k: Smi = 0;
// 12. Repeat, while k < len
while (k < len) {
// a. Let Pk be ! ToString(k).
// b. Let kValue be ? Get(arrayLike, Pk).
const kValue = GetProperty(arrayLike, k);
let mappedValue: JSAny;
// c. If mapping is true, then
if (mapping) {
// i. Let mappedValue be ? Call(mapfn, thisArg, « kValue, k »).
mappedValue =
Call(context, UnsafeCast<Callable>(mapfn), thisArg, kValue, k);
} else {
// d. Else, let mappedValue be kValue.
mappedValue = kValue;
}
// e. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
FastCreateDataProperty(a, k, mappedValue);
// f. Set k to k + 1.
k += 1;
}
// 13. Perform ? Set(A, "length", len, true).
array::SetPropertyLength(a, len);
// 14. Return A.
return a;
}
label IteratorNotCallable(_value: JSAny) deferred {
ThrowTypeError(MessageTemplate::kIteratorSymbolNonCallable);
}
}
}
......@@ -231,6 +231,7 @@ const kBigIntMaxLength: constexpr intptr generates 'BigInt::kMaxLength';
extern enum MessageTemplate {
kInvalidArrayBufferLength,
kInvalidArrayLength,
kInvalidIndex,
kNotConstructor,
kCalledNonCallable,
kCalledOnNullOrUndefined,
......@@ -465,6 +466,7 @@ extern macro BuildAppendJSArray(
extern macro EnsureArrayPushable(implicit context: Context)(Map): ElementsKind
labels Bailout;
// TODO: Reduce duplication once varargs are supported in macros.
extern macro Construct(implicit context: Context)(Constructor): JSReceiver;
extern macro Construct(implicit context: Context)(
Constructor, JSAny): JSReceiver;
extern macro Construct(implicit context: Context)(
......@@ -951,6 +953,10 @@ macro GetObjectFunction(implicit context: Context)(): JSFunction {
return UnsafeCast<JSFunction>(
LoadNativeContext(context)[NativeContextSlot::OBJECT_FUNCTION_INDEX]);
}
macro GetArrayFunction(implicit context: Context)(): JSFunction {
return UnsafeCast<JSFunction>(
LoadNativeContext(context)[NativeContextSlot::ARRAY_FUNCTION_INDEX]);
}
macro GetArrayBufferFunction(implicit context: Context)(): Constructor {
return UnsafeCast<Constructor>(
LoadNativeContext(context)[NativeContextSlot::ARRAY_BUFFER_FUN_INDEX]);
......
......@@ -543,219 +543,6 @@ class ArrayPopulatorAssembler : public CodeStubAssembler {
}
};
// ES #sec-array.from
TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler) {
TNode<Context> context = CAST(Parameter(Descriptor::kContext));
TNode<Int32T> argc =
UncheckedCast<Int32T>(Parameter(Descriptor::kJSActualArgumentsCount));
CodeStubArguments args(this, argc);
TNode<Object> items = args.GetOptionalArgumentValue(0);
TNode<Object> receiver = args.GetReceiver();
Label fast_iterate(this), normal_iterate(this);
// Use fast path if:
// * |items| is the only argument, and
// * the receiver is the Array function.
GotoIfNot(Word32Equal(argc, Int32Constant(1)), &normal_iterate);
TNode<Object> array_function = LoadContextElement(
LoadNativeContext(context), Context::ARRAY_FUNCTION_INDEX);
Branch(TaggedEqual(array_function, receiver), &fast_iterate, &normal_iterate);
BIND(&fast_iterate);
{
IteratorBuiltinsAssembler iterator_assembler(state());
TVARIABLE(Object, var_fast_result);
iterator_assembler.FastIterableToList(context, items, &var_fast_result,
&normal_iterate);
args.PopAndReturn(var_fast_result.value());
}
BIND(&normal_iterate);
TNode<Object> map_function = args.GetOptionalArgumentValue(1);
// If map_function is not undefined, then ensure it's callable else throw.
{
Label no_error(this), error(this);
GotoIf(IsUndefined(map_function), &no_error);
GotoIf(TaggedIsSmi(map_function), &error);
Branch(IsCallable(CAST(map_function)), &no_error, &error);
BIND(&error);
ThrowTypeError(context, MessageTemplate::kCalledNonCallable, map_function);
BIND(&no_error);
}
Label iterable(this), not_iterable(this), finished(this), if_exception(this);
TNode<Object> this_arg = args.GetOptionalArgumentValue(2);
// The spec doesn't require ToObject to be called directly on the iterable
// branch, but it's part of GetMethod that is in the spec.
TNode<JSReceiver> array_like = ToObject_Inline(context, items);
TVARIABLE(Object, array);
TVARIABLE(Number, length);
// Determine whether items[Symbol.iterator] is defined:
IteratorBuiltinsAssembler iterator_assembler(state());
TNode<Object> iterator_method =
iterator_assembler.GetIteratorMethod(context, array_like);
Branch(IsNullOrUndefined(iterator_method), &not_iterable, &iterable);
BIND(&iterable);
{
TVARIABLE(Number, index, SmiConstant(0));
TVARIABLE(Object, var_exception);
Label loop(this, &index), loop_done(this),
on_exception(this, Label::kDeferred),
index_overflow(this, Label::kDeferred);
// Check that the method is callable.
{
Label get_method_not_callable(this, Label::kDeferred), next(this);
GotoIf(TaggedIsSmi(iterator_method), &get_method_not_callable);
GotoIfNot(IsCallable(CAST(iterator_method)), &get_method_not_callable);
Goto(&next);
BIND(&get_method_not_callable);
ThrowTypeError(context, MessageTemplate::kCalledNonCallable,
iterator_method);
BIND(&next);
}
// Construct the output array with empty length.
array = ConstructArrayLike(context, receiver);
// Actually get the iterator and throw if the iterator method does not yield
// one.
IteratorRecord iterator_record =
iterator_assembler.GetIterator(context, items, iterator_method);
TNode<NativeContext> native_context = LoadNativeContext(context);
TNode<Map> fast_iterator_result_map = CAST(
LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX));
Goto(&loop);
BIND(&loop);
{
// Loop while iterator is not done.
TNode<JSReceiver> next = iterator_assembler.IteratorStep(
context, iterator_record, &loop_done, fast_iterator_result_map);
TVARIABLE(Object, value,
iterator_assembler.IteratorValue(context, next,
fast_iterator_result_map));
// If a map_function is supplied then call it (using this_arg as
// receiver), on the value returned from the iterator. Exceptions are
// caught so the iterator can be closed.
{
Label next(this);
GotoIf(IsUndefined(map_function), &next);
CSA_ASSERT(this, IsCallable(CAST(map_function)));
TNode<Object> v =
CallJS(CodeFactory::Call(isolate()), context, map_function,
this_arg, value.value(), index.value());
GotoIfException(v, &on_exception, &var_exception);
value = v;
Goto(&next);
BIND(&next);
}
// Store the result in the output object (catching any exceptions so the
// iterator can be closed).
TNode<Object> define_status =
CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
index.value(), value.value());
GotoIfException(define_status, &on_exception, &var_exception);
index = NumberInc(index.value());
// The spec requires that we throw an exception if index reaches 2^53-1,
// but an empty loop would take >100 days to do this many iterations. To
// actually run for that long would require an iterator that never set
// done to true and a target array which somehow never ran out of memory,
// e.g. a proxy that discarded the values. Ignoring this case just means
// we would repeatedly call CreateDataProperty with index = 2^53.
CSA_ASSERT_BRANCH(this, [&](Label* ok, Label* not_ok) {
BranchIfNumberRelationalComparison(Operation::kLessThan, index.value(),
NumberConstant(kMaxSafeInteger), ok,
not_ok);
});
Goto(&loop);
}
BIND(&loop_done);
{
length = index;
Goto(&finished);
}
BIND(&on_exception);
{
// Close the iterator, rethrowing either the passed exception or
// exceptions thrown during the close.
iterator_assembler.IteratorCloseOnException(context, iterator_record,
var_exception.value());
}
}
BIND(&not_iterable);
{
// Treat array_like as an array and try to get its length.
length = ToLength_Inline(
context, GetProperty(context, array_like, factory()->length_string()));
// Construct an array using the receiver as constructor with the same length
// as the input array.
array = ConstructArrayLike(context, receiver, length.value());
TVARIABLE(Number, index, SmiConstant(0));
GotoIf(TaggedEqual(length.value(), SmiConstant(0)), &finished);
// Loop from 0 to length-1.
{
Label loop(this, &index);
Goto(&loop);
BIND(&loop);
TVARIABLE(Object, value);
value = GetProperty(context, array_like, index.value());
// If a map_function is supplied then call it (using this_arg as
// receiver), on the value retrieved from the array.
{
Label next(this);
GotoIf(IsUndefined(map_function), &next);
CSA_ASSERT(this, IsCallable(CAST(map_function)));
value = CallJS(CodeFactory::Call(isolate()), context, map_function,
this_arg, value.value(), index.value());
Goto(&next);
BIND(&next);
}
// Store the result in the output object.
CallRuntime(Runtime::kCreateDataProperty, context, array.value(),
index.value(), value.value());
index = NumberInc(index.value());
BranchIfNumberRelationalComparison(Operation::kLessThan, index.value(),
length.value(), &loop, &finished);
}
}
BIND(&finished);
// Finally set the length on the output and return it.
SetPropertyLength(context, array.value(), length.value());
args.PopAndReturn(array.value());
}
TF_BUILTIN(TypedArrayPrototypeMap, ArrayBuiltinsAssembler) {
TNode<IntPtrT> argc =
ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount));
......
......@@ -304,8 +304,6 @@ namespace internal {
CPP(ArrayConcat) \
/* ES6 #sec-array.prototype.fill */ \
CPP(ArrayPrototypeFill) \
/* ES6 #sec-array.from */ \
TFJ(ArrayFrom, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* ES7 #sec-array.prototype.includes */ \
TFS(ArrayIncludesSmiOrObject, kElements, kSearchElement, kLength, \
kFromIndex) \
......
......@@ -368,7 +368,7 @@ TF_BUILTIN(IterableToListMayPreserveHoles, IteratorBuiltinsAssembler) {
void IteratorBuiltinsAssembler::FastIterableToList(
TNode<Context> context, TNode<Object> iterable,
TVariable<Object>* var_result, Label* slow) {
TVariable<JSArray>* var_result, Label* slow) {
Label done(this), check_string(this), check_map(this), check_set(this);
GotoIfNot(
......@@ -377,8 +377,8 @@ void IteratorBuiltinsAssembler::FastIterableToList(
&check_string);
// Fast path for fast JSArray.
*var_result =
CallBuiltin(Builtins::kCloneFastJSArrayFillingHoles, context, iterable);
*var_result = CAST(
CallBuiltin(Builtins::kCloneFastJSArrayFillingHoles, context, iterable));
Goto(&done);
BIND(&check_string);
......@@ -394,7 +394,7 @@ void IteratorBuiltinsAssembler::FastIterableToList(
GotoIf(
IntPtrGreaterThan(length, IntPtrConstant(JSArray::kMaxFastArrayLength)),
slow);
*var_result = CallBuiltin(Builtins::kStringToList, context, iterable);
*var_result = CAST(CallBuiltin(Builtins::kStringToList, context, iterable));
Goto(&done);
}
......@@ -405,7 +405,8 @@ void IteratorBuiltinsAssembler::FastIterableToList(
state(), iterable, context, &map_fast_call, &check_set);
BIND(&map_fast_call);
*var_result = CallBuiltin(Builtins::kMapIteratorToList, context, iterable);
*var_result =
CAST(CallBuiltin(Builtins::kMapIteratorToList, context, iterable));
Goto(&done);
}
......@@ -417,13 +418,20 @@ void IteratorBuiltinsAssembler::FastIterableToList(
BIND(&set_fast_call);
*var_result =
CallBuiltin(Builtins::kSetOrSetIteratorToList, context, iterable);
CAST(CallBuiltin(Builtins::kSetOrSetIteratorToList, context, iterable));
Goto(&done);
}
BIND(&done);
}
TNode<JSArray> IteratorBuiltinsAssembler::FastIterableToList(
TNode<Context> context, TNode<Object> iterable, Label* slow) {
TVARIABLE(JSArray, var_fast_result);
FastIterableToList(context, iterable, &var_fast_result, slow);
return var_fast_result.value();
}
// This builtin loads the property Symbol.iterator as the iterator, and has fast
// paths for fast arrays, for primitive strings, for sets and set iterators, and
// for map iterators. These fast paths will only be taken if Symbol.iterator and
......@@ -443,7 +451,7 @@ TF_BUILTIN(IterableToListWithSymbolLookup, IteratorBuiltinsAssembler) {
GotoIfForceSlowPath(&slow_path);
TVARIABLE(Object, var_result);
TVARIABLE(JSArray, var_result);
FastIterableToList(context, iterable, &var_result, &slow_path);
Return(var_result.value());
......
......@@ -79,7 +79,9 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler {
TNode<Object> iterable);
void FastIterableToList(TNode<Context> context, TNode<Object> iterable,
TVariable<Object>* var_result, Label* slow);
TVariable<JSArray>* var_result, Label* slow);
TNode<JSArray> FastIterableToList(TNode<Context> context,
TNode<Object> iterable, Label* slow);
};
} // namespace internal
......
......@@ -15,10 +15,15 @@ namespace iterator {
next: JSAny;
}
extern macro IteratorBuiltinsAssembler::FastIterableToList(
implicit context: Context)(JSAny): JSArray labels Slow;
extern macro IteratorBuiltinsAssembler::GetIteratorMethod(
implicit context: Context)(JSAny): JSAny;
extern macro IteratorBuiltinsAssembler::GetIterator(
implicit context: Context)(JSAny): IteratorRecord;
extern macro IteratorBuiltinsAssembler::GetIterator(
implicit context: Context)(JSAny, JSAny): IteratorRecord;
extern macro IteratorBuiltinsAssembler::IteratorStep(
implicit context: Context)(IteratorRecord): JSReceiver
......
......@@ -27,6 +27,7 @@ extern enum NativeContextSlot extends intptr constexpr 'Context::Field' {
ARRAY_BUFFER_FUN_INDEX,
ARRAY_BUFFER_NOINIT_FUN_INDEX,
ARRAY_BUFFER_MAP_INDEX,
ARRAY_FUNCTION_INDEX,
ARRAY_JOIN_STACK_INDEX,
OBJECT_FUNCTION_INDEX,
ITERATOR_RESULT_MAP_INDEX,
......
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