Update side effect analysis for InvokeNewArray instruction

Bug: 135918413, 129530569
Change-Id: I283f1cea16d2c2d89599425d1d60017a8ad1fc0d
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index f65a4b3..6e90c5f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -244,6 +244,10 @@
     return isPrimitiveType((char) descriptor.content[1]);
   }
 
+  public boolean isWideType() {
+    return isDoubleType() || isLongType();
+  }
+
   public boolean isD8R8SynthesizedClassType() {
     String name = toSourceString();
     return name.contains(COMPANION_CLASS_NAME_SUFFIX)
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 797f2ef..4941c86 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -3,13 +3,17 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import static com.android.tools.r8.optimize.MemberRebindingAnalysis.isTypeVisibleFromContext;
+
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.code.FilledNewArray;
 import com.android.tools.r8.code.FilledNewArrayRange;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.AbstractError;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -129,4 +133,64 @@
   private static Unreachable cfUnsupported() {
     throw new Unreachable("InvokeNewArray (non-empty) not supported when compiling to classfiles.");
   }
+
+  @Override
+  public AbstractError instructionInstanceCanThrow(AppView<?> appView, DexType context) {
+    DexType baseType = type.isArrayType() ? type.toBaseType(appView.dexItemFactory()) : type;
+    if (baseType.isPrimitiveType()) {
+      // Primitives types are known to be present and accessible.
+      assert !type.isWideType() : "The array's contents must be single-word";
+      return AbstractError.bottom();
+    }
+
+    assert baseType.isReferenceType();
+
+    if (baseType == context) {
+      // The enclosing type is known to be present and accessible.
+      return AbstractError.bottom();
+    }
+
+    if (!appView.enableWholeProgramOptimizations()) {
+      // Conservatively bail-out in D8, because we require whole program knowledge to determine if
+      // the type is present and accessible.
+      return AbstractError.top();
+    }
+
+    // Check if the type is guaranteed to be present.
+    DexClass clazz = appView.definitionFor(baseType);
+    if (clazz == null) {
+      return AbstractError.top();
+    }
+
+    if (clazz.isLibraryClass()) {
+      if (!appView.dexItemFactory().libraryTypesAssumedToBePresent.contains(baseType)) {
+        return AbstractError.top();
+      }
+    }
+
+    // Check if the type is guaranteed to be accessible.
+    if (!isTypeVisibleFromContext(appView, context, clazz)) {
+      return AbstractError.top();
+    }
+
+    // Note: Implicitly assuming that all the arguments are of the right type, because the input
+    // code must be valid.
+    return AbstractError.bottom();
+  }
+
+  @Override
+  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    // Check if the instruction has a side effect on the locals environment.
+    if (hasOutValue() && outValue().hasLocalInfo()) {
+      assert appView.options().debug;
+      return true;
+    }
+
+    return instructionInstanceCanThrow(appView, context).isThrowing();
+  }
+
+  @Override
+  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+    return !instructionMayHaveSideEffects(appView, code.method.method.holder);
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index 83b6830..fde5ca7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -806,7 +806,28 @@
 
   public boolean isConstantArray() {
     Value root = getAliasedValue();
-    return !root.isPhi() && definition.isNewArrayEmpty();
+    if (!root.isPhi()) {
+      if (definition.isNewArrayEmpty()) {
+        // For now, simply check if the created array is empty.
+        NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
+        Value sizeValue = newArrayEmpty.size().getAliasedValue();
+        if (!sizeValue.hasValueRange()) {
+          return false;
+        }
+
+        LongInterval sizeRange = sizeValue.getValueRange();
+        if (!sizeRange.isSingleValue()) {
+          return false;
+        }
+
+        long size = sizeRange.getSingleValue();
+        if (size == 0) {
+          // Empty arrays are always constant.
+          return true;
+        }
+      }
+    }
+    return false;
   }
 
   public boolean isPhi() {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index c3831eb..557d4fc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -382,10 +382,17 @@
               // OK, this does not read one of the fields in the enclosing class.
               continue;
             }
-            if (instruction.isInvoke() && instruction.asInvoke().outValue() != null) {
-              // This invoke could return a value that has been computed based on the value of one
-              // of the fields in the enclosing class, so give up.
-              return finalFieldPut.values();
+            // Give up if this invoke-instruction may return a value that has been computed based on
+            // the value of one of the fields in the enclosing class.
+            if (instruction.isInvoke() && instruction.hasOutValue()) {
+              Value outValue = instruction.outValue();
+              if (outValue.numberOfAllUsers() > 0) {
+                if (instruction.isInvokeNewArray() && outValue.isConstantArray()) {
+                  // OK, this value is technically a constant.
+                  continue;
+                }
+                return finalFieldPut.values();
+              }
             }
           }
         }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 64773de..2ba89ba 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -314,15 +314,22 @@
     }
   }
 
+  public static boolean isTypeVisibleFromContext(
+      AppView<?> appView, DexType context, DexType type) {
+    DexClass clazz = appView.definitionFor(type);
+    return clazz != null && isTypeVisibleFromContext(appView, context, clazz);
+  }
+
+  public static boolean isTypeVisibleFromContext(
+      AppView<?> appView, DexType context, DexClass clazz) {
+    ConstraintWithTarget classVisibility =
+        ConstraintWithTarget.deriveConstraint(context, clazz.type, clazz.accessFlags, appView);
+    return classVisibility != ConstraintWithTarget.NEVER;
+  }
+
   public static boolean isMemberVisibleFromOriginalContext(
       AppView<?> appView, DexType context, DexType holder, AccessFlags<?> memberAccessFlags) {
-    DexClass clazz = appView.definitionFor(holder);
-    if (clazz == null) {
-      return false;
-    }
-    ConstraintWithTarget classVisibility =
-        ConstraintWithTarget.deriveConstraint(context, holder, clazz.accessFlags, appView);
-    if (classVisibility == ConstraintWithTarget.NEVER) {
+    if (!isTypeVisibleFromContext(appView, context, holder)) {
       return false;
     }
     ConstraintWithTarget memberVisibility =
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
index 34bad33..071936d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/B135918413.java
@@ -18,6 +18,7 @@
 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.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -37,6 +38,7 @@
     this.parameters = parameters;
   }
 
+  @Ignore("b/135918413")
   @Test
   public void test() throws Exception {
     testForR8(parameters.getBackend())
@@ -60,6 +62,10 @@
     FieldSubject alwaysEmptyFieldSubject = configClassSubject.uniqueFieldWithName("alwaysEmpty");
     assertThat(alwaysEmptyFieldSubject, isPresent());
 
+    FieldSubject alwaysNonEmptyFieldSubject =
+        configClassSubject.uniqueFieldWithName("alwaysNonEmpty");
+    assertThat(alwaysNonEmptyFieldSubject, isPresent());
+
     MethodSubject mainMethodSubject = classSubject.mainMethod();
     assertThat(mainMethodSubject, isPresent());
     assertTrue(
@@ -67,10 +73,12 @@
             .streamInstructions()
             .filter(InstructionSubject::isStaticGet)
             .map(InstructionSubject::getField)
+            .map(field -> field.name.toSourceString())
             .allMatch(
-                field ->
-                    field.name.toSourceString().equals(alwaysEmptyFieldSubject.getFinalName())
-                        || field.name.toSourceString().equals("out")));
+                name ->
+                    name.equals(alwaysEmptyFieldSubject.getFinalName())
+                        || name.equals(alwaysNonEmptyFieldSubject.getFinalName())
+                        || name.equals("out")));
 
     MethodSubject deadMethodSubject = classSubject.uniqueMethodWithName("dead");
     assertThat(deadMethodSubject, not(isPresent()));
@@ -85,8 +93,12 @@
         dead();
       }
       if (Config.alwaysEmpty.length == 0) {
-        System.out.println(" world!");
+        System.out.print(" world");
       }
+      for (String str : Config.alwaysNonEmpty) {
+        System.out.print(str);
+      }
+      System.out.println();
     }
 
     @NeverInline
@@ -99,5 +111,6 @@
 
     public static boolean alwaysTrue = true;
     public static String[] alwaysEmpty = {};
+    public static String[] alwaysNonEmpty = {"!"};
   }
 }