diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h index 405138263e2..cc806de07ea 100644 --- a/runtime/vm/compiler/backend/il.h +++ b/runtime/vm/compiler/backend/il.h @@ -1977,8 +1977,6 @@ class TemplateDefinition : public CSETrait::Base { virtual void RawSetInputAt(intptr_t i, Value* value) { inputs_[i] = value; } }; -class InductionVariableInfo; - class PhiInstr : public Definition { public: PhiInstr(JoinEntryInstr* block, intptr_t num_inputs) @@ -1986,7 +1984,6 @@ class PhiInstr : public Definition { inputs_(num_inputs), representation_(kTagged), reaching_defs_(NULL), - loop_variable_info_(NULL), is_alive_(false), is_receiver_(kUnknownReceiver) { for (intptr_t i = 0; i < num_inputs; ++i) { @@ -2042,14 +2039,6 @@ class PhiInstr : public Definition { // A phi is redundant if all input operands are the same. bool IsRedundant() const; - void set_induction_variable_info(InductionVariableInfo* info) { - loop_variable_info_ = info; - } - - InductionVariableInfo* induction_variable_info() { - return loop_variable_info_; - } - PRINT_TO_SUPPORT enum ReceiverType { kUnknownReceiver = -1, kNotReceiver = 0, kReceiver = 1 }; @@ -2071,7 +2060,6 @@ class PhiInstr : public Definition { GrowableArray inputs_; Representation representation_; BitVector* reaching_defs_; - InductionVariableInfo* loop_variable_info_; bool is_alive_; int8_t is_receiver_; diff --git a/runtime/vm/compiler/backend/loops.cc b/runtime/vm/compiler/backend/loops.cc index 394da9eb9a4..707864ee224 100644 --- a/runtime/vm/compiler/backend/loops.cc +++ b/runtime/vm/compiler/backend/loops.cc @@ -65,16 +65,19 @@ class InductionVarAnalysis : public ValueObject { void Classify(LoopInfo* loop, Definition* def); void ClassifySCC(LoopInfo* loop); - // Transfer methods. Computes how induction of the operands, if any, + // Transfer methods. Compute how induction of the operands, if any, // tranfers over the operation performed by the given definition. InductionVar* TransferPhi(LoopInfo* loop, Definition* def, intptr_t idx = -1); InductionVar* TransferBinary(LoopInfo* loop, Definition* def); InductionVar* TransferUnary(LoopInfo* loop, Definition* def); - // Solver methods. Computes how temporary meaning given to the + // Solver methods. Compute how temporary meaning given to the // definitions in a cycle transfer over the operation performed // by the given definition. InductionVar* SolvePhi(LoopInfo* loop, Definition* def, intptr_t idx = -1); + InductionVar* SolveConstraint(LoopInfo* loop, + Definition* def, + InductionVar* init); InductionVar* SolveBinary(LoopInfo* loop, Definition* def, InductionVar* init); @@ -239,8 +242,8 @@ void InductionVarAnalysis::Classify(LoopInfo* loop, Definition* def) { } } else if (def->IsPhi()) { induc = TransferPhi(loop, def); - } else if (def->IsRedefinition() || def->IsConstraint() || def->IsBox() || - def->IsUnbox()) { + } else if (def->IsRedefinition() || def->IsBox() || def->IsUnbox() || + def->IsConstraint()) { induc = Lookup(loop, def->InputAt(0)->definition()); // pass-through } else if (def->IsBinaryIntegerOp()) { induc = TransferBinary(loop, def); @@ -278,9 +281,10 @@ void InductionVarAnalysis::ClassifySCC(LoopInfo* loop) { InductionVar* update = nullptr; if (def->IsPhi()) { update = SolvePhi(loop, def); - } else if (def->IsRedefinition() || def->IsConstraint() || def->IsBox() || - def->IsUnbox()) { + } else if (def->IsRedefinition() || def->IsBox() || def->IsUnbox()) { update = LookupCycle(def->InputAt(0)->definition()); // pass-through + } else if (def->IsConstraint()) { + update = SolveConstraint(loop, def, init); } else if (def->IsBinaryIntegerOp()) { update = SolveBinary(loop, def, init); } else if (def->IsUnaryIntegerOp()) { @@ -381,6 +385,21 @@ InductionVar* InductionVarAnalysis::SolvePhi(LoopInfo* loop, return induc; } +InductionVar* InductionVarAnalysis::SolveConstraint(LoopInfo* loop, + Definition* def, + InductionVar* init) { + InductionVar* c = LookupCycle(def->InputAt(0)->definition()); + if (c == init) { + // Record a non-artifical bound constraint on a phi. + // TODO(ajcbik): detect full loop logic, trip counts, etc. + ConstraintInstr* constraint = def->AsConstraint(); + if (constraint->target() != nullptr) { + loop->limit_ = constraint; + } + } + return c; +} + InductionVar* InductionVarAnalysis::SolveBinary(LoopInfo* loop, Definition* def, InductionVar* init) { @@ -629,6 +648,7 @@ LoopInfo::LoopInfo(intptr_t id, BlockEntryInstr* header, BitVector* blocks) blocks_(blocks), back_edges_(), induction_(), + limit_(nullptr), outer_(nullptr), inner_(nullptr), next_(nullptr) {} @@ -650,9 +670,9 @@ bool LoopInfo::IsBackEdge(BlockEntryInstr* block) const { return false; } -bool LoopInfo::IsHeaderPhi(Instruction* instr) const { - return instr->IsPhi() && instr->GetBlock() == header_ && - !instr->AsPhi()->IsRedundant(); // phi(x,..,x) = x +bool LoopInfo::IsHeaderPhi(Definition* def) const { + return def != nullptr && def->IsPhi() && def->GetBlock() == header_ && + !def->AsPhi()->IsRedundant(); // phi(x,..,x) = x } bool LoopInfo::IsIn(LoopInfo* loop) const { @@ -679,6 +699,8 @@ void LoopInfo::ResetInduction() { } void LoopInfo::AddInduction(Definition* def, InductionVar* induc) { + ASSERT(def != nullptr); + ASSERT(induc != nullptr); induction_.Insert(InductionKV::Pair(def, induc)); } diff --git a/runtime/vm/compiler/backend/loops.h b/runtime/vm/compiler/backend/loops.h index 1d5b45a5bb5..30fdf59e5a7 100644 --- a/runtime/vm/compiler/backend/loops.h +++ b/runtime/vm/compiler/backend/loops.h @@ -38,7 +38,7 @@ class InductionVar : public ZoneAllocated { // Constructor for a constant. explicit InductionVar(int64_t offset) : InductionVar(offset, 0, nullptr) {} - // Constructor for induction. + // Constructor for an induction. InductionVar(Kind kind, InductionVar* initial, InductionVar* next) : kind_(kind), initial_(initial), next_(next) { ASSERT(IsInvariant(initial)); @@ -73,6 +73,29 @@ class InductionVar : public ZoneAllocated { return false; } + // Getters. + Kind kind() const { return kind_; } + int64_t offset() const { + ASSERT(kind_ == kInvariant); + return offset_; + } + int64_t mult() const { + ASSERT(kind_ == kInvariant); + return mult_; + } + Definition* def() const { + ASSERT(kind_ == kInvariant); + return def_; + } + InductionVar* initial() const { + ASSERT(kind_ != kInvariant); + return initial_; + } + InductionVar* next() const { + ASSERT(kind_ != kInvariant); + return next_; + } + // For debugging. const char* ToCString() const; @@ -134,8 +157,8 @@ class LoopInfo : public ZoneAllocated { // Returns true if given block is backedge of this loop. bool IsBackEdge(BlockEntryInstr* block) const; - // Returns true if given instruction is a header phi for this loop. - bool IsHeaderPhi(Instruction* instr) const; + // Returns true if given definition is a header phi for this loop. + bool IsHeaderPhi(Definition* def) const; // Returns true if this loop is nested inside given loop. bool IsIn(LoopInfo* loop) const; @@ -158,8 +181,9 @@ class LoopInfo : public ZoneAllocated { // Getters. intptr_t id() const { return id_; } BlockEntryInstr* header() const { return header_; } - const GrowableArray& back_edges() { return back_edges_; } BitVector* blocks() const { return blocks_; } + const GrowableArray& back_edges() { return back_edges_; } + ConstraintInstr* limit() const { return limit_; } LoopInfo* outer() const { return outer_; } LoopInfo* inner() const { return inner_; } LoopInfo* next() const { return next_; } @@ -187,9 +211,12 @@ class LoopInfo : public ZoneAllocated { // Back edges of loop (usually one). GrowableArray back_edges_; - // Map instruction -> induction for this loop. + // Map definition -> induction for this loop. DirectChainedHashMap induction_; + // Constraint on a header phi. + ConstraintInstr* limit_; + // Loop hierarchy. LoopInfo* outer_; LoopInfo* inner_; diff --git a/runtime/vm/compiler/backend/range_analysis.cc b/runtime/vm/compiler/backend/range_analysis.cc index 5b502291dac..d94536aab33 100644 --- a/runtime/vm/compiler/backend/range_analysis.cc +++ b/runtime/vm/compiler/backend/range_analysis.cc @@ -30,7 +30,7 @@ DECLARE_FLAG(bool, trace_constant_propagation); void RangeAnalysis::Analyze() { CollectValues(); InsertConstraints(); - DiscoverSimpleInductionVariables(); + flow_graph_->GetLoopHierarchy().ComputeInduction(); InferRanges(); EliminateRedundantBoundsChecks(); MarkUnreachableBlocks(); @@ -43,6 +43,7 @@ void RangeAnalysis::Analyze() { RemoveConstraints(); } +// Helper method to chase to a constrained definition. static Definition* UnwrapConstraint(Definition* defn) { while (defn->IsConstraint()) { defn = defn->AsConstraint()->value()->definition(); @@ -50,167 +51,6 @@ static Definition* UnwrapConstraint(Definition* defn) { return defn; } -// Simple induction variable is a variable that satisfies the following pattern: -// -// v1 <- phi(v0, v1 + 1) -// -// If there are two simple induction variables in the same block and one of -// them is constrained - then another one is constrained as well, e.g. -// from -// -// B1: -// v3 <- phi(v0, v3 + 1) -// v4 <- phi(v2, v4 + 1) -// Bx: -// v3 is constrained to [v0, v1] -// -// it follows that -// -// Bx: -// v4 is constrained to [v2, v2 + (v0 - v1)] -// -// This pass essentially pattern matches induction variables introduced -// like this: -// -// for (var i = i0, j = j0; i < L; i++, j++) { -// j is known to be within [j0, j0 + (L - i0 - 1)] -// } -// -class InductionVariableInfo : public ZoneAllocated { - public: - InductionVariableInfo(PhiInstr* phi, - Definition* initial_value, - BinarySmiOpInstr* increment, - ConstraintInstr* limit) - : phi_(phi), - initial_value_(initial_value), - increment_(increment), - limit_(limit), - bound_(NULL) {} - - PhiInstr* phi() const { return phi_; } - Definition* initial_value() const { return initial_value_; } - BinarySmiOpInstr* increment() const { return increment_; } - - // Outermost constraint that constrains this induction variable into - // [-inf, X] range. - ConstraintInstr* limit() const { return limit_; } - - // Induction variable from the same join block that has limiting constraint. - PhiInstr* bound() const { return bound_; } - void set_bound(PhiInstr* bound) { bound_ = bound; } - - private: - PhiInstr* phi_; - Definition* initial_value_; - BinarySmiOpInstr* increment_; - ConstraintInstr* limit_; - - PhiInstr* bound_; -}; - -static ConstraintInstr* FindBoundingConstraint(PhiInstr* phi, - Definition* defn) { - ConstraintInstr* limit = NULL; - for (ConstraintInstr* constraint = defn->AsConstraint(); constraint != NULL; - constraint = constraint->value()->definition()->AsConstraint()) { - if (constraint->target() == NULL) { - continue; // Only interested in non-artifical constraints. - } - - Range* constraining_range = constraint->constraint(); - if (constraining_range->min().Equals(RangeBoundary::MinSmi()) && - (constraining_range->max().IsSymbol() && - phi->IsDominatedBy(constraining_range->max().symbol()))) { - limit = constraint; - } - } - - return limit; -} - -static InductionVariableInfo* DetectSimpleInductionVariable(PhiInstr* phi) { - if (phi->Type()->ToCid() != kSmiCid) { - return NULL; - } - - if (phi->InputCount() != 2) { - return NULL; - } - - BitVector* loop_blocks = phi->block()->loop_info()->blocks(); - - const intptr_t backedge_idx = - loop_blocks->Contains(phi->block()->PredecessorAt(0)->preorder_number()) - ? 0 - : 1; - - Definition* initial_value = phi->InputAt(1 - backedge_idx)->definition(); - - BinarySmiOpInstr* increment = - UnwrapConstraint(phi->InputAt(backedge_idx)->definition()) - ->AsBinarySmiOp(); - - if ((increment != NULL) && (increment->op_kind() == Token::kADD) && - (UnwrapConstraint(increment->left()->definition()) == phi) && - increment->right()->BindsToConstant() && - increment->right()->BoundConstant().IsSmi() && - (Smi::Cast(increment->right()->BoundConstant()).Value() == 1)) { - return new InductionVariableInfo( - phi, initial_value, increment, - FindBoundingConstraint(phi, increment->left()->definition())); - } - - return NULL; -} - -// TODO(ajcbik): move induction variable recognition in loop framework -void RangeAnalysis::DiscoverSimpleInductionVariables() { - GrowableArray loop_variables; - - for (BlockIterator block_it = flow_graph_->reverse_postorder_iterator(); - !block_it.Done(); block_it.Advance()) { - BlockEntryInstr* block = block_it.Current(); - - JoinEntryInstr* join = block->AsJoinEntry(); - if (join != NULL && join->loop_info() != NULL && - join->loop_info()->header() == join) { - loop_variables.Clear(); - - for (PhiIterator phi_it(join); !phi_it.Done(); phi_it.Advance()) { - PhiInstr* current = phi_it.Current(); - - InductionVariableInfo* info = DetectSimpleInductionVariable(current); - if (info != NULL) { - if (FLAG_support_il_printer && FLAG_trace_range_analysis) { - THR_Print("Simple loop variable: %s bound <%s>\n", - current->ToCString(), - info->limit() != NULL ? info->limit()->ToCString() : "?"); - } - - loop_variables.Add(info); - } - } - } - - InductionVariableInfo* bound = NULL; - for (intptr_t i = 0; i < loop_variables.length(); i++) { - if (loop_variables[i]->limit() != NULL) { - bound = loop_variables[i]; - break; - } - } - - if (bound != NULL) { - for (intptr_t i = 0; i < loop_variables.length(); i++) { - InductionVariableInfo* info = loop_variables[i]; - info->set_bound(bound->phi()); - info->phi()->set_induction_variable_info(info); - } - } - } -} - void RangeAnalysis::CollectValues() { auto graph_entry = flow_graph_->graph_entry(); @@ -1077,6 +917,8 @@ class BoundsCheckGeneralizer { } typedef Definition* (BoundsCheckGeneralizer::*PhiBoundFunc)(PhiInstr*, + LoopInfo*, + InductionVar*, Instruction*); // Construct symbolic lower bound for a value at the given point. @@ -1091,6 +933,52 @@ class BoundsCheckGeneralizer { value, point); } + // Helper methods to implement "older" business logic. + // TODO(ajcbik): generalize with new induction variable information + + // Only accept loops with a smi constraint on smi induction. + LoopInfo* GetSmiBoundedLoop(PhiInstr* phi) { + LoopInfo* loop = phi->GetBlock()->loop_info(); + if (loop == nullptr) { + return nullptr; + } + ConstraintInstr* limit = loop->limit(); + if (limit == nullptr) { + return nullptr; + } + Definition* def = UnwrapConstraint(limit->value()->definition()); + Range* constraining_range = limit->constraint(); + if (GetSmiInduction(loop, def) != nullptr && + constraining_range->min().Equals(RangeBoundary::MinSmi()) && + constraining_range->max().IsSymbol() && + def->IsDominatedBy(constraining_range->max().symbol())) { + return loop; + } + return nullptr; + } + + // Only accept smi linear induction with unit stride. + InductionVar* GetSmiInduction(LoopInfo* loop, Definition* def) { + if (loop != nullptr && def->Type()->ToCid() == kSmiCid) { + InductionVar* induc = loop->LookupInduction(def); + if (induc != nullptr && induc->kind() == InductionVar::kLinear && + induc->next()->offset() == 1 && induc->next()->mult() == 0) { + return induc; + } + } + return nullptr; + } + + // Reconstruct invariant (phi-init is always already in the graph). + Definition* GenerateInvariant(InductionVar* induc) { + if (induc->mult() == 0) { + return flow_graph_->GetConstant( + Smi::ZoneHandle(Smi::New(induc->offset()))); + } + ASSERT(induc->offset() == 0 && induc->mult() == 1); + return induc->def(); + } + // Construct symbolic bound for a value at the given point: // // 1. if value is an induction variable use its bounds; @@ -1109,8 +997,10 @@ class BoundsCheckGeneralizer { value = UnwrapConstraint(value); if (value->IsPhi()) { PhiInstr* phi = value->AsPhi(); - if (phi->induction_variable_info() != NULL) { - return (this->*phi_bound_func)(phi, point); + LoopInfo* loop = GetSmiBoundedLoop(phi); + InductionVar* induc = GetSmiInduction(loop, phi); + if (induc != nullptr) { + return (this->*phi_bound_func)(phi, loop, induc, point); } } else if (value->IsBinarySmiOp()) { BinarySmiOpInstr* bin_op = value->AsBinarySmiOp(); @@ -1131,58 +1021,61 @@ class BoundsCheckGeneralizer { } } } - return value; } - Definition* InductionVariableUpperBound(PhiInstr* phi, Instruction* point) { - const InductionVariableInfo& info = *phi->induction_variable_info(); - if (info.bound() == phi) { - if (point->IsDominatedBy(info.limit())) { - // Given induction variable - // - // x <- phi(x0, x + 1) - // - // and a constraint x <= M that dominates the given - // point we conclude that M is an upper bound for x. - return RangeBoundaryToDefinition(info.limit()->constraint()->max()); - } - } else { - const InductionVariableInfo& bound_info = - *info.bound()->induction_variable_info(); - if (point->IsDominatedBy(bound_info.limit())) { - // Given two induction variables - // - // x <- phi(x0, x + 1) - // y <- phi(y0, y + 1) - // - // and a constraint x <= M that dominates the given - // point we can conclude that - // - // y <= y0 + (M - x0) - // - Definition* limit = - RangeBoundaryToDefinition(bound_info.limit()->constraint()->max()); - BinarySmiOpInstr* loop_length = MakeBinaryOp( - Token::kSUB, ConstructUpperBound(limit, point), - ConstructLowerBound(bound_info.initial_value(), point)); - return MakeBinaryOp(Token::kADD, - ConstructUpperBound(info.initial_value(), point), - loop_length); - } + Definition* InductionVariableUpperBound(PhiInstr* phi, + LoopInfo* loop, + InductionVar* induc, + Instruction* point) { + // Test if limit dominates given point. + ConstraintInstr* limit = loop->limit(); + if (!point->IsDominatedBy(limit)) { + return phi; + } + // Decide between direct or indirect bound. + Definition* bounded_phi = UnwrapConstraint(limit->value()->definition()); + if (bounded_phi == phi) { + // Given a smi bounded loop with smi induction variable + // + // x <- phi(x0, x + 1) + // + // and a constraint x <= M that dominates the given + // point we conclude that M is an upper bound for x. + return RangeBoundaryToDefinition(limit->constraint()->max()); + } else { + // Given a smi bounded loop with two smi induction variables + // + // x <- phi(x0, x + 1) + // y <- phi(y0, y + 1) + // + // and a constraint x <= M that dominates the given + // point we can conclude that + // + // y <= y0 + (M - x0) + // + InductionVar* bounded_induc = GetSmiInduction(loop, bounded_phi); + Definition* x0 = GenerateInvariant(bounded_induc->initial()); + Definition* y0 = GenerateInvariant(induc->initial()); + Definition* m = RangeBoundaryToDefinition(limit->constraint()->max()); + BinarySmiOpInstr* loop_length = + MakeBinaryOp(Token::kSUB, ConstructUpperBound(m, point), + ConstructLowerBound(x0, point)); + return MakeBinaryOp(Token::kADD, ConstructUpperBound(y0, point), + loop_length); } - - return phi; } - Definition* InductionVariableLowerBound(PhiInstr* phi, Instruction* point) { - // Given induction variable + Definition* InductionVariableLowerBound(PhiInstr* phi, + LoopInfo* loop, + InductionVar* induc, + Instruction* point) { + // Given a smi bounded loop with smi induction variable // // x <- phi(x0, x + 1) // // we can conclude that LowerBound(x) == x0. - const InductionVariableInfo& info = *phi->induction_variable_info(); - return ConstructLowerBound(info.initial_value(), point); + return ConstructLowerBound(GenerateInvariant(induc->initial()), point); } // Try to re-associate binary operations in the floating DAG of operations diff --git a/runtime/vm/compiler/backend/range_analysis.h b/runtime/vm/compiler/backend/range_analysis.h index 0b25eaa045e..56dc78e790a 100644 --- a/runtime/vm/compiler/backend/range_analysis.h +++ b/runtime/vm/compiler/backend/range_analysis.h @@ -538,8 +538,8 @@ class RangeAnalysis : public ValueObject { enum JoinOperator { NONE, WIDEN, NARROW }; static char OpPrefix(JoinOperator op); - // Collect all values that were proven to be smi in smi_values_ array and all - // CheckSmi instructions in smi_check_ array. + // Collect all integer values (smi or int), all 64-bit binary + // and shift operations, and all check bounds. void CollectValues(); // Iterate over smi values and constrain them at branch successors. @@ -583,8 +583,6 @@ class RangeAnalysis : public ValueObject { // Convert mint operations that stay within int32 range into Int32 operations. void NarrowMintToInt32(); - void DiscoverSimpleInductionVariables(); - // Remove artificial Constraint instructions and replace them with actual // unconstrained definitions. void RemoveConstraints(); @@ -600,11 +598,11 @@ class RangeAnalysis : public ValueObject { Range int64_range_; - // Value that are known to be smi or mint. + // All values that are known to be smi or mint. GrowableArray values_; + // All 64-bit binary and shift operations. GrowableArray binary_int64_ops_; - GrowableArray shift_int64_ops_; // All CheckArrayBound instructions.