|  | // 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. | 
|  |  | 
|  | #include "src/builtins/builtins-iterator-gen.h" | 
|  | #include "src/builtins/growable-fixed-array-gen.h" | 
|  |  | 
|  | #include "src/builtins/builtins-collections-gen.h" | 
|  | #include "src/builtins/builtins-string-gen.h" | 
|  | #include "src/builtins/builtins-utils-gen.h" | 
|  | #include "src/builtins/builtins.h" | 
|  | #include "src/codegen/code-stub-assembler.h" | 
|  | #include "src/heap/factory-inl.h" | 
|  |  | 
|  | namespace v8 { | 
|  | namespace internal { | 
|  |  | 
|  | using IteratorRecord = TorqueStructIteratorRecord; | 
|  | using compiler::Node; | 
|  |  | 
|  | TNode<Object> IteratorBuiltinsAssembler::GetIteratorMethod( | 
|  | TNode<Context> context, TNode<Object> object) { | 
|  | return GetProperty(context, object, factory()->iterator_symbol()); | 
|  | } | 
|  |  | 
|  | IteratorRecord IteratorBuiltinsAssembler::GetIterator(TNode<Context> context, | 
|  | TNode<Object> object) { | 
|  | TNode<Object> method = GetIteratorMethod(context, object); | 
|  | return GetIterator(context, object, method); | 
|  | } | 
|  |  | 
|  | IteratorRecord IteratorBuiltinsAssembler::GetIterator(TNode<Context> context, | 
|  | TNode<Object> object, | 
|  | TNode<Object> method) { | 
|  | Label if_not_callable(this, Label::kDeferred), if_callable(this); | 
|  | GotoIf(TaggedIsSmi(method), &if_not_callable); | 
|  | Branch(IsCallable(CAST(method)), &if_callable, &if_not_callable); | 
|  |  | 
|  | BIND(&if_not_callable); | 
|  | CallRuntime(Runtime::kThrowIteratorError, context, object); | 
|  | Unreachable(); | 
|  |  | 
|  | BIND(&if_callable); | 
|  | { | 
|  | TNode<Object> iterator = Call(context, method, object); | 
|  |  | 
|  | Label get_next(this), if_notobject(this, Label::kDeferred); | 
|  | GotoIf(TaggedIsSmi(iterator), &if_notobject); | 
|  | Branch(IsJSReceiver(CAST(iterator)), &get_next, &if_notobject); | 
|  |  | 
|  | BIND(&if_notobject); | 
|  | CallRuntime(Runtime::kThrowSymbolIteratorInvalid, context); | 
|  | Unreachable(); | 
|  |  | 
|  | BIND(&get_next); | 
|  | TNode<Object> next = | 
|  | GetProperty(context, iterator, factory()->next_string()); | 
|  | return IteratorRecord{TNode<JSReceiver>::UncheckedCast(iterator), | 
|  | TNode<Object>::UncheckedCast(next)}; | 
|  | } | 
|  | } | 
|  |  | 
|  | TNode<JSReceiver> IteratorBuiltinsAssembler::IteratorStep( | 
|  | TNode<Context> context, const IteratorRecord& iterator, Label* if_done, | 
|  | base::Optional<TNode<Map>> fast_iterator_result_map) { | 
|  | DCHECK_NOT_NULL(if_done); | 
|  | // 1. a. Let result be ? Invoke(iterator, "next", « »). | 
|  | TNode<Object> result = Call(context, iterator.next, iterator.object); | 
|  |  | 
|  | // 3. If Type(result) is not Object, throw a TypeError exception. | 
|  | Label if_notobject(this, Label::kDeferred), return_result(this); | 
|  | GotoIf(TaggedIsSmi(result), &if_notobject); | 
|  | TNode<HeapObject> heap_object_result = CAST(result); | 
|  | TNode<Map> result_map = LoadMap(heap_object_result); | 
|  |  | 
|  | if (fast_iterator_result_map) { | 
|  | // Fast iterator result case: | 
|  | Label if_generic(this); | 
|  |  | 
|  | // 4. Return result. | 
|  | GotoIfNot(TaggedEqual(result_map, *fast_iterator_result_map), &if_generic); | 
|  |  | 
|  | // IteratorComplete | 
|  | // 2. Return ToBoolean(? Get(iterResult, "done")). | 
|  | TNode<Object> done = | 
|  | LoadObjectField(heap_object_result, JSIteratorResult::kDoneOffset); | 
|  | BranchIfToBooleanIsTrue(done, if_done, &return_result); | 
|  |  | 
|  | BIND(&if_generic); | 
|  | } | 
|  |  | 
|  | // Generic iterator result case: | 
|  | { | 
|  | // 3. If Type(result) is not Object, throw a TypeError exception. | 
|  | GotoIfNot(IsJSReceiverMap(result_map), &if_notobject); | 
|  |  | 
|  | // IteratorComplete | 
|  | // 2. Return ToBoolean(? Get(iterResult, "done")). | 
|  | TNode<Object> done = | 
|  | GetProperty(context, heap_object_result, factory()->done_string()); | 
|  | BranchIfToBooleanIsTrue(done, if_done, &return_result); | 
|  | } | 
|  |  | 
|  | BIND(&if_notobject); | 
|  | CallRuntime(Runtime::kThrowIteratorResultNotAnObject, context, result); | 
|  | Unreachable(); | 
|  |  | 
|  | BIND(&return_result); | 
|  | return CAST(heap_object_result); | 
|  | } | 
|  |  | 
|  | TNode<Object> IteratorBuiltinsAssembler::IteratorValue( | 
|  | TNode<Context> context, TNode<JSReceiver> result, | 
|  | base::Optional<TNode<Map>> fast_iterator_result_map) { | 
|  | Label exit(this); | 
|  | TVARIABLE(Object, var_value); | 
|  | if (fast_iterator_result_map) { | 
|  | // Fast iterator result case: | 
|  | Label if_generic(this); | 
|  | TNode<Map> map = LoadMap(result); | 
|  | GotoIfNot(TaggedEqual(map, *fast_iterator_result_map), &if_generic); | 
|  | var_value = LoadObjectField(result, JSIteratorResult::kValueOffset); | 
|  | Goto(&exit); | 
|  |  | 
|  | BIND(&if_generic); | 
|  | } | 
|  |  | 
|  | // Generic iterator result case: | 
|  | var_value = GetProperty(context, result, factory()->value_string()); | 
|  | Goto(&exit); | 
|  |  | 
|  | BIND(&exit); | 
|  | return var_value.value(); | 
|  | } | 
|  |  | 
|  | TNode<JSArray> IteratorBuiltinsAssembler::IterableToList( | 
|  | TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn) { | 
|  | GrowableFixedArray values(state()); | 
|  | FillFixedArrayFromIterable(context, iterable, iterator_fn, &values); | 
|  | return values.ToJSArray(context); | 
|  | } | 
|  |  | 
|  | TNode<FixedArray> IteratorBuiltinsAssembler::IterableToFixedArray( | 
|  | TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn) { | 
|  | GrowableFixedArray values(state()); | 
|  | FillFixedArrayFromIterable(context, iterable, iterator_fn, &values); | 
|  | TNode<FixedArray> new_array = values.ToFixedArray(); | 
|  | return new_array; | 
|  | } | 
|  |  | 
|  | void IteratorBuiltinsAssembler::FillFixedArrayFromIterable( | 
|  | TNode<Context> context, TNode<Object> iterable, TNode<Object> iterator_fn, | 
|  | GrowableFixedArray* values) { | 
|  | // 1. Let iteratorRecord be ? GetIterator(items, method). | 
|  | IteratorRecord iterator_record = GetIterator(context, iterable, iterator_fn); | 
|  |  | 
|  | // 2. Let values be a new empty List. | 
|  |  | 
|  | // The GrowableFixedArray has already been created. It's ok if we do this step | 
|  | // out of order, since creating an empty List is not observable. | 
|  |  | 
|  | Label loop_start(this, {values->var_array(), values->var_length(), | 
|  | values->var_capacity()}), | 
|  | done(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<JSReceiver> next = IteratorStep(context, iterator_record, &done); | 
|  | //  b. If next is not false, then | 
|  | //   i. Let nextValue be ? IteratorValue(next). | 
|  | TNode<Object> next_value = IteratorValue(context, next); | 
|  | //   ii. Append nextValue to the end of the List values. | 
|  | values->Push(next_value); | 
|  | Goto(&loop_start); | 
|  | } | 
|  |  | 
|  | BIND(&done); | 
|  | } | 
|  |  | 
|  | TF_BUILTIN(IterableToList, IteratorBuiltinsAssembler) { | 
|  | auto context = Parameter<Context>(Descriptor::kContext); | 
|  | auto iterable = Parameter<Object>(Descriptor::kIterable); | 
|  | auto iterator_fn = Parameter<Object>(Descriptor::kIteratorFn); | 
|  |  | 
|  | Return(IterableToList(context, iterable, iterator_fn)); | 
|  | } | 
|  |  | 
|  | TF_BUILTIN(IterableToFixedArray, IteratorBuiltinsAssembler) { | 
|  | auto context = Parameter<Context>(Descriptor::kContext); | 
|  | auto iterable = Parameter<Object>(Descriptor::kIterable); | 
|  | auto iterator_fn = Parameter<Object>(Descriptor::kIteratorFn); | 
|  |  | 
|  | Return(IterableToFixedArray(context, iterable, iterator_fn)); | 
|  | } | 
|  |  | 
|  | TF_BUILTIN(IterableToFixedArrayForWasm, IteratorBuiltinsAssembler) { | 
|  | auto context = Parameter<Context>(Descriptor::kContext); | 
|  | auto iterable = Parameter<Object>(Descriptor::kIterable); | 
|  | auto expected_length = Parameter<Smi>(Descriptor::kExpectedLength); | 
|  |  | 
|  | TNode<Object> iterator_fn = GetIteratorMethod(context, iterable); | 
|  | GrowableFixedArray values(state()); | 
|  |  | 
|  | Label done(this); | 
|  |  | 
|  | FillFixedArrayFromIterable(context, iterable, iterator_fn, &values); | 
|  |  | 
|  | GotoIf(WordEqual(SmiUntag(expected_length), values.var_length()->value()), | 
|  | &done); | 
|  | Return(CallRuntime( | 
|  | Runtime::kThrowTypeError, context, | 
|  | SmiConstant(MessageTemplate::kWasmTrapMultiReturnLengthMismatch))); | 
|  |  | 
|  | BIND(&done); | 
|  | Return(values.var_array()->value()); | 
|  | } | 
|  |  | 
|  | TNode<JSArray> IteratorBuiltinsAssembler::StringListFromIterable( | 
|  | TNode<Context> context, TNode<Object> iterable) { | 
|  | Label done(this); | 
|  | GrowableFixedArray list(state()); | 
|  | // 1. If iterable is undefined, then | 
|  | //   a. Return a new empty List. | 
|  | GotoIf(IsUndefined(iterable), &done); | 
|  |  | 
|  | // 2. Let iteratorRecord be ? GetIterator(items). | 
|  | IteratorRecord iterator_record = GetIterator(context, iterable); | 
|  |  | 
|  | // 3. Let list be a new empty List. | 
|  |  | 
|  | Label loop_start(this, | 
|  | {list.var_array(), list.var_length(), list.var_capacity()}); | 
|  | Goto(&loop_start); | 
|  | // 4. Let next be true. | 
|  | // 5. Repeat, while next is not false | 
|  | Label if_isnotstringtype(this, Label::kDeferred), | 
|  | if_exception(this, Label::kDeferred); | 
|  | BIND(&loop_start); | 
|  | { | 
|  | //  a. Set next to ? IteratorStep(iteratorRecord). | 
|  | TNode<JSReceiver> next = IteratorStep(context, iterator_record, &done); | 
|  | //  b. If next is not false, then | 
|  | //   i. Let nextValue be ? IteratorValue(next). | 
|  | TNode<Object> next_value = IteratorValue(context, next); | 
|  | //   ii. If Type(nextValue) is not String, then | 
|  | GotoIf(TaggedIsSmi(next_value), &if_isnotstringtype); | 
|  | TNode<Uint16T> next_value_type = LoadInstanceType(CAST(next_value)); | 
|  | GotoIfNot(IsStringInstanceType(next_value_type), &if_isnotstringtype); | 
|  | //   iii. Append nextValue to the end of the List list. | 
|  | list.Push(next_value); | 
|  | Goto(&loop_start); | 
|  | // 5.b.ii | 
|  | BIND(&if_isnotstringtype); | 
|  | { | 
|  | // 1. Let error be ThrowCompletion(a newly created TypeError object). | 
|  | TVARIABLE(Object, var_exception); | 
|  | { | 
|  | compiler::ScopedExceptionHandler handler(this, &if_exception, | 
|  | &var_exception); | 
|  | CallRuntime(Runtime::kThrowTypeError, context, | 
|  | SmiConstant(MessageTemplate::kIterableYieldedNonString), | 
|  | next_value); | 
|  | } | 
|  | Unreachable(); | 
|  |  | 
|  | // 2. Return ? IteratorClose(iteratorRecord, error). | 
|  | BIND(&if_exception); | 
|  | IteratorCloseOnException(context, iterator_record); | 
|  | CallRuntime(Runtime::kReThrow, context, var_exception.value()); | 
|  | Unreachable(); | 
|  | } | 
|  | } | 
|  |  | 
|  | BIND(&done); | 
|  | // 6. Return list. | 
|  | return list.ToJSArray(context); | 
|  | } | 
|  |  | 
|  | TF_BUILTIN(StringListFromIterable, IteratorBuiltinsAssembler) { | 
|  | auto context = Parameter<Context>(Descriptor::kContext); | 
|  | auto iterable = Parameter<Object>(Descriptor::kIterable); | 
|  |  | 
|  | Return(StringListFromIterable(context, iterable)); | 
|  | } | 
|  |  | 
|  | // This builtin always returns a new JSArray and is thus safe to use even in the | 
|  | // presence of code that may call back into user-JS. This builtin will take the | 
|  | // fast path if the iterable is a fast array and the Array prototype and the | 
|  | // Symbol.iterator is untouched. The fast path skips the iterator and copies the | 
|  | // backing store to the new array. Note that if the array has holes, the holes | 
|  | // will be copied to the new array, which is inconsistent with the behavior of | 
|  | // an actual iteration, where holes should be replaced with undefined (if the | 
|  | // prototype has no elements). To maintain the correct behavior for holey | 
|  | // arrays, use the builtins IterableToList or IterableToListWithSymbolLookup. | 
|  | TF_BUILTIN(IterableToListMayPreserveHoles, IteratorBuiltinsAssembler) { | 
|  | auto context = Parameter<Context>(Descriptor::kContext); | 
|  | auto iterable = Parameter<Object>(Descriptor::kIterable); | 
|  | auto iterator_fn = Parameter<Object>(Descriptor::kIteratorFn); | 
|  |  | 
|  | Label slow_path(this); | 
|  |  | 
|  | GotoIfNot(IsFastJSArrayWithNoCustomIteration(context, iterable), &slow_path); | 
|  |  | 
|  | // The fast path will copy holes to the new array. | 
|  | TailCallBuiltin(Builtins::kCloneFastJSArray, context, iterable); | 
|  |  | 
|  | BIND(&slow_path); | 
|  | TailCallBuiltin(Builtins::kIterableToList, context, iterable, iterator_fn); | 
|  | } | 
|  |  | 
|  | void IteratorBuiltinsAssembler::FastIterableToList( | 
|  | TNode<Context> context, TNode<Object> iterable, | 
|  | TVariable<JSArray>* var_result, Label* slow) { | 
|  | Label done(this), check_string(this), check_map(this), check_set(this); | 
|  |  | 
|  | GotoIfNot( | 
|  | Word32Or(IsFastJSArrayWithNoCustomIteration(context, iterable), | 
|  | IsFastJSArrayForReadWithNoCustomIteration(context, iterable)), | 
|  | &check_string); | 
|  |  | 
|  | // Fast path for fast JSArray. | 
|  | *var_result = CAST( | 
|  | CallBuiltin(Builtins::kCloneFastJSArrayFillingHoles, context, iterable)); | 
|  | Goto(&done); | 
|  |  | 
|  | BIND(&check_string); | 
|  | { | 
|  | Label string_maybe_fast_call(this); | 
|  | StringBuiltinsAssembler string_assembler(state()); | 
|  | string_assembler.BranchIfStringPrimitiveWithNoCustomIteration( | 
|  | iterable, context, &string_maybe_fast_call, &check_map); | 
|  |  | 
|  | BIND(&string_maybe_fast_call); | 
|  | const TNode<IntPtrT> length = LoadStringLengthAsWord(CAST(iterable)); | 
|  | // Use string length as conservative approximation of number of codepoints. | 
|  | GotoIf( | 
|  | IntPtrGreaterThan(length, IntPtrConstant(JSArray::kMaxFastArrayLength)), | 
|  | slow); | 
|  | *var_result = CAST(CallBuiltin(Builtins::kStringToList, context, iterable)); | 
|  | Goto(&done); | 
|  | } | 
|  |  | 
|  | BIND(&check_map); | 
|  | { | 
|  | Label map_fast_call(this); | 
|  | BranchIfIterableWithOriginalKeyOrValueMapIterator( | 
|  | state(), iterable, context, &map_fast_call, &check_set); | 
|  |  | 
|  | BIND(&map_fast_call); | 
|  | *var_result = | 
|  | CAST(CallBuiltin(Builtins::kMapIteratorToList, context, iterable)); | 
|  | Goto(&done); | 
|  | } | 
|  |  | 
|  | BIND(&check_set); | 
|  | { | 
|  | Label set_fast_call(this); | 
|  | BranchIfIterableWithOriginalValueSetIterator(state(), iterable, context, | 
|  | &set_fast_call, slow); | 
|  |  | 
|  | BIND(&set_fast_call); | 
|  | *var_result = | 
|  | 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 | 
|  | // the Iterator prototype are not modified in a way that changes the original | 
|  | // iteration behavior. | 
|  | // * In case of fast holey arrays, holes will be converted to undefined to | 
|  | //   reflect iteration semantics. Note that replacement by undefined is only | 
|  | //   correct when the NoElements protector is valid. | 
|  | // * In case of map/set iterators, there is an additional requirement that the | 
|  | //   iterator is not partially consumed. To be spec-compliant, after spreading | 
|  | //   the iterator is set to be exhausted. | 
|  | TF_BUILTIN(IterableToListWithSymbolLookup, IteratorBuiltinsAssembler) { | 
|  | auto context = Parameter<Context>(Descriptor::kContext); | 
|  | auto iterable = Parameter<Object>(Descriptor::kIterable); | 
|  |  | 
|  | Label slow_path(this); | 
|  |  | 
|  | GotoIfForceSlowPath(&slow_path); | 
|  |  | 
|  | TVARIABLE(JSArray, var_result); | 
|  | FastIterableToList(context, iterable, &var_result, &slow_path); | 
|  | Return(var_result.value()); | 
|  |  | 
|  | BIND(&slow_path); | 
|  | { | 
|  | TNode<Object> iterator_fn = GetIteratorMethod(context, iterable); | 
|  | TailCallBuiltin(Builtins::kIterableToList, context, iterable, iterator_fn); | 
|  | } | 
|  | } | 
|  |  | 
|  | TF_BUILTIN(GetIteratorWithFeedbackLazyDeoptContinuation, | 
|  | IteratorBuiltinsAssembler) { | 
|  | auto context = Parameter<Context>(Descriptor::kContext); | 
|  | auto receiver = Parameter<Object>(Descriptor::kReceiver); | 
|  | // TODO(v8:10047): Use TaggedIndex here once TurboFan supports it. | 
|  | auto call_slot_smi = Parameter<Smi>(Descriptor::kCallSlot); | 
|  | TNode<TaggedIndex> call_slot = SmiToTaggedIndex(call_slot_smi); | 
|  | auto feedback = Parameter<FeedbackVector>(Descriptor::kFeedback); | 
|  | auto iterator_method = Parameter<Object>(Descriptor::kResult); | 
|  |  | 
|  | TNode<Object> result = | 
|  | CallBuiltin(Builtins::kCallIteratorWithFeedback, context, receiver, | 
|  | iterator_method, call_slot, feedback); | 
|  | Return(result); | 
|  | } | 
|  |  | 
|  | // This builtin creates a FixedArray based on an Iterable and doesn't have a | 
|  | // fast path for anything. | 
|  | TF_BUILTIN(IterableToFixedArrayWithSymbolLookupSlow, | 
|  | IteratorBuiltinsAssembler) { | 
|  | auto context = Parameter<Context>(Descriptor::kContext); | 
|  | auto iterable = Parameter<Object>(Descriptor::kIterable); | 
|  |  | 
|  | TNode<Object> iterator_fn = GetIteratorMethod(context, iterable); | 
|  | TailCallBuiltin(Builtins::kIterableToFixedArray, context, iterable, | 
|  | iterator_fn); | 
|  | } | 
|  |  | 
|  | }  // namespace internal | 
|  | }  // namespace v8 |