Allow class inlining of merged singleton classes

Bug: 173337498
Change-Id: I317e3e77cb502f7a42b97cbabb6c080ab8739a4e
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index eb016fe..37b3796 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -224,8 +224,6 @@
           anyInlinedMethods = true;
         }
 
-        assert inliningIRProvider.verifyIRCacheIsEmpty();
-
         // Restore normality.
         code.removeAllDeadAndTrivialPhis(affectedValues);
         if (!affectedValues.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 4b15332..23d9242 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -25,11 +25,12 @@
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.code.AliasedValueConfiguration;
 import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
-import com.android.tools.r8.ir.code.ConstNumber;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstanceGet;
@@ -59,7 +60,6 @@
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.utils.OptionalBool;
-import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
@@ -93,6 +93,7 @@
 
   private Value eligibleInstance;
   private DexProgramClass eligibleClass;
+  private ObjectState objectState;
 
   private final Map<InvokeMethodWithReceiver, InliningInfo> methodCallsOnInstance =
       new IdentityHashMap<>();
@@ -100,8 +101,6 @@
   private final ProgramMethodSet indirectMethodCallsOnInstance = ProgramMethodSet.create();
   private final Map<InvokeMethod, InliningInfo> extraMethodCalls
       = new IdentityHashMap<>();
-  private final List<Pair<InvokeMethod, Integer>> unusedArguments
-      = new ArrayList<>();
 
   private final Map<InvokeMethod, ProgramMethod> directInlinees = new IdentityHashMap<>();
   private final List<ProgramMethod> indirectInlinees = new ArrayList<>();
@@ -191,6 +190,11 @@
     if (eligibleClass == null) {
       return EligibilityStatus.NOT_ELIGIBLE;
     }
+    AbstractValue abstractValue = optimizationInfo.getAbstractValue();
+    objectState =
+        abstractValue.isSingleFieldValue()
+            ? abstractValue.asSingleFieldValue().getState()
+            : ObjectState.empty();
     return EligibilityStatus.ELIGIBLE;
   }
 
@@ -382,7 +386,6 @@
   // Process inlining, includes the following steps:
   //
   //  * remove linked assume instructions if any so that users of the eligible field are up-to-date.
-  //  * replace unused instance usages as arguments which are never used
   //  * inline extra methods if any, collect new direct method calls
   //  * inline direct methods if any
   //  * remove superclass initializer call and field reads
@@ -398,7 +401,6 @@
       throws IllegalClassInlinerStateException {
     // Verify that `eligibleInstance` is not aliased.
     assert eligibleInstance == eligibleInstance.getAliasedValue();
-    replaceUsagesAsUnusedArgument(code);
 
     boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code, inliningIRProvider);
     if (anyInlinedMethods) {
@@ -412,8 +414,6 @@
       }
       assert extraMethodCalls.isEmpty()
           : "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", ");
-      assert unusedArguments.isEmpty()
-          : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", ");
     }
 
     anyInlinedMethods |= forceInlineDirectMethodInvocations(code, inliningIRProvider);
@@ -431,26 +431,9 @@
     methodCallsOnInstance.clear();
     indirectMethodCallsOnInstance.clear();
     extraMethodCalls.clear();
-    unusedArguments.clear();
     receivers.reset();
   }
 
-  private void replaceUsagesAsUnusedArgument(IRCode code) {
-    for (Pair<InvokeMethod, Integer> unusedArgument : unusedArguments) {
-      InvokeMethod invoke = unusedArgument.getFirst();
-      BasicBlock block = invoke.getBlock();
-
-      ConstNumber nullValue = code.createConstNull();
-      nullValue.setPosition(invoke.getPosition());
-      block.listIterator(code, invoke).add(nullValue);
-      assert nullValue.getBlock() == block;
-
-      int argIndex = unusedArgument.getSecond();
-      invoke.replaceValue(argIndex, nullValue.outValue());
-    }
-    unusedArguments.clear();
-  }
-
   private boolean forceInlineExtraMethodInvocations(
       IRCode code, InliningIRProvider inliningIRProvider) {
     if (extraMethodCalls.isEmpty()) {
@@ -737,6 +720,7 @@
         new TreeSet<>(Comparator.comparingInt(x -> x.outValue().getNumber()));
     for (Instruction user : eligibleInstance.uniqueUsers()) {
       if (user.isInstanceGet()) {
+        assert root.isNewInstance();
         if (user.outValue().hasAnyUsers()) {
           uniqueInstanceGetUsersWithDeterministicOrder.add(user.asInstanceGet());
         } else {
@@ -748,6 +732,7 @@
       if (user.isInstancePut()) {
         // Skip in this iteration since these instructions are needed to properly calculate what
         // value should field reads be replaced with.
+        assert root.isNewInstance();
         continue;
       }
 
@@ -1111,7 +1096,7 @@
 
     assert root.isStaticGet();
     return classInlinerMethodConstraint.isEligibleForStaticGetClassInlining(
-        singleTarget, parameter);
+        appView, parameter, objectState);
   }
 
   private boolean isExtraMethodCall(InvokeMethod invoke) {
@@ -1136,10 +1121,6 @@
   // Analyzes if a method invoke the eligible instance is passed to is eligible. In short,
   // it can be eligible if:
   //
-  //   -- eligible instance is passed as argument #N which is not used in the method,
-  //      such cases are collected in 'unusedArguments' parameter and later replaced
-  //      with 'null' value
-  //
   //   -- eligible instance is passed as argument #N which is only used in the method to
   //      call a method on this object (we call it indirect method call), and method is
   //      eligible according to the same rules defined for direct method call eligibility
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
index 0c6d335e..ff68dfd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/AlwaysFalseClassInlinerMethodConstraint.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public class AlwaysFalseClassInlinerMethodConstraint implements ClassInlinerMethodConstraint {
 
@@ -34,7 +37,8 @@
   }
 
   @Override
-  public boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter) {
+  public boolean isEligibleForStaticGetClassInlining(
+      AppView<AppInfoWithLiveness> appView, int parameter, ObjectState objectState) {
     return false;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
index c86f373..ce14bb4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ClassInlinerMethodConstraint.java
@@ -4,8 +4,11 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 
 public interface ClassInlinerMethodConstraint {
 
@@ -15,7 +18,8 @@
 
   boolean isEligibleForNewInstanceClassInlining(ProgramMethod method, int parameter);
 
-  boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter);
+  boolean isEligibleForStaticGetClassInlining(
+      AppView<AppInfoWithLiveness> appView, int parameter, ObjectState objectState);
 
   static AlwaysFalseClassInlinerMethodConstraint alwaysFalse() {
     return AlwaysFalseClassInlinerMethodConstraint.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
index 5c384e8..8ad6b4a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/constraint/ConditionalClassInlinerMethodConstraint.java
@@ -4,12 +4,18 @@
 
 package com.android.tools.r8.ir.optimize.classinliner.constraint;
 
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.value.ObjectState;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisContext;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.AnalysisState;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.NonEmptyParameterUsage;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsage;
 import com.android.tools.r8.ir.optimize.classinliner.analysis.ParameterUsagePerContext;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
 import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 
@@ -51,7 +57,8 @@
   }
 
   @Override
-  public boolean isEligibleForStaticGetClassInlining(ProgramMethod method, int parameter) {
+  public boolean isEligibleForStaticGetClassInlining(
+      AppView<AppInfoWithLiveness> appView, int parameter, ObjectState objectState) {
     AnalysisContext defaultContext = AnalysisContext.getDefaultContext();
     ParameterUsage usage = usages.get(parameter).get(defaultContext);
     if (usage.isBottom()) {
@@ -72,9 +79,13 @@
       // We will not be able to remove the monitor instruction afterwards.
       return false;
     }
-    if (!knownUsage.getFieldsReadFromParameter().isEmpty()) {
-      // We don't know the value of the field.
-      return false;
+    for (DexField fieldReadFromParameter : knownUsage.getFieldsReadFromParameter()) {
+      DexClass holder = appView.definitionFor(fieldReadFromParameter.getHolderType());
+      DexEncodedField definition = fieldReadFromParameter.lookupOnClass(holder);
+      if (definition == null
+          || !objectState.getAbstractFieldValue(definition).isSingleConstValue()) {
+        return false;
+      }
     }
     return true;
   }