diff --git a/src/compiler/access-builder.cc b/src/compiler/access-builder.cc index c7b85255b299..f24c9dbe2bff 100644 --- a/src/compiler/access-builder.cc +++ b/src/compiler/access-builder.cc @@ -170,6 +170,35 @@ FieldAccess AccessBuilder::ForJSFunctionNextFunctionLink() { return access; } +// static +FieldAccess AccessBuilder::ForJSBoundFunctionBoundTargetFunction() { + FieldAccess access = { + kTaggedBase, JSBoundFunction::kBoundTargetFunctionOffset, + Handle(), MaybeHandle(), + Type::Callable(), MachineType::TaggedPointer(), + kPointerWriteBarrier}; + return access; +} + +// static +FieldAccess AccessBuilder::ForJSBoundFunctionBoundThis() { + FieldAccess access = {kTaggedBase, JSBoundFunction::kBoundThisOffset, + Handle(), MaybeHandle(), + Type::NonInternal(), MachineType::AnyTagged(), + kFullWriteBarrier}; + return access; +} + +// static +FieldAccess AccessBuilder::ForJSBoundFunctionBoundArguments() { + FieldAccess access = { + kTaggedBase, JSBoundFunction::kBoundArgumentsOffset, + Handle(), MaybeHandle(), + Type::Internal(), MachineType::TaggedPointer(), + kPointerWriteBarrier}; + return access; +} + // static FieldAccess AccessBuilder::ForJSGeneratorObjectContext() { FieldAccess access = {kTaggedBase, JSGeneratorObject::kContextOffset, diff --git a/src/compiler/access-builder.h b/src/compiler/access-builder.h index b4c3ed061588..eb8dd50198b6 100644 --- a/src/compiler/access-builder.h +++ b/src/compiler/access-builder.h @@ -73,6 +73,15 @@ class V8_EXPORT_PRIVATE AccessBuilder final // Provides access to JSFunction::next_function_link() field. static FieldAccess ForJSFunctionNextFunctionLink(); + // Provides access to JSBoundFunction::bound_target_function() field. + static FieldAccess ForJSBoundFunctionBoundTargetFunction(); + + // Provides access to JSBoundFunction::bound_this() field. + static FieldAccess ForJSBoundFunctionBoundThis(); + + // Provides access to JSBoundFunction::bound_arguments() field. + static FieldAccess ForJSBoundFunctionBoundArguments(); + // Provides access to JSGeneratorObject::context() field. static FieldAccess ForJSGeneratorObjectContext(); diff --git a/src/compiler/js-builtin-reducer.cc b/src/compiler/js-builtin-reducer.cc index 3d4e53378170..ba30f4e3767b 100644 --- a/src/compiler/js-builtin-reducer.cc +++ b/src/compiler/js-builtin-reducer.cc @@ -1252,6 +1252,114 @@ Reduction JSBuiltinReducer::ReduceDateGetTime(Node* node) { return NoChange(); } +// ES6 section 19.2.3.2 Function.prototype.bind ( thisArg, ...args ) +Reduction JSBuiltinReducer::ReduceFunctionBind(Node* node) { + // Value inputs to the {node} are as follows: + // + // - target, which is Function.prototype.bind JSFunction + // - receiver, which is the [[BoundTargetFunction]] + // - bound_this (optional), which is the [[BoundThis]] + // - and all the remaining value inouts are [[BoundArguments]] + Node* receiver = NodeProperties::GetValueInput(node, 1); + Type* receiver_type = NodeProperties::GetType(receiver); + Node* bound_this = (node->op()->ValueInputCount() < 3) + ? jsgraph()->UndefinedConstant() + : NodeProperties::GetValueInput(node, 2); + Node* effect = NodeProperties::GetEffectInput(node); + Node* control = NodeProperties::GetControlInput(node); + if (receiver_type->IsHeapConstant() && + receiver_type->AsHeapConstant()->Value()->IsJSFunction()) { + Handle target_function = + Handle::cast(receiver_type->AsHeapConstant()->Value()); + + // Check that the "length" property on the {target_function} is the + // default JSFunction accessor. + LookupIterator length_lookup(target_function, factory()->length_string(), + target_function, LookupIterator::OWN); + if (length_lookup.state() != LookupIterator::ACCESSOR || + !length_lookup.GetAccessors()->IsAccessorInfo()) { + return NoChange(); + } + + // Check that the "name" property on the {target_function} is the + // default JSFunction accessor. + LookupIterator name_lookup(target_function, factory()->name_string(), + target_function, LookupIterator::OWN); + if (name_lookup.state() != LookupIterator::ACCESSOR || + !name_lookup.GetAccessors()->IsAccessorInfo()) { + return NoChange(); + } + + // Determine the prototype of the {target_function}. + Handle prototype(target_function->map()->prototype(), isolate()); + + // Setup the map for the JSBoundFunction instance. + Handle map = target_function->IsConstructor() + ? isolate()->bound_function_with_constructor_map() + : isolate()->bound_function_without_constructor_map(); + if (map->prototype() != *prototype) { + map = Map::TransitionToPrototype(map, prototype, REGULAR_PROTOTYPE); + } + DCHECK_EQ(target_function->IsConstructor(), map->is_constructor()); + + // Create the [[BoundArguments]] for the result. + Node* bound_arguments = jsgraph()->EmptyFixedArrayConstant(); + if (node->op()->ValueInputCount() > 3) { + int const length = node->op()->ValueInputCount() - 3; + effect = graph()->NewNode( + common()->BeginRegion(RegionObservability::kNotObservable), effect); + bound_arguments = effect = graph()->NewNode( + simplified()->Allocate(Type::OtherInternal(), NOT_TENURED), + jsgraph()->Constant(FixedArray::SizeFor(length)), effect, control); + effect = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForMap()), bound_arguments, + jsgraph()->FixedArrayMapConstant(), effect, control); + effect = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForFixedArrayLength()), + bound_arguments, jsgraph()->Constant(length), effect, control); + for (int i = 0; i < length; ++i) { + effect = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForFixedArraySlot(i)), + bound_arguments, NodeProperties::GetValueInput(node, 3 + i), effect, + control); + } + bound_arguments = effect = + graph()->NewNode(common()->FinishRegion(), bound_arguments, effect); + } + + // Create the JSBoundFunction result. + effect = graph()->NewNode( + common()->BeginRegion(RegionObservability::kNotObservable), effect); + Node* value = effect = graph()->NewNode( + simplified()->Allocate(Type::BoundFunction(), NOT_TENURED), + jsgraph()->Constant(JSBoundFunction::kSize), effect, control); + effect = graph()->NewNode(simplified()->StoreField(AccessBuilder::ForMap()), + value, jsgraph()->Constant(map), effect, control); + effect = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForJSObjectProperties()), value, + jsgraph()->EmptyFixedArrayConstant(), effect, control); + effect = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForJSObjectElements()), value, + jsgraph()->EmptyFixedArrayConstant(), effect, control); + effect = graph()->NewNode( + simplified()->StoreField( + AccessBuilder::ForJSBoundFunctionBoundTargetFunction()), + value, receiver, effect, control); + effect = graph()->NewNode( + simplified()->StoreField(AccessBuilder::ForJSBoundFunctionBoundThis()), + value, bound_this, effect, control); + effect = + graph()->NewNode(simplified()->StoreField( + AccessBuilder::ForJSBoundFunctionBoundArguments()), + value, bound_arguments, effect, control); + value = effect = graph()->NewNode(common()->FinishRegion(), value, effect); + + ReplaceWithValue(node, value, effect, control); + return Replace(value); + } + return NoChange(); +} + // ES6 section 18.2.2 isFinite ( number ) Reduction JSBuiltinReducer::ReduceGlobalIsFinite(Node* node) { JSCallReduction r(node); @@ -2326,6 +2434,8 @@ Reduction JSBuiltinReducer::Reduce(Node* node) { return ReduceDateNow(node); case kDateGetTime: return ReduceDateGetTime(node); + case kFunctionBind: + return ReduceFunctionBind(node); case kGlobalIsFinite: reduction = ReduceGlobalIsFinite(node); break; diff --git a/src/compiler/js-builtin-reducer.h b/src/compiler/js-builtin-reducer.h index 736ece34e4f5..66462d803e16 100644 --- a/src/compiler/js-builtin-reducer.h +++ b/src/compiler/js-builtin-reducer.h @@ -61,6 +61,7 @@ class V8_EXPORT_PRIVATE JSBuiltinReducer final Reduction ReduceArrayShift(Node* node); Reduction ReduceDateNow(Node* node); Reduction ReduceDateGetTime(Node* node); + Reduction ReduceFunctionBind(Node* node); Reduction ReduceGlobalIsFinite(Node* node); Reduction ReduceGlobalIsNaN(Node* node); Reduction ReduceMathAbs(Node* node); diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc index a6d5e2f27cb8..b90e1fe12ed9 100644 --- a/src/compiler/typer.cc +++ b/src/compiler/typer.cc @@ -1513,6 +1513,8 @@ Type* Typer::Visitor::JSCallTyper(Type* fun, Typer* t) { return Type::String(); // Function functions. + case kFunctionBind: + return Type::BoundFunction(); case kFunctionHasInstance: return Type::Boolean(); diff --git a/src/objects.h b/src/objects.h index 486873f6f580..9f0cafb7887d 100644 --- a/src/objects.h +++ b/src/objects.h @@ -4683,6 +4683,7 @@ class ContextExtension : public Struct { V(Date.prototype, getSeconds, DateGetSeconds) \ V(Date.prototype, getTime, DateGetTime) \ V(Function.prototype, apply, FunctionApply) \ + V(Function.prototype, bind, FunctionBind) \ V(Function.prototype, call, FunctionCall) \ V(Object, assign, ObjectAssign) \ V(Object, create, ObjectCreate) \ diff --git a/test/mjsunit/compiler/function-bind.js b/test/mjsunit/compiler/function-bind.js new file mode 100644 index 000000000000..11337b4bf9bd --- /dev/null +++ b/test/mjsunit/compiler/function-bind.js @@ -0,0 +1,77 @@ +// 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. + +// Flags: --allow-natives-syntax + +(function() { + "use strict"; + function bar() { return this; } + + function foo(x) { + return bar.bind(x); + } + + assertEquals(0, foo(0)()); + assertEquals(1, foo(1)()); + %OptimizeFunctionOnNextCall(foo); + assertEquals("", foo("")()); +})(); + +(function() { + "use strict"; + function bar(x) { return x; } + + function foo(x) { + return bar.bind(undefined, x); + } + + assertEquals(0, foo(0)()); + assertEquals(1, foo(1)()); + %OptimizeFunctionOnNextCall(foo); + assertEquals("", foo("")()); +})(); + +(function() { + function bar(x) { return x; } + + function foo(x) { + return bar.bind(undefined, x); + } + + assertEquals(0, foo(0)()); + assertEquals(1, foo(1)()); + %OptimizeFunctionOnNextCall(foo); + assertEquals("", foo("")()); +})(); + +(function() { + "use strict"; + function bar(x, y) { return x + y; } + + function foo(x, y) { + return bar.bind(undefined, x, y); + } + + assertEquals(0, foo(0, 0)()); + assertEquals(2, foo(1, 1)()); + %OptimizeFunctionOnNextCall(foo); + assertEquals("ab", foo("a", "b")()); + assertEquals(0, foo(0, 1).length); + assertEquals("bound bar", foo(1, 2).name) +})(); + +(function() { + function bar(x, y) { return x + y; } + + function foo(x, y) { + return bar.bind(undefined, x, y); + } + + assertEquals(0, foo(0, 0)()); + assertEquals(2, foo(1, 1)()); + %OptimizeFunctionOnNextCall(foo); + assertEquals("ab", foo("a", "b")()); + assertEquals(0, foo(0, 1).length); + assertEquals("bound bar", foo(1, 2).name) +})();