Code sinking near "always throwing" method calls

Rationale:
This is a slight generalization of the recent CL
under the same description; no longer restricted
to static methods, also to virtuals with a single
target are analyzed now.

Test: test-art-host test-art-target
Change-Id: Ib618f0c545c2cd0924228338f4aa273738868eb7
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index 41e4bbe..452be6f 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -459,25 +459,29 @@
   }
 
   if (actual_method != nullptr) {
+    // Single target.
     bool result = TryInlineAndReplace(invoke_instruction,
                                       actual_method,
                                       ReferenceTypeInfo::CreateInvalid(),
                                       /* do_rtp */ true,
                                       cha_devirtualize);
-    if (result && !invoke_instruction->IsInvokeStaticOrDirect()) {
-      if (cha_devirtualize) {
-        // Add dependency due to devirtulization. We've assumed resolved_method
-        // has single implementation.
-        outermost_graph_->AddCHASingleImplementationDependency(resolved_method);
-        MaybeRecordStat(stats_, MethodCompilationStat::kCHAInline);
-      } else {
-        MaybeRecordStat(stats_, MethodCompilationStat::kInlinedInvokeVirtualOrInterface);
+    if (result) {
+      // Successfully inlined.
+      if (!invoke_instruction->IsInvokeStaticOrDirect()) {
+        if (cha_devirtualize) {
+          // Add dependency due to devirtualization. We've assumed resolved_method
+          // has single implementation.
+          outermost_graph_->AddCHASingleImplementationDependency(resolved_method);
+          MaybeRecordStat(stats_, MethodCompilationStat::kCHAInline);
+        } else {
+          MaybeRecordStat(stats_, MethodCompilationStat::kInlinedInvokeVirtualOrInterface);
+        }
       }
-    } else if (!result && invoke_instruction->IsInvokeStaticOrDirect()) {
-      // Analyze always throws property for static/direct method call with single target.
-      if (AlwaysThrows(actual_method)) {
-        invoke_instruction->SetAlwaysThrows(true);
-      }
+    } else if (!cha_devirtualize && AlwaysThrows(actual_method)) {
+      // Set always throws property for non-inlined method call with single target
+      // (unless it was obtained through CHA, because that would imply we have
+      // to add the CHA dependency, which seems not worth it).
+      invoke_instruction->SetAlwaysThrows(true);
     }
     return result;
   }
diff --git a/test/673-checker-throw-vmethod/expected.txt b/test/673-checker-throw-vmethod/expected.txt
new file mode 100644
index 0000000..b0aad4d
--- /dev/null
+++ b/test/673-checker-throw-vmethod/expected.txt
@@ -0,0 +1 @@
+passed
diff --git a/test/673-checker-throw-vmethod/info.txt b/test/673-checker-throw-vmethod/info.txt
new file mode 100644
index 0000000..250810b
--- /dev/null
+++ b/test/673-checker-throw-vmethod/info.txt
@@ -0,0 +1 @@
+Test detecting throwing methods for code sinking.
diff --git a/test/673-checker-throw-vmethod/src/Main.java b/test/673-checker-throw-vmethod/src/Main.java
new file mode 100644
index 0000000..d0e1591
--- /dev/null
+++ b/test/673-checker-throw-vmethod/src/Main.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Tests for detecting throwing methods for code sinking.
+ */
+public class Main {
+
+  //
+  // Some "runtime library" methods.
+  //
+
+  public final void doThrow(String par) {
+    throw new Error("you are null: " + par);
+  }
+
+  public final void checkNotNullDirect(Object obj, String par) {
+    if (obj == null)
+      throw new Error("you are null: " + par);
+  }
+
+  public final void checkNotNullSplit(Object obj, String par) {
+    if (obj == null)
+      doThrow(par);
+  }
+
+  //
+  // Various ways of enforcing non-null parameter.
+  // In all cases, par should be subject to code sinking.
+  //
+
+  /// CHECK-START: void Main.doit1(int[]) code_sinking (before)
+  /// CHECK: begin_block
+  /// CHECK:   <<Str:l\d+>> LoadString
+  /// CHECK:   <<Tst:z\d+>> NotEqual
+  /// CHECK:                If [<<Tst>>]
+  /// CHECK: end_block
+  /// CHECK: begin_block
+  /// CHECK:                InvokeVirtual [{{l\d+}},<<Str>>]
+  /// CHECK:                Throw
+  /// CHECK: end_block
+  //
+  /// CHECK-START: void Main.doit1(int[]) code_sinking (after)
+  /// CHECK: begin_block
+  /// CHECK:   <<Tst:z\d+>> NotEqual
+  /// CHECK:                If [<<Tst>>]
+  /// CHECK: end_block
+  /// CHECK: begin_block
+  /// CHECK:   <<Str:l\d+>> LoadString
+  /// CHECK:                InvokeVirtual [{{l\d+}},<<Str>>]
+  /// CHECK:                Throw
+  /// CHECK: end_block
+  public void doit1(int[] a) {
+    String par = "a";
+    if (a == null)
+      throw new Error("you are null: " + par);
+    for (int i = 0; i < a.length; i++) {
+      a[i] = 1;
+    }
+  }
+
+  /// CHECK-START: void Main.doit2(int[]) code_sinking (before)
+  /// CHECK: begin_block
+  /// CHECK:   <<Str:l\d+>> LoadString
+  /// CHECK:   <<Tst:z\d+>> NotEqual
+  /// CHECK:                If [<<Tst>>]
+  /// CHECK: end_block
+  /// CHECK: begin_block
+  /// CHECK:                InvokeVirtual [{{l\d+}},<<Str>>] method_name:Main.doThrow
+  /// CHECK: end_block
+  //
+  /// CHECK-START: void Main.doit2(int[]) code_sinking (after)
+  /// CHECK: begin_block
+  /// CHECK:   <<Tst:z\d+>> NotEqual
+  /// CHECK:                If [<<Tst>>]
+  /// CHECK: end_block
+  /// CHECK: begin_block
+  /// CHECK:   <<Str:l\d+>> LoadString
+  /// CHECK:                InvokeVirtual [{{l\d+}},<<Str>>] method_name:Main.doThrow
+  /// CHECK: end_block
+  public void doit2(int[] a) {
+    String par = "a";
+    if (a == null)
+      doThrow(par);
+    for (int i = 0; i < a.length; i++) {
+      a[i] = 2;
+    }
+  }
+
+  /// CHECK-START: void Main.doit3(int[]) code_sinking (before)
+  /// CHECK: begin_block
+  /// CHECK:   <<Str:l\d+>> LoadString
+  /// CHECK:   <<Tst:z\d+>> NotEqual
+  /// CHECK:                If [<<Tst>>]
+  /// CHECK: end_block
+  /// CHECK: begin_block
+  /// CHECK:                InvokeVirtual [{{l\d+}},<<Str>>]
+  /// CHECK:                Throw
+  /// CHECK: end_block
+  //
+  /// CHECK-START: void Main.doit3(int[]) code_sinking (after)
+  /// CHECK: begin_block
+  /// CHECK:   <<Tst:z\d+>> NotEqual
+  /// CHECK:                If [<<Tst>>]
+  /// CHECK: end_block
+  /// CHECK: begin_block
+  /// CHECK:   <<Str:l\d+>> LoadString
+  /// CHECK:                InvokeVirtual [{{l\d+}},<<Str>>]
+  /// CHECK:                Throw
+  /// CHECK: end_block
+  public void doit3(int[] a) {
+    String par = "a";
+    checkNotNullDirect(a, par);
+    for (int i = 0; i < a.length; i++) {
+      a[i] = 3;
+    }
+  }
+
+  /// CHECK-START: void Main.doit4(int[]) code_sinking (before)
+  /// CHECK: begin_block
+  /// CHECK:   <<Str:l\d+>> LoadString
+  /// CHECK:   <<Tst:z\d+>> NotEqual
+  /// CHECK:                If [<<Tst>>]
+  /// CHECK: end_block
+  /// CHECK: begin_block
+  /// CHECK:                InvokeVirtual [{{l\d+}},<<Str>>] method_name:Main.doThrow
+  /// CHECK: end_block
+  //
+  /// CHECK-START: void Main.doit4(int[]) code_sinking (after)
+  /// CHECK: begin_block
+  /// CHECK:   <<Tst:z\d+>> NotEqual
+  /// CHECK:                If [<<Tst>>]
+  /// CHECK: end_block
+  /// CHECK: begin_block
+  /// CHECK:   <<Str:l\d+>> LoadString
+  /// CHECK:                InvokeVirtual [{{l\d+}},<<Str>>] method_name:Main.doThrow
+  /// CHECK: end_block
+  public void doit4(int[] a) {
+    String par = "a";
+    checkNotNullSplit(a, par);
+    for (int i = 0; i < a.length; i++) {
+      a[i] = 4;
+    }
+  }
+
+  //
+  // Test driver.
+  //
+
+  static public void main(String[] args) {
+    int[] a = new int[100];
+    for (int i = 0; i < 100; i++) {
+      a[i] = 0;
+    }
+
+    Main m = new Main();
+
+    try {
+      m.doit1(null);
+      System.out.println("should not reach this!");
+    } catch (Error e) {
+      m.doit1(a);
+    }
+    for (int i = 0; i < 100; i++) {
+      expectEquals(1, a[i]);
+    }
+
+    try {
+      m.doit2(null);
+      System.out.println("should not reach this!");
+    } catch (Error e) {
+      m.doit2(a);
+    }
+    for (int i = 0; i < 100; i++) {
+      expectEquals(2, a[i]);
+    }
+
+    try {
+      m.doit3(null);
+      System.out.println("should not reach this!");
+    } catch (Error e) {
+      m.doit3(a);
+    }
+    for (int i = 0; i < 100; i++) {
+      expectEquals(3, a[i]);
+    }
+
+    try {
+      m.doit4(null);
+      System.out.println("should not reach this!");
+    } catch (Error e) {
+      m.doit4(a);
+    }
+    for (int i = 0; i < 100; i++) {
+      expectEquals(4, a[i]);
+    }
+
+    System.out.println("passed");
+  }
+
+  private static void expectEquals(int expected, int result) {
+    if (expected != result) {
+      throw new Error("Expected: " + expected + ", found: " + result);
+    }
+  }
+}