Add support for try catches in LSE

We now run LSE if there are try catches in the graph. We invalidate
values at the beginning of catch blocks since we can't know for sure
which instruction was the one that threw. As a note, finally blocks
are also considered catch blocks so we will invalidate at the
beginning of those blocks as well.

Test changes:
* 004 had to be changed since we are removing the dex_pc:3 from
  the StackMap (the array itself is never instanced).
* 061 had to be changed to actually trigger the OOM.

Bug: 227283233
Test: art/test/testrunner/testrunner.py --host --64 --optimizing -b
Change-Id: I79a85447069895b61fb48dde90f24d675d504873
diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc
index d42e4f7..4651210 100644
--- a/compiler/optimizing/load_store_elimination.cc
+++ b/compiler/optimizing/load_store_elimination.cc
@@ -78,6 +78,8 @@
  *    all back-edges. We use Phi placeholders also for array heap locations with
  *    index defined inside the loop but this helps only when the value remains
  *    zero from the array allocation throughout the loop.
+ *  - For catch blocks, we clear all assumptions since we arrived due to an
+ *    instruction throwing.
  *  - For other basic blocks, we merge incoming values from the end of all
  *    predecessors. If any incoming value is unknown, the start value for this
  *    block is also unknown. Otherwise, if all the incoming values are the same
@@ -113,8 +115,6 @@
  *  - SIMD graphs (with VecLoad and VecStore instructions) are also handled. Any
  *    partial overlap access among ArrayGet/ArraySet/VecLoad/Store is seen as
  *    alias and no load/store is eliminated in such case.
- *  - Currently this LSE algorithm doesn't handle graph with try-catch, due to
- *    the special block merging structure.
  *
  * The time complexity of the initial phase has several components. The total
  * time for the initialization of heap values for all blocks is
@@ -1618,8 +1618,9 @@
   ScopedArenaVector<ValueRecord>& heap_values = heap_values_for_[block->GetBlockId()];
   DCHECK(heap_values.empty());
   size_t num_heap_locations = heap_location_collector_.GetNumberOfHeapLocations();
-  if (block->GetPredecessors().empty()) {
-    DCHECK(block->IsEntryBlock());
+  if (block->GetPredecessors().empty() || (block->GetTryCatchInformation() != nullptr &&
+                                           block->GetTryCatchInformation()->IsCatchBlock())) {
+    DCHECK_IMPLIES(block->GetPredecessors().empty(), block->IsEntryBlock());
     heap_values.resize(num_heap_locations,
                        {/*value=*/Value::PureUnknown(), /*stored_by=*/Value::PureUnknown()});
     return;
@@ -3877,9 +3878,8 @@
 };
 
 bool LoadStoreElimination::Run(bool enable_partial_lse) {
-  if (graph_->IsDebuggable() || graph_->HasTryCatch()) {
+  if (graph_->IsDebuggable()) {
     // Debugger may set heap values or trigger deoptimization of callers.
-    // Try/catch support not implemented yet.
     // Skip this optimization.
     return false;
   }
diff --git a/test/004-ReferenceMap/stack_walk_refmap_jni.cc b/test/004-ReferenceMap/stack_walk_refmap_jni.cc
index 4317b5d..0659c0b 100644
--- a/test/004-ReferenceMap/stack_walk_refmap_jni.cc
+++ b/test/004-ReferenceMap/stack_walk_refmap_jni.cc
@@ -52,7 +52,6 @@
     // we know the Dex registers with live reference values. Assert that what we
     // find is what is expected.
     if (m_name.compare("f") == 0) {
-      CHECK_REGS_CONTAIN_REFS(0x03U, true, 8);  // v8: this
       CHECK_REGS_CONTAIN_REFS(0x06U, true, 8, 1);  // v8: this, v1: x
       CHECK_REGS_CONTAIN_REFS(0x0cU, true, 8, 3, 1);  // v8: this, v3: y, v1: x
       CHECK_REGS_CONTAIN_REFS(0x10U, true, 8, 3, 1);  // v8: this, v3: y, v1: x
diff --git a/test/061-out-of-memory/src/Main.java b/test/061-out-of-memory/src/Main.java
index bda978e..c39274b 100644
--- a/test/061-out-of-memory/src/Main.java
+++ b/test/061-out-of-memory/src/Main.java
@@ -108,7 +108,7 @@
         System.out.println("testOomeSmall succeeded");
     }
 
-    private static void testOomeToCharArray() {
+    private static Object testOomeToCharArray() {
         Object[] o = new Object[2000000];
         String test = "test";
         int i = 0;
@@ -123,5 +123,6 @@
             o = null;
             System.out.println("Got expected toCharArray OOM");
         }
+        return o;
     }
 }
diff --git a/test/530-checker-lse-try-catch/expected-stderr.txt b/test/530-checker-lse-try-catch/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/530-checker-lse-try-catch/expected-stderr.txt
diff --git a/test/530-checker-lse-try-catch/expected-stdout.txt b/test/530-checker-lse-try-catch/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/530-checker-lse-try-catch/expected-stdout.txt
diff --git a/test/530-checker-lse-try-catch/info.txt b/test/530-checker-lse-try-catch/info.txt
new file mode 100644
index 0000000..9ceef82
--- /dev/null
+++ b/test/530-checker-lse-try-catch/info.txt
@@ -0,0 +1 @@
+Checker test for testing load-store elimination for try catches.
diff --git a/test/530-checker-lse-try-catch/src/Main.java b/test/530-checker-lse-try-catch/src/Main.java
new file mode 100644
index 0000000..c7db02a
--- /dev/null
+++ b/test/530-checker-lse-try-catch/src/Main.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+class Point {
+  int x;
+  int y;
+}
+
+public class Main {
+  public static void main(String[] args) {
+    final boolean boolean_throw = false;
+    final boolean boolean_other_throw = false;
+    assertEquals(3,
+        $noinline$testDifferentFields(
+            new Point(), new Point(), boolean_throw, boolean_other_throw));
+    assertEquals(1, $noinline$testRedundantStore(new Point(), boolean_throw, boolean_other_throw));
+    assertEquals(1, $noinline$testTryCatchBlocking(new Point(), boolean_throw));
+    assertEquals(1, $noinline$testTryCatchPhi(new Point(), boolean_throw));
+    assertEquals(2, $noinline$testTryCatchPhiWithTwoCatches(new Point(), new int[0]));
+  }
+
+  /// CHECK-START: int Main.$noinline$testDifferentFields(Point, Point, boolean, boolean) load_store_elimination (before)
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.x
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldGet field_name:Point.x
+  /// CHECK-DAG:     InstanceFieldGet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testDifferentFields(Point, Point, boolean, boolean) load_store_elimination (after)
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.x
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testDifferentFields(Point, Point, boolean, boolean) load_store_elimination (after)
+  /// CHECK-NOT:     InstanceFieldGet field_name:Point.x
+  /// CHECK-NOT:     InstanceFieldGet field_name:Point.y
+
+  // Consistency check to make sure the try/catches weren't removed by an earlier pass.
+  /// CHECK-START: int Main.$noinline$testDifferentFields(Point, Point, boolean, boolean) load_store_elimination (after)
+  /// CHECK:         TryBoundary kind:entry
+  /// CHECK:         TryBoundary kind:entry
+
+  // Different fields shouldn't alias.
+  private static int $noinline$testDifferentFields(
+      Point obj1, Point obj2, boolean boolean_throw, boolean boolean_other_throw) {
+    try {
+      if (boolean_throw) {
+        throw new Error();
+      }
+    } catch (Error e) {
+      return 0;
+    }
+    obj1.x = 1;
+    obj2.y = 2;
+    int result = obj1.x + obj2.y;
+    try {
+      if (boolean_other_throw) {
+        throw new Error();
+      }
+    } catch (Error e) {
+      return 0;
+    }
+    return result;
+  }
+
+  /// CHECK-START: int Main.$noinline$testRedundantStore(Point, boolean, boolean) load_store_elimination (before)
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldGet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testRedundantStore(Point, boolean, boolean) load_store_elimination (after)
+  /// CHECK:         InstanceFieldSet field_name:Point.y
+  /// CHECK-NOT:     InstanceFieldSet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testRedundantStore(Point, boolean, boolean) load_store_elimination (after)
+  /// CHECK-NOT:     InstanceFieldGet field_name:Point.y
+
+  // Consistency check to make sure the try/catches weren't removed by an earlier pass.
+  /// CHECK-START: int Main.$noinline$testRedundantStore(Point, boolean, boolean) load_store_elimination (after)
+  /// CHECK-DAG:     TryBoundary kind:entry
+  /// CHECK-DAG:     TryBoundary kind:entry
+
+  // Redundant store of the same value.
+  private static int $noinline$testRedundantStore(
+      Point obj, boolean boolean_throw, boolean boolean_other_throw) {
+    try {
+      if (boolean_throw) {
+        throw new Error();
+      }
+    } catch (Error e) {
+      return 0;
+    }
+    obj.y = 1;
+    obj.y = 1;
+    try {
+      if (boolean_other_throw) {
+        throw new Error();
+      }
+    } catch (Error e) {
+      return 0;
+    }
+    return obj.y;
+  }
+
+  /// CHECK-START: int Main.$noinline$testTryCatchBlocking(Point, boolean) load_store_elimination (before)
+  /// CHECK: InstanceFieldSet field_name:Point.y
+  /// CHECK: InstanceFieldGet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testTryCatchBlocking(Point, boolean) load_store_elimination (after)
+  /// CHECK: InstanceFieldSet field_name:Point.y
+  /// CHECK: InstanceFieldGet field_name:Point.y
+
+  // Consistency check to make sure the try/catch wasn't removed by an earlier pass.
+  /// CHECK-START: int Main.$noinline$testTryCatchBlocking(Point, boolean) load_store_elimination (after)
+  /// CHECK: TryBoundary kind:entry
+
+  // We cannot remove the Get since we might have thrown.
+  private static int $noinline$testTryCatchBlocking(Point obj, boolean boolean_throw) {
+    obj.y = 1;
+    try {
+      if (boolean_throw) {
+        throw new Error();
+      }
+    } catch (Error e) {
+    }
+    return obj.y;
+  }
+
+  /// CHECK-START: int Main.$noinline$testTryCatchPhi(Point, boolean) load_store_elimination (before)
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldGet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testTryCatchPhi(Point, boolean) load_store_elimination (after)
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testTryCatchPhi(Point, boolean) load_store_elimination (after)
+  /// CHECK-NOT:     InstanceFieldGet field_name:Point.y
+
+  // Consistency check to make sure the try/catch wasn't removed by an earlier pass.
+  /// CHECK-START: int Main.$noinline$testTryCatchPhi(Point, boolean) load_store_elimination (after)
+  /// CHECK-DAG:     TryBoundary kind:entry
+
+  // We either threw and we set the value in the catch, or we didn't throw and we set the value
+  // before the catch. We can solve that with a Phi and skip the get.
+  private static int $noinline$testTryCatchPhi(Point obj, boolean boolean_throw) {
+    obj.y = 1;
+    try {
+      if (boolean_throw) {
+        throw new Error();
+      }
+    } catch (Error e) {
+      obj.y = 2;
+    }
+    return obj.y;
+  }
+
+
+  /// CHECK-START: int Main.$noinline$testTryCatchPhiWithTwoCatches(Point, int[]) load_store_elimination (before)
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldGet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testTryCatchPhiWithTwoCatches(Point, int[]) load_store_elimination (after)
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+  /// CHECK-DAG:     InstanceFieldSet field_name:Point.y
+
+  /// CHECK-START: int Main.$noinline$testTryCatchPhiWithTwoCatches(Point, int[]) load_store_elimination (after)
+  /// CHECK-NOT: InstanceFieldGet field_name:Point.y
+
+  // Consistency check to make sure the try/catch wasn't removed by an earlier pass.
+  /// CHECK-START: int Main.$noinline$testTryCatchPhiWithTwoCatches(Point, int[]) load_store_elimination (after)
+  /// CHECK-DAG: TryBoundary kind:entry
+  private static int $noinline$testTryCatchPhiWithTwoCatches(Point obj, int[] numbers) {
+    obj.y = 1;
+    try {
+      if (numbers[0] == 1) {
+        throw new Error();
+      }
+    } catch (ArrayIndexOutOfBoundsException e) {
+      obj.y = 2;
+    } catch (Error e) {
+      obj.y = 3;
+    }
+    return obj.y;
+  }
+
+  private static void assertEquals(int expected, int actual) {
+    if (expected != actual) {
+      throw new AssertionError("Expected: " + expected + ", Actual: " + actual);
+    }
+  }
+}
diff --git a/test/639-checker-code-sinking/src/Main.java b/test/639-checker-code-sinking/src/Main.java
index 743d501..5e465f2 100644
--- a/test/639-checker-code-sinking/src/Main.java
+++ b/test/639-checker-code-sinking/src/Main.java
@@ -679,6 +679,11 @@
       }
     } catch (NullPointerException e) {
     }
+
+    // We want to use obj over here so that it doesn't get deleted by LSE.
+    if (obj.x == 123) {
+      return 123;
+    }
     return x;
   }