| // 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.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| 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.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.TestParametersCollection; |
| import com.android.tools.r8.graph.AppView; |
| 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.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import java.util.List; |
| 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; |
| private final List<String> EXPECTED = ImmutableList.of("0", "A::baz"); |
| |
| @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(EXPECTED); |
| } |
| |
| @Test |
| public void testR8() throws Exception { |
| testForR8(parameters.getBackend()) |
| .addInnerClasses(getClass()) |
| .setMinApi(parameters.getApiLevel()) |
| .addKeepMainRule(Main.class) |
| .addOptionsModification( |
| options -> { |
| options.testing.validInliningReasons = ImmutableSet.of(Reason.ALWAYS); |
| options.testing.inlineeIrModifier = this::modifyIr; |
| }) |
| .run(parameters.getRuntime(), Main.class) |
| .assertSuccessWithOutputLines(EXPECTED) |
| .inspect( |
| inspector -> { |
| // Assert that the A has been class-inlined into the caller. |
| ClassSubject aSubject = inspector.clazz(A.class); |
| assertThat(aSubject, not(isPresent())); |
| }); |
| } |
| |
| private void modifyIr(IRCode irCode, AppView<?> appView) { |
| modifyIr(irCode); |
| } |
| |
| 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 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(); |
| } |
| } |
| } |