blob: 59e51182aef83d13fd502625dee6effa7ced1d5d [file] [log] [blame]
// Copyright (c) 2019, 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.inliner;
import static com.android.tools.r8.utils.codeinspector.CodeMatchers.invokesMethod;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNot.not;
import com.android.tools.r8.KeepConstantArguments;
import com.android.tools.r8.KeepUnusedArguments;
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.ToolHelper;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import java.util.Objects;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
public class InliningAfterClassInitializationTest extends TestBase {
private final TestParameters parameters;
@Parameterized.Parameters(name = "{0}")
public static TestParametersCollection data() {
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
public InliningAfterClassInitializationTest(TestParameters parameters) {
this.parameters = parameters;
}
@Test
public void testClass1() throws Exception {
Class<TestClass1> mainClass = TestClass1.class;
CodeInspector inspector =
buildAndRun(
mainClass,
StringUtils.lines("In A.<clinit>()", "In A.notInlineable()", "In A.inlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
assertThat(inlineableMethod, not(isPresent()));
MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
assertThat(notInlineableMethod, isPresent());
MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
assertThat(testMethod, isPresent());
assertThat(testMethod, invokesMethod(notInlineableMethod));
}
@Test
public void testClass2() throws Exception {
Class<TestClass2> mainClass = TestClass2.class;
CodeInspector inspector =
buildAndRun(
mainClass,
StringUtils.lines("In A.<clinit>()", "Field A.staticField", "In A.inlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
assertThat(inlineableMethod, not(isPresent()));
}
@Test
public void testClass3() throws Exception {
Class<TestClass3> mainClass = TestClass3.class;
CodeInspector inspector =
buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
assertThat(inlineableMethod, not(isPresent()));
}
@Test
public void testClass4() throws Exception {
Class<TestClass4> mainClass = TestClass4.class;
CodeInspector inspector =
buildAndRun(
mainClass,
StringUtils.lines("In A.<clinit>()", "Field A.instanceField", "In A.inlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
assertThat(inlineableMethod, not(isPresent()));
}
@Test
public void testClass5() throws Exception {
Class<TestClass5> mainClass = TestClass5.class;
CodeInspector inspector =
buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
assertThat(inlineableMethod, not(isPresent()));
}
@Test
public void testClass6() throws Exception {
Class<TestClass6> mainClass = TestClass6.class;
CodeInspector inspector =
buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.notInlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
assertThat(notInlineableMethod, isPresent());
MethodSubject testMethod = inspector.clazz(mainClass).uniqueMethodWithName("test");
assertThat(testMethod, isPresent());
assertThat(testMethod, invokesMethod(notInlineableMethod));
}
@Test
public void testClass7() throws Exception {
Class<TestClass7> mainClass = TestClass7.class;
CodeInspector inspector =
buildAndRun(
mainClass,
StringUtils.lines(
"Caught NullPointerException",
"In A.<clinit>()",
"Field A.instanceField",
"In A.inlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
assertThat(inlineableMethod, not(isPresent()));
}
@Test
public void testClass8() throws Exception {
Class<TestClass8> mainClass = TestClass8.class;
CodeInspector inspector =
buildAndRun(
mainClass,
StringUtils.lines(
"In A.<clinit>()", "In A.notInlineable()", "In A.alsoNotInlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
assertThat(notInlineableMethod, isPresent());
MethodSubject alsoNotInlineableMethod = classA.uniqueMethodWithName("alsoNotInlineable");
assertThat(alsoNotInlineableMethod, isPresent());
MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
assertThat(testMethod, isPresent());
assertThat(testMethod, invokesMethod(notInlineableMethod));
assertThat(testMethod, invokesMethod(alsoNotInlineableMethod));
}
@Test
public void testClass9() throws Exception {
Class<TestClass9> mainClass = TestClass9.class;
CodeInspector inspector =
buildAndRun(
mainClass,
StringUtils.lines("In A.<clinit>()", "Field A.staticField", "In A.notInlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject notInlineableMethod = classA.uniqueMethodWithName("notInlineable");
assertThat(notInlineableMethod, isPresent());
MethodSubject testMethod = inspector.clazz(mainClass).mainMethod();
assertThat(testMethod, isPresent());
assertThat(testMethod, invokesMethod(notInlineableMethod));
}
@Test
public void testClass10() throws Exception {
Class<TestClass10> mainClass = TestClass10.class;
CodeInspector inspector =
buildAndRun(mainClass, StringUtils.lines("In A.<clinit>()", "In A.inlineable()"));
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject inlineableMethod = classA.uniqueMethodWithName("inlineable");
assertThat(inlineableMethod, not(isPresent()));
}
private CodeInspector buildAndRun(Class<?> mainClass, String expectedOutput) throws Exception {
if (parameters.isCfRuntime()) {
testForJvm()
.addTestClasspath()
.run(parameters.getRuntime(), mainClass)
.assertSuccessWithOutput(expectedOutput);
}
return testForR8(parameters.getBackend())
.addInnerClasses(InliningAfterClassInitializationTest.class)
.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
.addKeepMainRule(mainClass)
.addOptionsModification(
options -> options.enableInliningOfInvokesWithClassInitializationSideEffects = false)
.enableConstantArgumentAnnotations()
.enableUnusedArgumentAnnotations()
.enableInliningAnnotations()
.setMinApi(parameters.getApiLevel())
.run(parameters.getRuntime(), mainClass)
.assertSuccessWithOutput(expectedOutput)
.inspector();
}
static class TestClass1 {
public static void main(String[] args) {
A.notInlineable();
// Since the previous call will cause the initializer of A to run, we can safely inline this
// call.
A.inlineable();
}
}
static class TestClass2 {
public static void main(String[] args) {
System.out.println(A.staticField);
// Since the previous instruction will cause the initializer of A to run, we can safely inline
// this call.
A.inlineable();
}
}
static class TestClass3 {
public static void main(String[] args) {
A.staticField = "Hello world!";
// Since the previous instruction will cause the initializer of A to run, we can safely inline
// this call.
A.inlineable();
// Make sure the field is read to prevent the static-put instruction from being removed
// (b/123553485).
spuriousFieldRead();
}
private static void spuriousFieldRead() {
if (System.currentTimeMillis() < 0) {
System.out.println(A.staticField);
}
}
}
static class TestClass4 {
public static void main(String[] args) {
test(new A());
}
@NeverInline
private static void test(A obj) {
System.out.println(obj.instanceField);
// Since the previous instruction will cause the initializer of A to run, we can safely inline
// this call.
A.inlineable();
}
}
static class TestClass5 {
public static void main(String[] args) {
test(new A());
}
@NeverInline
private static void test(A obj) {
obj.instanceField = "Hello world!";
// Since the previous instruction will cause the initializer of A to run, we can safely inline
// this call.
A.inlineable();
// Make sure the field is read to prevent the instance-put instruction from being removed.
spuriousFieldRead(obj);
}
private static void spuriousFieldRead(A obj) {
if (System.currentTimeMillis() < 0) {
System.out.println(obj.instanceField);
}
}
}
static class TestClass6 {
public static void main(String[] args) {
test(null);
}
@KeepConstantArguments
@KeepUnusedArguments
@NeverInline
private static void test(A obj) {
try {
String value = obj.instanceField;
System.out.println(value);
} catch (NullPointerException e) {
// Ignore.
}
// This call cannot be inlined because `obj.field` may throw a NullPointerException, in which
// case A is not guaranteed to be initialized.
A.notInlineable();
}
}
static class TestClass7 {
public static void main(String[] args) {
test(null);
test(new A());
}
@NeverInline
private static void test(A obj) {
try {
String value = obj.instanceField;
System.out.println(value);
} catch (NullPointerException e) {
// Ignore.
System.out.println("Caught NullPointerException");
return;
}
// Due to the `return` in the catch handler above, A is guaranteed to be initialized if we
// reach this line.
A.inlineable();
}
}
static class TestClass8 {
public static void main(String[] args) {
try {
A.notInlineable();
} catch (ExceptionInInitializerError e) {
System.out.println("Caught ExceptionInInitializerError");
}
A.alsoNotInlineable();
}
}
static class TestClass9 {
public static void main(String[] args) {
try {
String value = A.staticField;
System.out.println(value);
} catch (ExceptionInInitializerError e) {
System.out.println("Caught ExceptionInInitializerError");
}
A.notInlineable();
}
}
static class TestClass10 {
public static void main(String[] args) {
test(new A());
}
@NeverInline
private static void test(A obj) {
Objects.requireNonNull(obj);
// A is guaranteed to be initialized since requireNonNull() will throw if `obj` is null.
A.inlineable();
}
}
static class A {
public String instanceField = "Field A.instanceField";
public static String staticField =
System.currentTimeMillis() >= 0 ? "Field A.staticField" : null;
static {
System.out.println("In A.<clinit>()");
}
static void notInlineable() {
System.out.println("In A.notInlineable()");
}
static void alsoNotInlineable() {
System.out.println("In A.alsoNotInlineable()");
}
static void inlineable() {
System.out.println("In A.inlineable()");
}
}
}