Index: src/compiler/control-reducer.cc |
diff --git a/src/compiler/control-reducer.cc b/src/compiler/control-reducer.cc |
index 03d0583b23a0aec5649b9a9caef66b2719ee9897..e1bd0c9afb80b67c2aaa7763acabe3ce09c03312 100644 |
--- a/src/compiler/control-reducer.cc |
+++ b/src/compiler/control-reducer.cc |
@@ -14,7 +14,8 @@ namespace v8 { |
namespace internal { |
namespace compiler { |
-enum VisitState { kUnvisited, kOnStack, kRevisit, kVisited }; |
+enum VisitState { kUnvisited = 0, kOnStack = 1, kRevisit = 2, kVisited = 3 }; |
+enum Reachability { kFromStart = 8 }; |
#define TRACE(x) \ |
if (FLAG_trace_turbo) PrintF x |
@@ -39,23 +40,169 @@ class ControlReducerImpl { |
ZoneDeque<Node*> revisit_; |
Node* dead_; |
- void Trim() { |
- // Mark all nodes reachable from end. |
+ void Reduce() { |
+ Push(graph()->end()); |
+ do { |
+ // Process the node on the top of the stack, potentially pushing more |
+ // or popping the node off the stack. |
+ ReduceTop(); |
+ // If the stack becomes empty, revisit any nodes in the revisit queue. |
+ // If no nodes in the revisit queue, try removing dead loops. |
+ // If no dead loops, then finish. |
+ } while (!stack_.empty() || TryRevisit() || RepairAndRemoveLoops()); |
+ } |
+ |
+ bool TryRevisit() { |
+ while (!revisit_.empty()) { |
+ Node* n = revisit_.back(); |
+ revisit_.pop_back(); |
+ if (state_[n->id()] == kRevisit) { // state can change while in queue. |
+ Push(n); |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ // Repair the graph after the possible creation of non-terminating or dead |
+ // loops. Removing dead loops can produce more opportunities for reduction. |
+ bool RepairAndRemoveLoops() { |
+ // TODO(turbofan): we can skip this if the graph has no loops, but |
+ // we have to be careful about proper loop detection during reduction. |
+ |
+ // Gather all nodes backwards-reachable from end (through inputs). |
+ state_.assign(graph()->NodeCount(), kUnvisited); |
NodeVector nodes(zone_); |
- state_.assign(jsgraph_->graph()->NodeCount(), kUnvisited); |
- Push(jsgraph_->graph()->end()); |
+ AddNodesReachableFromEnd(nodes); |
+ |
+ // Walk forward through control nodes, looking for back edges to nodes |
+ // that are not connected to end. Those are non-terminating loops (NTLs). |
+ Node* start = graph()->start(); |
+ ZoneVector<byte> fw_reachability(graph()->NodeCount(), 0, zone_); |
+ fw_reachability[start->id()] = kFromStart | kOnStack; |
+ stack_.push_back(start); |
+ |
while (!stack_.empty()) { |
- Node* node = stack_[stack_.size() - 1]; |
- stack_.pop_back(); |
- state_[node->id()] = kVisited; |
- nodes.push_back(node); |
- for (InputIter i = node->inputs().begin(); i != node->inputs().end(); |
- ++i) { |
- Recurse(*i); // pushes node onto the stack if necessary. |
+ Node* node = stack_.back(); |
+ TRACE(("ControlFw: #%d:%s\n", node->id(), node->op()->mnemonic())); |
+ bool pop = true; |
+ for (Node* const succ : node->uses()) { |
+ byte reach = fw_reachability[succ->id()]; |
+ if ((reach & kOnStack) != 0 && state_[succ->id()] != kVisited) { |
+ // {succ} is on stack and not reachable from end. |
+ ConnectNTL(nodes, succ); |
+ fw_reachability.resize(graph()->NodeCount(), 0); |
+ pop = false; // continue traversing inputs to this node. |
+ break; |
+ } |
+ if ((reach & kFromStart) == 0 && |
+ IrOpcode::IsControlOpcode(succ->opcode())) { |
+ // {succ} is a control node and not yet reached from start. |
+ fw_reachability[succ->id()] |= kFromStart | kOnStack; |
+ stack_.push_back(succ); |
+ pop = false; // "recurse" into successor control node. |
+ break; |
+ } |
+ } |
+ if (pop) { |
+ fw_reachability[node->id()] &= ~kOnStack; |
+ stack_.pop_back(); |
} |
} |
+ |
+ // Trim references from dead nodes to live nodes first. |
+ jsgraph_->GetCachedNodes(&nodes); |
+ TrimNodes(nodes); |
+ |
+ // Any control nodes not reachable from start are dead, even loops. |
+ for (size_t i = 0; i < nodes.size(); i++) { |
+ Node* node = nodes[i]; |
+ byte reach = fw_reachability[node->id()]; |
+ if ((reach & kFromStart) == 0 && |
+ IrOpcode::IsControlOpcode(node->opcode())) { |
+ ReplaceNode(node, dead()); // uses will be added to revisit queue. |
+ } |
+ } |
+ return TryRevisit(); // try to push a node onto the stack. |
+ } |
+ |
+ // Connect {loop}, the header of a non-terminating loop, to the end node. |
+ void ConnectNTL(NodeVector& nodes, 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* to_add = loop; |
+ 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); |
+ } 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); |
+ end->ReplaceInput(0, merge); |
+ to_add = merge; |
+ } else { |
+ // Append a new input to the final merge at the end. |
+ merge->AppendInput(graph()->zone(), loop); |
+ merge->set_op(common_->Merge(merge->InputCount())); |
+ } |
+ nodes.push_back(to_add); |
+ state_.resize(graph()->NodeCount(), kUnvisited); |
+ state_[to_add->id()] = kVisited; |
+ AddBackwardsReachableNodes(nodes, nodes.size() - 1); |
+ } |
+ |
+ void AddNodesReachableFromEnd(NodeVector& nodes) { |
+ Node* end = graph()->end(); |
+ state_[end->id()] = kVisited; |
+ if (!end->IsDead()) { |
+ nodes.push_back(end); |
+ AddBackwardsReachableNodes(nodes, nodes.size() - 1); |
+ } |
+ } |
+ |
+ void AddBackwardsReachableNodes(NodeVector& nodes, size_t cursor) { |
+ while (cursor < nodes.size()) { |
+ Node* node = nodes[cursor++]; |
+ for (Node* const input : node->inputs()) { |
+ if (state_[input->id()] != kVisited) { |
+ state_[input->id()] = kVisited; |
+ nodes.push_back(input); |
+ } |
+ } |
+ } |
+ } |
+ |
+ void Trim() { |
+ // Gather all nodes backwards-reachable from end through inputs. |
+ state_.assign(graph()->NodeCount(), kUnvisited); |
+ NodeVector nodes(zone_); |
+ AddNodesReachableFromEnd(nodes); |
+ |
// Process cached nodes in the JSGraph too. |
jsgraph_->GetCachedNodes(&nodes); |
+ TrimNodes(nodes); |
+ } |
+ |
+ void TrimNodes(NodeVector& nodes) { |
// Remove dead->live edges. |
for (size_t j = 0; j < nodes.size(); j++) { |
Node* node = nodes[j]; |
@@ -75,18 +222,46 @@ class ControlReducerImpl { |
// Verify that no inputs to live nodes are NULL. |
for (size_t j = 0; j < nodes.size(); j++) { |
Node* node = nodes[j]; |
- for (InputIter i = node->inputs().begin(); i != node->inputs().end(); |
- ++i) { |
- CHECK_NE(NULL, *i); |
+ for (Node* const input : node->inputs()) { |
+ CHECK_NE(NULL, input); |
} |
- for (UseIter i = node->uses().begin(); i != node->uses().end(); ++i) { |
- size_t id = static_cast<size_t>((*i)->id()); |
+ for (Node* const use : node->uses()) { |
+ size_t id = static_cast<size_t>(use->id()); |
CHECK_EQ(kVisited, state_[id]); |
} |
} |
#endif |
} |
+ // Reduce the node on the top of the stack. |
+ // If an input {i} is not yet visited or needs to be revisited, push {i} onto |
+ // the stack and return. Otherwise, all inputs are visited, so apply |
+ // reductions for {node} and pop it off the stack. |
+ void ReduceTop() { |
+ size_t height = stack_.size(); |
+ Node* node = stack_.back(); |
+ |
+ if (node->IsDead()) return Pop(); // Node was killed while on stack. |
+ |
+ TRACE(("ControlReduce: #%d:%s\n", node->id(), node->op()->mnemonic())); |
+ |
+ // Recurse on an input if necessary. |
+ for (Node* const input : node->inputs()) { |
+ if (Recurse(input)) return; |
+ } |
+ |
+ // All inputs should be visited or on stack. Apply reductions to node. |
+ Node* replacement = ReduceNode(node); |
+ if (replacement != node) ReplaceNode(node, replacement); |
+ |
+ // After reducing the node, pop it off the stack. |
+ CHECK_EQ(static_cast<int>(height), static_cast<int>(stack_.size())); |
+ Pop(); |
+ |
+ // If there was a replacement, reduce it after popping {node}. |
+ if (replacement != node) Recurse(replacement); |
+ } |
+ |
// Push a node onto the stack if its state is {kUnvisited} or {kRevisit}. |
bool Recurse(Node* node) { |
size_t id = static_cast<size_t>(node->id()); |
@@ -103,13 +278,223 @@ class ControlReducerImpl { |
state_[node->id()] = kOnStack; |
stack_.push_back(node); |
} |
+ |
+ void Pop() { |
+ int pos = static_cast<int>(stack_.size()) - 1; |
+ DCHECK_GE(pos, 0); |
+ DCHECK_EQ(kOnStack, state_[stack_[pos]->id()]); |
+ state_[stack_[pos]->id()] = kVisited; |
+ stack_.pop_back(); |
+ } |
+ |
+ // Queue a node to be revisited if it has been visited once already. |
+ void Revisit(Node* node) { |
+ size_t id = static_cast<size_t>(node->id()); |
+ if (id < state_.size() && state_[id] == kVisited) { |
+ TRACE((" Revisit #%d:%s\n", node->id(), node->op()->mnemonic())); |
+ state_[id] = kRevisit; |
+ revisit_.push_back(node); |
+ } |
+ } |
+ |
+ Node* dead() { |
+ if (dead_ == NULL) dead_ = graph()->NewNode(common_->Dead()); |
+ return dead_; |
+ } |
+ |
+ //=========================================================================== |
+ // Reducer implementation: perform reductions on a node. |
+ //=========================================================================== |
+ Node* ReduceNode(Node* node) { |
+ if (OperatorProperties::GetControlInputCount(node->op()) == 1) { |
+ // If a node has only one control input and it is dead, replace with dead. |
+ Node* control = NodeProperties::GetControlInput(node); |
+ if (control->opcode() == IrOpcode::kDead) { |
+ TRACE(("ControlDead: #%d:%s\n", node->id(), node->op()->mnemonic())); |
+ return control; |
+ } |
+ } |
+ |
+ // Reduce branches, phis, and merges. |
+ switch (node->opcode()) { |
+ case IrOpcode::kBranch: |
+ return ReduceBranch(node); |
+ case IrOpcode::kLoop: |
+ case IrOpcode::kMerge: |
+ return ReduceMerge(node); |
+ case IrOpcode::kPhi: |
+ case IrOpcode::kEffectPhi: |
+ return ReducePhi(node); |
+ default: |
+ return node; |
+ } |
+ } |
+ |
+ // Reduce redundant phis. |
+ Node* ReducePhi(Node* node) { |
+ int n = node->InputCount(); |
+ if (n <= 1) return dead(); // No non-control inputs. |
+ if (n == 2) return node->InputAt(0); // Only one non-control input. |
+ |
+ Node* replacement = NULL; |
+ Node::Inputs inputs = node->inputs(); |
+ for (InputIter it = inputs.begin(); n > 1; --n, ++it) { |
+ Node* input = *it; |
+ if (input->opcode() == IrOpcode::kDead) continue; // ignore dead inputs. |
+ if (input != node && input != replacement) { // non-redundant input. |
+ if (replacement != NULL) return node; |
+ replacement = input; |
+ } |
+ } |
+ return replacement == NULL ? dead() : replacement; |
+ } |
+ |
+ // 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::kDead) { |
+ live++; |
+ live_index = index; |
+ } |
+ index++; |
+ } |
+ |
+ if (live > 1 && live == node->InputCount()) return node; // nothing to do. |
+ |
+ TRACE(("ReduceMerge: #%d:%s (%d live)\n", node->id(), |
+ node->op()->mnemonic(), live)); |
+ |
+ if (live == 0) return dead(); // no remaining inputs. |
+ |
+ // Gather phis and effect phis to be edited. |
+ ZoneVector<Node*> phis(zone_); |
+ for (Node* const use : node->uses()) { |
+ if (use->opcode() == IrOpcode::kPhi || |
+ use->opcode() == IrOpcode::kEffectPhi) { |
+ phis.push_back(use); |
+ } |
+ } |
+ |
+ if (live == 1) { |
+ // All phis are redundant. Replace them with their live input. |
+ for (Node* const phi : phis) ReplaceNode(phi, phi->InputAt(live_index)); |
+ // The merge itself is redundant. |
+ return node->InputAt(live_index); |
+ } |
+ |
+ // 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); |
+ return node; |
+ } |
+ |
+ // Reduce branches if they have constant inputs. |
+ Node* ReduceBranch(Node* node) { |
+ Node* cond = node->InputAt(0); |
+ bool is_true; |
+ switch (cond->opcode()) { |
+ case IrOpcode::kInt32Constant: |
+ is_true = !Int32Matcher(cond).Is(0); |
+ break; |
+ case IrOpcode::kNumberConstant: |
+ is_true = !NumberMatcher(cond).Is(0); |
+ break; |
+ case IrOpcode::kHeapConstant: { |
+ Handle<Object> object = |
+ HeapObjectMatcher<Object>(cond).Value().handle(); |
+ if (object->IsTrue()) |
+ is_true = true; |
+ else if (object->IsFalse()) |
+ is_true = false; |
+ else |
+ return node; // TODO(turbofan): fold branches on strings, objects. |
+ break; |
+ } |
+ default: |
+ return node; |
+ } |
+ |
+ TRACE(("BranchReduce: #%d:%s = %s\n", node->id(), node->op()->mnemonic(), |
+ is_true ? "true" : "false")); |
+ |
+ // Replace IfTrue and IfFalse projections from this branch. |
+ Node* control = NodeProperties::GetControlInput(node); |
+ for (UseIter i = node->uses().begin(); i != node->uses().end();) { |
+ Node* to = *i; |
+ if (to->opcode() == IrOpcode::kIfTrue) { |
+ TRACE((" IfTrue: #%d:%s\n", to->id(), to->op()->mnemonic())); |
+ i.UpdateToAndIncrement(NULL); |
+ ReplaceNode(to, is_true ? control : dead()); |
+ } else if (to->opcode() == IrOpcode::kIfFalse) { |
+ TRACE((" IfFalse: #%d:%s\n", to->id(), to->op()->mnemonic())); |
+ i.UpdateToAndIncrement(NULL); |
+ ReplaceNode(to, is_true ? dead() : control); |
+ } else { |
+ ++i; |
+ } |
+ } |
+ return control; |
+ } |
+ |
+ // 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 pos = 0; |
+ for (int i = 0; i < node->InputCount(); i++) { |
+ // skip dead inputs. |
+ if (i < merge->InputCount() && |
+ merge->InputAt(i)->opcode() == IrOpcode::kDead) |
+ continue; |
+ // compact live inputs. |
+ if (pos != i) node->ReplaceInput(pos, node->InputAt(i)); |
+ pos++; |
+ } |
+ node->TrimInputCount(pos); |
+ if (node->opcode() == IrOpcode::kPhi) { |
+ node->set_op(common_->Phi(OpParameter<MachineType>(node->op()), pos - 1)); |
+ } else if (node->opcode() == IrOpcode::kEffectPhi) { |
+ node->set_op(common_->EffectPhi(pos - 1)); |
+ } else if (node->opcode() == IrOpcode::kMerge) { |
+ node->set_op(common_->Merge(pos)); |
+ } else if (node->opcode() == IrOpcode::kLoop) { |
+ node->set_op(common_->Loop(pos)); |
+ } else { |
+ UNREACHABLE(); |
+ } |
+ } |
+ |
+ // Replace uses of {node} with {replacement} and revisit the uses. |
+ void ReplaceNode(Node* node, Node* replacement) { |
+ if (node == replacement) return; |
+ TRACE((" Replace: #%d:%s with #%d:%s\n", node->id(), |
+ node->op()->mnemonic(), replacement->id(), |
+ replacement->op()->mnemonic())); |
+ for (Node* const use : node->uses()) { |
+ // Don't revisit this node if it refers to itself. |
+ if (use != node) Revisit(use); |
+ } |
+ node->ReplaceUses(replacement); |
+ node->Kill(); |
+ } |
+ |
+ Graph* graph() { return jsgraph_->graph(); } |
}; |
+ |
void ControlReducer::ReduceGraph(Zone* zone, JSGraph* jsgraph, |
CommonOperatorBuilder* common) { |
- ControlReducerImpl impl(zone, jsgraph, NULL); |
- // Only trim the graph for now. Control reduction can reduce non-terminating |
- // loops to graphs that are unschedulable at the moment. |
+ ControlReducerImpl impl(zone, jsgraph, common); |
+ impl.Reduce(); |
impl.Trim(); |
} |
@@ -118,6 +503,33 @@ void ControlReducer::TrimGraph(Zone* zone, JSGraph* jsgraph) { |
ControlReducerImpl impl(zone, jsgraph, NULL); |
impl.Trim(); |
} |
+ |
+ |
+Node* ControlReducer::ReducePhiForTesting(JSGraph* jsgraph, |
+ CommonOperatorBuilder* common, |
+ Node* node) { |
+ Zone zone(jsgraph->graph()->zone()->isolate()); |
+ ControlReducerImpl impl(&zone, jsgraph, common); |
+ return impl.ReducePhi(node); |
+} |
+ |
+ |
+Node* ControlReducer::ReduceMergeForTesting(JSGraph* jsgraph, |
+ CommonOperatorBuilder* common, |
+ Node* node) { |
+ Zone zone(jsgraph->graph()->zone()->isolate()); |
+ ControlReducerImpl impl(&zone, jsgraph, common); |
+ return impl.ReduceMerge(node); |
+} |
+ |
+ |
+Node* ControlReducer::ReduceBranchForTesting(JSGraph* jsgraph, |
+ CommonOperatorBuilder* common, |
+ Node* node) { |
+ Zone zone(jsgraph->graph()->zone()->isolate()); |
+ ControlReducerImpl impl(&zone, jsgraph, common); |
+ return impl.ReduceBranch(node); |
+} |
} |
} |
} // namespace v8::internal::compiler |