Remove trivial phis in class inliner.

Bug: 160394262
Bug: 160640028
Bug: 160634549
Change-Id: I610c6a3768a7f8ad2b3ab7c2c0b72a9e7d336ddc
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
index f80e1d7..3feac87 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerCostAnalysis.java
@@ -12,11 +12,8 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.AssumeAndCheckCastAliasedValueConfiguration;
 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.InvokeMethod;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.inliner.InliningIRProvider;
@@ -26,7 +23,6 @@
 import java.util.Map;
 import java.util.Set;
 
-// TODO(b/160634549): Rename or refactor this to reflect its non-cost related analysis.
 /** Analysis that estimates the cost of class inlining an object allocation. */
 class ClassInlinerCostAnalysis {
 
@@ -77,28 +73,6 @@
         continue;
       }
       IRCode inliningIR = inliningIRProvider.getAndCacheInliningIR(invoke, inlinee);
-
-      // If the instance is part of a phi, then inlining will invalidate the inliner assumptions.
-      // TODO(b/160634549): This is not a budget miss but a hard requirement.
-      InstructionIterator iterator = inliningIR.entryBlock().iterator();
-      while (iterator.hasNext()) {
-        Instruction next = iterator.next();
-        if (!next.isArgument()) {
-          break;
-        }
-        Value argumentValue = next.outValue();
-        TypeElement argumentType = argumentValue.getType();
-        if (argumentType.isClassType()
-            && argumentType.asClassType().getClassType() == eligibleClass.type) {
-          assert argumentValue.uniqueUsers().stream()
-              .noneMatch(
-                  AssumeAndCheckCastAliasedValueConfiguration.getInstance()::isIntroducingAnAlias);
-          if (argumentValue.hasPhiUsers()) {
-            return true;
-          }
-        }
-      }
-
       int increment =
           inlinee.getDefinition().getCode().estimatedSizeForInlining()
               - estimateNumberOfNonMaterializingInstructions(invoke, inliningIR);
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 4e52743..72be050 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
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 import static com.google.common.base.Predicates.alwaysFalse;
 
+import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AccessControl;
 import com.android.tools.r8.graph.AppView;
@@ -41,6 +42,7 @@
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.MethodProcessor;
@@ -58,11 +60,11 @@
 import com.android.tools.r8.ir.optimize.inliner.NopWhyAreYouNotInliningReporter;
 import com.android.tools.r8.kotlin.KotlinClassLevelInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.WorkList;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -581,11 +583,35 @@
     while (!currentUsers.isEmpty()) {
       Set<Instruction> indirectOutValueUsers = Sets.newIdentityHashSet();
       for (Instruction instruction : currentUsers) {
-        if (instruction.isAssume() || instruction.isCheckCast()) {
-          Value src = ListUtils.first(instruction.inValues());
+        if (aliasesThroughAssumeAndCheckCasts.isIntroducingAnAlias(instruction)) {
+          Value src = aliasesThroughAssumeAndCheckCasts.getAliasForOutValue(instruction);
           Value dest = instruction.outValue();
-          indirectOutValueUsers.addAll(dest.uniqueUsers());
+          if (dest.hasPhiUsers()) {
+            // It is possible that a trivial phi is constructed upon IR building for the eligible
+            // value. It must actually be trivial so verify that it is indeed trivial and replace
+            // all of the phis involved with the value.
+            WorkList<Phi> worklist = WorkList.newIdentityWorkList(dest.uniquePhiUsers());
+            while (worklist.hasNext()) {
+              Phi phi = worklist.next();
+              for (Value operand : phi.getOperands()) {
+                operand = operand.getAliasedValue(aliasesThroughAssumeAndCheckCasts);
+                if (operand.isPhi()) {
+                  worklist.addIfNotSeen(operand.asPhi());
+                } else if (src != operand) {
+                  throw new InternalCompilerError(
+                      "Unexpected non-trivial phi in method eligible for class inlining");
+                }
+              }
+            }
+            // The only value flowing into any of the phis is src, so replace all phis by src.
+            for (Phi phi : worklist.getSeenSet()) {
+              indirectOutValueUsers.addAll(phi.uniqueUsers());
+              phi.replaceUsers(src);
+              phi.removeDeadPhi();
+            }
+          }
           assert !dest.hasPhiUsers();
+          indirectOutValueUsers.addAll(dest.uniqueUsers());
           dest.replaceUsers(src);
           removeInstruction(instruction);
         }
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index d8bd420..092a4fc 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -6,6 +6,7 @@
 
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashSet;
 import java.util.Set;
@@ -78,6 +79,10 @@
     return workingList.removeFirst();
   }
 
+  public Set<T> getSeenSet() {
+    return Collections.unmodifiableSet(seen);
+  }
+
   public enum EqualityTest {
     HASH,
     IDENTITY
diff --git a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
index 0c3206c..383a7ef 100644
--- a/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
+++ b/src/test/java/com/android/tools/r8/regress/Regress160394262Test.java
@@ -57,15 +57,13 @@
   }
 
   private void checkJoinerIsClassInlined(CodeInspector inspector) {
-    // TODO(b/160640028): When compiling to DEX a trivial phi remains in the inline code preventing
-    //  class inlining of Joiner and the anonymous Joiner subclass.
+    assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), not(isPresent()));
+    // TODO(b/160640028): When compiling to DEX the outer Joiner class is not inlined.
     if (parameters.isDexRuntime()) {
       assertThat(inspector.clazz(Joiner.class), isPresent());
-      assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), isPresent());
-      return;
+    } else {
+      assertThat(inspector.clazz(Joiner.class), not(isPresent()));
     }
-    assertThat(inspector.clazz(Joiner.class), not(isPresent()));
-    assertThat(inspector.clazz(Joiner.class.getTypeName() + "$1"), not(isPresent()));
   }
 
   static class TestClass {