Extend the range of assume instructions, part 4.

Outliner is updated to embrace assum instructions while looking for
outlining candidate instructions in a row. Its logic on widening the
window of instructions regards assume instructions same as constants,
i.e., without increment, and then it will not add assume instructions
into the outline candidate code, which is also same as how constants are
handled.

Bug: 120920488
Change-Id: Idee99290b6ed29639ffefd8bf1e5aacb7ec89b85
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index b46a834..a75ce9e 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -1310,6 +1310,13 @@
       assert code.isConsistentSSA();
     }
 
+    previous = printMethod(code, "IR after lambda merger (SSA)", previous);
+
+    if (options.outline.enabled) {
+      outlineHandler.accept(code, method);
+      assert code.isConsistentSSA();
+    }
+
     if (nonNullTracker != null) {
       // TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args,
       //   this may not be the right place to collect call site optimization info.
@@ -1330,13 +1337,6 @@
 
     assert code.verifyTypes(appView);
 
-    previous = printMethod(code, "IR after lambda merger (SSA)", previous);
-
-    if (options.outline.enabled) {
-      outlineHandler.accept(code, method);
-      assert code.isConsistentSSA();
-    }
-
     previous = printMethod(code, "IR after outline handler (SSA)", previous);
 
     // TODO(mkroghj) Test if shorten live ranges is worth it.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 4837545..1845186 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -556,8 +556,10 @@
           templateInstructions.add(OutlineInstruction.fromInstruction(current));
         } else if (current.isConstInstruction()) {
           // Don't include const instructions in the template.
+        } else if (current.isAssume()) {
+          // Don't include assume instructions in the template.
         } else {
-          assert false : "Unexpected type of instruction in outlining template.";
+          assert false : "Unexpected type of instruction in outlining template:" + current;
         }
       }
     }
@@ -788,6 +790,12 @@
           include = true;
           instructionIncrement = 0;
         }
+      } else if (instruction.isAssume()) {
+        // Technically, assume instructions will be removed, and thus it should not be included.
+        // However, we should keep searching, so here we pretend to include it with 0 increment.
+        // When adding instruction into the outline candidate, we filter out assume instructions.
+        include = true;
+        instructionIncrement = 0;
       } else {
         include = canIncludeInstruction(instruction);
       }
@@ -986,12 +994,16 @@
 
     // Add the current instruction to the outline.
     private void includeInstruction(Instruction instruction) {
+      if (instruction.isAssume()) {
+        return;
+      }
+
       List<Value> inValues = orderedInValues(instruction, returnValue);
 
       Value prevReturnValue = returnValue;
       if (returnValue != null) {
         for (Value value : inValues) {
-          if (value == returnValue) {
+          if (value.getAliasedValue() == returnValue) {
             assert returnValueUsersLeft > 0;
             returnValueUsersLeft--;
           }
@@ -1013,7 +1025,7 @@
           || instruction.isArithmeticBinop();
       if (inValues.size() > 0) {
         for (int i = 0; i < inValues.size(); i++) {
-          Value value = inValues.get(i);
+          Value value = inValues.get(i).getAliasedValue();
           if (value == prevReturnValue) {
             argumentsMap.add(OutlineInstruction.OUTLINE_TEMP);
             continue;
@@ -1067,7 +1079,6 @@
       }
     }
 
-
     protected abstract void handle(int start, int end, Outline outline);
 
     private void candidate(int start, int index) {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
new file mode 100644
index 0000000..eabd3cc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
@@ -0,0 +1,186 @@
+// Copyright (c) 2019, 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.ir.optimize.outliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class OutlinesWithNonNullTest extends TestBase {
+  private static final String JVM_OUTPUT = StringUtils.lines(
+      "42",
+      "arg",
+      "42",
+      "arg"
+  );
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().build();
+  }
+
+  public OutlinesWithNonNullTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNonNullOnOneSide() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addProgramClasses(TestArg.class, TestClassWithNonNullOnOneSide.class)
+        .addKeepMainRule(TestClassWithNonNullOnOneSide.class)
+        .setMinApi(parameters.getRuntime())
+        .allowAccessModification()
+        .noMinification()
+        .addOptionsModification(
+            options -> {
+              options.outline.threshold = 2;
+              options.outline.minSize = 2;
+            })
+        .compile()
+        .inspect(inspector -> validateOutlining(inspector, TestClassWithNonNullOnOneSide.class))
+        .run(parameters.getRuntime(), TestClassWithNonNullOnOneSide.class)
+        .assertSuccessWithOutput(JVM_OUTPUT);
+  }
+
+  @Test
+  public void testNonNullOnBothSides() throws Exception {
+    testForR8(parameters.getBackend())
+        .enableClassInliningAnnotations()
+        .enableInliningAnnotations()
+        .addProgramClasses(TestArg.class, TestClassWithNonNullOnBothSides.class)
+        .addKeepMainRule(TestClassWithNonNullOnBothSides.class)
+        .setMinApi(parameters.getRuntime())
+        .allowAccessModification()
+        .noMinification()
+        .addOptionsModification(
+            options -> {
+              options.outline.threshold = 2;
+              options.outline.minSize = 2;
+            })
+        .compile()
+        .inspect(inspector -> validateOutlining(inspector, TestClassWithNonNullOnBothSides.class))
+        .run(parameters.getRuntime(), TestClassWithNonNullOnBothSides.class)
+        .assertSuccessWithOutput(JVM_OUTPUT);
+  }
+
+  private void validateOutlining(CodeInspector inspector, Class<?> main) {
+    ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME);
+    assertThat(outlineClass, isPresent());
+    MethodSubject outlineMethod = outlineClass.uniqueMethodWithName("outline0");
+    assertThat(outlineMethod, isPresent());
+
+    ClassSubject argClass = inspector.clazz(TestArg.class);
+    assertThat(argClass, isPresent());
+    MethodSubject printHash = argClass.uniqueMethodWithName("printHash");
+    assertThat(printHash, isPresent());
+    MethodSubject printArg= argClass.uniqueMethodWithName("printArg");
+    assertThat(printArg, isPresent());
+
+    ClassSubject classSubject = inspector.clazz(main);
+    assertThat(classSubject, isPresent());
+    MethodSubject method1 = classSubject.uniqueMethodWithName("method1");
+    assertThat(method1, isPresent());
+    assertThat(method1, CodeMatchers.invokesMethod(outlineMethod));
+    assertThat(method1, not(CodeMatchers.invokesMethod(printHash)));
+    assertThat(method1, not(CodeMatchers.invokesMethod(printArg)));
+    MethodSubject method2 = classSubject.uniqueMethodWithName("method2");
+    assertThat(method2, isPresent());
+    assertThat(method2, CodeMatchers.invokesMethod(outlineMethod));
+    assertThat(method2, not(CodeMatchers.invokesMethod(printHash)));
+    assertThat(method2, not(CodeMatchers.invokesMethod(printArg)));
+  }
+
+  @NeverClassInline
+  public static class TestArg {
+    @Override
+    public int hashCode() {
+      return 42;
+    }
+
+    @Override
+    public String toString() {
+      return "arg";
+    }
+
+    @NeverInline
+    static void printHash(Object arg) {
+      if (arg == null) {
+        throw new NullPointerException();
+      }
+      System.out.println(arg.hashCode());
+      // This method guarantees that, at the normal exit, argument is not null.
+    }
+
+    @NeverInline
+    static void printArg(Object arg) {
+      System.out.println(arg);
+    }
+  }
+
+  static class TestClassWithNonNullOnOneSide {
+    @NeverInline
+    static void method1(Object arg) {
+      TestArg.printHash(arg);
+      // We will have non-null aliasing here.
+      TestArg.printArg(arg);
+    }
+
+    @NeverInline
+    static void method2(Object arg) {
+      if (arg != null) {
+        // We will have non-null aliasing here.
+        TestArg.printHash(arg);
+        TestArg.printArg(arg);
+      }
+    }
+
+    public static void main(String... args) {
+      TestArg arg = new TestArg();
+      method1(arg);
+      method2(arg);
+    }
+  }
+
+  static class TestClassWithNonNullOnBothSides {
+    @NeverInline
+    static void method1(Object arg) {
+      TestArg.printHash(arg);
+      // We will have non-null aliasing here.
+      TestArg.printArg(arg);
+    }
+
+    @NeverInline
+    static void method2(Object arg) {
+      TestArg.printHash(arg);
+      // We will have non-null aliasing here.
+      TestArg.printArg(arg);
+    }
+
+    public static void main(String... args) {
+      TestArg arg = new TestArg();
+      method1(arg);
+      method2(arg);
+    }
+  }
+}