blob: 5cea9fa609d14e34b093c27a128eedfa50085a85 [file] [log] [blame]
// 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();
}
}
}