Mark graphs as always throwing alongside methods

This CL removes the need of using an extra boolean
(`did_set_always_throws`) as we already encode the information in
HasAlwaysThrowingInvokes after aosp/2153582.

Also, if we know that a method always throws in the particular
instance that it is called, we can mark it as such. This is an
improvement versus using the dex instructions as methods like:

int foo(int a) {
  if (a == 0) { throw new Error("a is 0!"); }
  return a / a;
}

will be marked as always throws if called as foo(0).

Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: I5368878d0023775c53028a5cccd4a1111b50f60e
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index d8ace5e..3a2f7f2 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -141,7 +141,11 @@
   }
 
   bool did_inline = false;
-  bool did_set_always_throws = false;
+  // The inliner is the only phase that sets invokes as `always throwing`, and since we only run the
+  // inliner once per graph this value should always be false at the beginning of the inlining
+  // phase. This is important since we use `HasAlwaysThrowingInvokes` to know whether the inliner
+  // phase performed a relevant change in the graph.
+  DCHECK(!graph_->HasAlwaysThrowingInvokes());
 
   // Initialize the number of instructions for the method being compiled. Recursive calls
   // to HInliner::Run have already updated the instruction count.
@@ -182,7 +186,7 @@
               call->GetMethodReference().PrettyMethod(/* with_signature= */ false);
           // Tests prevent inlining by having $noinline$ in their method names.
           if (callee_name.find("$noinline$") == std::string::npos) {
-            if (TryInline(call, &did_set_always_throws)) {
+            if (TryInline(call)) {
               did_inline = true;
             } else if (honor_inline_directives) {
               bool should_have_inlined = (callee_name.find("$inline$") != std::string::npos);
@@ -192,7 +196,7 @@
         } else {
           DCHECK(!honor_inline_directives);
           // Normal case: try to inline.
-          if (TryInline(call, &did_set_always_throws)) {
+          if (TryInline(call)) {
             did_inline = true;
           }
         }
@@ -201,11 +205,9 @@
     }
   }
 
-  if (did_set_always_throws) {
-    graph_->SetHasAlwaysThrowingInvokes(/* value= */ true);
-  }
-
-  return did_inline || did_set_always_throws;
+  // We return true if we either inlined at least one method, or we marked one of our methods as
+  // always throwing.
+  return did_inline || graph_->HasAlwaysThrowingInvokes();
 }
 
 static bool IsMethodOrDeclaringClassFinal(ArtMethod* method)
@@ -440,7 +442,7 @@
   return throw_seen;
 }
 
-bool HInliner::TryInline(HInvoke* invoke_instruction, /*inout*/ bool* did_set_always_throws) {
+bool HInliner::TryInline(HInvoke* invoke_instruction) {
   MaybeRecordStat(stats_, MethodCompilationStat::kTryInline);
 
   // Don't bother to move further if we know the method is unresolved or the invocation is
@@ -491,11 +493,10 @@
       } else {
         invoke_to_analyze = invoke_instruction;
       }
-      // Set always throws property for non-inlined method call with single
-      // target.
+      // Set always throws property for non-inlined method call with single target.
       if (AlwaysThrows(actual_method)) {
-        invoke_to_analyze->SetAlwaysThrows(true);
-        *did_set_always_throws = true;
+        invoke_to_analyze->SetAlwaysThrows(/* always_throws= */ true);
+        graph_->SetHasAlwaysThrowingInvokes(/* value= */ true);
       }
     }
     return result;
@@ -1819,8 +1820,9 @@
 // If this function returns true, it will also set out_number_of_instructions to
 // the number of instructions in the inlined body.
 bool HInliner::CanInlineBody(const HGraph* callee_graph,
-                             const HBasicBlock* target_block,
+                             HInvoke* invoke,
                              size_t* out_number_of_instructions) const {
+  const HBasicBlock* target_block = invoke->GetBlock();
   ArtMethod* const resolved_method = callee_graph->GetArtMethod();
 
   HBasicBlock* exit_block = callee_graph->GetExitBlock();
@@ -1862,6 +1864,11 @@
   }
 
   if (!has_one_return) {
+    // If we know that the method always throws with the particular parameters, set it as such. This
+    // is better than using the dex instructions as we have more information about this particular
+    // call.
+    invoke->SetAlwaysThrows(/* always_throws= */ true);
+    graph_->SetHasAlwaysThrowingInvokes(/* value= */ true);
     LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedAlwaysThrows)
         << "Method " << resolved_method->PrettyMethod()
         << " could not be inlined because it always throws";
@@ -2058,7 +2065,7 @@
   RunOptimizations(callee_graph, code_item, dex_compilation_unit);
 
   size_t number_of_instructions = 0;
-  if (!CanInlineBody(callee_graph, invoke_instruction->GetBlock(), &number_of_instructions)) {
+  if (!CanInlineBody(callee_graph, invoke_instruction, &number_of_instructions)) {
     return false;
   }
 
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index a2c2085..e33160e 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -70,9 +70,7 @@
     kInlineCacheMissingTypes = 5
   };
 
-  // We set `did_set_always_throws` as true if we analyzed `invoke_instruction` and it always
-  // throws.
-  bool TryInline(HInvoke* invoke_instruction, /*inout*/ bool* did_set_always_throws);
+  bool TryInline(HInvoke* invoke_instruction);
 
   // Try to inline `resolved_method` in place of `invoke_instruction`. `do_rtp` is whether
   // reference type propagation can run after the inlining. If the inlining is successful, this
@@ -142,7 +140,7 @@
   // This checks for instructions and constructs that we do not support
   // inlining, such as inlining a throw instruction into a try block.
   bool CanInlineBody(const HGraph* callee_graph,
-                     const HBasicBlock* target_block,
+                     HInvoke* invoke,
                      size_t* out_number_of_instructions) const
     REQUIRES_SHARED(Locks::mutator_lock_);
 
diff --git a/test/2042-checker-dce-always-throw/src/Main.java b/test/2042-checker-dce-always-throw/src/Main.java
index a82bbc3..097c9e0 100644
--- a/test/2042-checker-dce-always-throw/src/Main.java
+++ b/test/2042-checker-dce-always-throw/src/Main.java
@@ -22,6 +22,7 @@
     // Basic test for non-trivial blocks (i.e. not just an invoke and a Goto)
     assertEquals(0, $noinline$testSimplifyThrowAndPrint(1));
     assertEquals(0, $noinline$testSimplifyTwoThrows(1));
+    assertEquals(0, $noinline$testSimplifyWithArgument(1));
 
     // Try catch tests
     assertEquals(0, $noinline$testDoNotSimplifyInTry(1));
@@ -101,6 +102,35 @@
     return 0;
   }
 
+  private static int throwIfZero(int num) {
+    if (num == 0) {
+      throw new Error("num is 0!");
+    }
+    return num / num;
+  }
+
+  /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (before)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.throwIfZero always_throws:true
+  /// CHECK-DAG:   InvokeVirtual method_name:java.io.PrintStream.println
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
+  /// CHECK-EVAL:  "<<ExitBlock>>" != "<<TargetBlock>>"
+
+  /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.throwIfZero always_throws:true
+  /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>
+  /// CHECK-DAG:   Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+  /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (after)
+  /// CHECK-NOT:   InvokeVirtual method_name:java.io.PrintStream.println
+  private static int $noinline$testSimplifyWithArgument(int num) {
+    if (num == 0) {
+      throwIfZero(0);
+      System.out.println("I am unrechable!");
+    }
+    return 0;
+  }
+
   /// CHECK-START: int Main.$noinline$testSimplifyThrowWithTryCatch(int) dead_code_elimination$after_inlining (before)
   /// CHECK-DAG:   InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
   /// CHECK-DAG:   Exit block:<<ExitBlock:B\d+>>