Commit d3f74c98 authored by Benedikt Meurer's avatar Benedikt Meurer Committed by Commit Bot

[turbofan] Enable loop peeling for various higher-order Array builtins.

This adds appropriate LoopExit nodes for the JSCallReducer lowerings of
the following higher order Array builtins:

  - Array.prototype.every()
  - Array.prototype.find()
  - Array.prototype.findIndex()
  - Array.prototype.some()

Loop peeling allows TurboFan to make loop invariant operations in the
callback passed to the higher order builtin fully redundant, and thus
completely eliminate the loop invariant code from the subsequent loop
iterations. This can have a huge performance impact, depending on what
kind of code runs inside of the callback. For example, on the micro-
benchmarks outlined in http://crbug.com/v8/8273 we go from

  forLoop: 364 ms.
  every: 443 ms.
  some: 432 ms.
  find: 522 ms.
  findIndex: 437 ms.

to

  forLoop: 369 ms.
  every: 354 ms.
  some: 348 ms.
  find: 419 ms.
  findIndex: 360 ms.

which is 20% improvement, and essentially brings the Array builtins (the
appropriate ones Array#some() and Array#every() in this case) on par
with the hand-written `for`-loop.

Bug: v8:1956, v8:8273
Change-Id: I9d32736e5402807b4ac79cd5ad15ceacd1945681
Reviewed-on: https://chromium-review.googlesource.com/c/1305935Reviewed-by: 's avatarDaniel Clifford <danno@chromium.org>
Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
Cr-Commit-Position: refs/heads/master@{#57110}
parent 738c2a9f
......@@ -1187,6 +1187,11 @@ Reduction JSCallReducer::ReduceArrayForEach(Node* node,
control = if_false;
effect = eloop;
// Introduce proper LoopExit and LoopExitEffect nodes to mark
// {loop} as a candidate for loop peeling (crbug.com/v8/8273).
control = graph()->NewNode(common()->LoopExit(), control, loop);
effect = graph()->NewNode(common()->LoopExitEffect(), effect, control);
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
......@@ -2119,10 +2124,16 @@ Reduction JSCallReducer::ReduceArrayFind(Node* node, ArrayFindVariant variant,
Node* if_not_found_value = (variant == ArrayFindVariant::kFind)
? jsgraph()->UndefinedConstant()
: jsgraph()->MinusOneConstant();
Node* return_value =
Node* value =
graph()->NewNode(common()->Phi(MachineRepresentation::kTagged, 2),
if_found_return_value, if_not_found_value, control);
// Introduce proper LoopExit/LoopExitEffect/LoopExitValue to mark
// {loop} as a candidate for loop peeling (crbug.com/v8/8273).
control = graph()->NewNode(common()->LoopExit(), control, loop);
effect = graph()->NewNode(common()->LoopExitEffect(), effect, control);
value = graph()->NewNode(common()->LoopExitValue(), value, control);
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
......@@ -2131,8 +2142,8 @@ Reduction JSCallReducer::ReduceArrayFind(Node* node, ArrayFindVariant variant,
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, return_value, effect, control);
return Replace(return_value);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Node* JSCallReducer::DoFilterPostCallbackWork(ElementsKind kind, Node** control,
......@@ -2463,10 +2474,16 @@ Reduction JSCallReducer::ReduceArrayEvery(Node* node,
control = graph()->NewNode(common()->Merge(2), if_false, if_false_callback);
effect =
graph()->NewNode(common()->EffectPhi(2), eloop, efalse_callback, control);
Node* return_value = graph()->NewNode(
Node* value = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2),
jsgraph()->TrueConstant(), jsgraph()->FalseConstant(), control);
// Introduce proper LoopExit/LoopExitEffect/LoopExitValue to mark
// {loop} as a candidate for loop peeling (crbug.com/v8/8273).
control = graph()->NewNode(common()->LoopExit(), control, loop);
effect = graph()->NewNode(common()->LoopExitEffect(), effect, control);
value = graph()->NewNode(common()->LoopExitValue(), value, control);
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
......@@ -2475,8 +2492,8 @@ Reduction JSCallReducer::ReduceArrayEvery(Node* node,
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, return_value, effect, control);
return Replace(return_value);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
namespace {
......@@ -2812,10 +2829,16 @@ Reduction JSCallReducer::ReduceArraySome(Node* node,
control = graph()->NewNode(common()->Merge(2), if_false, if_true_callback);
effect =
graph()->NewNode(common()->EffectPhi(2), eloop, etrue_callback, control);
Node* return_value = graph()->NewNode(
Node* value = graph()->NewNode(
common()->Phi(MachineRepresentation::kTagged, 2),
jsgraph()->FalseConstant(), jsgraph()->TrueConstant(), control);
// Introduce proper LoopExit/LoopExitEffect/LoopExitValue to mark
// {loop} as a candidate for loop peeling (crbug.com/v8/8273).
control = graph()->NewNode(common()->LoopExit(), control, loop);
effect = graph()->NewNode(common()->LoopExitEffect(), effect, control);
value = graph()->NewNode(common()->LoopExitValue(), value, control);
// Wire up the branch for the case when IsCallable fails for the callback.
// Since {check_throw} is an unconditional throw, it's impossible to
// return a successful completion. Therefore, we simply connect the successful
......@@ -2824,8 +2847,8 @@ Reduction JSCallReducer::ReduceArraySome(Node* node,
graph()->NewNode(common()->Throw(), check_throw, check_fail);
NodeProperties::MergeControlToEnd(graph(), common(), throw_node);
ReplaceWithValue(node, return_value, effect, control);
return Replace(return_value);
ReplaceWithValue(node, value, effect, control);
return Replace(value);
}
Reduction JSCallReducer::ReduceCallApiFunction(
......
// Copyright 2018 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
// Basic loop peeling test case with Array.prototype.every().
(function() {
function foo(a, o) {
return a.every(x => x === o.x);
}
assertTrue(foo([3, 3, 3], {x:3}));
assertFalse(foo([3, 3, 2], {x:3}));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo([3, 3, 3], {x:3}));
assertFalse(foo([3, 3, 2], {x:3}));
})();
// Copyright 2018 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
// Basic loop peeling test case with Array.prototype.find().
(function() {
function foo(a, o) {
return a.find(x => x === o.x);
}
assertEquals(3, foo([1, 2, 3], {x:3}));
assertEquals(undefined, foo([0, 1, 2], {x:3}));
%OptimizeFunctionOnNextCall(foo);
assertEquals(3, foo([1, 2, 3], {x:3}));
assertEquals(undefined, foo([0, 1, 2], {x:3}));
})();
// Copyright 2018 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
// Basic loop peeling test case with Array.prototype.findIndex().
(function() {
function foo(a, o) {
return a.findIndex(x => x === o.x);
}
assertEquals(2, foo([1, 2, 3], {x:3}));
assertEquals(-1, foo([0, 1, 2], {x:3}));
%OptimizeFunctionOnNextCall(foo);
assertEquals(2, foo([1, 2, 3], {x:3}));
assertEquals(-1, foo([0, 1, 2], {x:3}));
})();
// Copyright 2018 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
// Basic loop peeling test case with Array.prototype.some().
(function() {
function foo(a, o) {
return a.some(x => x === o.x);
}
assertTrue(foo([1, 2, 3], {x:3}));
assertFalse(foo([0, 1, 2], {x:3}));
%OptimizeFunctionOnNextCall(foo);
assertTrue(foo([1, 2, 3], {x:3}));
assertFalse(foo([0, 1, 2], {x: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