Handle throwing array get/put in redundand field load/store elimination

Fixes: b/344363462
Change-Id: I44cb43c51fcfa9a21fd480c4543951c96547388a
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
index 537b313..f409e03 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/RedundantFieldLoadAndStoreElimination.java
@@ -434,9 +434,7 @@
               // instruction has side-effects that can change the value of fields. If so, it must be
               // handled above. If not, it can be safely added to the assert.
               assert instruction.isArgument()
-                      || instruction.isArrayGet()
                       || instruction.isArrayLength()
-                      || instruction.isArrayPut()
                       || instruction.isAssume()
                       || instruction.isBinop()
                       || instruction.isCheckCast()
@@ -630,6 +628,10 @@
     }
 
     private void handleArrayGet(InstructionListIterator it, ArrayGet arrayGet) {
+      if (arrayGet.instructionInstanceCanThrow(appView, method)) {
+        // The read might not happen if the array get can throw.
+        activeState.clearMostRecentFieldWrites();
+      }
       if (arrayGet.array().hasLocalInfo()) {
         // The array may be modified through the debugger. Therefore subsequent reads of the same
         // array slot may not read this local.
@@ -659,6 +661,12 @@
       int index = arrayPut.indexOrDefault(-1);
       MemberType memberType = arrayPut.getMemberType();
 
+      // If the instruction can throw, we can't use any previous field stores for store-after-store
+      // elimination.
+      if (arrayPut.instructionInstanceCanThrow(appView, method)) {
+        activeState.clearMostRecentFieldWrites();
+      }
+
       // An array-put instruction can potentially write the given array slot on all arrays because
       // of
       // aliases.
diff --git a/src/test/java/com/android/tools/r8/B344363462Test.java b/src/test/java/com/android/tools/r8/B344363462Test.java
deleted file mode 100644
index bba41ed..0000000
--- a/src/test/java/com/android/tools/r8/B344363462Test.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2024, 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;
-
-import com.android.tools.r8.utils.StringUtils;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@RunWith(Parameterized.class)
-public class B344363462Test extends TestBase {
-
-  @Parameter() public TestParameters parameters;
-
-  @Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
-  }
-
-  private static final String EXPECTED_OUTPUT = StringUtils.lines("-225");
-  private static final String UNEXPECTED_OUTPUT = StringUtils.lines("0");
-
-  @Test
-  public void testD8() throws Exception {
-    parameters.assumeDexRuntime();
-    testForD8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(EXPECTED_OUTPUT);
-  }
-
-  @Test
-  public void testD8Release() throws Exception {
-    parameters.assumeDexRuntime();
-    testForD8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .setMinApi(parameters)
-        .release()
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(UNEXPECTED_OUTPUT);
-  }
-
-  @Test
-  public void testR8() throws Exception {
-    testForR8(parameters.getBackend())
-        .addInnerClasses(getClass())
-        .addKeepMainRule(TestClass.class)
-        .setMinApi(parameters)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutput(UNEXPECTED_OUTPUT);
-  }
-
-  static class TestClass {
-
-    int iFld1;
-
-    void t() {
-      int i5 = 61127, i7 = 42011;
-      long[] lArr = new long[10];
-      try {
-        iFld1 -= 225L;
-        lArr[i5] = i7;
-        iFld1 = i7;
-      } catch (ArrayIndexOutOfBoundsException err) {
-      }
-      System.out.println(iFld1);
-    }
-
-    public static void main(String[] strArr) {
-      TestClass test = new TestClass();
-      test.t();
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java b/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java
new file mode 100644
index 0000000..de5ba07
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/redundantarraygetelimination/RedundantArrayGetEliminationWithThrowingArrayAccessTest.java
@@ -0,0 +1,278 @@
+// Copyright (c) 2024, 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.redundantarraygetelimination;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+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.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RedundantArrayGetEliminationWithThrowingArrayAccessTest extends TestBase {
+
+  @Parameter() public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("-1", "-3", "-6", "-10", "42011", "9", "42010", "8", "13");
+
+  @Test
+  public void testD8() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private void countInstanceGetOfField(int count, MethodSubject method, FieldSubject field) {
+    assertThat(method, isPresentAndRenamed());
+    assertThat(field, isPresentAndRenamed());
+    assertEquals(
+        count,
+        method
+            .streamInstructions()
+            .filter(InstructionSubject::isInstancePut)
+            .map(InstructionSubject::getField)
+            .filter(f -> f.isIdenticalTo(field.getDexField()))
+            .count());
+  }
+
+  private void zeroArrayGet(MethodSubject method) {
+    countArrayGet(0, method);
+  }
+
+  private void oneArrayGet(MethodSubject method) {
+    countArrayGet(1, method);
+  }
+
+  private void countArrayGet(int count, MethodSubject method) {
+    assertThat(method, isPresentAndRenamed());
+    assertEquals(count, method.streamInstructions().filter(InstructionSubject::isArrayGet).count());
+  }
+
+  private void oneInstanceGetOfField(MethodSubject method, FieldSubject field) {
+    countInstanceGetOfField(1, method, field);
+  }
+
+  private void twoInstanceGetOfField(MethodSubject method, FieldSubject field) {
+    countInstanceGetOfField(2, method, field);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClass = inspector.clazz(TestClass.class);
+    assertThat(testClass, isPresentAndNotRenamed());
+    FieldSubject iFld1 = testClass.uniqueFieldWithOriginalName("iFld1");
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutThrows"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutThrows"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetThrows"), iFld1);
+    oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetThrows"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutThrows"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutThrows"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetThrows"), iFld1);
+    oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetThrows"));
+    oneInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutSucceeds"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizePutSucceeds"));
+    oneInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetSucceeds"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfKnownSizeGetSucceeds"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutSucceeds"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutSucceeds"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetSucceeds"), iFld1);
+    oneArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizeGetSucceeds"));
+    twoInstanceGetOfField(
+        testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutAndGetSucceeds"), iFld1);
+    zeroArrayGet(testClass.uniqueMethodWithOriginalName("arrayOfUnknownSizePutAndGetSucceeds"));
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    parameters.assumeDexRuntime();
+    testForD8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters)
+        .release()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .setMinApi(parameters)
+        .enableInliningAnnotations()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT)
+        .inspect(this::inspect);
+  }
+
+  static class TestClass {
+
+    int iFld1;
+
+    long[] array() {
+      return new long[System.currentTimeMillis() > 0 ? 10 : 20];
+    }
+
+    @NeverInline
+    void arrayOfKnownSizePutThrows() {
+      int i5 = 61127, i7 = 42011;
+      long[] lArr = new long[10];
+      try {
+        iFld1 -= 1L;
+        lArr[i5] = i7;
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfKnownSizeGetThrows() {
+      int i5 = 61127, i7 = 42011;
+      long[] lArr = new long[10];
+      try {
+        iFld1 -= 2L;
+        long x = lArr[i5];
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizePutThrows() {
+      int i5 = 61127, i7 = 42011;
+      long[] lArr = array();
+      try {
+        iFld1 -= 3L;
+        lArr[i5] = i7;
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizeGetThrows() {
+      int i5 = 61127, i7 = 42011;
+      long[] lArr = array();
+      try {
+        iFld1 -= 4L;
+        long x = lArr[i5];
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfKnownSizePutSucceeds() {
+      int i5 = 9, i7 = 42011;
+      long[] lArr = new long[10];
+      try {
+        iFld1 -= 5L;
+        lArr[i5] = i7;
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfKnownSizeGetSucceeds() {
+      int i5 = 9, i7 = 42011;
+      long[] lArr = new long[10];
+      try {
+        iFld1 -= 6L;
+        long x = lArr[i5];
+        iFld1 = i5;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizePutSucceeds() {
+      int i5 = 9, i7 = 42010;
+      long[] lArr = array();
+      try {
+        iFld1 -= 225L;
+        lArr[i5] = i7;
+        iFld1 = i7;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizeGetSucceeds() {
+      int i5 = 8, i7 = 42011;
+      long[] lArr = array();
+      try {
+        iFld1 -= 225L;
+        long x = lArr[i5];
+        iFld1 = i5;
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    @NeverInline
+    void arrayOfUnknownSizePutAndGetSucceeds() {
+      int i5 = 0, i7 = 13;
+      long[] lArr = array();
+      try {
+        iFld1 -= 5L;
+        lArr[i5] = i7;
+        iFld1 = (int) lArr[i5];
+      } catch (ArrayIndexOutOfBoundsException err) {
+      }
+      System.out.println(iFld1);
+    }
+
+    public static void main(String[] strArr) {
+      TestClass test = new TestClass();
+      test.arrayOfKnownSizePutThrows();
+      test.arrayOfKnownSizeGetThrows();
+      test.arrayOfUnknownSizePutThrows();
+      test.arrayOfUnknownSizeGetThrows();
+      test.arrayOfKnownSizePutSucceeds();
+      test.arrayOfKnownSizeGetSucceeds();
+      test.arrayOfUnknownSizePutSucceeds();
+      test.arrayOfUnknownSizeGetSucceeds();
+      test.arrayOfUnknownSizePutAndGetSucceeds();
+    }
+  }
+}