Add test for having phi in generated inlinee for class-inlining

Bug: 178608910
Change-Id: I3d99ce1e896f1bfff2de522d40033d70cbb1df4a
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 273f7b7..d9175f2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -751,6 +751,9 @@
       if (lambdaMerger != null) {
         lambdaMerger.rewriteCodeForInlining(target, code, context, inliningIRProvider);
       }
+      if (options.testing.inlineeIrModifier != null) {
+        options.testing.inlineeIrModifier.accept(code);
+      }
       assert code.isConsistentSSA();
       return new InlineeWithReason(code, reason);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 6368a32..cd5ff31 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1246,6 +1246,7 @@
     public boolean forceNameReflectionOptimization = false;
     public boolean enableNarrowAndWideningingChecksInD8 = false;
     public Consumer<IRCode> irModifier = null;
+    public Consumer<IRCode> inlineeIrModifier = null;
     public int basicBlockMuncherIterationLimit = NO_LIMIT;
     public boolean dontReportFailingCheckDiscarded = false;
     public boolean deterministicSortingBasedOnDexType = true;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
new file mode 100644
index 0000000..48795d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlinerPhiDirectUserAfterInlineTest.java
@@ -0,0 +1,239 @@
+// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.classinliner;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ir.code.Argument;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Phi.RegisterReadType;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+// This is a reproduction of b/178608910.
+@RunWith(Parameterized.class)
+public class ClassInlinerPhiDirectUserAfterInlineTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public ClassInlinerPhiDirectUserAfterInlineTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testNoClassInlining() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+              // Here we modify the IR when it is processed normally.
+              options.testing.irModifier = this::modifyIr;
+              options.enableClassInlining = false;
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("0", "A::baz");
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(Main.class)
+        .addOptionsModification(
+            options -> {
+              options.testing.validInliningReasons = ImmutableSet.of(Reason.FORCE);
+              options.testing.inlineeIrModifier = this::modifyIr;
+            })
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              // TODO(b/178608910): This should not fail.
+              diagnostics.assertErrorsMatch(
+                  diagnosticMessage(
+                      containsString("Unexpected values live at entry to first block")));
+            });
+  }
+
+  private void modifyIr(IRCode irCode) {
+    if (irCode.context().getReference().qualifiedName().equals(A.class.getTypeName() + ".foo")) {
+      assertEquals(7, irCode.blocks.size());
+      // This is the code that we expect
+      // <pre>
+      // blocks:
+      // block 0, pred-counts: 0, succ-count: 1, filled: true, sealed: true
+      // predecessors: -
+      // successors: 1  (no try/catch successors)
+      // no phis
+      //  #0:foo;0:main: -1: Argument             v3 <-
+      //               : -1: ConstNumber          v4(0) <-  0 (INT)
+      //               : -1: Goto                 block 1
+      //
+      // block 1, pred-counts: 2, succ-count: 2, filled: true, sealed: true
+      // predecessors: 0 5
+      // successors: 2 3  (no try/catch successors)
+      // v7 <- phi(v4(0), v13) : INT
+      //  #0:foo;0:main: -1: InstanceGet          v6 <- v3; field: int
+      // com.android.tools.r8.ir.optimize.classinliner
+      //    .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+      //               : -1: If                   v6 EQZ block 2 (fallthrough 3)
+      //
+      // block 2, pred-counts: 1, succ-count: 0, filled: true, sealed: true
+      // predecessors: 1
+      // successors: -
+      // no phis
+      //  #0:foo;0:main: -1: Invoke-Virtual       v3, v7; method: void
+      // com.android.tools.r8.ir.optimize.classinliner
+      //  .ClassInlinerPhiDirectUserAfterInlineTest$A.bar(int)
+      //               : -1: Return
+      //
+      // block 3, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+      // predecessors: 1
+      // successors: 4  (no try/catch successors)
+      // no phis
+      //  #0:foo;0:main: -1: ConstNumber          v8(2) <-  2 (INT)
+      //               : -1: Add                  v9 <- v7, v8(2)
+      //               : -1: Goto                 block 4
+      //
+      // block 4, pred-counts: 2, succ-count: 2, filled: true, sealed: true
+      // predecessors: 3 6
+      // successors: 5 6  (no try/catch successors)
+      // v13 <- phi(v9, v15) : INT
+      //  #0:foo;0:main: -1: InstanceGet          v11 <- v3; field: int
+      // com.android.tools.r8.ir.optimize.classinliner
+      //  .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+      //               : -1: ConstNumber          v12(10) <-  10 (INT)
+      //               : -1: If                   v11, v12(10) LE  block 5
+      // (fallthrough 6)
+      //
+      // block 5, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+      // predecessors: 4
+      // successors: 1  (no try/catch successors)
+      // no phis
+      //  #0:foo;0:main: -1: Goto                 block 1
+      //
+      // block 6, pred-counts: 1, succ-count: 1, filled: true, sealed: true
+      // predecessors: 4
+      // successors: 4  (no try/catch successors)
+      // no phis
+      //  #0:foo;0:main: -1: ConstNumber          v14(3) <-  3 (INT)
+      //               : -1: Add                  v15 <- v13, v14(3)
+      //               : -1: ConstNumber          v16(-1) <-  -1 (INT)
+      //               : -1: Add                  v17 <- v11, v16(-1)
+      //               : -1: InstancePut          v3, v17; field: int
+      // com.android.tools.r8.ir.optimize.classinliner
+      //  .ClassInlinerPhiDirectUserAfterInlineTest$A.number
+      //               : -1: Goto                 block 4
+      // </pre>
+      // We modify block 1 to have:
+      // vX : phi(v3, vY)
+      // .. InstanceGet       vX ...
+      //
+      // and block 4 to have:
+      // vY : phi(v3, vX)
+      BasicBlock basicBlock = irCode.blocks.get(0);
+      Argument argument = basicBlock.getInstructions().get(0).asArgument();
+      assertNotNull(argument);
+      Value argumentValue = argument.outValue();
+
+      BasicBlock block1 = irCode.blocks.get(1);
+      assertTrue(block1.exit().isIf());
+
+      BasicBlock block3 = irCode.blocks.get(3);
+      assertTrue(block1.getSuccessors().contains(block3));
+      assertTrue(block3.exit().isGoto());
+
+      BasicBlock block4 = irCode.blocks.get(4);
+      assertSame(block3.getUniqueNormalSuccessor(), block4);
+
+      Phi firstPhi =
+          new Phi(
+              irCode.valueNumberGenerator.next(),
+              block1,
+              argumentValue.getType(),
+              null,
+              RegisterReadType.NORMAL);
+
+      Phi secondPhi =
+          new Phi(
+              irCode.valueNumberGenerator.next(),
+              block4,
+              argumentValue.getType(),
+              null,
+              RegisterReadType.NORMAL);
+
+      firstPhi.addOperands(ImmutableList.of(argumentValue, secondPhi));
+      secondPhi.addOperands(ImmutableList.of(argumentValue, firstPhi));
+
+      // Replace the invoke to use the phi
+      InstanceGet instanceGet = block1.getInstructions().get(0).asInstanceGet();
+      assertNotNull(instanceGet);
+      assertEquals(A.class.getTypeName(), instanceGet.getField().holder.toSourceString());
+      instanceGet.replaceValue(0, firstPhi);
+    }
+  }
+
+  public static class Outer {}
+
+  public static class A {
+
+    int number = 0;
+
+    public void foo() {
+      int otherNumber = 0;
+      while (number != 0) {
+        otherNumber += 2;
+        while (number > 10) {
+          otherNumber += 3;
+          number--;
+        }
+      }
+      bar(otherNumber);
+    }
+
+    public void bar(int number) {
+      System.out.println(number + "");
+    }
+
+    public void baz() {
+      System.out.println("A::baz");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      A a = new A();
+      a.number = args.length;
+      a.foo();
+      a.baz();
+    }
+  }
+}