blob: f0c6a782c083c70a554de139f90b161ebceb8c07 [file] [log] [blame]
// Copyright (c) 2018, 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.staticizer;
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.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeDirect;
import com.android.tools.r8.code.InvokeStatic;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.SgetObject;
import com.android.tools.r8.code.SputObject;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.optimize.staticizer.dualcallinline.Candidate;
import com.android.tools.r8.ir.optimize.staticizer.dualcallinline.DualCallTest;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictField;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateConflictMethod;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOk;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOkFieldOnly;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.CandidateOkSideEffects;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictField;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostConflictMethod;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOk;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOkFieldOnly;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.HostOkSideEffects;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.MoveToHostFieldOnlyTestClass;
import com.android.tools.r8.ir.optimize.staticizer.movetohost.MoveToHostTestClass;
import com.android.tools.r8.ir.optimize.staticizer.trivial.Simple;
import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithGetter;
import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithParams;
import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithPhi;
import com.android.tools.r8.ir.optimize.staticizer.trivial.SimpleWithSideEffects;
import com.android.tools.r8.ir.optimize.staticizer.trivial.TrivialTestClass;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.Lists;
import com.google.common.collect.Streams;
import java.util.List;
import java.util.stream.Collectors;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class ClassStaticizerTest extends TestBase {
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
// TODO(b/112831361): support for class staticizer in CF backend.
return getTestParameters().withDexRuntimes().build();
}
public ClassStaticizerTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void testTrivial() throws Exception {
Class<?> main = TrivialTestClass.class;
Class<?>[] classes = {
NeverInline.class,
TrivialTestClass.class,
Simple.class,
SimpleWithSideEffects.class,
SimpleWithParams.class,
SimpleWithGetter.class,
SimpleWithPhi.class,
SimpleWithPhi.Companion.class
};
String javaOutput = runOnJava(main);
TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
.addKeepMainRule(main)
.noMinification()
.addKeepRules("-keepattributes InnerClasses,EnclosingMethod")
.addOptionsModification(this::configure)
.allowAccessModification()
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), main)
.assertSuccessWithOutput(javaOutput);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
assertEquals(
Lists.newArrayList(
"STATIC: String trivial.Simple.bar(String)",
"STATIC: String trivial.Simple.foo()",
"STATIC: String trivial.TrivialTestClass.next()"),
references(clazz, "testSimple", "void"));
assertTrue(instanceMethods(inspector.clazz(Simple.class)).isEmpty());
assertEquals(
Lists.newArrayList(
"STATIC: String trivial.SimpleWithPhi.bar(String)",
"STATIC: String trivial.SimpleWithPhi.foo()",
"STATIC: String trivial.SimpleWithPhi.foo()",
"STATIC: String trivial.TrivialTestClass.next()"),
references(clazz, "testSimpleWithPhi", "void", "int"));
assertTrue(instanceMethods(inspector.clazz(SimpleWithPhi.class)).isEmpty());
assertEquals(
Lists.newArrayList(
"STATIC: String trivial.SimpleWithParams.bar(String)",
"STATIC: String trivial.SimpleWithParams.foo()",
"STATIC: String trivial.TrivialTestClass.next()"),
references(clazz, "testSimpleWithParams", "void"));
assertTrue(instanceMethods(inspector.clazz(SimpleWithParams.class)).isEmpty());
assertEquals(
Lists.newArrayList(
"STATIC: String trivial.SimpleWithSideEffects.bar(String)",
"STATIC: String trivial.SimpleWithSideEffects.foo()",
"STATIC: String trivial.TrivialTestClass.next()",
"trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE",
"trivial.SimpleWithSideEffects trivial.SimpleWithSideEffects.INSTANCE"),
references(clazz, "testSimpleWithSideEffects", "void"));
assertTrue(instanceMethods(inspector.clazz(SimpleWithSideEffects.class)).isEmpty());
// TODO(b/111832046): add support for singleton instance getters.
assertEquals(
Lists.newArrayList(
"STATIC: String trivial.TrivialTestClass.next()",
"VIRTUAL: String trivial.SimpleWithGetter.bar(String)",
"VIRTUAL: String trivial.SimpleWithGetter.foo()",
"trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE",
"trivial.SimpleWithGetter trivial.SimpleWithGetter.INSTANCE"),
references(clazz, "testSimpleWithGetter", "void"));
assertFalse(instanceMethods(inspector.clazz(SimpleWithGetter.class)).isEmpty());
}
@Test
public void testMoveToHost_fieldOnly() throws Exception {
Class<?> main = MoveToHostFieldOnlyTestClass.class;
Class<?>[] classes = {
NeverInline.class,
MoveToHostFieldOnlyTestClass.class,
HostOkFieldOnly.class,
CandidateOkFieldOnly.class
};
TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
.enableSideEffectAnnotations()
.addKeepMainRule(main)
.allowAccessModification()
.noMinification()
.addOptionsModification(this::configure)
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), main);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
assertEquals(
Lists.newArrayList(),
references(clazz, "testOk_fieldOnly", "void"));
assertFalse(inspector.clazz(CandidateOkFieldOnly.class).isPresent());
}
@Test
public void testMoveToHost() throws Exception {
Class<?> main = MoveToHostTestClass.class;
Class<?>[] classes = {
NeverInline.class,
MoveToHostTestClass.class,
HostOk.class,
CandidateOk.class,
HostOkSideEffects.class,
CandidateOkSideEffects.class,
HostConflictMethod.class,
CandidateConflictMethod.class,
HostConflictField.class,
CandidateConflictField.class
};
String javaOutput = runOnJava(main);
TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
.enableMemberValuePropagationAnnotations()
.addKeepMainRule(main)
.allowAccessModification()
.noMinification()
.addOptionsModification(this::configure)
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), main)
.assertSuccessWithOutput(javaOutput);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
assertEquals(
Lists.newArrayList(
"STATIC: String movetohost.HostOk.bar(String)",
"STATIC: String movetohost.HostOk.foo()",
"STATIC: String movetohost.MoveToHostTestClass.next()",
"STATIC: String movetohost.MoveToHostTestClass.next()",
"STATIC: void movetohost.HostOk.blah(String)"),
references(clazz, "testOk", "void"));
assertFalse(inspector.clazz(CandidateOk.class).isPresent());
assertEquals(
Lists.newArrayList(
"STATIC: String movetohost.HostOkSideEffects.bar(String)",
"STATIC: String movetohost.HostOkSideEffects.foo()",
"STATIC: String movetohost.MoveToHostTestClass.next()",
"movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE",
"movetohost.HostOkSideEffects movetohost.HostOkSideEffects.INSTANCE"),
references(clazz, "testOkSideEffects", "void"));
assertFalse(inspector.clazz(CandidateOkSideEffects.class).isPresent());
assertEquals(
Lists.newArrayList(
"DIRECT: void movetohost.HostConflictMethod.<init>()",
"STATIC: String movetohost.CandidateConflictMethod.bar(String)",
"STATIC: String movetohost.CandidateConflictMethod.foo()",
"STATIC: String movetohost.MoveToHostTestClass.next()",
"STATIC: String movetohost.MoveToHostTestClass.next()",
"VIRTUAL: String movetohost.HostConflictMethod.bar(String)"),
references(clazz, "testConflictMethod", "void"));
assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
assertEquals(
Lists.newArrayList(
"DIRECT: void movetohost.HostConflictField.<init>()",
"STATIC: String movetohost.CandidateConflictField.bar(String)",
"STATIC: String movetohost.CandidateConflictField.foo()",
"STATIC: String movetohost.MoveToHostTestClass.next()",
"String movetohost.CandidateConflictField.field"),
references(clazz, "testConflictField", "void"));
assertTrue(inspector.clazz(CandidateConflictMethod.class).isPresent());
}
private List<String> instanceMethods(ClassSubject clazz) {
assertNotNull(clazz);
assertTrue(clazz.isPresent());
return Streams.stream(clazz.getDexClass().methods())
.filter(method -> !method.isStatic())
.map(method -> method.method.toSourceString())
.sorted()
.collect(Collectors.toList());
}
private List<String> references(
ClassSubject clazz, String methodName, String retValue, String... params) {
assertNotNull(clazz);
assertTrue(clazz.isPresent());
MethodSignature signature = new MethodSignature(methodName, retValue, params);
DexCode code = clazz.method(signature).getMethod().getCode().asDexCode();
return Streams.concat(
filterInstructionKind(code, SgetObject.class)
.map(Instruction::getField)
.filter(fld -> isTypeOfInterest(fld.holder))
.map(DexField::toSourceString),
filterInstructionKind(code, SputObject.class)
.map(Instruction::getField)
.filter(fld -> isTypeOfInterest(fld.holder))
.map(DexField::toSourceString),
filterInstructionKind(code, InvokeStatic.class)
.map(insn -> (InvokeStatic) insn)
.map(InvokeStatic::getMethod)
.filter(method -> isTypeOfInterest(method.holder))
.map(method -> "STATIC: " + method.toSourceString()),
filterInstructionKind(code, InvokeVirtual.class)
.map(insn -> (InvokeVirtual) insn)
.map(InvokeVirtual::getMethod)
.filter(method -> isTypeOfInterest(method.holder))
.map(method -> "VIRTUAL: " + method.toSourceString()),
filterInstructionKind(code, InvokeDirect.class)
.map(insn -> (InvokeDirect) insn)
.map(InvokeDirect::getMethod)
.filter(method -> isTypeOfInterest(method.holder))
.map(method -> "DIRECT: " + method.toSourceString()))
.map(txt -> txt.replace("java.lang.", ""))
.map(txt -> txt.replace("com.android.tools.r8.ir.optimize.staticizer.", ""))
.sorted()
.collect(Collectors.toList());
}
private boolean isTypeOfInterest(DexType type) {
return type.toSourceString().startsWith("com.android.tools.r8.ir.optimize.staticizer");
}
private void configure(InternalOptions options) {
options.enableClassInlining = false;
options.enableUninstantiatedTypeOptimization = false;
}
@Test
public void dualInlinedMethodRewritten() throws Exception {
Class<?> main = DualCallTest.class;
Class<?>[] classes = {
DualCallTest.class,
Candidate.class
};
String javaOutput = runOnJava(main);
TestRunResult result =
testForR8(parameters.getBackend())
.addProgramClasses(classes)
.enableInliningAnnotations()
.addKeepMainRule(main)
.allowAccessModification()
.noMinification()
.addOptionsModification(this::configure)
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), main)
.assertSuccessWithOutput(javaOutput);
CodeInspector inspector = result.inspector();
ClassSubject clazz = inspector.clazz(main);
// Check that "calledTwice" is removed (inlined into main).
assertThat(clazz.uniqueMethodWithName("calledTwice"), not(isPresent()));
// Check that the two inlines of "calledTwice" is correctly rewritten.
assertThat(clazz.uniqueMethodWithName("foo"), isPresent());
assertThat(clazz.uniqueMethodWithName("bar"), isPresent());
assertEquals(
Lists.newArrayList(
"STATIC: String dualcallinline.DualCallTest.foo()",
"STATIC: String dualcallinline.DualCallTest.foo()"),
references(clazz, "main", "void", "java.lang.String[]"));
}
}