| // 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-array-gen.h" |
| |
| #include "src/builtins/builtins-iterator-gen.h" |
| #include "src/builtins/builtins-string-gen.h" |
| #include "src/builtins/builtins-typed-array-gen.h" |
| #include "src/builtins/builtins-utils-gen.h" |
| #include "src/builtins/builtins.h" |
| #include "src/code-stub-assembler.h" |
| #include "src/frame-constants.h" |
| #include "src/heap/factory-inl.h" |
| #include "src/objects/arguments-inl.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| using Node = compiler::Node; |
| template <class T> |
| using TNode = compiler::TNode<T>; |
| |
| ArrayBuiltinsAssembler::ArrayBuiltinsAssembler( |
| compiler::CodeAssemblerState* state) |
| : BaseBuiltinsFromDSLAssembler(state), |
| k_(this, MachineRepresentation::kTagged), |
| a_(this, MachineRepresentation::kTagged), |
| to_(this, MachineRepresentation::kTagged, SmiConstant(0)), |
| fully_spec_compliant_(this, {&k_, &a_, &to_}) {} |
| |
| void ArrayBuiltinsAssembler::FindResultGenerator() { |
| a_.Bind(UndefinedConstant()); |
| } |
| |
| Node* ArrayBuiltinsAssembler::FindProcessor(Node* k_value, Node* k) { |
| Node* value = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), |
| this_arg(), k_value, k, o()); |
| Label false_continue(this), return_true(this); |
| BranchIfToBooleanIsTrue(value, &return_true, &false_continue); |
| BIND(&return_true); |
| ReturnFromBuiltin(k_value); |
| BIND(&false_continue); |
| return a(); |
| } |
| |
| void ArrayBuiltinsAssembler::FindIndexResultGenerator() { |
| a_.Bind(SmiConstant(-1)); |
| } |
| |
| Node* ArrayBuiltinsAssembler::FindIndexProcessor(Node* k_value, Node* k) { |
| Node* value = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), |
| this_arg(), k_value, k, o()); |
| Label false_continue(this), return_true(this); |
| BranchIfToBooleanIsTrue(value, &return_true, &false_continue); |
| BIND(&return_true); |
| ReturnFromBuiltin(k); |
| BIND(&false_continue); |
| return a(); |
| } |
| |
| void ArrayBuiltinsAssembler::ForEachResultGenerator() { |
| a_.Bind(UndefinedConstant()); |
| } |
| |
| Node* ArrayBuiltinsAssembler::ForEachProcessor(Node* k_value, Node* k) { |
| CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), this_arg(), |
| k_value, k, o()); |
| return a(); |
| } |
| |
| void ArrayBuiltinsAssembler::SomeResultGenerator() { |
| a_.Bind(FalseConstant()); |
| } |
| |
| Node* ArrayBuiltinsAssembler::SomeProcessor(Node* k_value, Node* k) { |
| Node* value = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), |
| this_arg(), k_value, k, o()); |
| Label false_continue(this), return_true(this); |
| BranchIfToBooleanIsTrue(value, &return_true, &false_continue); |
| BIND(&return_true); |
| ReturnFromBuiltin(TrueConstant()); |
| BIND(&false_continue); |
| return a(); |
| } |
| |
| void ArrayBuiltinsAssembler::EveryResultGenerator() { |
| a_.Bind(TrueConstant()); |
| } |
| |
| Node* ArrayBuiltinsAssembler::EveryProcessor(Node* k_value, Node* k) { |
| Node* value = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), |
| this_arg(), k_value, k, o()); |
| Label true_continue(this), return_false(this); |
| BranchIfToBooleanIsTrue(value, &true_continue, &return_false); |
| BIND(&return_false); |
| ReturnFromBuiltin(FalseConstant()); |
| BIND(&true_continue); |
| return a(); |
| } |
| |
| void ArrayBuiltinsAssembler::ReduceResultGenerator() { |
| return a_.Bind(this_arg()); |
| } |
| |
| Node* ArrayBuiltinsAssembler::ReduceProcessor(Node* k_value, Node* k) { |
| VARIABLE(result, MachineRepresentation::kTagged); |
| Label done(this, {&result}), initial(this); |
| GotoIf(WordEqual(a(), TheHoleConstant()), &initial); |
| result.Bind(CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), |
| UndefinedConstant(), a(), k_value, k, o())); |
| Goto(&done); |
| |
| BIND(&initial); |
| result.Bind(k_value); |
| Goto(&done); |
| |
| BIND(&done); |
| return result.value(); |
| } |
| |
| void ArrayBuiltinsAssembler::ReducePostLoopAction() { |
| Label ok(this); |
| GotoIf(WordNotEqual(a(), TheHoleConstant()), &ok); |
| ThrowTypeError(context(), MessageTemplate::kReduceNoInitial); |
| BIND(&ok); |
| } |
| |
| void ArrayBuiltinsAssembler::FilterResultGenerator() { |
| // 7. Let A be ArraySpeciesCreate(O, 0). |
| // This version of ArraySpeciesCreate will create with the correct |
| // ElementsKind in the fast case. |
| GenerateArraySpeciesCreate(); |
| } |
| |
| Node* ArrayBuiltinsAssembler::FilterProcessor(Node* k_value, Node* k) { |
| // ii. Let selected be ToBoolean(? Call(callbackfn, T, kValue, k, O)). |
| Node* selected = CallJS(CodeFactory::Call(isolate()), context(), |
| callbackfn(), this_arg(), k_value, k, o()); |
| Label true_continue(this, &to_), false_continue(this); |
| BranchIfToBooleanIsTrue(selected, &true_continue, &false_continue); |
| BIND(&true_continue); |
| // iii. If selected is true, then... |
| { |
| Label after_work(this, &to_); |
| Node* kind = nullptr; |
| |
| // If a() is a JSArray, we can have a fast path. |
| Label fast(this); |
| Label runtime(this); |
| Label object_push_pre(this), object_push(this), double_push(this); |
| BranchIfFastJSArray(a(), context(), &fast, &runtime); |
| |
| BIND(&fast); |
| { |
| GotoIf(WordNotEqual(LoadJSArrayLength(a()), to_.value()), &runtime); |
| kind = EnsureArrayPushable(LoadMap(a()), &runtime); |
| GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS), |
| &object_push_pre); |
| |
| BuildAppendJSArray(HOLEY_SMI_ELEMENTS, a(), k_value, &runtime); |
| Goto(&after_work); |
| } |
| |
| BIND(&object_push_pre); |
| { |
| Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &double_push, |
| &object_push); |
| } |
| |
| BIND(&object_push); |
| { |
| BuildAppendJSArray(HOLEY_ELEMENTS, a(), k_value, &runtime); |
| Goto(&after_work); |
| } |
| |
| BIND(&double_push); |
| { |
| BuildAppendJSArray(HOLEY_DOUBLE_ELEMENTS, a(), k_value, &runtime); |
| Goto(&after_work); |
| } |
| |
| BIND(&runtime); |
| { |
| // 1. Perform ? CreateDataPropertyOrThrow(A, ToString(to), kValue). |
| CallRuntime(Runtime::kCreateDataProperty, context(), a(), to_.value(), |
| k_value); |
| Goto(&after_work); |
| } |
| |
| BIND(&after_work); |
| { |
| // 2. Increase to by 1. |
| to_.Bind(NumberInc(to_.value())); |
| Goto(&false_continue); |
| } |
| } |
| BIND(&false_continue); |
| return a(); |
| } |
| |
| void ArrayBuiltinsAssembler::MapResultGenerator() { |
| GenerateArraySpeciesCreate(len_); |
| } |
| |
| void ArrayBuiltinsAssembler::TypedArrayMapResultGenerator() { |
| // 6. Let A be ? TypedArraySpeciesCreate(O, len). |
| TNode<JSTypedArray> original_array = CAST(o()); |
| TNode<Smi> length = CAST(len_); |
| const char* method_name = "%TypedArray%.prototype.map"; |
| |
| TypedArrayBuiltinsAssembler typedarray_asm(state()); |
| TNode<JSTypedArray> a = typedarray_asm.SpeciesCreateByLength( |
| context(), original_array, length, method_name); |
| // In the Spec and our current implementation, the length check is already |
| // performed in TypedArraySpeciesCreate. |
| CSA_ASSERT(this, SmiLessThanOrEqual(CAST(len_), LoadTypedArrayLength(a))); |
| fast_typed_array_target_ = |
| Word32Equal(LoadInstanceType(LoadElements(original_array)), |
| LoadInstanceType(LoadElements(a))); |
| a_.Bind(a); |
| } |
| |
| Node* ArrayBuiltinsAssembler::SpecCompliantMapProcessor(Node* k_value, |
| Node* k) { |
| // i. Let kValue be ? Get(O, Pk). Performed by the caller of |
| // SpecCompliantMapProcessor. |
| // ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O). |
| Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(), |
| callbackfn(), this_arg(), k_value, k, o()); |
| |
| // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value). |
| CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mapped_value); |
| return a(); |
| } |
| |
| Node* ArrayBuiltinsAssembler::FastMapProcessor(Node* k_value, Node* k) { |
| // i. Let kValue be ? Get(O, Pk). Performed by the caller of |
| // FastMapProcessor. |
| // ii. Let mapped_value be ? Call(callbackfn, T, kValue, k, O). |
| Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(), |
| callbackfn(), this_arg(), k_value, k, o()); |
| |
| // mode is SMI_PARAMETERS because k has tagged representation. |
| ParameterMode mode = SMI_PARAMETERS; |
| Label runtime(this), finished(this); |
| Label transition_pre(this), transition_smi_fast(this), |
| transition_smi_double(this); |
| Label array_not_smi(this), array_fast(this), array_double(this); |
| |
| TNode<Int32T> kind = LoadElementsKind(a()); |
| Node* elements = LoadElements(a()); |
| GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS), &array_not_smi); |
| TryStoreArrayElement(HOLEY_SMI_ELEMENTS, mode, &transition_pre, elements, k, |
| mapped_value); |
| Goto(&finished); |
| |
| BIND(&transition_pre); |
| { |
| // array is smi. Value is either tagged or a heap number. |
| CSA_ASSERT(this, TaggedIsNotSmi(mapped_value)); |
| GotoIf(IsHeapNumberMap(LoadMap(mapped_value)), &transition_smi_double); |
| Goto(&transition_smi_fast); |
| } |
| |
| BIND(&array_not_smi); |
| { |
| Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &array_double, |
| &array_fast); |
| } |
| |
| BIND(&transition_smi_fast); |
| { |
| // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value). |
| Node* const native_context = LoadNativeContext(context()); |
| Node* const fast_map = LoadContextElement( |
| native_context, Context::JS_ARRAY_HOLEY_ELEMENTS_MAP_INDEX); |
| |
| // Since this transition is only a map change, just do it right here. |
| // Since a() doesn't have an allocation site, it's safe to do the |
| // map store directly, otherwise I'd call TransitionElementsKind(). |
| StoreMap(a(), fast_map); |
| Goto(&array_fast); |
| } |
| |
| BIND(&array_fast); |
| { |
| TryStoreArrayElement(HOLEY_ELEMENTS, mode, &runtime, elements, k, |
| mapped_value); |
| Goto(&finished); |
| } |
| |
| BIND(&transition_smi_double); |
| { |
| // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value). |
| Node* const native_context = LoadNativeContext(context()); |
| Node* const double_map = LoadContextElement( |
| native_context, Context::JS_ARRAY_HOLEY_DOUBLE_ELEMENTS_MAP_INDEX); |
| |
| const ElementsKind kFromKind = HOLEY_SMI_ELEMENTS; |
| const ElementsKind kToKind = HOLEY_DOUBLE_ELEMENTS; |
| const bool kIsJSArray = true; |
| |
| Label transition_in_runtime(this, Label::kDeferred); |
| TransitionElementsKind(a(), double_map, kFromKind, kToKind, kIsJSArray, |
| &transition_in_runtime); |
| Goto(&array_double); |
| |
| BIND(&transition_in_runtime); |
| CallRuntime(Runtime::kTransitionElementsKind, context(), a(), double_map); |
| Goto(&array_double); |
| } |
| |
| BIND(&array_double); |
| { |
| // TODO(mvstanton): If we use a variable for elements and bind it |
| // appropriately, we can avoid an extra load of elements by binding the |
| // value only after a transition from smi to double. |
| elements = LoadElements(a()); |
| // If the mapped_value isn't a number, this will bail out to the runtime |
| // to make the transition. |
| TryStoreArrayElement(HOLEY_DOUBLE_ELEMENTS, mode, &runtime, elements, k, |
| mapped_value); |
| Goto(&finished); |
| } |
| |
| BIND(&runtime); |
| { |
| // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mapped_value). |
| CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, |
| mapped_value); |
| Goto(&finished); |
| } |
| |
| BIND(&finished); |
| return a(); |
| } |
| |
| // See tc39.github.io/ecma262/#sec-%typedarray%.prototype.map. |
| Node* ArrayBuiltinsAssembler::TypedArrayMapProcessor(Node* k_value, Node* k) { |
| // 8. c. Let mapped_value be ? Call(callbackfn, T, « kValue, k, O »). |
| Node* mapped_value = CallJS(CodeFactory::Call(isolate()), context(), |
| callbackfn(), this_arg(), k_value, k, o()); |
| Label fast(this), slow(this), done(this), detached(this, Label::kDeferred); |
| |
| // 8. d. Perform ? Set(A, Pk, mapped_value, true). |
| // Since we know that A is a TypedArray, this always ends up in |
| // #sec-integer-indexed-exotic-objects-set-p-v-receiver and then |
| // tc39.github.io/ecma262/#sec-integerindexedelementset . |
| Branch(fast_typed_array_target_, &fast, &slow); |
| |
| BIND(&fast); |
| // #sec-integerindexedelementset |
| // 5. If arrayTypeName is "BigUint64Array" or "BigInt64Array", let |
| // numValue be ? ToBigInt(v). |
| // 6. Otherwise, let numValue be ? ToNumber(value). |
| Node* num_value; |
| if (source_elements_kind_ == BIGINT64_ELEMENTS || |
| source_elements_kind_ == BIGUINT64_ELEMENTS) { |
| num_value = ToBigInt(context(), mapped_value); |
| } else { |
| num_value = ToNumber_Inline(context(), mapped_value); |
| } |
| // The only way how this can bailout is because of a detached buffer. |
| EmitElementStore(a(), k, num_value, false, source_elements_kind_, |
| KeyedAccessStoreMode::STANDARD_STORE, &detached, |
| context()); |
| Goto(&done); |
| |
| BIND(&slow); |
| SetPropertyStrict(context(), CAST(a()), CAST(k), CAST(mapped_value)); |
| Goto(&done); |
| |
| BIND(&detached); |
| // tc39.github.io/ecma262/#sec-integerindexedelementset |
| // 8. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. |
| ThrowTypeError(context_, MessageTemplate::kDetachedOperation, name_); |
| |
| BIND(&done); |
| return a(); |
| } |
| |
| void ArrayBuiltinsAssembler::NullPostLoopAction() {} |
| |
| void ArrayBuiltinsAssembler::FillFixedArrayWithSmiZero( |
| TNode<FixedArray> array, TNode<Smi> smi_length) { |
| CSA_ASSERT(this, Word32BinaryNot(IsFixedDoubleArray(array))); |
| |
| TNode<IntPtrT> length = SmiToIntPtr(smi_length); |
| TNode<WordT> byte_length = TimesPointerSize(length); |
| CSA_ASSERT(this, UintPtrLessThan(length, byte_length)); |
| |
| static const int32_t fa_base_data_offset = |
| FixedArray::kHeaderSize - kHeapObjectTag; |
| TNode<IntPtrT> backing_store = IntPtrAdd( |
| BitcastTaggedToWord(array), IntPtrConstant(fa_base_data_offset)); |
| |
| // Call out to memset to perform initialization. |
| TNode<ExternalReference> memset = |
| ExternalConstant(ExternalReference::libc_memset_function()); |
| STATIC_ASSERT(kSizetSize == kIntptrSize); |
| CallCFunction3(MachineType::Pointer(), MachineType::Pointer(), |
| MachineType::IntPtr(), MachineType::UintPtr(), memset, |
| backing_store, IntPtrConstant(0), byte_length); |
| } |
| |
| void ArrayBuiltinsAssembler::ReturnFromBuiltin(Node* value) { |
| if (argc_ == nullptr) { |
| Return(value); |
| } else { |
| // argc_ doesn't include the receiver, so it has to be added back in |
| // manually. |
| PopAndReturn(IntPtrAdd(argc_, IntPtrConstant(1)), value); |
| } |
| } |
| |
| void ArrayBuiltinsAssembler::InitIteratingArrayBuiltinBody( |
| TNode<Context> context, TNode<Object> receiver, Node* callbackfn, |
| Node* this_arg, TNode<IntPtrT> argc) { |
| context_ = context; |
| receiver_ = receiver; |
| callbackfn_ = callbackfn; |
| this_arg_ = this_arg; |
| argc_ = argc; |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateIteratingArrayBuiltinBody( |
| const char* name, const BuiltinResultGenerator& generator, |
| const CallResultProcessor& processor, const PostLoopAction& action, |
| const Callable& slow_case_continuation, |
| MissingPropertyMode missing_property_mode, ForEachDirection direction) { |
| Label non_array(this), array_changes(this, {&k_, &a_, &to_}); |
| |
| // TODO(danno): Seriously? Do we really need to throw the exact error |
| // message on null and undefined so that the webkit tests pass? |
| Label throw_null_undefined_exception(this, Label::kDeferred); |
| GotoIf(IsNullOrUndefined(receiver()), &throw_null_undefined_exception); |
| |
| // By the book: taken directly from the ECMAScript 2015 specification |
| |
| // 1. Let O be ToObject(this value). |
| // 2. ReturnIfAbrupt(O) |
| o_ = ToObject_Inline(context(), receiver()); |
| |
| // 3. Let len be ToLength(Get(O, "length")). |
| // 4. ReturnIfAbrupt(len). |
| TVARIABLE(Number, merged_length); |
| Label has_length(this, &merged_length), not_js_array(this); |
| GotoIf(DoesntHaveInstanceType(o(), JS_ARRAY_TYPE), ¬_js_array); |
| merged_length = LoadJSArrayLength(CAST(o())); |
| Goto(&has_length); |
| |
| BIND(¬_js_array); |
| { |
| Node* len_property = |
| GetProperty(context(), o(), isolate()->factory()->length_string()); |
| merged_length = ToLength_Inline(context(), len_property); |
| Goto(&has_length); |
| } |
| BIND(&has_length); |
| { |
| len_ = merged_length.value(); |
| |
| // 5. If IsCallable(callbackfn) is false, throw a TypeError exception. |
| Label type_exception(this, Label::kDeferred); |
| Label done(this); |
| GotoIf(TaggedIsSmi(callbackfn()), &type_exception); |
| Branch(IsCallableMap(LoadMap(callbackfn())), &done, &type_exception); |
| |
| BIND(&throw_null_undefined_exception); |
| ThrowTypeError(context(), MessageTemplate::kCalledOnNullOrUndefined, |
| name); |
| |
| BIND(&type_exception); |
| ThrowTypeError(context(), MessageTemplate::kCalledNonCallable, |
| callbackfn()); |
| |
| BIND(&done); |
| } |
| |
| // 6. If thisArg was supplied, let T be thisArg; else let T be undefined. |
| // [Already done by the arguments adapter] |
| |
| if (direction == ForEachDirection::kForward) { |
| // 7. Let k be 0. |
| k_.Bind(SmiConstant(0)); |
| } else { |
| k_.Bind(NumberDec(len())); |
| } |
| |
| generator(this); |
| |
| HandleFastElements(processor, action, &fully_spec_compliant_, direction, |
| missing_property_mode); |
| |
| BIND(&fully_spec_compliant_); |
| |
| Node* result = |
| CallStub(slow_case_continuation, context(), receiver(), callbackfn(), |
| this_arg(), a_.value(), o(), k_.value(), len(), to_.value()); |
| ReturnFromBuiltin(result); |
| } |
| |
| void ArrayBuiltinsAssembler::InitIteratingArrayBuiltinLoopContinuation( |
| TNode<Context> context, TNode<Object> receiver, Node* callbackfn, |
| Node* this_arg, Node* a, TNode<JSReceiver> o, Node* initial_k, |
| TNode<Number> len, Node* to) { |
| context_ = context; |
| this_arg_ = this_arg; |
| callbackfn_ = callbackfn; |
| a_.Bind(a); |
| k_.Bind(initial_k); |
| o_ = o; |
| len_ = len; |
| to_.Bind(to); |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateIteratingTypedArrayBuiltinBody( |
| const char* name, const BuiltinResultGenerator& generator, |
| const CallResultProcessor& processor, const PostLoopAction& action, |
| ForEachDirection direction) { |
| name_ = name; |
| |
| // ValidateTypedArray: tc39.github.io/ecma262/#sec-validatetypedarray |
| |
| Label throw_not_typed_array(this, Label::kDeferred); |
| |
| GotoIf(TaggedIsSmi(receiver_), &throw_not_typed_array); |
| GotoIfNot(HasInstanceType(CAST(receiver_), JS_TYPED_ARRAY_TYPE), |
| &throw_not_typed_array); |
| |
| TNode<JSTypedArray> typed_array = CAST(receiver_); |
| o_ = typed_array; |
| |
| TNode<JSArrayBuffer> array_buffer = LoadArrayBufferViewBuffer(typed_array); |
| ThrowIfArrayBufferIsDetached(context_, array_buffer, name_); |
| |
| len_ = LoadTypedArrayLength(typed_array); |
| |
| Label throw_not_callable(this, Label::kDeferred); |
| Label distinguish_types(this); |
| GotoIf(TaggedIsSmi(callbackfn_), &throw_not_callable); |
| Branch(IsCallableMap(LoadMap(callbackfn_)), &distinguish_types, |
| &throw_not_callable); |
| |
| BIND(&throw_not_typed_array); |
| ThrowTypeError(context_, MessageTemplate::kNotTypedArray); |
| |
| BIND(&throw_not_callable); |
| ThrowTypeError(context_, MessageTemplate::kCalledNonCallable, callbackfn_); |
| |
| Label unexpected_instance_type(this); |
| BIND(&unexpected_instance_type); |
| Unreachable(); |
| |
| std::vector<int32_t> instance_types = { |
| #define INSTANCE_TYPE(Type, type, TYPE, ctype) FIXED_##TYPE##_ARRAY_TYPE, |
| TYPED_ARRAYS(INSTANCE_TYPE) |
| #undef INSTANCE_TYPE |
| }; |
| std::vector<Label> labels; |
| for (size_t i = 0; i < instance_types.size(); ++i) { |
| labels.push_back(Label(this)); |
| } |
| std::vector<Label*> label_ptrs; |
| for (Label& label : labels) { |
| label_ptrs.push_back(&label); |
| } |
| |
| BIND(&distinguish_types); |
| |
| generator(this); |
| |
| if (direction == ForEachDirection::kForward) { |
| k_.Bind(SmiConstant(0)); |
| } else { |
| k_.Bind(NumberDec(len())); |
| } |
| CSA_ASSERT(this, IsSafeInteger(k())); |
| Node* instance_type = LoadInstanceType(LoadElements(typed_array)); |
| Switch(instance_type, &unexpected_instance_type, instance_types.data(), |
| label_ptrs.data(), labels.size()); |
| |
| for (size_t i = 0; i < labels.size(); ++i) { |
| BIND(&labels[i]); |
| Label done(this); |
| source_elements_kind_ = ElementsKindForInstanceType( |
| static_cast<InstanceType>(instance_types[i])); |
| // TODO(tebbi): Silently cancelling the loop on buffer detachment is a |
| // spec violation. Should go to &throw_detached and throw a TypeError |
| // instead. |
| VisitAllTypedArrayElements(array_buffer, processor, &done, direction, |
| typed_array); |
| Goto(&done); |
| // No exception, return success |
| BIND(&done); |
| action(this); |
| ReturnFromBuiltin(a_.value()); |
| } |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateIteratingArrayBuiltinLoopContinuation( |
| const CallResultProcessor& processor, const PostLoopAction& action, |
| MissingPropertyMode missing_property_mode, ForEachDirection direction) { |
| Label loop(this, {&k_, &a_, &to_}); |
| Label after_loop(this); |
| Goto(&loop); |
| BIND(&loop); |
| { |
| if (direction == ForEachDirection::kForward) { |
| // 8. Repeat, while k < len |
| GotoIfNumberGreaterThanOrEqual(k(), len_, &after_loop); |
| } else { |
| // OR |
| // 10. Repeat, while k >= 0 |
| GotoIfNumberGreaterThanOrEqual(SmiConstant(-1), k(), &after_loop); |
| } |
| |
| Label done_element(this, &to_); |
| // a. Let Pk be ToString(k). |
| // k() is guaranteed to be a positive integer, hence ToString is |
| // side-effect free and HasProperty/GetProperty do the conversion inline. |
| CSA_ASSERT(this, IsSafeInteger(k())); |
| |
| if (missing_property_mode == MissingPropertyMode::kSkip) { |
| // b. Let kPresent be HasProperty(O, Pk). |
| // c. ReturnIfAbrupt(kPresent). |
| TNode<Oddball> k_present = |
| HasProperty(context(), o(), k(), kHasProperty); |
| |
| // d. If kPresent is true, then |
| GotoIf(IsFalse(k_present), &done_element); |
| } |
| |
| // i. Let kValue be Get(O, Pk). |
| // ii. ReturnIfAbrupt(kValue). |
| Node* k_value = GetProperty(context(), o(), k()); |
| |
| // iii. Let funcResult be Call(callbackfn, T, «kValue, k, O»). |
| // iv. ReturnIfAbrupt(funcResult). |
| a_.Bind(processor(this, k_value, k())); |
| Goto(&done_element); |
| |
| BIND(&done_element); |
| |
| if (direction == ForEachDirection::kForward) { |
| // e. Increase k by 1. |
| k_.Bind(NumberInc(k())); |
| } else { |
| // e. Decrease k by 1. |
| k_.Bind(NumberDec(k())); |
| } |
| Goto(&loop); |
| } |
| BIND(&after_loop); |
| |
| action(this); |
| Return(a_.value()); |
| } |
| |
| ElementsKind ArrayBuiltinsAssembler::ElementsKindForInstanceType( |
| InstanceType type) { |
| switch (type) { |
| #define INSTANCE_TYPE_TO_ELEMENTS_KIND(Type, type, TYPE, ctype) \ |
| case FIXED_##TYPE##_ARRAY_TYPE: \ |
| return TYPE##_ELEMENTS; |
| |
| TYPED_ARRAYS(INSTANCE_TYPE_TO_ELEMENTS_KIND) |
| #undef INSTANCE_TYPE_TO_ELEMENTS_KIND |
| |
| default: |
| UNREACHABLE(); |
| } |
| } |
| |
| void ArrayBuiltinsAssembler::VisitAllTypedArrayElements( |
| Node* array_buffer, const CallResultProcessor& processor, Label* detached, |
| ForEachDirection direction, TNode<JSTypedArray> typed_array) { |
| VariableList list({&a_, &k_, &to_}, zone()); |
| |
| FastLoopBody body = [&](Node* index) { |
| GotoIf(IsDetachedBuffer(array_buffer), detached); |
| Node* elements = LoadElements(typed_array); |
| Node* base_ptr = |
| LoadObjectField(elements, FixedTypedArrayBase::kBasePointerOffset); |
| Node* external_ptr = |
| LoadObjectField(elements, FixedTypedArrayBase::kExternalPointerOffset, |
| MachineType::Pointer()); |
| Node* data_ptr = IntPtrAdd(BitcastTaggedToWord(base_ptr), external_ptr); |
| Node* value = LoadFixedTypedArrayElementAsTagged( |
| data_ptr, index, source_elements_kind_, SMI_PARAMETERS); |
| k_.Bind(index); |
| a_.Bind(processor(this, value, index)); |
| }; |
| Node* start = SmiConstant(0); |
| Node* end = len_; |
| IndexAdvanceMode advance_mode = IndexAdvanceMode::kPost; |
| int incr = 1; |
| if (direction == ForEachDirection::kReverse) { |
| std::swap(start, end); |
| advance_mode = IndexAdvanceMode::kPre; |
| incr = -1; |
| } |
| BuildFastLoop(list, start, end, body, incr, ParameterMode::SMI_PARAMETERS, |
| advance_mode); |
| } |
| |
| void ArrayBuiltinsAssembler::VisitAllFastElementsOneKind( |
| ElementsKind kind, const CallResultProcessor& processor, |
| Label* array_changed, ParameterMode mode, ForEachDirection direction, |
| MissingPropertyMode missing_property_mode, TNode<Smi> length) { |
| Comment("begin VisitAllFastElementsOneKind"); |
| // We only use this kind of processing if the no-elements protector is |
| // in place at the start. We'll continue checking during array iteration. |
| CSA_ASSERT(this, Word32BinaryNot(IsNoElementsProtectorCellInvalid())); |
| VARIABLE(original_map, MachineRepresentation::kTagged); |
| original_map.Bind(LoadMap(o())); |
| VariableList list({&original_map, &a_, &k_, &to_}, zone()); |
| Node* start = IntPtrOrSmiConstant(0, mode); |
| Node* end = TaggedToParameter(length, mode); |
| IndexAdvanceMode advance_mode = direction == ForEachDirection::kReverse |
| ? IndexAdvanceMode::kPre |
| : IndexAdvanceMode::kPost; |
| if (direction == ForEachDirection::kReverse) std::swap(start, end); |
| BuildFastLoop( |
| list, start, end, |
| [=, &original_map](Node* index) { |
| k_.Bind(ParameterToTagged(index, mode)); |
| Label one_element_done(this), hole_element(this), |
| process_element(this); |
| |
| // Check if o's map has changed during the callback. If so, we have to |
| // fall back to the slower spec implementation for the rest of the |
| // iteration. |
| Node* o_map = LoadMap(o()); |
| GotoIf(WordNotEqual(o_map, original_map.value()), array_changed); |
| |
| TNode<JSArray> o_array = CAST(o()); |
| // Check if o's length has changed during the callback and if the |
| // index is now out of range of the new length. |
| GotoIf(SmiGreaterThanOrEqual(CAST(k_.value()), |
| CAST(LoadJSArrayLength(o_array))), |
| array_changed); |
| |
| // Re-load the elements array. If may have been resized. |
| Node* elements = LoadElements(o_array); |
| |
| // Fast case: load the element directly from the elements FixedArray |
| // and call the callback if the element is not the hole. |
| DCHECK(kind == PACKED_ELEMENTS || kind == PACKED_DOUBLE_ELEMENTS); |
| int base_size = kind == PACKED_ELEMENTS |
| ? FixedArray::kHeaderSize |
| : (FixedArray::kHeaderSize - kHeapObjectTag); |
| Node* offset = ElementOffsetFromIndex(index, kind, mode, base_size); |
| VARIABLE(value, MachineRepresentation::kTagged); |
| if (kind == PACKED_ELEMENTS) { |
| value.Bind(LoadObjectField(elements, offset)); |
| GotoIf(WordEqual(value.value(), TheHoleConstant()), &hole_element); |
| } else { |
| Node* double_value = |
| LoadDoubleWithHoleCheck(elements, offset, &hole_element); |
| value.Bind(AllocateHeapNumberWithValue(double_value)); |
| } |
| Goto(&process_element); |
| |
| BIND(&hole_element); |
| if (missing_property_mode == MissingPropertyMode::kSkip) { |
| // The NoElementsProtectorCell could go invalid during callbacks. |
| Branch(IsNoElementsProtectorCellInvalid(), array_changed, |
| &one_element_done); |
| } else { |
| value.Bind(UndefinedConstant()); |
| Goto(&process_element); |
| } |
| BIND(&process_element); |
| { |
| a_.Bind(processor(this, value.value(), k())); |
| Goto(&one_element_done); |
| } |
| BIND(&one_element_done); |
| }, |
| 1, mode, advance_mode); |
| Comment("end VisitAllFastElementsOneKind"); |
| } |
| |
| void ArrayBuiltinsAssembler::HandleFastElements( |
| const CallResultProcessor& processor, const PostLoopAction& action, |
| Label* slow, ForEachDirection direction, |
| MissingPropertyMode missing_property_mode) { |
| Label switch_on_elements_kind(this), fast_elements(this), |
| maybe_double_elements(this), fast_double_elements(this); |
| |
| Comment("begin HandleFastElements"); |
| // Non-smi lengths must use the slow path. |
| GotoIf(TaggedIsNotSmi(len()), slow); |
| |
| BranchIfFastJSArray(o(), context(), |
| &switch_on_elements_kind, slow); |
| |
| BIND(&switch_on_elements_kind); |
| TNode<Smi> smi_len = CAST(len()); |
| // Select by ElementsKind |
| Node* o_map = LoadMap(o()); |
| Node* bit_field2 = LoadMapBitField2(o_map); |
| Node* kind = DecodeWord32<Map::ElementsKindBits>(bit_field2); |
| Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), |
| &maybe_double_elements, &fast_elements); |
| |
| ParameterMode mode = OptimalParameterMode(); |
| BIND(&fast_elements); |
| { |
| VisitAllFastElementsOneKind(PACKED_ELEMENTS, processor, slow, mode, |
| direction, missing_property_mode, smi_len); |
| |
| action(this); |
| |
| // No exception, return success |
| ReturnFromBuiltin(a_.value()); |
| } |
| |
| BIND(&maybe_double_elements); |
| Branch(IsElementsKindGreaterThan(kind, HOLEY_DOUBLE_ELEMENTS), slow, |
| &fast_double_elements); |
| |
| BIND(&fast_double_elements); |
| { |
| VisitAllFastElementsOneKind(PACKED_DOUBLE_ELEMENTS, processor, slow, mode, |
| direction, missing_property_mode, smi_len); |
| |
| action(this); |
| |
| // No exception, return success |
| ReturnFromBuiltin(a_.value()); |
| } |
| } |
| |
| // Perform ArraySpeciesCreate (ES6 #sec-arrayspeciescreate). |
| // This version is specialized to create a zero length array |
| // of the elements kind of the input array. |
| void ArrayBuiltinsAssembler::GenerateArraySpeciesCreate() { |
| Label runtime(this, Label::kDeferred), done(this); |
| |
| TNode<Smi> len = SmiConstant(0); |
| TNode<Map> original_map = LoadMap(o()); |
| GotoIfNot( |
| InstanceTypeEqual(LoadMapInstanceType(original_map), JS_ARRAY_TYPE), |
| &runtime); |
| |
| GotoIfNot(IsPrototypeInitialArrayPrototype(context(), original_map), |
| &runtime); |
| |
| Node* species_protector = ArraySpeciesProtectorConstant(); |
| Node* value = |
| LoadObjectField(species_protector, PropertyCell::kValueOffset); |
| TNode<Smi> const protector_invalid = |
| SmiConstant(Isolate::kProtectorInvalid); |
| GotoIf(WordEqual(value, protector_invalid), &runtime); |
| |
| // Respect the ElementsKind of the input array. |
| TNode<Int32T> elements_kind = LoadMapElementsKind(original_map); |
| GotoIfNot(IsFastElementsKind(elements_kind), &runtime); |
| TNode<Context> native_context = LoadNativeContext(context()); |
| TNode<Map> array_map = |
| LoadJSArrayElementsMap(elements_kind, native_context); |
| TNode<JSArray> array = |
| CAST(AllocateJSArray(GetInitialFastElementsKind(), array_map, len, len, |
| nullptr, CodeStubAssembler::SMI_PARAMETERS)); |
| a_.Bind(array); |
| |
| Goto(&done); |
| |
| BIND(&runtime); |
| { |
| // 5. Let A be ? ArraySpeciesCreate(O, len). |
| Node* constructor = |
| CallRuntime(Runtime::kArraySpeciesConstructor, context(), o()); |
| a_.Bind(ConstructJS(CodeFactory::Construct(isolate()), context(), |
| constructor, len)); |
| Goto(&fully_spec_compliant_); |
| } |
| |
| BIND(&done); |
| } |
| |
| // Perform ArraySpeciesCreate (ES6 #sec-arrayspeciescreate). |
| void ArrayBuiltinsAssembler::GenerateArraySpeciesCreate(TNode<Number> len) { |
| Label runtime(this, Label::kDeferred), done(this); |
| |
| Node* const original_map = LoadMap(o()); |
| GotoIfNot( |
| InstanceTypeEqual(LoadMapInstanceType(original_map), JS_ARRAY_TYPE), |
| &runtime); |
| |
| GotoIfNot(IsPrototypeInitialArrayPrototype(context(), original_map), |
| &runtime); |
| |
| Node* species_protector = ArraySpeciesProtectorConstant(); |
| Node* value = |
| LoadObjectField(species_protector, PropertyCell::kValueOffset); |
| Node* const protector_invalid = SmiConstant(Isolate::kProtectorInvalid); |
| GotoIf(WordEqual(value, protector_invalid), &runtime); |
| |
| GotoIfNot(TaggedIsPositiveSmi(len), &runtime); |
| GotoIf( |
| SmiAbove(CAST(len), SmiConstant(JSArray::kInitialMaxFastElementArray)), |
| &runtime); |
| |
| // We need to be conservative and start with holey because the builtins |
| // that create output arrays aren't guaranteed to be called for every |
| // element in the input array (maybe the callback deletes an element). |
| const ElementsKind elements_kind = |
| GetHoleyElementsKind(GetInitialFastElementsKind()); |
| TNode<Context> native_context = LoadNativeContext(context()); |
| TNode<Map> array_map = |
| LoadJSArrayElementsMap(elements_kind, native_context); |
| a_.Bind(AllocateJSArray(PACKED_SMI_ELEMENTS, array_map, len, len, nullptr, |
| CodeStubAssembler::SMI_PARAMETERS)); |
| |
| Goto(&done); |
| |
| BIND(&runtime); |
| { |
| // 5. Let A be ? ArraySpeciesCreate(O, len). |
| Node* constructor = |
| CallRuntime(Runtime::kArraySpeciesConstructor, context(), o()); |
| a_.Bind(ConstructJS(CodeFactory::Construct(isolate()), context(), |
| constructor, len)); |
| Goto(&fully_spec_compliant_); |
| } |
| |
| BIND(&done); |
| } |
| |
| TF_BUILTIN(ArrayPrototypePop, CodeStubAssembler) { |
| TNode<Int32T> argc = |
| UncheckedCast<Int32T>(Parameter(Descriptor::kJSActualArgumentsCount)); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| CSA_ASSERT(this, IsUndefined(Parameter(Descriptor::kJSNewTarget))); |
| |
| CodeStubArguments args(this, ChangeInt32ToIntPtr(argc)); |
| TNode<Object> receiver = args.GetReceiver(); |
| |
| Label runtime(this, Label::kDeferred); |
| Label fast(this); |
| |
| // Only pop in this stub if |
| // 1) the array has fast elements |
| // 2) the length is writable, |
| // 3) the elements backing store isn't copy-on-write, |
| // 4) we aren't supposed to shrink the backing store. |
| |
| // 1) Check that the array has fast elements. |
| BranchIfFastJSArray(receiver, context, &fast, &runtime); |
| |
| BIND(&fast); |
| { |
| TNode<JSArray> array_receiver = CAST(receiver); |
| CSA_ASSERT(this, TaggedIsPositiveSmi(LoadJSArrayLength(array_receiver))); |
| Node* length = |
| LoadAndUntagObjectField(array_receiver, JSArray::kLengthOffset); |
| Label return_undefined(this), fast_elements(this); |
| GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &return_undefined); |
| |
| // 2) Ensure that the length is writable. |
| EnsureArrayLengthWritable(LoadMap(array_receiver), &runtime); |
| |
| // 3) Check that the elements backing store isn't copy-on-write. |
| Node* elements = LoadElements(array_receiver); |
| GotoIf(WordEqual(LoadMap(elements), |
| LoadRoot(Heap::kFixedCOWArrayMapRootIndex)), |
| &runtime); |
| |
| Node* new_length = IntPtrSub(length, IntPtrConstant(1)); |
| |
| // 4) Check that we're not supposed to shrink the backing store, as |
| // implemented in elements.cc:ElementsAccessorBase::SetLengthImpl. |
| Node* capacity = SmiUntag(LoadFixedArrayBaseLength(elements)); |
| GotoIf(IntPtrLessThan( |
| IntPtrAdd(IntPtrAdd(new_length, new_length), |
| IntPtrConstant(JSObject::kMinAddedElementsCapacity)), |
| capacity), |
| &runtime); |
| |
| StoreObjectFieldNoWriteBarrier(array_receiver, JSArray::kLengthOffset, |
| SmiTag(new_length)); |
| |
| TNode<Int32T> elements_kind = LoadElementsKind(array_receiver); |
| GotoIf(Int32LessThanOrEqual(elements_kind, |
| Int32Constant(TERMINAL_FAST_ELEMENTS_KIND)), |
| &fast_elements); |
| |
| Node* value = LoadFixedDoubleArrayElement( |
| elements, new_length, MachineType::Float64(), 0, INTPTR_PARAMETERS, |
| &return_undefined); |
| |
| int32_t header_size = FixedDoubleArray::kHeaderSize - kHeapObjectTag; |
| Node* offset = ElementOffsetFromIndex(new_length, HOLEY_DOUBLE_ELEMENTS, |
| INTPTR_PARAMETERS, header_size); |
| if (Is64()) { |
| Node* double_hole = Int64Constant(kHoleNanInt64); |
| StoreNoWriteBarrier(MachineRepresentation::kWord64, elements, offset, |
| double_hole); |
| } else { |
| STATIC_ASSERT(kHoleNanLower32 == kHoleNanUpper32); |
| Node* double_hole = Int32Constant(kHoleNanLower32); |
| StoreNoWriteBarrier(MachineRepresentation::kWord32, elements, offset, |
| double_hole); |
| StoreNoWriteBarrier(MachineRepresentation::kWord32, elements, |
| IntPtrAdd(offset, IntPtrConstant(kPointerSize)), |
| double_hole); |
| } |
| args.PopAndReturn(AllocateHeapNumberWithValue(value)); |
| |
| BIND(&fast_elements); |
| { |
| Node* value = LoadFixedArrayElement(CAST(elements), new_length); |
| StoreFixedArrayElement(CAST(elements), new_length, TheHoleConstant()); |
| GotoIf(WordEqual(value, TheHoleConstant()), &return_undefined); |
| args.PopAndReturn(value); |
| } |
| |
| BIND(&return_undefined); |
| { args.PopAndReturn(UndefinedConstant()); } |
| } |
| |
| BIND(&runtime); |
| { |
| // We are not using Parameter(Descriptor::kJSTarget) and loading the value |
| // from the current frame here in order to reduce register pressure on the |
| // fast path. |
| TNode<JSFunction> target = LoadTargetFromFrame(); |
| TailCallBuiltin(Builtins::kArrayPop, context, target, UndefinedConstant(), |
| argc); |
| } |
| } |
| |
| TF_BUILTIN(ArrayPrototypePush, CodeStubAssembler) { |
| TVARIABLE(IntPtrT, arg_index); |
| Label default_label(this, &arg_index); |
| Label smi_transition(this); |
| Label object_push_pre(this); |
| Label object_push(this, &arg_index); |
| Label double_push(this, &arg_index); |
| Label double_transition(this); |
| Label runtime(this, Label::kDeferred); |
| |
| // TODO(ishell): use constants from Descriptor once the JSFunction linkage |
| // arguments are reordered. |
| TNode<Int32T> argc = |
| UncheckedCast<Int32T>(Parameter(Descriptor::kJSActualArgumentsCount)); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| CSA_ASSERT(this, IsUndefined(Parameter(Descriptor::kJSNewTarget))); |
| |
| CodeStubArguments args(this, ChangeInt32ToIntPtr(argc)); |
| TNode<Object> receiver = args.GetReceiver(); |
| TNode<JSArray> array_receiver; |
| Node* kind = nullptr; |
| |
| Label fast(this); |
| BranchIfFastJSArray(receiver, context, &fast, &runtime); |
| |
| BIND(&fast); |
| { |
| array_receiver = CAST(receiver); |
| arg_index = IntPtrConstant(0); |
| kind = EnsureArrayPushable(LoadMap(array_receiver), &runtime); |
| GotoIf(IsElementsKindGreaterThan(kind, HOLEY_SMI_ELEMENTS), |
| &object_push_pre); |
| |
| Node* new_length = BuildAppendJSArray(PACKED_SMI_ELEMENTS, array_receiver, |
| &args, &arg_index, &smi_transition); |
| args.PopAndReturn(new_length); |
| } |
| |
| // If the argument is not a smi, then use a heavyweight SetProperty to |
| // transition the array for only the single next element. If the argument is |
| // a smi, the failure is due to some other reason and we should fall back on |
| // the most generic implementation for the rest of the array. |
| BIND(&smi_transition); |
| { |
| Node* arg = args.AtIndex(arg_index.value()); |
| GotoIf(TaggedIsSmi(arg), &default_label); |
| Node* length = LoadJSArrayLength(array_receiver); |
| // TODO(danno): Use the KeyedStoreGeneric stub here when possible, |
| // calling into the runtime to do the elements transition is overkill. |
| SetPropertyStrict(context, array_receiver, CAST(length), CAST(arg)); |
| Increment(&arg_index); |
| // The runtime SetProperty call could have converted the array to dictionary |
| // mode, which must be detected to abort the fast-path. |
| Node* map = LoadMap(array_receiver); |
| Node* bit_field2 = LoadMapBitField2(map); |
| Node* kind = DecodeWord32<Map::ElementsKindBits>(bit_field2); |
| GotoIf(Word32Equal(kind, Int32Constant(DICTIONARY_ELEMENTS)), |
| &default_label); |
| |
| GotoIfNotNumber(arg, &object_push); |
| Goto(&double_push); |
| } |
| |
| BIND(&object_push_pre); |
| { |
| Branch(IsElementsKindGreaterThan(kind, HOLEY_ELEMENTS), &double_push, |
| &object_push); |
| } |
| |
| BIND(&object_push); |
| { |
| Node* new_length = BuildAppendJSArray(PACKED_ELEMENTS, array_receiver, |
| &args, &arg_index, &default_label); |
| args.PopAndReturn(new_length); |
| } |
| |
| BIND(&double_push); |
| { |
| Node* new_length = |
| BuildAppendJSArray(PACKED_DOUBLE_ELEMENTS, array_receiver, &args, |
| &arg_index, &double_transition); |
| args.PopAndReturn(new_length); |
| } |
| |
| // If the argument is not a double, then use a heavyweight SetProperty to |
| // transition the array for only the single next element. If the argument is |
| // a double, the failure is due to some other reason and we should fall back |
| // on the most generic implementation for the rest of the array. |
| BIND(&double_transition); |
| { |
| Node* arg = args.AtIndex(arg_index.value()); |
| GotoIfNumber(arg, &default_label); |
| Node* length = LoadJSArrayLength(array_receiver); |
| // TODO(danno): Use the KeyedStoreGeneric stub here when possible, |
| // calling into the runtime to do the elements transition is overkill. |
| SetPropertyStrict(context, array_receiver, CAST(length), CAST(arg)); |
| Increment(&arg_index); |
| // The runtime SetProperty call could have converted the array to dictionary |
| // mode, which must be detected to abort the fast-path. |
| Node* map = LoadMap(array_receiver); |
| Node* bit_field2 = LoadMapBitField2(map); |
| Node* kind = DecodeWord32<Map::ElementsKindBits>(bit_field2); |
| GotoIf(Word32Equal(kind, Int32Constant(DICTIONARY_ELEMENTS)), |
| &default_label); |
| Goto(&object_push); |
| } |
| |
| // Fallback that stores un-processed arguments using the full, heavyweight |
| // SetProperty machinery. |
| BIND(&default_label); |
| { |
| args.ForEach( |
| [this, array_receiver, context](Node* arg) { |
| Node* length = LoadJSArrayLength(array_receiver); |
| SetPropertyStrict(context, array_receiver, CAST(length), CAST(arg)); |
| }, |
| arg_index.value()); |
| args.PopAndReturn(LoadJSArrayLength(array_receiver)); |
| } |
| |
| BIND(&runtime); |
| { |
| // We are not using Parameter(Descriptor::kJSTarget) and loading the value |
| // from the current frame here in order to reduce register pressure on the |
| // fast path. |
| TNode<JSFunction> target = LoadTargetFromFrame(); |
| TailCallBuiltin(Builtins::kArrayPush, context, target, UndefinedConstant(), |
| argc); |
| } |
| } |
| |
| class ArrayPrototypeSliceCodeStubAssembler : public CodeStubAssembler { |
| public: |
| explicit ArrayPrototypeSliceCodeStubAssembler( |
| compiler::CodeAssemblerState* state) |
| : CodeStubAssembler(state) {} |
| |
| Node* HandleFastSlice(TNode<Context> context, Node* array, Node* from, |
| Node* count, Label* slow) { |
| VARIABLE(result, MachineRepresentation::kTagged); |
| Label done(this); |
| |
| GotoIf(TaggedIsNotSmi(from), slow); |
| GotoIf(TaggedIsNotSmi(count), slow); |
| |
| Label try_fast_arguments(this), try_simple_slice(this); |
| |
| Node* map = LoadMap(array); |
| GotoIfNot(IsJSArrayMap(map), &try_fast_arguments); |
| |
| // Check prototype chain if receiver does not have packed elements |
| GotoIfNot(IsPrototypeInitialArrayPrototype(context, map), slow); |
| |
| GotoIf(IsNoElementsProtectorCellInvalid(), slow); |
| |
| GotoIf(IsArraySpeciesProtectorCellInvalid(), slow); |
| |
| // Bailout if receiver has slow elements. |
| Node* elements_kind = LoadMapElementsKind(map); |
| GotoIfNot(IsFastElementsKind(elements_kind), &try_simple_slice); |
| |
| // Make sure that the length hasn't been changed by side-effect. |
| Node* array_length = LoadJSArrayLength(array); |
| GotoIf(TaggedIsNotSmi(array_length), slow); |
| GotoIf(SmiAbove(SmiAdd(CAST(from), CAST(count)), CAST(array_length)), slow); |
| |
| CSA_ASSERT(this, SmiGreaterThanOrEqual(CAST(from), SmiConstant(0))); |
| |
| result.Bind(CallBuiltin(Builtins::kExtractFastJSArray, context, array, from, |
| count)); |
| Goto(&done); |
| |
| BIND(&try_fast_arguments); |
| |
| Node* const native_context = LoadNativeContext(context); |
| Node* const fast_aliasted_arguments_map = LoadContextElement( |
| native_context, Context::FAST_ALIASED_ARGUMENTS_MAP_INDEX); |
| GotoIf(WordNotEqual(map, fast_aliasted_arguments_map), &try_simple_slice); |
| |
| TNode<SloppyArgumentsElements> sloppy_elements = CAST(LoadElements(array)); |
| TNode<Smi> sloppy_elements_length = |
| LoadFixedArrayBaseLength(sloppy_elements); |
| TNode<Smi> parameter_map_length = |
| SmiSub(sloppy_elements_length, |
| SmiConstant(SloppyArgumentsElements::kParameterMapStart)); |
| VARIABLE(index_out, MachineType::PointerRepresentation()); |
| |
| int max_fast_elements = |
| (kMaxRegularHeapObjectSize - FixedArray::kHeaderSize - JSArray::kSize - |
| AllocationMemento::kSize) / |
| kPointerSize; |
| GotoIf(SmiAboveOrEqual(CAST(count), SmiConstant(max_fast_elements)), |
| &try_simple_slice); |
| |
| GotoIf(SmiLessThan(CAST(from), SmiConstant(0)), slow); |
| |
| TNode<Smi> end = SmiAdd(CAST(from), CAST(count)); |
| |
| TNode<FixedArray> unmapped_elements = CAST(LoadFixedArrayElement( |
| sloppy_elements, SloppyArgumentsElements::kArgumentsIndex)); |
| TNode<Smi> unmapped_elements_length = |
| LoadFixedArrayBaseLength(unmapped_elements); |
| |
| GotoIf(SmiAbove(end, unmapped_elements_length), slow); |
| |
| Node* array_map = LoadJSArrayElementsMap(HOLEY_ELEMENTS, native_context); |
| result.Bind(AllocateJSArray(HOLEY_ELEMENTS, array_map, count, count, |
| nullptr, SMI_PARAMETERS)); |
| |
| index_out.Bind(IntPtrConstant(0)); |
| TNode<FixedArray> result_elements = CAST(LoadElements(result.value())); |
| TNode<Smi> from_mapped = SmiMin(parameter_map_length, CAST(from)); |
| TNode<Smi> to = SmiMin(parameter_map_length, end); |
| Node* arguments_context = LoadFixedArrayElement( |
| sloppy_elements, SloppyArgumentsElements::kContextIndex); |
| VariableList var_list({&index_out}, zone()); |
| BuildFastLoop( |
| var_list, from_mapped, to, |
| [this, result_elements, arguments_context, sloppy_elements, |
| unmapped_elements, &index_out](Node* current) { |
| Node* context_index = LoadFixedArrayElement( |
| sloppy_elements, current, |
| kPointerSize * SloppyArgumentsElements::kParameterMapStart, |
| SMI_PARAMETERS); |
| Label is_the_hole(this), done(this); |
| GotoIf(IsTheHole(context_index), &is_the_hole); |
| Node* mapped_argument = |
| LoadContextElement(arguments_context, SmiUntag(context_index)); |
| StoreFixedArrayElement(result_elements, index_out.value(), |
| mapped_argument, SKIP_WRITE_BARRIER); |
| Goto(&done); |
| BIND(&is_the_hole); |
| Node* argument = LoadFixedArrayElement(unmapped_elements, current, 0, |
| SMI_PARAMETERS); |
| StoreFixedArrayElement(result_elements, index_out.value(), argument, |
| SKIP_WRITE_BARRIER); |
| Goto(&done); |
| BIND(&done); |
| index_out.Bind(IntPtrAdd(index_out.value(), IntPtrConstant(1))); |
| }, |
| 1, SMI_PARAMETERS, IndexAdvanceMode::kPost); |
| |
| TNode<Smi> unmapped_from = |
| SmiMin(SmiMax(parameter_map_length, CAST(from)), end); |
| |
| BuildFastLoop( |
| var_list, unmapped_from, end, |
| [this, unmapped_elements, result_elements, &index_out](Node* current) { |
| Node* argument = LoadFixedArrayElement(unmapped_elements, current, 0, |
| SMI_PARAMETERS); |
| StoreFixedArrayElement(result_elements, index_out.value(), argument, |
| SKIP_WRITE_BARRIER); |
| index_out.Bind(IntPtrAdd(index_out.value(), IntPtrConstant(1))); |
| }, |
| 1, SMI_PARAMETERS, IndexAdvanceMode::kPost); |
| |
| Goto(&done); |
| |
| BIND(&try_simple_slice); |
| Node* simple_result = CallRuntime(Runtime::kTrySliceSimpleNonFastElements, |
| context, array, from, count); |
| GotoIfNumber(simple_result, slow); |
| result.Bind(simple_result); |
| |
| Goto(&done); |
| |
| BIND(&done); |
| return result.value(); |
| } |
| |
| void CopyOneElement(TNode<Context> context, Node* o, Node* a, Node* p_k, |
| Variable& n) { |
| // b. Let kPresent be HasProperty(O, Pk). |
| // c. ReturnIfAbrupt(kPresent). |
| TNode<Oddball> k_present = HasProperty(context, o, p_k, kHasProperty); |
| |
| // d. If kPresent is true, then |
| Label done_element(this); |
| GotoIf(IsFalse(k_present), &done_element); |
| |
| // i. Let kValue be Get(O, Pk). |
| // ii. ReturnIfAbrupt(kValue). |
| Node* k_value = GetProperty(context, o, p_k); |
| |
| // iii. Let status be CreateDataPropertyOrThrow(A, ToString(n), kValue). |
| // iv. ReturnIfAbrupt(status). |
| CallRuntime(Runtime::kCreateDataProperty, context, a, n.value(), k_value); |
| |
| Goto(&done_element); |
| BIND(&done_element); |
| } |
| }; |
| |
| TF_BUILTIN(ArrayPrototypeSlice, ArrayPrototypeSliceCodeStubAssembler) { |
| Node* const argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| Label slow(this, Label::kDeferred), fast_elements_kind(this); |
| |
| CodeStubArguments args(this, argc); |
| TNode<Object> receiver = args.GetReceiver(); |
| |
| TVARIABLE(JSReceiver, o); |
| VARIABLE(len, MachineRepresentation::kTagged); |
| Label length_done(this), generic_length(this), check_arguments_length(this), |
| load_arguments_length(this); |
| |
| GotoIf(TaggedIsSmi(receiver), &generic_length); |
| GotoIfNot(IsJSArray(CAST(receiver)), &check_arguments_length); |
| |
| TNode<JSArray> array_receiver = CAST(receiver); |
| o = array_receiver; |
| len.Bind(LoadJSArrayLength(array_receiver)); |
| |
| // Check for the array clone case. There can be no arguments to slice, the |
| // array prototype chain must be intact and have no elements, the array has to |
| // have fast elements. |
| GotoIf(WordNotEqual(argc, IntPtrConstant(0)), &length_done); |
| |
| Label clone(this); |
| BranchIfFastJSArrayForCopy(receiver, context, &clone, &length_done); |
| BIND(&clone); |
| |
| args.PopAndReturn( |
| CallBuiltin(Builtins::kCloneFastJSArray, context, receiver)); |
| |
| BIND(&check_arguments_length); |
| |
| Node* map = LoadMap(array_receiver); |
| Node* native_context = LoadNativeContext(context); |
| GotoIfContextElementEqual(map, native_context, |
| Context::FAST_ALIASED_ARGUMENTS_MAP_INDEX, |
| &load_arguments_length); |
| GotoIfContextElementEqual(map, native_context, |
| Context::SLOW_ALIASED_ARGUMENTS_MAP_INDEX, |
| &load_arguments_length); |
| GotoIfContextElementEqual(map, native_context, |
| Context::STRICT_ARGUMENTS_MAP_INDEX, |
| &load_arguments_length); |
| GotoIfContextElementEqual(map, native_context, |
| Context::SLOPPY_ARGUMENTS_MAP_INDEX, |
| &load_arguments_length); |
| |
| Goto(&generic_length); |
| |
| BIND(&load_arguments_length); |
| Node* arguments_length = |
| LoadObjectField(array_receiver, JSArgumentsObject::kLengthOffset); |
| GotoIf(TaggedIsNotSmi(arguments_length), &generic_length); |
| o = CAST(receiver); |
| len.Bind(arguments_length); |
| Goto(&length_done); |
| |
| BIND(&generic_length); |
| // 1. Let O be ToObject(this value). |
| // 2. ReturnIfAbrupt(O). |
| o = ToObject_Inline(context, receiver); |
| |
| // 3. Let len be ToLength(Get(O, "length")). |
| // 4. ReturnIfAbrupt(len). |
| len.Bind(ToLength_Inline( |
| context, |
| GetProperty(context, o.value(), isolate()->factory()->length_string()))); |
| Goto(&length_done); |
| |
| BIND(&length_done); |
| |
| // 5. Let relativeStart be ToInteger(start). |
| // 6. ReturnIfAbrupt(relativeStart). |
| TNode<Object> arg0 = args.GetOptionalArgumentValue(0, SmiConstant(0)); |
| Node* relative_start = ToInteger_Inline(context, arg0); |
| |
| // 7. If relativeStart < 0, let k be max((len + relativeStart),0); |
| // else let k be min(relativeStart, len.value()). |
| VARIABLE(k, MachineRepresentation::kTagged); |
| Label relative_start_positive(this), relative_start_done(this); |
| GotoIfNumberGreaterThanOrEqual(relative_start, SmiConstant(0), |
| &relative_start_positive); |
| k.Bind(NumberMax(NumberAdd(len.value(), relative_start), NumberConstant(0))); |
| Goto(&relative_start_done); |
| BIND(&relative_start_positive); |
| k.Bind(NumberMin(relative_start, len.value())); |
| Goto(&relative_start_done); |
| BIND(&relative_start_done); |
| |
| // 8. If end is undefined, let relativeEnd be len; |
| // else let relativeEnd be ToInteger(end). |
| // 9. ReturnIfAbrupt(relativeEnd). |
| TNode<Object> end = args.GetOptionalArgumentValue(1, UndefinedConstant()); |
| Label end_undefined(this), end_done(this); |
| VARIABLE(relative_end, MachineRepresentation::kTagged); |
| GotoIf(WordEqual(end, UndefinedConstant()), &end_undefined); |
| relative_end.Bind(ToInteger_Inline(context, end)); |
| Goto(&end_done); |
| BIND(&end_undefined); |
| relative_end.Bind(len.value()); |
| Goto(&end_done); |
| BIND(&end_done); |
| |
| // 10. If relativeEnd < 0, let final be max((len + relativeEnd),0); |
| // else let final be min(relativeEnd, len). |
| VARIABLE(final, MachineRepresentation::kTagged); |
| Label relative_end_positive(this), relative_end_done(this); |
| GotoIfNumberGreaterThanOrEqual(relative_end.value(), NumberConstant(0), |
| &relative_end_positive); |
| final.Bind(NumberMax(NumberAdd(len.value(), relative_end.value()), |
| NumberConstant(0))); |
| Goto(&relative_end_done); |
| BIND(&relative_end_positive); |
| final.Bind(NumberMin(relative_end.value(), len.value())); |
| Goto(&relative_end_done); |
| BIND(&relative_end_done); |
| |
| // 11. Let count be max(final – k, 0). |
| Node* count = |
| NumberMax(NumberSub(final.value(), k.value()), NumberConstant(0)); |
| |
| // Handle FAST_ELEMENTS |
| Label non_fast(this); |
| Node* fast_result = |
| HandleFastSlice(context, o.value(), k.value(), count, &non_fast); |
| args.PopAndReturn(fast_result); |
| |
| // 12. Let A be ArraySpeciesCreate(O, count). |
| // 13. ReturnIfAbrupt(A). |
| BIND(&non_fast); |
| |
| Node* constructor = |
| CallRuntime(Runtime::kArraySpeciesConstructor, context, o.value()); |
| Node* a = ConstructJS(CodeFactory::Construct(isolate()), context, constructor, |
| count); |
| |
| // 14. Let n be 0. |
| VARIABLE(n, MachineRepresentation::kTagged); |
| n.Bind(SmiConstant(0)); |
| |
| Label loop(this, {&k, &n}); |
| Label after_loop(this); |
| Goto(&loop); |
| BIND(&loop); |
| { |
| // 15. Repeat, while k < final |
| GotoIfNumberGreaterThanOrEqual(k.value(), final.value(), &after_loop); |
| |
| Node* p_k = k.value(); // ToString(context, k.value()) is no-op |
| |
| CopyOneElement(context, o.value(), a, p_k, n); |
| |
| // e. Increase k by 1. |
| k.Bind(NumberInc(k.value())); |
| |
| // f. Increase n by 1. |
| n.Bind(NumberInc(n.value())); |
| |
| Goto(&loop); |
| } |
| |
| BIND(&after_loop); |
| |
| // 16. Let setStatus be Set(A, "length", n, true). |
| // 17. ReturnIfAbrupt(setStatus). |
| SetPropertyStrict(context, CAST(a), CodeStubAssembler::LengthStringConstant(), |
| CAST(n.value())); |
| args.PopAndReturn(a); |
| } |
| |
| TNode<Object> ArrayBuiltinsAssembler::GenerateFastArrayShift( |
| TNode<Context> context, TNode<Object> receiver, Label* slow) { |
| Label fast(this), done(this); |
| |
| // Only shift in this stub if |
| // 1) the array has fast elements |
| // 2) the length is writable, |
| // 3) the elements backing store isn't copy-on-write, |
| // 4) we aren't supposed to shrink the backing store, |
| // 5) we aren't supposed to left-trim the backing store. |
| |
| // 1) Check that the array has fast elements. |
| BranchIfFastJSArray(receiver, context, &fast, slow); |
| |
| BIND(&fast); |
| { |
| TVARIABLE(Object, result, UndefinedConstant()); |
| TNode<JSArray> array_receiver = CAST(receiver); |
| CSA_ASSERT(this, TaggedIsPositiveSmi(LoadJSArrayLength(array_receiver))); |
| Node* length = |
| LoadAndUntagObjectField(array_receiver, JSArray::kLengthOffset); |
| Label return_undefined(this), fast_elements_tagged(this), |
| fast_elements_smi(this); |
| GotoIf(IntPtrEqual(length, IntPtrConstant(0)), &return_undefined); |
| |
| // 2) Ensure that the length is writable. |
| EnsureArrayLengthWritable(LoadMap(array_receiver), slow); |
| |
| // 3) Check that the elements backing store isn't copy-on-write. |
| Node* elements = LoadElements(array_receiver); |
| GotoIf(WordEqual(LoadMap(elements), |
| LoadRoot(Heap::kFixedCOWArrayMapRootIndex)), |
| slow); |
| |
| Node* new_length = IntPtrSub(length, IntPtrConstant(1)); |
| |
| // 4) Check that we're not supposed to right-trim the backing store, as |
| // implemented in elements.cc:ElementsAccessorBase::SetLengthImpl. |
| Node* capacity = SmiUntag(LoadFixedArrayBaseLength(elements)); |
| GotoIf(IntPtrLessThan( |
| IntPtrAdd(IntPtrAdd(new_length, new_length), |
| IntPtrConstant(JSObject::kMinAddedElementsCapacity)), |
| capacity), |
| slow); |
| |
| // 5) Check that we're not supposed to left-trim the backing store, as |
| // implemented in elements.cc:FastElementsAccessor::MoveElements. |
| GotoIf(IntPtrGreaterThan(new_length, |
| IntPtrConstant(JSArray::kMaxCopyElements)), |
| slow); |
| |
| StoreObjectFieldNoWriteBarrier(array_receiver, JSArray::kLengthOffset, |
| SmiTag(new_length)); |
| |
| TNode<Int32T> elements_kind = LoadElementsKind(array_receiver); |
| GotoIf( |
| Int32LessThanOrEqual(elements_kind, Int32Constant(HOLEY_SMI_ELEMENTS)), |
| &fast_elements_smi); |
| GotoIf(Int32LessThanOrEqual(elements_kind, Int32Constant(HOLEY_ELEMENTS)), |
| &fast_elements_tagged); |
| |
| // Fast double elements kind: |
| { |
| CSA_ASSERT(this, |
| Int32LessThanOrEqual(elements_kind, |
| Int32Constant(HOLEY_DOUBLE_ELEMENTS))); |
| |
| Label move_elements(this); |
| result = AllocateHeapNumberWithValue(LoadFixedDoubleArrayElement( |
| elements, IntPtrConstant(0), MachineType::Float64(), 0, |
| INTPTR_PARAMETERS, &move_elements)); |
| Goto(&move_elements); |
| BIND(&move_elements); |
| |
| int32_t header_size = FixedDoubleArray::kHeaderSize - kHeapObjectTag; |
| Node* memmove = |
| ExternalConstant(ExternalReference::libc_memmove_function()); |
| Node* start = IntPtrAdd( |
| BitcastTaggedToWord(elements), |
| ElementOffsetFromIndex(IntPtrConstant(0), HOLEY_DOUBLE_ELEMENTS, |
| INTPTR_PARAMETERS, header_size)); |
| CallCFunction3(MachineType::AnyTagged(), MachineType::Pointer(), |
| MachineType::Pointer(), MachineType::UintPtr(), memmove, |
| start, IntPtrAdd(start, IntPtrConstant(kDoubleSize)), |
| IntPtrMul(new_length, IntPtrConstant(kDoubleSize))); |
| Node* offset = ElementOffsetFromIndex(new_length, HOLEY_DOUBLE_ELEMENTS, |
| INTPTR_PARAMETERS, header_size); |
| if (Is64()) { |
| Node* double_hole = Int64Constant(kHoleNanInt64); |
| StoreNoWriteBarrier(MachineRepresentation::kWord64, elements, offset, |
| double_hole); |
| } else { |
| STATIC_ASSERT(kHoleNanLower32 == kHoleNanUpper32); |
| Node* double_hole = Int32Constant(kHoleNanLower32); |
| StoreNoWriteBarrier(MachineRepresentation::kWord32, elements, offset, |
| double_hole); |
| StoreNoWriteBarrier(MachineRepresentation::kWord32, elements, |
| IntPtrAdd(offset, IntPtrConstant(kPointerSize)), |
| double_hole); |
| } |
| |
| Goto(&done); |
| } |
| |
| BIND(&fast_elements_tagged); |
| { |
| TNode<FixedArray> elements_fixed_array = CAST(elements); |
| TNode<Object> value = LoadFixedArrayElement(elements_fixed_array, 0); |
| BuildFastLoop( |
| IntPtrConstant(0), new_length, |
| [&](Node* index) { |
| StoreFixedArrayElement( |
| elements_fixed_array, index, |
| LoadFixedArrayElement(elements_fixed_array, |
| IntPtrAdd(index, IntPtrConstant(1)))); |
| }, |
| 1, ParameterMode::INTPTR_PARAMETERS, IndexAdvanceMode::kPost); |
| StoreFixedArrayElement(elements_fixed_array, new_length, |
| TheHoleConstant()); |
| GotoIf(WordEqual(value, TheHoleConstant()), &return_undefined); |
| |
| result = value; |
| Goto(&done); |
| } |
| |
| BIND(&fast_elements_smi); |
| { |
| TNode<FixedArray> elements_fixed_array = CAST(elements); |
| TNode<Object> value = LoadFixedArrayElement(elements_fixed_array, 0); |
| BuildFastLoop( |
| IntPtrConstant(0), new_length, |
| [&](Node* index) { |
| StoreFixedArrayElement( |
| elements_fixed_array, index, |
| LoadFixedArrayElement(elements_fixed_array, |
| IntPtrAdd(index, IntPtrConstant(1))), |
| SKIP_WRITE_BARRIER); |
| }, |
| 1, ParameterMode::INTPTR_PARAMETERS, IndexAdvanceMode::kPost); |
| StoreFixedArrayElement(elements_fixed_array, new_length, |
| TheHoleConstant()); |
| GotoIf(WordEqual(value, TheHoleConstant()), &return_undefined); |
| |
| result = value; |
| Goto(&done); |
| } |
| |
| BIND(&return_undefined); |
| { |
| result = UndefinedConstant(); |
| Goto(&done); |
| } |
| |
| BIND(&done); |
| return result.value(); |
| } |
| } |
| |
| TF_BUILTIN(ExtractFastJSArray, ArrayBuiltinsAssembler) { |
| ParameterMode mode = OptimalParameterMode(); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| Node* array = Parameter(Descriptor::kSource); |
| Node* begin = TaggedToParameter(Parameter(Descriptor::kBegin), mode); |
| Node* count = TaggedToParameter(Parameter(Descriptor::kCount), mode); |
| |
| CSA_ASSERT(this, IsJSArray(array)); |
| CSA_ASSERT(this, Word32BinaryNot(IsNoElementsProtectorCellInvalid())); |
| |
| Return(ExtractFastJSArray(context, array, begin, count, mode)); |
| } |
| |
| TF_BUILTIN(CloneFastJSArray, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| Node* array = Parameter(Descriptor::kSource); |
| |
| CSA_ASSERT(this, IsJSArray(array)); |
| CSA_ASSERT(this, Word32BinaryNot(IsNoElementsProtectorCellInvalid())); |
| |
| ParameterMode mode = OptimalParameterMode(); |
| Return(CloneFastJSArray(context, array, mode)); |
| } |
| |
| TF_BUILTIN(ArrayFindLoopContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject)); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn, |
| this_arg, array, object, initial_k, |
| len, to); |
| |
| GenerateIteratingArrayBuiltinLoopContinuation( |
| &ArrayBuiltinsAssembler::FindProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, |
| MissingPropertyMode::kUseUndefined, ForEachDirection::kForward); |
| } |
| |
| // Continuation that is called after an eager deoptimization from TF (ex. the |
| // array changes during iteration). |
| TF_BUILTIN(ArrayFindLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArrayFindLoopContinuation, context, receiver, |
| callbackfn, this_arg, UndefinedConstant(), receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| |
| // Continuation that is called after a lazy deoptimization from TF (ex. the |
| // callback function is no longer callable). |
| TF_BUILTIN(ArrayFindLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArrayFindLoopContinuation, context, receiver, |
| callbackfn, this_arg, UndefinedConstant(), receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| |
| // Continuation that is called after a lazy deoptimization from TF that happens |
| // right after the callback and it's returned value must be handled before |
| // iteration continues. |
| TF_BUILTIN(ArrayFindLoopAfterCallbackLazyDeoptContinuation, |
| ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* found_value = Parameter(Descriptor::kFoundValue); |
| Node* is_found = Parameter(Descriptor::kIsFound); |
| |
| // This custom lazy deopt point is right after the callback. find() needs |
| // to pick up at the next step, which is returning the element if the callback |
| // value is truthy. Otherwise, continue the search by calling the |
| // continuation. |
| Label if_true(this), if_false(this); |
| BranchIfToBooleanIsTrue(is_found, &if_true, &if_false); |
| BIND(&if_true); |
| Return(found_value); |
| BIND(&if_false); |
| Return(CallBuiltin(Builtins::kArrayFindLoopContinuation, context, receiver, |
| callbackfn, this_arg, UndefinedConstant(), receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| |
| // ES #sec-get-%typedarray%.prototype.find |
| TF_BUILTIN(ArrayPrototypeFind, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingArrayBuiltinBody( |
| "Array.prototype.find", &ArrayBuiltinsAssembler::FindResultGenerator, |
| &ArrayBuiltinsAssembler::FindProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, |
| Builtins::CallableFor(isolate(), Builtins::kArrayFindLoopContinuation), |
| MissingPropertyMode::kUseUndefined, ForEachDirection::kForward); |
| } |
| |
| TF_BUILTIN(ArrayFindIndexLoopContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject)); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn, |
| this_arg, array, object, initial_k, |
| len, to); |
| |
| GenerateIteratingArrayBuiltinLoopContinuation( |
| &ArrayBuiltinsAssembler::FindIndexProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, |
| MissingPropertyMode::kUseUndefined, ForEachDirection::kForward); |
| } |
| |
| TF_BUILTIN(ArrayFindIndexLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArrayFindIndexLoopContinuation, context, |
| receiver, callbackfn, this_arg, SmiConstant(-1), receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayFindIndexLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArrayFindIndexLoopContinuation, context, |
| receiver, callbackfn, this_arg, SmiConstant(-1), receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayFindIndexLoopAfterCallbackLazyDeoptContinuation, |
| ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* found_value = Parameter(Descriptor::kFoundValue); |
| Node* is_found = Parameter(Descriptor::kIsFound); |
| |
| // This custom lazy deopt point is right after the callback. find() needs |
| // to pick up at the next step, which is returning the element if the callback |
| // value is truthy. Otherwise, continue the search by calling the |
| // continuation. |
| Label if_true(this), if_false(this); |
| BranchIfToBooleanIsTrue(is_found, &if_true, &if_false); |
| BIND(&if_true); |
| Return(found_value); |
| BIND(&if_false); |
| Return(CallBuiltin(Builtins::kArrayFindIndexLoopContinuation, context, |
| receiver, callbackfn, this_arg, SmiConstant(-1), receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| |
| // ES #sec-get-%typedarray%.prototype.findIndex |
| TF_BUILTIN(ArrayPrototypeFindIndex, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingArrayBuiltinBody( |
| "Array.prototype.findIndex", |
| &ArrayBuiltinsAssembler::FindIndexResultGenerator, |
| &ArrayBuiltinsAssembler::FindIndexProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, |
| Builtins::CallableFor(isolate(), |
| Builtins::kArrayFindIndexLoopContinuation), |
| MissingPropertyMode::kUseUndefined, ForEachDirection::kForward); |
| } |
| |
| class ArrayPopulatorAssembler : public CodeStubAssembler { |
| public: |
| explicit ArrayPopulatorAssembler(compiler::CodeAssemblerState* state) |
| : CodeStubAssembler(state) {} |
| |
| TNode<Object> ConstructArrayLike(TNode<Context> context, |
| TNode<Object> receiver) { |
| TVARIABLE(Object, array); |
| Label is_constructor(this), is_not_constructor(this), done(this); |
| GotoIf(TaggedIsSmi(receiver), &is_not_constructor); |
| Branch(IsConstructor(CAST(receiver)), &is_constructor, &is_not_constructor); |
| |
| BIND(&is_constructor); |
| { |
| array = CAST( |
| ConstructJS(CodeFactory::Construct(isolate()), context, receiver)); |
| Goto(&done); |
| } |
| |
| BIND(&is_not_constructor); |
| { |
| Label allocate_js_array(this); |
| |
| TNode<Map> array_map = CAST(LoadContextElement( |
| context, Context::JS_ARRAY_PACKED_SMI_ELEMENTS_MAP_INDEX)); |
| |
| array = CAST(AllocateJSArray(PACKED_SMI_ELEMENTS, array_map, |
| SmiConstant(0), SmiConstant(0), nullptr, |
| ParameterMode::SMI_PARAMETERS)); |
| Goto(&done); |
| } |
| |
| BIND(&done); |
| return array.value(); |
| } |
| |
| TNode<Object> ConstructArrayLike(TNode<Context> context, |
| TNode<Object> receiver, |
| TNode<Number> length) { |
| TVARIABLE(Object, array); |
| Label is_constructor(this), is_not_constructor(this), done(this); |
| CSA_ASSERT(this, IsNumberNormalized(length)); |
| GotoIf(TaggedIsSmi(receiver), &is_not_constructor); |
| Branch(IsConstructor(CAST(receiver)), &is_constructor, &is_not_constructor); |
| |
| BIND(&is_constructor); |
| { |
| array = CAST(ConstructJS(CodeFactory::Construct(isolate()), context, |
| receiver, length)); |
| Goto(&done); |
| } |
| |
| BIND(&is_not_constructor); |
| { |
| Label allocate_js_array(this); |
| |
| Label next(this), runtime(this, Label::kDeferred); |
| TNode<Smi> limit = SmiConstant(JSArray::kInitialMaxFastElementArray); |
| CSA_ASSERT_BRANCH(this, [=](Label* ok, Label* not_ok) { |
| BranchIfNumberRelationalComparison(Operation::kGreaterThanOrEqual, |
| length, SmiConstant(0), ok, not_ok); |
| }); |
| // This check also transitively covers the case where length is too big |
| // to be representable by a SMI and so is not usable with |
| // AllocateJSArray. |
| BranchIfNumberRelationalComparison(Operation::kGreaterThanOrEqual, length, |
| limit, &runtime, &next); |
| |
| BIND(&runtime); |
| { |
| TNode<Context> native_context = LoadNativeContext(context); |
| TNode<JSFunction> array_function = CAST( |
| LoadContextElement(native_context, Context::ARRAY_FUNCTION_INDEX)); |
| array = CallRuntime(Runtime::kNewArray, context, array_function, length, |
| array_function, UndefinedConstant()); |
| Goto(&done); |
| } |
| |
| BIND(&next); |
| CSA_ASSERT(this, TaggedIsSmi(length)); |
| |
| TNode<Map> array_map = CAST(LoadContextElement( |
| context, Context::JS_ARRAY_PACKED_SMI_ELEMENTS_MAP_INDEX)); |
| |
| // TODO(delphick): Consider using |
| // AllocateUninitializedJSArrayWithElements to avoid initializing an |
| // array and then writing over it. |
| array = CAST(AllocateJSArray(PACKED_SMI_ELEMENTS, array_map, length, |
| SmiConstant(0), nullptr, |
| ParameterMode::SMI_PARAMETERS)); |
| Goto(&done); |
| } |
| |
| BIND(&done); |
| return array.value(); |
| } |
| |
| void GenerateSetLength(TNode<Context> context, TNode<Object> array, |
| TNode<Number> length) { |
| Label fast(this), runtime(this), done(this); |
| // There's no need to set the length, if |
| // 1) the array is a fast JS array and |
| // 2) the new length is equal to the old length. |
| // as the set is not observable. Otherwise fall back to the run-time. |
| |
| // 1) Check that the array has fast elements. |
| // TODO(delphick): Consider changing this since it does an an unnecessary |
| // check for SMIs. |
| // TODO(delphick): Also we could hoist this to after the array construction |
| // and copy the args into array in the same way as the Array constructor. |
| BranchIfFastJSArray(array, context, &fast, &runtime); |
| |
| BIND(&fast); |
| { |
| TNode<JSArray> fast_array = CAST(array); |
| |
| TNode<Smi> length_smi = CAST(length); |
| TNode<Smi> old_length = LoadFastJSArrayLength(fast_array); |
| CSA_ASSERT(this, TaggedIsPositiveSmi(old_length)); |
| |
| // 2) If the created array's length matches the required length, then |
| // there's nothing else to do. Otherwise use the runtime to set the |
| // property as that will insert holes into excess elements or shrink |
| // the backing store as appropriate. |
| Branch(SmiNotEqual(length_smi, old_length), &runtime, &done); |
| } |
| |
| BIND(&runtime); |
| { |
| SetPropertyStrict(context, array, |
| CodeStubAssembler::LengthStringConstant(), length); |
| Goto(&done); |
| } |
| |
| BIND(&done); |
| } |
| }; |
| |
| // 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, ChangeInt32ToIntPtr(argc)); |
| |
| 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); |
| TNode<Object> items = args.GetOptionalArgumentValue(0); |
| // 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()); |
| Node* 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, args.GetReceiver()); |
| |
| // 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<Context> native_context = LoadNativeContext(context); |
| TNode<Object> fast_iterator_result_map = |
| LoadContextElement(native_context, Context::ITERATOR_RESULT_MAP_INDEX); |
| |
| Goto(&loop); |
| |
| BIND(&loop); |
| { |
| // Loop while iterator is not done. |
| TNode<Object> next = CAST(iterator_assembler.IteratorStep( |
| context, iterator_record, &loop_done, fast_iterator_result_map)); |
| TVARIABLE(Object, value, |
| CAST(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))); |
| Node* v = CallJS(CodeFactory::Call(isolate()), context, map_function, |
| this_arg, value.value(), index.value()); |
| GotoIfException(v, &on_exception, &var_exception); |
| value = CAST(v); |
| Goto(&next); |
| BIND(&next); |
| } |
| |
| // Store the result in the output object (catching any exceptions so the |
| // iterator can be closed). |
| Node* 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); |
| } |
| } |
| |
| 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, args.GetReceiver(), length.value()); |
| |
| TVARIABLE(Number, index, SmiConstant(0)); |
| |
| // TODO(ishell): remove <Object, Object> |
| GotoIf(WordEqual<Object, Object>(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 = CAST(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. |
| GenerateSetLength(context, array.value(), length.value()); |
| args.PopAndReturn(array.value()); |
| } |
| |
| // ES #sec-array.of |
| TF_BUILTIN(ArrayOf, ArrayPopulatorAssembler) { |
| TNode<Int32T> argc = |
| UncheckedCast<Int32T>(Parameter(Descriptor::kJSActualArgumentsCount)); |
| TNode<Smi> length = SmiFromInt32(argc); |
| |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| |
| CodeStubArguments args(this, length, nullptr, ParameterMode::SMI_PARAMETERS); |
| |
| TNode<Object> array = ConstructArrayLike(context, args.GetReceiver(), length); |
| |
| // TODO(delphick): Avoid using CreateDataProperty on the fast path. |
| BuildFastLoop(SmiConstant(0), length, |
| [=](Node* index) { |
| CallRuntime( |
| Runtime::kCreateDataProperty, context, |
| static_cast<Node*>(array), index, |
| args.AtIndex(index, ParameterMode::SMI_PARAMETERS)); |
| }, |
| 1, ParameterMode::SMI_PARAMETERS, IndexAdvanceMode::kPost); |
| |
| GenerateSetLength(context, array, length); |
| args.PopAndReturn(array); |
| } |
| |
| // ES #sec-get-%typedarray%.prototype.find |
| TF_BUILTIN(TypedArrayPrototypeFind, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingTypedArrayBuiltinBody( |
| "%TypedArray%.prototype.find", |
| &ArrayBuiltinsAssembler::FindResultGenerator, |
| &ArrayBuiltinsAssembler::FindProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction); |
| } |
| |
| // ES #sec-get-%typedarray%.prototype.findIndex |
| TF_BUILTIN(TypedArrayPrototypeFindIndex, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingTypedArrayBuiltinBody( |
| "%TypedArray%.prototype.findIndex", |
| &ArrayBuiltinsAssembler::FindIndexResultGenerator, |
| &ArrayBuiltinsAssembler::FindIndexProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction); |
| } |
| |
| TF_BUILTIN(TypedArrayPrototypeForEach, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingTypedArrayBuiltinBody( |
| "%TypedArray%.prototype.forEach", |
| &ArrayBuiltinsAssembler::ForEachResultGenerator, |
| &ArrayBuiltinsAssembler::ForEachProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction); |
| } |
| |
| TF_BUILTIN(ArraySomeLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* result = Parameter(Descriptor::kResult); |
| |
| // This custom lazy deopt point is right after the callback. every() needs |
| // to pick up at the next step, which is either continuing to the next |
| // array element or returning false if {result} is false. |
| Label true_continue(this), false_continue(this); |
| |
| // iii. If selected is true, then... |
| BranchIfToBooleanIsTrue(result, &true_continue, &false_continue); |
| BIND(&true_continue); |
| { Return(TrueConstant()); } |
| BIND(&false_continue); |
| { |
| // Increment k. |
| initial_k = NumberInc(initial_k); |
| |
| Return(CallBuiltin(Builtins::kArraySomeLoopContinuation, context, receiver, |
| callbackfn, this_arg, FalseConstant(), receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| } |
| |
| TF_BUILTIN(ArraySomeLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArraySomeLoopContinuation, context, receiver, |
| callbackfn, this_arg, FalseConstant(), receiver, initial_k, |
| len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArraySomeLoopContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject)); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn, |
| this_arg, array, object, initial_k, |
| len, to); |
| |
| GenerateIteratingArrayBuiltinLoopContinuation( |
| &ArrayBuiltinsAssembler::SomeProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(ArraySome, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingArrayBuiltinBody( |
| "Array.prototype.some", &ArrayBuiltinsAssembler::SomeResultGenerator, |
| &ArrayBuiltinsAssembler::SomeProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, |
| Builtins::CallableFor(isolate(), Builtins::kArraySomeLoopContinuation), |
| MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(TypedArrayPrototypeSome, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingTypedArrayBuiltinBody( |
| "%TypedArray%.prototype.some", |
| &ArrayBuiltinsAssembler::SomeResultGenerator, |
| &ArrayBuiltinsAssembler::SomeProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction); |
| } |
| |
| TF_BUILTIN(ArrayEveryLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* result = Parameter(Descriptor::kResult); |
| |
| // This custom lazy deopt point is right after the callback. every() needs |
| // to pick up at the next step, which is either continuing to the next |
| // array element or returning false if {result} is false. |
| Label true_continue(this), false_continue(this); |
| |
| // iii. If selected is true, then... |
| BranchIfToBooleanIsTrue(result, &true_continue, &false_continue); |
| BIND(&true_continue); |
| { |
| // Increment k. |
| initial_k = NumberInc(initial_k); |
| |
| Return(CallBuiltin(Builtins::kArrayEveryLoopContinuation, context, receiver, |
| callbackfn, this_arg, TrueConstant(), receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| BIND(&false_continue); |
| { Return(FalseConstant()); } |
| } |
| |
| TF_BUILTIN(ArrayEveryLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArrayEveryLoopContinuation, context, receiver, |
| callbackfn, this_arg, TrueConstant(), receiver, initial_k, |
| len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayEveryLoopContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject)); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn, |
| this_arg, array, object, initial_k, |
| len, to); |
| |
| GenerateIteratingArrayBuiltinLoopContinuation( |
| &ArrayBuiltinsAssembler::EveryProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(ArrayEvery, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingArrayBuiltinBody( |
| "Array.prototype.every", &ArrayBuiltinsAssembler::EveryResultGenerator, |
| &ArrayBuiltinsAssembler::EveryProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, |
| Builtins::CallableFor(isolate(), Builtins::kArrayEveryLoopContinuation), |
| MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(TypedArrayPrototypeEvery, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingTypedArrayBuiltinBody( |
| "%TypedArray%.prototype.every", |
| &ArrayBuiltinsAssembler::EveryResultGenerator, |
| &ArrayBuiltinsAssembler::EveryProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction); |
| } |
| |
| TF_BUILTIN(ArrayReduceLoopContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* accumulator = Parameter(Descriptor::kAccumulator); |
| TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject)); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn, |
| this_arg, accumulator, object, |
| initial_k, len, to); |
| |
| GenerateIteratingArrayBuiltinLoopContinuation( |
| &ArrayBuiltinsAssembler::ReduceProcessor, |
| &ArrayBuiltinsAssembler::ReducePostLoopAction, |
| MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(ArrayReducePreLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| // Simulate starting the loop at 0, but ensuring that the accumulator is |
| // the hole. The continuation stub will search for the initial non-hole |
| // element, rightly throwing an exception if not found. |
| Return(CallBuiltin(Builtins::kArrayReduceLoopContinuation, context, receiver, |
| callbackfn, UndefinedConstant(), TheHoleConstant(), |
| receiver, SmiConstant(0), len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayReduceLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* accumulator = Parameter(Descriptor::kAccumulator); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArrayReduceLoopContinuation, context, receiver, |
| callbackfn, UndefinedConstant(), accumulator, receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayReduceLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* result = Parameter(Descriptor::kResult); |
| |
| Return(CallBuiltin(Builtins::kArrayReduceLoopContinuation, context, receiver, |
| callbackfn, UndefinedConstant(), result, receiver, |
| initial_k, len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayReduce, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* initial_value = args.GetOptionalArgumentValue(1, TheHoleConstant()); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, initial_value, |
| argc); |
| |
| GenerateIteratingArrayBuiltinBody( |
| "Array.prototype.reduce", &ArrayBuiltinsAssembler::ReduceResultGenerator, |
| &ArrayBuiltinsAssembler::ReduceProcessor, |
| &ArrayBuiltinsAssembler::ReducePostLoopAction, |
| Builtins::CallableFor(isolate(), Builtins::kArrayReduceLoopContinuation), |
| MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(TypedArrayPrototypeReduce, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* initial_value = args.GetOptionalArgumentValue(1, TheHoleConstant()); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, initial_value, |
| argc); |
| |
| GenerateIteratingTypedArrayBuiltinBody( |
| "%TypedArray%.prototype.reduce", |
| &ArrayBuiltinsAssembler::ReduceResultGenerator, |
| &ArrayBuiltinsAssembler::ReduceProcessor, |
| &ArrayBuiltinsAssembler::ReducePostLoopAction); |
| } |
| |
| TF_BUILTIN(ArrayReduceRightLoopContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* accumulator = Parameter(Descriptor::kAccumulator); |
| TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject)); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn, |
| this_arg, accumulator, object, |
| initial_k, len, to); |
| |
| GenerateIteratingArrayBuiltinLoopContinuation( |
| &ArrayBuiltinsAssembler::ReduceProcessor, |
| &ArrayBuiltinsAssembler::ReducePostLoopAction, MissingPropertyMode::kSkip, |
| ForEachDirection::kReverse); |
| } |
| |
| TF_BUILTIN(ArrayReduceRightPreLoopEagerDeoptContinuation, |
| ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| TNode<Smi> len = CAST(Parameter(Descriptor::kLength)); |
| |
| // Simulate starting the loop at 0, but ensuring that the accumulator is |
| // the hole. The continuation stub will search for the initial non-hole |
| // element, rightly throwing an exception if not found. |
| Return(CallBuiltin(Builtins::kArrayReduceRightLoopContinuation, context, |
| receiver, callbackfn, UndefinedConstant(), |
| TheHoleConstant(), receiver, SmiSub(len, SmiConstant(1)), |
| len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayReduceRightLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* accumulator = Parameter(Descriptor::kAccumulator); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArrayReduceRightLoopContinuation, context, |
| receiver, callbackfn, UndefinedConstant(), accumulator, |
| receiver, initial_k, len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayReduceRightLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* result = Parameter(Descriptor::kResult); |
| |
| Return(CallBuiltin(Builtins::kArrayReduceRightLoopContinuation, context, |
| receiver, callbackfn, UndefinedConstant(), result, |
| receiver, initial_k, len, UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayReduceRight, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* initial_value = args.GetOptionalArgumentValue(1, TheHoleConstant()); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, initial_value, |
| argc); |
| |
| GenerateIteratingArrayBuiltinBody( |
| "Array.prototype.reduceRight", |
| &ArrayBuiltinsAssembler::ReduceResultGenerator, |
| &ArrayBuiltinsAssembler::ReduceProcessor, |
| &ArrayBuiltinsAssembler::ReducePostLoopAction, |
| Builtins::CallableFor(isolate(), |
| Builtins::kArrayReduceRightLoopContinuation), |
| MissingPropertyMode::kSkip, ForEachDirection::kReverse); |
| } |
| |
| TF_BUILTIN(TypedArrayPrototypeReduceRight, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* initial_value = args.GetOptionalArgumentValue(1, TheHoleConstant()); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, initial_value, |
| argc); |
| |
| GenerateIteratingTypedArrayBuiltinBody( |
| "%TypedArray%.prototype.reduceRight", |
| &ArrayBuiltinsAssembler::ReduceResultGenerator, |
| &ArrayBuiltinsAssembler::ReduceProcessor, |
| &ArrayBuiltinsAssembler::ReducePostLoopAction, |
| ForEachDirection::kReverse); |
| } |
| |
| TF_BUILTIN(ArrayFilterLoopContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject)); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn, |
| this_arg, array, object, initial_k, |
| len, to); |
| |
| GenerateIteratingArrayBuiltinLoopContinuation( |
| &ArrayBuiltinsAssembler::FilterProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(ArrayFilterLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| Return(CallBuiltin(Builtins::kArrayFilterLoopContinuation, context, receiver, |
| callbackfn, this_arg, array, receiver, initial_k, len, |
| to)); |
| } |
| |
| TF_BUILTIN(ArrayFilterLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* value_k = Parameter(Descriptor::kValueK); |
| Node* result = Parameter(Descriptor::kResult); |
| |
| VARIABLE(to, MachineRepresentation::kTagged, Parameter(Descriptor::kTo)); |
| |
| // This custom lazy deopt point is right after the callback. filter() needs |
| // to pick up at the next step, which is setting the callback result in |
| // the output array. After incrementing k and to, we can glide into the loop |
| // continuation builtin. |
| |
| Label true_continue(this, &to), false_continue(this); |
| |
| // iii. If selected is true, then... |
| BranchIfToBooleanIsTrue(result, &true_continue, &false_continue); |
| BIND(&true_continue); |
| { |
| // 1. Perform ? CreateDataPropertyOrThrow(A, ToString(to), kValue). |
| CallRuntime(Runtime::kCreateDataProperty, context, array, to.value(), |
| value_k); |
| // 2. Increase to by 1. |
| to.Bind(NumberInc(to.value())); |
| Goto(&false_continue); |
| } |
| BIND(&false_continue); |
| |
| // Increment k. |
| initial_k = NumberInc(initial_k); |
| |
| Return(CallBuiltin(Builtins::kArrayFilterLoopContinuation, context, receiver, |
| callbackfn, this_arg, array, receiver, initial_k, len, |
| to.value())); |
| } |
| |
| TF_BUILTIN(ArrayFilter, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingArrayBuiltinBody( |
| "Array.prototype.filter", &ArrayBuiltinsAssembler::FilterResultGenerator, |
| &ArrayBuiltinsAssembler::FilterProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, |
| Builtins::CallableFor(isolate(), Builtins::kArrayFilterLoopContinuation), |
| MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(ArrayMapLoopContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| TNode<JSReceiver> object = CAST(Parameter(Descriptor::kObject)); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* to = Parameter(Descriptor::kTo); |
| |
| InitIteratingArrayBuiltinLoopContinuation(context, receiver, callbackfn, |
| this_arg, array, object, initial_k, |
| len, to); |
| |
| GenerateIteratingArrayBuiltinLoopContinuation( |
| &ArrayBuiltinsAssembler::SpecCompliantMapProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(ArrayMapLoopEagerDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| |
| Return(CallBuiltin(Builtins::kArrayMapLoopContinuation, context, receiver, |
| callbackfn, this_arg, array, receiver, initial_k, len, |
| UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayMapLoopLazyDeoptContinuation, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Node* callbackfn = Parameter(Descriptor::kCallbackFn); |
| Node* this_arg = Parameter(Descriptor::kThisArg); |
| Node* array = Parameter(Descriptor::kArray); |
| Node* initial_k = Parameter(Descriptor::kInitialK); |
| TNode<Number> len = CAST(Parameter(Descriptor::kLength)); |
| Node* result = Parameter(Descriptor::kResult); |
| |
| // This custom lazy deopt point is right after the callback. map() needs |
| // to pick up at the next step, which is setting the callback result in |
| // the output array. After incrementing k, we can glide into the loop |
| // continuation builtin. |
| |
| // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). |
| CallRuntime(Runtime::kCreateDataProperty, context, array, initial_k, result); |
| // Then we have to increment k before going on. |
| initial_k = NumberInc(initial_k); |
| |
| Return(CallBuiltin(Builtins::kArrayMapLoopContinuation, context, receiver, |
| callbackfn, this_arg, array, receiver, initial_k, len, |
| UndefinedConstant())); |
| } |
| |
| TF_BUILTIN(ArrayMap, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingArrayBuiltinBody( |
| "Array.prototype.map", &ArrayBuiltinsAssembler::MapResultGenerator, |
| &ArrayBuiltinsAssembler::FastMapProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction, |
| Builtins::CallableFor(isolate(), Builtins::kArrayMapLoopContinuation), |
| MissingPropertyMode::kSkip); |
| } |
| |
| TF_BUILTIN(TypedArrayPrototypeMap, ArrayBuiltinsAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = args.GetReceiver(); |
| Node* callbackfn = args.GetOptionalArgumentValue(0); |
| Node* this_arg = args.GetOptionalArgumentValue(1); |
| |
| InitIteratingArrayBuiltinBody(context, receiver, callbackfn, this_arg, argc); |
| |
| GenerateIteratingTypedArrayBuiltinBody( |
| "%TypedArray%.prototype.map", |
| &ArrayBuiltinsAssembler::TypedArrayMapResultGenerator, |
| &ArrayBuiltinsAssembler::TypedArrayMapProcessor, |
| &ArrayBuiltinsAssembler::NullPostLoopAction); |
| } |
| |
| TF_BUILTIN(ArrayIsArray, CodeStubAssembler) { |
| TNode<Object> object = CAST(Parameter(Descriptor::kArg)); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| |
| Label call_runtime(this), return_true(this), return_false(this); |
| |
| GotoIf(TaggedIsSmi(object), &return_false); |
| TNode<Int32T> instance_type = LoadInstanceType(CAST(object)); |
| |
| GotoIf(InstanceTypeEqual(instance_type, JS_ARRAY_TYPE), &return_true); |
| |
| // TODO(verwaest): Handle proxies in-place. |
| Branch(InstanceTypeEqual(instance_type, JS_PROXY_TYPE), &call_runtime, |
| &return_false); |
| |
| BIND(&return_true); |
| Return(TrueConstant()); |
| |
| BIND(&return_false); |
| Return(FalseConstant()); |
| |
| BIND(&call_runtime); |
| Return(CallRuntime(Runtime::kArrayIsArray, context, object)); |
| } |
| |
| class ArrayIncludesIndexofAssembler : public CodeStubAssembler { |
| public: |
| explicit ArrayIncludesIndexofAssembler(compiler::CodeAssemblerState* state) |
| : CodeStubAssembler(state) {} |
| |
| enum SearchVariant { kIncludes, kIndexOf }; |
| |
| void Generate(SearchVariant variant, TNode<IntPtrT> argc, |
| TNode<Context> context); |
| void GenerateSmiOrObject(SearchVariant variant, Node* context, Node* elements, |
| Node* search_element, Node* array_length, |
| Node* from_index); |
| void GeneratePackedDoubles(SearchVariant variant, Node* elements, |
| Node* search_element, Node* array_length, |
| Node* from_index); |
| void GenerateHoleyDoubles(SearchVariant variant, Node* elements, |
| Node* search_element, Node* array_length, |
| Node* from_index); |
| }; |
| |
| void ArrayIncludesIndexofAssembler::Generate(SearchVariant variant, |
| TNode<IntPtrT> argc, |
| TNode<Context> context) { |
| const int kSearchElementArg = 0; |
| const int kFromIndexArg = 1; |
| |
| CodeStubArguments args(this, argc); |
| |
| TNode<Object> receiver = args.GetReceiver(); |
| TNode<Object> search_element = |
| args.GetOptionalArgumentValue(kSearchElementArg); |
| |
| Node* intptr_zero = IntPtrConstant(0); |
| |
| Label init_index(this), return_not_found(this), call_runtime(this); |
| |
| // Take slow path if not a JSArray, if retrieving elements requires |
| // traversing prototype, or if access checks are required. |
| BranchIfFastJSArray(receiver, context, &init_index, &call_runtime); |
| |
| BIND(&init_index); |
| VARIABLE(index_var, MachineType::PointerRepresentation(), intptr_zero); |
| TNode<JSArray> array = CAST(receiver); |
| |
| // JSArray length is always a positive Smi for fast arrays. |
| CSA_ASSERT(this, TaggedIsPositiveSmi(LoadJSArrayLength(array))); |
| Node* array_length = LoadFastJSArrayLength(array); |
| Node* array_length_untagged = SmiUntag(array_length); |
| |
| { |
| // Initialize fromIndex. |
| Label is_smi(this), is_nonsmi(this), done(this); |
| |
| // If no fromIndex was passed, default to 0. |
| GotoIf(IntPtrLessThanOrEqual(argc, IntPtrConstant(kFromIndexArg)), &done); |
| |
| Node* start_from = args.AtIndex(kFromIndexArg); |
| // Handle Smis and undefined here and everything else in runtime. |
| // We must be very careful with side effects from the ToInteger conversion, |
| // as the side effects might render previously checked assumptions about |
| // the receiver being a fast JSArray and its length invalid. |
| Branch(TaggedIsSmi(start_from), &is_smi, &is_nonsmi); |
| |
| BIND(&is_nonsmi); |
| { |
| GotoIfNot(IsUndefined(start_from), &call_runtime); |
| Goto(&done); |
| } |
| BIND(&is_smi); |
| { |
| Node* intptr_start_from = SmiUntag(start_from); |
| index_var.Bind(intptr_start_from); |
| |
| GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), intptr_zero), &done); |
| // The fromIndex is negative: add it to the array's length. |
| index_var.Bind(IntPtrAdd(array_length_untagged, index_var.value())); |
| // Clamp negative results at zero. |
| GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), intptr_zero), &done); |
| index_var.Bind(intptr_zero); |
| Goto(&done); |
| } |
| BIND(&done); |
| } |
| |
| // Fail early if startIndex >= array.length. |
| GotoIf(IntPtrGreaterThanOrEqual(index_var.value(), array_length_untagged), |
| &return_not_found); |
| |
| Label if_smiorobjects(this), if_packed_doubles(this), if_holey_doubles(this); |
| |
| TNode<Int32T> elements_kind = LoadElementsKind(array); |
| Node* elements = LoadElements(array); |
| STATIC_ASSERT(PACKED_SMI_ELEMENTS == 0); |
| STATIC_ASSERT(HOLEY_SMI_ELEMENTS == 1); |
| STATIC_ASSERT(PACKED_ELEMENTS == 2); |
| STATIC_ASSERT(HOLEY_ELEMENTS == 3); |
| GotoIf(Uint32LessThanOrEqual(elements_kind, Int32Constant(HOLEY_ELEMENTS)), |
| &if_smiorobjects); |
| GotoIf(Word32Equal(elements_kind, Int32Constant(PACKED_DOUBLE_ELEMENTS)), |
| &if_packed_doubles); |
| GotoIf(Word32Equal(elements_kind, Int32Constant(HOLEY_DOUBLE_ELEMENTS)), |
| &if_holey_doubles); |
| Goto(&return_not_found); |
| |
| BIND(&if_smiorobjects); |
| { |
| Callable callable = |
| (variant == kIncludes) |
| ? Builtins::CallableFor(isolate(), |
| Builtins::kArrayIncludesSmiOrObject) |
| : Builtins::CallableFor(isolate(), |
| Builtins::kArrayIndexOfSmiOrObject); |
| Node* result = CallStub(callable, context, elements, search_element, |
| array_length, SmiTag(index_var.value())); |
| args.PopAndReturn(result); |
| } |
| |
| BIND(&if_packed_doubles); |
| { |
| Callable callable = |
| (variant == kIncludes) |
| ? Builtins::CallableFor(isolate(), |
| Builtins::kArrayIncludesPackedDoubles) |
| : Builtins::CallableFor(isolate(), |
| Builtins::kArrayIndexOfPackedDoubles); |
| Node* result = CallStub(callable, context, elements, search_element, |
| array_length, SmiTag(index_var.value())); |
| args.PopAndReturn(result); |
| } |
| |
| BIND(&if_holey_doubles); |
| { |
| Callable callable = |
| (variant == kIncludes) |
| ? Builtins::CallableFor(isolate(), |
| Builtins::kArrayIncludesHoleyDoubles) |
| : Builtins::CallableFor(isolate(), |
| Builtins::kArrayIndexOfHoleyDoubles); |
| Node* result = CallStub(callable, context, elements, search_element, |
| array_length, SmiTag(index_var.value())); |
| args.PopAndReturn(result); |
| } |
| |
| BIND(&return_not_found); |
| if (variant == kIncludes) { |
| args.PopAndReturn(FalseConstant()); |
| } else { |
| args.PopAndReturn(NumberConstant(-1)); |
| } |
| |
| BIND(&call_runtime); |
| { |
| Node* start_from = |
| args.GetOptionalArgumentValue(kFromIndexArg, UndefinedConstant()); |
| Runtime::FunctionId function = variant == kIncludes |
| ? Runtime::kArrayIncludes_Slow |
| : Runtime::kArrayIndexOf; |
| args.PopAndReturn( |
| CallRuntime(function, context, array, search_element, start_from)); |
| } |
| } |
| |
| void ArrayIncludesIndexofAssembler::GenerateSmiOrObject( |
| SearchVariant variant, Node* context, Node* elements, Node* search_element, |
| Node* array_length, Node* from_index) { |
| VARIABLE(index_var, MachineType::PointerRepresentation(), |
| SmiUntag(from_index)); |
| VARIABLE(search_num, MachineRepresentation::kFloat64); |
| Node* array_length_untagged = SmiUntag(array_length); |
| |
| Label ident_loop(this, &index_var), heap_num_loop(this, &search_num), |
| string_loop(this), bigint_loop(this, &index_var), |
| undef_loop(this, &index_var), not_smi(this), not_heap_num(this), |
| return_found(this), return_not_found(this); |
| |
| GotoIfNot(TaggedIsSmi(search_element), ¬_smi); |
| search_num.Bind(SmiToFloat64(search_element)); |
| Goto(&heap_num_loop); |
| |
| BIND(¬_smi); |
| if (variant == kIncludes) { |
| GotoIf(IsUndefined(search_element), &undef_loop); |
| } |
| Node* map = LoadMap(search_element); |
| GotoIfNot(IsHeapNumberMap(map), ¬_heap_num); |
| search_num.Bind(LoadHeapNumberValue(search_element)); |
| Goto(&heap_num_loop); |
| |
| BIND(¬_heap_num); |
| Node* search_type = LoadMapInstanceType(map); |
| GotoIf(IsStringInstanceType(search_type), &string_loop); |
| GotoIf(IsBigIntInstanceType(search_type), &bigint_loop); |
| Goto(&ident_loop); |
| |
| BIND(&ident_loop); |
| { |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| Node* element_k = LoadFixedArrayElement(CAST(elements), index_var.value()); |
| GotoIf(WordEqual(element_k, search_element), &return_found); |
| |
| Increment(&index_var); |
| Goto(&ident_loop); |
| } |
| |
| if (variant == kIncludes) { |
| BIND(&undef_loop); |
| |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| Node* element_k = LoadFixedArrayElement(CAST(elements), index_var.value()); |
| GotoIf(IsUndefined(element_k), &return_found); |
| GotoIf(IsTheHole(element_k), &return_found); |
| |
| Increment(&index_var); |
| Goto(&undef_loop); |
| } |
| |
| BIND(&heap_num_loop); |
| { |
| Label nan_loop(this, &index_var), not_nan_loop(this, &index_var); |
| Label* nan_handling = variant == kIncludes ? &nan_loop : &return_not_found; |
| BranchIfFloat64IsNaN(search_num.value(), nan_handling, ¬_nan_loop); |
| |
| BIND(¬_nan_loop); |
| { |
| Label continue_loop(this), not_smi(this); |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| Node* element_k = |
| LoadFixedArrayElement(CAST(elements), index_var.value()); |
| GotoIfNot(TaggedIsSmi(element_k), ¬_smi); |
| Branch(Float64Equal(search_num.value(), SmiToFloat64(element_k)), |
| &return_found, &continue_loop); |
| |
| BIND(¬_smi); |
| GotoIfNot(IsHeapNumber(element_k), &continue_loop); |
| Branch(Float64Equal(search_num.value(), LoadHeapNumberValue(element_k)), |
| &return_found, &continue_loop); |
| |
| BIND(&continue_loop); |
| Increment(&index_var); |
| Goto(¬_nan_loop); |
| } |
| |
| // Array.p.includes uses SameValueZero comparisons, where NaN == NaN. |
| if (variant == kIncludes) { |
| BIND(&nan_loop); |
| Label continue_loop(this); |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| Node* element_k = |
| LoadFixedArrayElement(CAST(elements), index_var.value()); |
| GotoIf(TaggedIsSmi(element_k), &continue_loop); |
| GotoIfNot(IsHeapNumber(CAST(element_k)), &continue_loop); |
| BranchIfFloat64IsNaN(LoadHeapNumberValue(element_k), &return_found, |
| &continue_loop); |
| |
| BIND(&continue_loop); |
| Increment(&index_var); |
| Goto(&nan_loop); |
| } |
| } |
| |
| BIND(&string_loop); |
| { |
| TNode<String> search_element_string = CAST(search_element); |
| Label continue_loop(this), next_iteration(this, &index_var), |
| slow_compare(this), runtime(this, Label::kDeferred); |
| TNode<IntPtrT> search_length = |
| LoadStringLengthAsWord(search_element_string); |
| Goto(&next_iteration); |
| BIND(&next_iteration); |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| Node* element_k = LoadFixedArrayElement(CAST(elements), index_var.value()); |
| GotoIf(TaggedIsSmi(element_k), &continue_loop); |
| GotoIf(WordEqual(search_element_string, element_k), &return_found); |
| Node* element_k_type = LoadInstanceType(element_k); |
| GotoIfNot(IsStringInstanceType(element_k_type), &continue_loop); |
| Branch(WordEqual(search_length, LoadStringLengthAsWord(element_k)), |
| &slow_compare, &continue_loop); |
| |
| BIND(&slow_compare); |
| StringBuiltinsAssembler string_asm(state()); |
| string_asm.StringEqual_Core(context, search_element_string, search_type, |
| element_k, element_k_type, search_length, |
| &return_found, &continue_loop, &runtime); |
| BIND(&runtime); |
| TNode<Object> result = CallRuntime(Runtime::kStringEqual, context, |
| search_element_string, element_k); |
| Branch(WordEqual(result, TrueConstant()), &return_found, &continue_loop); |
| |
| BIND(&continue_loop); |
| Increment(&index_var); |
| Goto(&next_iteration); |
| } |
| |
| BIND(&bigint_loop); |
| { |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| |
| Node* element_k = LoadFixedArrayElement(CAST(elements), index_var.value()); |
| Label continue_loop(this); |
| GotoIf(TaggedIsSmi(element_k), &continue_loop); |
| GotoIfNot(IsBigInt(CAST(element_k)), &continue_loop); |
| TNode<Object> result = CallRuntime(Runtime::kBigIntEqualToBigInt, context, |
| search_element, element_k); |
| Branch(WordEqual(result, TrueConstant()), &return_found, &continue_loop); |
| |
| BIND(&continue_loop); |
| Increment(&index_var); |
| Goto(&bigint_loop); |
| } |
| BIND(&return_found); |
| if (variant == kIncludes) { |
| Return(TrueConstant()); |
| } else { |
| Return(SmiTag(index_var.value())); |
| } |
| |
| BIND(&return_not_found); |
| if (variant == kIncludes) { |
| Return(FalseConstant()); |
| } else { |
| Return(NumberConstant(-1)); |
| } |
| } |
| |
| void ArrayIncludesIndexofAssembler::GeneratePackedDoubles(SearchVariant variant, |
| Node* elements, |
| Node* search_element, |
| Node* array_length, |
| Node* from_index) { |
| VARIABLE(index_var, MachineType::PointerRepresentation(), |
| SmiUntag(from_index)); |
| Node* array_length_untagged = SmiUntag(array_length); |
| |
| Label nan_loop(this, &index_var), not_nan_loop(this, &index_var), |
| hole_loop(this, &index_var), search_notnan(this), return_found(this), |
| return_not_found(this); |
| VARIABLE(search_num, MachineRepresentation::kFloat64); |
| search_num.Bind(Float64Constant(0)); |
| |
| GotoIfNot(TaggedIsSmi(search_element), &search_notnan); |
| search_num.Bind(SmiToFloat64(search_element)); |
| Goto(¬_nan_loop); |
| |
| BIND(&search_notnan); |
| GotoIfNot(IsHeapNumber(search_element), &return_not_found); |
| |
| search_num.Bind(LoadHeapNumberValue(search_element)); |
| |
| Label* nan_handling = variant == kIncludes ? &nan_loop : &return_not_found; |
| BranchIfFloat64IsNaN(search_num.value(), nan_handling, ¬_nan_loop); |
| |
| BIND(¬_nan_loop); |
| { |
| Label continue_loop(this); |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| Node* element_k = LoadFixedDoubleArrayElement(elements, index_var.value(), |
| MachineType::Float64()); |
| Branch(Float64Equal(element_k, search_num.value()), &return_found, |
| &continue_loop); |
| BIND(&continue_loop); |
| Increment(&index_var); |
| Goto(¬_nan_loop); |
| } |
| |
| // Array.p.includes uses SameValueZero comparisons, where NaN == NaN. |
| if (variant == kIncludes) { |
| BIND(&nan_loop); |
| Label continue_loop(this); |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| Node* element_k = LoadFixedDoubleArrayElement(elements, index_var.value(), |
| MachineType::Float64()); |
| BranchIfFloat64IsNaN(element_k, &return_found, &continue_loop); |
| BIND(&continue_loop); |
| Increment(&index_var); |
| Goto(&nan_loop); |
| } |
| |
| BIND(&return_found); |
| if (variant == kIncludes) { |
| Return(TrueConstant()); |
| } else { |
| Return(SmiTag(index_var.value())); |
| } |
| |
| BIND(&return_not_found); |
| if (variant == kIncludes) { |
| Return(FalseConstant()); |
| } else { |
| Return(NumberConstant(-1)); |
| } |
| } |
| |
| void ArrayIncludesIndexofAssembler::GenerateHoleyDoubles(SearchVariant variant, |
| Node* elements, |
| Node* search_element, |
| Node* array_length, |
| Node* from_index) { |
| VARIABLE(index_var, MachineType::PointerRepresentation(), |
| SmiUntag(from_index)); |
| Node* array_length_untagged = SmiUntag(array_length); |
| |
| Label nan_loop(this, &index_var), not_nan_loop(this, &index_var), |
| hole_loop(this, &index_var), search_notnan(this), return_found(this), |
| return_not_found(this); |
| VARIABLE(search_num, MachineRepresentation::kFloat64); |
| search_num.Bind(Float64Constant(0)); |
| |
| GotoIfNot(TaggedIsSmi(search_element), &search_notnan); |
| search_num.Bind(SmiToFloat64(search_element)); |
| Goto(¬_nan_loop); |
| |
| BIND(&search_notnan); |
| if (variant == kIncludes) { |
| GotoIf(IsUndefined(search_element), &hole_loop); |
| } |
| GotoIfNot(IsHeapNumber(search_element), &return_not_found); |
| |
| search_num.Bind(LoadHeapNumberValue(search_element)); |
| |
| Label* nan_handling = variant == kIncludes ? &nan_loop : &return_not_found; |
| BranchIfFloat64IsNaN(search_num.value(), nan_handling, ¬_nan_loop); |
| |
| BIND(¬_nan_loop); |
| { |
| Label continue_loop(this); |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| |
| // No need for hole checking here; the following Float64Equal will |
| // return 'not equal' for holes anyway. |
| Node* element_k = LoadFixedDoubleArrayElement(elements, index_var.value(), |
| MachineType::Float64()); |
| |
| Branch(Float64Equal(element_k, search_num.value()), &return_found, |
| &continue_loop); |
| BIND(&continue_loop); |
| Increment(&index_var); |
| Goto(¬_nan_loop); |
| } |
| |
| // Array.p.includes uses SameValueZero comparisons, where NaN == NaN. |
| if (variant == kIncludes) { |
| BIND(&nan_loop); |
| Label continue_loop(this); |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| |
| // Load double value or continue if it's the hole NaN. |
| Node* element_k = LoadFixedDoubleArrayElement( |
| elements, index_var.value(), MachineType::Float64(), 0, |
| INTPTR_PARAMETERS, &continue_loop); |
| |
| BranchIfFloat64IsNaN(element_k, &return_found, &continue_loop); |
| BIND(&continue_loop); |
| Increment(&index_var); |
| Goto(&nan_loop); |
| } |
| |
| // Array.p.includes treats the hole as undefined. |
| if (variant == kIncludes) { |
| BIND(&hole_loop); |
| GotoIfNot(UintPtrLessThan(index_var.value(), array_length_untagged), |
| &return_not_found); |
| |
| // Check if the element is a double hole, but don't load it. |
| LoadFixedDoubleArrayElement(elements, index_var.value(), |
| MachineType::None(), 0, INTPTR_PARAMETERS, |
| &return_found); |
| |
| Increment(&index_var); |
| Goto(&hole_loop); |
| } |
| |
| BIND(&return_found); |
| if (variant == kIncludes) { |
| Return(TrueConstant()); |
| } else { |
| Return(SmiTag(index_var.value())); |
| } |
| |
| BIND(&return_not_found); |
| if (variant == kIncludes) { |
| Return(FalseConstant()); |
| } else { |
| Return(NumberConstant(-1)); |
| } |
| } |
| |
| TF_BUILTIN(ArrayIncludes, ArrayIncludesIndexofAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| |
| Generate(kIncludes, argc, context); |
| } |
| |
| TF_BUILTIN(ArrayIncludesSmiOrObject, ArrayIncludesIndexofAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* elements = Parameter(Descriptor::kElements); |
| Node* search_element = Parameter(Descriptor::kSearchElement); |
| Node* array_length = Parameter(Descriptor::kLength); |
| Node* from_index = Parameter(Descriptor::kFromIndex); |
| |
| GenerateSmiOrObject(kIncludes, context, elements, search_element, |
| array_length, from_index); |
| } |
| |
| TF_BUILTIN(ArrayIncludesPackedDoubles, ArrayIncludesIndexofAssembler) { |
| Node* elements = Parameter(Descriptor::kElements); |
| Node* search_element = Parameter(Descriptor::kSearchElement); |
| Node* array_length = Parameter(Descriptor::kLength); |
| Node* from_index = Parameter(Descriptor::kFromIndex); |
| |
| GeneratePackedDoubles(kIncludes, elements, search_element, array_length, |
| from_index); |
| } |
| |
| TF_BUILTIN(ArrayIncludesHoleyDoubles, ArrayIncludesIndexofAssembler) { |
| Node* elements = Parameter(Descriptor::kElements); |
| Node* search_element = Parameter(Descriptor::kSearchElement); |
| Node* array_length = Parameter(Descriptor::kLength); |
| Node* from_index = Parameter(Descriptor::kFromIndex); |
| |
| GenerateHoleyDoubles(kIncludes, elements, search_element, array_length, |
| from_index); |
| } |
| |
| TF_BUILTIN(ArrayIndexOf, ArrayIncludesIndexofAssembler) { |
| TNode<IntPtrT> argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| |
| Generate(kIndexOf, argc, context); |
| } |
| |
| TF_BUILTIN(ArrayIndexOfSmiOrObject, ArrayIncludesIndexofAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* elements = Parameter(Descriptor::kElements); |
| Node* search_element = Parameter(Descriptor::kSearchElement); |
| Node* array_length = Parameter(Descriptor::kLength); |
| Node* from_index = Parameter(Descriptor::kFromIndex); |
| |
| GenerateSmiOrObject(kIndexOf, context, elements, search_element, array_length, |
| from_index); |
| } |
| |
| TF_BUILTIN(ArrayIndexOfPackedDoubles, ArrayIncludesIndexofAssembler) { |
| Node* elements = Parameter(Descriptor::kElements); |
| Node* search_element = Parameter(Descriptor::kSearchElement); |
| Node* array_length = Parameter(Descriptor::kLength); |
| Node* from_index = Parameter(Descriptor::kFromIndex); |
| |
| GeneratePackedDoubles(kIndexOf, elements, search_element, array_length, |
| from_index); |
| } |
| |
| TF_BUILTIN(ArrayIndexOfHoleyDoubles, ArrayIncludesIndexofAssembler) { |
| Node* elements = Parameter(Descriptor::kElements); |
| Node* search_element = Parameter(Descriptor::kSearchElement); |
| Node* array_length = Parameter(Descriptor::kLength); |
| Node* from_index = Parameter(Descriptor::kFromIndex); |
| |
| GenerateHoleyDoubles(kIndexOf, elements, search_element, array_length, |
| from_index); |
| } |
| |
| // ES #sec-array.prototype.values |
| TF_BUILTIN(ArrayPrototypeValues, CodeStubAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Return(CreateArrayIterator(context, ToObject_Inline(context, receiver), |
| IterationKind::kValues)); |
| } |
| |
| // ES #sec-array.prototype.entries |
| TF_BUILTIN(ArrayPrototypeEntries, CodeStubAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Return(CreateArrayIterator(context, ToObject_Inline(context, receiver), |
| IterationKind::kEntries)); |
| } |
| |
| // ES #sec-array.prototype.keys |
| TF_BUILTIN(ArrayPrototypeKeys, CodeStubAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<Object> receiver = CAST(Parameter(Descriptor::kReceiver)); |
| Return(CreateArrayIterator(context, ToObject_Inline(context, receiver), |
| IterationKind::kKeys)); |
| } |
| |
| // ES #sec-%arrayiteratorprototype%.next |
| TF_BUILTIN(ArrayIteratorPrototypeNext, CodeStubAssembler) { |
| const char* method_name = "Array Iterator.prototype.next"; |
| |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| Node* iterator = Parameter(Descriptor::kReceiver); |
| |
| VARIABLE(var_done, MachineRepresentation::kTagged, TrueConstant()); |
| VARIABLE(var_value, MachineRepresentation::kTagged, UndefinedConstant()); |
| |
| Label allocate_entry_if_needed(this); |
| Label allocate_iterator_result(this); |
| Label if_typedarray(this), if_other(this, Label::kDeferred), if_array(this), |
| if_generic(this, Label::kDeferred); |
| Label set_done(this, Label::kDeferred); |
| |
| // If O does not have all of the internal slots of an Array Iterator Instance |
| // (22.1.5.3), throw a TypeError exception |
| ThrowIfNotInstanceType(context, iterator, JS_ARRAY_ITERATOR_TYPE, |
| method_name); |
| |
| // Let a be O.[[IteratedObject]]. |
| TNode<JSReceiver> array = |
| CAST(LoadObjectField(iterator, JSArrayIterator::kIteratedObjectOffset)); |
| |
| // Let index be O.[[ArrayIteratorNextIndex]]. |
| TNode<Number> index = |
| CAST(LoadObjectField(iterator, JSArrayIterator::kNextIndexOffset)); |
| CSA_ASSERT(this, IsNumberNonNegativeSafeInteger(index)); |
| GotoIfNot(TaggedIsSmi(index), &if_other); |
| |
| // Dispatch based on the type of the {array}. |
| TNode<Map> array_map = LoadMap(array); |
| TNode<Int32T> array_type = LoadMapInstanceType(array_map); |
| GotoIf(InstanceTypeEqual(array_type, JS_ARRAY_TYPE), &if_array); |
| Branch(InstanceTypeEqual(array_type, JS_TYPED_ARRAY_TYPE), &if_typedarray, |
| &if_other); |
| |
| BIND(&if_array); |
| { |
| // Check that the {index} is within range for the {array}. |
| TNode<Number> length = LoadJSArrayLength(CAST(array)); |
| GotoIfNumberGreaterThanOrEqual(index, length, &set_done); |
| StoreObjectFieldNoWriteBarrier(iterator, JSArrayIterator::kNextIndexOffset, |
| SmiInc(CAST(index))); |
| |
| var_done.Bind(FalseConstant()); |
| var_value.Bind(index); |
| |
| GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField( |
| iterator, JSArrayIterator::kKindOffset), |
| Int32Constant(static_cast<int>(IterationKind::kKeys))), |
| &allocate_iterator_result); |
| |
| Label if_hole(this, Label::kDeferred); |
| TNode<Int32T> elements_kind = LoadMapElementsKind(array_map); |
| TNode<FixedArrayBase> elements = LoadElements(CAST(array)); |
| var_value.Bind(LoadFixedArrayBaseElementAsTagged( |
| elements, CAST(index), elements_kind, &if_generic, &if_hole)); |
| Goto(&allocate_entry_if_needed); |
| |
| BIND(&if_hole); |
| { |
| GotoIf(IsNoElementsProtectorCellInvalid(), &if_generic); |
| var_value.Bind(UndefinedConstant()); |
| Goto(&allocate_entry_if_needed); |
| } |
| } |
| |
| BIND(&if_other); |
| { |
| // If the {array} is actually a JSArray the {index} must be a valid |
| // array index, as the TurboFan fast-path inlining relies on the fact |
| // that the [[ArrayIteratorNextIndex]] field always contains a valid |
| // Unsigned32 value as long as the [[ArrayIteratorIteratedObject]] |
| // field contains a JSArray instance. Also rule out JSTypedArray's |
| // here as they should never reach here (both because the {index} |
| // in that case must always be a Smi, and second because loading |
| // the "length" property would be wrong for JSTypedArray's). |
| CSA_ASSERT(this, Word32BinaryNot(IsJSTypedArray(array))); |
| CSA_ASSERT(this, Word32Or(Word32BinaryNot(IsJSArray(array)), |
| IsNumberArrayIndex(index))); |
| |
| // Check that the {index} is within the bounds of the {array}s "length". |
| TNode<Number> length = CAST( |
| CallBuiltin(Builtins::kToLength, context, |
| GetProperty(context, array, factory()->length_string()))); |
| GotoIfNumberGreaterThanOrEqual(index, length, &set_done); |
| StoreObjectField(iterator, JSArrayIterator::kNextIndexOffset, |
| NumberInc(index)); |
| |
| var_done.Bind(FalseConstant()); |
| var_value.Bind(index); |
| |
| Branch(Word32Equal(LoadAndUntagToWord32ObjectField( |
| iterator, JSArrayIterator::kKindOffset), |
| Int32Constant(static_cast<int>(IterationKind::kKeys))), |
| &allocate_iterator_result, &if_generic); |
| } |
| |
| BIND(&set_done); |
| { |
| // Change the [[ArrayIteratorNextIndex]] such that the {iterator} will |
| // never produce values anymore, because it will always fail the bounds |
| // check. Note that this is different from what the specification does, |
| // which is changing the [[IteratedObject]] to undefined, because leaving |
| // [[IteratedObject]] alone helps TurboFan to generate better code with |
| // the inlining in JSCallReducer::ReduceArrayIteratorPrototypeNext(). |
| // |
| // The terminal value we chose here depends on the type of the {array}, |
| // for JSArray's we use kMaxUInt32 so that TurboFan can always use |
| // Word32 representation for fast-path indices (and this is safe since |
| // the "length" of JSArray's is limited to Unsigned32 range). For other |
| // JSReceiver's we have to use kMaxSafeInteger, since the "length" can |
| // be any arbitrary value in the safe integer range. |
| // |
| // Note specifically that JSTypedArray's will never take this path, so |
| // we don't need to worry about their maximum value. |
| CSA_ASSERT(this, Word32BinaryNot(IsJSTypedArray(array))); |
| TNode<Number> max_length = |
| SelectConstant(IsJSArray(array), NumberConstant(kMaxUInt32), |
| NumberConstant(kMaxSafeInteger)); |
| StoreObjectField(iterator, JSArrayIterator::kNextIndexOffset, max_length); |
| Goto(&allocate_iterator_result); |
| } |
| |
| BIND(&if_generic); |
| { |
| var_value.Bind(GetProperty(context, array, index)); |
| Goto(&allocate_entry_if_needed); |
| } |
| |
| BIND(&if_typedarray); |
| { |
| // Check that the {array}s buffer wasn't neutered. |
| ThrowIfArrayBufferViewBufferIsDetached(context, CAST(array), method_name); |
| |
| // If we go outside of the {length}, we don't need to update the |
| // [[ArrayIteratorNextIndex]] anymore, since a JSTypedArray's |
| // length cannot change anymore, so this {iterator} will never |
| // produce values again anyways. |
| TNode<Smi> length = LoadTypedArrayLength(CAST(array)); |
| GotoIfNot(SmiBelow(CAST(index), length), &allocate_iterator_result); |
| StoreObjectFieldNoWriteBarrier(iterator, JSArrayIterator::kNextIndexOffset, |
| SmiInc(CAST(index))); |
| |
| var_done.Bind(FalseConstant()); |
| var_value.Bind(index); |
| |
| GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField( |
| iterator, JSArrayIterator::kKindOffset), |
| Int32Constant(static_cast<int>(IterationKind::kKeys))), |
| &allocate_iterator_result); |
| |
| TNode<Int32T> elements_kind = LoadMapElementsKind(array_map); |
| Node* elements = LoadElements(CAST(array)); |
| Node* base_ptr = |
| LoadObjectField(elements, FixedTypedArrayBase::kBasePointerOffset); |
| Node* external_ptr = |
| LoadObjectField(elements, FixedTypedArrayBase::kExternalPointerOffset, |
| MachineType::Pointer()); |
| TNode<WordT> data_ptr = |
| IntPtrAdd(BitcastTaggedToWord(base_ptr), external_ptr); |
| var_value.Bind(LoadFixedTypedArrayElementAsTagged(data_ptr, CAST(index), |
| elements_kind)); |
| Goto(&allocate_entry_if_needed); |
| } |
| |
| BIND(&allocate_entry_if_needed); |
| { |
| GotoIf(Word32Equal(LoadAndUntagToWord32ObjectField( |
| iterator, JSArrayIterator::kKindOffset), |
| Int32Constant(static_cast<int>(IterationKind::kValues))), |
| &allocate_iterator_result); |
| |
| Node* result = |
| AllocateJSIteratorResultForEntry(context, index, var_value.value()); |
| Return(result); |
| } |
| |
| BIND(&allocate_iterator_result); |
| { |
| Node* result = |
| AllocateJSIteratorResult(context, var_value.value(), var_done.value()); |
| Return(result); |
| } |
| } |
| |
| namespace { |
| |
| class ArrayFlattenAssembler : public CodeStubAssembler { |
| public: |
| explicit ArrayFlattenAssembler(compiler::CodeAssemblerState* state) |
| : CodeStubAssembler(state) {} |
| |
| // https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray |
| Node* FlattenIntoArray(Node* context, Node* target, Node* source, |
| Node* source_length, Node* start, Node* depth, |
| Node* mapper_function = nullptr, |
| Node* this_arg = nullptr) { |
| CSA_ASSERT(this, IsJSReceiver(target)); |
| CSA_ASSERT(this, IsJSReceiver(source)); |
| CSA_ASSERT(this, IsNumberPositive(source_length)); |
| CSA_ASSERT(this, IsNumberPositive(start)); |
| CSA_ASSERT(this, IsNumber(depth)); |
| |
| // 1. Let targetIndex be start. |
| VARIABLE(var_target_index, MachineRepresentation::kTagged, start); |
| |
| // 2. Let sourceIndex be 0. |
| VARIABLE(var_source_index, MachineRepresentation::kTagged, SmiConstant(0)); |
| |
| // 3. Repeat... |
| Label loop(this, {&var_target_index, &var_source_index}), done_loop(this); |
| Goto(&loop); |
| BIND(&loop); |
| { |
| Node* const source_index = var_source_index.value(); |
| Node* const target_index = var_target_index.value(); |
| |
| // ...while sourceIndex < sourceLen |
| GotoIfNumberGreaterThanOrEqual(source_index, source_length, &done_loop); |
| |
| // a. Let P be ! ToString(sourceIndex). |
| // b. Let exists be ? HasProperty(source, P). |
| CSA_ASSERT(this, |
| SmiGreaterThanOrEqual(CAST(source_index), SmiConstant(0))); |
| Node* const exists = |
| HasProperty(context, source, source_index, kHasProperty); |
| |
| // c. If exists is true, then |
| Label next(this); |
| GotoIfNot(IsTrue(exists), &next); |
| { |
| // i. Let element be ? Get(source, P). |
| Node* element = GetProperty(context, source, source_index); |
| |
| // ii. If mapperFunction is present, then |
| if (mapper_function != nullptr) { |
| CSA_ASSERT(this, Word32Or(IsUndefined(mapper_function), |
| IsCallable(mapper_function))); |
| DCHECK_NOT_NULL(this_arg); |
| |
| // 1. Set element to ? Call(mapperFunction, thisArg , « element, |
| // sourceIndex, source »). |
| element = |
| CallJS(CodeFactory::Call(isolate()), context, mapper_function, |
| this_arg, element, source_index, source); |
| } |
| |
| // iii. Let shouldFlatten be false. |
| Label if_flatten_array(this), if_flatten_proxy(this, Label::kDeferred), |
| if_noflatten(this); |
| // iv. If depth > 0, then |
| GotoIfNumberGreaterThanOrEqual(SmiConstant(0), depth, &if_noflatten); |
| // 1. Set shouldFlatten to ? IsArray(element). |
| GotoIf(TaggedIsSmi(element), &if_noflatten); |
| GotoIf(IsJSArray(element), &if_flatten_array); |
| GotoIfNot(IsJSProxy(element), &if_noflatten); |
| Branch(IsTrue(CallRuntime(Runtime::kArrayIsArray, context, element)), |
| &if_flatten_proxy, &if_noflatten); |
| |
| BIND(&if_flatten_array); |
| { |
| CSA_ASSERT(this, IsJSArray(element)); |
| |
| // 1. Let elementLen be ? ToLength(? Get(element, "length")). |
| Node* const element_length = |
| LoadObjectField(element, JSArray::kLengthOffset); |
| |
| // 2. Set targetIndex to ? FlattenIntoArray(target, element, |
| // elementLen, targetIndex, |
| // depth - 1). |
| var_target_index.Bind( |
| CallBuiltin(Builtins::kFlattenIntoArray, context, target, element, |
| element_length, target_index, NumberDec(depth))); |
| Goto(&next); |
| } |
| |
| BIND(&if_flatten_proxy); |
| { |
| CSA_ASSERT(this, IsJSProxy(element)); |
| |
| // 1. Let elementLen be ? ToLength(? Get(element, "length")). |
| Node* const element_length = ToLength_Inline( |
| context, GetProperty(context, element, LengthStringConstant())); |
| |
| // 2. Set targetIndex to ? FlattenIntoArray(target, element, |
| // elementLen, targetIndex, |
| // depth - 1). |
| var_target_index.Bind( |
| CallBuiltin(Builtins::kFlattenIntoArray, context, target, element, |
| element_length, target_index, NumberDec(depth))); |
| Goto(&next); |
| } |
| |
| BIND(&if_noflatten); |
| { |
| // 1. If targetIndex >= 2^53-1, throw a TypeError exception. |
| Label throw_error(this, Label::kDeferred); |
| GotoIfNumberGreaterThanOrEqual( |
| target_index, NumberConstant(kMaxSafeInteger), &throw_error); |
| |
| // 2. Perform ? CreateDataPropertyOrThrow(target, |
| // ! ToString(targetIndex), |
| // element). |
| CallRuntime(Runtime::kCreateDataProperty, context, target, |
| target_index, element); |
| |
| // 3. Increase targetIndex by 1. |
| var_target_index.Bind(NumberInc(target_index)); |
| Goto(&next); |
| |
| BIND(&throw_error); |
| ThrowTypeError(context, MessageTemplate::kFlattenPastSafeLength, |
| source_length, target_index); |
| } |
| } |
| BIND(&next); |
| |
| // d. Increase sourceIndex by 1. |
| var_source_index.Bind(NumberInc(source_index)); |
| Goto(&loop); |
| } |
| |
| BIND(&done_loop); |
| return var_target_index.value(); |
| } |
| }; |
| |
| } // namespace |
| |
| // https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray |
| TF_BUILTIN(FlattenIntoArray, ArrayFlattenAssembler) { |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const target = Parameter(Descriptor::kTarget); |
| Node* const source = Parameter(Descriptor::kSource); |
| Node* const source_length = Parameter(Descriptor::kSourceLength); |
| Node* const start = Parameter(Descriptor::kStart); |
| Node* const depth = Parameter(Descriptor::kDepth); |
| |
| Return( |
| FlattenIntoArray(context, target, source, source_length, start, depth)); |
| } |
| |
| // https://tc39.github.io/proposal-flatMap/#sec-FlattenIntoArray |
| TF_BUILTIN(FlatMapIntoArray, ArrayFlattenAssembler) { |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const target = Parameter(Descriptor::kTarget); |
| Node* const source = Parameter(Descriptor::kSource); |
| Node* const source_length = Parameter(Descriptor::kSourceLength); |
| Node* const start = Parameter(Descriptor::kStart); |
| Node* const depth = Parameter(Descriptor::kDepth); |
| Node* const mapper_function = Parameter(Descriptor::kMapperFunction); |
| Node* const this_arg = Parameter(Descriptor::kThisArg); |
| |
| Return(FlattenIntoArray(context, target, source, source_length, start, depth, |
| mapper_function, this_arg)); |
| } |
| |
| // https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flat |
| TF_BUILTIN(ArrayPrototypeFlat, CodeStubAssembler) { |
| Node* const argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const receiver = args.GetReceiver(); |
| Node* const depth = args.GetOptionalArgumentValue(0); |
| |
| // 1. Let O be ? ToObject(this value). |
| Node* const o = ToObject_Inline(CAST(context), CAST(receiver)); |
| |
| // 2. Let sourceLen be ? ToLength(? Get(O, "length")). |
| Node* const source_length = |
| ToLength_Inline(context, GetProperty(context, o, LengthStringConstant())); |
| |
| // 3. Let depthNum be 1. |
| VARIABLE(var_depth_num, MachineRepresentation::kTagged, SmiConstant(1)); |
| |
| // 4. If depth is not undefined, then |
| Label done(this); |
| GotoIf(IsUndefined(depth), &done); |
| { |
| // a. Set depthNum to ? ToInteger(depth). |
| var_depth_num.Bind(ToInteger_Inline(context, depth)); |
| Goto(&done); |
| } |
| BIND(&done); |
| |
| // 5. Let A be ? ArraySpeciesCreate(O, 0). |
| Node* const constructor = |
| CallRuntime(Runtime::kArraySpeciesConstructor, context, o); |
| Node* const a = ConstructJS(CodeFactory::Construct(isolate()), context, |
| constructor, SmiConstant(0)); |
| |
| // 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, depthNum). |
| CallBuiltin(Builtins::kFlattenIntoArray, context, a, o, source_length, |
| SmiConstant(0), var_depth_num.value()); |
| |
| // 7. Return A. |
| args.PopAndReturn(a); |
| } |
| |
| // https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap |
| TF_BUILTIN(ArrayPrototypeFlatMap, CodeStubAssembler) { |
| Node* const argc = |
| ChangeInt32ToIntPtr(Parameter(Descriptor::kJSActualArgumentsCount)); |
| CodeStubArguments args(this, argc); |
| Node* const context = Parameter(Descriptor::kContext); |
| Node* const receiver = args.GetReceiver(); |
| Node* const mapper_function = args.GetOptionalArgumentValue(0); |
| |
| // 1. Let O be ? ToObject(this value). |
| Node* const o = ToObject_Inline(CAST(context), CAST(receiver)); |
| |
| // 2. Let sourceLen be ? ToLength(? Get(O, "length")). |
| Node* const source_length = |
| ToLength_Inline(context, GetProperty(context, o, LengthStringConstant())); |
| |
| // 3. If IsCallable(mapperFunction) is false, throw a TypeError exception. |
| Label if_not_callable(this, Label::kDeferred); |
| GotoIf(TaggedIsSmi(mapper_function), &if_not_callable); |
| GotoIfNot(IsCallable(mapper_function), &if_not_callable); |
| |
| // 4. If thisArg is present, let T be thisArg; else let T be undefined. |
| Node* const t = args.GetOptionalArgumentValue(1); |
| |
| // 5. Let A be ? ArraySpeciesCreate(O, 0). |
| Node* const constructor = |
| CallRuntime(Runtime::kArraySpeciesConstructor, context, o); |
| Node* const a = ConstructJS(CodeFactory::Construct(isolate()), context, |
| constructor, SmiConstant(0)); |
| |
| // 6. Perform ? FlattenIntoArray(A, O, sourceLen, 0, 1, mapperFunction, T). |
| CallBuiltin(Builtins::kFlatMapIntoArray, context, a, o, source_length, |
| SmiConstant(0), SmiConstant(1), mapper_function, t); |
| |
| // 7. Return A. |
| args.PopAndReturn(a); |
| |
| BIND(&if_not_callable); |
| { ThrowTypeError(context, MessageTemplate::kMapperFunctionNonCallable); } |
| } |
| |
| TF_BUILTIN(ArrayConstructor, ArrayBuiltinsAssembler) { |
| // This is a trampoline to ArrayConstructorImpl which just adds |
| // allocation_site parameter value and sets new_target if necessary. |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<JSFunction> function = CAST(Parameter(Descriptor::kTarget)); |
| TNode<Object> new_target = CAST(Parameter(Descriptor::kNewTarget)); |
| TNode<Int32T> argc = |
| UncheckedCast<Int32T>(Parameter(Descriptor::kActualArgumentsCount)); |
| |
| // If new_target is undefined, then this is the 'Call' case, so set new_target |
| // to function. |
| new_target = |
| SelectConstant<Object>(IsUndefined(new_target), function, new_target); |
| |
| // Run the native code for the Array function called as a normal function. |
| TNode<Object> no_allocation_site = UndefinedConstant(); |
| TailCallBuiltin(Builtins::kArrayConstructorImpl, context, function, |
| new_target, argc, no_allocation_site); |
| } |
| |
| void ArrayBuiltinsAssembler::TailCallArrayConstructorStub( |
| const Callable& callable, TNode<Context> context, TNode<JSFunction> target, |
| TNode<HeapObject> allocation_site_or_undefined, TNode<Int32T> argc) { |
| TNode<Code> code = HeapConstant(callable.code()); |
| |
| // We are going to call here ArrayNoArgumentsConstructor or |
| // ArraySingleArgumentsConstructor which in addition to the register arguments |
| // also expect some number of arguments on the expression stack. |
| // Since |
| // 1) incoming JS arguments are still on the stack, |
| // 2) the ArrayNoArgumentsConstructor, ArraySingleArgumentsConstructor and |
| // ArrayNArgumentsConstructor are defined so that the register arguments |
| // are passed on the same registers, |
| // in order to be able to generate a tail call to those builtins we do the |
| // following trick here: we tail call to the constructor builtin using |
| // ArrayNArgumentsConstructorDescriptor, so the tail call instruction |
| // pops the current frame but leaves all the incoming JS arguments on the |
| // expression stack so that the target builtin can still find them where it |
| // expects. |
| TailCallStub(ArrayNArgumentsConstructorDescriptor{}, code, context, target, |
| allocation_site_or_undefined, argc); |
| } |
| |
| void ArrayBuiltinsAssembler::CreateArrayDispatchNoArgument( |
| TNode<Context> context, TNode<JSFunction> target, TNode<Int32T> argc, |
| AllocationSiteOverrideMode mode, TNode<AllocationSite> allocation_site) { |
| if (mode == DISABLE_ALLOCATION_SITES) { |
| Callable callable = CodeFactory::ArrayNoArgumentConstructor( |
| isolate(), GetInitialFastElementsKind(), mode); |
| |
| TailCallArrayConstructorStub(callable, context, target, UndefinedConstant(), |
| argc); |
| } else { |
| DCHECK_EQ(mode, DONT_OVERRIDE); |
| TNode<Int32T> elements_kind = LoadElementsKind(allocation_site); |
| |
| // TODO(ishell): Compute the builtin index dynamically instead of |
| // iterating over all expected elements kinds. |
| int last_index = |
| GetSequenceIndexFromFastElementsKind(TERMINAL_FAST_ELEMENTS_KIND); |
| for (int i = 0; i <= last_index; ++i) { |
| Label next(this); |
| ElementsKind kind = GetFastElementsKindFromSequenceIndex(i); |
| GotoIfNot(Word32Equal(elements_kind, Int32Constant(kind)), &next); |
| |
| Callable callable = |
| CodeFactory::ArrayNoArgumentConstructor(isolate(), kind, mode); |
| |
| TailCallArrayConstructorStub(callable, context, target, allocation_site, |
| argc); |
| |
| BIND(&next); |
| } |
| |
| // If we reached this point there is a problem. |
| Abort(AbortReason::kUnexpectedElementsKindInArrayConstructor); |
| } |
| } |
| |
| void ArrayBuiltinsAssembler::CreateArrayDispatchSingleArgument( |
| TNode<Context> context, TNode<JSFunction> target, TNode<Int32T> argc, |
| AllocationSiteOverrideMode mode, TNode<AllocationSite> allocation_site) { |
| if (mode == DISABLE_ALLOCATION_SITES) { |
| ElementsKind initial = GetInitialFastElementsKind(); |
| ElementsKind holey_initial = GetHoleyElementsKind(initial); |
| Callable callable = CodeFactory::ArraySingleArgumentConstructor( |
| isolate(), holey_initial, mode); |
| |
| TailCallArrayConstructorStub(callable, context, target, UndefinedConstant(), |
| argc); |
| } else { |
| DCHECK_EQ(mode, DONT_OVERRIDE); |
| TNode<Smi> transition_info = LoadTransitionInfo(allocation_site); |
| |
| // Least significant bit in fast array elements kind means holeyness. |
| STATIC_ASSERT(PACKED_SMI_ELEMENTS == 0); |
| STATIC_ASSERT(HOLEY_SMI_ELEMENTS == 1); |
| STATIC_ASSERT(PACKED_ELEMENTS == 2); |
| STATIC_ASSERT(HOLEY_ELEMENTS == 3); |
| STATIC_ASSERT(PACKED_DOUBLE_ELEMENTS == 4); |
| STATIC_ASSERT(HOLEY_DOUBLE_ELEMENTS == 5); |
| |
| Label normal_sequence(this); |
| TVARIABLE(Int32T, var_elements_kind, |
| Signed(DecodeWord32<AllocationSite::ElementsKindBits>( |
| SmiToInt32(transition_info)))); |
| // Is the low bit set? If so, we are holey and that is good. |
| int fast_elements_kind_holey_mask = |
| AllocationSite::ElementsKindBits::encode(static_cast<ElementsKind>(1)); |
| GotoIf(IsSetSmi(transition_info, fast_elements_kind_holey_mask), |
| &normal_sequence); |
| { |
| // Make elements kind holey and update elements kind in the type info. |
| var_elements_kind = |
| Signed(Word32Or(var_elements_kind.value(), Int32Constant(1))); |
| StoreObjectFieldNoWriteBarrier( |
| allocation_site, AllocationSite::kTransitionInfoOrBoilerplateOffset, |
| SmiOr(transition_info, SmiConstant(fast_elements_kind_holey_mask))); |
| Goto(&normal_sequence); |
| } |
| BIND(&normal_sequence); |
| |
| // TODO(ishell): Compute the builtin index dynamically instead of |
| // iterating over all expected elements kinds. |
| // TODO(ishell): Given that the code above ensures that the elements kind |
| // is holey we can skip checking with non-holey elements kinds. |
| int last_index = |
| GetSequenceIndexFromFastElementsKind(TERMINAL_FAST_ELEMENTS_KIND); |
| for (int i = 0; i <= last_index; ++i) { |
| Label next(this); |
| ElementsKind kind = GetFastElementsKindFromSequenceIndex(i); |
| GotoIfNot(Word32Equal(var_elements_kind.value(), Int32Constant(kind)), |
| &next); |
| |
| Callable callable = |
| CodeFactory::ArraySingleArgumentConstructor(isolate(), kind, mode); |
| |
| TailCallArrayConstructorStub(callable, context, target, allocation_site, |
| argc); |
| |
| BIND(&next); |
| } |
| |
| // If we reached this point there is a problem. |
| Abort(AbortReason::kUnexpectedElementsKindInArrayConstructor); |
| } |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateDispatchToArrayStub( |
| TNode<Context> context, TNode<JSFunction> target, TNode<Int32T> argc, |
| AllocationSiteOverrideMode mode, TNode<AllocationSite> allocation_site) { |
| Label check_one_case(this), fallthrough(this); |
| GotoIfNot(Word32Equal(argc, Int32Constant(0)), &check_one_case); |
| CreateArrayDispatchNoArgument(context, target, argc, mode, allocation_site); |
| |
| BIND(&check_one_case); |
| GotoIfNot(Word32Equal(argc, Int32Constant(1)), &fallthrough); |
| CreateArrayDispatchSingleArgument(context, target, argc, mode, |
| allocation_site); |
| |
| BIND(&fallthrough); |
| } |
| |
| TF_BUILTIN(ArrayConstructorImpl, ArrayBuiltinsAssembler) { |
| TNode<JSFunction> target = CAST(Parameter(Descriptor::kTarget)); |
| TNode<Object> new_target = CAST(Parameter(Descriptor::kNewTarget)); |
| TNode<Int32T> argc = |
| UncheckedCast<Int32T>(Parameter(Descriptor::kActualArgumentsCount)); |
| TNode<HeapObject> maybe_allocation_site = |
| CAST(Parameter(Descriptor::kAllocationSite)); |
| |
| // Initial map for the builtin Array functions should be Map. |
| CSA_ASSERT(this, IsMap(CAST(LoadObjectField( |
| target, JSFunction::kPrototypeOrInitialMapOffset)))); |
| |
| // We should either have undefined or a valid AllocationSite |
| CSA_ASSERT(this, Word32Or(IsUndefined(maybe_allocation_site), |
| IsAllocationSite(maybe_allocation_site))); |
| |
| // "Enter" the context of the Array function. |
| TNode<Context> context = |
| CAST(LoadObjectField(target, JSFunction::kContextOffset)); |
| |
| Label runtime(this, Label::kDeferred); |
| GotoIf(WordNotEqual(target, new_target), &runtime); |
| |
| Label no_info(this); |
| // If the feedback vector is the undefined value call an array constructor |
| // that doesn't use AllocationSites. |
| GotoIf(IsUndefined(maybe_allocation_site), &no_info); |
| |
| GenerateDispatchToArrayStub(context, target, argc, DONT_OVERRIDE, |
| CAST(maybe_allocation_site)); |
| Goto(&runtime); |
| |
| BIND(&no_info); |
| GenerateDispatchToArrayStub(context, target, argc, DISABLE_ALLOCATION_SITES); |
| Goto(&runtime); |
| |
| BIND(&runtime); |
| GenerateArrayNArgumentsConstructor(context, target, new_target, argc, |
| maybe_allocation_site); |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateConstructor( |
| Node* context, Node* array_function, Node* array_map, Node* array_size, |
| Node* allocation_site, ElementsKind elements_kind, |
| AllocationSiteMode mode) { |
| Label ok(this); |
| Label smi_size(this); |
| Label small_smi_size(this); |
| Label call_runtime(this, Label::kDeferred); |
| |
| Branch(TaggedIsSmi(array_size), &smi_size, &call_runtime); |
| |
| BIND(&smi_size); |
| |
| if (IsFastPackedElementsKind(elements_kind)) { |
| Label abort(this, Label::kDeferred); |
| Branch(SmiEqual(CAST(array_size), SmiConstant(0)), &small_smi_size, &abort); |
| |
| BIND(&abort); |
| Node* reason = SmiConstant(AbortReason::kAllocatingNonEmptyPackedArray); |
| TailCallRuntime(Runtime::kAbort, context, reason); |
| } else { |
| int element_size = |
| IsDoubleElementsKind(elements_kind) ? kDoubleSize : kPointerSize; |
| int max_fast_elements = |
| (kMaxRegularHeapObjectSize - FixedArray::kHeaderSize - JSArray::kSize - |
| AllocationMemento::kSize) / |
| element_size; |
| Branch(SmiAboveOrEqual(CAST(array_size), SmiConstant(max_fast_elements)), |
| &call_runtime, &small_smi_size); |
| } |
| |
| BIND(&small_smi_size); |
| { |
| Node* array = AllocateJSArray( |
| elements_kind, array_map, array_size, array_size, |
| mode == DONT_TRACK_ALLOCATION_SITE ? nullptr : allocation_site, |
| CodeStubAssembler::SMI_PARAMETERS); |
| Return(array); |
| } |
| |
| BIND(&call_runtime); |
| { |
| TailCallRuntime(Runtime::kNewArray, context, array_function, array_size, |
| array_function, allocation_site); |
| } |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateArrayNoArgumentConstructor( |
| ElementsKind kind, AllocationSiteOverrideMode mode) { |
| typedef ArrayNoArgumentConstructorDescriptor Descriptor; |
| Node* native_context = LoadObjectField(Parameter(Descriptor::kFunction), |
| JSFunction::kContextOffset); |
| bool track_allocation_site = |
| AllocationSite::ShouldTrack(kind) && mode != DISABLE_ALLOCATION_SITES; |
| Node* allocation_site = |
| track_allocation_site ? Parameter(Descriptor::kAllocationSite) : nullptr; |
| Node* array_map = LoadJSArrayElementsMap(kind, native_context); |
| Node* array = AllocateJSArray( |
| kind, array_map, IntPtrConstant(JSArray::kPreallocatedArrayElements), |
| SmiConstant(0), allocation_site); |
| Return(array); |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateArraySingleArgumentConstructor( |
| ElementsKind kind, AllocationSiteOverrideMode mode) { |
| typedef ArraySingleArgumentConstructorDescriptor Descriptor; |
| Node* context = Parameter(Descriptor::kContext); |
| Node* function = Parameter(Descriptor::kFunction); |
| Node* native_context = LoadObjectField(function, JSFunction::kContextOffset); |
| Node* array_map = LoadJSArrayElementsMap(kind, native_context); |
| |
| AllocationSiteMode allocation_site_mode = DONT_TRACK_ALLOCATION_SITE; |
| if (mode == DONT_OVERRIDE) { |
| allocation_site_mode = AllocationSite::ShouldTrack(kind) |
| ? TRACK_ALLOCATION_SITE |
| : DONT_TRACK_ALLOCATION_SITE; |
| } |
| |
| Node* array_size = Parameter(Descriptor::kArraySizeSmiParameter); |
| Node* allocation_site = Parameter(Descriptor::kAllocationSite); |
| |
| GenerateConstructor(context, function, array_map, array_size, allocation_site, |
| kind, allocation_site_mode); |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateArrayNArgumentsConstructor( |
| TNode<Context> context, TNode<JSFunction> target, TNode<Object> new_target, |
| TNode<Int32T> argc, TNode<HeapObject> maybe_allocation_site) { |
| // Replace incoming JS receiver argument with the target. |
| // TODO(ishell): Avoid replacing the target on the stack and just add it |
| // as another additional parameter for Runtime::kNewArray. |
| CodeStubArguments args(this, ChangeInt32ToIntPtr(argc)); |
| args.SetReceiver(target); |
| |
| // Adjust arguments count for the runtime call: +1 for implicit receiver |
| // and +2 for new_target and maybe_allocation_site. |
| argc = Int32Add(argc, Int32Constant(3)); |
| TailCallRuntime(Runtime::kNewArray, argc, context, new_target, |
| maybe_allocation_site); |
| } |
| |
| TF_BUILTIN(ArrayNArgumentsConstructor, ArrayBuiltinsAssembler) { |
| TNode<Context> context = CAST(Parameter(Descriptor::kContext)); |
| TNode<JSFunction> target = CAST(Parameter(Descriptor::kFunction)); |
| TNode<Int32T> argc = |
| UncheckedCast<Int32T>(Parameter(Descriptor::kActualArgumentsCount)); |
| TNode<HeapObject> maybe_allocation_site = |
| CAST(Parameter(Descriptor::kAllocationSite)); |
| |
| GenerateArrayNArgumentsConstructor(context, target, target, argc, |
| maybe_allocation_site); |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateInternalArrayNoArgumentConstructor( |
| ElementsKind kind) { |
| typedef ArrayNoArgumentConstructorDescriptor Descriptor; |
| Node* array_map = LoadObjectField(Parameter(Descriptor::kFunction), |
| JSFunction::kPrototypeOrInitialMapOffset); |
| Node* array = AllocateJSArray( |
| kind, array_map, IntPtrConstant(JSArray::kPreallocatedArrayElements), |
| SmiConstant(0)); |
| Return(array); |
| } |
| |
| void ArrayBuiltinsAssembler::GenerateInternalArraySingleArgumentConstructor( |
| ElementsKind kind) { |
| typedef ArraySingleArgumentConstructorDescriptor Descriptor; |
| Node* context = Parameter(Descriptor::kContext); |
| Node* function = Parameter(Descriptor::kFunction); |
| Node* array_map = |
| LoadObjectField(function, JSFunction::kPrototypeOrInitialMapOffset); |
| Node* array_size = Parameter(Descriptor::kArraySizeSmiParameter); |
| Node* allocation_site = UndefinedConstant(); |
| |
| GenerateConstructor(context, function, array_map, array_size, allocation_site, |
| kind, DONT_TRACK_ALLOCATION_SITE); |
| } |
| |
| #define GENERATE_ARRAY_CTOR(name, kind_camel, kind_caps, mode_camel, \ |
| mode_caps) \ |
| TF_BUILTIN(Array##name##Constructor_##kind_camel##_##mode_camel, \ |
| ArrayBuiltinsAssembler) { \ |
| GenerateArray##name##Constructor(kind_caps, mode_caps); \ |
| } |
| |
| // The ArrayNoArgumentConstructor builtin family. |
| GENERATE_ARRAY_CTOR(NoArgument, PackedSmi, PACKED_SMI_ELEMENTS, DontOverride, |
| DONT_OVERRIDE); |
| GENERATE_ARRAY_CTOR(NoArgument, HoleySmi, HOLEY_SMI_ELEMENTS, DontOverride, |
| DONT_OVERRIDE); |
| GENERATE_ARRAY_CTOR(NoArgument, PackedSmi, PACKED_SMI_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(NoArgument, HoleySmi, HOLEY_SMI_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(NoArgument, Packed, PACKED_ELEMENTS, DisableAllocationSites, |
| DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(NoArgument, Holey, HOLEY_ELEMENTS, DisableAllocationSites, |
| DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(NoArgument, PackedDouble, PACKED_DOUBLE_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(NoArgument, HoleyDouble, HOLEY_DOUBLE_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| |
| // The ArraySingleArgumentConstructor builtin family. |
| GENERATE_ARRAY_CTOR(SingleArgument, PackedSmi, PACKED_SMI_ELEMENTS, |
| DontOverride, DONT_OVERRIDE); |
| GENERATE_ARRAY_CTOR(SingleArgument, HoleySmi, HOLEY_SMI_ELEMENTS, DontOverride, |
| DONT_OVERRIDE); |
| GENERATE_ARRAY_CTOR(SingleArgument, PackedSmi, PACKED_SMI_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(SingleArgument, HoleySmi, HOLEY_SMI_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(SingleArgument, Packed, PACKED_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(SingleArgument, Holey, HOLEY_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(SingleArgument, PackedDouble, PACKED_DOUBLE_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| GENERATE_ARRAY_CTOR(SingleArgument, HoleyDouble, HOLEY_DOUBLE_ELEMENTS, |
| DisableAllocationSites, DISABLE_ALLOCATION_SITES); |
| |
| #undef GENERATE_ARRAY_CTOR |
| |
| #define GENERATE_INTERNAL_ARRAY_CTOR(name, kind_camel, kind_caps) \ |
| TF_BUILTIN(InternalArray##name##Constructor_##kind_camel, \ |
| ArrayBuiltinsAssembler) { \ |
| GenerateInternalArray##name##Constructor(kind_caps); \ |
| } |
| |
| GENERATE_INTERNAL_ARRAY_CTOR(NoArgument, Packed, PACKED_ELEMENTS); |
| GENERATE_INTERNAL_ARRAY_CTOR(NoArgument, Holey, HOLEY_ELEMENTS); |
| GENERATE_INTERNAL_ARRAY_CTOR(SingleArgument, Packed, PACKED_ELEMENTS); |
| GENERATE_INTERNAL_ARRAY_CTOR(SingleArgument, Holey, HOLEY_ELEMENTS); |
| |
| #undef GENERATE_INTERNAL_ARRAY_CTOR |
| |
| } // namespace internal |
| } // namespace v8 |