Commit 1c555714 authored by bmeurer's avatar bmeurer Committed by Commit Bot

[turbofan] Further optimize spread/apply with arguments/rest parameters.

Extend the use list check for the arguments object/rest parameters
during apply/spread optimization to allow for more cases, such that
even in code like

  function foo() {
    if (arguments.length === 1) return arguments[0];
    return bar.apply(this, arguments);
  }

we don't need to materialize the arguments object. This obviously comes
with a phase ordering problem, which we resolve by introducing a
waitlist in the JSCallReducer, which contains the nodes that we should
check again after all the other reductions are done, and which might
then be reducible. This is not 100% ideal, but get's us closer to where
we want to be, and it's crucial to speed up Node core, especially the
event emitter.

BUG=v8:4551,v8:5511, v8:5726
R=petermarshall@chromium.org

Review-Url: https://codereview.chromium.org/2956233002
Cr-Commit-Position: refs/heads/master@{#46337}
parent f79b3d4e
......@@ -40,6 +40,23 @@ Reduction JSCallReducer::Reduce(Node* node) {
return NoChange();
}
void JSCallReducer::Finalize() {
// TODO(turbofan): This is not the best solution; ideally we would be able
// to teach the GraphReducer about arbitrary dependencies between different
// nodes, even if they don't show up in the use list of the other node.
std::set<Node*> const waitlist = std::move(waitlist_);
for (Node* node : waitlist) {
if (!node->IsDead()) {
Reduction const reduction = Reduce(node);
if (reduction.Changed()) {
Node* replacement = reduction.replacement();
if (replacement != node) {
Replace(node, replacement);
}
}
}
}
}
// ES6 section 22.1.1 The Array Constructor
Reduction JSCallReducer::ReduceArrayConstructor(Node* node) {
......@@ -686,6 +703,23 @@ Reduction JSCallReducer::ReduceCallApiFunction(
return Changed(node);
}
namespace {
// Check whether elements aren't mutated; we play it extremely safe here by
// explicitly checking that {node} is only used by {LoadField} or {LoadElement}.
bool IsSafeArgumentsElements(Node* node) {
for (Edge const edge : node->use_edges()) {
if (!NodeProperties::IsValueEdge(edge)) continue;
if (edge.from()->opcode() != IrOpcode::kLoadField &&
edge.from()->opcode() != IrOpcode::kLoadElement) {
return false;
}
}
return true;
}
} // namespace
Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
Node* node, int arity, CallFrequency const& frequency) {
DCHECK(node->opcode() == IrOpcode::kJSCallWithArrayLike ||
......@@ -704,19 +738,63 @@ Reduction JSCallReducer::ReduceCallOrConstructWithArrayLikeOrSpread(
// Check if {arguments_list} is an arguments object, and {node} is the only
// value user of {arguments_list} (except for value uses in frame states).
Node* arguments_list = NodeProperties::GetValueInput(node, arity);
if (arguments_list->opcode() != IrOpcode::kJSCreateArguments)
if (arguments_list->opcode() != IrOpcode::kJSCreateArguments) {
return NoChange();
}
for (Edge edge : arguments_list->use_edges()) {
if (!NodeProperties::IsValueEdge(edge)) continue;
Node* const user = edge.from();
if (user == node) continue;
// Ignore uses as frame state's locals or parameters.
if (user->opcode() == IrOpcode::kStateValues) continue;
// Ignore uses as frame state's accumulator.
if (user->opcode() == IrOpcode::kFrameState &&
user->InputAt(2) == arguments_list) {
continue;
switch (user->opcode()) {
case IrOpcode::kCheckMaps:
case IrOpcode::kFrameState:
case IrOpcode::kStateValues:
case IrOpcode::kReferenceEqual:
case IrOpcode::kReturn:
// Ignore safe uses that definitely don't mess with the arguments.
continue;
case IrOpcode::kLoadField: {
DCHECK_EQ(arguments_list, user->InputAt(0));
FieldAccess const& access = FieldAccessOf(user->op());
if (access.offset == JSArray::kLengthOffset) {
// Ignore uses for arguments#length.
STATIC_ASSERT(JSArray::kLengthOffset ==
JSArgumentsObject::kLengthOffset);
continue;
} else if (access.offset == JSObject::kElementsOffset) {
// Ignore safe uses for arguments#elements.
if (IsSafeArgumentsElements(user)) continue;
}
break;
}
case IrOpcode::kJSCallWithArrayLike:
// Ignore uses as argumentsList input to calls with array like.
if (user->InputAt(2) == arguments_list) continue;
break;
case IrOpcode::kJSConstructWithArrayLike:
// Ignore uses as argumentsList input to calls with array like.
if (user->InputAt(1) == arguments_list) continue;
break;
case IrOpcode::kJSCallWithSpread: {
// Ignore uses as spread input to calls with spread.
SpreadWithArityParameter p = SpreadWithArityParameterOf(user->op());
int const arity = static_cast<int>(p.arity() - 1);
if (user->InputAt(arity) == arguments_list) continue;
break;
}
case IrOpcode::kJSConstructWithSpread: {
// Ignore uses as spread input to construct with spread.
SpreadWithArityParameter p = SpreadWithArityParameterOf(user->op());
int const arity = static_cast<int>(p.arity() - 2);
if (user->InputAt(arity) == arguments_list) continue;
break;
}
default:
break;
}
if (!NodeProperties::IsValueEdge(edge)) continue;
// We cannot currently reduce the {node} to something better than what
// it already is, but we might be able to do something about the {node}
// later, so put it on the waitlist and try again during finalization.
waitlist_.insert(node);
return NoChange();
}
......
......@@ -43,6 +43,10 @@ class JSCallReducer final : public AdvancedReducer {
Reduction Reduce(Node* node) final;
// Processes the waitlist gathered while the reducer was running,
// and does a final attempt to reduce the nodes in the waitlist.
void Finalize() final;
private:
Reduction ReduceArrayConstructor(Node* node);
Reduction ReduceBooleanConstructor(Node* node);
......@@ -86,6 +90,7 @@ class JSCallReducer final : public AdvancedReducer {
Flags const flags_;
Handle<Context> const native_context_;
CompilationDependencies* const dependencies_;
std::set<Node*> waitlist_;
};
} // namespace compiler
......
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