| // 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 |