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();
+ }
+ }
+}