Added geometric induction variables analysis.

Rationale:
Information on geometric and polynomial (coming soon) sequences
are nice to have to further enhance BCE and last-value assignment.

Test: test-art-host
Change-Id: Ib5e2998c3eb1009def6fd00b82935da7c3ba7c6e
diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc
index 235793d..75619a3 100644
--- a/compiler/optimizing/induction_var_range.cc
+++ b/compiler/optimizing/induction_var_range.cc
@@ -57,6 +57,21 @@
   return false;
 }
 
+/** Returns b^e for b,e >= 1. */
+static int64_t IntPow(int64_t b, int64_t e) {
+  DCHECK_GE(b, 1);
+  DCHECK_GE(e, 1);
+  int64_t pow = 1;
+  while (e) {
+    if (e & 1) {
+      pow *= b;
+    }
+    e >>= 1;
+    b *= b;
+  }
+  return pow;
+}
+
 /**
  * Detects an instruction that is >= 0. As long as the value is carried by
  * a single instruction, arithmetic wrap-around cannot occur.
@@ -399,6 +414,8 @@
     /*out*/ HLoopInformation** loop,
     /*out*/ HInductionVarAnalysis::InductionInfo** info,
     /*out*/ HInductionVarAnalysis::InductionInfo** trip) const {
+  DCHECK(context != nullptr);
+  DCHECK(context->GetBlock() != nullptr);
   HLoopInformation* lp = context->GetBlock()->GetLoopInformation();  // closest enveloping loop
   if (lp != nullptr) {
     HInductionVarAnalysis::InductionInfo* i = induction_analysis_->LookupInfo(lp, instruction);
@@ -474,6 +491,8 @@
                                                       HInductionVarAnalysis::InductionInfo* trip,
                                                       bool in_body,
                                                       bool is_min) const {
+  DCHECK(info != nullptr);
+  DCHECK(info->induction_class == HInductionVarAnalysis::kLinear);
   // Detect common situation where an offset inside the trip-count cancels out during range
   // analysis (finding max a * (TC - 1) + OFFSET for a == 1 and TC = UPPER - OFFSET or finding
   // min a * (TC - 1) + OFFSET for a == -1 and TC = OFFSET - UPPER) to avoid losing information
@@ -520,6 +539,30 @@
                   GetVal(info->op_b, trip, in_body, is_min));
 }
 
+InductionVarRange::Value InductionVarRange::GetGeometric(HInductionVarAnalysis::InductionInfo* info,
+                                                         HInductionVarAnalysis::InductionInfo* trip,
+                                                         bool in_body,
+                                                         bool is_min) const {
+  DCHECK(info != nullptr);
+  DCHECK(info->induction_class == HInductionVarAnalysis::kGeometric);
+  int64_t a = 0;
+  int64_t f = 0;
+  if (IsConstant(info->op_a, kExact, &a) &&
+      CanLongValueFitIntoInt(a) &&
+      IsIntAndGet(info->fetch, &f) &&
+      CanLongValueFitIntoInt(f) && f >= 1) {
+    // Conservative bounds on a * f^-i + b with f >= 1 can be computed without trip count.
+    // Same for mod. All other forms would require a much more elaborate evaluation.
+    const bool is_min_a = a >= 0 ? is_min : !is_min;
+    if (info->operation == HInductionVarAnalysis::kDiv) {
+      return AddValue(Value(is_min_a ? 0 : a), GetVal(info->op_b, trip, in_body, is_min));
+    } else if (info->operation == HInductionVarAnalysis::kNop) {
+      return AddValue(Value(is_min_a ? (a % f) : a), GetVal(info->op_b, trip, in_body, is_min));
+    }
+  }
+  return Value();
+}
+
 InductionVarRange::Value InductionVarRange::GetFetch(HInstruction* instruction,
                                                      HInductionVarAnalysis::InductionInfo* trip,
                                                      bool in_body,
@@ -631,6 +674,10 @@
         break;
       case HInductionVarAnalysis::kLinear:
         return CorrectForType(GetLinear(info, trip, in_body, is_min), info->type);
+      case HInductionVarAnalysis::kPolynomial:
+        break;
+      case HInductionVarAnalysis::kGeometric:
+        return GetGeometric(info, trip, in_body, is_min);
       case HInductionVarAnalysis::kWrapAround:
       case HInductionVarAnalysis::kPeriodic:
         return MergeVal(GetVal(info->op_a, trip, in_body, is_min),
@@ -842,17 +889,21 @@
   *needs_taken_test = IsBodyTripCount(trip);
   // Handle last value request.
   if (is_last_value) {
-    if (info->induction_class == HInductionVarAnalysis::kLinear) {
-      if (*stride_value > 0) {
-        lower = nullptr;
-      } else {
-        upper = nullptr;
-      }
-    } else if (info->induction_class == HInductionVarAnalysis::kPeriodic) {
-      DCHECK(!in_body);
-      return GenerateLastValuePeriodic(info, trip, graph, block, lower, needs_taken_test);
-    } else {
-      return false;
+    DCHECK(!in_body);
+    switch (info->induction_class) {
+      case HInductionVarAnalysis::kLinear:
+        if (*stride_value > 0) {
+          lower = nullptr;
+        } else {
+          upper = nullptr;
+        }
+        break;
+      case HInductionVarAnalysis::kGeometric:
+        return GenerateLastValueGeometric(info, trip, graph, block, lower);
+      case HInductionVarAnalysis::kPeriodic:
+        return GenerateLastValuePeriodic(info, trip, graph, block, lower, needs_taken_test);
+      default:
+        return false;
     }
   }
   // Code generation for taken test: generate the code when requested or otherwise analyze
@@ -874,12 +925,68 @@
       GenerateCode(info, trip, graph, block, upper, in_body, /* is_min */ false);
 }
 
+bool InductionVarRange::GenerateLastValueGeometric(HInductionVarAnalysis::InductionInfo* info,
+                                                   HInductionVarAnalysis::InductionInfo* trip,
+                                                   HGraph* graph,
+                                                   HBasicBlock* block,
+                                                   /*out*/HInstruction** result) const {
+  DCHECK(info != nullptr);
+  DCHECK(info->induction_class == HInductionVarAnalysis::kGeometric);
+  // Detect known base and trip count (always taken).
+  int64_t f = 0;
+  int64_t t = 0;
+  if (IsIntAndGet(info->fetch, &f) && f >= 1 && IsConstant(trip->op_a, kExact, &t) && t >= 1) {
+    HInstruction* opa = nullptr;
+    HInstruction* opb = nullptr;
+    if (GenerateCode(info->op_a, nullptr, graph, block, &opa, false, false) &&
+        GenerateCode(info->op_b, nullptr, graph, block, &opb, false, false)) {
+      // Generate a % f + b.
+      if (info->operation == HInductionVarAnalysis::kNop) {
+        if (graph != nullptr) {
+          HInstruction* rem =
+              Insert(block, new (graph->GetArena()) HRem(info->type, opa, info->fetch, kNoDexPc));
+          *result = Insert(block, new (graph->GetArena()) HAdd(info->type, rem, opb));
+        }
+        return true;
+      }
+      // Compute f ^ t.
+      int64_t fpowt = IntPow(f, t);
+      if (graph != nullptr) {
+        DCHECK(info->type == Primitive::kPrimInt);  // due to codegen, generalize?
+        if (fpowt == 0) {
+          // Special case: repeated mul/div always yields zero.
+          *result = graph->GetIntConstant(0);
+        } else if (info->operation == HInductionVarAnalysis::kMul) {
+          // Last value multiplication: a * f ^ t + b.
+          HInstruction* mul = Insert(block,
+                                     new (graph->GetArena()) HMul(info->type,
+                                                                  opa,
+                                                                  graph->GetIntConstant(fpowt)));
+          *result = Insert(block, new (graph->GetArena()) HAdd(info->type, mul, opb));
+        } else {
+          // Last value multiplication: a * f ^ -t + b.
+          DCHECK_EQ(info->operation, HInductionVarAnalysis::kDiv);
+          HInstruction* div = Insert(block,
+                                     new (graph->GetArena()) HDiv(info->type,
+                                                                  opa,
+                                                                  graph->GetIntConstant(fpowt),
+                                                                  kNoDexPc));
+          *result = Insert(block, new (graph->GetArena()) HAdd(info->type, div, opb));
+        }
+      }
+      return true;
+    }
+  }
+  return false;
+}
+
 bool InductionVarRange::GenerateLastValuePeriodic(HInductionVarAnalysis::InductionInfo* info,
                                                   HInductionVarAnalysis::InductionInfo* trip,
                                                   HGraph* graph,
                                                   HBasicBlock* block,
                                                   /*out*/HInstruction** result,
                                                   /*out*/bool* needs_taken_test) const {
+  DCHECK(info != nullptr);
   DCHECK(info->induction_class == HInductionVarAnalysis::kPeriodic);
   // Count period.
   int32_t period = 1;
@@ -937,6 +1044,7 @@
       return true;
     }
     // Verify type safety.
+    // TODO: generalize
     Primitive::Type type = Primitive::kPrimInt;
     if (info->type != Primitive::kPrimInt && info->type != Primitive::kPrimBoolean) {
       return false;
@@ -1058,6 +1166,9 @@
         }
         break;
       }
+      case HInductionVarAnalysis::kPolynomial:
+      case HInductionVarAnalysis::kGeometric:
+        break;
       case HInductionVarAnalysis::kWrapAround:
       case HInductionVarAnalysis::kPeriodic: {
         // Wrap-around and periodic inductions are restricted to constants only, so that extreme
@@ -1071,8 +1182,6 @@
         }
         break;
       }
-      default:
-        break;
     }
   }
   return false;