Skip to content

Commit

Permalink
[turbofan] Optimize Function.prototype.bind for the common case.
Browse files Browse the repository at this point in the history
When the input to Function.prototype.bind is a known function, we can
inline the allocation of the JSBoundFunction into TurboFan, which
provides a 2x speed-up for several hot functions in Node streams (as
discovered by Matteo Collina). One of example of this can be found in
nodejs/node#13322, which can be optimized and
made more readable using bind instead of closures.

[email protected]

Review-Url: https://codereview.chromium.org/2916063002
Cr-Commit-Position: refs/heads/master@{#45679}
  • Loading branch information
bmeurer authored and Commit Bot committed Jun 2, 2017
1 parent 1e813e5 commit 3028f80
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 0 deletions.
29 changes: 29 additions & 0 deletions src/compiler/access-builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,35 @@ FieldAccess AccessBuilder::ForJSFunctionNextFunctionLink() {
return access;
}

// static
FieldAccess AccessBuilder::ForJSBoundFunctionBoundTargetFunction() {
FieldAccess access = {
kTaggedBase, JSBoundFunction::kBoundTargetFunctionOffset,
Handle<Name>(), MaybeHandle<Map>(),
Type::Callable(), MachineType::TaggedPointer(),
kPointerWriteBarrier};
return access;
}

// static
FieldAccess AccessBuilder::ForJSBoundFunctionBoundThis() {
FieldAccess access = {kTaggedBase, JSBoundFunction::kBoundThisOffset,
Handle<Name>(), MaybeHandle<Map>(),
Type::NonInternal(), MachineType::AnyTagged(),
kFullWriteBarrier};
return access;
}

// static
FieldAccess AccessBuilder::ForJSBoundFunctionBoundArguments() {
FieldAccess access = {
kTaggedBase, JSBoundFunction::kBoundArgumentsOffset,
Handle<Name>(), MaybeHandle<Map>(),
Type::Internal(), MachineType::TaggedPointer(),
kPointerWriteBarrier};
return access;
}

// static
FieldAccess AccessBuilder::ForJSGeneratorObjectContext() {
FieldAccess access = {kTaggedBase, JSGeneratorObject::kContextOffset,
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/access-builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
110 changes: 110 additions & 0 deletions src/compiler/js-builtin-reducer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<JSFunction> target_function =
Handle<JSFunction>::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<Object> prototype(target_function->map()->prototype(), isolate());

// Setup the map for the JSBoundFunction instance.
Handle<Map> 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);
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/compiler/js-builtin-reducer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/typer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
1 change: 1 addition & 0 deletions src/objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
77 changes: 77 additions & 0 deletions test/mjsunit/compiler/function-bind.js
Original file line number Diff line number Diff line change
@@ -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)
})();

0 comments on commit 3028f80

Please sign in to comment.