Relax dead condition of instance-put.

by implementing instructionMayHaveSideEffects.

This is a variant of https://r8-review.googlesource.com/c/r8/+/37480

Bug: 129530569
Change-Id: I5ec4e83342de56ee4a03977a39d91d31d1b042bb
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 22fddb0..9cea0c0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
@@ -27,6 +28,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import java.util.Arrays;
 import org.objectweb.asm.Opcodes;
 
@@ -96,6 +98,43 @@
   }
 
   @Override
+  public boolean instructionMayHaveSideEffects(AppView<?> appView, DexType context) {
+    if (appView.appInfo().hasLiveness()) {
+      AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
+
+      if (instructionInstanceCanThrow(appView, context).isThrowing()) {
+        return true;
+      }
+
+      DexEncodedField resolveField = appInfoWithLiveness.resolveField(getField());
+      assert resolveField != null : "NoSuchFieldError (resolution failure) should be caught.";
+      if (appInfoWithLiveness.isFieldRead(resolveField.field)) {
+        return true;
+      }
+
+      return false;
+    }
+
+    // In D8, we always have to assume that the field can be read, and thus have side effects.
+    assert instructionInstanceCanThrow(appView, context).isThrowing();
+    return true;
+  }
+
+  @Override
+  public boolean canBeDeadCode(AppView<?> appView, IRCode code) {
+    // instance-put can be dead as long as it cannot have any of the following:
+    // * NoSuchFieldError (resolution failure)
+    // * IncompatibleClassChangeError (static-* instruction for instance fields)
+    // * IllegalAccessError (not visible from the access context)
+    // * NullPointerException (null receiver)
+    // * not read at all
+    boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.method.holder);
+    assert appView.enableWholeProgramOptimizations() || haveSideEffects
+        : "Expected instance-put instruction to have side effects in D8";
+    return !haveSideEffects;
+  }
+
+  @Override
   public boolean identicalAfterRegisterAllocation(Instruction other, RegisterAllocator allocator) {
     if (!super.identicalAfterRegisterAllocation(other, allocator)) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index eb000ec..d8df7f1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
@@ -107,7 +108,9 @@
         return true;
       }
 
-      if (appInfoWithLiveness.isFieldRead(getField())) {
+      DexEncodedField resolveField = appInfoWithLiveness.resolveField(getField());
+      assert resolveField != null : "NoSuchFieldError (resolution failure) should be caught.";
+      if (appInfoWithLiveness.isFieldRead(resolveField.field)) {
         return true;
       }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index b1e36a0..6f9da7a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -26,10 +26,8 @@
 import com.android.tools.r8.ir.code.ConstInstruction;
 import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.ConstString;
-import com.android.tools.r8.ir.code.FieldInstruction;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.Position;
@@ -104,8 +102,10 @@
       replacement = createConstNumberReplacement(
           code, rule.getReturnValue().getSingleValue(), typeLattice, instruction.getLocalInfo());
     }
-    if (replacement == null && rule != null
-        && rule.hasReturnValue() && rule.getReturnValue().isField()) {
+    if (replacement == null
+        && rule != null
+        && rule.hasReturnValue()
+        && rule.getReturnValue().isField()) {
       DexField field = rule.getReturnValue().getField();
       assert typeLattice
           == TypeLatticeElement.fromDexType(field.type, Nullability.maybeNull(), appView);
@@ -329,30 +329,6 @@
     }
   }
 
-  private void rewritePutWithConstantValues(
-      InstructionIterator iterator, FieldInstruction current, DexType context) {
-    DexField field = current.getField();
-    // TODO(b/123857022): Should be possible to use definitionFor().
-    DexEncodedField target =
-        current.isInstancePut()
-            ? appView.appInfo().lookupInstanceTarget(field.holder, field)
-            : appView.appInfo().lookupStaticTarget(field.holder, field);
-    if (target == null) {
-      return;
-    }
-
-    if (target.field.holder.classInitializationMayHaveSideEffects(
-        appView, type -> appView.appInfo().isSubtype(context, type))) {
-      return;
-    }
-
-    // TODO(b/123857022): Should be possible to use `!isFieldRead(field)`.
-    if (!appView.appInfo().isFieldRead(target.field)) {
-      // Remove writes to dead (i.e. never read) fields.
-      iterator.removeOrReplaceByDebugLocalRead();
-    }
-  }
-
   private void insertAssumeNotNull(
       IRCode code,
       Set<Value> affectedValues,
@@ -395,8 +371,6 @@
         if (current.isInvokeMethod()) {
           rewriteInvokeMethodWithConstantValues(
               code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
-        } else if (current.isInstancePut()) {
-          rewritePutWithConstantValues(iterator, current.asFieldInstruction(), callingContext);
         } else if (current.isStaticGet()) {
           rewriteStaticGetWithConstantValues(
               code,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index c6402bd..bb36308 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -3,18 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.Iterator;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -49,11 +46,9 @@
     ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME);
     clazz.forAllMethods(
         methodSubject -> {
-          Iterator<InstructionSubject> iterator = methodSubject.iterateInstructions();
-          while (iterator.hasNext()) {
-            InstructionSubject instruction = iterator.next();
-            assertFalse(instruction.isInstancePut() || instruction.isStaticPut());
-          }
+          assertTrue(
+              methodSubject.streamInstructions().noneMatch(
+                  i -> i.isInstancePut() || i.isStaticPut()));
         });
   }
 
@@ -63,26 +58,11 @@
     ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME);
     clazz.forAllMethods(
         methodSubject -> {
-          Iterator<InstructionSubject> iterator = methodSubject.iterateInstructions();
-          if (methodSubject.isClassInitializer()) {
-            int numPuts = 0;
-            while (iterator.hasNext()) {
-              if (iterator.next().isStaticPut()) {
-                ++numPuts;
-              }
-            }
-            // dead code removal is not part of -dontoptimize.
-            assertEquals(0, numPuts);
-          }
-          if (methodSubject.isInstanceInitializer()) {
-            int numPuts = 0;
-            while (iterator.hasNext()) {
-              if (iterator.next().isInstancePut()) {
-                ++numPuts;
-              }
-            }
-            assertEquals(1, numPuts);
-          }
+          // Dead code removal is not part of -dontoptimize. That is, even with -dontoptimize,
+          // field put instructions are gone with better dead code removal.
+          assertTrue(
+              methodSubject.streamInstructions().noneMatch(
+                  i -> i.isInstancePut() || i.isStaticPut()));
         });
   }
 
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index f02109d..8d5db8c 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -51,6 +51,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
         "-keep class " + CLASS_NAME,
+        "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
         "-dontoptimize");
     CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
@@ -83,6 +84,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
         "-keep class " + CLASS_NAME,
+        "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
         "-dontoptimize");
     CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
@@ -124,6 +126,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { java.lang.String aClassName; }",
         "-keep class " + CLASS_NAME,
+        "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { !static <fields>; }",
         "-keep,allowobfuscation class " + BOO,
         "-dontoptimize");
     CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);