Leverage member value propagation for optimizing null-valued fields

Bug: 150269949
Change-Id: Id4ce1c4b419dbceb5bdc0e6d4979701fa4f12fec
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 73395b2..7a85dc8 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -42,6 +42,10 @@
     this.staticValue = staticValue;
   }
 
+  public DexType type() {
+    return field.type;
+  }
+
   public boolean isProgramField(DexDefinitionSupplier definitions) {
     if (field.holder.isClassType()) {
       DexClass clazz = definitions.definitionFor(field.holder);
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 115afec..b8a5787 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
@@ -128,6 +128,11 @@
       DexEncodedField encodedField =
           appInfoWithLiveness.resolveField(getField()).getResolvedField();
       assert encodedField != null : "NoSuchFieldError (resolution failure) should be caught.";
+
+      if (encodedField.type().isAlwaysNull(appViewWithLiveness)) {
+        return false;
+      }
+
       return appInfoWithLiveness.isFieldRead(encodedField)
           || isStoringObjectWithFinalizer(appViewWithLiveness, encodedField);
     }
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 678a569..36b6520 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
@@ -125,6 +125,10 @@
         return false;
       }
 
+      if (encodedField.type().isAlwaysNull(appViewWithLiveness)) {
+        return false;
+      }
+
       return appInfoWithLiveness.isFieldRead(encodedField)
           || isStoringObjectWithFinalizer(appViewWithLiveness, encodedField);
     }
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 8222958..51c1c01 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
@@ -357,13 +357,10 @@
       return;
     }
 
-    // Check if the field is pinned. In that case, it could be written by reflection.
-    if (appView.appInfo().isPinned(target.field)) {
-      return;
-    }
-
     AbstractValue abstractValue;
-    if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
+    if (field.type.isAlwaysNull(appView)) {
+      abstractValue = appView.abstractValueFactory().createSingleNumberValue(0);
+    } else if (appView.appInfo().isFieldWrittenByFieldPutInstruction(target)) {
       abstractValue = target.getOptimizationInfo().getAbstractValue();
       if (abstractValue.isUnknown() && !target.isStatic()) {
         AbstractValue abstractReceiverValue =
@@ -485,12 +482,15 @@
 
   private void replaceInstancePutByNullCheckIfNeverRead(
       IRCode code, InstructionListIterator iterator, InstancePut current) {
-    DexEncodedField target = appView.appInfo().resolveField(current.getField()).getResolvedField();
-    if (target == null || appView.appInfo().isFieldRead(target)) {
+    DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
+    if (field == null || field.isStatic()) {
       return;
     }
 
-    if (target.isStatic()) {
+    // If the field is read, we can't remove the instance-put unless the value of the field is known
+    // to be null (in which case the instance-put is a no-op because it assigns the field the same
+    // value as its default value).
+    if (!field.type().isAlwaysNull(appView) && appView.appInfo().isFieldRead(field)) {
       return;
     }
 
@@ -500,11 +500,14 @@
   private void replaceStaticPutByInitClassIfNeverRead(
       IRCode code, InstructionListIterator iterator, StaticPut current) {
     DexEncodedField field = appView.appInfo().resolveField(current.getField()).getResolvedField();
-    if (field == null || appView.appInfo().isFieldRead(field)) {
+    if (field == null || !field.isStatic()) {
       return;
     }
 
-    if (!field.isStatic()) {
+    // If the field is read, we can't remove the static-put unless the value of the field is known
+    // to be null (in which case the static-put is a no-op because it assigns the field the same
+    // value as its default value).
+    if (!field.type().isAlwaysNull(appView) && appView.appInfo().isFieldRead(field)) {
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 8d89c42..85f0274 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -9,9 +9,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -23,7 +21,6 @@
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.ArgumentInfoCollection;
 import com.android.tools.r8.graph.RewrittenPrototypeDescription.RemovedArgumentInfo;
 import com.android.tools.r8.graph.TopDownClassHierarchyTraversal;
-import com.android.tools.r8.ir.analysis.TypeChecker;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.FieldInstruction;
@@ -102,11 +99,9 @@
   private static final MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final TypeChecker typeChecker;
 
   public UninstantiatedTypeOptimization(AppView<AppInfoWithLiveness> appView) {
     this.appView = appView;
-    this.typeChecker = new TypeChecker(appView);
   }
 
   public UninstantiatedTypeOptimizationGraphLense run(
@@ -351,14 +346,7 @@
             continue;
           }
         }
-        if (instruction.isFieldInstruction()) {
-          rewriteFieldInstruction(
-              instruction.asFieldInstruction(),
-              instructionIterator,
-              code,
-              assumeDynamicTypeRemover,
-              valuesToNarrow);
-        } else if (instruction.isInvokeMethod()) {
+        if (instruction.isInvokeMethod()) {
           rewriteInvoke(
               instruction.asInvokeMethod(),
               blockIterator,
@@ -401,57 +389,6 @@
     return true;
   }
 
-  // instance-{get|put} with a null receiver has already been rewritten to `throw null`.
-  // At this point, field-instruction whose target field type is uninstantiated will be handled.
-  private void rewriteFieldInstruction(
-      FieldInstruction instruction,
-      InstructionListIterator instructionIterator,
-      IRCode code,
-      AssumeDynamicTypeRemover assumeDynamicTypeRemover,
-      Set<Value> affectedValues) {
-    ProgramMethod context = code.context();
-    DexField field = instruction.getField();
-    DexType fieldType = field.type;
-    if (fieldType.isAlwaysNull(appView)) {
-      // TODO(b/123857022): Should be possible to use definitionFor().
-      DexEncodedField encodedField = appView.appInfo().resolveField(field).getResolvedField();
-      if (encodedField == null) {
-        return;
-      }
-
-      boolean instructionCanBeRemoved = !instruction.instructionInstanceCanThrow(appView, context);
-
-      BasicBlock block = instruction.getBlock();
-      if (instruction.isFieldPut()) {
-        if (!typeChecker.checkFieldPut(instruction)) {
-          // Broken type hierarchy. See FieldTypeTest#test_brokenTypeHierarchy.
-          assert appView.options().testing.allowTypeErrors;
-          return;
-        }
-
-        // We know that the right-hand side must be null, so this is a no-op.
-        if (instructionCanBeRemoved) {
-          instructionIterator.removeOrReplaceByDebugLocalRead();
-        }
-      } else {
-        if (instructionCanBeRemoved) {
-          // Replace the field read by the constant null.
-          assumeDynamicTypeRemover.markUsersForRemoval(instruction.outValue());
-          affectedValues.addAll(instruction.outValue().affectedValues());
-          instructionIterator.replaceCurrentInstruction(code.createConstNull());
-        } else {
-          replaceOutValueByNull(
-              instruction, instructionIterator, code, assumeDynamicTypeRemover, affectedValues);
-        }
-      }
-
-      if (block.hasCatchHandlers()) {
-        // This block can no longer throw.
-        block.getCatchHandlers().getUniqueTargets().forEach(BasicBlock::unlinkCatchHandler);
-      }
-    }
-  }
-
   // invoke instructions with a null receiver has already been rewritten to `throw null`.
   // At this point, we attempt to explore non-null-param-or-throw optimization info and replace
   // the invocation with `throw null` if an argument is known to be null and the method is going to