Commit 99100db4 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Unfold bound functions at call sites.

So far the JSCallReducer was only able to unfold constant
JSBoundFunction targets for JSCall nodes, which is not the
common case. With the introduction of JSCreateBoundFunction
operator earlier, we can now also recognize calls to bound
functions where the bind happens earlier in the function,
i.e. as the example of

  a.map(f.bind(self))

in https://twitter.com/BenLesh/status/920700003974123520, which
is a handy way to use Function#bind. So this transformation
takes a node like

  JSCall(JSCreateBoundFunction(bound_target_function,
                               bound_this,
                               a1,...,aN),
         receiver, p1,...,pM)

and turns that into

  JSCall(bound_target_function, bound_this, a1,...,aN,p1,...,pM)

allowing TurboFan to further inline the bound_target_function
at this call site if that's also inlinable (i.e. it's a known
constant JSFunction or the result of a JSCreateClosure call).

This improves the micro-benchmark from

  arrowCall: 55 ms.
  boundCall: 221 ms.
  arrowMap: 181 ms.
  boundMap: 806 ms.

to

  arrowCall: 71 ms.
  boundCall: 76 ms.
  arrowMap: 188 ms.
  boundMap: 186 ms.

so that Function#bind in this case is as fast as using closures,
which is an up to 4.3x improvement in the Array#map example.

Bug: v8:5257, v8:6961
Change-Id: Ibca650faad912bf9db1db6fbc48772e7551289a6
Reviewed-on: https://chromium-review.googlesource.com/727799Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48713}
parent ef2036a4
......@@ -1823,6 +1823,8 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
Node* target = NodeProperties::GetValueInput(node, 0);
Node* control = NodeProperties::GetControlInput(node);
Node* effect = NodeProperties::GetEffectInput(node);
size_t arity = p.arity();
DCHECK_LE(2u, arity);
// Try to specialize JSCall {node}s with constant {target}s.
HeapObjectMatcher m(target);
......@@ -1902,13 +1904,10 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
Handle<Object> bound_this(function->bound_this(), isolate());
Handle<FixedArray> bound_arguments(function->bound_arguments(),
isolate());
CallParameters const& p = CallParametersOf(node->op());
ConvertReceiverMode const convert_mode =
(bound_this->IsNullOrUndefined(isolate()))
? ConvertReceiverMode::kNullOrUndefined
: ConvertReceiverMode::kNotNullOrUndefined;
size_t arity = p.arity();
DCHECK_LE(2u, arity);
// Patch {node} to use [[BoundTargetFunction]] and [[BoundThis]].
NodeProperties::ReplaceValueInput(
node, jsgraph()->Constant(bound_target_function), 0);
......@@ -1934,6 +1933,40 @@ Reduction JSCallReducer::ReduceJSCall(Node* node) {
return NoChange();
}
// If {target} is the result of a JSCreateBoundFunction operation,
// we can just fold the construction and call the bound target
// function directly instead.
if (target->opcode() == IrOpcode::kJSCreateBoundFunction) {
Node* bound_target_function = NodeProperties::GetValueInput(target, 0);
Node* bound_this = NodeProperties::GetValueInput(target, 1);
int const bound_arguments_length =
static_cast<int>(CreateBoundFunctionParametersOf(target->op()).arity());
// Patch the {node} to use [[BoundTargetFunction]] and [[BoundThis]].
NodeProperties::ReplaceValueInput(node, bound_target_function, 0);
NodeProperties::ReplaceValueInput(node, bound_this, 1);
// Insert the [[BoundArguments]] for {node}.
for (int i = 0; i < bound_arguments_length; ++i) {
Node* value = NodeProperties::GetValueInput(target, 2 + i);
node->InsertInput(graph()->zone(), 2 + i, value);
arity++;
}
// Update the JSCall operator on {node}.
ConvertReceiverMode const convert_mode =
CanBeNullOrUndefined(bound_this)
? ConvertReceiverMode::kAny
: ConvertReceiverMode::kNotNullOrUndefined;
NodeProperties::ChangeOp(
node, javascript()->Call(arity, p.frequency(), VectorSlotPair(),
convert_mode));
// Try to further reduce the JSCall {node}.
Reduction const reduction = ReduceJSCall(node);
return reduction.Changed() ? reduction : Changed(node);
}
// Extract feedback from the {node} using the CallICNexus.
if (!p.feedback().IsValid()) return NoChange();
CallICNexus nexus(p.feedback().vector(), p.feedback().slot());
......
......@@ -75,3 +75,27 @@
assertEquals(0, foo(0, 1).length);
assertEquals("bound bar", foo(1, 2).name)
})();
(function() {
function bar(f) { return f(1); }
function foo(g) { return bar(g.bind(null, 2)); }
assertEquals(3, foo((x, y) => x + y));
assertEquals(1, foo((x, y) => x - y));
%OptimizeFunctionOnNextCall(foo);
assertEquals(3, foo((x, y) => x + y));
assertEquals(1, foo((x, y) => x - y));
})();
(function() {
function add(x, y) { return x + y; }
function foo(a) { return a.map(add.bind(null, 1)); }
assertEquals([1, 2, 3], foo([0, 1, 2]));
assertEquals([2, 3, 4], foo([1, 2, 3]));
%OptimizeFunctionOnNextCall(foo);
assertEquals([1, 2, 3], foo([0, 1, 2]));
assertEquals([2, 3, 4], foo([1, 2, 3]));
})();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment