Chromium Code Reviews| Index: src/compiler/control-reducer.cc |
| diff --git a/src/compiler/control-reducer.cc b/src/compiler/control-reducer.cc |
| index 03d0583b23a0aec5649b9a9caef66b2719ee9897..bf9a2f19785694c892e61d58dbf0b836ef50403d 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,158 @@ 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); |
| + Node* node = stack_.back(); |
| + TRACE(("ControlFw: #%d:%s\n", node->id(), node->op()->mnemonic())); |
| + bool pop = true; |
| + for (UseIter i = node->uses().begin(); i != node->uses().end(); ++i) { |
| + Node* succ = *i; |
| + 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())); |
| + CHECK_EQ(IrOpcode::kLoop, loop->opcode()); |
| + Node* to_add = loop; |
| + Node* end = graph()->end(); |
| + CHECK_EQ(IrOpcode::kEnd, end->opcode()); |
| + Node* merge = end->InputAt(0); |
| + if (merge->opcode() != IrOpcode::kMerge) { |
| + if (merge->opcode() == IrOpcode::kDead) { |
| + // end died; just connect end to the loop. |
| + end->ReplaceInput(0, loop); |
| + to_add = loop; |
| + } else { |
| + // Introduce a new merge for the end. |
| + merge = graph()->NewNode(common_->Merge(2), merge, loop); |
| + state_.resize(graph()->NodeCount(), kUnvisited); |
| + 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_[to_add->id()] = kVisited; |
| + AddBackwardsReachableNodes(nodes, nodes.size() - 1); |
| + } |
| + |
| + void AddNodesReachableFromEnd(NodeVector& nodes) { |
| + Node* end = graph()->end(); |
| + if (!end->IsDead()) { |
| + state_[end->id()] = kVisited; |
| + 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 (InputIter i = node->inputs().begin(); i != node->inputs().end(); |
| ++i) { |
| - Recurse(*i); // pushes node onto the stack if necessary. |
| + Node* input = *i; |
| + 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]; |
| @@ -87,6 +223,35 @@ class ControlReducerImpl { |
| #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() { |
| + int 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 (InputIter i = node->inputs().begin(); i != node->inputs().end(); ++i) { |
|
Benedikt Meurer
2014/10/23 10:45:25
for (Node* const input : node->inputs()) if (Recur
titzer
2014/10/23 11:23:44
Done.
|
| + if (Recurse(*i)) 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(height, stack_.size()); |
|
Benedikt Meurer
2014/10/23 10:45:25
Windows builder will probably complain about this
titzer
2014/10/23 11:23:44
Fixed with static_cast
|
| + 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 +268,232 @@ class ControlReducerImpl { |
| state_[node->id()] = kOnStack; |
| stack_.push_back(node); |
| } |
| + |
| + void Pop() { |
| + int pos = 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(); |
| + DCHECK_EQ(1, OperatorProperties::GetControlInputCount(node->op())); |
| + if (node->opcode() == IrOpcode::kPhi) { |
| + DCHECK_EQ(n, 1 + OperatorProperties::GetValueInputCount(node->op())); |
| + } |
| + if (node->opcode() == IrOpcode::kEffectPhi) { |
| + DCHECK_EQ(n, 1 + OperatorProperties::GetEffectInputCount(node->op())); |
| + } |
| + 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 live_index = 0; |
| + for (InputIter i = node->inputs().begin(); i != node->inputs().end(); ++i) { |
| + if ((*i)->opcode() != IrOpcode::kDead) { |
| + live++; |
| + live_index = i.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. |
| + ZoneDeque<Node*> phis(zone_); |
|
Benedikt Meurer
2014/10/23 10:45:25
Hm, I'd like to have a ZoneStack here, i.e. a clas
titzer
2014/10/23 11:23:44
Order doesn't really matter here, so I could as we
|
| + for (UseIter i = node->uses().begin(); i != node->uses().end(); ++i) { |
|
Benedikt Meurer
2014/10/23 10:45:25
for (Node* const to : node->uses()) { ... }
titzer
2014/10/23 11:23:44
Done.
|
| + Node* to = *i; |
| + if (to->opcode() == IrOpcode::kPhi || |
| + to->opcode() == IrOpcode::kEffectPhi) { |
| + phis.push_back(to); |
| + } |
| + } |
| + |
| + // Remove dead inputs from merge and corresponding phis. |
| + while (!phis.empty()) { |
| + Node* phi = phis.back(); |
| + phis.pop_back(); |
| + TRACE((" PhiInMerge: #%d:%s (%d live)\n", phi->id(), |
| + phi->op()->mnemonic(), live)); |
| + if (live == 1) { |
| + Node* result = phi->InputAt(live_index); |
| + ReplaceNode(phi, result); // phi is redundant. |
| + } else { |
| + RemoveDeadInputs(node, phi); |
| + Revisit(phi); // re-reduce every phi. |
| + } |
| + } |
| + |
| + // If only one input is remaining, remove the merge altogether. |
| + if (live == 1) return node->InputAt(live_index); |
| + RemoveDeadInputs(node, node); |
| + return node; |
| + } |
| + |
| + // Reduce branches with 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(); |
| + } |
| + } |
| + |
| + 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 (UseIter i = node->uses().begin(); i != node->uses().end(); ++i) { |
| + // Don't revisit this node if it refers to itself. |
| + if (*i != node) Revisit(*i); |
| + } |
| + node->ReplaceUses(replacement); |
| + node->Kill(); |
| + } |
| + |
| + Graph* graph() { return jsgraph_->graph(); } |
| }; |
| + |
| void ControlReducer::ReduceGraph(Zone* zone, JSGraph* jsgraph, |
| CommonOperatorBuilder* common) { |
| - ControlReducerImpl impl(zone, jsgraph, NULL); |
| + ControlReducerImpl impl(zone, jsgraph, common); |
| // Only trim the graph for now. Control reduction can reduce non-terminating |
| // loops to graphs that are unschedulable at the moment. |
| + impl.Reduce(); |
| impl.Trim(); |
| } |
| @@ -118,6 +502,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 |