Commit 6fca2cfa authored by Michael Starzinger's avatar Michael Starzinger Committed by Commit Bot

[turbofan] Elide redundant {IfSuccess} control projections.

This changes the IR to no longer require single {IfSuccess} projection
nodes unless there is a corresponding {IfException} node that links the
potentially throwing call to an exception handler. This reduces graph
size as well as compilation time when exception handlers aren't present.

The new invariant for potentially throwing nodes is: Nodes that can
potentially throw either have both IfSuccess/IfException projections as
the only control uses and no direct control uses, or no projections at
all and solely direct control uses.

R=jarin@chromium.org

Change-Id: I3d9cd816d74ad5af13e0673da7ec7a98f1ecdc7e
Reviewed-on: https://chromium-review.googlesource.com/449715
Commit-Queue: Michael Starzinger <mstarzinger@chromium.org>
Reviewed-by: 's avatarJaroslav Sevcik <jarin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#43814}
parent 94b33165
......@@ -2930,12 +2930,6 @@ Node* AstGraphBuilder::MakeNode(const Operator* op, int value_input_count,
if (result->op()->EffectOutputCount() > 0) {
environment_->UpdateEffectDependency(result);
}
// Add implicit success continuation for throwing nodes.
if (!result->op()->HasProperty(Operator::kNoThrow)) {
const Operator* op = common()->IfSuccess();
Node* on_success = graph()->NewNode(op, result);
environment_->UpdateControlDependency(on_success);
}
}
}
......
......@@ -2425,7 +2425,7 @@ Node* BytecodeGraphBuilder::MakeNode(const Operator* op, int value_input_count,
set_environment(success_env);
}
// Add implicit success continuation for throwing nodes.
if (!result->op()->HasProperty(Operator::kNoThrow)) {
if (!result->op()->HasProperty(Operator::kNoThrow) && inside_handler) {
const Operator* if_success = common()->IfSuccess();
Node* on_success = graph()->NewNode(if_success, result);
environment()->UpdateControlDependency(on_success);
......
......@@ -484,22 +484,6 @@ void EffectControlLinearizer::Run() {
}
}
namespace {
void TryScheduleCallIfSuccess(Node* node, Node** control) {
// Schedule the call's IfSuccess node if there is no exception use.
if (!NodeProperties::IsExceptionalCall(node)) {
for (Edge edge : node->use_edges()) {
if (NodeProperties::IsControlEdge(edge) &&
edge.from()->opcode() == IrOpcode::kIfSuccess) {
*control = edge.from();
}
}
}
}
} // namespace
void EffectControlLinearizer::ProcessNode(Node* node, Node** frame_state,
Node** effect, Node** control) {
SourcePositionTable::Scope scope(source_positions_,
......@@ -583,13 +567,9 @@ void EffectControlLinearizer::ProcessNode(Node* node, Node** frame_state,
for (int i = 0; i < node->op()->ControlInputCount(); i++) {
NodeProperties::ReplaceControlInput(node, *control, i);
}
// Update the current control and wire IfSuccess right after calls.
// Update the current control.
if (node->op()->ControlOutputCount() > 0) {
*control = node;
if (node->opcode() == IrOpcode::kCall) {
// Schedule the call's IfSuccess node (if there is no exception use).
TryScheduleCallIfSuccess(node, control);
}
}
}
......
......@@ -792,19 +792,19 @@ Reduction JSBuiltinReducer::ReduceArrayIsArray(Node* node) {
control = graph()->NewNode(common()->IfTrue(), control);
// Let the %ArrayIsArray runtime function deal with the JSProxy {value}.
value = effect =
value = effect = control =
graph()->NewNode(javascript()->CallRuntime(Runtime::kArrayIsArray), value,
context, frame_state, effect, control);
NodeProperties::SetType(value, Type::Boolean());
control = graph()->NewNode(common()->IfSuccess(), value);
// Rewire any IfException edges on {node} to {value}.
for (Edge edge : node->use_edges()) {
Node* const user = edge.from();
if (user->opcode() == IrOpcode::kIfException) {
edge.UpdateTo(value);
Revisit(user);
}
// Update potential {IfException} uses of {node} to point to the above
// %ArrayIsArray runtime call node instead.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
NodeProperties::ReplaceControlInput(on_exception, control);
NodeProperties::ReplaceEffectInput(on_exception, effect);
control = graph()->NewNode(common()->IfSuccess(), control);
Revisit(on_exception);
}
// The {value} is neither a JSArray nor a JSProxy.
......
......@@ -646,8 +646,6 @@ Reduction JSCreateLowering::ReduceNewArrayToStubCall(
graph()->NewNode(common()->Branch(BranchHint::kFalse), equal, control);
Node* call_holey;
Node* call_packed;
Node* if_success_packed;
Node* if_success_holey;
Node* context = NodeProperties::GetContextInput(node);
Node* frame_state = NodeProperties::GetFrameStateInput(node);
Node* if_equal = graph()->NewNode(common()->IfTrue(), branch);
......@@ -671,7 +669,6 @@ Reduction JSCreateLowering::ReduceNewArrayToStubCall(
call_holey =
graph()->NewNode(common()->Call(desc), arraysize(inputs), inputs);
if_success_holey = graph()->NewNode(common()->IfSuccess(), call_holey);
}
Node* if_not_equal = graph()->NewNode(common()->IfFalse(), branch);
{
......@@ -695,10 +692,8 @@ Reduction JSCreateLowering::ReduceNewArrayToStubCall(
call_packed =
graph()->NewNode(common()->Call(desc), arraysize(inputs), inputs);
if_success_packed = graph()->NewNode(common()->IfSuccess(), call_packed);
}
Node* merge = graph()->NewNode(common()->Merge(2), if_success_holey,
if_success_packed);
Node* merge = graph()->NewNode(common()->Merge(2), call_holey, call_packed);
Node* effect_phi = graph()->NewNode(common()->EffectPhi(2), call_holey,
call_packed, merge);
Node* phi =
......
......@@ -654,21 +654,29 @@ void JSGenericLowering::LowerJSStackCheck(Node* node) {
Node* if_false = graph()->NewNode(common()->IfFalse(), branch);
NodeProperties::ReplaceControlInput(node, if_false);
Node* efalse = node;
Node* efalse = if_false = node;
Node* merge = graph()->NewNode(common()->Merge(2), if_true, if_false);
Node* ephi = graph()->NewNode(common()->EffectPhi(2), etrue, efalse, merge);
// Wire the new diamond into the graph, {node} can still throw.
NodeProperties::ReplaceUses(node, node, ephi, node, node);
NodeProperties::ReplaceUses(node, node, ephi, merge, merge);
NodeProperties::ReplaceControlInput(merge, if_false, 1);
NodeProperties::ReplaceEffectInput(ephi, efalse, 1);
// TODO(mstarzinger): This iteration cuts out the IfSuccess projection from
// the node and places it inside the diamond. Come up with a helper method!
for (Node* use : node->uses()) {
if (use->opcode() == IrOpcode::kIfSuccess) {
use->ReplaceUses(merge);
merge->ReplaceInput(1, use);
// This iteration cuts out potential {IfSuccess} or {IfException} projection
// uses of the original node and places them inside the diamond, so that we
// can change the original {node} into the slow-path runtime call.
for (Edge edge : merge->use_edges()) {
if (!NodeProperties::IsControlEdge(edge)) continue;
if (edge.from()->opcode() == IrOpcode::kIfSuccess) {
NodeProperties::ReplaceUses(edge.from(), nullptr, nullptr, merge);
NodeProperties::ReplaceControlInput(merge, edge.from(), 1);
edge.UpdateTo(node);
}
if (edge.from()->opcode() == IrOpcode::kIfException) {
NodeProperties::ReplaceEffectInput(edge.from(), node);
edge.UpdateTo(node);
}
}
......
......@@ -226,26 +226,21 @@ Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate) {
// to the known {target}); the last input is the control dependency.
inputs[0] = target;
inputs[input_count - 1] = if_successes[i];
calls[i] = graph()->NewNode(node->op(), input_count, inputs);
if_successes[i] = graph()->NewNode(common()->IfSuccess(), calls[i]);
calls[i] = if_successes[i] =
graph()->NewNode(node->op(), input_count, inputs);
}
// Check if we have an exception projection for the call {node}.
Node* if_exception = nullptr;
for (Edge const edge : node->use_edges()) {
if (NodeProperties::IsControlEdge(edge) &&
edge.from()->opcode() == IrOpcode::kIfException) {
if_exception = edge.from();
break;
}
}
if (if_exception != nullptr) {
// Morph the {if_exception} projection into a join.
if (NodeProperties::IsExceptionalCall(node, &if_exception)) {
Node* if_exceptions[kMaxCallPolymorphism + 1];
for (int i = 0; i < num_calls; ++i) {
if_successes[i] = graph()->NewNode(common()->IfSuccess(), calls[i]);
if_exceptions[i] =
graph()->NewNode(common()->IfException(), calls[i], calls[i]);
}
// Morph the {if_exception} projection into a join.
Node* exception_control =
graph()->NewNode(common()->Merge(num_calls), num_calls, if_exceptions);
if_exceptions[num_calls] = exception_control;
......@@ -258,7 +253,7 @@ Reduction JSInliningHeuristic::InlineCandidate(Candidate const& candidate) {
exception_control);
}
// Morph the call site into the dispatched call sites.
// Morph the original call site into a join of the dispatched call sites.
Node* control =
graph()->NewNode(common()->Merge(num_calls), num_calls, if_successes);
calls[num_calls] = control;
......
......@@ -141,12 +141,15 @@ Reduction JSInliner::InlineCall(Node* call, Node* new_target, Node* context,
int subcall_count = static_cast<int>(uncaught_subcalls.size());
if (subcall_count > 0) {
TRACE(
"Inlinee contains %d calls without IfException; "
"linking to existing IfException\n",
"Inlinee contains %d calls without local exception handler; "
"linking to surrounding exception handler\n",
subcall_count);
}
NodeVector on_exception_nodes(local_zone_);
for (Node* subcall : uncaught_subcalls) {
Node* on_success = graph()->NewNode(common()->IfSuccess(), subcall);
NodeProperties::ReplaceUses(subcall, subcall, subcall, on_success);
NodeProperties::ReplaceControlInput(on_success, subcall);
Node* on_exception =
graph()->NewNode(common()->IfException(), subcall, subcall);
on_exception_nodes.push_back(on_exception);
......@@ -578,24 +581,18 @@ Reduction JSInliner::ReduceJSCall(Node* node) {
end = graph()->end();
}
// If we are inlining into a surrounding exception handler, we collect all
// potentially throwing nodes within the inlinee that are not handled locally
// by the inlinee itself. They are later wired into the surrounding handler.
NodeVector uncaught_subcalls(local_zone_);
if (exception_target != nullptr) {
// Find all uncaught 'calls' in the inlinee.
AllNodes inlined_nodes(local_zone_, end, graph());
for (Node* subnode : inlined_nodes.reachable) {
// Every possibly throwing node with an IfSuccess should get an
// IfException.
if (subnode->op()->HasProperty(Operator::kNoThrow)) {
continue;
}
bool hasIfException = false;
for (Node* use : subnode->uses()) {
if (use->opcode() == IrOpcode::kIfException) {
hasIfException = true;
break;
}
}
if (!hasIfException) {
// Every possibly throwing node should get {IfSuccess} and {IfException}
// projections, unless there already is local exception handling.
if (subnode->op()->HasProperty(Operator::kNoThrow)) continue;
if (!NodeProperties::IsExceptionalCall(subnode)) {
DCHECK_EQ(2, subnode->op()->ControlOutputCount());
uncaught_subcalls.push_back(subnode);
}
......@@ -634,9 +631,8 @@ Reduction JSInliner::ReduceJSCall(Node* node) {
Node* create =
graph()->NewNode(javascript()->Create(), call.target(), new_target,
context, frame_state_inside, effect, control);
Node* success = graph()->NewNode(common()->IfSuccess(), create);
uncaught_subcalls.push_back(create); // Adds {IfException}.
NodeProperties::ReplaceControlInput(node, success);
uncaught_subcalls.push_back(create); // Adds {IfSuccess} & {IfException}.
NodeProperties::ReplaceControlInput(node, create);
NodeProperties::ReplaceEffectInput(node, create);
// Insert a check of the return value to determine whether the return
// value or the implicit receiver should be selected as a result of the
......
......@@ -1356,11 +1356,10 @@ JSNativeContextSpecialization::BuildPropertyAccess(
// Introduce the call to the getter function.
if (access_info.constant()->IsJSFunction()) {
value = effect = graph()->NewNode(
value = effect = control = graph()->NewNode(
javascript()->Call(2, 0.0f, VectorSlotPair(),
ConvertReceiverMode::kNotNullOrUndefined),
target, receiver, context, frame_state0, effect, control);
control = graph()->NewNode(common()->IfSuccess(), value);
} else {
DCHECK(access_info.constant()->IsFunctionTemplateInfo());
Handle<FunctionTemplateInfo> function_template_info(
......@@ -1393,11 +1392,10 @@ JSNativeContextSpecialization::BuildPropertyAccess(
// Introduce the call to the setter function.
if (access_info.constant()->IsJSFunction()) {
effect = graph()->NewNode(
effect = control = graph()->NewNode(
javascript()->Call(3, 0.0f, VectorSlotPair(),
ConvertReceiverMode::kNotNullOrUndefined),
target, receiver, value, context, frame_state0, effect, control);
control = graph()->NewNode(common()->IfSuccess(), effect);
} else {
DCHECK(access_info.constant()->IsFunctionTemplateInfo());
Handle<FunctionTemplateInfo> function_template_info(
......@@ -1662,7 +1660,6 @@ JSNativeContextSpecialization::BuildPropertyAccess(
value = effect = control =
graph()->NewNode(common()->Call(desc), arraysize(inputs), inputs);
control = graph()->NewNode(common()->IfSuccess(), control);
}
return ValueEffectControl(value, effect, control);
......@@ -2105,10 +2102,10 @@ JSNativeContextSpecialization::InlineApiCall(
inputs[6] = value;
}
Node* control0;
Node* effect0;
Node* value0 = effect0 =
Node* value0 = effect0 = control0 =
graph()->NewNode(common()->Call(call_descriptor), index, inputs);
Node* control0 = graph()->NewNode(common()->IfSuccess(), value0);
return ValueEffectControl(value0, effect0, control0);
}
......
......@@ -261,19 +261,7 @@ class JSBinopReduction final {
// Reconnect the control output to bypass the IfSuccess node and
// possibly disconnect from the IfException node.
for (Edge edge : node_->use_edges()) {
Node* const user = edge.from();
DCHECK(!user->IsDead());
if (NodeProperties::IsControlEdge(edge)) {
if (user->opcode() == IrOpcode::kIfSuccess) {
user->ReplaceUses(NodeProperties::GetControlInput(node_));
user->Kill();
} else {
DCHECK_EQ(user->opcode(), IrOpcode::kIfException);
edge.UpdateTo(jsgraph()->Dead());
}
}
}
lowering_->RelaxControls(node_);
// Remove the frame state and the context.
if (OperatorProperties::HasFrameStateInput(node_->op())) {
......@@ -414,9 +402,7 @@ class JSBinopReduction final {
DCHECK(!NodeProperties::GetType(node)->Is(Type::PlainPrimitive()));
Node* const n = graph()->NewNode(javascript()->ToNumber(), node, context(),
frame_state, effect(), control());
Node* const if_success = graph()->NewNode(common()->IfSuccess(), n);
NodeProperties::ReplaceControlInput(node_, if_success);
NodeProperties::ReplaceUses(node_, node_, node_, node_, n);
NodeProperties::ReplaceControlInput(node_, n);
update_effect(n);
return n;
}
......@@ -688,25 +674,27 @@ Reduction JSTypedLowering::ReduceCreateConsString(Node* node) {
Node* efalse = effect;
{
// Throw a RangeError in case of overflow.
Node* vfalse = efalse = graph()->NewNode(
Node* vfalse = efalse = if_false = graph()->NewNode(
javascript()->CallRuntime(Runtime::kThrowInvalidStringLength),
context, frame_state, efalse, if_false);
// Update potential {IfException} uses of {node} to point to the
// %ThrowInvalidStringLength runtime call node instead.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
NodeProperties::ReplaceControlInput(on_exception, vfalse);
NodeProperties::ReplaceEffectInput(on_exception, efalse);
if_false = graph()->NewNode(common()->IfSuccess(), vfalse);
Revisit(on_exception);
}
// The above %ThrowInvalidStringLength runtime call is an unconditional
// throw, making it impossible to return a successful completion in this
// case. We simply connect the successful completion to the graph end.
if_false = graph()->NewNode(common()->Throw(), efalse, if_false);
// TODO(bmeurer): This should be on the AdvancedReducer somehow.
NodeProperties::MergeControlToEnd(graph(), common(), if_false);
Revisit(graph()->end());
// Update potential {IfException} uses of {node} to point to the
// %ThrowInvalidStringLength runtime call node instead.
for (Edge edge : node->use_edges()) {
if (edge.from()->opcode() == IrOpcode::kIfException) {
DCHECK(NodeProperties::IsControlEdge(edge) ||
NodeProperties::IsEffectEdge(edge));
edge.UpdateTo(vfalse);
Revisit(edge.from());
}
}
}
control = graph()->NewNode(common()->IfTrue(), branch);
}
......@@ -1233,10 +1221,9 @@ Reduction JSTypedLowering::ReduceJSToObject(Node* node) {
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0,
CallDescriptor::kNeedsFrameState, node->op()->properties());
rfalse = efalse = graph()->NewNode(
rfalse = efalse = if_false = graph()->NewNode(
common()->Call(desc), jsgraph()->HeapConstant(callable.code()),
receiver, context, frame_state, efalse, if_false);
if_false = graph()->NewNode(common()->IfSuccess(), rfalse);
}
control = graph()->NewNode(common()->Merge(2), if_true, if_false);
......@@ -1506,18 +1493,18 @@ Reduction JSTypedLowering::ReduceJSOrdinaryHasInstance(Node* node) {
Node* vfalse1;
{
// Slow path, need to call the %HasInPrototypeChain runtime function.
vfalse1 = efalse1 = graph()->NewNode(
vfalse1 = efalse1 = if_false1 = graph()->NewNode(
javascript()->CallRuntime(Runtime::kHasInPrototypeChain), object,
prototype, context, frame_state, efalse1, if_false1);
if_false1 = graph()->NewNode(common()->IfSuccess(), vfalse1);
// Replace any potential IfException on {node} to catch exceptions
// Replace any potential {IfException} uses of {node} to catch exceptions
// from this %HasInPrototypeChain runtime call instead.
for (Edge edge : node->use_edges()) {
if (edge.from()->opcode() == IrOpcode::kIfException) {
edge.UpdateTo(vfalse1);
Revisit(edge.from());
}
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
NodeProperties::ReplaceControlInput(on_exception, vfalse1);
NodeProperties::ReplaceEffectInput(on_exception, efalse1);
if_false1 = graph()->NewNode(common()->IfSuccess(), vfalse1);
Revisit(on_exception);
}
}
......@@ -2211,10 +2198,9 @@ Reduction JSTypedLowering::ReduceJSForInNext(Node* node) {
CallDescriptor const* const desc = Linkage::GetStubCallDescriptor(
isolate(), graph()->zone(), callable.descriptor(), 0,
CallDescriptor::kNeedsFrameState);
vfalse0 = efalse0 = graph()->NewNode(
vfalse0 = efalse0 = if_false0 = graph()->NewNode(
common()->Call(desc), jsgraph()->HeapConstant(callable.code()), key,
receiver, context, frame_state, effect, if_false0);
if_false0 = graph()->NewNode(common()->IfSuccess(), vfalse0);
}
control = graph()->NewNode(common()->Merge(2), if_true0, if_false0);
......
......@@ -2909,7 +2909,6 @@ void SimplifiedLowering::DoJSToNumberTruncatesToFloat64(
Node* frame_state = node->InputAt(2);
Node* effect = node->InputAt(3);
Node* control = node->InputAt(4);
Node* throwing;
Node* check0 = graph()->NewNode(simplified()->ObjectIsSmi(), value);
Node* branch0 =
......@@ -2927,10 +2926,18 @@ void SimplifiedLowering::DoJSToNumberTruncatesToFloat64(
Node* efalse0 = effect;
Node* vfalse0;
{
throwing = vfalse0 = efalse0 =
vfalse0 = efalse0 = if_false0 =
graph()->NewNode(ToNumberOperator(), ToNumberCode(), value, context,
frame_state, efalse0, if_false0);
if_false0 = graph()->NewNode(common()->IfSuccess(), throwing);
// Update potential {IfException} uses of {node} to point to the above
// {ToNumber} stub call node instead.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
NodeProperties::ReplaceControlInput(on_exception, vfalse0);
NodeProperties::ReplaceEffectInput(on_exception, efalse0);
if_false0 = graph()->NewNode(common()->IfSuccess(), vfalse0);
}
Node* check1 = graph()->NewNode(simplified()->ObjectIsSmi(), vfalse0);
Node* branch1 = graph()->NewNode(common()->Branch(), check1, if_false0);
......@@ -2972,10 +2979,9 @@ void SimplifiedLowering::DoJSToNumberTruncatesToFloat64(
if (edge.from()->opcode() == IrOpcode::kIfSuccess) {
edge.from()->ReplaceUses(control);
edge.from()->Kill();
} else if (edge.from()->opcode() == IrOpcode::kIfException) {
edge.UpdateTo(throwing);
} else {
UNREACHABLE();
DCHECK(edge.from()->opcode() != IrOpcode::kIfException);
edge.UpdateTo(control);
}
} else if (NodeProperties::IsEffectEdge(edge)) {
edge.UpdateTo(effect);
......@@ -2993,7 +2999,6 @@ void SimplifiedLowering::DoJSToNumberTruncatesToWord32(
Node* frame_state = node->InputAt(2);
Node* effect = node->InputAt(3);
Node* control = node->InputAt(4);
Node* throwing;
Node* check0 = graph()->NewNode(simplified()->ObjectIsSmi(), value);
Node* branch0 =
......@@ -3008,10 +3013,18 @@ void SimplifiedLowering::DoJSToNumberTruncatesToWord32(
Node* efalse0 = effect;
Node* vfalse0;
{
throwing = vfalse0 = efalse0 =
vfalse0 = efalse0 = if_false0 =
graph()->NewNode(ToNumberOperator(), ToNumberCode(), value, context,
frame_state, efalse0, if_false0);
if_false0 = graph()->NewNode(common()->IfSuccess(), throwing);
// Update potential {IfException} uses of {node} to point to the above
// {ToNumber} stub call node instead.
Node* on_exception = nullptr;
if (NodeProperties::IsExceptionalCall(node, &on_exception)) {
NodeProperties::ReplaceControlInput(on_exception, vfalse0);
NodeProperties::ReplaceEffectInput(on_exception, efalse0);
if_false0 = graph()->NewNode(common()->IfSuccess(), vfalse0);
}
Node* check1 = graph()->NewNode(simplified()->ObjectIsSmi(), vfalse0);
Node* branch1 = graph()->NewNode(common()->Branch(), check1, if_false0);
......@@ -3049,10 +3062,9 @@ void SimplifiedLowering::DoJSToNumberTruncatesToWord32(
if (edge.from()->opcode() == IrOpcode::kIfSuccess) {
edge.from()->ReplaceUses(control);
edge.from()->Kill();
} else if (edge.from()->opcode() == IrOpcode::kIfException) {
edge.UpdateTo(throwing);
} else {
UNREACHABLE();
DCHECK(edge.from()->opcode() != IrOpcode::kIfException);
edge.UpdateTo(control);
}
} else if (NodeProperties::IsEffectEdge(edge)) {
edge.UpdateTo(effect);
......
......@@ -18,21 +18,20 @@ Reduction TailCallOptimization::Reduce(Node* node) {
if (node->opcode() != IrOpcode::kReturn) return NoChange();
// The value which is returned must be the result of a potential tail call,
// there must be no try/catch/finally around the Call, and there must be no
// other effect between the Call and the Return nodes.
// other effect or control between the Call and the Return nodes.
Node* const call = NodeProperties::GetValueInput(node, 1);
if (call->opcode() == IrOpcode::kCall &&
CallDescriptorOf(call->op())->SupportsTailCalls() &&
NodeProperties::GetEffectInput(node) == call &&
!NodeProperties::IsExceptionalCall(call)) {
Node* const control = NodeProperties::GetControlInput(node);
NodeProperties::GetControlInput(node) == call &&
!NodeProperties::IsExceptionalCall(call) && call->UseCount() == 3) {
// Ensure that no additional arguments are being popped other than those in
// the CallDescriptor, otherwise the tail call transformation is invalid.
DCHECK_EQ(0, Int32Matcher(NodeProperties::GetValueInput(node, 0)).Value());
if (control->opcode() == IrOpcode::kIfSuccess &&
call->OwnedBy(node, control) && control->OwnedBy(node)) {
// Furthermore, control has to flow via an IfSuccess from the Call, so
// the Return node value and effect depends directly on the Call node,
// and indirectly control depends on the Call via an IfSuccess.
// Furthermore, the Return node value, effect, and control depends
// directly on the Call, no other uses of the Call node exist.
//
// The input graph looks as follows:
// Value1 ... ValueN Effect Control
// ^ ^ ^ ^
......@@ -42,13 +41,8 @@ Reduction TailCallOptimization::Reduce(Node* node) {
// \ | | /
// Call[Descriptor]
// ^ ^ ^
// | | |
// +-+ | |
// | | |
// | +-+ |
// | | IfSuccess
// | | ^
// | | |
// Int32(0) <-+ | | |
// \ | | |
// Return
// ^
// |
......@@ -65,7 +59,6 @@ Reduction TailCallOptimization::Reduce(Node* node) {
// ^
// |
DCHECK_EQ(call, NodeProperties::GetControlInput(control, 0));
DCHECK_EQ(4, node->InputCount());
node->ReplaceInput(0, NodeProperties::GetEffectInput(call));
node->ReplaceInput(1, NodeProperties::GetControlInput(call));
......@@ -75,11 +68,10 @@ Reduction TailCallOptimization::Reduce(Node* node) {
node->InsertInput(graph()->zone(), index,
NodeProperties::GetValueInput(call, index));
}
NodeProperties::ChangeOp(
node, common()->TailCall(CallDescriptorOf(call->op())));
NodeProperties::ChangeOp(node,
common()->TailCall(CallDescriptorOf(call->op())));
return Changed(node);
}
}
return NoChange();
}
......
......@@ -152,27 +152,45 @@ void Verifier::Visitor::Check(Node* node) {
"control");
}
// Verify that nodes that can throw only have IfSuccess/IfException control
// uses.
// Verify that nodes that can throw either have both IfSuccess/IfException
// projections as the only control uses or no projections at all.
if (!node->op()->HasProperty(Operator::kNoThrow)) {
int count_success = 0, count_exception = 0;
Node* discovered_if_exception = nullptr;
Node* discovered_if_success = nullptr;
int total_number_of_control_uses = 0;
for (Edge edge : node->use_edges()) {
if (!NodeProperties::IsControlEdge(edge)) {
continue;
}
total_number_of_control_uses++;
Node* control_use = edge.from();
if (control_use->opcode() != IrOpcode::kIfSuccess &&
control_use->opcode() != IrOpcode::kIfException) {
if (control_use->opcode() == IrOpcode::kIfSuccess) {
CHECK_NULL(discovered_if_success); // Only one allowed.
discovered_if_success = control_use;
}
if (control_use->opcode() == IrOpcode::kIfException) {
CHECK_NULL(discovered_if_exception); // Only one allowed.
discovered_if_exception = control_use;
}
}
if (discovered_if_success && !discovered_if_exception) {
V8_Fatal(__FILE__, __LINE__,
"#%d:%s should be followed by IfSuccess/IfException, but is "
"followed by #%d:%s",
node->id(), node->op()->mnemonic(), control_use->id(),
control_use->op()->mnemonic());
}
if (control_use->opcode() == IrOpcode::kIfSuccess) ++count_success;
if (control_use->opcode() == IrOpcode::kIfException) ++count_exception;
CHECK_LE(count_success, 1);
CHECK_LE(count_exception, 1);
"only followed by single #%d:%s",
node->id(), node->op()->mnemonic(),
discovered_if_success->id(),
discovered_if_success->op()->mnemonic());
}
if (discovered_if_exception && !discovered_if_success) {
V8_Fatal(__FILE__, __LINE__,
"#%d:%s should be followed by IfSuccess/IfException, but is "
"only followed by single #%d:%s",
node->id(), node->op()->mnemonic(),
discovered_if_exception->id(),
discovered_if_exception->op()->mnemonic());
}
if (discovered_if_success || discovered_if_exception) {
CHECK_EQ(2, total_number_of_control_uses);
}
}
}
......
......@@ -2525,65 +2525,6 @@ Node* WasmGraphBuilder::BuildChangeTaggedToFloat64(Node* value) {
MachineOperatorBuilder* machine = jsgraph()->machine();
CommonOperatorBuilder* common = jsgraph()->common();
if (CanCover(value, IrOpcode::kJSToNumber)) {
// ChangeTaggedToFloat64(JSToNumber(x)) =>
// if IsSmi(x) then ChangeSmiToFloat64(x)
// else let y = JSToNumber(x) in
// if IsSmi(y) then ChangeSmiToFloat64(y)
// else BuildLoadHeapNumberValue(y)
Node* object = NodeProperties::GetValueInput(value, 0);
Node* context = NodeProperties::GetContextInput(value);
Node* frame_state = NodeProperties::GetFrameStateInput(value);
Node* effect = NodeProperties::GetEffectInput(value);
Node* control = NodeProperties::GetControlInput(value);
const Operator* merge_op = common->Merge(2);
const Operator* ephi_op = common->EffectPhi(2);
const Operator* phi_op = common->Phi(MachineRepresentation::kFloat64, 2);
Node* check1 = BuildTestNotSmi(object);
Node* branch1 =
graph()->NewNode(common->Branch(BranchHint::kFalse), check1, control);
Node* if_true1 = graph()->NewNode(common->IfTrue(), branch1);
Node* vtrue1 = graph()->NewNode(value->op(), object, context, frame_state,
effect, if_true1);
Node* etrue1 = vtrue1;
Node* check2 = BuildTestNotSmi(vtrue1);
Node* branch2 = graph()->NewNode(common->Branch(), check2, if_true1);
Node* if_true2 = graph()->NewNode(common->IfTrue(), branch2);
Node* vtrue2 = BuildLoadHeapNumberValue(vtrue1, if_true2);
Node* if_false2 = graph()->NewNode(common->IfFalse(), branch2);
Node* vfalse2 = BuildChangeSmiToFloat64(vtrue1);
if_true1 = graph()->NewNode(merge_op, if_true2, if_false2);
vtrue1 = graph()->NewNode(phi_op, vtrue2, vfalse2, if_true1);
Node* if_false1 = graph()->NewNode(common->IfFalse(), branch1);
Node* vfalse1 = BuildChangeSmiToFloat64(object);
Node* efalse1 = effect;
Node* merge1 = graph()->NewNode(merge_op, if_true1, if_false1);
Node* ephi1 = graph()->NewNode(ephi_op, etrue1, efalse1, merge1);
Node* phi1 = graph()->NewNode(phi_op, vtrue1, vfalse1, merge1);
// Wire the new diamond into the graph, {JSToNumber} can still throw.
NodeProperties::ReplaceUses(value, phi1, ephi1, etrue1, etrue1);
// TODO(mstarzinger): This iteration cuts out the IfSuccess projection from
// the node and places it inside the diamond. Come up with a helper method!
for (Node* use : etrue1->uses()) {
if (use->opcode() == IrOpcode::kIfSuccess) {
use->ReplaceUses(merge1);
NodeProperties::ReplaceControlInput(branch2, use);
}
}
return phi1;
}
Node* check = BuildTestNotSmi(value);
Node* branch = graph()->NewNode(common->Branch(BranchHint::kFalse), check,
graph()->start());
......
......@@ -153,127 +153,6 @@ TEST_F(EffectControlLinearizerTest, DiamondLoad) {
ret, IsReturn(phi, IsEffectPhi(vtrue, graph()->start(), merge), merge));
}
TEST_F(EffectControlLinearizerTest, FloatingDiamondsControlWiring) {
Schedule schedule(zone());
// Create the graph and schedule. Roughly (omitting effects and unimportant
// nodes):
//
// BLOCK 0:
// r1: Start
// c1: Call
// b1: Branch(const0, s1)
// |
// +-------+------+
// | |
// BLOCK 1: BLOCK 2:
// t1: IfTrue(b1) f1: IfFalse(b1)
// | |
// +-------+------+
// |
// BLOCK 3:
// m1: Merge(t1, f1)
// c2: IfSuccess(c1)
// b2: Branch(const0 , s1)
// |
// +-------+------+
// | |
// BLOCK 4: BLOCK 5:
// t2: IfTrue(b2) f2:IfFalse(b2)
// | |
// +-------+------+
// |
// BLOCK 6:
// m2: Merge(t2, f2)
// r1: Return(c1, c2)
LinkageLocation kLocationSignature[] = {
LinkageLocation::ForRegister(0, MachineType::Pointer()),
LinkageLocation::ForRegister(1, MachineType::Pointer())};
const CallDescriptor* kCallDescriptor = new (zone()) CallDescriptor(
CallDescriptor::kCallCodeObject, MachineType::AnyTagged(),
LinkageLocation::ForRegister(0, MachineType::Pointer()),
new (zone()) LocationSignature(1, 1, kLocationSignature), 0,
Operator::kNoProperties, 0, 0, CallDescriptor::kNoFlags);
Node* p0 = Parameter(0);
Node* p1 = Parameter(1);
Node* const0 = Int32Constant(0);
Node* call = graph()->NewNode(common()->Call(kCallDescriptor), p0, p1,
graph()->start(), graph()->start());
Node* if_success = graph()->NewNode(common()->IfSuccess(), call);
// First Floating diamond.
Node* branch1 =
graph()->NewNode(common()->Branch(), const0, graph()->start());
Node* if_true1 = graph()->NewNode(common()->IfTrue(), branch1);
Node* if_false1 = graph()->NewNode(common()->IfFalse(), branch1);
Node* merge1 = graph()->NewNode(common()->Merge(2), if_true1, if_false1);
// Second floating diamond.
Node* branch2 =
graph()->NewNode(common()->Branch(), const0, graph()->start());
Node* if_true2 = graph()->NewNode(common()->IfTrue(), branch2);
Node* if_false2 = graph()->NewNode(common()->IfFalse(), branch2);
Node* merge2 = graph()->NewNode(common()->Merge(2), if_true2, if_false2);
Node* zero = graph()->NewNode(common()->Int32Constant(0));
Node* ret = graph()->NewNode(common()->Return(), zero, call, graph()->start(),
if_success);
// Build the basic block structure.
BasicBlock* start = schedule.start();
schedule.rpo_order()->push_back(start);
start->set_rpo_number(0);
BasicBlock* t1block = AddBlockToSchedule(&schedule);
BasicBlock* f1block = AddBlockToSchedule(&schedule);
BasicBlock* m1block = AddBlockToSchedule(&schedule);
BasicBlock* t2block = AddBlockToSchedule(&schedule);
BasicBlock* f2block = AddBlockToSchedule(&schedule);
BasicBlock* m2block = AddBlockToSchedule(&schedule);
// Populate the basic blocks with nodes.
schedule.AddNode(start, graph()->start());
schedule.AddNode(start, p0);
schedule.AddNode(start, p1);
schedule.AddNode(start, const0);
schedule.AddNode(start, call);
schedule.AddBranch(start, branch1, t1block, f1block);
schedule.AddNode(t1block, if_true1);
schedule.AddGoto(t1block, m1block);
schedule.AddNode(f1block, if_false1);
schedule.AddGoto(f1block, m1block);
schedule.AddNode(m1block, merge1);
// The scheduler does not always put the IfSuccess node to the corresponding
// call's block, simulate that here.
schedule.AddNode(m1block, if_success);
schedule.AddBranch(m1block, branch2, t2block, f2block);
schedule.AddNode(t2block, if_true2);
schedule.AddGoto(t2block, m2block);
schedule.AddNode(f2block, if_false2);
schedule.AddGoto(f2block, m2block);
schedule.AddNode(m2block, merge2);
schedule.AddReturn(m2block, ret);
// Run the state effect introducer.
EffectControlLinearizer introducer(jsgraph(), &schedule, zone(),
source_positions());
introducer.Run();
// The effect input to the return should be an effect phi with the
// newly introduced effectful change operators.
ASSERT_THAT(ret, IsReturn(call, call, merge2));
ASSERT_THAT(branch2, IsBranch(const0, merge1));
ASSERT_THAT(branch1, IsBranch(const0, if_success));
ASSERT_THAT(if_success, IsIfSuccess(call));
}
TEST_F(EffectControlLinearizerTest, LoopLoad) {
Schedule schedule(zone());
......
......@@ -38,10 +38,8 @@ TEST_F(TailCallOptimizationTest, CallCodeObject0) {
Node* p1 = Parameter(1);
Node* call = graph()->NewNode(common()->Call(kCallDescriptor), p0, p1,
graph()->start(), graph()->start());
Node* if_success = graph()->NewNode(common()->IfSuccess(), call);
Node* zero = graph()->NewNode(common()->Int32Constant(0));
Node* ret =
graph()->NewNode(common()->Return(), zero, call, call, if_success);
Node* ret = graph()->NewNode(common()->Return(), zero, call, call, call);
Reduction r = Reduce(ret);
ASSERT_FALSE(r.Changed());
}
......@@ -85,10 +83,8 @@ TEST_F(TailCallOptimizationTest, CallCodeObject2) {
Node* p1 = Parameter(1);
Node* call = graph()->NewNode(common()->Call(kCallDescriptor), p0, p1,
graph()->start(), graph()->start());
Node* if_success = graph()->NewNode(common()->IfSuccess(), call);
Node* zero = graph()->NewNode(common()->Int32Constant(0));
Node* ret =
graph()->NewNode(common()->Return(), zero, call, call, if_success);
Node* ret = graph()->NewNode(common()->Return(), zero, call, call, call);
Reduction r = Reduce(ret);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsTailCall(kCallDescriptor, p0, p1,
......@@ -109,10 +105,8 @@ TEST_F(TailCallOptimizationTest, CallJSFunction0) {
Node* p1 = Parameter(1);
Node* call = graph()->NewNode(common()->Call(kCallDescriptor), p0, p1,
graph()->start(), graph()->start());
Node* if_success = graph()->NewNode(common()->IfSuccess(), call);
Node* zero = graph()->NewNode(common()->Int32Constant(0));
Node* ret =
graph()->NewNode(common()->Return(), zero, call, call, if_success);
Node* ret = graph()->NewNode(common()->Return(), zero, call, call, call);
Reduction r = Reduce(ret);
ASSERT_FALSE(r.Changed());
}
......@@ -155,10 +149,8 @@ TEST_F(TailCallOptimizationTest, CallJSFunction2) {
Node* p1 = Parameter(1);
Node* call = graph()->NewNode(common()->Call(kCallDescriptor), p0, p1,
graph()->start(), graph()->start());
Node* if_success = graph()->NewNode(common()->IfSuccess(), call);
Node* zero = graph()->NewNode(common()->Int32Constant(0));
Node* ret =
graph()->NewNode(common()->Return(), zero, call, call, if_success);
Node* ret = graph()->NewNode(common()->Return(), zero, call, call, call);
Reduction r = Reduce(ret);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsTailCall(kCallDescriptor, p0, p1,
......
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