diff --git a/BUILD.gn b/BUILD.gn index bed2ac3193..633021ab10 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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", diff --git a/src/builtins/array-from.tq b/src/builtins/array-from.tq new file mode 100644 index 0000000000..6eb3b00693 --- /dev/null +++ b/src/builtins/array-from.tq @@ -0,0 +1,184 @@ +// 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(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(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); + } + } +} diff --git a/src/builtins/base.tq b/src/builtins/base.tq index d99fd67358..0683cec309 100644 --- a/src/builtins/base.tq +++ b/src/builtins/base.tq @@ -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( LoadNativeContext(context)[NativeContextSlot::OBJECT_FUNCTION_INDEX]); } +macro GetArrayFunction(implicit context: Context)(): JSFunction { + return UnsafeCast( + LoadNativeContext(context)[NativeContextSlot::ARRAY_FUNCTION_INDEX]); +} macro GetArrayBufferFunction(implicit context: Context)(): Constructor { return UnsafeCast( LoadNativeContext(context)[NativeContextSlot::ARRAY_BUFFER_FUN_INDEX]); diff --git a/src/builtins/builtins-array-gen.cc b/src/builtins/builtins-array-gen.cc index 619ab898cd..72a28f431e 100644 --- a/src/builtins/builtins-array-gen.cc +++ b/src/builtins/builtins-array-gen.cc @@ -543,219 +543,6 @@ class ArrayPopulatorAssembler : public CodeStubAssembler { } }; -// ES #sec-array.from -TF_BUILTIN(ArrayFrom, ArrayPopulatorAssembler) { - TNode context = CAST(Parameter(Descriptor::kContext)); - TNode argc = - UncheckedCast(Parameter(Descriptor::kJSActualArgumentsCount)); - - CodeStubArguments args(this, argc); - TNode items = args.GetOptionalArgumentValue(0); - TNode 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 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 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 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 array_like = ToObject_Inline(context, items); - - TVARIABLE(Object, array); - TVARIABLE(Number, length); - - // Determine whether items[Symbol.iterator] is defined: - IteratorBuiltinsAssembler iterator_assembler(state()); - TNode iterator_method = - iterator_assembler.GetIteratorMethod(context, array_like); - Branch(IsNullOrUndefined(iterator_method), ¬_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 native_context = LoadNativeContext(context); - TNode fast_iterator_result_map = CAST( - LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX)); - - Goto(&loop); - - BIND(&loop); - { - // Loop while iterator is not done. - TNode 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 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 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(¬_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 argc = ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h index 64a14b4024..5bff6637d5 100644 --- a/src/builtins/builtins-definitions.h +++ b/src/builtins/builtins-definitions.h @@ -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) \ diff --git a/src/builtins/builtins-iterator-gen.cc b/src/builtins/builtins-iterator-gen.cc index f14e979ce9..e9dca2dbc3 100644 --- a/src/builtins/builtins-iterator-gen.cc +++ b/src/builtins/builtins-iterator-gen.cc @@ -368,7 +368,7 @@ TF_BUILTIN(IterableToListMayPreserveHoles, IteratorBuiltinsAssembler) { void IteratorBuiltinsAssembler::FastIterableToList( TNode context, TNode iterable, - TVariable* var_result, Label* slow) { + TVariable* 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 IteratorBuiltinsAssembler::FastIterableToList( + TNode context, TNode 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()); diff --git a/src/builtins/builtins-iterator-gen.h b/src/builtins/builtins-iterator-gen.h index 2af50bbf2f..61665d2825 100644 --- a/src/builtins/builtins-iterator-gen.h +++ b/src/builtins/builtins-iterator-gen.h @@ -79,7 +79,9 @@ class IteratorBuiltinsAssembler : public CodeStubAssembler { TNode iterable); void FastIterableToList(TNode context, TNode iterable, - TVariable* var_result, Label* slow); + TVariable* var_result, Label* slow); + TNode FastIterableToList(TNode context, + TNode iterable, Label* slow); }; } // namespace internal diff --git a/src/builtins/iterator.tq b/src/builtins/iterator.tq index 82c230202b..37def238a4 100644 --- a/src/builtins/iterator.tq +++ b/src/builtins/iterator.tq @@ -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 diff --git a/src/objects/contexts.tq b/src/objects/contexts.tq index 766cbbf917..0266f90977 100644 --- a/src/objects/contexts.tq +++ b/src/objects/contexts.tq @@ -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,