// 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-utils-gen.h" #include "src/builtins/builtins.h" #include "src/code-stub-assembler.h" namespace v8 { namespace internal { class ArrayBuiltinCodeStubAssembler : public CodeStubAssembler { public: explicit ArrayBuiltinCodeStubAssembler(compiler::CodeAssemblerState* state) : CodeStubAssembler(state), k_(this, MachineRepresentation::kTagged), a_(this, MachineRepresentation::kTagged), to_(this, MachineRepresentation::kTagged, SmiConstant(0)) {} typedef std::function BuiltinResultGenerator; typedef std::function BuiltinResultIndexInitializer; typedef std::function CallResultProcessor; typedef std::function PostLoopAction; Node* ForEachResultGenerator() { return UndefinedConstant(); } Node* ForEachProcessor(Node* k_value, Node* k) { CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), this_arg(), k_value, k, o()); return a(); } Node* SomeResultGenerator() { return FalseConstant(); } Node* 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); Return(TrueConstant()); BIND(&false_continue); return a(); } Node* EveryResultGenerator() { return TrueConstant(); } Node* 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); Return(FalseConstant()); BIND(&true_continue); return a(); } Node* ReduceResultGenerator() { VARIABLE(a, MachineRepresentation::kTagged, UndefinedConstant()); Label no_initial_value(this), has_initial_value(this), done(this, {&a}); // 8. If initialValue is present, then Node* parent_frame_ptr = LoadParentFramePointer(); Node* marker_or_function = LoadBufferObject( parent_frame_ptr, CommonFrameConstants::kContextOrFrameTypeOffset); GotoIf( MarkerIsNotFrameType(marker_or_function, StackFrame::ARGUMENTS_ADAPTOR), &has_initial_value); // Has arguments adapter, check count. Node* adapted_parameter_count = LoadBufferObject( parent_frame_ptr, ArgumentsAdaptorFrameConstants::kLengthOffset); Branch(SmiLessThan(adapted_parameter_count, SmiConstant(IteratingArrayBuiltinDescriptor::kThisArg)), &no_initial_value, &has_initial_value); // a. Set accumulator to initialValue. BIND(&has_initial_value); a.Bind(this_arg()); Goto(&done); // 9. Else initialValue is not present, BIND(&no_initial_value); // a. Let kPresent be false. a.Bind(TheHoleConstant()); Goto(&done); BIND(&done); return a.value(); } Node* 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 ReducePostLoopAction() { Label ok(this); GotoIf(WordNotEqual(a(), TheHoleConstant()), &ok); CallRuntime(Runtime::kThrowTypeError, context(), SmiConstant(MessageTemplate::kReduceNoInitial)); Unreachable(); BIND(&ok); } Node* FilterResultGenerator() { // 7. Let A be ArraySpeciesCreate(O, 0). return ArraySpeciesCreate(context(), o(), SmiConstant(0)); } Node* 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(), FastJSArrayAccessMode::ANY_ACCESS, &fast, &runtime); BIND(&fast); { kind = EnsureArrayPushable(a(), &runtime); GotoIf(IsElementsKindGreaterThan(kind, FAST_HOLEY_SMI_ELEMENTS), &object_push_pre); BuildAppendJSArray(FAST_SMI_ELEMENTS, a(), k_value, &runtime); Goto(&after_work); } BIND(&object_push_pre); { Branch(IsElementsKindGreaterThan(kind, FAST_HOLEY_ELEMENTS), &double_push, &object_push); } BIND(&object_push); { BuildAppendJSArray(FAST_ELEMENTS, a(), k_value, &runtime); Goto(&after_work); } BIND(&double_push); { BuildAppendJSArray(FAST_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(); } Node* MapResultGenerator() { // 5. Let A be ? ArraySpeciesCreate(O, len). return ArraySpeciesCreate(context(), o(), len_); } Node* MapProcessor(Node* k_value, Node* k) { // i. Let kValue be ? Get(O, Pk). Performed by the caller of MapProcessor. // ii. Let mappedValue be ? Call(callbackfn, T, kValue, k, O). Node* mappedValue = CallJS(CodeFactory::Call(isolate()), context(), callbackfn(), this_arg(), k_value, k, o()); Label finished(this); Node* kind = nullptr; Node* elements = nullptr; // If a() is a JSArray, we can have a fast path. // mode is SMI_PARAMETERS because k has tagged representation. ParameterMode mode = SMI_PARAMETERS; Label fast(this); Label runtime(this); Label object_push_pre(this), object_push(this), double_push(this); BranchIfFastJSArray(a(), context(), FastJSArrayAccessMode::ANY_ACCESS, &fast, &runtime); BIND(&fast); { kind = EnsureArrayPushable(a(), &runtime); elements = LoadElements(a()); GotoIf(IsElementsKindGreaterThan(kind, FAST_HOLEY_SMI_ELEMENTS), &object_push_pre); TryStoreArrayElement(FAST_SMI_ELEMENTS, mode, &runtime, elements, k, mappedValue); Goto(&finished); } BIND(&object_push_pre); { Branch(IsElementsKindGreaterThan(kind, FAST_HOLEY_ELEMENTS), &double_push, &object_push); } BIND(&object_push); { TryStoreArrayElement(FAST_ELEMENTS, mode, &runtime, elements, k, mappedValue); Goto(&finished); } BIND(&double_push); { TryStoreArrayElement(FAST_DOUBLE_ELEMENTS, mode, &runtime, elements, k, mappedValue); Goto(&finished); } BIND(&runtime); { // iii. Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue). CallRuntime(Runtime::kCreateDataProperty, context(), a(), k, mappedValue); Goto(&finished); } BIND(&finished); return a(); } void NullPostLoopAction() {} protected: Node* context() { return context_; } Node* receiver() { return receiver_; } Node* new_target() { return new_target_; } Node* o() { return o_; } Node* len() { return len_; } Node* callbackfn() { return callbackfn_; } Node* this_arg() { return this_arg_; } Node* k() { return k_.value(); } Node* a() { return a_.value(); } void InitIteratingArrayBuiltinBody(Node* context, Node* receiver, Node* callbackfn, Node* this_arg, Node* new_target) { context_ = context; receiver_ = receiver; new_target_ = new_target; callbackfn_ = callbackfn; this_arg_ = this_arg; } void GenerateIteratingArrayBuiltinBody( const char* name, const BuiltinResultGenerator& generator, const CallResultProcessor& processor, const PostLoopAction& action, const Callable& slow_case_continuation, ForEachDirection direction = ForEachDirection::kForward) { Label non_array(this), slow(this, {&k_, &a_, &to_}), 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(WordEqual(receiver(), NullConstant()), &throw_null_undefined_exception); GotoIf(WordEqual(receiver(), UndefinedConstant()), &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_ = CallStub(CodeFactory::ToObject(isolate()), 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( CallStub(CodeFactory::ToLength(isolate()), 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); { CallRuntime( Runtime::kThrowTypeError, context(), SmiConstant(MessageTemplate::kCalledOnNullOrUndefined), HeapConstant(isolate()->factory()->NewStringFromAsciiChecked(name))); Unreachable(); } BIND(&type_exception); { CallRuntime(Runtime::kThrowTypeError, context(), SmiConstant(MessageTemplate::kCalledNonCallable), callbackfn()); Unreachable(); } 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())); } a_.Bind(generator(this)); HandleFastElements(processor, action, &slow, direction); BIND(&slow); Node* target = LoadFromFrame(StandardFrameConstants::kFunctionOffset, MachineType::TaggedPointer()); TailCallStub( slow_case_continuation, context(), target, new_target(), Int32Constant(IteratingArrayBuiltinLoopContinuationDescriptor::kArity), receiver(), callbackfn(), this_arg(), a_.value(), o(), k_.value(), len(), to_.value()); } void 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; a_.Bind(a); k_.Bind(initial_k); o_ = o; len_ = len; to_.Bind(to); } void GenerateIteratingTypedArrayBuiltinBody( const char* name, const BuiltinResultGenerator& generator, const CallResultProcessor& processor, const PostLoopAction& action, ForEachDirection direction = ForEachDirection::kForward) { Node* name_string = HeapConstant(isolate()->factory()->NewStringFromAsciiChecked(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); { CallRuntime(Runtime::kThrowTypeError, context_, SmiConstant(MessageTemplate::kNotTypedArray)); Unreachable(); } BIND(&throw_detached); { CallRuntime(Runtime::kThrowTypeError, context_, SmiConstant(MessageTemplate::kDetachedOperation), name_string); Unreachable(); } BIND(&throw_not_callable); { CallRuntime(Runtime::kThrowTypeError, context_, SmiConstant(MessageTemplate::kCalledNonCallable), callbackfn_); Unreachable(); } 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