Update if simplification based on ranges.

When one of the ranges represents a single value there are simplification
posibilities at the edges of the other interval.

[s, e] < s -> always false
[s, e] >= s -> always true
[s, e] > e -> always false
[s, e] <= e -> always true

R=christofferqa@google.com

Bug: 111736519
Change-Id: Ic15d40dfda410f065cf6205d7a91b4a9624bd0b2
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index a8059bd..df6e65a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2448,16 +2448,73 @@
           // Zero test with a value range, or comparison between between two values,
           // each with a value ranges.
           if (theIf.isZeroTest()) {
-            if (!inValues.get(0).isValueInRange(0)) {
-              int cond = Long.signum(inValues.get(0).getValueRange().getMin());
-              simplifyIfWithKnownCondition(code, block, theIf, cond);
+            LongInterval interval = inValues.get(0).getValueRange();
+            if (!interval.containsValue(0)) {
+              // Interval doesn't contain zero at all.
+              int sign = Long.signum(interval.getMin());
+              simplifyIfWithKnownCondition(code, block, theIf, sign);
+            } else {
+              // Interval contains zero.
+              switch (theIf.getType()) {
+                case GE:
+                case LT:
+                  // [a, b] >= 0 is always true if a >= 0.
+                  // [a, b] < 0 is always false if a >= 0.
+                  // In both cases a zero condition takes the right branch.
+                  if (interval.getMin() == 0) {
+                    simplifyIfWithKnownCondition(code, block, theIf, 0);
+                  }
+                  break;
+                case LE:
+                case GT:
+                  // [a, b] <= 0 is always true if b <= 0.
+                  // [a, b] > 0 is always false if b <= 0.
+                  if (interval.getMax() == 0) {
+                    simplifyIfWithKnownCondition(code, block, theIf, 0);
+                  }
+                  break;
+                case EQ:
+                case NE:
+                  // Only a single element interval [0, 0] can be dealt with here.
+                  // Such intervals should have been replaced by constants.
+                  assert !interval.isSingleValue();
+                  break;
+              }
             }
           } else {
             LongInterval leftRange = inValues.get(0).getValueRange();
             LongInterval rightRange = inValues.get(1).getValueRange();
+            // Two overlapping ranges. Check for single point overlap.
             if (!leftRange.overlapsWith(rightRange)) {
+              // No overlap.
               int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
               simplifyIfWithKnownCondition(code, block, theIf, cond);
+            } else {
+              // The two intervals overlap. We can simplify if they overlap at the end points.
+              switch (theIf.getType()) {
+                case LT:
+                case GE:
+                  // [a, b] < [c, d] is always false when a == d.
+                  // [a, b] >= [c, d] is always true when a == d.
+                  // In both cases 0 condition will choose the right branch.
+                  if (leftRange.getMin() == rightRange.getMax()) {
+                    simplifyIfWithKnownCondition(code, block, theIf, 0);
+                  }
+                  break;
+                case GT:
+                case LE:
+                  // [a, b] > [c, d] is always false when b == c.
+                  // [a, b] <= [c, d] is always true when b == c.
+                  // In both cases 0 condition will choose the right branch.
+                  if (leftRange.getMax() == rightRange.getMin()) {
+                    simplifyIfWithKnownCondition(code, block, theIf, 0);
+                  }
+                  break;
+                case EQ:
+                case NE:
+                  // Since there is overlap EQ and NE cannot be determined.
+                  break;
+              }
             }
           }
         } else if (theIf.isZeroTest() && !inValues.get(0).isConstNumber()
diff --git a/src/test/examples/assumevalues6/Assumevalues.java b/src/test/examples/assumevalues6/Assumevalues.java
new file mode 100644
index 0000000..b3ad1de
--- /dev/null
+++ b/src/test/examples/assumevalues6/Assumevalues.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2018 the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package assumevalues6;
+
+public class Assumevalues {
+  public static int field = 0;
+  public static int field2 = 2;
+  public static int field3 = 2;
+
+  public static void main(String[] args) {
+    if (0 > field) {
+      System.out.println("NOPE1");
+    }
+    if (field < 0) {
+      System.out.println("NOPE2");
+    }
+    if (field3 == 0) {
+      System.out.println("NOPE3");
+    }
+    if (field3 != 0) {
+      System.out.println("YUP1");
+    }
+    if (2 < field) {
+      System.out.println("NOPE4");
+    }
+    if (field > 2) {
+      System.out.println("NOPE5");
+    }
+    if (field2 < field) {
+      System.out.println("NOPE6");
+    }
+    if (field > field2) {
+      System.out.println("NOPE7");
+    }
+    if (field <= field2) {
+      System.out.println("YUP2");
+    }
+    if (field2 >= field) {
+      System.out.println("YUP3");
+    }
+    System.out.println("OK");
+  }
+}
diff --git a/src/test/examples/assumevalues6/keep-rules.txt b/src/test/examples/assumevalues6/keep-rules.txt
new file mode 100644
index 0000000..4910a9d
--- /dev/null
+++ b/src/test/examples/assumevalues6/keep-rules.txt
@@ -0,0 +1,16 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep class assumevalues6.Assumevalues {
+  void main(...);
+}
+
+# Mark some fields with value ranges.
+-assumevalues public class assumevalues6.Assumevalues {
+  int field return 0..2;
+  int field2 return 2..4;
+  int field3 return 2..2;
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
new file mode 100644
index 0000000..e093f00
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShakingAssumevalues6Test.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking.examples;
+
+import com.android.tools.r8.TestBase.MinifyMode;
+import com.android.tools.r8.shaking.TreeShakingTest;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.ConstStringInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class TreeShakingAssumevalues6Test extends TreeShakingTest {
+
+  @Parameters(name = "mode:{0}-{1} minify:{2}")
+  public static Collection<Object[]> data() {
+    List<Object[]> parameters = new ArrayList<>();
+    for (MinifyMode minify : MinifyMode.values()) {
+      parameters.add(new Object[] {Frontend.JAR, Backend.CF, minify});
+      parameters.add(new Object[] {Frontend.JAR, Backend.DEX, minify});
+      parameters.add(new Object[] {Frontend.DEX, Backend.DEX, minify});
+    }
+    return parameters;
+  }
+
+  public TreeShakingAssumevalues6Test(Frontend frontend, Backend backend, MinifyMode minify) {
+    super("examples/assumevalues6", "assumevalues6.Assumevalues", frontend, backend, minify);
+  }
+
+  @Test
+  public void test() throws Exception {
+    runTest(
+        getBackend() == Backend.DEX ? TreeShakingAssumevalues6Test::assumevalues6CheckCode : null,
+        TreeShakingAssumevalues6Test::assumevalues6CheckOutput,
+        null,
+        ImmutableList.of("src/test/examples/assumevalues6/keep-rules.txt"));
+  }
+
+  private static void assumevalues6CheckCode(CodeInspector inspector) {
+    inspector.forAllClasses(c -> {
+      c.forAllMethods(m -> {
+        if (m.getFinalName().equals("main")) {
+          m.iterateInstructions().forEachRemaining(i -> {
+            if (i.isConstString(JumboStringMode.ALLOW)) {
+              ConstStringInstructionSubject str = (ConstStringInstructionSubject) i;
+              assert !str.getString().toASCIIString().contains("NOPE");
+            }
+          });
+        }
+      });
+    });
+  }
+
+  private static void assumevalues6CheckOutput(String output1, String output2) {
+    String expected = StringUtils.lines("YUP1", "YUP2", "YUP3", "OK");
+    Assert.assertEquals(expected, output1);
+    Assert.assertEquals(expected, output2);
+  }
+}
\ No newline at end of file