// Copyright 2017 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/builtins/builtins-iterator-gen.h" #include "src/builtins/builtins-string-gen.h" #include "src/builtins/builtins-typedarray-gen.h" #include "src/builtins/builtins-utils-gen.h" #include "src/builtins/builtins.h" #include "src/code-stub-assembler.h" #include "src/factory-inl.h" #include "src/frame-constants.h" #include "src/builtins/builtins-array-gen.h" namespace v8 { namespace internal { using Node = compiler::Node; ArrayBuiltinsAssembler::ArrayBuiltinsAssembler( compiler::CodeAssemblerState* state) : CodeStubAssembler(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(SmiNotEqual(LoadJSArrayLength(a()), to_.value()), &runtime); kind = EnsureArrayPushable(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 original_array = CAST(o()); TNode length = CAST(len_); const char* method_name = "%TypedArray%.prototype.map"; TypedArrayBuiltinsAssembler typedarray_asm(state()); TNode a = typedarray_asm.SpeciesCreateByLength( CAST(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( len_, LoadObjectField(a, JSTypedArray::kLengthOffset))); fast_typed_array_target_ = Word32Equal(LoadInstanceType(LoadElements(o_)), 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); Node* kind = LoadMapElementsKind(LoadMap(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); CallStub(CodeFactory::TransitionElementsKind( isolate(), HOLEY_SMI_ELEMENTS, HOLEY_DOUBLE_ELEMENTS, true), 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 3. Let numValue be ? ToNumber(value). Node* 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); Goto(&done); BIND(&slow); CallRuntime(Runtime::kSetProperty, context(), a(), k, mapped_value, SmiConstant(LanguageMode::kStrict)); Goto(&done); BIND(&detached); // tc39.github.io/ecma262/#sec-integerindexedelementset // 5. If IsDetachedBuffer(buffer) is true, throw a TypeError exception. ThrowTypeError(context_, MessageTemplate::kDetachedOperation, name_); BIND(&done); return a(); } void ArrayBuiltinsAssembler::NullPostLoopAction() {} 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( Node* context, Node* receiver, Node* callbackfn, Node* this_arg, Node* new_target, Node* argc) { context_ = context; receiver_ = receiver; new_target_ = new_target; 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_ = CallBuiltin(Builtins::kToObject, context(), receiver()); // 3. Let len be ToLength(Get(O, "length")). // 4. ReturnIfAbrupt(len). VARIABLE(merged_length, MachineRepresentation::kTagged); Label has_length(this, &merged_length), not_js_array(this); GotoIf(DoesntHaveInstanceType(o(), JS_ARRAY_TYPE), ¬_js_array); merged_length.Bind(LoadJSArrayLength(o())); Goto(&has_length); BIND(¬_js_array); Node* len_property = GetProperty(context(), o(), isolate()->factory()->length_string()); merged_length.Bind(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( Node* context, Node* receiver, Node* callbackfn, Node* this_arg, Node* a, Node* o, Node* initial_k, Node* len, Node* to) { context_ = context; this_arg_ = this_arg; callbackfn_ = callbackfn; argc_ = nullptr; 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), throw_detached(this, Label::kDeferred); GotoIf(TaggedIsSmi(receiver_), &throw_not_typed_array); GotoIfNot(HasInstanceType(receiver_, JS_TYPED_ARRAY_TYPE), &throw_not_typed_array); o_ = receiver_; Node* array_buffer = LoadObjectField(o_, JSTypedArray::kBufferOffset); GotoIf(IsDetachedBuffer(array_buffer), &throw_detached); len_ = LoadObjectField(o_, JSTypedArray::kLengthOffset); 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_detached); ThrowTypeError(context_, MessageTemplate::kDetachedOperation, name_); BIND(&throw_not_callable); ThrowTypeError(context_, MessageTemplate::kCalledNonCallable, callbackfn_); Label unexpected_instance_type(this); BIND(&unexpected_instance_type); Unreachable(); std::vector instance_types = { #define INSTANCE_TYPE(Type, type, TYPE, ctype, size) FIXED_##TYPE##_ARRAY_TYPE, TYPED_ARRAYS(INSTANCE_TYPE) #undef INSTANCE_TYPE }; std::vector