Remove dead array initializations

When we can guarantee that a NewArray and NewArrayFilledData will not throw and is not used, remove them.

Bug: 122683370
Change-Id: I64e47d7adecb889c8181b3c03f66fc1ed9877472
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index c010973..e235682 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -64,6 +64,24 @@
   }
 
   @Override
+  public boolean instructionInstanceCanThrow() {
+    return !(size().definition != null
+        && size().definition.isConstNumber()
+        && size().definition.asConstNumber().getRawValue() >= 0
+        && size().definition.asConstNumber().getRawValue() < Integer.MAX_VALUE);
+  }
+
+  @Override
+  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+    if (instructionInstanceCanThrow()) {
+      return false;
+    }
+    // This would belong better in instructionInstanceCanThrow, but that is not passed an appInfo.
+    DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    return baseType.isPrimitiveType() || appInfo.definitionFor(baseType) != null;
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isNewArrayEmpty() && other.asNewArrayEmpty().type == type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index ed51913..f534fb9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.code.FillArrayDataPayload;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -73,6 +74,19 @@
   }
 
   @Override
+  public boolean canBeDeadCode(AppInfo appInfo, IRCode code) {
+    if (!src().getTypeLattice().isNullable() && src().numberOfAllUsers() == 1) {
+      // The NewArrayFilledData instruction is only inserted by an R8 optimization following
+      // a NewArrayEmpty when there are more than one entry.
+      assert src().uniqueUsers().iterator().next() == this;
+      assert src().definition != null;
+      assert src().definition.isNewArrayEmpty();
+      return true;
+    }
+    return false;
+  }
+
+  @Override
   public boolean instructionTypeCanThrow() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index be171c9..fa259c2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -115,16 +115,15 @@
         continue;
       }
       Value outValue = current.outValue();
-      // Instructions with no out value cannot be dead code by the current definition
-      // (unused out value). They typically side-effect input values or deals with control-flow.
-      assert outValue != null;
-      if (!outValue.isDead(appInfo)) {
+      if (outValue != null && !outValue.isDead(appInfo)) {
         continue;
       }
       updateWorklist(worklist, current);
       // All users will be removed for this instruction. Eagerly clear them so further inspection
       // of this instruction during dead code elimination will terminate here.
-      outValue.clearUsers();
+      if (outValue != null) {
+        outValue.clearUsers();
+      }
       iterator.removeOrReplaceByDebugLocalRead();
     }
   }
diff --git a/src/test/examples/classmerging/ArrayTypeCollisionTest.java b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
index 9e4c9fd..c422946 100644
--- a/src/test/examples/classmerging/ArrayTypeCollisionTest.java
+++ b/src/test/examples/classmerging/ArrayTypeCollisionTest.java
@@ -12,11 +12,11 @@
   }
 
   private static void method(A[] obj) {
-    System.out.println("In method(A[])");
+    System.out.println("In method(A[]), length: " + obj.length);
   }
 
   private static void method(B[] obj) {
-    System.out.println("In method(B[])");
+    System.out.println("In method(B[]), length: " + obj.length);
   }
 
   // A cannot be merged into B because that would lead to a collision.
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 2284fa5..4e01188 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -209,7 +209,8 @@
             "  public static void main(...);",
             "}",
             "-neverinline class " + main + " {",
-            "  static void method(...);",
+            "  static classmerging.A[] method(...);",
+            "  static classmerging.B[] method(...);",
             "}"));
   }
 
diff --git a/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
new file mode 100644
index 0000000..5de7de0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/deadcode/RemoveDeadArray.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2019, 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.deadcode;
+
+import static junit.framework.TestCase.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.code.Aput;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class RemoveDeadArray extends TestBase {
+
+  public static class TestClassWithCatch {
+    static {
+      try {
+        int[] foobar = new int[]{42, 42, 42, 42};
+      } catch (Exception ex) {
+        System.out.println("foobar");
+      }
+    }
+  }
+
+  public static class TestClass {
+    private static int[] foobar = new int[]{42, 42, 42, 42};
+    public static void main(java.lang.String[] args) {
+      ShouldGoAway[] willBeRemoved = new ShouldGoAway[4];
+      ShouldGoAway[] willAlsoBeRemoved = new ShouldGoAway[0];
+      System.out.println("foobar");
+    }
+
+    public static class ShouldGoAway { }
+  }
+
+  private Backend backend;
+
+  public RemoveDeadArray(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    // Todo(ricow): enable unused array removal for cf backend.
+    return new Backend[]{Backend.DEX};
+  }
+
+
+  @Test
+  public void testDeadArraysRemoved() throws Exception {
+    R8TestCompileResult result =
+        testForR8(backend)
+            .addProgramClasses(TestClass.class, TestClass.ShouldGoAway.class)
+            .addKeepMainRule(TestClass.class)
+            .compile();
+    CodeInspector inspector = result.inspector();
+    assertFalse(inspector.clazz(TestClass.class).clinit().isPresent());
+
+    MethodSubject main = inspector.clazz(TestClass.class).mainMethod();
+    main.streamInstructions().noneMatch(instructionSubject -> instructionSubject.isNewArray());
+    assertFalse(main.getMethod().getCode().asDexCode().toString().contains("NewArray"));
+    runOnArt(result.app, TestClass.class.getName());
+ }
+
+  @Test
+  public void testNotRemoveStaticCatch() throws Exception {
+    R8TestCompileResult result =
+        testForR8(backend)
+            .addProgramClasses(TestClassWithCatch.class)
+            .addKeepAllClassesRule()
+            .compile();
+    CodeInspector inspector = result.inspector();
+    MethodSubject clinit = inspector.clazz(TestClassWithCatch.class).clinit();
+    assertTrue(clinit.isPresent());
+    // Ensure that our optimization does not hit, we should still have 4 Aput instructions.
+    long aPutCount = Arrays.stream(clinit.getMethod().getCode().asDexCode().instructions)
+        .filter(instruction -> instruction instanceof  Aput)
+        .count();
+    assertEquals(4, aPutCount);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 924ec2d..7316d04 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.cf.code.CfLoad;
 import com.android.tools.r8.cf.code.CfMonitor;
 import com.android.tools.r8.cf.code.CfNew;
+import com.android.tools.r8.cf.code.CfNewArray;
 import com.android.tools.r8.cf.code.CfNop;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfReturn;
@@ -277,6 +278,11 @@
   }
 
   @Override
+  public boolean isNewArray() {
+    return instruction instanceof CfNewArray;
+  }
+
+  @Override
   public int size() {
     // TODO(b/122302789): CfInstruction#getSize()
     throw new UnsupportedOperationException("CfInstruction doesn't have size yet.");
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 6d463da..d48e81a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -67,6 +67,7 @@
 import com.android.tools.r8.code.MulIntLit8;
 import com.android.tools.r8.code.MulLong;
 import com.android.tools.r8.code.MulLong2Addr;
+import com.android.tools.r8.code.NewArray;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.Nop;
 import com.android.tools.r8.code.PackedSwitch;
@@ -354,6 +355,11 @@
   }
 
   @Override
+  public boolean isNewArray() {
+    return instruction instanceof NewArray;
+  }
+
+  @Override
   public boolean isMonitorEnter() {
     return instruction instanceof MonitorEnter;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index 6abeacf..b417c68 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -80,6 +80,8 @@
 
   boolean isMultiplication();
 
+  boolean isNewArray();
+
   boolean isMonitorEnter();
 
   boolean isMonitorExit();