[turbofan] Ensure that NTLs are always properly connected to the end.
Up until now we used a special Terminate node to artifically connect non terminating loops to the End node, but this was kind of adhoc and didn't work for the CFG. So without all kinds of weird hacks, the end block in the CFG will not be connected to NTLs, which makes it impossible to compute post dominance / control dependence in the current setting. So instead of Terminate, we add a special Branch to NTLs, whose condition is the special Always node, which corresponds to True, except that it cannot be folded away. This way we don't need any special machinery in the scheduler, since it's just a regular Branch. R=titzer@chromium.org Review URL: https://codereview.chromium.org/875263004 Cr-Commit-Position: refs/heads/master@{#26294}
This commit is contained in:
parent
f605f1c223
commit
59a02ebdbe
@ -110,6 +110,7 @@ size_t ProjectionIndexOf(const Operator* const op) {
|
||||
|
||||
|
||||
#define CACHED_OP_LIST(V) \
|
||||
V(Always, Operator::kPure, 0, 0, 0, 1, 0, 0) \
|
||||
V(Dead, Operator::kFoldable, 0, 0, 0, 0, 0, 1) \
|
||||
V(End, Operator::kFoldable, 0, 0, 1, 0, 0, 0) \
|
||||
V(IfTrue, Operator::kFoldable, 0, 0, 1, 0, 0, 1) \
|
||||
@ -294,14 +295,6 @@ const Operator* CommonOperatorBuilder::Merge(int control_input_count) {
|
||||
}
|
||||
|
||||
|
||||
const Operator* CommonOperatorBuilder::Terminate(int effects) {
|
||||
return new (zone()) Operator( // --
|
||||
IrOpcode::kTerminate, Operator::kPure, // opcode
|
||||
"Terminate", // name
|
||||
0, effects, 1, 0, 0, 1); // counts
|
||||
}
|
||||
|
||||
|
||||
const Operator* CommonOperatorBuilder::Parameter(int index) {
|
||||
switch (index) {
|
||||
#define CACHED_PARAMETER(index) \
|
||||
@ -427,6 +420,15 @@ const Operator* CommonOperatorBuilder::EffectPhi(int arguments) {
|
||||
}
|
||||
|
||||
|
||||
const Operator* CommonOperatorBuilder::EffectSet(int arguments) {
|
||||
DCHECK(arguments > 1); // Disallow empty/singleton sets.
|
||||
return new (zone()) Operator( // --
|
||||
IrOpcode::kEffectSet, Operator::kPure, // opcode
|
||||
"EffectSet", // name
|
||||
0, arguments, 0, 0, 1, 0); // counts
|
||||
}
|
||||
|
||||
|
||||
const Operator* CommonOperatorBuilder::ValueEffect(int arguments) {
|
||||
DCHECK(arguments > 0); // Disallow empty value effects.
|
||||
return new (zone()) Operator( // --
|
||||
|
@ -162,13 +162,17 @@ class CommonOperatorBuilder FINAL : public ZoneObject {
|
||||
public:
|
||||
explicit CommonOperatorBuilder(Zone* zone);
|
||||
|
||||
// Special operator used only in Branches to mark them as always taken, but
|
||||
// still unfoldable. This is required to properly connect non terminating
|
||||
// loops to end (in both the sea of nodes and the CFG).
|
||||
const Operator* Always();
|
||||
|
||||
const Operator* Dead();
|
||||
const Operator* End();
|
||||
const Operator* Branch(BranchHint = BranchHint::kNone);
|
||||
const Operator* IfTrue();
|
||||
const Operator* IfFalse();
|
||||
const Operator* Throw();
|
||||
const Operator* Terminate(int effects);
|
||||
const Operator* Return();
|
||||
|
||||
const Operator* Start(int num_formal_parameters);
|
||||
@ -191,6 +195,7 @@ class CommonOperatorBuilder FINAL : public ZoneObject {
|
||||
const Operator* Select(MachineType, BranchHint = BranchHint::kNone);
|
||||
const Operator* Phi(MachineType type, int arguments);
|
||||
const Operator* EffectPhi(int arguments);
|
||||
const Operator* EffectSet(int arguments);
|
||||
const Operator* ValueEffect(int arguments);
|
||||
const Operator* Finish(int arguments);
|
||||
const Operator* StateValues(int arguments);
|
||||
|
@ -169,44 +169,80 @@ class ControlReducerImpl {
|
||||
Node* ConnectNTL(Node* loop) {
|
||||
TRACE(("ConnectNTL: #%d:%s\n", loop->id(), loop->op()->mnemonic()));
|
||||
|
||||
if (loop->opcode() != IrOpcode::kTerminate) {
|
||||
// Insert a {Terminate} node if the loop has effects.
|
||||
ZoneDeque<Node*> effects(zone_);
|
||||
for (Node* const use : loop->uses()) {
|
||||
if (use->opcode() == IrOpcode::kEffectPhi) effects.push_back(use);
|
||||
}
|
||||
int count = static_cast<int>(effects.size());
|
||||
if (count > 0) {
|
||||
Node** inputs = zone_->NewArray<Node*>(1 + count);
|
||||
for (int i = 0; i < count; i++) inputs[i] = effects[i];
|
||||
inputs[count] = loop;
|
||||
loop = graph()->NewNode(common_->Terminate(count), 1 + count, inputs);
|
||||
TRACE(("AddTerminate: #%d:%s[%d]\n", loop->id(), loop->op()->mnemonic(),
|
||||
count));
|
||||
Node* always = graph()->NewNode(common_->Always());
|
||||
// Mark the node as visited so that we can revisit later.
|
||||
MarkAsVisited(always);
|
||||
|
||||
Node* branch = graph()->NewNode(common_->Branch(), always, loop);
|
||||
// Mark the node as visited so that we can revisit later.
|
||||
MarkAsVisited(branch);
|
||||
|
||||
Node* if_true = graph()->NewNode(common_->IfTrue(), branch);
|
||||
// Mark the node as visited so that we can revisit later.
|
||||
MarkAsVisited(if_true);
|
||||
|
||||
Node* if_false = graph()->NewNode(common_->IfFalse(), branch);
|
||||
// Mark the node as visited so that we can revisit later.
|
||||
MarkAsVisited(if_false);
|
||||
|
||||
// Hook up the branch into the loop and collect all loop effects.
|
||||
NodeVector effects(zone_);
|
||||
for (auto edge : loop->use_edges()) {
|
||||
DCHECK_EQ(loop, edge.to());
|
||||
DCHECK(NodeProperties::IsControlEdge(edge));
|
||||
if (edge.from() == branch) continue;
|
||||
switch (edge.from()->opcode()) {
|
||||
#define CASE(Opcode) case IrOpcode::k##Opcode:
|
||||
CONTROL_OP_LIST(CASE)
|
||||
#undef CASE
|
||||
// Update all control nodes (except {branch}) pointing to the {loop}.
|
||||
edge.UpdateTo(if_true);
|
||||
break;
|
||||
case IrOpcode::kEffectPhi:
|
||||
effects.push_back(edge.from());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Node* to_add = loop;
|
||||
// Compute effects for the Return.
|
||||
Node* effect = graph()->start();
|
||||
int const effects_count = static_cast<int>(effects.size());
|
||||
if (effects_count == 1) {
|
||||
effect = effects[0];
|
||||
} else if (effects_count > 1) {
|
||||
effect = graph()->NewNode(common_->EffectSet(effects_count),
|
||||
effects_count, &effects.front());
|
||||
// Mark the node as visited so that we can revisit later.
|
||||
MarkAsVisited(effect);
|
||||
}
|
||||
|
||||
// Add a return to connect the NTL to the end.
|
||||
Node* ret = graph()->NewNode(
|
||||
common_->Return(), jsgraph_->UndefinedConstant(), effect, if_false);
|
||||
// Mark the node as visited so that we can revisit later.
|
||||
MarkAsVisited(ret);
|
||||
|
||||
Node* end = graph()->end();
|
||||
CHECK_EQ(IrOpcode::kEnd, end->opcode());
|
||||
Node* merge = end->InputAt(0);
|
||||
if (merge == NULL || merge->opcode() == IrOpcode::kDead) {
|
||||
// The end node died; just connect end to {loop}.
|
||||
end->ReplaceInput(0, loop);
|
||||
// The end node died; just connect end to {ret}.
|
||||
end->ReplaceInput(0, ret);
|
||||
} else if (merge->opcode() != IrOpcode::kMerge) {
|
||||
// Introduce a final merge node for {end->InputAt(0)} and {loop}.
|
||||
merge = graph()->NewNode(common_->Merge(2), merge, loop);
|
||||
// Introduce a final merge node for {end->InputAt(0)} and {ret}.
|
||||
merge = graph()->NewNode(common_->Merge(2), merge, ret);
|
||||
end->ReplaceInput(0, merge);
|
||||
to_add = merge;
|
||||
ret = merge;
|
||||
// Mark the node as visited so that we can revisit later.
|
||||
EnsureStateSize(merge->id());
|
||||
state_[merge->id()] = kVisited;
|
||||
MarkAsVisited(merge);
|
||||
} else {
|
||||
// Append a new input to the final merge at the end.
|
||||
merge->AppendInput(graph()->zone(), loop);
|
||||
merge->AppendInput(graph()->zone(), ret);
|
||||
merge->set_op(common_->Merge(merge->InputCount()));
|
||||
}
|
||||
return to_add;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void AddNodesReachableFromEnd(ReachabilityMarker& marked, NodeVector& nodes) {
|
||||
@ -337,6 +373,13 @@ class ControlReducerImpl {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark {node} as visited.
|
||||
void MarkAsVisited(Node* node) {
|
||||
size_t id = static_cast<size_t>(node->id());
|
||||
EnsureStateSize(id);
|
||||
state_[id] = kVisited;
|
||||
}
|
||||
|
||||
Node* dead() {
|
||||
if (dead_ == NULL) dead_ = graph()->NewNode(common_->Dead());
|
||||
return dead_;
|
||||
|
@ -7,8 +7,14 @@
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
// Forward declarations.
|
||||
class Zone;
|
||||
|
||||
|
||||
namespace compiler {
|
||||
|
||||
// Forward declarations.
|
||||
class JSGraph;
|
||||
class CommonOperatorBuilder;
|
||||
class Node;
|
||||
|
@ -508,6 +508,9 @@ void InstructionSelector::VisitControl(BasicBlock* block) {
|
||||
CheckNoPhis(tbranch);
|
||||
CheckNoPhis(fbranch);
|
||||
if (tbranch == fbranch) return VisitGoto(tbranch);
|
||||
// Treat special Branch(Always, IfTrue, IfFalse) as Goto(IfTrue).
|
||||
Node* const condition = input->InputAt(0);
|
||||
if (condition->opcode() == IrOpcode::kAlways) return VisitGoto(tbranch);
|
||||
return VisitBranch(input, tbranch, fbranch);
|
||||
}
|
||||
case BasicBlock::kReturn: {
|
||||
@ -541,8 +544,8 @@ MachineType InstructionSelector::GetMachineType(Node* node) {
|
||||
case IrOpcode::kIfTrue:
|
||||
case IrOpcode::kIfFalse:
|
||||
case IrOpcode::kEffectPhi:
|
||||
case IrOpcode::kEffectSet:
|
||||
case IrOpcode::kMerge:
|
||||
case IrOpcode::kTerminate:
|
||||
// No code needed for these graph artifacts.
|
||||
return kMachNone;
|
||||
case IrOpcode::kFinish:
|
||||
|
@ -45,9 +45,12 @@ Reduction JSGenericLowering::Reduce(Node* node) {
|
||||
// have inserted the correct ChangeBoolToBit, otherwise we need to perform
|
||||
// poor-man's representation inference here and insert manual change.
|
||||
if (!info()->is_typing_enabled()) {
|
||||
Node* test = graph()->NewNode(machine()->WordEqual(), node->InputAt(0),
|
||||
jsgraph()->TrueConstant());
|
||||
node->ReplaceInput(0, test);
|
||||
Node* condition = node->InputAt(0);
|
||||
if (condition->opcode() != IrOpcode::kAlways) {
|
||||
Node* test = graph()->NewNode(machine()->WordEqual(), condition,
|
||||
jsgraph()->TrueConstant());
|
||||
node->ReplaceInput(0, test);
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Fall-through.
|
||||
|
@ -14,7 +14,6 @@
|
||||
V(IfFalse) \
|
||||
V(Merge) \
|
||||
V(Return) \
|
||||
V(Terminate) \
|
||||
V(OsrNormalEntry) \
|
||||
V(OsrLoopEntry) \
|
||||
V(Throw)
|
||||
@ -37,6 +36,7 @@
|
||||
#define INNER_OP_LIST(V) \
|
||||
V(Select) \
|
||||
V(Phi) \
|
||||
V(EffectSet) \
|
||||
V(EffectPhi) \
|
||||
V(ValueEffect) \
|
||||
V(Finish) \
|
||||
@ -49,7 +49,8 @@
|
||||
|
||||
#define COMMON_OP_LIST(V) \
|
||||
CONSTANT_OP_LIST(V) \
|
||||
INNER_OP_LIST(V)
|
||||
INNER_OP_LIST(V) \
|
||||
V(Always)
|
||||
|
||||
// Opcodes for JavaScript operators.
|
||||
#define JS_COMPARE_BINOP_LIST(V) \
|
||||
@ -277,7 +278,7 @@ class IrOpcode {
|
||||
|
||||
// Returns true if opcode for common operator.
|
||||
static bool IsCommonOpcode(Value value) {
|
||||
return kDead <= value && value <= kProjection;
|
||||
return kDead <= value && value <= kAlways;
|
||||
}
|
||||
|
||||
// Returns true if opcode for control operator.
|
||||
|
@ -316,13 +316,6 @@ class CFGBuilder : public ZoneObject {
|
||||
case IrOpcode::kMerge:
|
||||
BuildBlockForNode(node);
|
||||
break;
|
||||
case IrOpcode::kTerminate: {
|
||||
// Put Terminate in the loop to which it refers.
|
||||
Node* loop = NodeProperties::GetControlInput(node);
|
||||
BasicBlock* block = BuildBlockForNode(loop);
|
||||
FixNode(block, node);
|
||||
break;
|
||||
}
|
||||
case IrOpcode::kBranch:
|
||||
BuildBlocksForSuccessors(node, IrOpcode::kIfTrue, IrOpcode::kIfFalse);
|
||||
break;
|
||||
|
@ -473,6 +473,8 @@ class RepresentationSelector {
|
||||
SetOutput(node, kRepTagged | changer_->TypeFromUpperBound(upper));
|
||||
return;
|
||||
}
|
||||
case IrOpcode::kAlways:
|
||||
return VisitLeaf(node, kRepBit);
|
||||
case IrOpcode::kInt32Constant:
|
||||
return VisitLeaf(node, kRepWord32);
|
||||
case IrOpcode::kInt64Constant:
|
||||
|
@ -589,6 +589,11 @@ Bounds Typer::Visitor::TypeStart(Node* node) {
|
||||
// Common operators.
|
||||
|
||||
|
||||
Bounds Typer::Visitor::TypeAlways(Node* node) {
|
||||
return Bounds(Type::None(zone()), Type::Boolean(zone()));
|
||||
}
|
||||
|
||||
|
||||
Bounds Typer::Visitor::TypeParameter(Node* node) {
|
||||
return Bounds::Unbounded(zone());
|
||||
}
|
||||
@ -670,6 +675,12 @@ Bounds Typer::Visitor::TypeEffectPhi(Node* node) {
|
||||
}
|
||||
|
||||
|
||||
Bounds Typer::Visitor::TypeEffectSet(Node* node) {
|
||||
UNREACHABLE();
|
||||
return Bounds();
|
||||
}
|
||||
|
||||
|
||||
Bounds Typer::Visitor::TypeValueEffect(Node* node) {
|
||||
UNREACHABLE();
|
||||
return Bounds();
|
||||
|
@ -178,6 +178,16 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
}
|
||||
|
||||
switch (node->opcode()) {
|
||||
case IrOpcode::kAlways:
|
||||
// Always has no inputs.
|
||||
CHECK_EQ(0, input_count);
|
||||
// Always uses are Branch.
|
||||
for (auto use : node->uses()) {
|
||||
CHECK(use->opcode() == IrOpcode::kBranch);
|
||||
}
|
||||
// Type is boolean.
|
||||
CheckUpperIs(node, Type::Boolean());
|
||||
break;
|
||||
case IrOpcode::kStart:
|
||||
// Start has no inputs.
|
||||
CHECK_EQ(0, input_count);
|
||||
@ -233,12 +243,6 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
// Type is empty.
|
||||
CheckNotTyped(node);
|
||||
break;
|
||||
case IrOpcode::kTerminate:
|
||||
// Type is empty.
|
||||
CheckNotTyped(node);
|
||||
CHECK_EQ(1, control_count);
|
||||
CHECK_EQ(input_count, 1 + effect_count);
|
||||
break;
|
||||
case IrOpcode::kOsrNormalEntry:
|
||||
case IrOpcode::kOsrLoopEntry:
|
||||
// Osr entries have
|
||||
@ -348,6 +352,12 @@ void Verifier::Visitor::Check(Node* node) {
|
||||
CHECK_EQ(input_count, 1 + effect_count);
|
||||
break;
|
||||
}
|
||||
case IrOpcode::kEffectSet: {
|
||||
CHECK_EQ(0, value_count);
|
||||
CHECK_EQ(0, control_count);
|
||||
CHECK_LT(1, effect_count);
|
||||
break;
|
||||
}
|
||||
case IrOpcode::kValueEffect:
|
||||
// TODO(rossberg): what are the constraints on these?
|
||||
break;
|
||||
|
@ -1214,172 +1214,6 @@ TEST(CDeadLoop2) {
|
||||
}
|
||||
|
||||
|
||||
TEST(CNonTermLoop1) {
|
||||
ControlReducerTester R;
|
||||
Node* loop =
|
||||
R.SetSelfReferences(R.graph.NewNode(R.common.Loop(2), R.start, R.self));
|
||||
R.ReduceGraph();
|
||||
Node* end = R.graph.end();
|
||||
CheckLoop(loop, R.start, loop);
|
||||
Node* merge = end->InputAt(0);
|
||||
CheckMerge(merge, R.start, loop);
|
||||
}
|
||||
|
||||
|
||||
TEST(CNonTermLoop2) {
|
||||
ControlReducerTester R;
|
||||
Diamond d(R, R.p0);
|
||||
Node* loop = R.SetSelfReferences(
|
||||
R.graph.NewNode(R.common.Loop(2), d.if_false, R.self));
|
||||
d.merge->ReplaceInput(1, R.dead);
|
||||
Node* end = R.graph.end();
|
||||
end->ReplaceInput(0, d.merge);
|
||||
R.ReduceGraph();
|
||||
CHECK_EQ(end, R.graph.end());
|
||||
CheckLoop(loop, d.if_false, loop);
|
||||
Node* merge = end->InputAt(0);
|
||||
CheckMerge(merge, d.if_true, loop);
|
||||
}
|
||||
|
||||
|
||||
TEST(NonTermLoop3) {
|
||||
ControlReducerTester R;
|
||||
Node* loop = R.graph.NewNode(R.common.Loop(2), R.start, R.start);
|
||||
Branch b(R, R.one, loop);
|
||||
loop->ReplaceInput(1, b.if_true);
|
||||
Node* end = R.graph.end();
|
||||
end->ReplaceInput(0, b.if_false);
|
||||
|
||||
R.ReduceGraph();
|
||||
|
||||
CHECK_EQ(end, R.graph.end());
|
||||
CheckInputs(end, loop);
|
||||
CheckInputs(loop, R.start, loop);
|
||||
}
|
||||
|
||||
|
||||
TEST(CNonTermLoop_terminate1) {
|
||||
ControlReducerTester R;
|
||||
Node* loop = R.graph.NewNode(R.common.Loop(2), R.start, R.start);
|
||||
Node* effect = R.SetSelfReferences(
|
||||
R.graph.NewNode(R.common.EffectPhi(2), R.start, R.self, loop));
|
||||
Branch b(R, R.one, loop);
|
||||
loop->ReplaceInput(1, b.if_true);
|
||||
Node* end = R.graph.end();
|
||||
end->ReplaceInput(0, b.if_false);
|
||||
|
||||
R.ReduceGraph();
|
||||
|
||||
CHECK_EQ(end, R.graph.end());
|
||||
CheckLoop(loop, R.start, loop);
|
||||
Node* terminate = end->InputAt(0);
|
||||
CHECK_EQ(IrOpcode::kTerminate, terminate->opcode());
|
||||
CHECK_EQ(2, terminate->InputCount());
|
||||
CHECK_EQ(1, terminate->op()->EffectInputCount());
|
||||
CHECK_EQ(1, terminate->op()->ControlInputCount());
|
||||
CheckInputs(terminate, effect, loop);
|
||||
}
|
||||
|
||||
|
||||
TEST(CNonTermLoop_terminate2) {
|
||||
ControlReducerTester R;
|
||||
Node* loop = R.graph.NewNode(R.common.Loop(2), R.start, R.start);
|
||||
Node* effect1 = R.SetSelfReferences(
|
||||
R.graph.NewNode(R.common.EffectPhi(2), R.start, R.self, loop));
|
||||
Node* effect2 = R.SetSelfReferences(
|
||||
R.graph.NewNode(R.common.EffectPhi(2), R.start, R.self, loop));
|
||||
Branch b(R, R.one, loop);
|
||||
loop->ReplaceInput(1, b.if_true);
|
||||
Node* end = R.graph.end();
|
||||
end->ReplaceInput(0, b.if_false);
|
||||
|
||||
R.ReduceGraph();
|
||||
|
||||
CheckLoop(loop, R.start, loop);
|
||||
CHECK_EQ(end, R.graph.end());
|
||||
Node* terminate = end->InputAt(0);
|
||||
CHECK_EQ(IrOpcode::kTerminate, terminate->opcode());
|
||||
CHECK_EQ(3, terminate->InputCount());
|
||||
CHECK_EQ(2, terminate->op()->EffectInputCount());
|
||||
CHECK_EQ(1, terminate->op()->ControlInputCount());
|
||||
Node* e0 = terminate->InputAt(0);
|
||||
Node* e1 = terminate->InputAt(1);
|
||||
CHECK(e0 == effect1 || e1 == effect1);
|
||||
CHECK(e0 == effect2 || e1 == effect2);
|
||||
CHECK_EQ(loop, terminate->InputAt(2));
|
||||
}
|
||||
|
||||
|
||||
TEST(CNonTermLoop_terminate_m1) {
|
||||
ControlReducerTester R;
|
||||
Node* loop =
|
||||
R.SetSelfReferences(R.graph.NewNode(R.common.Loop(2), R.start, R.self));
|
||||
Node* effect = R.SetSelfReferences(
|
||||
R.graph.NewNode(R.common.EffectPhi(2), R.start, R.self, loop));
|
||||
R.ReduceGraph();
|
||||
Node* end = R.graph.end();
|
||||
CHECK_EQ(R.start, loop->InputAt(0));
|
||||
CHECK_EQ(loop, loop->InputAt(1));
|
||||
Node* merge = end->InputAt(0);
|
||||
CHECK_EQ(IrOpcode::kMerge, merge->opcode());
|
||||
CHECK_EQ(2, merge->InputCount());
|
||||
CHECK_EQ(2, merge->op()->ControlInputCount());
|
||||
CHECK_EQ(R.start, merge->InputAt(0));
|
||||
|
||||
Node* terminate = merge->InputAt(1);
|
||||
CHECK_EQ(IrOpcode::kTerminate, terminate->opcode());
|
||||
CHECK_EQ(2, terminate->InputCount());
|
||||
CHECK_EQ(1, terminate->op()->EffectInputCount());
|
||||
CHECK_EQ(1, terminate->op()->ControlInputCount());
|
||||
CHECK_EQ(effect, terminate->InputAt(0));
|
||||
CHECK_EQ(loop, terminate->InputAt(1));
|
||||
}
|
||||
|
||||
|
||||
TEST(CNonTermLoop_big1) {
|
||||
ControlReducerTester R;
|
||||
Branch b1(R, R.p0);
|
||||
Node* rt = R.graph.NewNode(R.common.Return(), R.one, R.start, b1.if_true);
|
||||
|
||||
Branch b2(R, R.p0, b1.if_false);
|
||||
Node* rf = R.graph.NewNode(R.common.Return(), R.zero, R.start, b2.if_true);
|
||||
Node* loop = R.SetSelfReferences(
|
||||
R.graph.NewNode(R.common.Loop(2), b2.if_false, R.self));
|
||||
Node* merge = R.graph.NewNode(R.common.Merge(2), rt, rf);
|
||||
R.end->ReplaceInput(0, merge);
|
||||
|
||||
R.ReduceGraph();
|
||||
|
||||
CheckInputs(R.end, merge);
|
||||
CheckInputs(merge, rt, rf, loop);
|
||||
CheckInputs(loop, b2.if_false, loop);
|
||||
}
|
||||
|
||||
|
||||
TEST(CNonTermLoop_big2) {
|
||||
ControlReducerTester R;
|
||||
Branch b1(R, R.p0);
|
||||
Node* rt = R.graph.NewNode(R.common.Return(), R.one, R.start, b1.if_true);
|
||||
|
||||
Node* loop = R.graph.NewNode(R.common.Loop(2), b1.if_false, R.start);
|
||||
Branch b2(R, R.zero, loop);
|
||||
loop->ReplaceInput(1, b2.if_false);
|
||||
Node* rf = R.graph.NewNode(R.common.Return(), R.zero, R.start, b2.if_true);
|
||||
Node* merge = R.graph.NewNode(R.common.Merge(2), rt, rf);
|
||||
R.end->ReplaceInput(0, merge);
|
||||
|
||||
R.ReduceGraph();
|
||||
|
||||
Node* new_merge = R.end->InputAt(0); // old merge was reduced.
|
||||
CHECK_NE(merge, new_merge);
|
||||
CheckInputs(new_merge, rt, loop);
|
||||
CheckInputs(loop, b1.if_false, loop);
|
||||
CHECK(merge->IsDead());
|
||||
CHECK(rf->IsDead());
|
||||
CHECK(b2.if_true->IsDead());
|
||||
}
|
||||
|
||||
|
||||
TEST(Return1) {
|
||||
ControlReducerTester R;
|
||||
Node* ret = R.Return(R.one, R.start, R.start);
|
||||
|
@ -28,6 +28,7 @@ struct SharedOperator {
|
||||
int value_input_count;
|
||||
int effect_input_count;
|
||||
int control_input_count;
|
||||
int value_output_count;
|
||||
int effect_output_count;
|
||||
int control_output_count;
|
||||
};
|
||||
@ -39,19 +40,21 @@ std::ostream& operator<<(std::ostream& os, const SharedOperator& fop) {
|
||||
|
||||
|
||||
const SharedOperator kSharedOperators[] = {
|
||||
#define SHARED(Name, properties, value_input_count, effect_input_count, \
|
||||
control_input_count, effect_output_count, control_output_count) \
|
||||
{ \
|
||||
&CommonOperatorBuilder::Name, IrOpcode::k##Name, properties, \
|
||||
value_input_count, effect_input_count, control_input_count, \
|
||||
effect_output_count, control_output_count \
|
||||
#define SHARED(Name, properties, value_input_count, effect_input_count, \
|
||||
control_input_count, value_output_count, effect_output_count, \
|
||||
control_output_count) \
|
||||
{ \
|
||||
&CommonOperatorBuilder::Name, IrOpcode::k##Name, properties, \
|
||||
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, 0, 1),
|
||||
SHARED(End, Operator::kFoldable, 0, 0, 1, 0, 0),
|
||||
SHARED(IfTrue, Operator::kFoldable, 0, 0, 1, 0, 1),
|
||||
SHARED(IfFalse, Operator::kFoldable, 0, 0, 1, 0, 1),
|
||||
SHARED(Throw, Operator::kFoldable, 1, 1, 1, 0, 1),
|
||||
SHARED(Return, Operator::kNoProperties, 1, 1, 1, 0, 1)
|
||||
SHARED(Always, Operator::kPure, 0, 0, 0, 1, 0, 0),
|
||||
SHARED(Dead, Operator::kFoldable, 0, 0, 0, 0, 0, 1),
|
||||
SHARED(End, Operator::kFoldable, 0, 0, 1, 0, 0, 0),
|
||||
SHARED(IfTrue, Operator::kFoldable, 0, 0, 1, 0, 0, 1),
|
||||
SHARED(IfFalse, Operator::kFoldable, 0, 0, 1, 0, 0, 1),
|
||||
SHARED(Throw, Operator::kFoldable, 1, 1, 1, 0, 0, 1),
|
||||
SHARED(Return, Operator::kNoProperties, 1, 1, 1, 0, 0, 1)
|
||||
#undef SHARED
|
||||
};
|
||||
|
||||
@ -83,7 +86,7 @@ TEST_P(CommonSharedOperatorTest, NumberOfInputsAndOutputs) {
|
||||
sop.value_input_count + sop.effect_input_count + sop.control_input_count,
|
||||
OperatorProperties::GetTotalInputCount(op));
|
||||
|
||||
EXPECT_EQ(0, op->ValueOutputCount());
|
||||
EXPECT_EQ(sop.value_output_count, op->ValueOutputCount());
|
||||
EXPECT_EQ(sop.effect_output_count, op->EffectOutputCount());
|
||||
EXPECT_EQ(sop.control_output_count, op->ControlOutputCount());
|
||||
}
|
||||
|
124
test/unittests/compiler/control-reducer-unittest.cc
Normal file
124
test/unittests/compiler/control-reducer-unittest.cc
Normal file
@ -0,0 +1,124 @@
|
||||
// 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/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 {
|
||||
protected:
|
||||
void ReduceGraph() {
|
||||
JSOperatorBuilder javascript(zone());
|
||||
MachineOperatorBuilder machine(zone());
|
||||
JSGraph jsgraph(isolate(), graph(), common(), &javascript, &machine);
|
||||
ControlReducer::ReduceGraph(zone(), &jsgraph, common());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
TEST_F(ControlReducerTest, NonTerminatingLoop) {
|
||||
Node* loop = graph()->NewNode(common()->Loop(2), graph()->start());
|
||||
loop->AppendInput(graph()->zone(), loop);
|
||||
ReduceGraph();
|
||||
Capture<Node*> branch;
|
||||
EXPECT_THAT(
|
||||
graph()->end(),
|
||||
IsEnd(IsMerge(
|
||||
graph()->start(),
|
||||
IsReturn(IsUndefinedConstant(), graph()->start(),
|
||||
IsIfFalse(
|
||||
AllOf(CaptureEq(&branch),
|
||||
IsBranch(IsAlways(),
|
||||
AllOf(loop, IsLoop(graph()->start(),
|
||||
IsIfTrue(CaptureEq(
|
||||
&branch)))))))))));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ControlReducerTest, NonTerminatingLoopWithEffectPhi) {
|
||||
Node* loop = graph()->NewNode(common()->Loop(2), graph()->start());
|
||||
loop->AppendInput(graph()->zone(), loop);
|
||||
Node* ephi = graph()->NewNode(common()->EffectPhi(2), graph()->start());
|
||||
ephi->AppendInput(graph()->zone(), ephi);
|
||||
ephi->AppendInput(graph()->zone(), loop);
|
||||
ReduceGraph();
|
||||
Capture<Node*> branch;
|
||||
EXPECT_THAT(
|
||||
graph()->end(),
|
||||
IsEnd(IsMerge(
|
||||
graph()->start(),
|
||||
IsReturn(IsUndefinedConstant(),
|
||||
AllOf(ephi, IsEffectPhi(graph()->start(), ephi, loop)),
|
||||
IsIfFalse(
|
||||
AllOf(CaptureEq(&branch),
|
||||
IsBranch(IsAlways(),
|
||||
AllOf(loop, IsLoop(graph()->start(),
|
||||
IsIfTrue(CaptureEq(
|
||||
&branch)))))))))));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ControlReducerTest, NonTerminatingLoopWithTwoEffectPhis) {
|
||||
Node* loop = graph()->NewNode(common()->Loop(2), graph()->start());
|
||||
loop->AppendInput(graph()->zone(), loop);
|
||||
Node* ephi1 = graph()->NewNode(common()->EffectPhi(2), graph()->start());
|
||||
ephi1->AppendInput(graph()->zone(), ephi1);
|
||||
ephi1->AppendInput(graph()->zone(), loop);
|
||||
Node* ephi2 = graph()->NewNode(common()->EffectPhi(2), graph()->start());
|
||||
ephi2->AppendInput(graph()->zone(), ephi2);
|
||||
ephi2->AppendInput(graph()->zone(), loop);
|
||||
ReduceGraph();
|
||||
Capture<Node*> branch;
|
||||
EXPECT_THAT(
|
||||
graph()->end(),
|
||||
IsEnd(IsMerge(
|
||||
graph()->start(),
|
||||
IsReturn(
|
||||
IsUndefinedConstant(),
|
||||
IsEffectSet(
|
||||
AllOf(ephi1, IsEffectPhi(graph()->start(), ephi1, loop)),
|
||||
AllOf(ephi2, IsEffectPhi(graph()->start(), ephi2, loop))),
|
||||
IsIfFalse(AllOf(
|
||||
CaptureEq(&branch),
|
||||
IsBranch(
|
||||
IsAlways(),
|
||||
AllOf(loop, IsLoop(graph()->start(),
|
||||
IsIfTrue(CaptureEq(&branch)))))))))));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(ControlReducerTest, NonTerminatingLoopWithDeadEnd) {
|
||||
Node* loop = graph()->NewNode(common()->Loop(2), graph()->start());
|
||||
loop->AppendInput(graph()->zone(), loop);
|
||||
graph()->end()->ReplaceInput(0, graph()->NewNode(common()->Dead()));
|
||||
ReduceGraph();
|
||||
Capture<Node*> branch;
|
||||
EXPECT_THAT(
|
||||
graph()->end(),
|
||||
IsEnd(IsReturn(
|
||||
IsUndefinedConstant(), graph()->start(),
|
||||
IsIfFalse(AllOf(
|
||||
CaptureEq(&branch),
|
||||
IsBranch(IsAlways(),
|
||||
AllOf(loop, IsLoop(graph()->start(),
|
||||
IsIfTrue(CaptureEq(&branch))))))))));
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
@ -13,6 +13,7 @@ namespace compiler {
|
||||
|
||||
GraphTest::GraphTest(int num_parameters) : common_(zone()), graph_(zone()) {
|
||||
graph()->SetStart(graph()->NewNode(common()->Start(num_parameters)));
|
||||
graph()->SetEnd(graph()->NewNode(common()->End(), graph()->start()));
|
||||
}
|
||||
|
||||
|
||||
@ -92,6 +93,12 @@ Matcher<Node*> GraphTest::IsTrueConstant() {
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> GraphTest::IsUndefinedConstant() {
|
||||
return IsHeapConstant(
|
||||
Unique<HeapObject>::CreateImmovable(factory()->undefined_value()));
|
||||
}
|
||||
|
||||
|
||||
TypedGraphTest::TypedGraphTest(int num_parameters)
|
||||
: GraphTest(num_parameters),
|
||||
typer_(isolate(), graph(), MaybeHandle<Context>()) {}
|
||||
|
@ -50,6 +50,7 @@ class GraphTest : public TestWithContext, public TestWithIsolateAndZone {
|
||||
|
||||
Matcher<Node*> IsFalseConstant();
|
||||
Matcher<Node*> IsTrueConstant();
|
||||
Matcher<Node*> IsUndefinedConstant();
|
||||
|
||||
CommonOperatorBuilder* common() { return &common_; }
|
||||
Graph* graph() { return &graph_; }
|
||||
|
@ -462,6 +462,37 @@ class IsEffectPhiMatcher FINAL : public NodeMatcher {
|
||||
};
|
||||
|
||||
|
||||
class IsEffectSetMatcher FINAL : public NodeMatcher {
|
||||
public:
|
||||
IsEffectSetMatcher(const Matcher<Node*>& effect0_matcher,
|
||||
const Matcher<Node*>& effect1_matcher)
|
||||
: NodeMatcher(IrOpcode::kEffectSet),
|
||||
effect0_matcher_(effect0_matcher),
|
||||
effect1_matcher_(effect1_matcher) {}
|
||||
|
||||
void DescribeTo(std::ostream* os) const FINAL {
|
||||
NodeMatcher::DescribeTo(os);
|
||||
*os << "), effect0 (";
|
||||
effect0_matcher_.DescribeTo(os);
|
||||
*os << ") and effect1 (";
|
||||
effect1_matcher_.DescribeTo(os);
|
||||
*os << ")";
|
||||
}
|
||||
|
||||
bool MatchAndExplain(Node* node, MatchResultListener* listener) const FINAL {
|
||||
return (NodeMatcher::MatchAndExplain(node, listener) &&
|
||||
PrintMatchAndExplain(NodeProperties::GetEffectInput(node, 0),
|
||||
"effect0", effect0_matcher_, listener) &&
|
||||
PrintMatchAndExplain(NodeProperties::GetEffectInput(node, 1),
|
||||
"effect1", effect1_matcher_, listener));
|
||||
}
|
||||
|
||||
private:
|
||||
const Matcher<Node*> effect0_matcher_;
|
||||
const Matcher<Node*> effect1_matcher_;
|
||||
};
|
||||
|
||||
|
||||
class IsProjectionMatcher FINAL : public NodeMatcher {
|
||||
public:
|
||||
IsProjectionMatcher(const Matcher<size_t>& index_matcher,
|
||||
@ -1148,6 +1179,17 @@ class IsUnopMatcher FINAL : public NodeMatcher {
|
||||
private:
|
||||
const Matcher<Node*> input_matcher_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
Matcher<Node*> IsAlways() {
|
||||
return MakeMatcher(new NodeMatcher(IrOpcode::kAlways));
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsEnd(const Matcher<Node*>& control_matcher) {
|
||||
return MakeMatcher(new IsControl1Matcher(IrOpcode::kEnd, control_matcher));
|
||||
}
|
||||
|
||||
|
||||
@ -1290,6 +1332,12 @@ Matcher<Node*> IsEffectPhi(const Matcher<Node*>& effect0_matcher,
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsEffectSet(const Matcher<Node*>& effect0_matcher,
|
||||
const Matcher<Node*>& effect1_matcher) {
|
||||
return MakeMatcher(new IsEffectSetMatcher(effect0_matcher, effect1_matcher));
|
||||
}
|
||||
|
||||
|
||||
Matcher<Node*> IsProjection(const Matcher<size_t>& index_matcher,
|
||||
const Matcher<Node*>& base_matcher) {
|
||||
return MakeMatcher(new IsProjectionMatcher(index_matcher, base_matcher));
|
||||
|
@ -31,6 +31,8 @@ class Node;
|
||||
using ::testing::Matcher;
|
||||
|
||||
|
||||
Matcher<Node*> IsAlways();
|
||||
Matcher<Node*> IsEnd(const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsBranch(const Matcher<Node*>& value_matcher,
|
||||
const Matcher<Node*>& control_matcher);
|
||||
Matcher<Node*> IsMerge(const Matcher<Node*>& control0_matcher,
|
||||
@ -73,6 +75,8 @@ Matcher<Node*> IsPhi(const Matcher<MachineType>& type_matcher,
|
||||
Matcher<Node*> IsEffectPhi(const Matcher<Node*>& effect0_matcher,
|
||||
const Matcher<Node*>& effect1_matcher,
|
||||
const Matcher<Node*>& merge_matcher);
|
||||
Matcher<Node*> IsEffectSet(const Matcher<Node*>& effect0_matcher,
|
||||
const Matcher<Node*>& effect1_matcher);
|
||||
Matcher<Node*> IsProjection(const Matcher<size_t>& index_matcher,
|
||||
const Matcher<Node*>& base_matcher);
|
||||
Matcher<Node*> IsCall(const Matcher<CallDescriptor*>& descriptor_matcher,
|
||||
|
@ -1966,29 +1966,6 @@ TARGET_TEST_F(SchedulerTest, BranchHintFalse) {
|
||||
CHECK(!schedule->block(f)->deferred());
|
||||
}
|
||||
|
||||
|
||||
TARGET_TEST_F(SchedulerTest, ScheduleTerminate) {
|
||||
Node* start = graph()->NewNode(common()->Start(1));
|
||||
graph()->SetStart(start);
|
||||
|
||||
Node* loop = graph()->NewNode(common()->Loop(2), start, start);
|
||||
loop->ReplaceInput(1, loop); // self loop, NTL.
|
||||
|
||||
Node* effect = graph()->NewNode(common()->EffectPhi(1), start, loop);
|
||||
effect->ReplaceInput(0, effect);
|
||||
|
||||
Node* terminate = graph()->NewNode(common()->Terminate(1), effect, loop);
|
||||
Node* end = graph()->NewNode(common()->End(), terminate);
|
||||
|
||||
graph()->SetEnd(end);
|
||||
|
||||
Schedule* schedule = ComputeAndVerifySchedule(6, graph());
|
||||
BasicBlock* block = schedule->block(loop);
|
||||
CHECK_NE(NULL, loop);
|
||||
CHECK_EQ(block, schedule->block(effect));
|
||||
CHECK_GE(block->rpo_number(), 0);
|
||||
}
|
||||
|
||||
} // namespace compiler
|
||||
} // namespace internal
|
||||
} // namespace v8
|
||||
|
@ -42,6 +42,7 @@
|
||||
'compiler/common-operator-unittest.cc',
|
||||
'compiler/compiler-test-utils.h',
|
||||
'compiler/control-equivalence-unittest.cc',
|
||||
'compiler/control-reducer-unittest.cc',
|
||||
'compiler/diamond-unittest.cc',
|
||||
'compiler/graph-reducer-unittest.cc',
|
||||
'compiler/graph-unittest.cc',
|
||||
|
Loading…
Reference in New Issue
Block a user