// Copyright 2016 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/loop-variable-optimizer.h" #include "src/compiler/common-operator.h" #include "src/compiler/graph.h" #include "src/compiler/node-marker.h" #include "src/compiler/node-properties.h" #include "src/compiler/node.h" #include "src/zone/zone-containers.h" #include "src/zone/zone.h" namespace v8 { namespace internal { namespace compiler { // Macro for outputting trace information from representation inference. #define TRACE(...) \ do { \ if (FLAG_trace_turbo_loop) PrintF(__VA_ARGS__); \ } while (false) LoopVariableOptimizer::LoopVariableOptimizer(Graph* graph, CommonOperatorBuilder* common, Zone* zone) : graph_(graph), common_(common), zone_(zone), limits_(graph->NodeCount(), zone), induction_vars_(zone) {} void LoopVariableOptimizer::Run() { ZoneQueue<Node*> queue(zone()); queue.push(graph()->start()); NodeMarker<bool> queued(graph(), 2); while (!queue.empty()) { Node* node = queue.front(); queue.pop(); queued.Set(node, false); DCHECK_NULL(limits_[node->id()]); bool all_inputs_visited = true; int inputs_end = (node->opcode() == IrOpcode::kLoop) ? kFirstBackedge : node->op()->ControlInputCount(); for (int i = 0; i < inputs_end; i++) { if (limits_[NodeProperties::GetControlInput(node, i)->id()] == nullptr) { all_inputs_visited = false; break; } } if (!all_inputs_visited) continue; VisitNode(node); DCHECK_NOT_NULL(limits_[node->id()]); // Queue control outputs. for (Edge edge : node->use_edges()) { if (NodeProperties::IsControlEdge(edge) && edge.from()->op()->ControlOutputCount() > 0) { Node* use = edge.from(); if (use->opcode() == IrOpcode::kLoop && edge.index() != kAssumedLoopEntryIndex) { VisitBackedge(node, use); } else if (!queued.Get(use)) { queue.push(use); queued.Set(use, true); } } } } } class LoopVariableOptimizer::Constraint : public ZoneObject { public: InductionVariable::ConstraintKind kind() const { return kind_; } Node* left() const { return left_; } Node* right() const { return right_; } const Constraint* next() const { return next_; } Constraint(Node* left, InductionVariable::ConstraintKind kind, Node* right, const Constraint* next) : left_(left), right_(right), kind_(kind), next_(next) {} private: Node* left_; Node* right_; InductionVariable::ConstraintKind kind_; const Constraint* next_; }; class LoopVariableOptimizer::VariableLimits : public ZoneObject { public: static VariableLimits* Empty(Zone* zone) { return new (zone) VariableLimits(); } VariableLimits* Copy(Zone* zone) const { return new (zone) VariableLimits(this); } void Add(Node* left, InductionVariable::ConstraintKind kind, Node* right, Zone* zone) { head_ = new (zone) Constraint(left, kind, right, head_); limit_count_++; } void Merge(const VariableLimits* other) { // Change the current condition list to a longest common tail // of this condition list and the other list. (The common tail // should correspond to the list from the common dominator.) // First, we throw away the prefix of the longer list, so that // we have lists of the same length. size_t other_size = other->limit_count_; const Constraint* other_limit = other->head_; while (other_size > limit_count_) { other_limit = other_limit->next(); other_size--; } while (limit_count_ > other_size) { head_ = head_->next(); limit_count_--; } // Then we go through both lists in lock-step until we find // the common tail. while (head_ != other_limit) { DCHECK(limit_count_ > 0); limit_count_--; other_limit = other_limit->next(); head_ = head_->next(); } } const Constraint* head() const { return head_; } private: VariableLimits() {} explicit VariableLimits(const VariableLimits* other) : head_(other->head_), limit_count_(other->limit_count_) {} const Constraint* head_ = nullptr; size_t limit_count_ = 0; }; void InductionVariable::AddUpperBound(Node* bound, InductionVariable::ConstraintKind kind) { if (FLAG_trace_turbo_loop) { OFStream os(stdout); os << "New upper bound for " << phi()->id() << " (loop " << NodeProperties::GetControlInput(phi())->id() << "): " << *bound << std::endl; } upper_bounds_.push_back(Bound(bound, kind)); } void InductionVariable::AddLowerBound(Node* bound, InductionVariable::ConstraintKind kind) { if (FLAG_trace_turbo_loop) { OFStream os(stdout); os << "New lower bound for " << phi()->id() << " (loop " << NodeProperties::GetControlInput(phi())->id() << "): " << *bound; } lower_bounds_.push_back(Bound(bound, kind)); } void LoopVariableOptimizer::VisitBackedge(Node* from, Node* loop) { if (loop->op()->ControlInputCount() != 2) return; // Go through the constraints, and update the induction variables in // this loop if they are involved in the constraint. const VariableLimits* limits = limits_[from->id()]; for (const Constraint* constraint = limits->head(); constraint != nullptr; constraint = constraint->next()) { if (constraint->left()->opcode() == IrOpcode::kPhi && NodeProperties::GetControlInput(constraint->left()) == loop) { auto var = induction_vars_.find(constraint->left()->id()); if (var != induction_vars_.end()) { var->second->AddUpperBound(constraint->right(), constraint->kind()); } } if (constraint->right()->opcode() == IrOpcode::kPhi && NodeProperties::GetControlInput(constraint->right()) == loop) { auto var = induction_vars_.find(constraint->right()->id()); if (var != induction_vars_.end()) { var->second->AddLowerBound(constraint->left(), constraint->kind()); } } } } void LoopVariableOptimizer::VisitNode(Node* node) { switch (node->opcode()) { case IrOpcode::kMerge: return VisitMerge(node); case IrOpcode::kLoop: return VisitLoop(node); case IrOpcode::kIfFalse: return VisitIf(node, false); case IrOpcode::kIfTrue: return VisitIf(node, true); case IrOpcode::kStart: return VisitStart(node); case IrOpcode::kLoopExit: return VisitLoopExit(node); default: return VisitOtherControl(node); } } void LoopVariableOptimizer::VisitMerge(Node* node) { // Merge the limits of all incoming edges. VariableLimits* merged = limits_[node->InputAt(0)->id()]->Copy(zone()); for (int i = 1; i < node->InputCount(); i++) { merged->Merge(limits_[node->InputAt(i)->id()]); } limits_[node->id()] = merged; } void LoopVariableOptimizer::VisitLoop(Node* node) { DetectInductionVariables(node); // Conservatively take the limits from the loop entry here. return TakeConditionsFromFirstControl(node); } void LoopVariableOptimizer::VisitIf(Node* node, bool polarity) { Node* branch = node->InputAt(0); Node* cond = branch->InputAt(0); VariableLimits* limits = limits_[branch->id()]->Copy(zone()); // Normalize to less than comparison. switch (cond->opcode()) { case IrOpcode::kJSLessThan: AddCmpToLimits(limits, cond, InductionVariable::kStrict, polarity); break; case IrOpcode::kJSGreaterThan: AddCmpToLimits(limits, cond, InductionVariable::kNonStrict, !polarity); break; case IrOpcode::kJSLessThanOrEqual: AddCmpToLimits(limits, cond, InductionVariable::kNonStrict, polarity); break; case IrOpcode::kJSGreaterThanOrEqual: AddCmpToLimits(limits, cond, InductionVariable::kStrict, !polarity); break; default: break; } limits_[node->id()] = limits; } void LoopVariableOptimizer::AddCmpToLimits( VariableLimits* limits, Node* node, InductionVariable::ConstraintKind kind, bool polarity) { Node* left = node->InputAt(0); Node* right = node->InputAt(1); if (FindInductionVariable(left) || FindInductionVariable(right)) { if (polarity) { limits->Add(left, kind, right, zone()); } else { kind = (kind == InductionVariable::kStrict) ? InductionVariable::kNonStrict : InductionVariable::kStrict; limits->Add(right, kind, left, zone()); } } } void LoopVariableOptimizer::VisitStart(Node* node) { limits_[node->id()] = VariableLimits::Empty(zone()); } void LoopVariableOptimizer::VisitLoopExit(Node* node) { return TakeConditionsFromFirstControl(node); } void LoopVariableOptimizer::VisitOtherControl(Node* node) { DCHECK_EQ(1, node->op()->ControlInputCount()); return TakeConditionsFromFirstControl(node); } void LoopVariableOptimizer::TakeConditionsFromFirstControl(Node* node) { const VariableLimits* limits = limits_[NodeProperties::GetControlInput(node, 0)->id()]; DCHECK_NOT_NULL(limits); limits_[node->id()] = limits; } const InductionVariable* LoopVariableOptimizer::FindInductionVariable( Node* node) { auto var = induction_vars_.find(node->id()); if (var != induction_vars_.end()) { return var->second; } return nullptr; } InductionVariable* LoopVariableOptimizer::TryGetInductionVariable(Node* phi) { DCHECK_EQ(2, phi->op()->ValueInputCount()); DCHECK_EQ(IrOpcode::kLoop, NodeProperties::GetControlInput(phi)->opcode()); Node* initial = phi->InputAt(0); Node* arith = phi->InputAt(1); InductionVariable::ArithmeticType arithmeticType; if (arith->opcode() == IrOpcode::kJSAdd) { arithmeticType = InductionVariable::ArithmeticType::kAddition; } else if (arith->opcode() == IrOpcode::kJSSubtract) { arithmeticType = InductionVariable::ArithmeticType::kSubtraction; } else { return nullptr; } // TODO(jarin) Support both sides. if (arith->InputAt(0) != phi) { if (arith->InputAt(0)->opcode() != IrOpcode::kJSToNumber || arith->InputAt(0)->InputAt(0) != phi) { return nullptr; } } Node* incr = arith->InputAt(1); return new (zone()) InductionVariable(phi, arith, incr, initial, zone(), arithmeticType); } void LoopVariableOptimizer::DetectInductionVariables(Node* loop) { if (loop->op()->ControlInputCount() != 2) return; TRACE("Loop variables for loop %i:", loop->id()); for (Edge edge : loop->use_edges()) { if (NodeProperties::IsControlEdge(edge) && edge.from()->opcode() == IrOpcode::kPhi) { Node* phi = edge.from(); InductionVariable* induction_var = TryGetInductionVariable(phi); if (induction_var) { induction_vars_[phi->id()] = induction_var; TRACE(" %i", induction_var->phi()->id()); } } } TRACE("\n"); } void LoopVariableOptimizer::ChangeToInductionVariablePhis() { for (auto entry : induction_vars_) { // It only make sense to analyze the induction variables if // there is a bound. InductionVariable* induction_var = entry.second; DCHECK_EQ(MachineRepresentation::kTagged, PhiRepresentationOf(induction_var->phi()->op())); if (induction_var->upper_bounds().size() == 0 && induction_var->lower_bounds().size() == 0) { continue; } // Insert the increment value to the value inputs. induction_var->phi()->InsertInput(graph()->zone(), induction_var->phi()->InputCount() - 1, induction_var->increment()); // Insert the bound inputs to the value inputs. for (auto bound : induction_var->lower_bounds()) { induction_var->phi()->InsertInput( graph()->zone(), induction_var->phi()->InputCount() - 1, bound.bound); } for (auto bound : induction_var->upper_bounds()) { induction_var->phi()->InsertInput( graph()->zone(), induction_var->phi()->InputCount() - 1, bound.bound); } NodeProperties::ChangeOp( induction_var->phi(), common()->InductionVariablePhi(induction_var->phi()->InputCount() - 1)); } } void LoopVariableOptimizer::ChangeToPhisAndInsertGuards() { for (auto entry : induction_vars_) { InductionVariable* induction_var = entry.second; if (induction_var->phi()->opcode() == IrOpcode::kInductionVariablePhi) { // Turn the induction variable phi back to normal phi. int value_count = 2; Node* control = NodeProperties::GetControlInput(induction_var->phi()); DCHECK_EQ(value_count, control->op()->ControlInputCount()); induction_var->phi()->TrimInputCount(value_count + 1); induction_var->phi()->ReplaceInput(value_count, control); NodeProperties::ChangeOp( induction_var->phi(), common()->Phi(MachineRepresentation::kTagged, value_count)); // If the backedge is not a subtype of the phi's type, we insert a sigma // to get the typing right. Node* backedge_value = induction_var->phi()->InputAt(1); Type* backedge_type = NodeProperties::GetType(backedge_value); Type* phi_type = NodeProperties::GetType(induction_var->phi()); if (!backedge_type->Is(phi_type)) { Node* backedge_control = NodeProperties::GetControlInput(induction_var->phi())->InputAt(1); Node* rename = graph()->NewNode(common()->TypeGuard(phi_type), backedge_value, backedge_control); induction_var->phi()->ReplaceInput(1, rename); } } } } } // namespace compiler } // namespace internal } // namespace v8