v8/test/unittests/compiler/graph-reducer-unittest.cc

858 lines
24 KiB
C++
Raw Normal View History

// 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/graph.h"
#include "src/compiler/node.h"
#include "src/compiler/operator.h"
#include "test/unittests/compiler/graph-reducer-unittest.h"
#include "test/unittests/test-utils.h"
using testing::_;
using testing::DefaultValue;
using testing::ElementsAre;
using testing::Return;
using testing::Sequence;
using testing::StrictMock;
using testing::UnorderedElementsAre;
namespace v8 {
namespace internal {
namespace compiler {
namespace {
struct TestOperator : public Operator {
TestOperator(Operator::Opcode opcode, Operator::Properties properties,
const char* op_name, size_t value_in, size_t value_out)
: Operator(opcode, properties, op_name, value_in, 0, 0, value_out, 0, 0) {
}
};
const uint8_t kOpcodeA0 = 10;
const uint8_t kOpcodeA1 = 11;
const uint8_t kOpcodeA2 = 12;
const uint8_t kOpcodeB0 = 20;
const uint8_t kOpcodeB1 = 21;
const uint8_t kOpcodeB2 = 22;
const uint8_t kOpcodeC0 = 30;
const uint8_t kOpcodeC1 = 31;
const uint8_t kOpcodeC2 = 32;
static TestOperator kOpA0(kOpcodeA0, Operator::kNoWrite, "opa1", 0, 1);
static TestOperator kOpA1(kOpcodeA1, Operator::kNoProperties, "opa2", 1, 1);
static TestOperator kOpA2(kOpcodeA2, Operator::kNoProperties, "opa3", 2, 1);
static TestOperator kOpB0(kOpcodeB0, Operator::kNoWrite, "opa0", 0, 0);
static TestOperator kOpB1(kOpcodeB1, Operator::kNoWrite, "opa1", 1, 0);
static TestOperator kOpB2(kOpcodeB2, Operator::kNoWrite, "opa2", 2, 0);
static TestOperator kOpC0(kOpcodeC0, Operator::kNoWrite, "opc0", 0, 0);
static TestOperator kOpC1(kOpcodeC1, Operator::kNoWrite, "opc1", 1, 0);
static TestOperator kOpC2(kOpcodeC2, Operator::kNoWrite, "opc2", 2, 0);
struct MockReducer : public Reducer {
MOCK_METHOD1(Reduce, Reduction(Node*));
};
// Replaces all "A" operators with "B" operators without creating new nodes.
class InPlaceABReducer final : public Reducer {
public:
Reduction Reduce(Node* node) final {
switch (node->op()->opcode()) {
case kOpcodeA0:
EXPECT_EQ(0, node->InputCount());
node->set_op(&kOpB0);
return Replace(node);
case kOpcodeA1:
EXPECT_EQ(1, node->InputCount());
node->set_op(&kOpB1);
return Replace(node);
case kOpcodeA2:
EXPECT_EQ(2, node->InputCount());
node->set_op(&kOpB2);
return Replace(node);
}
return NoChange();
}
};
// Replaces all "A" operators with "B" operators by allocating new nodes.
class NewABReducer final : public Reducer {
public:
explicit NewABReducer(Graph* graph) : graph_(graph) {}
Reduction Reduce(Node* node) final {
switch (node->op()->opcode()) {
case kOpcodeA0:
EXPECT_EQ(0, node->InputCount());
return Replace(graph_->NewNode(&kOpB0));
case kOpcodeA1:
EXPECT_EQ(1, node->InputCount());
return Replace(graph_->NewNode(&kOpB1, node->InputAt(0)));
case kOpcodeA2:
EXPECT_EQ(2, node->InputCount());
return Replace(
graph_->NewNode(&kOpB2, node->InputAt(0), node->InputAt(1)));
}
return NoChange();
}
private:
Graph* const graph_;
};
// Wraps all "kOpA0" nodes in "kOpB1" operators by allocating new nodes.
class A0Wrapper final : public Reducer {
public:
explicit A0Wrapper(Graph* graph) : graph_(graph) {}
Reduction Reduce(Node* node) final {
switch (node->op()->opcode()) {
case kOpcodeA0:
EXPECT_EQ(0, node->InputCount());
return Replace(graph_->NewNode(&kOpB1, node));
}
return NoChange();
}
private:
Graph* const graph_;
};
// Wraps all "kOpB0" nodes in two "kOpC1" operators by allocating new nodes.
class B0Wrapper final : public Reducer {
public:
explicit B0Wrapper(Graph* graph) : graph_(graph) {}
Reduction Reduce(Node* node) final {
switch (node->op()->opcode()) {
case kOpcodeB0:
EXPECT_EQ(0, node->InputCount());
return Replace(graph_->NewNode(&kOpC1, graph_->NewNode(&kOpC1, node)));
}
return NoChange();
}
private:
Graph* const graph_;
};
// Replaces all "kOpA1" nodes with the first input.
class A1Forwarder final : public Reducer {
public:
Reduction Reduce(Node* node) final {
switch (node->op()->opcode()) {
case kOpcodeA1:
EXPECT_EQ(1, node->InputCount());
return Replace(node->InputAt(0));
}
return NoChange();
}
};
// Replaces all "kOpB1" nodes with the first input.
class B1Forwarder final : public Reducer {
public:
Reduction Reduce(Node* node) final {
switch (node->op()->opcode()) {
case kOpcodeB1:
EXPECT_EQ(1, node->InputCount());
return Replace(node->InputAt(0));
}
return NoChange();
}
};
// Replaces all "B" operators with "C" operators without creating new nodes.
class InPlaceBCReducer final : public Reducer {
public:
Reduction Reduce(Node* node) final {
switch (node->op()->opcode()) {
case kOpcodeB0:
EXPECT_EQ(0, node->InputCount());
node->set_op(&kOpC0);
return Replace(node);
case kOpcodeB1:
EXPECT_EQ(1, node->InputCount());
node->set_op(&kOpC1);
return Replace(node);
case kOpcodeB2:
EXPECT_EQ(2, node->InputCount());
node->set_op(&kOpC2);
return Replace(node);
}
return NoChange();
}
};
// Swaps the inputs to "kOp2A" and "kOp2B" nodes based on ids.
class AB2Sorter final : public Reducer {
public:
Reduction Reduce(Node* node) final {
switch (node->op()->opcode()) {
case kOpcodeA2:
case kOpcodeB2:
EXPECT_EQ(2, node->InputCount());
Node* x = node->InputAt(0);
Node* y = node->InputAt(1);
if (x->id() > y->id()) {
node->ReplaceInput(0, y);
node->ReplaceInput(1, x);
return Replace(node);
}
}
return NoChange();
}
};
} // namespace
class AdvancedReducerTest : public TestWithZone {
public:
AdvancedReducerTest() : graph_(zone()) {}
protected:
Graph* graph() { return &graph_; }
private:
Graph graph_;
};
TEST_F(AdvancedReducerTest, Replace) {
struct DummyReducer final : public AdvancedReducer {
explicit DummyReducer(Editor* editor) : AdvancedReducer(editor) {}
Reduction Reduce(Node* node) final {
Replace(node, node);
return NoChange();
}
};
StrictMock<MockAdvancedReducerEditor> e;
DummyReducer r(&e);
Node* node0 = graph()->NewNode(&kOpA0);
Node* node1 = graph()->NewNode(&kOpA1, node0);
EXPECT_CALL(e, Replace(node0, node0));
EXPECT_CALL(e, Replace(node1, node1));
EXPECT_FALSE(r.Reduce(node0).Changed());
EXPECT_FALSE(r.Reduce(node1).Changed());
}
TEST_F(AdvancedReducerTest, Revisit) {
struct DummyReducer final : public AdvancedReducer {
explicit DummyReducer(Editor* editor) : AdvancedReducer(editor) {}
Reduction Reduce(Node* node) final {
Revisit(node);
return NoChange();
}
};
StrictMock<MockAdvancedReducerEditor> e;
DummyReducer r(&e);
Node* node0 = graph()->NewNode(&kOpA0);
Node* node1 = graph()->NewNode(&kOpA1, node0);
EXPECT_CALL(e, Revisit(node0));
EXPECT_CALL(e, Revisit(node1));
EXPECT_FALSE(r.Reduce(node0).Changed());
EXPECT_FALSE(r.Reduce(node1).Changed());
}
namespace {
struct ReplaceWithValueReducer final : public AdvancedReducer {
explicit ReplaceWithValueReducer(Editor* editor) : AdvancedReducer(editor) {}
Reduction Reduce(Node* node) final { return NoChange(); }
using AdvancedReducer::ReplaceWithValue;
};
[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}
2015-06-19 12:07:17 +00:00
const Operator kMockOperator(IrOpcode::kDead, Operator::kNoProperties,
"MockOperator", 0, 0, 0, 1, 0, 0);
[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}
2015-06-19 12:07:17 +00:00
const Operator kMockOpEffect(IrOpcode::kDead, Operator::kNoProperties,
"MockOpEffect", 0, 1, 0, 1, 1, 0);
[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}
2015-06-19 12:07:17 +00:00
const Operator kMockOpControl(IrOpcode::kDead, Operator::kNoProperties,
"MockOpControl", 0, 0, 1, 1, 0, 1);
const IfExceptionHint kNoHint = IfExceptionHint::kLocallyCaught;
} // namespace
TEST_F(AdvancedReducerTest, ReplaceWithValue_ValueUse) {
CommonOperatorBuilder common(zone());
Node* node = graph()->NewNode(&kMockOperator);
Node* use_value = graph()->NewNode(common.Return(), node);
Node* replacement = graph()->NewNode(&kMockOperator);
GraphReducer graph_reducer(zone(), graph(), nullptr);
ReplaceWithValueReducer r(&graph_reducer);
r.ReplaceWithValue(node, replacement);
EXPECT_EQ(replacement, use_value->InputAt(0));
EXPECT_EQ(0, node->UseCount());
EXPECT_EQ(1, replacement->UseCount());
EXPECT_THAT(replacement->uses(), ElementsAre(use_value));
}
TEST_F(AdvancedReducerTest, ReplaceWithValue_EffectUse) {
CommonOperatorBuilder common(zone());
Node* start = graph()->NewNode(common.Start(1));
Node* node = graph()->NewNode(&kMockOpEffect, start);
Node* use_effect = graph()->NewNode(common.EffectPhi(1), node);
Node* replacement = graph()->NewNode(&kMockOperator);
GraphReducer graph_reducer(zone(), graph(), nullptr);
ReplaceWithValueReducer r(&graph_reducer);
r.ReplaceWithValue(node, replacement);
EXPECT_EQ(start, use_effect->InputAt(0));
EXPECT_EQ(0, node->UseCount());
EXPECT_EQ(2, start->UseCount());
EXPECT_EQ(0, replacement->UseCount());
EXPECT_THAT(start->uses(), UnorderedElementsAre(use_effect, node));
}
TEST_F(AdvancedReducerTest, ReplaceWithValue_ControlUse1) {
CommonOperatorBuilder common(zone());
Node* start = graph()->NewNode(common.Start(1));
Node* node = graph()->NewNode(&kMockOpControl, start);
Node* success = graph()->NewNode(common.IfSuccess(), node);
Node* use_control = graph()->NewNode(common.Merge(1), success);
Node* replacement = graph()->NewNode(&kMockOperator);
GraphReducer graph_reducer(zone(), graph(), nullptr);
ReplaceWithValueReducer r(&graph_reducer);
r.ReplaceWithValue(node, replacement);
EXPECT_EQ(start, use_control->InputAt(0));
EXPECT_EQ(0, node->UseCount());
EXPECT_EQ(2, start->UseCount());
EXPECT_EQ(0, replacement->UseCount());
EXPECT_THAT(start->uses(), UnorderedElementsAre(use_control, node));
}
TEST_F(AdvancedReducerTest, ReplaceWithValue_ControlUse2) {
CommonOperatorBuilder common(zone());
Node* start = graph()->NewNode(common.Start(1));
Node* effect = graph()->NewNode(&kMockOperator);
Node* dead = graph()->NewNode(&kMockOperator);
Node* node = graph()->NewNode(&kMockOpControl, start);
Node* success = graph()->NewNode(common.IfSuccess(), node);
Node* exception = graph()->NewNode(common.IfException(kNoHint), effect, node);
Node* use_control = graph()->NewNode(common.Merge(1), success);
Node* replacement = graph()->NewNode(&kMockOperator);
GraphReducer graph_reducer(zone(), graph(), dead);
ReplaceWithValueReducer r(&graph_reducer);
r.ReplaceWithValue(node, replacement);
EXPECT_EQ(start, use_control->InputAt(0));
EXPECT_EQ(dead, exception->InputAt(1));
EXPECT_EQ(0, node->UseCount());
EXPECT_EQ(2, start->UseCount());
EXPECT_EQ(1, dead->UseCount());
EXPECT_EQ(0, replacement->UseCount());
EXPECT_THAT(start->uses(), UnorderedElementsAre(use_control, node));
EXPECT_THAT(dead->uses(), ElementsAre(exception));
}
TEST_F(AdvancedReducerTest, ReplaceWithValue_ControlUse3) {
CommonOperatorBuilder common(zone());
Node* start = graph()->NewNode(common.Start(1));
Node* effect = graph()->NewNode(&kMockOperator);
Node* dead = graph()->NewNode(&kMockOperator);
Node* node = graph()->NewNode(&kMockOpControl, start);
Node* success = graph()->NewNode(common.IfSuccess(), node);
Node* exception = graph()->NewNode(common.IfException(kNoHint), effect, node);
Node* use_control = graph()->NewNode(common.Merge(1), success);
Node* replacement = graph()->NewNode(&kMockOperator);
GraphReducer graph_reducer(zone(), graph(), dead);
ReplaceWithValueReducer r(&graph_reducer);
r.ReplaceWithValue(node, replacement);
EXPECT_EQ(start, use_control->InputAt(0));
EXPECT_EQ(dead, exception->InputAt(1));
EXPECT_EQ(0, node->UseCount());
EXPECT_EQ(2, start->UseCount());
EXPECT_EQ(1, dead->UseCount());
EXPECT_EQ(0, replacement->UseCount());
EXPECT_THAT(start->uses(), UnorderedElementsAre(use_control, node));
EXPECT_THAT(dead->uses(), ElementsAre(exception));
}
class GraphReducerTest : public TestWithZone {
public:
GraphReducerTest() : graph_(zone()) {}
static void SetUpTestCase() {
TestWithZone::SetUpTestCase();
DefaultValue<Reduction>::Set(Reducer::NoChange());
}
static void TearDownTestCase() {
DefaultValue<Reduction>::Clear();
TestWithZone::TearDownTestCase();
}
protected:
void ReduceNode(Node* node, Reducer* r) {
GraphReducer reducer(zone(), graph());
reducer.AddReducer(r);
reducer.ReduceNode(node);
}
void ReduceNode(Node* node, Reducer* r1, Reducer* r2) {
GraphReducer reducer(zone(), graph());
reducer.AddReducer(r1);
reducer.AddReducer(r2);
reducer.ReduceNode(node);
}
void ReduceNode(Node* node, Reducer* r1, Reducer* r2, Reducer* r3) {
GraphReducer reducer(zone(), graph());
reducer.AddReducer(r1);
reducer.AddReducer(r2);
reducer.AddReducer(r3);
reducer.ReduceNode(node);
}
void ReduceGraph(Reducer* r1) {
GraphReducer reducer(zone(), graph());
reducer.AddReducer(r1);
reducer.ReduceGraph();
}
void ReduceGraph(Reducer* r1, Reducer* r2) {
GraphReducer reducer(zone(), graph());
reducer.AddReducer(r1);
reducer.AddReducer(r2);
reducer.ReduceGraph();
}
void ReduceGraph(Reducer* r1, Reducer* r2, Reducer* r3) {
GraphReducer reducer(zone(), graph());
reducer.AddReducer(r1);
reducer.AddReducer(r2);
reducer.AddReducer(r3);
reducer.ReduceGraph();
}
Graph* graph() { return &graph_; }
private:
Graph graph_;
};
TEST_F(GraphReducerTest, NodeIsDeadAfterReplace) {
StrictMock<MockReducer> r;
Node* node0 = graph()->NewNode(&kOpA0);
Node* node1 = graph()->NewNode(&kOpA1, node0);
Node* node2 = graph()->NewNode(&kOpA1, node0);
EXPECT_CALL(r, Reduce(node0)).WillOnce(Return(Reducer::NoChange()));
EXPECT_CALL(r, Reduce(node1)).WillOnce(Return(Reducer::Replace(node2)));
ReduceNode(node1, &r);
EXPECT_FALSE(node0->IsDead());
EXPECT_TRUE(node1->IsDead());
EXPECT_FALSE(node2->IsDead());
}
TEST_F(GraphReducerTest, ReduceOnceForEveryReducer) {
StrictMock<MockReducer> r1, r2;
Node* node0 = graph()->NewNode(&kOpA0);
EXPECT_CALL(r1, Reduce(node0));
EXPECT_CALL(r2, Reduce(node0));
ReduceNode(node0, &r1, &r2);
}
TEST_F(GraphReducerTest, ReduceAgainAfterChanged) {
Sequence s1, s2, s3;
StrictMock<MockReducer> r1, r2, r3;
Node* node0 = graph()->NewNode(&kOpA0);
EXPECT_CALL(r1, Reduce(node0));
EXPECT_CALL(r2, Reduce(node0));
EXPECT_CALL(r3, Reduce(node0)).InSequence(s1, s2, s3).WillOnce(
Return(Reducer::Changed(node0)));
EXPECT_CALL(r1, Reduce(node0)).InSequence(s1);
EXPECT_CALL(r2, Reduce(node0)).InSequence(s2);
ReduceNode(node0, &r1, &r2, &r3);
}
TEST_F(GraphReducerTest, ReduceGraphFromEnd1) {
StrictMock<MockReducer> r1;
Node* n = graph()->NewNode(&kOpA0);
Node* end = graph()->NewNode(&kOpA1, n);
graph()->SetEnd(end);
Sequence s;
EXPECT_CALL(r1, Reduce(n));
EXPECT_CALL(r1, Reduce(end));
ReduceGraph(&r1);
}
TEST_F(GraphReducerTest, ReduceGraphFromEnd2) {
StrictMock<MockReducer> r1;
Node* n1 = graph()->NewNode(&kOpA0);
Node* n2 = graph()->NewNode(&kOpA1, n1);
Node* n3 = graph()->NewNode(&kOpA1, n1);
Node* end = graph()->NewNode(&kOpA2, n2, n3);
graph()->SetEnd(end);
Sequence s1, s2;
EXPECT_CALL(r1, Reduce(n1)).InSequence(s1, s2);
EXPECT_CALL(r1, Reduce(n2)).InSequence(s1);
EXPECT_CALL(r1, Reduce(n3)).InSequence(s2);
EXPECT_CALL(r1, Reduce(end)).InSequence(s1, s2);
ReduceGraph(&r1);
}
TEST_F(GraphReducerTest, ReduceInPlace1) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* end = graph()->NewNode(&kOpA1, n1);
graph()->SetEnd(end);
// Tests A* => B* with in-place updates.
InPlaceABReducer r;
for (int i = 0; i < 3; i++) {
size_t before = graph()->NodeCount();
ReduceGraph(&r);
EXPECT_EQ(before, graph()->NodeCount());
EXPECT_EQ(&kOpB0, n1->op());
EXPECT_EQ(&kOpB1, end->op());
EXPECT_EQ(n1, end->InputAt(0));
}
}
TEST_F(GraphReducerTest, ReduceInPlace2) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* n2 = graph()->NewNode(&kOpA1, n1);
Node* n3 = graph()->NewNode(&kOpA1, n1);
Node* end = graph()->NewNode(&kOpA2, n2, n3);
graph()->SetEnd(end);
// Tests A* => B* with in-place updates.
InPlaceABReducer r;
for (int i = 0; i < 3; i++) {
size_t before = graph()->NodeCount();
ReduceGraph(&r);
EXPECT_EQ(before, graph()->NodeCount());
EXPECT_EQ(&kOpB0, n1->op());
EXPECT_EQ(&kOpB1, n2->op());
EXPECT_EQ(n1, n2->InputAt(0));
EXPECT_EQ(&kOpB1, n3->op());
EXPECT_EQ(n1, n3->InputAt(0));
EXPECT_EQ(&kOpB2, end->op());
EXPECT_EQ(n2, end->InputAt(0));
EXPECT_EQ(n3, end->InputAt(1));
}
}
TEST_F(GraphReducerTest, ReduceNew1) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* n2 = graph()->NewNode(&kOpA1, n1);
Node* n3 = graph()->NewNode(&kOpA1, n1);
Node* end = graph()->NewNode(&kOpA2, n2, n3);
graph()->SetEnd(end);
NewABReducer r(graph());
// Tests A* => B* while creating new nodes.
for (int i = 0; i < 3; i++) {
size_t before = graph()->NodeCount();
ReduceGraph(&r);
if (i == 0) {
EXPECT_NE(before, graph()->NodeCount());
} else {
EXPECT_EQ(before, graph()->NodeCount());
}
Node* nend = graph()->end();
EXPECT_NE(end, nend); // end() should be updated too.
Node* nn2 = nend->InputAt(0);
Node* nn3 = nend->InputAt(1);
Node* nn1 = nn2->InputAt(0);
EXPECT_EQ(nn1, nn3->InputAt(0));
EXPECT_EQ(&kOpB0, nn1->op());
EXPECT_EQ(&kOpB1, nn2->op());
EXPECT_EQ(&kOpB1, nn3->op());
EXPECT_EQ(&kOpB2, nend->op());
}
}
TEST_F(GraphReducerTest, Wrapping1) {
Node* end = graph()->NewNode(&kOpA0);
graph()->SetEnd(end);
EXPECT_EQ(1U, graph()->NodeCount());
A0Wrapper r(graph());
ReduceGraph(&r);
EXPECT_EQ(2U, graph()->NodeCount());
Node* nend = graph()->end();
EXPECT_NE(end, nend);
EXPECT_EQ(&kOpB1, nend->op());
EXPECT_EQ(1, nend->InputCount());
EXPECT_EQ(end, nend->InputAt(0));
}
TEST_F(GraphReducerTest, Wrapping2) {
Node* end = graph()->NewNode(&kOpB0);
graph()->SetEnd(end);
EXPECT_EQ(1U, graph()->NodeCount());
B0Wrapper r(graph());
ReduceGraph(&r);
EXPECT_EQ(3U, graph()->NodeCount());
Node* nend = graph()->end();
EXPECT_NE(end, nend);
EXPECT_EQ(&kOpC1, nend->op());
EXPECT_EQ(1, nend->InputCount());
Node* n1 = nend->InputAt(0);
EXPECT_NE(end, n1);
EXPECT_EQ(&kOpC1, n1->op());
EXPECT_EQ(1, n1->InputCount());
EXPECT_EQ(end, n1->InputAt(0));
}
TEST_F(GraphReducerTest, Forwarding1) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* end = graph()->NewNode(&kOpA1, n1);
graph()->SetEnd(end);
A1Forwarder r;
// Tests A1(x) => x
for (int i = 0; i < 3; i++) {
size_t before = graph()->NodeCount();
ReduceGraph(&r);
EXPECT_EQ(before, graph()->NodeCount());
EXPECT_EQ(&kOpA0, n1->op());
EXPECT_EQ(n1, graph()->end());
}
}
TEST_F(GraphReducerTest, Forwarding2) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* n2 = graph()->NewNode(&kOpA1, n1);
Node* n3 = graph()->NewNode(&kOpA1, n1);
Node* end = graph()->NewNode(&kOpA2, n2, n3);
graph()->SetEnd(end);
A1Forwarder r;
// Tests reducing A2(A1(x), A1(y)) => A2(x, y).
for (int i = 0; i < 3; i++) {
size_t before = graph()->NodeCount();
ReduceGraph(&r);
EXPECT_EQ(before, graph()->NodeCount());
EXPECT_EQ(&kOpA0, n1->op());
EXPECT_EQ(n1, end->InputAt(0));
EXPECT_EQ(n1, end->InputAt(1));
EXPECT_EQ(&kOpA2, end->op());
EXPECT_EQ(0, n2->UseCount());
EXPECT_EQ(0, n3->UseCount());
}
}
TEST_F(GraphReducerTest, Forwarding3) {
// Tests reducing a chain of A1(A1(A1(A1(x)))) => x.
for (int i = 0; i < 8; i++) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* end = n1;
for (int j = 0; j < i; j++) {
end = graph()->NewNode(&kOpA1, end);
}
graph()->SetEnd(end);
A1Forwarder r;
for (size_t i = 0; i < 3; i++) {
size_t before = graph()->NodeCount();
ReduceGraph(&r);
EXPECT_EQ(before, graph()->NodeCount());
EXPECT_EQ(&kOpA0, n1->op());
EXPECT_EQ(n1, graph()->end());
}
}
}
TEST_F(GraphReducerTest, ReduceForward1) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* n2 = graph()->NewNode(&kOpA1, n1);
Node* n3 = graph()->NewNode(&kOpA1, n1);
Node* end = graph()->NewNode(&kOpA2, n2, n3);
graph()->SetEnd(end);
InPlaceABReducer r;
B1Forwarder f;
// Tests first reducing A => B, then B1(x) => x.
for (size_t i = 0; i < 3; i++) {
size_t before = graph()->NodeCount();
ReduceGraph(&r, &f);
EXPECT_EQ(before, graph()->NodeCount());
EXPECT_EQ(&kOpB0, n1->op());
EXPECT_TRUE(n2->IsDead());
EXPECT_EQ(n1, end->InputAt(0));
EXPECT_TRUE(n3->IsDead());
EXPECT_EQ(n1, end->InputAt(0));
EXPECT_EQ(&kOpB2, end->op());
EXPECT_EQ(0, n2->UseCount());
EXPECT_EQ(0, n3->UseCount());
}
}
TEST_F(GraphReducerTest, Sorter1) {
AB2Sorter r;
for (int i = 0; i < 6; i++) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* n2 = graph()->NewNode(&kOpA1, n1);
Node* n3 = graph()->NewNode(&kOpA1, n1);
Node* end = NULL; // Initialize to please the compiler.
if (i == 0) end = graph()->NewNode(&kOpA2, n2, n3);
if (i == 1) end = graph()->NewNode(&kOpA2, n3, n2);
if (i == 2) end = graph()->NewNode(&kOpA2, n2, n1);
if (i == 3) end = graph()->NewNode(&kOpA2, n1, n2);
if (i == 4) end = graph()->NewNode(&kOpA2, n3, n1);
if (i == 5) end = graph()->NewNode(&kOpA2, n1, n3);
graph()->SetEnd(end);
size_t before = graph()->NodeCount();
ReduceGraph(&r);
EXPECT_EQ(before, graph()->NodeCount());
EXPECT_EQ(&kOpA0, n1->op());
EXPECT_EQ(&kOpA1, n2->op());
EXPECT_EQ(&kOpA1, n3->op());
EXPECT_EQ(&kOpA2, end->op());
EXPECT_EQ(end, graph()->end());
EXPECT_LE(end->InputAt(0)->id(), end->InputAt(1)->id());
}
}
namespace {
// Generate a node graph with the given permutations.
void GenDAG(Graph* graph, int* p3, int* p2, int* p1) {
Node* level4 = graph->NewNode(&kOpA0);
Node* level3[] = {graph->NewNode(&kOpA1, level4),
graph->NewNode(&kOpA1, level4)};
Node* level2[] = {graph->NewNode(&kOpA1, level3[p3[0]]),
graph->NewNode(&kOpA1, level3[p3[1]]),
graph->NewNode(&kOpA1, level3[p3[0]]),
graph->NewNode(&kOpA1, level3[p3[1]])};
Node* level1[] = {graph->NewNode(&kOpA2, level2[p2[0]], level2[p2[1]]),
graph->NewNode(&kOpA2, level2[p2[2]], level2[p2[3]])};
Node* end = graph->NewNode(&kOpA2, level1[p1[0]], level1[p1[1]]);
graph->SetEnd(end);
}
} // namespace
TEST_F(GraphReducerTest, SortForwardReduce) {
// Tests combined reductions on a series of DAGs.
for (int j = 0; j < 2; j++) {
int p3[] = {j, 1 - j};
for (int m = 0; m < 2; m++) {
int p1[] = {m, 1 - m};
for (int k = 0; k < 24; k++) { // All permutations of 0, 1, 2, 3
int p2[] = {-1, -1, -1, -1};
int n = k;
for (int d = 4; d >= 1; d--) { // Construct permutation.
int p = n % d;
for (int z = 0; z < 4; z++) {
if (p2[z] == -1) {
if (p == 0) p2[z] = d - 1;
p--;
}
}
n = n / d;
}
GenDAG(graph(), p3, p2, p1);
AB2Sorter r1;
A1Forwarder r2;
InPlaceABReducer r3;
ReduceGraph(&r1, &r2, &r3);
Node* end = graph()->end();
EXPECT_EQ(&kOpB2, end->op());
Node* n1 = end->InputAt(0);
Node* n2 = end->InputAt(1);
EXPECT_NE(n1, n2);
EXPECT_LT(n1->id(), n2->id());
EXPECT_EQ(&kOpB2, n1->op());
EXPECT_EQ(&kOpB2, n2->op());
Node* n4 = n1->InputAt(0);
EXPECT_EQ(&kOpB0, n4->op());
EXPECT_EQ(n4, n1->InputAt(1));
EXPECT_EQ(n4, n2->InputAt(0));
EXPECT_EQ(n4, n2->InputAt(1));
}
}
}
}
TEST_F(GraphReducerTest, Order) {
// Test that the order of reducers doesn't matter, as they should be
// rerun for changed nodes.
for (int i = 0; i < 2; i++) {
Node* n1 = graph()->NewNode(&kOpA0);
Node* end = graph()->NewNode(&kOpA1, n1);
graph()->SetEnd(end);
InPlaceABReducer abr;
InPlaceBCReducer bcr;
// Tests A* => C* with in-place updates.
for (size_t j = 0; j < 3; j++) {
size_t before = graph()->NodeCount();
if (i == 0) {
ReduceGraph(&abr, &bcr);
} else {
ReduceGraph(&bcr, &abr);
}
EXPECT_EQ(before, graph()->NodeCount());
EXPECT_EQ(&kOpC0, n1->op());
EXPECT_EQ(&kOpC1, end->op());
EXPECT_EQ(n1, end->InputAt(0));
}
}
}
} // namespace compiler
} // namespace internal
} // namespace v8