Commit 733a2463 authored by bmeurer's avatar bmeurer Committed by Commit bot

[turbofan] Proper dead code elimination as regular reducer.

The three different concerns that the ControlReducer used to deal with
are now properly separated into

  a.) DeadCodeElimination, which is a regular AdvancedReducer, that
      propagates Dead via control edges,
  b.) CommonOperatorReducer, which does strength reduction on common
      operators (i.e. Branch, Phi, and friends), and
  c.) GraphTrimming, which removes dead->live edges from the graph.

This will make it possible to run the DeadCodeElimination together with
other passes that actually introduce Dead nodes, i.e. typed lowering;
and it opens the door for general inlining without two stage fix point
iteration.

To make the DeadCodeElimination easier and more uniform, we basically
reverted the introduction of DeadValue and DeadEffect, and changed the
Dead operator to produce control, value and effect. Note however that
this is not a requirement, but merely a way to make dead propagation
easier and more uniform. We could always go back and decide to have
different Dead operators if some other change requires that.

Note that there are several additional opportunities for cleanup now,
i.e. OSR deconstruction could be a regular reducer now, and we don't
need to use TheHole as dead value marker in the GraphReducer. And we can
actually run the dead code elimination together with the other passes
instead of using separate passes over the graph.  We will do this in
follow up CLs.

R=jarin@chromium.org, mstarzinger@chromium.org

Review URL: https://codereview.chromium.org/1193833002

Cr-Commit-Position: refs/heads/master@{#29146}
parent f82ccff5
......@@ -627,8 +627,8 @@ source_set("v8_base") {
"src/compiler/control-equivalence.h",
"src/compiler/control-flow-optimizer.cc",
"src/compiler/control-flow-optimizer.h",
"src/compiler/control-reducer.cc",
"src/compiler/control-reducer.h",
"src/compiler/dead-code-elimination.cc",
"src/compiler/dead-code-elimination.h",
"src/compiler/diamond.h",
"src/compiler/frame.h",
"src/compiler/frame-elider.cc",
......
......@@ -403,7 +403,7 @@ class AstGraphBuilder::FrameStateBeforeAndAfter {
if (count >= 1) {
// Add the frame state for after the operation.
DCHECK_EQ(IrOpcode::kDeadValue,
DCHECK_EQ(IrOpcode::kDead,
NodeProperties::GetFrameStateInput(node, 0)->opcode());
Node* frame_state_after =
......@@ -416,7 +416,7 @@ class AstGraphBuilder::FrameStateBeforeAndAfter {
if (count >= 2) {
// Add the frame state for before the operation.
DCHECK_EQ(IrOpcode::kDeadValue,
DCHECK_EQ(IrOpcode::kDead,
NodeProperties::GetFrameStateInput(node, 1)->opcode());
NodeProperties::ReplaceFrameStateInput(node, 1, frame_state_before_);
}
......@@ -3761,7 +3761,8 @@ void AstGraphBuilder::PrepareFrameState(Node* node, BailoutId ast_id,
OutputFrameStateCombine combine) {
if (OperatorProperties::GetFrameStateInputCount(node->op()) > 0) {
DCHECK_EQ(1, OperatorProperties::GetFrameStateInputCount(node->op()));
DCHECK_EQ(IrOpcode::kDeadValue,
DCHECK_EQ(IrOpcode::kDead,
NodeProperties::GetFrameStateInput(node, 0)->opcode());
NodeProperties::ReplaceFrameStateInput(
node, 0, environment()->Checkpoint(ast_id, combine));
......@@ -3816,9 +3817,9 @@ Node* AstGraphBuilder::MakeNode(const Operator* op, int value_input_count,
}
for (int i = 0; i < frame_state_count; i++) {
// The frame state will be inserted later. Here we misuse
// the {DeadValue} node as a sentinel to be later overwritten
// the {Dead} node as a sentinel to be later overwritten
// with the real frame state.
*current_input++ = jsgraph()->DeadValue();
*current_input++ = jsgraph()->Dead();
}
if (has_effect) {
*current_input++ = environment_->GetEffectDependency();
......
......@@ -485,11 +485,11 @@ class AstGraphBuilder::Environment : public ZoneObject {
// Mark this environment as being unreachable.
void MarkAsUnreachable() {
UpdateControlDependency(builder()->jsgraph()->DeadControl());
UpdateControlDependency(builder()->jsgraph()->Dead());
liveness_block_ = nullptr;
}
bool IsMarkedAsUnreachable() {
return GetControlDependency()->opcode() == IrOpcode::kDeadControl;
return GetControlDependency()->opcode() == IrOpcode::kDead;
}
// Merge another environment into this one.
......
......@@ -16,8 +16,39 @@ namespace v8 {
namespace internal {
namespace compiler {
namespace {
enum class Decision { kUnknown, kTrue, kFalse };
Decision DecideCondition(Node* const cond) {
switch (cond->opcode()) {
case IrOpcode::kInt32Constant: {
Int32Matcher mcond(cond);
return mcond.Value() ? Decision::kTrue : Decision::kFalse;
}
case IrOpcode::kInt64Constant: {
Int64Matcher mcond(cond);
return mcond.Value() ? Decision::kTrue : Decision::kFalse;
}
case IrOpcode::kHeapConstant: {
HeapObjectMatcher<HeapObject> mcond(cond);
return mcond.Value().handle()->BooleanValue() ? Decision::kTrue
: Decision::kFalse;
}
default:
return Decision::kUnknown;
}
}
} // namespace
Reduction CommonOperatorReducer::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kBranch:
return ReduceBranch(node);
case IrOpcode::kMerge:
return ReduceMerge(node);
case IrOpcode::kEffectPhi:
return ReduceEffectPhi(node);
case IrOpcode::kPhi:
......@@ -31,6 +62,66 @@ Reduction CommonOperatorReducer::Reduce(Node* node) {
}
Reduction CommonOperatorReducer::ReduceBranch(Node* node) {
DCHECK_EQ(IrOpcode::kBranch, node->opcode());
Node* const cond = node->InputAt(0);
Decision const decision = DecideCondition(cond);
if (decision == Decision::kUnknown) return NoChange();
Node* const control = node->InputAt(1);
node->set_op(common()->Dead());
node->TrimInputCount(0);
for (Node* const use : node->uses()) {
switch (use->opcode()) {
case IrOpcode::kIfTrue:
Replace(use, (decision == Decision::kTrue) ? control : node);
break;
case IrOpcode::kIfFalse:
Replace(use, (decision == Decision::kFalse) ? control : node);
break;
default:
UNREACHABLE();
}
}
return Changed(node);
}
Reduction CommonOperatorReducer::ReduceMerge(Node* node) {
DCHECK_EQ(IrOpcode::kMerge, node->opcode());
//
// Check if this is a merge that belongs to an unused diamond, which means
// that:
//
// a) the {Merge} has no {Phi} or {EffectPhi} uses, and
// b) the {Merge} has two inputs, one {IfTrue} and one {IfFalse}, which are
// both owned by the Merge, and
// c) and the {IfTrue} and {IfFalse} nodes point to the same {Branch}.
//
if (node->InputCount() == 2) {
for (Node* const use : node->uses()) {
if (IrOpcode::IsPhiOpcode(use->opcode())) return NoChange();
}
Node* if_true = node->InputAt(0);
Node* if_false = node->InputAt(1);
if (if_true->opcode() != IrOpcode::kIfTrue) std::swap(if_true, if_false);
if (if_true->opcode() == IrOpcode::kIfTrue &&
if_false->opcode() == IrOpcode::kIfFalse &&
if_true->InputAt(0) == if_false->InputAt(0) && if_true->OwnedBy(node) &&
if_false->OwnedBy(node)) {
Node* const branch = if_true->InputAt(0);
DCHECK_EQ(IrOpcode::kBranch, branch->opcode());
DCHECK(branch->OwnedBy(if_true, if_false));
Node* const control = branch->InputAt(1);
// Mark the {branch} as {Dead}.
branch->set_op(common()->Dead());
branch->TrimInputCount(0);
return Replace(control);
}
}
return NoChange();
}
Reduction CommonOperatorReducer::ReduceEffectPhi(Node* node) {
DCHECK_EQ(IrOpcode::kEffectPhi, node->opcode());
int const input_count = node->InputCount() - 1;
......@@ -146,11 +237,15 @@ Reduction CommonOperatorReducer::ReduceSelect(Node* node) {
Node* const vtrue = node->InputAt(1);
Node* const vfalse = node->InputAt(2);
if (vtrue == vfalse) return Replace(vtrue);
switch (DecideCondition(cond)) {
case Decision::kTrue:
return Replace(vtrue);
case Decision::kFalse:
return Replace(vfalse);
case Decision::kUnknown:
break;
}
switch (cond->opcode()) {
case IrOpcode::kHeapConstant: {
HeapObjectMatcher<HeapObject> mcond(cond);
return Replace(mcond.Value().handle()->BooleanValue() ? vtrue : vfalse);
}
case IrOpcode::kFloat32LessThan: {
Float32BinopMatcher mcond(cond);
if (mcond.left().Is(0.0) && mcond.right().Equals(vtrue) &&
......
......@@ -33,6 +33,8 @@ class CommonOperatorReducer final : public AdvancedReducer {
Reduction Reduce(Node* node) final;
private:
Reduction ReduceBranch(Node* node);
Reduction ReduceMerge(Node* node);
Reduction ReduceEffectPhi(Node* node);
Reduction ReducePhi(Node* node);
Reduction ReduceSelect(Node* node);
......
......@@ -116,9 +116,7 @@ std::ostream& operator<<(std::ostream& os, ParameterInfo const& i) {
#define CACHED_OP_LIST(V) \
V(DeadValue, Operator::kPure, 0, 0, 0, 1, 0, 0) \
V(DeadEffect, Operator::kPure, 0, 0, 0, 0, 1, 0) \
V(DeadControl, Operator::kFoldable, 0, 0, 0, 0, 0, 1) \
V(Dead, Operator::kFoldable, 0, 0, 0, 1, 1, 1) \
V(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
V(IfSuccess, Operator::kKontrol, 0, 0, 1, 0, 0, 1) \
......
......@@ -96,6 +96,7 @@ class CommonOperatorBuilder final : public ZoneObject {
public:
explicit CommonOperatorBuilder(Zone* zone);
const Operator* Dead();
const Operator* End(size_t control_input_count);
const Operator* Branch(BranchHint = BranchHint::kNone);
const Operator* IfTrue();
......@@ -110,10 +111,6 @@ class CommonOperatorBuilder final : public ZoneObject {
const Operator* Return();
const Operator* Terminate();
const Operator* DeadValue();
const Operator* DeadEffect();
const Operator* DeadControl();
const Operator* Start(int num_formal_parameters);
const Operator* Loop(int control_input_count);
const Operator* Merge(int control_input_count);
......
// Copyright 2014 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/common-operator.h"
#include "src/compiler/common-operator-reducer.h"
#include "src/compiler/control-reducer.h"
#include "src/compiler/graph.h"
#include "src/compiler/graph-reducer.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-marker.h"
#include "src/compiler/node-matchers.h"
#include "src/compiler/node-properties.h"
#include "src/zone-containers.h"
namespace v8 {
namespace internal {
namespace compiler {
#define TRACE(...) \
do { \
if (FLAG_trace_turbo_reduction) PrintF(__VA_ARGS__); \
} while (false)
enum Decision { kFalse, kUnknown, kTrue };
class ControlReducerImpl final : public AdvancedReducer {
public:
Zone* zone_;
JSGraph* jsgraph_;
int max_phis_for_select_;
ControlReducerImpl(Editor* editor, Zone* zone, JSGraph* jsgraph)
: AdvancedReducer(editor),
zone_(zone),
jsgraph_(jsgraph),
max_phis_for_select_(0) {}
Graph* graph() { return jsgraph_->graph(); }
CommonOperatorBuilder* common() { return jsgraph_->common(); }
Node* dead() { return jsgraph_->DeadControl(); }
//===========================================================================
// Reducer implementation: perform reductions on a node.
//===========================================================================
Reduction Reduce(Node* node) override {
if (node->op()->ControlInputCount() == 1 ||
node->opcode() == IrOpcode::kLoop) {
// If a node has only one control input and it is dead, replace with dead.
Node* control = NodeProperties::GetControlInput(node);
if (control->opcode() == IrOpcode::kDeadControl) {
TRACE("ControlDead: #%d:%s\n", node->id(), node->op()->mnemonic());
return Replace(control);
}
}
Node* result = node;
// Reduce branches, phis, and merges.
switch (node->opcode()) {
case IrOpcode::kBranch:
result = ReduceBranch(node);
break;
case IrOpcode::kIfTrue:
result = ReduceIfProjection(node, kTrue);
break;
case IrOpcode::kIfFalse:
result = ReduceIfProjection(node, kFalse);
break;
case IrOpcode::kLoop: // fallthrough
case IrOpcode::kMerge:
result = ReduceMerge(node);
break;
case IrOpcode::kEnd:
result = ReduceEnd(node);
break;
default:
break;
}
return result == node ? NoChange() : Replace(result);
}
// Try to statically fold a condition.
Decision DecideCondition(Node* cond) {
switch (cond->opcode()) {
case IrOpcode::kInt32Constant:
return Int32Matcher(cond).Is(0) ? kFalse : kTrue;
case IrOpcode::kInt64Constant:
return Int64Matcher(cond).Is(0) ? kFalse : kTrue;
case IrOpcode::kHeapConstant: {
Handle<Object> object =
HeapObjectMatcher<Object>(cond).Value().handle();
return object->BooleanValue() ? kTrue : kFalse;
}
default:
break;
}
return kUnknown;
}
// Reduce branches.
Node* ReduceBranch(Node* branch) {
if (DecideCondition(branch->InputAt(0)) != kUnknown) {
for (Node* use : branch->uses()) Revisit(use);
}
return branch;
}
// Reduce end by trimming away dead inputs.
Node* ReduceEnd(Node* node) {
// Count the number of live inputs.
int live = 0;
for (int index = 0; index < node->InputCount(); ++index) {
// Skip dead inputs.
if (node->InputAt(index)->opcode() == IrOpcode::kDeadControl) continue;
// Compact live inputs.
if (index != live) node->ReplaceInput(live, node->InputAt(index));
++live;
}
TRACE("ReduceEnd: #%d:%s (%d of %d live)\n", node->id(),
node->op()->mnemonic(), live, node->InputCount());
if (live == 0) return dead(); // No remaining inputs.
if (live < node->InputCount()) {
node->set_op(common()->End(live));
node->TrimInputCount(live);
}
return node;
}
// Reduce merges by trimming away dead inputs from the merge and phis.
Node* ReduceMerge(Node* node) {
// Count the number of live inputs.
int live = 0;
int index = 0;
int live_index = 0;
for (Node* const input : node->inputs()) {
if (input->opcode() != IrOpcode::kDeadControl) {
live++;
live_index = index;
}
index++;
}
TRACE("ReduceMerge: #%d:%s (%d of %d live)\n", node->id(),
node->op()->mnemonic(), live, index);
if (live == 0) return dead(); // no remaining inputs.
// Gather terminates, phis and effect phis to be edited.
NodeVector phis(zone_);
Node* terminate = nullptr;
for (Node* const use : node->uses()) {
if (NodeProperties::IsPhi(use)) {
phis.push_back(use);
} else if (use->opcode() == IrOpcode::kTerminate) {
DCHECK_NULL(terminate);
terminate = use;
}
}
if (live == 1) {
// All phis are redundant. Replace them with their live input.
for (Node* const phi : phis) {
Replace(phi, phi->InputAt(live_index));
}
// The terminate is not needed anymore.
if (terminate) Replace(terminate, dead());
// The merge itself is redundant.
return node->InputAt(live_index);
}
DCHECK_LE(2, live);
if (live < node->InputCount()) {
// Edit phis in place, removing dead inputs and revisiting them.
for (Node* const phi : phis) {
TRACE(" PhiInMerge: #%d:%s (%d live)\n", phi->id(),
phi->op()->mnemonic(), live);
RemoveDeadInputs(node, phi);
Revisit(phi);
}
// Edit the merge in place, removing dead inputs.
RemoveDeadInputs(node, node);
}
DCHECK_EQ(live, node->InputCount());
// Try to remove dead diamonds or introduce selects.
if (live == 2 && CheckPhisForSelect(phis)) {
DiamondMatcher matcher(node);
if (matcher.Matched() && matcher.IfProjectionsAreOwned()) {
// Dead diamond, i.e. neither the IfTrue nor the IfFalse nodes
// have uses except for the Merge. Remove the branch if there
// are no phis or replace phis with selects.
Node* control = NodeProperties::GetControlInput(matcher.Branch());
if (phis.size() == 0) {
// No phis. Remove the branch altogether.
TRACE(" DeadDiamond: #%d:Branch #%d:IfTrue #%d:IfFalse\n",
matcher.Branch()->id(), matcher.IfTrue()->id(),
matcher.IfFalse()->id());
return control;
} else {
// A small number of phis. Replace with selects.
Node* cond = matcher.Branch()->InputAt(0);
for (Node* phi : phis) {
Node* select = graph()->NewNode(
common()->Select(OpParameter<MachineType>(phi),
BranchHintOf(matcher.Branch()->op())),
cond, matcher.TrueInputOf(phi), matcher.FalseInputOf(phi));
TRACE(" MatchSelect: #%d:Branch #%d:IfTrue #%d:IfFalse -> #%d\n",
matcher.Branch()->id(), matcher.IfTrue()->id(),
matcher.IfFalse()->id(), select->id());
Replace(phi, select);
}
return control;
}
}
}
return node;
}
bool CheckPhisForSelect(const NodeVector& phis) {
if (phis.size() > static_cast<size_t>(max_phis_for_select_)) return false;
for (Node* phi : phis) {
if (phi->opcode() != IrOpcode::kPhi) return false; // no EffectPhis.
}
return true;
}
// Reduce if projections if the branch has a constant input.
Node* ReduceIfProjection(Node* node, Decision decision) {
Node* branch = node->InputAt(0);
DCHECK_EQ(IrOpcode::kBranch, branch->opcode());
Decision result = DecideCondition(branch->InputAt(0));
if (result == decision) {
// Fold a branch by replacing IfTrue/IfFalse with the branch control.
TRACE(" BranchReduce: #%d:%s => #%d:%s\n", branch->id(),
branch->op()->mnemonic(), node->id(), node->op()->mnemonic());
return branch->InputAt(1);
}
return result == kUnknown ? node : dead();
}
// Remove inputs to {node} corresponding to the dead inputs to {merge}
// and compact the remaining inputs, updating the operator.
void RemoveDeadInputs(Node* merge, Node* node) {
int live = 0;
for (int i = 0; i < merge->InputCount(); i++) {
// skip dead inputs.
if (merge->InputAt(i)->opcode() == IrOpcode::kDeadControl) continue;
// compact live inputs.
if (live != i) node->ReplaceInput(live, node->InputAt(i));
live++;
}
// compact remaining inputs.
int total = live;
for (int i = merge->InputCount(); i < node->InputCount(); i++) {
if (total != i) node->ReplaceInput(total, node->InputAt(i));
total++;
}
DCHECK_EQ(total, live + node->InputCount() - merge->InputCount());
DCHECK_NE(total, node->InputCount());
node->TrimInputCount(total);
node->set_op(common()->ResizeMergeOrPhi(node->op(), live));
}
};
void ControlReducer::ReduceGraph(Zone* zone, JSGraph* jsgraph,
int max_phis_for_select) {
GraphReducer graph_reducer(zone, jsgraph->graph());
CommonOperatorReducer common(&graph_reducer, jsgraph->graph(),
jsgraph->common(), jsgraph->machine());
ControlReducerImpl impl(&graph_reducer, zone, jsgraph);
impl.max_phis_for_select_ = max_phis_for_select;
graph_reducer.AddReducer(&impl);
graph_reducer.AddReducer(&common);
graph_reducer.ReduceGraph();
}
namespace {
class DummyEditor final : public AdvancedReducer::Editor {
public:
void Replace(Node* node, Node* replacement) final {
node->ReplaceUses(replacement);
}
void Revisit(Node* node) final {}
void ReplaceWithValue(Node* node, Node* value, Node* effect,
Node* control) final {}
};
} // namespace
Node* ControlReducer::ReduceMerge(JSGraph* jsgraph, Node* node,
int max_phis_for_select) {
Zone zone;
DummyEditor editor;
ControlReducerImpl impl(&editor, &zone, jsgraph);
impl.max_phis_for_select_ = max_phis_for_select;
return impl.ReduceMerge(node);
}
Node* ControlReducer::ReduceIfNodeForTesting(JSGraph* jsgraph, Node* node) {
Zone zone;
DummyEditor editor;
ControlReducerImpl impl(&editor, &zone, jsgraph);
switch (node->opcode()) {
case IrOpcode::kIfTrue:
return impl.ReduceIfProjection(node, kTrue);
case IrOpcode::kIfFalse:
return impl.ReduceIfProjection(node, kFalse);
default:
return node;
}
}
} // namespace compiler
} // namespace internal
} // namespace v8
// Copyright 2014 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.
#ifndef V8_COMPILER_CONTROL_REDUCER_H_
#define V8_COMPILER_CONTROL_REDUCER_H_
namespace v8 {
namespace internal {
// Forward declarations.
class Zone;
namespace compiler {
// Forward declarations.
class JSGraph;
class CommonOperatorBuilder;
class Node;
class ControlReducer {
public:
// Perform branch folding and dead code elimination on the graph.
static void ReduceGraph(Zone* zone, JSGraph* graph,
int max_phis_for_select = 0);
// Reduces a single merge node and attached phis.
static Node* ReduceMerge(JSGraph* graph, Node* node,
int max_phis_for_select = 0);
// Testing interface.
static Node* ReduceIfNodeForTesting(JSGraph* graph, Node* node);
};
}
}
} // namespace v8::internal::compiler
#endif // V8_COMPILER_CONTROL_REDUCER_H_
// 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/node-properties.h"
#include "src/compiler/operator-properties.h"
namespace v8 {
namespace internal {
namespace compiler {
DeadCodeElimination::DeadCodeElimination(Editor* editor, Graph* graph,
CommonOperatorBuilder* common)
: AdvancedReducer(editor),
graph_(graph),
common_(common),
dead_(graph->NewNode(common->Dead())) {}
Reduction DeadCodeElimination::Reduce(Node* node) {
switch (node->opcode()) {
case IrOpcode::kEnd:
return ReduceEnd(node);
case IrOpcode::kLoop:
case IrOpcode::kMerge:
return ReduceLoopOrMerge(node);
default:
return ReduceNode(node);
}
UNREACHABLE();
return NoChange();
}
Reduction DeadCodeElimination::ReduceEnd(Node* node) {
DCHECK_EQ(IrOpcode::kEnd, node->opcode());
int const input_count = node->InputCount();
DCHECK_LE(1, input_count);
int live_input_count = 0;
for (int i = 0; i < input_count; ++i) {
Node* const input = node->InputAt(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 < input_count) {
node->set_op(common()->End(live_input_count));
node->TrimInputCount(live_input_count);
return Changed(node);
}
DCHECK_EQ(input_count, live_input_count);
return NoChange();
}
Reduction DeadCodeElimination::ReduceLoopOrMerge(Node* node) {
DCHECK(IrOpcode::IsMergeOpcode(node->opcode()));
int const input_count = node->InputCount();
DCHECK_LE(1, input_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 < input_count; ++i) {
Node* const input = node->InputAt(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(input_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) {
// 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::kTerminate) {
DCHECK_EQ(IrOpcode::kLoop, node->opcode());
Replace(use, dead());
}
}
return Replace(node->InputAt(0));
}
DCHECK_LE(2, live_input_count);
DCHECK_LE(live_input_count, input_count);
// Trim input count for the {Merge} or {Loop} node.
if (live_input_count < input_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::ReduceNode(Node* node) {
// If {node} has exactly one control input and this is {Dead},
// replace {node} with {Dead}.
int const control_input_count = node->op()->ControlInputCount();
if (control_input_count == 0) return NoChange();
DCHECK_EQ(1, control_input_count);
Node* control = NodeProperties::GetControlInput(node);
if (control->opcode() == IrOpcode::kDead) return Replace(control);
return NoChange();
}
void DeadCodeElimination::TrimMergeOrPhi(Node* node, int size) {
const Operator* const op = common()->ResizeMergeOrPhi(node->op(), size);
node->TrimInputCount(OperatorProperties::GetTotalInputCount(op));
node->set_op(op);
}
} // namespace compiler
} // namespace internal
} // namespace v8
// 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.
#ifndef V8_COMPILER_DEAD_CODE_ELIMINATION_H_
#define V8_COMPILER_DEAD_CODE_ELIMINATION_H_
#include "src/compiler/graph-reducer.h"
namespace v8 {
namespace internal {
namespace compiler {
// Forward declarations.
class CommonOperatorBuilder;
// Propagates {Dead} control through the graph and thereby removes dead code.
// Note that this does not include trimming dead uses from the graph, and it
// also does not include detecting dead code by any other means than seeing a
// {Dead} control input; that is left to other reducers.
class DeadCodeElimination final : public AdvancedReducer {
public:
DeadCodeElimination(Editor* editor, Graph* graph,
CommonOperatorBuilder* common);
~DeadCodeElimination() final {}
Reduction Reduce(Node* node) final;
private:
Reduction ReduceEnd(Node* node);
Reduction ReduceLoopOrMerge(Node* node);
Reduction ReduceNode(Node* node);
void TrimMergeOrPhi(Node* node, int size);
Graph* graph() const { return graph_; }
CommonOperatorBuilder* common() const { return common_; }
Node* dead() const { return dead_; }
Graph* const graph_;
CommonOperatorBuilder* const common_;
Node* const dead_;
DISALLOW_COPY_AND_ASSIGN(DeadCodeElimination);
};
} // namespace compiler
} // namespace internal
} // namespace v8
#endif // V8_COMPILER_DEAD_CODE_ELIMINATION_H_
......@@ -256,7 +256,7 @@ void GraphVisualizer::PrintNode(Node* node, bool gray) {
os_ << " shape=\"record\"\n";
switch (node->opcode()) {
case IrOpcode::kEnd:
case IrOpcode::kDeadControl:
case IrOpcode::kDead:
case IrOpcode::kStart:
os_ << " style=\"diagonals\"\n";
break;
......
......@@ -203,13 +203,8 @@ Node* JSGraph::EmptyFrameState() {
}
Node* JSGraph::DeadValue() {
return CACHED(kDeadValue, graph()->NewNode(common()->DeadValue()));
}
Node* JSGraph::DeadControl() {
return CACHED(kDeadControl, graph()->NewNode(common()->DeadControl()));
Node* JSGraph::Dead() {
return CACHED(kDead, graph()->NewNode(common()->Dead()));
}
......
......@@ -117,11 +117,8 @@ class JSGraph : public ZoneObject {
// cannot deopt.
Node* EmptyFrameState();
// Creates a value node that servers as value input for dead nodes.
Node* DeadValue();
// Creates a control node that serves as control dependency for dead nodes.
Node* DeadControl();
// Create a control node that serves as dependency for dead nodes.
Node* Dead();
JSOperatorBuilder* javascript() const { return javascript_; }
CommonOperatorBuilder* common() const { return common_; }
......@@ -145,8 +142,7 @@ class JSGraph : public ZoneObject {
kOneConstant,
kNaNConstant,
kEmptyFrameState,
kDeadValue,
kDeadControl,
kDead,
kNumCachedNodes // Must remain last.
};
......
......@@ -62,8 +62,8 @@ class JSCallFunctionAccessor {
class CopyVisitor {
public:
CopyVisitor(Graph* source_graph, Graph* target_graph, Zone* temp_zone)
: sentinel_op_(IrOpcode::kDeadControl, Operator::kNoProperties,
"Sentinel", 0, 0, 0, 0, 0, 0),
: sentinel_op_(IrOpcode::kDead, Operator::kNoProperties, "Sentinel", 0, 0,
0, 0, 0, 0),
sentinel_(target_graph->NewNode(&sentinel_op_)),
copies_(source_graph->NodeCount(), sentinel_, temp_zone),
source_graph_(source_graph),
......
......@@ -212,7 +212,7 @@ PeeledIteration* LoopPeeler::Peel(Graph* graph, CommonOperatorBuilder* common,
5 + (loop->TotalSize() + exits.size() + rets.size()) * 2;
Peeling peeling(graph, tmp_zone, estimated_peeled_size, &iter->node_pairs_);
Node* dead = graph->NewNode(common->DeadControl());
Node* dead = graph->NewNode(common->Dead());
// Map the loop header nodes to their entry values.
for (Node* node : loop_tree->HeaderNodes(loop)) {
......
......@@ -26,7 +26,6 @@
V(Terminate) \
V(OsrNormalEntry) \
V(OsrLoopEntry) \
V(DeadControl) \
V(Throw) \
V(End)
......@@ -58,9 +57,7 @@
#define COMMON_OP_LIST(V) \
CONSTANT_OP_LIST(V) \
INNER_OP_LIST(V) \
V(DeadEffect) \
V(DeadValue)
V(Dead)
// Opcodes for JavaScript operators.
#define JS_COMPARE_BINOP_LIST(V) \
......@@ -321,7 +318,7 @@ class IrOpcode {
// Returns true if opcode for common operator.
static bool IsCommonOpcode(Value value) {
return kStart <= value && value <= kDeadValue;
return kStart <= value && value <= kDead;
}
// Returns true if opcode for control operator.
......
......@@ -117,7 +117,7 @@ int OperatorProperties::GetTotalInputCount(const Operator* op) {
bool OperatorProperties::IsBasicBlockBegin(const Operator* op) {
Operator::Opcode const opcode = op->opcode();
return opcode == IrOpcode::kStart || opcode == IrOpcode::kEnd ||
opcode == IrOpcode::kDeadControl || opcode == IrOpcode::kLoop ||
opcode == IrOpcode::kDead || opcode == IrOpcode::kLoop ||
opcode == IrOpcode::kMerge || opcode == IrOpcode::kIfTrue ||
opcode == IrOpcode::kIfFalse || opcode == IrOpcode::kIfSuccess ||
opcode == IrOpcode::kIfException || opcode == IrOpcode::kIfValue ||
......
......@@ -5,9 +5,11 @@
#include "src/compiler.h"
#include "src/compiler/all-nodes.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/control-reducer.h"
#include "src/compiler/common-operator-reducer.h"
#include "src/compiler/dead-code-elimination.h"
#include "src/compiler/frame.h"
#include "src/compiler/graph.h"
#include "src/compiler/graph-reducer.h"
#include "src/compiler/graph-trimmer.h"
#include "src/compiler/graph-visualizer.h"
#include "src/compiler/js-graph.h"
......@@ -317,7 +319,7 @@ void OsrHelper::Deconstruct(JSGraph* jsgraph, CommonOperatorBuilder* common,
// Analyze the graph to determine how deeply nested the OSR loop is.
LoopTree* loop_tree = LoopFinder::BuildLoopTree(graph, tmp_zone);
Node* dead = jsgraph->DeadControl();
Node* dead = jsgraph->Dead();
LoopTree::Loop* loop = loop_tree->ContainingLoop(osr_loop);
if (loop->depth() > 0) {
PeelOuterLoopsForOsr(graph, common, tmp_zone, dead, loop_tree, loop,
......@@ -331,16 +333,28 @@ void OsrHelper::Deconstruct(JSGraph* jsgraph, CommonOperatorBuilder* common,
osr_loop_entry->ReplaceUses(graph->start());
osr_loop_entry->Kill();
// Normally the control reducer removes loops whose first input is dead,
// but we need to avoid that because the osr_loop is reachable through
// the second input, so reduce it and its phis manually.
osr_loop->ReplaceInput(0, dead);
Node* node = ControlReducer::ReduceMerge(jsgraph, osr_loop);
if (node != osr_loop) osr_loop->ReplaceUses(node);
// Remove the first input to the {osr_loop}.
int const live_input_count = osr_loop->InputCount() - 1;
CHECK_NE(0, live_input_count);
for (Node* const use : osr_loop->uses()) {
if (NodeProperties::IsPhi(use)) {
use->set_op(common->ResizeMergeOrPhi(use->op(), live_input_count));
use->RemoveInput(0);
}
}
osr_loop->set_op(common->ResizeMergeOrPhi(osr_loop->op(), live_input_count));
osr_loop->RemoveInput(0);
// Run control reduction and graph trimming.
ControlReducer::ReduceGraph(tmp_zone, jsgraph);
GraphTrimmer trimmer(tmp_zone, jsgraph->graph());
// TODO(bmeurer): The OSR deconstruction could be a regular reducer and play
// nice together with the rest, instead of having this custom stuff here.
GraphReducer graph_reducer(tmp_zone, graph);
DeadCodeElimination dce(&graph_reducer, graph, common);
CommonOperatorReducer cor(&graph_reducer, graph, common, jsgraph->machine());
graph_reducer.AddReducer(&dce);
graph_reducer.AddReducer(&cor);
graph_reducer.ReduceGraph();
GraphTrimmer trimmer(tmp_zone, graph);
NodeVector roots(tmp_zone);
jsgraph->GetCachedNodes(&roots);
trimmer.TrimGraph(roots.begin(), roots.end());
......
......@@ -16,7 +16,7 @@
#include "src/compiler/code-generator.h"
#include "src/compiler/common-operator-reducer.h"
#include "src/compiler/control-flow-optimizer.h"
#include "src/compiler/control-reducer.h"
#include "src/compiler/dead-code-elimination.h"
#include "src/compiler/frame-elider.h"
#include "src/compiler/graph-replay.h"
#include "src/compiler/graph-trimmer.h"
......@@ -407,8 +407,8 @@ class SourcePositionWrapper final : public Reducer {
class JSGraphReducer final : public GraphReducer {
public:
JSGraphReducer(JSGraph* jsgraph, Zone* zone)
: GraphReducer(zone, jsgraph->graph(), jsgraph->DeadValue(),
jsgraph->DeadControl()) {}
: GraphReducer(zone, jsgraph->graph(), jsgraph->TheHoleConstant(),
jsgraph->Dead()) {}
~JSGraphReducer() final {}
};
......@@ -640,7 +640,13 @@ struct ChangeLoweringPhase {
struct EarlyControlReductionPhase {
static const char* phase_name() { return "early control reduction"; }
void Run(PipelineData* data, Zone* temp_zone) {
ControlReducer::ReduceGraph(temp_zone, data->jsgraph(), 0);
GraphReducer graph_reducer(temp_zone, data->graph());
DeadCodeElimination dce(&graph_reducer, data->graph(), data->common());
CommonOperatorReducer common(&graph_reducer, data->graph(), data->common(),
data->machine());
graph_reducer.AddReducer(&dce);
graph_reducer.AddReducer(&common);
graph_reducer.ReduceGraph();
}
};
......@@ -648,7 +654,13 @@ struct EarlyControlReductionPhase {
struct LateControlReductionPhase {
static const char* phase_name() { return "late control reduction"; }
void Run(PipelineData* data, Zone* temp_zone) {
ControlReducer::ReduceGraph(temp_zone, data->jsgraph(), 0);
GraphReducer graph_reducer(temp_zone, data->graph());
DeadCodeElimination dce(&graph_reducer, data->graph(), data->common());
CommonOperatorReducer common(&graph_reducer, data->graph(), data->common(),
data->machine());
graph_reducer.AddReducer(&dce);
graph_reducer.AddReducer(&common);
graph_reducer.ReduceGraph();
}
};
......
......@@ -497,7 +497,7 @@ class RepresentationSelector {
// Common operators.
//------------------------------------------------------------------
case IrOpcode::kStart:
case IrOpcode::kDeadControl:
case IrOpcode::kDead:
return VisitLeaf(node, 0);
case IrOpcode::kParameter: {
// TODO(titzer): use representation from linkage.
......
......@@ -268,7 +268,6 @@ class Typer::Visitor : public Reducer {
DECLARE_CASE(Terminate)
DECLARE_CASE(OsrNormalEntry)
DECLARE_CASE(OsrLoopEntry)
DECLARE_CASE(DeadControl)
DECLARE_CASE(Throw)
DECLARE_CASE(End)
#undef DECLARE_CASE
......@@ -313,7 +312,6 @@ class Typer::Visitor : public Reducer {
DECLARE_CASE(Terminate)
DECLARE_CASE(OsrNormalEntry)
DECLARE_CASE(OsrLoopEntry)
DECLARE_CASE(DeadControl)
DECLARE_CASE(Throw)
DECLARE_CASE(End)
#undef DECLARE_CASE
......@@ -742,17 +740,6 @@ Bounds Typer::Visitor::TypeFinish(Node* node) {
}
Bounds Typer::Visitor::TypeDeadValue(Node* node) {
return Bounds(Type::None(zone()), Type::Any(zone()));
}
Bounds Typer::Visitor::TypeDeadEffect(Node* node) {
UNREACHABLE();
return Bounds();
}
Bounds Typer::Visitor::TypeFrameState(Node* node) {
// TODO(rossberg): Ideally FrameState wouldn't have a value output.
return Bounds(Type::None(zone()), Type::Internal(zone()));
......@@ -780,6 +767,11 @@ Bounds Typer::Visitor::TypeProjection(Node* node) {
}
Bounds Typer::Visitor::TypeDead(Node* node) {
return Bounds::Unbounded(zone());
}
// JS comparison operators.
......
......@@ -192,9 +192,7 @@ void Verifier::Visitor::Check(Node* node) {
// Type is empty.
CheckNotTyped(node);
break;
case IrOpcode::kDeadValue:
case IrOpcode::kDeadEffect:
case IrOpcode::kDeadControl:
case IrOpcode::kDead:
// Dead is never connected to the graph.
// TODO(mstarzinger): Make the GraphReducer immediately perform control
// reduction in case control is killed. This will prevent {Dead} from
......
......@@ -54,7 +54,6 @@
'compiler/test-basic-block-profiler.cc',
'compiler/test-branch-combine.cc',
'compiler/test-changes-lowering.cc',
'compiler/test-control-reducer.cc',
'compiler/test-gap-resolver.cc',
'compiler/test-graph-visualizer.cc',
'compiler/test-instruction.cc',
......
// Copyright 2014 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/v8.h"
#include "test/cctest/cctest.h"
#include "src/base/bits.h"
#include "src/compiler/all-nodes.h"
#include "src/compiler/common-operator.h"
#include "src/compiler/control-reducer.h"
#include "src/compiler/graph.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/node-properties.h"
using namespace v8::internal;
using namespace v8::internal::compiler;
static const size_t kNumLeafs = 4;
enum Decision { kFalse, kUnknown, kTrue };
// TODO(titzer): convert this whole file into unit tests.
static int CheckInputs(Node* node, Node* i0 = NULL, Node* i1 = NULL,
Node* i2 = NULL) {
int count = 3;
if (i2 == NULL) count = 2;
if (i1 == NULL) count = 1;
if (i0 == NULL) count = 0;
CHECK_EQ(count, node->InputCount());
if (i0 != NULL) CHECK_EQ(i0, node->InputAt(0));
if (i1 != NULL) CHECK_EQ(i1, node->InputAt(1));
if (i2 != NULL) CHECK_EQ(i2, node->InputAt(2));
return count;
}
static int CheckMerge(Node* node, Node* i0 = NULL, Node* i1 = NULL,
Node* i2 = NULL) {
CHECK_EQ(IrOpcode::kMerge, node->opcode());
int count = CheckInputs(node, i0, i1, i2);
CHECK_EQ(count, node->op()->ControlInputCount());
return count;
}
static int CheckLoop(Node* node, Node* i0 = NULL, Node* i1 = NULL,
Node* i2 = NULL) {
CHECK_EQ(IrOpcode::kLoop, node->opcode());
int count = CheckInputs(node, i0, i1, i2);
CHECK_EQ(count, node->op()->ControlInputCount());
return count;
}
bool IsUsedBy(Node* a, Node* b) {
auto const uses = a->uses();
return std::find(uses.begin(), uses.end(), b) != uses.end();
}
// A helper for all tests dealing with ControlTester.
class ControlReducerTester : HandleAndZoneScope {
public:
ControlReducerTester()
: isolate(main_isolate()),
common(main_zone()),
graph(main_zone()),
jsgraph(main_isolate(), &graph, &common, NULL, NULL),
start(graph.NewNode(common.Start(1))),
end(graph.NewNode(common.End(1), start)),
p0(graph.NewNode(common.Parameter(0), start)),
zero(jsgraph.Int32Constant(0)),
one(jsgraph.OneConstant()),
half(jsgraph.Constant(0.5)),
self(graph.NewNode(common.Int32Constant(0xaabbccdd))),
dead(graph.NewNode(common.DeadControl())) {
graph.SetEnd(end);
graph.SetStart(start);
leaf[0] = zero;
leaf[1] = one;
leaf[2] = half;
leaf[3] = p0;
}
Isolate* isolate;
CommonOperatorBuilder common;
Graph graph;
JSGraph jsgraph;
Node* start;
Node* end;
Node* p0;
Node* zero;
Node* one;
Node* half;
Node* self;
Node* dead;
Node* leaf[kNumLeafs];
Node* Phi(Node* a) {
return SetSelfReferences(graph.NewNode(op(1, false), a, start));
}
Node* Phi(Node* a, Node* b) {
return SetSelfReferences(graph.NewNode(op(2, false), a, b, start));
}
Node* Phi(Node* a, Node* b, Node* c) {
return SetSelfReferences(graph.NewNode(op(3, false), a, b, c, start));
}
Node* Phi(Node* a, Node* b, Node* c, Node* d) {
return SetSelfReferences(graph.NewNode(op(4, false), a, b, c, d, start));
}
Node* EffectPhi(Node* a) {
return SetSelfReferences(graph.NewNode(op(1, true), a, start));
}
Node* EffectPhi(Node* a, Node* b) {
return SetSelfReferences(graph.NewNode(op(2, true), a, b, start));
}
Node* EffectPhi(Node* a, Node* b, Node* c) {
return SetSelfReferences(graph.NewNode(op(3, true), a, b, c, start));
}
Node* EffectPhi(Node* a, Node* b, Node* c, Node* d) {
return SetSelfReferences(graph.NewNode(op(4, true), a, b, c, d, start));
}
Node* SetSelfReferences(Node* node) {
for (Edge edge : node->input_edges()) {
if (edge.to() == self) node->ReplaceInput(edge.index(), node);
}
return node;
}
const Operator* op(int count, bool effect) {
return effect ? common.EffectPhi(count) : common.Phi(kMachAnyTagged, count);
}
void ReduceGraph() { ControlReducer::ReduceGraph(main_zone(), &jsgraph); }
void ReduceMerge(Node* expect, Node* merge) {
Node* result = ControlReducer::ReduceMerge(&jsgraph, merge);
CHECK_EQ(expect, result);
}
void ReduceMergeIterative(Node* expect, Node* merge) {
p0->ReplaceInput(0, start); // hack: parameters may be trimmed.
Node* end = graph.NewNode(common.End(1), merge);
graph.SetEnd(end);
ReduceGraph();
CheckInputs(end, expect);
}
void ReduceBranch(Decision expected, Node* branch) {
Node* control = branch->InputAt(1);
for (Node* use : branch->uses()) {
if (use->opcode() == IrOpcode::kIfTrue) {
Node* result = ControlReducer::ReduceIfNodeForTesting(&jsgraph, use);
if (expected == kTrue) CHECK_EQ(control, result);
if (expected == kFalse) {
CHECK_EQ(IrOpcode::kDeadControl, result->opcode());
}
if (expected == kUnknown) CHECK_EQ(use, result);
} else if (use->opcode() == IrOpcode::kIfFalse) {
Node* result = ControlReducer::ReduceIfNodeForTesting(&jsgraph, use);
if (expected == kFalse) CHECK_EQ(control, result);
if (expected == kTrue) {
CHECK_EQ(IrOpcode::kDeadControl, result->opcode());
}
if (expected == kUnknown) CHECK_EQ(use, result);
} else {
UNREACHABLE();
}
}
}
Node* Return(Node* val, Node* effect, Node* control) {
Node* ret = graph.NewNode(common.Return(), val, effect, control);
end->ReplaceInput(0, ret);
return ret;
}
};
struct Branch {
Node* branch;
Node* if_true;
Node* if_false;
Branch(ControlReducerTester& R, Node* cond, Node* control = NULL) {
if (control == NULL) control = R.start;
branch = R.graph.NewNode(R.common.Branch(), cond, control);
if_true = R.graph.NewNode(R.common.IfTrue(), branch);
if_false = R.graph.NewNode(R.common.IfFalse(), branch);
}
};
// TODO(titzer): use the diamonds from src/compiler/diamond.h here.
struct Diamond {
Node* branch;
Node* if_true;
Node* if_false;
Node* merge;
Node* phi;
Diamond(ControlReducerTester& R, Node* cond) {
branch = R.graph.NewNode(R.common.Branch(), cond, R.start);
if_true = R.graph.NewNode(R.common.IfTrue(), branch);
if_false = R.graph.NewNode(R.common.IfFalse(), branch);
merge = R.graph.NewNode(R.common.Merge(2), if_true, if_false);
phi = NULL;
}
Diamond(ControlReducerTester& R, Node* cond, Node* tv, Node* fv) {
branch = R.graph.NewNode(R.common.Branch(), cond, R.start);
if_true = R.graph.NewNode(R.common.IfTrue(), branch);
if_false = R.graph.NewNode(R.common.IfFalse(), branch);
merge = R.graph.NewNode(R.common.Merge(2), if_true, if_false);
phi = R.graph.NewNode(R.common.Phi(kMachAnyTagged, 2), tv, fv, merge);
}
void chain(Diamond& that) { branch->ReplaceInput(1, that.merge); }
// Nest {this} into either the if_true or if_false branch of {that}.
void nest(Diamond& that, bool if_true) {
if (if_true) {
branch->ReplaceInput(1, that.if_true);
that.merge->ReplaceInput(0, merge);
} else {
branch->ReplaceInput(1, that.if_false);
that.merge->ReplaceInput(1, merge);
}
}
};
struct While {
Node* branch;
Node* if_true;
Node* exit;
Node* loop;
While(ControlReducerTester& R, Node* cond) {
loop = R.graph.NewNode(R.common.Loop(2), R.start, R.start);
branch = R.graph.NewNode(R.common.Branch(), cond, loop);
if_true = R.graph.NewNode(R.common.IfTrue(), branch);
exit = R.graph.NewNode(R.common.IfFalse(), branch);
loop->ReplaceInput(1, if_true);
}
void chain(Node* control) { loop->ReplaceInput(0, control); }
};
// Helper for checking that nodes are *not* reachable from end.
struct DeadChecker {
Zone zone;
AllNodes nodes;
explicit DeadChecker(Graph* graph) : zone(), nodes(&zone, graph) {}
void Check(Node* node) { CHECK(!nodes.IsLive(node)); }
void Check(Diamond& d) {
Check(d.branch);
Check(d.if_true);
Check(d.if_false);
Check(d.merge);
if (d.phi != NULL) Check(d.phi);
}
void CheckLive(Diamond& d, bool live_phi = true) {
CheckInputs(d.merge, d.if_true, d.if_false);
CheckInputs(d.if_true, d.branch);
CheckInputs(d.if_false, d.branch);
if (d.phi != NULL) {
if (live_phi) {
CHECK_EQ(3, d.phi->InputCount());
CHECK_EQ(d.merge, d.phi->InputAt(2));
} else {
Check(d.phi);
}
}
}
};
TEST(CMergeReduce_simple1) {
ControlReducerTester R;
Node* merge = R.graph.NewNode(R.common.Merge(1), R.start);
R.ReduceMerge(R.start, merge);
}
TEST(CMergeReduce_simple2) {
ControlReducerTester R;
Node* merge1 = R.graph.NewNode(R.common.Merge(1), R.start);
Node* merge2 = R.graph.NewNode(R.common.Merge(1), merge1);
R.ReduceMerge(merge1, merge2);
R.ReduceMergeIterative(R.start, merge2);
}
TEST(CMergeReduce_none1) {
ControlReducerTester R;
Node* merge = R.graph.NewNode(R.common.Merge(2), R.start, R.start);
R.ReduceMerge(merge, merge);
}
TEST(CMergeReduce_none2) {
ControlReducerTester R;
Node* t1 = R.graph.NewNode(R.common.IfTrue(), R.start);
Node* t2 = R.graph.NewNode(R.common.IfTrue(), R.start);
Node* merge = R.graph.NewNode(R.common.Merge(2), t1, t2);
R.ReduceMerge(merge, merge);
}
TEST(CMergeReduce_self3) {
ControlReducerTester R;
Node* merge =
R.SetSelfReferences(R.graph.NewNode(R.common.Merge(2), R.start, R.self));
R.ReduceMerge(merge, merge);
}
TEST(CMergeReduce_dead1) {
ControlReducerTester R;
Node* merge = R.graph.NewNode(R.common.Merge(2), R.start, R.dead);
R.ReduceMerge(R.start, merge);
}
TEST(CMergeReduce_dead2) {
ControlReducerTester R;
Node* merge1 = R.graph.NewNode(R.common.Merge(1), R.start);
Node* merge2 = R.graph.NewNode(R.common.Merge(2), merge1, R.dead);
R.ReduceMerge(merge1, merge2);
R.ReduceMergeIterative(R.start, merge2);
}
TEST(CMergeReduce_dead_rm1a) {
ControlReducerTester R;
for (int i = 0; i < 3; i++) {
Node* merge = R.graph.NewNode(R.common.Merge(3), R.start, R.start, R.start);
merge->ReplaceInput(i, R.dead);
R.ReduceMerge(merge, merge);
CheckMerge(merge, R.start, R.start);
}
}
TEST(CMergeReduce_dead_rm1b) {
ControlReducerTester R;
Node* t = R.graph.NewNode(R.common.IfTrue(), R.start);
Node* f = R.graph.NewNode(R.common.IfTrue(), R.start);
for (int i = 0; i < 2; i++) {
for (int j = i + 1; j < 3; j++) {
Node* merge = R.graph.NewNode(R.common.Merge(3), R.dead, R.dead, R.dead);
merge->ReplaceInput(i, t);
merge->ReplaceInput(j, f);
R.ReduceMerge(merge, merge);
CheckMerge(merge, t, f);
}
}
}
TEST(CMergeReduce_dead_rm2) {
ControlReducerTester R;
for (int i = 0; i < 3; i++) {
Node* merge = R.graph.NewNode(R.common.Merge(3), R.dead, R.dead, R.dead);
merge->ReplaceInput(i, R.start);
R.ReduceMerge(R.start, merge);
}
}
TEST(CLoopReduce_dead_rm1) {
ControlReducerTester R;
for (int i = 0; i < 3; i++) {
Node* loop = R.graph.NewNode(R.common.Loop(3), R.dead, R.start, R.start);
R.ReduceMerge(loop, loop);
CheckLoop(loop, R.start, R.start);
}
}
TEST(CMergeReduce_edit_phi1) {
ControlReducerTester R;
for (int i = 0; i < 3; i++) {
Node* merge = R.graph.NewNode(R.common.Merge(3), R.start, R.start, R.start);
merge->ReplaceInput(i, R.dead);
Node* phi = R.graph.NewNode(R.common.Phi(kMachAnyTagged, 3), R.leaf[0],
R.leaf[1], R.leaf[2], merge);
R.ReduceMerge(merge, merge);
CHECK_EQ(IrOpcode::kPhi, phi->opcode());
CHECK_EQ(2, phi->op()->ValueInputCount());
CHECK_EQ(3, phi->InputCount());
CHECK_EQ(R.leaf[i < 1 ? 1 : 0], phi->InputAt(0));
CHECK_EQ(R.leaf[i < 2 ? 2 : 1], phi->InputAt(1));
CHECK_EQ(merge, phi->InputAt(2));
}
}
TEST(CMergeReduce_edit_effect_phi1) {
ControlReducerTester R;
for (int i = 0; i < 3; i++) {
Node* merge = R.graph.NewNode(R.common.Merge(3), R.start, R.start, R.start);
merge->ReplaceInput(i, R.dead);
Node* phi = R.graph.NewNode(R.common.EffectPhi(3), R.leaf[0], R.leaf[1],
R.leaf[2], merge);
R.ReduceMerge(merge, merge);
CHECK_EQ(IrOpcode::kEffectPhi, phi->opcode());
CHECK_EQ(0, phi->op()->ValueInputCount());
CHECK_EQ(2, phi->op()->EffectInputCount());
CHECK_EQ(3, phi->InputCount());
CHECK_EQ(R.leaf[i < 1 ? 1 : 0], phi->InputAt(0));
CHECK_EQ(R.leaf[i < 2 ? 2 : 1], phi->InputAt(1));
CHECK_EQ(merge, phi->InputAt(2));
}
}
static const int kSelectorSize = 4;
// Helper to select K of N nodes according to a mask, useful for the test below.
struct Selector {
int mask;
int count;
explicit Selector(int m) {
mask = m;
count = v8::base::bits::CountPopulation32(m);
}
bool is_selected(int i) { return (mask & (1 << i)) != 0; }
void CheckNode(Node* node, IrOpcode::Value opcode, Node** inputs,
Node* control) {
CHECK_EQ(opcode, node->opcode());
CHECK_EQ(count + (control != NULL ? 1 : 0), node->InputCount());
int index = 0;
for (int i = 0; i < kSelectorSize; i++) {
if (mask & (1 << i)) {
CHECK_EQ(inputs[i], node->InputAt(index++));
}
}
CHECK_EQ(count, index);
if (control != NULL) CHECK_EQ(control, node->InputAt(index++));
}
int single_index() {
CHECK_EQ(1, count);
return WhichPowerOf2(mask);
}
};
TEST(CMergeReduce_exhaustive_4) {
ControlReducerTester R;
Node* controls[] = {
R.graph.NewNode(R.common.Start(1)), R.graph.NewNode(R.common.Start(2)),
R.graph.NewNode(R.common.Start(3)), R.graph.NewNode(R.common.Start(4))};
Node* values[] = {R.jsgraph.Int32Constant(11), R.jsgraph.Int32Constant(22),
R.jsgraph.Int32Constant(33), R.jsgraph.Int32Constant(44)};
Node* effects[] = {
R.jsgraph.Float64Constant(123.4), R.jsgraph.Float64Constant(223.4),
R.jsgraph.Float64Constant(323.4), R.jsgraph.Float64Constant(423.4)};
for (int mask = 0; mask < (1 << (kSelectorSize - 1)); mask++) {
// Reduce a single merge with a given mask.
Node* merge = R.graph.NewNode(R.common.Merge(4), controls[0], controls[1],
controls[2], controls[3]);
Node* phi = R.graph.NewNode(R.common.Phi(kMachAnyTagged, 4), values[0],
values[1], values[2], values[3], merge);
Node* ephi = R.graph.NewNode(R.common.EffectPhi(4), effects[0], effects[1],
effects[2], effects[3], merge);
Node* phi_use =
R.graph.NewNode(R.common.Phi(kMachAnyTagged, 1), phi, R.start);
Node* ephi_use = R.graph.NewNode(R.common.EffectPhi(1), ephi, R.start);
Selector selector(mask);
for (int i = 0; i < kSelectorSize; i++) { // set up dead merge inputs.
if (!selector.is_selected(i)) merge->ReplaceInput(i, R.dead);
}
Node* result = ControlReducer::ReduceMerge(&R.jsgraph, merge);
int count = selector.count;
if (count == 0) {
// result should be dead.
CHECK_EQ(IrOpcode::kDeadControl, result->opcode());
} else if (count == 1) {
// merge should be replaced with one of the controls.
CHECK_EQ(controls[selector.single_index()], result);
// Phis should have been directly replaced.
CHECK_EQ(values[selector.single_index()], phi_use->InputAt(0));
CHECK_EQ(effects[selector.single_index()], ephi_use->InputAt(0));
} else {
// Otherwise, nodes should be edited in place.
CHECK_EQ(merge, result);
selector.CheckNode(merge, IrOpcode::kMerge, controls, NULL);
selector.CheckNode(phi, IrOpcode::kPhi, values, merge);
selector.CheckNode(ephi, IrOpcode::kEffectPhi, effects, merge);
CHECK_EQ(phi, phi_use->InputAt(0));
CHECK_EQ(ephi, ephi_use->InputAt(0));
CHECK_EQ(count, phi->op()->ValueInputCount());
CHECK_EQ(count + 1, phi->InputCount());
CHECK_EQ(count, ephi->op()->EffectInputCount());
CHECK_EQ(count + 1, ephi->InputCount());
}
}
}
TEST(CMergeReduce_edit_many_phis1) {
ControlReducerTester R;
const int kPhiCount = 10;
Node* phis[kPhiCount];
for (int i = 0; i < 3; i++) {
Node* merge = R.graph.NewNode(R.common.Merge(3), R.start, R.start, R.start);
merge->ReplaceInput(i, R.dead);
for (int j = 0; j < kPhiCount; j++) {
phis[j] = R.graph.NewNode(R.common.Phi(kMachAnyTagged, 3), R.leaf[0],
R.leaf[1], R.leaf[2], merge);
}
R.ReduceMerge(merge, merge);
for (int j = 0; j < kPhiCount; j++) {
Node* phi = phis[j];
CHECK_EQ(IrOpcode::kPhi, phi->opcode());
CHECK_EQ(2, phi->op()->ValueInputCount());
CHECK_EQ(3, phi->InputCount());
CHECK_EQ(R.leaf[i < 1 ? 1 : 0], phi->InputAt(0));
CHECK_EQ(R.leaf[i < 2 ? 2 : 1], phi->InputAt(1));
CHECK_EQ(merge, phi->InputAt(2));
}
}
}
TEST(CMergeReduce_simple_chain1) {
ControlReducerTester R;
for (int i = 0; i < 5; i++) {
Node* merge = R.graph.NewNode(R.common.Merge(1), R.start);
for (int j = 0; j < i; j++) {
merge = R.graph.NewNode(R.common.Merge(1), merge);
}
R.ReduceMergeIterative(R.start, merge);
}
}
TEST(CMergeReduce_dead_chain1) {
ControlReducerTester R;
for (int i = 0; i < 5; i++) {
Node* merge = R.graph.NewNode(R.common.Merge(1), R.dead);
for (int j = 0; j < i; j++) {
merge = R.graph.NewNode(R.common.Merge(1), merge);
}
Node* end = R.graph.NewNode(R.common.End(1), merge);
R.graph.SetEnd(end);
R.ReduceGraph();
CHECK(merge->IsDead());
CHECK(!end->InputAt(0)); // end dies.
}
}
TEST(CMergeReduce_dead_chain2) {
ControlReducerTester R;
for (int i = 0; i < 5; i++) {
Node* merge = R.graph.NewNode(R.common.Merge(1), R.start);
for (int j = 0; j < i; j++) {
merge = R.graph.NewNode(R.common.Merge(2), merge, R.dead);
}
R.ReduceMergeIterative(R.start, merge);
}
}
TEST(CBranchReduce_none1) {
ControlReducerTester R;
Diamond d(R, R.p0);
R.ReduceBranch(kUnknown, d.branch);
}
TEST(CBranchReduce_none2) {
ControlReducerTester R;
Diamond d1(R, R.p0);
Diamond d2(R, R.p0);
d2.chain(d1);
R.ReduceBranch(kUnknown, d2.branch);
}
TEST(CBranchReduce_true) {
ControlReducerTester R;
Node* true_values[] = {R.jsgraph.Int32Constant(2),
R.jsgraph.Int64Constant(0x7fffffff),
R.jsgraph.TrueConstant()};
for (size_t i = 0; i < arraysize(true_values); i++) {
Diamond d(R, true_values[i]);
R.ReduceBranch(kTrue, d.branch);
}
}
TEST(CBranchReduce_false) {
ControlReducerTester R;
Node* false_values[] = {R.jsgraph.Int32Constant(0),
R.jsgraph.Int64Constant(0),
R.jsgraph.FalseConstant()};
for (size_t i = 0; i < arraysize(false_values); i++) {
Diamond d(R, false_values[i]);
R.ReduceBranch(kFalse, d.branch);
}
}
TEST(CDiamondReduce_true) {
ControlReducerTester R;
Diamond d1(R, R.jsgraph.TrueConstant());
R.ReduceMergeIterative(R.start, d1.merge);
}
TEST(CDiamondReduce_false) {
ControlReducerTester R;
Diamond d2(R, R.jsgraph.FalseConstant());
R.ReduceMergeIterative(R.start, d2.merge);
}
TEST(CChainedDiamondsReduce_true_false) {
ControlReducerTester R;
Diamond d1(R, R.jsgraph.TrueConstant());
Diamond d2(R, R.jsgraph.FalseConstant());
d2.chain(d1);
R.ReduceMergeIterative(R.start, d2.merge);
}
TEST(CChainedDiamondsReduce_x_false) {
ControlReducerTester R;
Diamond d1(R, R.p0);
Diamond d2(R, R.jsgraph.FalseConstant());
d2.chain(d1);
R.ReduceMergeIterative(R.start, d2.merge);
}
TEST(CChainedDiamondsReduce_false_x) {
ControlReducerTester R;
Diamond d1(R, R.zero);
Diamond d2(R, R.p0);
d2.chain(d1);
R.ReduceMergeIterative(R.start, d2.merge);
}
TEST(CChainedDiamondsReduce_phi1) {
ControlReducerTester R;
Diamond d1(R, R.zero, R.one, R.zero); // foldable branch, phi.
Diamond d2(R, d1.phi);
d2.chain(d1);
R.ReduceMergeIterative(R.start, d2.merge);
}
TEST(CNestedDiamondsReduce_true_true_false) {
ControlReducerTester R;
Diamond d1(R, R.jsgraph.TrueConstant());
Diamond d2(R, R.jsgraph.FalseConstant());
d2.nest(d1, true);
R.ReduceMergeIterative(R.start, d1.merge);
}
TEST(CNestedDiamondsReduce_false_true_false) {
ControlReducerTester R;
Diamond d1(R, R.jsgraph.TrueConstant());
Diamond d2(R, R.jsgraph.FalseConstant());
d2.nest(d1, false);
R.ReduceMergeIterative(R.start, d1.merge);
}
TEST(CNestedDiamonds_xyz) {
ControlReducerTester R;
for (int a = 0; a < 2; a++) {
for (int b = 0; b < 2; b++) {
for (int c = 0; c < 2; c++) {
Diamond d1(R, R.jsgraph.Int32Constant(a));
Diamond d2(R, R.jsgraph.Int32Constant(b));
d2.nest(d1, c);
R.ReduceMergeIterative(R.start, d1.merge);
}
}
}
}
TEST(CUnusedDiamond1) {
ControlReducerTester R;
// if (p0) { } else { }
Node* branch = R.graph.NewNode(R.common.Branch(), R.p0, R.start);
Node* if_true = R.graph.NewNode(R.common.IfTrue(), branch);
Node* if_false = R.graph.NewNode(R.common.IfFalse(), branch);
Node* merge = R.graph.NewNode(R.common.Merge(2), if_true, if_false);
R.ReduceMergeIterative(R.start, merge);
}
TEST(CUnusedDiamond2) {
ControlReducerTester R;
// if (p0) { } else { }
Node* branch = R.graph.NewNode(R.common.Branch(), R.p0, R.start);
Node* if_true = R.graph.NewNode(R.common.IfTrue(), branch);
Node* if_false = R.graph.NewNode(R.common.IfFalse(), branch);
Node* merge = R.graph.NewNode(R.common.Merge(2), if_false, if_true);
R.ReduceMergeIterative(R.start, merge);
}
TEST(CDeadLoop1) {
ControlReducerTester R;
While w(R, R.p0);
Diamond d(R, R.zero);
// if (0) { while (p0) ; } else { }
w.branch->ReplaceInput(1, d.if_true);
d.merge->ReplaceInput(0, w.exit);
R.ReduceMergeIterative(R.start, d.merge);
DeadChecker dead(&R.graph);
dead.Check(d);
dead.Check(w.loop);
}
TEST(Return1) {
ControlReducerTester R;
Node* ret = R.Return(R.one, R.start, R.start);
R.ReduceGraph();
CheckInputs(R.graph.end(), ret);
CheckInputs(ret, R.one, R.start, R.start);
}
TEST(Return2) {
ControlReducerTester R;
Diamond d(R, R.jsgraph.TrueConstant());
Node* ret = R.Return(R.half, R.start, d.merge);
R.ReduceGraph();
DeadChecker dead(&R.graph);
dead.Check(d.branch);
dead.Check(d.if_true);
dead.Check(d.if_false);
dead.Check(d.merge);
CheckInputs(R.graph.end(), ret);
CheckInputs(ret, R.half, R.start, R.start);
}
TEST(Return_true1) {
ControlReducerTester R;
Diamond d(R, R.jsgraph.TrueConstant(), R.half, R.zero);
Node* ret = R.Return(d.phi, R.start, d.merge);
R.ReduceGraph();
DeadChecker dead(&R.graph);
dead.Check(d.branch);
dead.Check(d.if_true);
dead.Check(d.if_false);
dead.Check(d.merge);
dead.Check(d.phi);
CheckInputs(R.graph.end(), ret);
CheckInputs(ret, R.half, R.start, R.start);
}
TEST(Return_false1) {
ControlReducerTester R;
Diamond d(R, R.jsgraph.FalseConstant(), R.one, R.half);
Node* ret = R.Return(d.phi, R.start, d.merge);
R.ReduceGraph();
DeadChecker dead(&R.graph);
dead.Check(d.branch);
dead.Check(d.if_true);
dead.Check(d.if_false);
dead.Check(d.merge);
dead.Check(d.phi);
CheckInputs(R.graph.end(), ret);
CheckInputs(ret, R.half, R.start, R.start);
}
TEST(Return_effect1) {
ControlReducerTester R;
Diamond d(R, R.jsgraph.TrueConstant());
Node* e1 = R.jsgraph.Float64Constant(-100.1);
Node* e2 = R.jsgraph.Float64Constant(+100.1);
Node* effect = R.graph.NewNode(R.common.EffectPhi(2), e1, e2, d.merge);
Node* ret = R.Return(R.p0, effect, d.merge);
R.ReduceGraph();
DeadChecker dead(&R.graph);
dead.Check(d);
dead.Check(effect);
CheckInputs(R.graph.end(), ret);
CheckInputs(ret, R.p0, e1, R.start);
}
TEST(Return_nested_diamonds1) {
ControlReducerTester R;
Diamond d2(R, R.p0, R.one, R.zero);
Diamond d3(R, R.p0, R.one, R.zero);
Diamond d1(R, R.p0, d2.phi, d3.phi);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // nothing should happen.
CheckInputs(ret, d1.phi, R.start, d1.merge);
CheckInputs(d1.phi, d2.phi, d3.phi, d1.merge);
CheckInputs(d1.merge, d2.merge, d3.merge);
DeadChecker dead(&R.graph);
dead.CheckLive(d2);
dead.CheckLive(d3);
}
TEST(Return_nested_diamonds1_dead1) {
ControlReducerTester R;
Diamond d2(R, R.p0); // dead diamond
Diamond d3(R, R.p0, R.one, R.zero);
Diamond d1(R, R.p0, R.one, d3.phi);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph();
CheckInputs(ret, d1.phi, R.start, d1.merge);
CheckInputs(d1.phi, R.one, d3.phi, d1.merge);
CheckInputs(d1.merge, d1.if_true, d3.merge);
DeadChecker dead(&R.graph);
dead.Check(d2); // d2 was a dead diamond.
dead.CheckLive(d3);
}
TEST(Return_nested_diamonds1_dead2) {
ControlReducerTester R;
Diamond d2(R, R.p0); // dead diamond
Diamond d3(R, R.p0); // dead diamond
Diamond d1(R, R.p0, R.one, R.zero);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph();
CheckInputs(ret, d1.phi, R.start, d1.merge);
CheckInputs(d1.phi, R.one, R.zero, d1.merge);
CheckInputs(d1.merge, d1.if_true, d1.if_false);
DeadChecker dead(&R.graph);
dead.Check(d2);
dead.Check(d3);
}
TEST(Return_nested_diamonds_true1) {
ControlReducerTester R;
Diamond d2(R, R.p0, R.one, R.zero);
Diamond d1(R, R.jsgraph.TrueConstant(), d2.phi, R.zero);
Diamond d3(R, R.p0);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // d1 gets folded true.
CheckInputs(ret, d2.phi, R.start, d2.merge);
CheckInputs(d2.branch, R.p0, R.start);
DeadChecker dead(&R.graph);
dead.Check(d1);
dead.CheckLive(d2);
dead.Check(d3);
}
TEST(Return_nested_diamonds_false1) {
ControlReducerTester R;
Diamond d3(R, R.p0, R.one, R.zero);
Diamond d1(R, R.zero, R.one, d3.phi);
Diamond d2(R, R.p0);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // d1 gets folded false.
CheckInputs(ret, d3.phi, R.start, d3.merge);
CheckInputs(d3.branch, R.p0, R.start);
DeadChecker dead(&R.graph);
dead.Check(d1);
dead.Check(d2);
dead.CheckLive(d3);
}
TEST(Return_nested_diamonds_true_true1) {
ControlReducerTester R;
Diamond d1(R, R.jsgraph.TrueConstant(), R.one, R.zero);
Diamond d2(R, R.jsgraph.TrueConstant());
Diamond d3(R, R.p0);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // d1 and d2 both get folded true.
CheckInputs(ret, R.one, R.start, R.start);
DeadChecker dead(&R.graph);
dead.Check(d1);
dead.Check(d2);
dead.Check(d3);
}
TEST(Return_nested_diamonds_true_false1) {
ControlReducerTester R;
Diamond d1(R, R.jsgraph.TrueConstant(), R.one, R.zero);
Diamond d2(R, R.jsgraph.FalseConstant());
Diamond d3(R, R.p0);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // d1 gets folded true and d2 gets folded false.
CheckInputs(ret, R.one, R.start, R.start);
DeadChecker dead(&R.graph);
dead.Check(d1);
dead.Check(d2);
dead.Check(d3);
}
TEST(Return_nested_diamonds2) {
ControlReducerTester R;
Node* x2 = R.jsgraph.Float64Constant(11.1);
Node* y2 = R.jsgraph.Float64Constant(22.2);
Node* x3 = R.jsgraph.Float64Constant(33.3);
Node* y3 = R.jsgraph.Float64Constant(44.4);
Diamond d2(R, R.p0, x2, y2);
Diamond d3(R, R.p0, x3, y3);
Diamond d1(R, R.p0, d2.phi, d3.phi);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // nothing should happen.
CheckInputs(ret, d1.phi, R.start, d1.merge);
CheckInputs(d1.phi, d2.phi, d3.phi, d1.merge);
CheckInputs(d1.merge, d2.merge, d3.merge);
DeadChecker dead(&R.graph);
dead.CheckLive(d2);
dead.CheckLive(d3);
}
TEST(Return_nested_diamonds_true2) {
ControlReducerTester R;
Node* x2 = R.jsgraph.Float64Constant(11.1);
Node* y2 = R.jsgraph.Float64Constant(22.2);
Node* x3 = R.jsgraph.Float64Constant(33.3);
Node* y3 = R.jsgraph.Float64Constant(44.4);
Diamond d2(R, R.p0, x2, y2);
Diamond d3(R, R.p0, x3, y3);
Diamond d1(R, R.jsgraph.TrueConstant(), d2.phi, d3.phi);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // d1 gets folded true.
CheckInputs(ret, d2.phi, R.start, d2.merge);
CheckInputs(d2.branch, R.p0, R.start);
DeadChecker dead(&R.graph);
dead.Check(d1);
dead.CheckLive(d2);
dead.Check(d3);
}
TEST(Return_nested_diamonds_true_true2) {
ControlReducerTester R;
Node* x2 = R.jsgraph.Float64Constant(11.1);
Node* y2 = R.jsgraph.Float64Constant(22.2);
Node* x3 = R.jsgraph.Float64Constant(33.3);
Node* y3 = R.jsgraph.Float64Constant(44.4);
Diamond d2(R, R.jsgraph.TrueConstant(), x2, y2);
Diamond d3(R, R.p0, x3, y3);
Diamond d1(R, R.jsgraph.TrueConstant(), d2.phi, d3.phi);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // d1 gets folded true.
CheckInputs(ret, x2, R.start, R.start);
DeadChecker dead(&R.graph);
dead.Check(d1);
dead.Check(d2);
dead.Check(d3);
}
TEST(Return_nested_diamonds_true_false2) {
ControlReducerTester R;
Node* x2 = R.jsgraph.Float64Constant(11.1);
Node* y2 = R.jsgraph.Float64Constant(22.2);
Node* x3 = R.jsgraph.Float64Constant(33.3);
Node* y3 = R.jsgraph.Float64Constant(44.4);
Diamond d2(R, R.jsgraph.FalseConstant(), x2, y2);
Diamond d3(R, R.p0, x3, y3);
Diamond d1(R, R.jsgraph.TrueConstant(), d2.phi, d3.phi);
d2.nest(d1, true);
d3.nest(d1, false);
Node* ret = R.Return(d1.phi, R.start, d1.merge);
R.ReduceGraph(); // d1 gets folded true.
CheckInputs(ret, y2, R.start, R.start);
DeadChecker dead(&R.graph);
dead.Check(d1);
dead.Check(d2);
dead.Check(d3);
}
......@@ -47,7 +47,7 @@ class LoopFinderTester : HandleAndZoneScope {
one(jsgraph.OneConstant()),
half(jsgraph.Constant(0.5)),
self(graph.NewNode(common.Int32Constant(0xaabbccdd))),
dead(graph.NewNode(common.DeadControl())),
dead(graph.NewNode(common.Dead())),
loop_tree(NULL) {
graph.SetEnd(end);
graph.SetStart(start);
......
......@@ -4,7 +4,6 @@
#include "src/compiler/common-operator.h"
#include "src/compiler/common-operator-reducer.h"
#include "src/compiler/js-operator.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/machine-type.h"
#include "src/compiler/operator.h"
......@@ -28,7 +27,6 @@ class CommonOperatorReducerTest : public GraphTest {
Reduction Reduce(
AdvancedReducer::Editor* editor, Node* node,
MachineOperatorBuilder::Flags flags = MachineOperatorBuilder::kNoFlags) {
JSOperatorBuilder javascript(zone());
MachineOperatorBuilder machine(zone(), kMachPtr, flags);
CommonOperatorReducer reducer(editor, graph(), common(), &machine);
return reducer.Reduce(node);
......@@ -65,6 +63,144 @@ const Operator kOp0(0, Operator::kNoProperties, "Op0", 0, 0, 0, 1, 1, 0);
} // namespace
// -----------------------------------------------------------------------------
// Branch
TEST_F(CommonOperatorReducerTest, BranchWithInt32ZeroConstant) {
TRACED_FOREACH(BranchHint, hint, kBranchHints) {
Node* const control = graph()->start();
Node* const branch =
graph()->NewNode(common()->Branch(hint), Int32Constant(0), control);
Node* const if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* const if_false = graph()->NewNode(common()->IfFalse(), branch);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Replace(if_true, IsDead()));
EXPECT_CALL(editor, Replace(if_false, control));
Reduction const r = Reduce(&editor, branch);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
TEST_F(CommonOperatorReducerTest, BranchWithInt32OneConstant) {
TRACED_FOREACH(BranchHint, hint, kBranchHints) {
Node* const control = graph()->start();
Node* const branch =
graph()->NewNode(common()->Branch(hint), Int32Constant(1), control);
Node* const if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* const if_false = graph()->NewNode(common()->IfFalse(), branch);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Replace(if_true, control));
EXPECT_CALL(editor, Replace(if_false, IsDead()));
Reduction const r = Reduce(&editor, branch);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
TEST_F(CommonOperatorReducerTest, BranchWithInt64ZeroConstant) {
TRACED_FOREACH(BranchHint, hint, kBranchHints) {
Node* const control = graph()->start();
Node* const branch =
graph()->NewNode(common()->Branch(hint), Int64Constant(0), control);
Node* const if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* const if_false = graph()->NewNode(common()->IfFalse(), branch);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Replace(if_true, IsDead()));
EXPECT_CALL(editor, Replace(if_false, control));
Reduction const r = Reduce(&editor, branch);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
TEST_F(CommonOperatorReducerTest, BranchWithInt64OneConstant) {
TRACED_FOREACH(BranchHint, hint, kBranchHints) {
Node* const control = graph()->start();
Node* const branch =
graph()->NewNode(common()->Branch(hint), Int64Constant(1), control);
Node* const if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* const if_false = graph()->NewNode(common()->IfFalse(), branch);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Replace(if_true, control));
EXPECT_CALL(editor, Replace(if_false, IsDead()));
Reduction const r = Reduce(&editor, branch);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
TEST_F(CommonOperatorReducerTest, BranchWithFalseConstant) {
TRACED_FOREACH(BranchHint, hint, kBranchHints) {
Node* const control = graph()->start();
Node* const branch =
graph()->NewNode(common()->Branch(hint), FalseConstant(), control);
Node* const if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* const if_false = graph()->NewNode(common()->IfFalse(), branch);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Replace(if_true, IsDead()));
EXPECT_CALL(editor, Replace(if_false, control));
Reduction const r = Reduce(&editor, branch);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
TEST_F(CommonOperatorReducerTest, BranchWithTrueConstant) {
TRACED_FOREACH(BranchHint, hint, kBranchHints) {
Node* const control = graph()->start();
Node* const branch =
graph()->NewNode(common()->Branch(hint), TrueConstant(), control);
Node* const if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* const if_false = graph()->NewNode(common()->IfFalse(), branch);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Replace(if_true, control));
EXPECT_CALL(editor, Replace(if_false, IsDead()));
Reduction const r = Reduce(&editor, branch);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
// -----------------------------------------------------------------------------
// Merge
TEST_F(CommonOperatorReducerTest, MergeOfUnusedDiamond0) {
Node* const value = Parameter(0);
Node* const control = graph()->start();
Node* const branch = graph()->NewNode(common()->Branch(), value, control);
Node* const if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* const if_false = graph()->NewNode(common()->IfFalse(), branch);
Reduction const r =
Reduce(graph()->NewNode(common()->Merge(2), if_true, if_false));
ASSERT_TRUE(r.Changed());
EXPECT_EQ(control, r.replacement());
EXPECT_THAT(branch, IsDead());
}
TEST_F(CommonOperatorReducerTest, MergeOfUnusedDiamond1) {
Node* const value = Parameter(0);
Node* const control = graph()->start();
Node* const branch = graph()->NewNode(common()->Branch(), value, control);
Node* const if_true = graph()->NewNode(common()->IfTrue(), branch);
Node* const if_false = graph()->NewNode(common()->IfFalse(), branch);
Reduction const r =
Reduce(graph()->NewNode(common()->Merge(2), if_false, if_true));
ASSERT_TRUE(r.Changed());
EXPECT_EQ(control, r.replacement());
EXPECT_THAT(branch, IsDead());
}
// -----------------------------------------------------------------------------
// EffectPhi
......@@ -283,6 +419,50 @@ TEST_F(CommonOperatorReducerTest, SelectWithSameThenAndElse) {
}
TEST_F(CommonOperatorReducerTest, SelectWithInt32ZeroConstant) {
Node* p0 = Parameter(0);
Node* p1 = Parameter(1);
Node* select = graph()->NewNode(common()->Select(kMachAnyTagged),
Int32Constant(0), p0, p1);
Reduction r = Reduce(select);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(p1, r.replacement());
}
TEST_F(CommonOperatorReducerTest, SelectWithInt32OneConstant) {
Node* p0 = Parameter(0);
Node* p1 = Parameter(1);
Node* select = graph()->NewNode(common()->Select(kMachAnyTagged),
Int32Constant(1), p0, p1);
Reduction r = Reduce(select);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(p0, r.replacement());
}
TEST_F(CommonOperatorReducerTest, SelectWithInt64ZeroConstant) {
Node* p0 = Parameter(0);
Node* p1 = Parameter(1);
Node* select = graph()->NewNode(common()->Select(kMachAnyTagged),
Int64Constant(0), p0, p1);
Reduction r = Reduce(select);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(p1, r.replacement());
}
TEST_F(CommonOperatorReducerTest, SelectWithInt64OneConstant) {
Node* p0 = Parameter(0);
Node* p1 = Parameter(1);
Node* select = graph()->NewNode(common()->Select(kMachAnyTagged),
Int64Constant(1), p0, p1);
Reduction r = Reduce(select);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(p0, r.replacement());
}
TEST_F(CommonOperatorReducerTest, SelectWithFalseConstant) {
Node* p0 = Parameter(0);
Node* p1 = Parameter(1);
......
......@@ -48,15 +48,13 @@ const SharedOperator kSharedOperators[] = {
value_input_count, effect_input_count, control_input_count, \
value_output_count, effect_output_count, control_output_count \
}
SHARED(Dead, Operator::kFoldable, 0, 0, 0, 1, 1, 1),
SHARED(IfTrue, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
SHARED(IfFalse, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
SHARED(IfSuccess, Operator::kKontrol, 0, 0, 1, 0, 0, 1),
SHARED(Throw, Operator::kKontrol, 1, 1, 1, 0, 0, 1),
SHARED(Return, Operator::kNoThrow, 1, 1, 1, 0, 0, 1),
SHARED(Terminate, Operator::kKontrol, 0, 1, 1, 0, 0, 1),
SHARED(DeadValue, Operator::kPure, 0, 0, 0, 1, 0, 0),
SHARED(DeadEffect, Operator::kPure, 0, 0, 0, 0, 1, 0),
SHARED(DeadControl, Operator::kFoldable, 0, 0, 0, 0, 0, 1)
SHARED(Terminate, Operator::kKontrol, 0, 1, 1, 0, 0, 1)
#undef SHARED
};
......
// 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/control-reducer.h"
#include "src/compiler/diamond.h"
#include "src/compiler/graph-visualizer.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/js-operator.h"
#include "src/compiler/machine-operator.h"
#include "src/compiler/node.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "testing/gmock-support.h"
using testing::_;
using testing::AllOf;
using testing::Capture;
using testing::CaptureEq;
namespace v8 {
namespace internal {
namespace compiler {
class ControlReducerTest : public GraphTest {
public:
ControlReducerTest()
: GraphTest(1),
machine_(zone()),
javascript_(zone()),
jsgraph_(isolate(), graph(), common(), &javascript_, &machine_) {}
protected:
MachineOperatorBuilder machine_;
JSOperatorBuilder javascript_;
JSGraph jsgraph_;
void ReduceGraph(int max_phis_for_select = 0) {
if (FLAG_trace_turbo_graph) {
OFStream os(stdout);
os << "-- Graph before control reduction" << std::endl;
os << AsRPO(*graph());
}
ControlReducer::ReduceGraph(zone(), jsgraph(), max_phis_for_select);
if (FLAG_trace_turbo_graph) {
OFStream os(stdout);
os << "-- Graph after control reduction" << std::endl;
os << AsRPO(*graph());
}
}
JSGraph* jsgraph() { return &jsgraph_; }
};
TEST_F(ControlReducerTest, SelectPhi) {
Node* p0 = Parameter(0);
const MachineType kType = kMachInt32;
Diamond d(graph(), common(), p0);
Node* phi =
d.Phi(kType, jsgraph()->Int32Constant(1), jsgraph()->Int32Constant(2));
Node* ret =
graph()->NewNode(common()->Return(), phi, graph()->start(), d.merge);
graph()->end()->ReplaceInput(0, ret);
ReduceGraph(1);
// Phi should be replaced with a select.
EXPECT_THAT(graph()->end(),
IsEnd(IsReturn(
IsSelect(kType, p0, IsInt32Constant(1), IsInt32Constant(2)),
graph()->start(), graph()->start())));
}
TEST_F(ControlReducerTest, SelectPhis_fail) {
Node* p0 = Parameter(0);
const MachineType kType = kMachInt32;
Diamond d(graph(), common(), p0);
Node* phi =
d.Phi(kType, jsgraph()->Int32Constant(1), jsgraph()->Int32Constant(2));
Node* phi2 =
d.Phi(kType, jsgraph()->Int32Constant(11), jsgraph()->Int32Constant(22));
USE(phi2);
Node* ret =
graph()->NewNode(common()->Return(), phi, graph()->start(), d.merge);
graph()->end()->ReplaceInput(0, ret);
ReduceGraph(1);
// Diamond should not be replaced with a select (too many phis).
EXPECT_THAT(ret, IsReturn(phi, graph()->start(), d.merge));
EXPECT_THAT(graph()->end(), IsEnd(ret));
}
TEST_F(ControlReducerTest, SelectTwoPhis) {
Node* p0 = Parameter(0);
const MachineType kType = kMachInt32;
Diamond d(graph(), common(), p0);
Node* phi1 =
d.Phi(kType, jsgraph()->Int32Constant(1), jsgraph()->Int32Constant(2));
Node* phi2 =
d.Phi(kType, jsgraph()->Int32Constant(2), jsgraph()->Int32Constant(3));
MachineOperatorBuilder machine(zone());
Node* add = graph()->NewNode(machine.Int32Add(), phi1, phi2);
Node* ret =
graph()->NewNode(common()->Return(), add, graph()->start(), d.merge);
graph()->end()->ReplaceInput(0, ret);
ReduceGraph(2);
// Phis should be replaced with two selects.
EXPECT_THAT(
ret,
IsReturn(IsInt32Add(
IsSelect(kType, p0, IsInt32Constant(1), IsInt32Constant(2)),
IsSelect(kType, p0, IsInt32Constant(2), IsInt32Constant(3))),
graph()->start(), graph()->start()));
EXPECT_THAT(graph()->end(), IsEnd(ret));
}
} // namespace compiler
} // namespace internal
} // namespace v8
// 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/common-operator.h"
#include "src/compiler/dead-code-elimination.h"
#include "test/unittests/compiler/graph-reducer-unittest.h"
#include "test/unittests/compiler/graph-unittest.h"
#include "test/unittests/compiler/node-test-utils.h"
#include "testing/gmock-support.h"
using testing::StrictMock;
namespace v8 {
namespace internal {
namespace compiler {
class DeadCodeEliminationTest : public GraphTest {
public:
explicit DeadCodeEliminationTest(int num_parameters = 4)
: GraphTest(num_parameters) {}
~DeadCodeEliminationTest() override {}
protected:
Reduction Reduce(AdvancedReducer::Editor* editor, Node* node) {
DeadCodeElimination reducer(editor, graph(), common());
return reducer.Reduce(node);
}
Reduction Reduce(Node* node) {
StrictMock<MockAdvancedReducerEditor> editor;
return Reduce(&editor, node);
}
};
namespace {
const MachineType kMachineTypes[] = {
kMachFloat32, kMachFloat64, kMachInt8, kMachUint8, kMachInt16,
kMachUint16, kMachInt32, kMachUint32, kMachInt64, kMachUint64,
kMachPtr, kMachAnyTagged, kRepBit, kRepWord8, kRepWord16,
kRepWord32, kRepWord64, kRepFloat32, kRepFloat64, kRepTagged};
const int kMaxInputs = 16;
const Operator kOp0(0, Operator::kNoProperties, "Op0", 1, 1, 1, 1, 1, 1);
} // namespace
// -----------------------------------------------------------------------------
// General dead propagation
TEST_F(DeadCodeEliminationTest, GeneralDeadPropagation) {
Node* const value = Parameter(0);
Node* const effect = graph()->start();
Node* const dead = graph()->NewNode(common()->Dead());
Node* const node = graph()->NewNode(&kOp0, value, effect, dead);
Reduction const r = Reduce(node);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
// -----------------------------------------------------------------------------
// Branch
TEST_F(DeadCodeEliminationTest, BranchWithDeadControlInput) {
BranchHint const kHints[] = {BranchHint::kNone, BranchHint::kTrue,
BranchHint::kFalse};
TRACED_FOREACH(BranchHint, hint, kHints) {
Reduction const r =
Reduce(graph()->NewNode(common()->Branch(hint), Parameter(0),
graph()->NewNode(common()->Dead())));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
// -----------------------------------------------------------------------------
// IfTrue
TEST_F(DeadCodeEliminationTest, IfTrueWithDeadInput) {
Reduction const r = Reduce(
graph()->NewNode(common()->IfTrue(), graph()->NewNode(common()->Dead())));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
// -----------------------------------------------------------------------------
// IfFalse
TEST_F(DeadCodeEliminationTest, IfFalseWithDeadInput) {
Reduction const r = Reduce(graph()->NewNode(
common()->IfFalse(), graph()->NewNode(common()->Dead())));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
// -----------------------------------------------------------------------------
// IfSuccess
TEST_F(DeadCodeEliminationTest, IfSuccessWithDeadInput) {
Reduction const r = Reduce(graph()->NewNode(
common()->IfSuccess(), graph()->NewNode(common()->Dead())));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
// -----------------------------------------------------------------------------
// IfException
TEST_F(DeadCodeEliminationTest, IfExceptionWithDeadControlInput) {
IfExceptionHint const kHints[] = {IfExceptionHint::kLocallyCaught,
IfExceptionHint::kLocallyUncaught};
TRACED_FOREACH(IfExceptionHint, hint, kHints) {
Reduction const r =
Reduce(graph()->NewNode(common()->IfException(hint), graph()->start(),
graph()->NewNode(common()->Dead())));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
// -----------------------------------------------------------------------------
// End
TEST_F(DeadCodeEliminationTest, EndWithDeadAndStart) {
Node* const dead = graph()->NewNode(common()->Dead());
Node* const start = graph()->start();
Reduction const r = Reduce(graph()->NewNode(common()->End(2), dead, start));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsEnd(start));
}
TEST_F(DeadCodeEliminationTest, EndWithOnlyDeadInputs) {
Node* inputs[kMaxInputs];
TRACED_FORRANGE(int, input_count, 1, kMaxInputs - 1) {
for (int i = 0; i < input_count; ++i) {
inputs[i] = graph()->NewNode(common()->Dead());
}
Reduction const r = Reduce(
graph()->NewNode(common()->End(input_count), input_count, inputs));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
// -----------------------------------------------------------------------------
// Merge
TEST_F(DeadCodeEliminationTest, MergeWithOnlyDeadInputs) {
Node* inputs[kMaxInputs + 1];
TRACED_FORRANGE(int, input_count, 1, kMaxInputs - 1) {
for (int i = 0; i < input_count; ++i) {
inputs[i] = graph()->NewNode(common()->Dead());
}
Reduction const r = Reduce(
graph()->NewNode(common()->Merge(input_count), input_count, inputs));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
TEST_F(DeadCodeEliminationTest, MergeWithOneLiveAndOneDeadInput) {
Node* const v0 = Parameter(0);
Node* const v1 = Parameter(1);
Node* const c0 =
graph()->NewNode(&kOp0, v0, graph()->start(), graph()->start());
Node* const c1 = graph()->NewNode(common()->Dead());
Node* const e0 = graph()->NewNode(&kOp0, v0, graph()->start(), c0);
Node* const e1 = graph()->NewNode(&kOp0, v1, graph()->start(), c1);
Node* const merge = graph()->NewNode(common()->Merge(2), c0, c1);
Node* const phi =
graph()->NewNode(common()->Phi(kMachAnyTagged, 2), v0, v1, merge);
Node* const ephi = graph()->NewNode(common()->EffectPhi(2), e0, e1, merge);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Replace(phi, v0));
EXPECT_CALL(editor, Replace(ephi, e0));
Reduction const r = Reduce(&editor, merge);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(c0, r.replacement());
}
TEST_F(DeadCodeEliminationTest, MergeWithTwoLiveAndTwoDeadInputs) {
Node* const v0 = Parameter(0);
Node* const v1 = Parameter(1);
Node* const v2 = Parameter(2);
Node* const v3 = Parameter(3);
Node* const c0 =
graph()->NewNode(&kOp0, v0, graph()->start(), graph()->start());
Node* const c1 = graph()->NewNode(common()->Dead());
Node* const c2 = graph()->NewNode(common()->Dead());
Node* const c3 = graph()->NewNode(&kOp0, v3, graph()->start(), c0);
Node* const e0 = graph()->start();
Node* const e1 = graph()->NewNode(&kOp0, v1, e0, c0);
Node* const e2 = graph()->NewNode(&kOp0, v2, e1, c0);
Node* const e3 = graph()->NewNode(&kOp0, v3, graph()->start(), c3);
Node* const merge = graph()->NewNode(common()->Merge(4), c0, c1, c2, c3);
Node* const phi =
graph()->NewNode(common()->Phi(kMachAnyTagged, 4), v0, v1, v2, v3, merge);
Node* const ephi =
graph()->NewNode(common()->EffectPhi(4), e0, e1, e2, e3, merge);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Revisit(phi));
EXPECT_CALL(editor, Revisit(ephi));
Reduction const r = Reduce(&editor, merge);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsMerge(c0, c3));
EXPECT_THAT(phi, IsPhi(kMachAnyTagged, v0, v3, r.replacement()));
EXPECT_THAT(ephi, IsEffectPhi(e0, e3, r.replacement()));
}
// -----------------------------------------------------------------------------
// Loop
TEST_F(DeadCodeEliminationTest, LoopWithDeadFirstInput) {
Node* inputs[kMaxInputs + 1];
TRACED_FORRANGE(int, input_count, 1, kMaxInputs - 1) {
inputs[0] = graph()->NewNode(common()->Dead());
for (int i = 1; i < input_count; ++i) {
inputs[i] = graph()->start();
}
Reduction const r = Reduce(
graph()->NewNode(common()->Loop(input_count), input_count, inputs));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
TEST_F(DeadCodeEliminationTest, LoopWithOnlyDeadInputs) {
Node* inputs[kMaxInputs + 1];
TRACED_FORRANGE(int, input_count, 1, kMaxInputs - 1) {
for (int i = 0; i < input_count; ++i) {
inputs[i] = graph()->NewNode(common()->Dead());
}
Reduction const r = Reduce(
graph()->NewNode(common()->Loop(input_count), input_count, inputs));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
TEST_F(DeadCodeEliminationTest, LoopWithOneLiveAndOneDeadInput) {
Node* const v0 = Parameter(0);
Node* const v1 = Parameter(1);
Node* const c0 =
graph()->NewNode(&kOp0, v0, graph()->start(), graph()->start());
Node* const c1 = graph()->NewNode(common()->Dead());
Node* const e0 = graph()->NewNode(&kOp0, v0, graph()->start(), c0);
Node* const e1 = graph()->NewNode(&kOp0, v1, graph()->start(), c1);
Node* const loop = graph()->NewNode(common()->Loop(2), c0, c1);
Node* const phi =
graph()->NewNode(common()->Phi(kMachAnyTagged, 2), v0, v1, loop);
Node* const ephi = graph()->NewNode(common()->EffectPhi(2), e0, e1, loop);
Node* const terminate = graph()->NewNode(common()->Terminate(), ephi, loop);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Replace(phi, v0));
EXPECT_CALL(editor, Replace(ephi, e0));
EXPECT_CALL(editor, Replace(terminate, IsDead()));
Reduction const r = Reduce(&editor, loop);
ASSERT_TRUE(r.Changed());
EXPECT_EQ(c0, r.replacement());
}
TEST_F(DeadCodeEliminationTest, LoopWithTwoLiveAndTwoDeadInputs) {
Node* const v0 = Parameter(0);
Node* const v1 = Parameter(1);
Node* const v2 = Parameter(2);
Node* const v3 = Parameter(3);
Node* const c0 =
graph()->NewNode(&kOp0, v0, graph()->start(), graph()->start());
Node* const c1 = graph()->NewNode(common()->Dead());
Node* const c2 = graph()->NewNode(common()->Dead());
Node* const c3 = graph()->NewNode(&kOp0, v3, graph()->start(), c0);
Node* const e0 = graph()->start();
Node* const e1 = graph()->NewNode(&kOp0, v1, e0, c0);
Node* const e2 = graph()->NewNode(&kOp0, v2, e1, c0);
Node* const e3 = graph()->NewNode(&kOp0, v3, graph()->start(), c3);
Node* const loop = graph()->NewNode(common()->Loop(4), c0, c1, c2, c3);
Node* const phi =
graph()->NewNode(common()->Phi(kMachAnyTagged, 4), v0, v1, v2, v3, loop);
Node* const ephi =
graph()->NewNode(common()->EffectPhi(4), e0, e1, e2, e3, loop);
StrictMock<MockAdvancedReducerEditor> editor;
EXPECT_CALL(editor, Revisit(phi));
EXPECT_CALL(editor, Revisit(ephi));
Reduction const r = Reduce(&editor, loop);
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsLoop(c0, c3));
EXPECT_THAT(phi, IsPhi(kMachAnyTagged, v0, v3, r.replacement()));
EXPECT_THAT(ephi, IsEffectPhi(e0, e3, r.replacement()));
}
// -----------------------------------------------------------------------------
// Phi
TEST_F(DeadCodeEliminationTest, PhiWithDeadControlInput) {
Node* inputs[kMaxInputs + 1];
TRACED_FOREACH(MachineType, type, kMachineTypes) {
TRACED_FORRANGE(int, input_count, 1, kMaxInputs) {
for (int i = 0; i < input_count; ++i) {
inputs[i] = Parameter(i);
}
inputs[input_count] = graph()->NewNode(common()->Dead());
Reduction const r = Reduce(graph()->NewNode(
common()->Phi(type, input_count), input_count + 1, inputs));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
}
// -----------------------------------------------------------------------------
// EffectPhi
TEST_F(DeadCodeEliminationTest, EffectPhiWithDeadControlInput) {
Node* inputs[kMaxInputs + 1];
TRACED_FORRANGE(int, input_count, 1, kMaxInputs) {
for (int i = 0; i < input_count; ++i) {
inputs[i] = graph()->start();
}
inputs[input_count] = graph()->NewNode(common()->Dead());
Reduction const r = Reduce(graph()->NewNode(
common()->EffectPhi(input_count), input_count + 1, inputs));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
}
// -----------------------------------------------------------------------------
// Terminate
TEST_F(DeadCodeEliminationTest, TerminateWithDeadControlInput) {
Reduction const r =
Reduce(graph()->NewNode(common()->Terminate(), graph()->start(),
graph()->NewNode(common()->Dead())));
ASSERT_TRUE(r.Changed());
EXPECT_THAT(r.replacement(), IsDead());
}
} // namespace compiler
} // namespace internal
} // namespace v8
......@@ -275,11 +275,11 @@ struct ReplaceWithValueReducer final : public AdvancedReducer {
using AdvancedReducer::ReplaceWithValue;
};
const Operator kMockOperator(IrOpcode::kDeadControl, Operator::kNoProperties,
const Operator kMockOperator(IrOpcode::kDead, Operator::kNoProperties,
"MockOperator", 0, 0, 0, 1, 0, 0);
const Operator kMockOpEffect(IrOpcode::kDeadControl, Operator::kNoProperties,
const Operator kMockOpEffect(IrOpcode::kDead, Operator::kNoProperties,
"MockOpEffect", 0, 1, 0, 1, 1, 0);
const Operator kMockOpControl(IrOpcode::kDeadControl, Operator::kNoProperties,
const Operator kMockOpControl(IrOpcode::kDead, Operator::kNoProperties,
"MockOpControl", 0, 0, 1, 1, 0, 1);
const IfExceptionHint kNoHint = IfExceptionHint::kLocallyCaught;
......
......@@ -32,10 +32,10 @@ class GraphTrimmerTest : public GraphTest {
namespace {
const Operator kDead0(IrOpcode::kDeadControl, Operator::kNoProperties, "Dead0",
0, 0, 1, 0, 0, 0);
const Operator kLive0(IrOpcode::kDeadControl, Operator::kNoProperties, "Live0",
0, 0, 1, 0, 0, 1);
const Operator kDead0(IrOpcode::kDead, Operator::kNoProperties, "Dead0", 0, 0,
1, 0, 0, 0);
const Operator kLive0(IrOpcode::kDead, Operator::kNoProperties, "Live0", 0, 0,
1, 0, 0, 1);
} // namespace
......
......@@ -32,7 +32,7 @@ class NodePropertiesTest : public TestWithZone {
namespace {
const Operator kMockOperator(IrOpcode::kDeadControl, Operator::kNoProperties,
const Operator kMockOperator(IrOpcode::kDead, Operator::kNoProperties,
"MockOperator", 0, 0, 0, 1, 1, 2);
const Operator kMockCallOperator(IrOpcode::kCall, Operator::kNoProperties,
"MockCallOperator", 0, 0, 0, 0, 0, 2);
......
......@@ -1378,6 +1378,11 @@ class IsUnopMatcher final : public NodeMatcher {
} // namespace
Matcher<Node*> IsDead() {
return MakeMatcher(new NodeMatcher(IrOpcode::kDead));
}
Matcher<Node*> IsEnd(const Matcher<Node*>& control0_matcher) {
return MakeMatcher(new IsControl1Matcher(IrOpcode::kEnd, control0_matcher));
}
......
......@@ -36,6 +36,7 @@ class Node;
using ::testing::Matcher;
Matcher<Node*> IsDead();
Matcher<Node*> IsEnd(const Matcher<Node*>& control0_matcher);
Matcher<Node*> IsEnd(const Matcher<Node*>& control0_matcher,
const Matcher<Node*>& control1_matcher);
......
......@@ -44,7 +44,7 @@
'compiler/compiler-test-utils.h',
'compiler/control-equivalence-unittest.cc',
'compiler/control-flow-optimizer-unittest.cc',
'compiler/control-reducer-unittest.cc',
'compiler/dead-code-elimination-unittest.cc',
'compiler/diamond-unittest.cc',
'compiler/graph-reducer-unittest.cc',
'compiler/graph-reducer-unittest.h',
......
......@@ -460,8 +460,8 @@
'../../src/compiler/control-equivalence.h',
'../../src/compiler/control-flow-optimizer.cc',
'../../src/compiler/control-flow-optimizer.h',
'../../src/compiler/control-reducer.cc',
'../../src/compiler/control-reducer.h',
'../../src/compiler/dead-code-elimination.cc',
'../../src/compiler/dead-code-elimination.h',
'../../src/compiler/diamond.h',
'../../src/compiler/frame.h',
'../../src/compiler/frame-elider.cc',
......
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