4329354ae8
This is a reland of 2c0b1f6e9d
This fixes two bugs:
- Unreachable might have value uses even after being connected
to Throw, so the solution is to just not replace them with the Dead node
anymore.
- We didn't trigger initial visitation of the new Throw node.
Re-visiting the changed End node takes care of this.
Original change's description:
> [turbofan] re-wire Unreachable to the graph end at EffectPhi's
>
> This avoids the EffectControlLinearizer stumbling upon unreachable
> code.
>
> Bug: chromium:958718
> Change-Id: I135c17813741e48e878a4624370eee1e06081031
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1605737
> Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
> Commit-Queue: Tobias Tebbi <tebbi@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#61434}
Bug: chromium:958718 chromium:962475 chromium:962474
Change-Id: I388a59912e6260a221cccc76102e0c4b00bff93e
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1609791
Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
Commit-Queue: Tobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#61482}
386 lines
14 KiB
C++
386 lines
14 KiB
C++
// Copyright 2015 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.
|
|
|
|
#include "src/compiler/dead-code-elimination.h"
|
|
|
|
#include "src/compiler/common-operator.h"
|
|
#include "src/compiler/graph.h"
|
|
#include "src/compiler/js-operator.h"
|
|
#include "src/compiler/node-properties.h"
|
|
#include "src/compiler/operator-properties.h"
|
|
|
|
namespace v8 {
|
|
namespace internal {
|
|
namespace compiler {
|
|
|
|
DeadCodeElimination::DeadCodeElimination(Editor* editor, Graph* graph,
|
|
CommonOperatorBuilder* common,
|
|
Zone* temp_zone)
|
|
: AdvancedReducer(editor),
|
|
graph_(graph),
|
|
common_(common),
|
|
dead_(graph->NewNode(common->Dead())),
|
|
zone_(temp_zone) {
|
|
NodeProperties::SetType(dead_, Type::None());
|
|
}
|
|
|
|
namespace {
|
|
|
|
// True if we can guarantee that {node} will never actually produce a value or
|
|
// effect.
|
|
bool NoReturn(Node* node) {
|
|
return node->opcode() == IrOpcode::kDead ||
|
|
node->opcode() == IrOpcode::kUnreachable ||
|
|
node->opcode() == IrOpcode::kDeadValue ||
|
|
NodeProperties::GetTypeOrAny(node).IsNone();
|
|
}
|
|
|
|
Node* FindDeadInput(Node* node) {
|
|
for (Node* input : node->inputs()) {
|
|
if (NoReturn(input)) return input;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Reduction DeadCodeElimination::Reduce(Node* node) {
|
|
DisallowHeapAccess no_heap_access;
|
|
switch (node->opcode()) {
|
|
case IrOpcode::kEnd:
|
|
return ReduceEnd(node);
|
|
case IrOpcode::kLoop:
|
|
case IrOpcode::kMerge:
|
|
return ReduceLoopOrMerge(node);
|
|
case IrOpcode::kLoopExit:
|
|
return ReduceLoopExit(node);
|
|
case IrOpcode::kUnreachable:
|
|
case IrOpcode::kIfException:
|
|
return ReduceUnreachableOrIfException(node);
|
|
case IrOpcode::kPhi:
|
|
return ReducePhi(node);
|
|
case IrOpcode::kEffectPhi:
|
|
return ReduceEffectPhi(node);
|
|
case IrOpcode::kDeoptimize:
|
|
case IrOpcode::kReturn:
|
|
case IrOpcode::kTerminate:
|
|
case IrOpcode::kTailCall:
|
|
return ReduceDeoptimizeOrReturnOrTerminateOrTailCall(node);
|
|
case IrOpcode::kThrow:
|
|
return PropagateDeadControl(node);
|
|
case IrOpcode::kBranch:
|
|
case IrOpcode::kSwitch:
|
|
return ReduceBranchOrSwitch(node);
|
|
default:
|
|
return ReduceNode(node);
|
|
}
|
|
UNREACHABLE();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::PropagateDeadControl(Node* node) {
|
|
DCHECK_EQ(1, node->op()->ControlInputCount());
|
|
Node* control = NodeProperties::GetControlInput(node);
|
|
if (control->opcode() == IrOpcode::kDead) return Replace(control);
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceEnd(Node* node) {
|
|
DCHECK_EQ(IrOpcode::kEnd, node->opcode());
|
|
Node::Inputs inputs = node->inputs();
|
|
DCHECK_LE(1, inputs.count());
|
|
int live_input_count = 0;
|
|
for (int i = 0; i < inputs.count(); ++i) {
|
|
Node* const input = inputs[i];
|
|
// Skip dead inputs.
|
|
if (input->opcode() == IrOpcode::kDead) continue;
|
|
// Compact live inputs.
|
|
if (i != live_input_count) node->ReplaceInput(live_input_count, input);
|
|
++live_input_count;
|
|
}
|
|
if (live_input_count == 0) {
|
|
return Replace(dead());
|
|
} else if (live_input_count < inputs.count()) {
|
|
node->TrimInputCount(live_input_count);
|
|
NodeProperties::ChangeOp(node, common()->End(live_input_count));
|
|
return Changed(node);
|
|
}
|
|
DCHECK_EQ(inputs.count(), live_input_count);
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceLoopOrMerge(Node* node) {
|
|
DCHECK(IrOpcode::IsMergeOpcode(node->opcode()));
|
|
Node::Inputs inputs = node->inputs();
|
|
DCHECK_LE(1, inputs.count());
|
|
// Count the number of live inputs to {node} and compact them on the fly, also
|
|
// compacting the inputs of the associated {Phi} and {EffectPhi} uses at the
|
|
// same time. We consider {Loop}s dead even if only the first control input
|
|
// is dead.
|
|
int live_input_count = 0;
|
|
if (node->opcode() != IrOpcode::kLoop ||
|
|
node->InputAt(0)->opcode() != IrOpcode::kDead) {
|
|
for (int i = 0; i < inputs.count(); ++i) {
|
|
Node* const input = inputs[i];
|
|
// Skip dead inputs.
|
|
if (input->opcode() == IrOpcode::kDead) continue;
|
|
// Compact live inputs.
|
|
if (live_input_count != i) {
|
|
node->ReplaceInput(live_input_count, input);
|
|
for (Node* const use : node->uses()) {
|
|
if (NodeProperties::IsPhi(use)) {
|
|
DCHECK_EQ(inputs.count() + 1, use->InputCount());
|
|
use->ReplaceInput(live_input_count, use->InputAt(i));
|
|
}
|
|
}
|
|
}
|
|
++live_input_count;
|
|
}
|
|
}
|
|
if (live_input_count == 0) {
|
|
return Replace(dead());
|
|
} else if (live_input_count == 1) {
|
|
NodeVector loop_exits(zone_);
|
|
// Due to compaction above, the live input is at offset 0.
|
|
for (Node* const use : node->uses()) {
|
|
if (NodeProperties::IsPhi(use)) {
|
|
Replace(use, use->InputAt(0));
|
|
} else if (use->opcode() == IrOpcode::kLoopExit &&
|
|
use->InputAt(1) == node) {
|
|
// Remember the loop exits so that we can mark their loop input dead.
|
|
// This has to be done after the use list iteration so that we do
|
|
// not mutate the use list while it is being iterated.
|
|
loop_exits.push_back(use);
|
|
} else if (use->opcode() == IrOpcode::kTerminate) {
|
|
DCHECK_EQ(IrOpcode::kLoop, node->opcode());
|
|
Replace(use, dead());
|
|
}
|
|
}
|
|
for (Node* loop_exit : loop_exits) {
|
|
loop_exit->ReplaceInput(1, dead());
|
|
Revisit(loop_exit);
|
|
}
|
|
return Replace(node->InputAt(0));
|
|
}
|
|
DCHECK_LE(2, live_input_count);
|
|
DCHECK_LE(live_input_count, inputs.count());
|
|
// Trim input count for the {Merge} or {Loop} node.
|
|
if (live_input_count < inputs.count()) {
|
|
// Trim input counts for all phi uses and revisit them.
|
|
for (Node* const use : node->uses()) {
|
|
if (NodeProperties::IsPhi(use)) {
|
|
use->ReplaceInput(live_input_count, node);
|
|
TrimMergeOrPhi(use, live_input_count);
|
|
Revisit(use);
|
|
}
|
|
}
|
|
TrimMergeOrPhi(node, live_input_count);
|
|
return Changed(node);
|
|
}
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::RemoveLoopExit(Node* node) {
|
|
DCHECK_EQ(IrOpcode::kLoopExit, node->opcode());
|
|
for (Node* const use : node->uses()) {
|
|
if (use->opcode() == IrOpcode::kLoopExitValue ||
|
|
use->opcode() == IrOpcode::kLoopExitEffect) {
|
|
Replace(use, use->InputAt(0));
|
|
}
|
|
}
|
|
Node* control = NodeProperties::GetControlInput(node, 0);
|
|
Replace(node, control);
|
|
return Replace(control);
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceNode(Node* node) {
|
|
DCHECK(!IrOpcode::IsGraphTerminator(node->opcode()));
|
|
int const effect_input_count = node->op()->EffectInputCount();
|
|
int const control_input_count = node->op()->ControlInputCount();
|
|
DCHECK_LE(control_input_count, 1);
|
|
if (control_input_count == 1) {
|
|
Reduction reduction = PropagateDeadControl(node);
|
|
if (reduction.Changed()) return reduction;
|
|
}
|
|
if (effect_input_count == 0 &&
|
|
(control_input_count == 0 || node->op()->ControlOutputCount() == 0)) {
|
|
return ReducePureNode(node);
|
|
}
|
|
if (effect_input_count > 0) {
|
|
return ReduceEffectNode(node);
|
|
}
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReducePhi(Node* node) {
|
|
DCHECK_EQ(IrOpcode::kPhi, node->opcode());
|
|
Reduction reduction = PropagateDeadControl(node);
|
|
if (reduction.Changed()) return reduction;
|
|
MachineRepresentation rep = PhiRepresentationOf(node->op());
|
|
if (rep == MachineRepresentation::kNone ||
|
|
NodeProperties::GetTypeOrAny(node).IsNone()) {
|
|
return Replace(DeadValue(node, rep));
|
|
}
|
|
int input_count = node->op()->ValueInputCount();
|
|
for (int i = 0; i < input_count; ++i) {
|
|
Node* input = NodeProperties::GetValueInput(node, i);
|
|
if (input->opcode() == IrOpcode::kDeadValue &&
|
|
DeadValueRepresentationOf(input->op()) != rep) {
|
|
NodeProperties::ReplaceValueInput(node, DeadValue(input, rep), i);
|
|
}
|
|
}
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceEffectPhi(Node* node) {
|
|
DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
|
|
Reduction reduction = PropagateDeadControl(node);
|
|
if (reduction.Changed()) return reduction;
|
|
|
|
Node* merge = NodeProperties::GetControlInput(node);
|
|
DCHECK(merge->opcode() == IrOpcode::kMerge ||
|
|
merge->opcode() == IrOpcode::kLoop);
|
|
int input_count = node->op()->EffectInputCount();
|
|
for (int i = 0; i < input_count; ++i) {
|
|
Node* effect = NodeProperties::GetEffectInput(node, i);
|
|
if (effect->opcode() == IrOpcode::kUnreachable) {
|
|
// If Unreachable hits an effect phi, we can re-connect the effect chain
|
|
// to the graph end and delete the corresponding inputs from the merge and
|
|
// phi nodes.
|
|
Node* control = NodeProperties::GetControlInput(merge, i);
|
|
Node* throw_node = graph_->NewNode(common_->Throw(), effect, control);
|
|
NodeProperties::MergeControlToEnd(graph_, common_, throw_node);
|
|
NodeProperties::ReplaceEffectInput(node, dead_, i);
|
|
NodeProperties::ReplaceControlInput(merge, dead_, i);
|
|
Revisit(merge);
|
|
Revisit(graph_->end());
|
|
reduction = Changed(node);
|
|
}
|
|
}
|
|
return reduction;
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReducePureNode(Node* node) {
|
|
DCHECK_EQ(0, node->op()->EffectInputCount());
|
|
if (node->opcode() == IrOpcode::kDeadValue) return NoChange();
|
|
if (Node* input = FindDeadInput(node)) {
|
|
return Replace(DeadValue(input));
|
|
}
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceUnreachableOrIfException(Node* node) {
|
|
DCHECK(node->opcode() == IrOpcode::kUnreachable ||
|
|
node->opcode() == IrOpcode::kIfException);
|
|
Reduction reduction = PropagateDeadControl(node);
|
|
if (reduction.Changed()) return reduction;
|
|
Node* effect = NodeProperties::GetEffectInput(node, 0);
|
|
if (effect->opcode() == IrOpcode::kDead) {
|
|
return Replace(effect);
|
|
}
|
|
if (effect->opcode() == IrOpcode::kUnreachable) {
|
|
return Replace(effect);
|
|
}
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceEffectNode(Node* node) {
|
|
DCHECK_EQ(1, node->op()->EffectInputCount());
|
|
Node* effect = NodeProperties::GetEffectInput(node, 0);
|
|
if (effect->opcode() == IrOpcode::kDead) {
|
|
return Replace(effect);
|
|
}
|
|
if (Node* input = FindDeadInput(node)) {
|
|
if (effect->opcode() == IrOpcode::kUnreachable) {
|
|
RelaxEffectsAndControls(node);
|
|
return Replace(DeadValue(input));
|
|
}
|
|
|
|
Node* control = node->op()->ControlInputCount() == 1
|
|
? NodeProperties::GetControlInput(node, 0)
|
|
: graph()->start();
|
|
Node* unreachable =
|
|
graph()->NewNode(common()->Unreachable(), effect, control);
|
|
NodeProperties::SetType(unreachable, Type::None());
|
|
ReplaceWithValue(node, DeadValue(input), node, control);
|
|
return Replace(unreachable);
|
|
}
|
|
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceDeoptimizeOrReturnOrTerminateOrTailCall(
|
|
Node* node) {
|
|
DCHECK(node->opcode() == IrOpcode::kDeoptimize ||
|
|
node->opcode() == IrOpcode::kReturn ||
|
|
node->opcode() == IrOpcode::kTerminate ||
|
|
node->opcode() == IrOpcode::kTailCall);
|
|
Reduction reduction = PropagateDeadControl(node);
|
|
if (reduction.Changed()) return reduction;
|
|
if (FindDeadInput(node) != nullptr) {
|
|
Node* effect = NodeProperties::GetEffectInput(node, 0);
|
|
Node* control = NodeProperties::GetControlInput(node, 0);
|
|
if (effect->opcode() != IrOpcode::kUnreachable) {
|
|
effect = graph()->NewNode(common()->Unreachable(), effect, control);
|
|
NodeProperties::SetType(effect, Type::None());
|
|
}
|
|
node->TrimInputCount(2);
|
|
node->ReplaceInput(0, effect);
|
|
node->ReplaceInput(1, control);
|
|
NodeProperties::ChangeOp(node, common()->Throw());
|
|
return Changed(node);
|
|
}
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceLoopExit(Node* node) {
|
|
Node* control = NodeProperties::GetControlInput(node, 0);
|
|
Node* loop = NodeProperties::GetControlInput(node, 1);
|
|
if (control->opcode() == IrOpcode::kDead ||
|
|
loop->opcode() == IrOpcode::kDead) {
|
|
return RemoveLoopExit(node);
|
|
}
|
|
return NoChange();
|
|
}
|
|
|
|
Reduction DeadCodeElimination::ReduceBranchOrSwitch(Node* node) {
|
|
DCHECK(node->opcode() == IrOpcode::kBranch ||
|
|
node->opcode() == IrOpcode::kSwitch);
|
|
Reduction reduction = PropagateDeadControl(node);
|
|
if (reduction.Changed()) return reduction;
|
|
Node* condition = NodeProperties::GetValueInput(node, 0);
|
|
if (condition->opcode() == IrOpcode::kDeadValue) {
|
|
// Branches or switches on {DeadValue} must originate from unreachable code
|
|
// and cannot matter. Due to schedule freedom between the effect and the
|
|
// control chain, they might still appear in reachable code. Remove them by
|
|
// always choosing the first projection.
|
|
size_t const projection_cnt = node->op()->ControlOutputCount();
|
|
Node** projections = zone_->NewArray<Node*>(projection_cnt);
|
|
NodeProperties::CollectControlProjections(node, projections,
|
|
projection_cnt);
|
|
Replace(projections[0], NodeProperties::GetControlInput(node));
|
|
return Replace(dead());
|
|
}
|
|
return NoChange();
|
|
}
|
|
|
|
void DeadCodeElimination::TrimMergeOrPhi(Node* node, int size) {
|
|
const Operator* const op = common()->ResizeMergeOrPhi(node->op(), size);
|
|
node->TrimInputCount(OperatorProperties::GetTotalInputCount(op));
|
|
NodeProperties::ChangeOp(node, op);
|
|
}
|
|
|
|
Node* DeadCodeElimination::DeadValue(Node* node, MachineRepresentation rep) {
|
|
if (node->opcode() == IrOpcode::kDeadValue) {
|
|
if (rep == DeadValueRepresentationOf(node->op())) return node;
|
|
node = NodeProperties::GetValueInput(node, 0);
|
|
}
|
|
Node* dead_value = graph()->NewNode(common()->DeadValue(rep), node);
|
|
NodeProperties::SetType(dead_value, Type::None());
|
|
return dead_value;
|
|
}
|
|
|
|
} // namespace compiler
|
|
} // namespace internal
|
|
} // namespace v8
|