Allow to sink code to catch blocks

We now allow to sink code to catch blocks, while blocking moving it
to blocks that would throw into catch blocks.

Bug: 75971227
Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: Iaf5ea647e96170cbfb95c4286c5c37c991cb2113
diff --git a/compiler/optimizing/code_sinking.cc b/compiler/optimizing/code_sinking.cc
index 2b56c88..0f336a2 100644
--- a/compiler/optimizing/code_sinking.cc
+++ b/compiler/optimizing/code_sinking.cc
@@ -217,8 +217,14 @@
     DCHECK(target_block != nullptr);
   }
 
-  // Bail if the instruction can throw and we are about to move into a catch block.
-  if (instruction->CanThrow() && target_block->GetTryCatchInformation() != nullptr) {
+  // Bail if the instruction would throw into a catch block.
+  if (instruction->CanThrow() && target_block->IsTryBlock()) {
+    // TODO(solanes): Here we could do something similar to the loop above and move to the first
+    // dominator, which is not a try block, instead of just returning nullptr. If we do so, we have
+    // to also make sure we are not in a loop.
+    // TODO(solanes): Alternatively, we could split the try block at the try boundary having two
+    // blocks: A and B. Block B would have the try boundary (and xhandler) and therefore block A
+    // would be the target block available to move the instruction.
     return nullptr;
   }
 
diff --git a/test/639-checker-code-sinking/src/Main.java b/test/639-checker-code-sinking/src/Main.java
index 91c3ec4..361806a 100644
--- a/test/639-checker-code-sinking/src/Main.java
+++ b/test/639-checker-code-sinking/src/Main.java
@@ -46,6 +46,7 @@
       // expected
       System.out.println(e.getMessage());
     }
+    testCatchBlock();
   }
 
   /// CHECK-START: void Main.testSimpleUse() code_sinking (before)
@@ -390,12 +391,116 @@
     return "" + intField;
   }
 
+  private static void testCatchBlock() {
+    assertEquals(456, testSinkToCatchBlock());
+    assertEquals(456, testDoNotSinkToTry());
+    assertEquals(456, testDoNotSinkToCatchInsideTry());
+  }
+
+  /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (before)
+  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
+  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
+  /// CHECK:                         TryBoundary kind:entry
+
+  /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (after)
+  /// CHECK:                         TryBoundary kind:entry
+  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
+  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
+
+  // Consistency check to make sure there's only one entry TryBoundary.
+  /// CHECK-START: int Main.testSinkToCatchBlock() code_sinking (after)
+  /// CHECK:                         TryBoundary kind:entry
+  /// CHECK-NOT:                     TryBoundary kind:entry
+
+  // Tests that we can sink the Object creation to the catch block.
+  private static int testSinkToCatchBlock() {
+    Object o = new Object();
+    try {
+      if (doEarlyReturn) {
+        return 123;
+      }
+    } catch (Error e) {
+      throw new Error(o.toString());
+    }
+    return 456;
+  }
+
+  /// CHECK-START: int Main.testDoNotSinkToTry() code_sinking (before)
+  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
+  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
+  /// CHECK:                         TryBoundary kind:entry
+
+  /// CHECK-START: int Main.testDoNotSinkToTry() code_sinking (after)
+  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
+  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
+  /// CHECK:                         TryBoundary kind:entry
+
+  // Consistency check to make sure there's only one entry TryBoundary.
+  /// CHECK-START: int Main.testDoNotSinkToTry() code_sinking (after)
+  /// CHECK:                         TryBoundary kind:entry
+  /// CHECK-NOT:                     TryBoundary kind:entry
+
+  // Tests that we don't sink the Object creation into the try.
+  private static int testDoNotSinkToTry() {
+    Object o = new Object();
+    try {
+      if (doEarlyReturn) {
+        throw new Error(o.toString());
+      }
+    } catch (Error e) {
+      throw new Error();
+    }
+    return 456;
+  }
+
+  /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (before)
+  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
+  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
+  /// CHECK:                         TryBoundary kind:entry
+  /// CHECK:                         TryBoundary kind:entry
+
+  /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (after)
+  /// CHECK: <<ObjLoadClass:l\d+>>   LoadClass class_name:java.lang.Object
+  /// CHECK:                         NewInstance [<<ObjLoadClass>>]
+  /// CHECK:                         TryBoundary kind:entry
+  /// CHECK:                         TryBoundary kind:entry
+
+  // Consistency check to make sure there's exactly two entry TryBoundary.
+  /// CHECK-START: int Main.testDoNotSinkToCatchInsideTry() code_sinking (after)
+  /// CHECK:                         TryBoundary kind:entry
+  /// CHECK:                         TryBoundary kind:entry
+  /// CHECK-NOT:                     TryBoundary kind:entry
+
+  // Tests that we don't sink the Object creation into a catch handler surrounded by try/catch.
+  private static int testDoNotSinkToCatchInsideTry() {
+    Object o = new Object();
+    try {
+      try {
+        if (doEarlyReturn) {
+          return 123;
+        }
+      } catch (Error e) {
+        throw new Error(o.toString());
+      }
+    } catch (Error e) {
+      throw new Error();
+    }
+    return 456;
+  }
+
+  private static void assertEquals(int expected, int actual) {
+    if (expected != actual) {
+      throw new AssertionError("Expected: " + expected + ", Actual: " + actual);
+    }
+  }
+
   volatile int volatileField;
   int intField;
   int intField2;
   Object objectField;
   static boolean doThrow;
   static boolean doLoop;
+  static boolean doEarlyReturn;
   static Main mainField = new Main();
   static Object obj = new Object();
 }