| // 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/codegen/code-stub-assembler.h" |
| #include "src/execution/frame-constants.h" |
| #include "src/objects/api-callbacks.h" |
| #include "src/objects/descriptor-array.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| TF_BUILTIN(FastFunctionPrototypeBind, CodeStubAssembler) { |
| Label slow(this); |
| |
| // TODO(ishell): use constants from Descriptor once the JSFunction linkage |
| // arguments are reordered. |
| Node* argc = Parameter(Descriptor::kJSActualArgumentsCount); |
| Node* context = Parameter(Descriptor::kContext); |
| Node* new_target = Parameter(Descriptor::kJSNewTarget); |
| |
| CodeStubArguments args(this, ChangeInt32ToIntPtr(argc)); |
| |
| // Check that receiver has instance type of JS_FUNCTION_TYPE |
| Node* receiver = args.GetReceiver(); |
| GotoIf(TaggedIsSmi(receiver), &slow); |
| |
| Node* receiver_map = LoadMap(receiver); |
| { |
| Node* instance_type = LoadMapInstanceType(receiver_map); |
| GotoIfNot( |
| Word32Or(InstanceTypeEqual(instance_type, JS_FUNCTION_TYPE), |
| InstanceTypeEqual(instance_type, JS_BOUND_FUNCTION_TYPE)), |
| &slow); |
| } |
| |
| // Disallow binding of slow-mode functions. We need to figure out whether the |
| // length and name property are in the original state. |
| Comment("Disallow binding of slow-mode functions"); |
| GotoIf(IsDictionaryMap(receiver_map), &slow); |
| |
| // Check whether the length and name properties are still present as |
| // AccessorInfo objects. In that case, their value can be recomputed even if |
| // the actual value on the object changes. |
| Comment("Check descriptor array length"); |
| TNode<DescriptorArray> descriptors = LoadMapDescriptors(receiver_map); |
| // Minimum descriptor array length required for fast path. |
| const int min_nof_descriptors = i::Max(JSFunction::kLengthDescriptorIndex, |
| JSFunction::kNameDescriptorIndex); |
| TNode<Int32T> nof_descriptors = LoadNumberOfDescriptors(descriptors); |
| GotoIf( |
| Int32LessThanOrEqual(nof_descriptors, Int32Constant(min_nof_descriptors)), |
| &slow); |
| |
| // Check whether the length and name properties are still present as |
| // AccessorInfo objects. In that case, their value can be recomputed even if |
| // the actual value on the object changes. |
| Comment("Check name and length properties"); |
| { |
| const int length_index = JSFunction::kLengthDescriptorIndex; |
| TNode<Name> maybe_length = |
| LoadKeyByDescriptorEntry(descriptors, length_index); |
| GotoIf(WordNotEqual(maybe_length, LoadRoot(RootIndex::klength_string)), |
| &slow); |
| |
| TNode<Object> maybe_length_accessor = |
| LoadValueByDescriptorEntry(descriptors, length_index); |
| GotoIf(TaggedIsSmi(maybe_length_accessor), &slow); |
| Node* length_value_map = LoadMap(CAST(maybe_length_accessor)); |
| GotoIfNot(IsAccessorInfoMap(length_value_map), &slow); |
| |
| const int name_index = JSFunction::kNameDescriptorIndex; |
| TNode<Name> maybe_name = LoadKeyByDescriptorEntry(descriptors, name_index); |
| GotoIf(WordNotEqual(maybe_name, LoadRoot(RootIndex::kname_string)), &slow); |
| |
| TNode<Object> maybe_name_accessor = |
| LoadValueByDescriptorEntry(descriptors, name_index); |
| GotoIf(TaggedIsSmi(maybe_name_accessor), &slow); |
| TNode<Map> name_value_map = LoadMap(CAST(maybe_name_accessor)); |
| GotoIfNot(IsAccessorInfoMap(name_value_map), &slow); |
| } |
| |
| // Choose the right bound function map based on whether the target is |
| // constructable. |
| Comment("Choose the right bound function map"); |
| VARIABLE(bound_function_map, MachineRepresentation::kTagged); |
| { |
| Label with_constructor(this); |
| VariableList vars({&bound_function_map}, zone()); |
| Node* native_context = LoadNativeContext(context); |
| |
| Label map_done(this, vars); |
| GotoIf(IsConstructorMap(receiver_map), &with_constructor); |
| |
| bound_function_map.Bind(LoadContextElement( |
| native_context, Context::BOUND_FUNCTION_WITHOUT_CONSTRUCTOR_MAP_INDEX)); |
| Goto(&map_done); |
| |
| BIND(&with_constructor); |
| bound_function_map.Bind(LoadContextElement( |
| native_context, Context::BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX)); |
| Goto(&map_done); |
| |
| BIND(&map_done); |
| } |
| |
| // Verify that __proto__ matches that of a the target bound function. |
| Comment("Verify that __proto__ matches target bound function"); |
| Node* prototype = LoadMapPrototype(receiver_map); |
| Node* expected_prototype = LoadMapPrototype(bound_function_map.value()); |
| GotoIf(WordNotEqual(prototype, expected_prototype), &slow); |
| |
| // Allocate the arguments array. |
| Comment("Allocate the arguments array"); |
| VARIABLE(argument_array, MachineRepresentation::kTagged); |
| { |
| Label empty_arguments(this); |
| Label arguments_done(this, &argument_array); |
| GotoIf(Uint32LessThanOrEqual(argc, Int32Constant(1)), &empty_arguments); |
| TNode<IntPtrT> elements_length = |
| Signed(ChangeUint32ToWord(Unsigned(Int32Sub(argc, Int32Constant(1))))); |
| TNode<FixedArray> elements = CAST(AllocateFixedArray( |
| PACKED_ELEMENTS, elements_length, kAllowLargeObjectAllocation)); |
| VARIABLE(index, MachineType::PointerRepresentation()); |
| index.Bind(IntPtrConstant(0)); |
| VariableList foreach_vars({&index}, zone()); |
| args.ForEach(foreach_vars, |
| [this, elements, &index](Node* arg) { |
| StoreFixedArrayElement(elements, index.value(), arg); |
| Increment(&index); |
| }, |
| IntPtrConstant(1)); |
| argument_array.Bind(elements); |
| Goto(&arguments_done); |
| |
| BIND(&empty_arguments); |
| argument_array.Bind(EmptyFixedArrayConstant()); |
| Goto(&arguments_done); |
| |
| BIND(&arguments_done); |
| } |
| |
| // Determine bound receiver. |
| Comment("Determine bound receiver"); |
| VARIABLE(bound_receiver, MachineRepresentation::kTagged); |
| { |
| Label has_receiver(this); |
| Label receiver_done(this, &bound_receiver); |
| GotoIf(Word32NotEqual(argc, Int32Constant(0)), &has_receiver); |
| bound_receiver.Bind(UndefinedConstant()); |
| Goto(&receiver_done); |
| |
| BIND(&has_receiver); |
| bound_receiver.Bind(args.AtIndex(0)); |
| Goto(&receiver_done); |
| |
| BIND(&receiver_done); |
| } |
| |
| // Allocate the resulting bound function. |
| Comment("Allocate the resulting bound function"); |
| { |
| Node* bound_function = Allocate(JSBoundFunction::kSize); |
| StoreMapNoWriteBarrier(bound_function, bound_function_map.value()); |
| StoreObjectFieldNoWriteBarrier( |
| bound_function, JSBoundFunction::kBoundTargetFunctionOffset, receiver); |
| StoreObjectFieldNoWriteBarrier(bound_function, |
| JSBoundFunction::kBoundThisOffset, |
| bound_receiver.value()); |
| StoreObjectFieldNoWriteBarrier(bound_function, |
| JSBoundFunction::kBoundArgumentsOffset, |
| argument_array.value()); |
| Node* empty_fixed_array = EmptyFixedArrayConstant(); |
| StoreObjectFieldNoWriteBarrier( |
| bound_function, JSObject::kPropertiesOrHashOffset, empty_fixed_array); |
| StoreObjectFieldNoWriteBarrier(bound_function, JSObject::kElementsOffset, |
| empty_fixed_array); |
| |
| args.PopAndReturn(bound_function); |
| } |
| |
| BIND(&slow); |
| { |
| // 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::kFunctionPrototypeBind, context, target, |
| new_target, argc); |
| } |
| } |
| |
| // ES6 #sec-function.prototype-@@hasinstance |
| TF_BUILTIN(FunctionPrototypeHasInstance, CodeStubAssembler) { |
| Node* context = Parameter(Descriptor::kContext); |
| Node* f = Parameter(Descriptor::kReceiver); |
| Node* v = Parameter(Descriptor::kV); |
| Node* result = OrdinaryHasInstance(context, f, v); |
| Return(result); |
| } |
| |
| } // namespace internal |
| } // namespace v8 |