Leverage member value propagation for optimizing methods that return null

Bug: 150269949
Change-Id: I0984d9bb11ec688c9addea543401892e8582b1f8
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index f3a889f..a83c964 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -209,9 +209,12 @@
     assert appView.appInfo().hasLiveness();
     AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
-    ResolutionResult resolutionResult =
-        appViewWithLiveness.appInfo().resolveMethod(getInvokedMethod(), getInterfaceBit());
-    if (resolutionResult.isFailedResolution()) {
+    SingleResolutionResult resolutionResult =
+        appViewWithLiveness
+            .appInfo()
+            .resolveMethod(getInvokedMethod(), getInterfaceBit())
+            .asSingleResolution();
+    if (resolutionResult == null) {
       return true;
     }
 
@@ -222,6 +225,12 @@
       return true;
     }
 
+    DexEncodedMethod resolvedMethod = resolutionResult.getResolvedMethod();
+    if (appViewWithLiveness.appInfo().noSideEffects.containsKey(getInvokedMethod())
+        || appViewWithLiveness.appInfo().noSideEffects.containsKey(resolvedMethod.getReference())) {
+      return false;
+    }
+
     // Find the target and check if the invoke may have side effects.
     DexEncodedMethod target = lookupSingleTarget(appViewWithLiveness, context);
     if (target == null) {
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 51c1c01..8ca93eb 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
@@ -230,21 +230,23 @@
       Set<Value> affectedValues,
       ListIterator<BasicBlock> blocks,
       InstructionListIterator iterator,
-      InvokeMethod current) {
-    DexMethod invokedMethod = current.getInvokedMethod();
+      InvokeMethod invoke) {
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (invokedMethod.proto.returnType.isVoidType()) {
+      return;
+    }
+
+    if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
+      return;
+    }
+
     DexType invokedHolder = invokedMethod.holder;
     if (!invokedHolder.isClassType()) {
       return;
     }
-    DexEncodedMethod target = current.lookupSingleTarget(appView, context);
-    if (target != null && target.isInstanceInitializer()) {
-      // Member value propagation does not apply to constructors. Removing a call to a constructor
-      // that is marked as having no side effects could lead to verification errors, due to
-      // uninitialized instances being used.
-      return;
-    }
 
-    ProguardMemberRuleLookup lookup = lookupMemberRule(target);
+    DexEncodedMethod singleTarget = invoke.lookupSingleTarget(appView, context);
+    ProguardMemberRuleLookup lookup = lookupMemberRule(singleTarget);
     if (lookup == null) {
       // -assumenosideeffects rules are applied to upward visible and overriding methods, but only
       // references that have actual definitions are marked by the root set builder. So, here, we
@@ -253,60 +255,56 @@
           appView.appInfo().unsafeResolveMethodDueToDexFormat(invokedMethod).getSingleTarget();
       lookup = lookupMemberRule(resolutionTarget);
     }
-    boolean invokeReplaced = false;
-    if (lookup != null) {
-      boolean hasUsedOutValue = current.hasOutValue() && current.outValue().isUsed();
-      if (!hasUsedOutValue) {
-        if (lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS) {
-          // Remove invoke if marked as having no side effects and the return value is not used.
-          iterator.removeOrReplaceByDebugLocalRead();
-        }
-        return;
-      }
 
+    if (lookup != null) {
       // Check to see if a constant value can be assumed.
       // But, if the current matched rule is -assumenosideeffects without the return value, it won't
       // be transformed into a replacement instruction. Check if there is -assumevalues rule bound
       // to the target.
-      if (target != null
+      if (singleTarget != null
           && lookup.type == RuleType.ASSUME_NO_SIDE_EFFECTS
           && !lookup.rule.hasReturnValue()) {
-        ProguardMemberRule rule = appView.appInfo().assumedValues.get(target.toReference());
+        ProguardMemberRule rule = appView.appInfo().assumedValues.get(singleTarget.toReference());
         if (rule != null) {
           lookup = new ProguardMemberRuleLookup(RuleType.ASSUME_VALUES, rule);
         }
       }
-      invokeReplaced =
-          tryConstantReplacementFromProguard(
-              code, affectedValues, blocks, iterator, current, lookup);
+      if (tryConstantReplacementFromProguard(
+          code, affectedValues, blocks, iterator, invoke, lookup)) {
+        return;
+      }
     }
-    if (invokeReplaced || !current.hasOutValue()) {
-      return;
-    }
+
     // No Proguard rule could replace the instruction check for knowledge about the return value.
-    if (target == null || !mayPropagateValueFor(target)) {
+    if (singleTarget == null || !mayPropagateValueFor(singleTarget)) {
       return;
     }
 
-    AbstractValue abstractReturnValue = target.getOptimizationInfo().getAbstractReturnValue();
+    AbstractValue abstractReturnValue;
+    if (singleTarget.returnType().isAlwaysNull(appView)) {
+      abstractReturnValue = appView.abstractValueFactory().createSingleNumberValue(0);
+    } else {
+      abstractReturnValue = singleTarget.getOptimizationInfo().getAbstractReturnValue();
+    }
+
     if (abstractReturnValue.isSingleValue()) {
       SingleValue singleReturnValue = abstractReturnValue.asSingleValue();
       if (singleReturnValue.isMaterializableInContext(appView, context)) {
-        BasicBlock block = current.getBlock();
-        Position position = current.getPosition();
+        BasicBlock block = invoke.getBlock();
+        Position position = invoke.getPosition();
 
         Instruction replacement =
-            singleReturnValue.createMaterializingInstruction(appView, code, current);
-        affectedValues.addAll(current.outValue().affectedValues());
-        current.moveDebugValues(replacement);
-        current.outValue().replaceUsers(replacement.outValue());
-        current.setOutValue(null);
+            singleReturnValue.createMaterializingInstruction(appView, code, invoke);
+        affectedValues.addAll(invoke.outValue().affectedValues());
+        invoke.moveDebugValues(replacement);
+        invoke.outValue().replaceUsers(replacement.outValue());
+        invoke.setOutValue(null);
 
-        if (current.isInvokeMethodWithReceiver()) {
-          replaceInstructionByNullCheckIfPossible(current, iterator, context);
-        } else if (current.isInvokeStatic()) {
+        if (invoke.isInvokeMethodWithReceiver()) {
+          replaceInstructionByNullCheckIfPossible(invoke, iterator, context);
+        } else if (invoke.isInvokeStatic()) {
           replaceInstructionByInitClassIfPossible(
-              current, target.holder(), code, iterator, context);
+              invoke, singleTarget.holder(), code, iterator, context);
         }
 
         // Insert the definition of the replacement.
@@ -316,7 +314,7 @@
         } else {
           iterator.add(replacement);
         }
-        target.getMutableOptimizationInfo().markAsPropagated();
+        singleTarget.getMutableOptimizationInfo().markAsPropagated();
       }
     }
   }
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 da5994e..11245e7 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
@@ -326,7 +326,6 @@
   }
 
   public void rewrite(IRCode code) {
-    AssumeRemover assumeRemover = new AssumeRemover(appView, code);
     Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
     Set<Value> affectedValues = Sets.newIdentityHashSet();
@@ -352,13 +351,11 @@
               blockIterator,
               instructionIterator,
               code,
-              assumeRemover,
               affectedValues,
               blocksToBeRemoved);
         }
       }
     }
-    assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
     code.removeBlocks(blocksToBeRemoved);
     code.removeAllDeadAndTrivialPhis(affectedValues);
     code.removeUnreachableBlocks();
@@ -398,7 +395,6 @@
       ListIterator<BasicBlock> blockIterator,
       InstructionListIterator instructionIterator,
       IRCode code,
-      AssumeRemover assumeRemover,
       Set<Value> affectedValues,
       Set<BasicBlock> blocksToBeRemoved) {
     DexEncodedMethod target = invoke.lookupSingleTarget(appView, code.context());
@@ -418,30 +414,5 @@
         }
       }
     }
-
-    DexType returnType = target.method.proto.returnType;
-    if (returnType.isAlwaysNull(appView)) {
-      replaceOutValueByNull(invoke, instructionIterator, code, assumeRemover, affectedValues);
-    }
-  }
-
-  private void replaceOutValueByNull(
-      Instruction instruction,
-      InstructionListIterator instructionIterator,
-      IRCode code,
-      AssumeRemover assumeRemover,
-      Set<Value> affectedValues) {
-    assert instructionIterator.peekPrevious() == instruction;
-    if (instruction.hasOutValue()) {
-      Value outValue = instruction.outValue();
-      if (outValue.numberOfAllUsers() > 0) {
-        assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
-        instructionIterator.previous();
-        affectedValues.addAll(outValue.affectedValues());
-        outValue.replaceUsers(
-            instructionIterator.insertConstNullInstruction(code, appView.options()));
-        instructionIterator.next();
-      }
-    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
index 289e095..3940548 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/B146957343.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.optimize.uninstantiatedtypes;
 
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
@@ -46,7 +47,7 @@
         .addProgramClasses(I.class, J.class, Main.class)
         .addProgramClassFileData(getAimplementsI())
         .addKeepMainRule(Main.class)
-        .addKeepRules("-keep class **A { createA(); }")
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(
             options -> options.enableUninstantiatedTypeOptimizationForInterfaces = true)
@@ -62,7 +63,7 @@
         .addProgramClasses(I.class, J.class, Main.class)
         .addProgramClassFileData(getAimplementsI())
         .addKeepMainRule(Main.class)
-        .addKeepRules("-keep class **A { createA(); }")
+        .enableInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .addOptionsModification(
             options -> options.enableUninstantiatedTypeOptimizationForInterfaces = false)
@@ -76,10 +77,13 @@
   public interface J extends I {}
 
   public static class A implements J {
+
+    @NeverInline
     public static J createA() {
       return new A();
     }
 
+    @NeverInline
     public void f() {
       System.out.println("In A.f()");
     }
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
index bc842e5..9c38214 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsPropagationWithoutMatchingDefinitionTest.java
@@ -116,7 +116,9 @@
 
     @NeverInline
     private static void testInvokeInterface(LoggerInterface logger, String message) {
-      logger.debug(TAG, message);
+      if (logger != null) {
+        logger.debug(TAG, message);
+      }
     }
 
     @NeverInline
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
index 29e4588..34b1b33 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/AssumenosideeffectsWithMultipleTargetsTest.java
@@ -138,7 +138,9 @@
 
     @NeverInline
     private static void testInvokeInterface(TestLogger logger, String message) {
-      logger.info(TAG, message);
+      if (logger != null) {
+        logger.info(TAG, message);
+      }
     }
 
     public static void main(String... args) {