Add a test for inlining of library method overrides
Change-Id: I204a5823b1b5aaba42bf49ee2cbd1c71c10a60e6
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
index 36b5a83..937b0ca 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallSiteInformation.java
@@ -54,8 +54,12 @@
// For non-pinned methods and methods that override library methods we do not know the exact
// number of call sites.
- if (appView.appInfo().isPinned(method)
- || encodedMethod.isLibraryMethodOverride().isTrue()) {
+ if (appView.appInfo().isPinned(method)) {
+ continue;
+ }
+
+ if (appView.options().disableInliningOfLibraryMethodOverrides
+ && encodedMethod.isLibraryMethodOverride().isTrue()) {
continue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index c45d136..d9e8efe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -76,11 +76,10 @@
}
private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexMethod invocationContext) {
- DexEncodedMethod candidate =
- invoke.lookupSingleTarget(inliner.appView, invocationContext.holder);
+ DexEncodedMethod candidate = invoke.lookupSingleTarget(appView, invocationContext.holder);
if ((candidate == null)
|| (candidate.getCode() == null)
- || inliner.appView.definitionFor(candidate.method.holder).isNotProgramClass()) {
+ || appView.definitionFor(candidate.method.holder).isNotProgramClass()) {
if (info != null) {
info.exclude(invoke, "No inlinee");
}
@@ -100,16 +99,17 @@
private Reason computeInliningReason(DexEncodedMethod target) {
if (target.getOptimizationInfo().forceInline()
- || (inliner.appView.appInfo().hasLiveness()
- && inliner.appView.withLiveness().appInfo().forceInline.contains(target.method))) {
+ || (appView.appInfo().hasLiveness()
+ && appView.withLiveness().appInfo().forceInline.contains(target.method))) {
assert !appView.appInfo().neverInline.contains(target.method);
return Reason.FORCE;
}
- if (inliner.appView.appInfo().hasLiveness()
- && inliner.appView.withLiveness().appInfo().alwaysInline.contains(target.method)) {
+ if (appView.appInfo().hasLiveness()
+ && appView.withLiveness().appInfo().alwaysInline.contains(target.method)) {
return Reason.ALWAYS;
}
- if (target.isLibraryMethodOverride().isTrue()) {
+ if (appView.options().disableInliningOfLibraryMethodOverrides
+ && target.isLibraryMethodOverride().isTrue()) {
// This method will always have an implicit call site from the library, so we won't be able to
// remove it after inlining even if we have single or dual call site information from the
// program.
@@ -140,7 +140,7 @@
if (appView.appInfo().isSubtype(method.method.holder, targetHolder)) {
return true;
}
- DexClass clazz = inliner.appView.definitionFor(targetHolder);
+ DexClass clazz = appView.definitionFor(targetHolder);
assert clazz != null;
if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
return true;
@@ -226,7 +226,7 @@
return false;
}
- DexClass holder = inliner.appView.definitionFor(candidate.method.holder);
+ DexClass holder = appView.definitionFor(candidate.method.holder);
if (holder.isInterface()) {
// Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
@@ -335,7 +335,7 @@
}
Reason reason = computeInliningReason(candidate);
- if (!candidate.isInliningCandidate(method, reason, inliner.appView.appInfo())) {
+ if (!candidate.isInliningCandidate(method, reason, appView.appInfo())) {
// Abort inlining attempt if the single target is not an inlining candidate.
if (info != null) {
info.exclude(invoke, "target is not identified for inlining");
@@ -409,7 +409,7 @@
Reason reason = computeInliningReason(candidate);
// Determine if this should be inlined no matter how big it is.
- if (!candidate.isInliningCandidate(method, reason, inliner.appView.appInfo())) {
+ if (!candidate.isInliningCandidate(method, reason, appView.appInfo())) {
// Abort inlining attempt if the single target is not an inlining candidate.
if (info != null) {
info.exclude(invoke, "target is not identified for inlining");
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 e43b307..1f19169 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -199,6 +199,7 @@
System.getProperty("com.android.tools.r8.disableInliningOfInvokesWithDefinitelyNullReceivers")
== null;
public boolean enableInliningOfInvokesWithNullableReceivers = true;
+ public boolean disableInliningOfLibraryMethodOverrides = true;
public boolean enableClassInlining = true;
public boolean enableClassStaticizer = true;
public boolean enableInitializedClassesAnalysis = true;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java
new file mode 100644
index 0000000..1a70cde
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/LibraryOverrideInliningTest.java
@@ -0,0 +1,104 @@
+// 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.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class LibraryOverrideInliningTest extends TestBase {
+
+ private final boolean disableInliningOfLibraryMethodOverrides;
+ private final TestParameters parameters;
+
+ @Parameters(name = "{1}, disableInliningOfLibraryMethodOverrides: {0}")
+ public static List<Object[]> data() {
+ return buildParameters(BooleanUtils.values(), getTestParameters().withAllRuntimes().build());
+ }
+
+ public LibraryOverrideInliningTest(
+ boolean disableInliningOfLibraryMethodOverrides, TestParameters parameters) {
+ this.disableInliningOfLibraryMethodOverrides = disableInliningOfLibraryMethodOverrides;
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LibraryOverrideInliningTest.class)
+ .addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options ->
+ options.disableInliningOfLibraryMethodOverrides =
+ disableInliningOfLibraryMethodOverrides)
+ .enableClassInliningAnnotations()
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject greeterClassSubject = inspector.clazz(Greeter.class);
+ assertThat(greeterClassSubject, isPresent());
+
+ MethodSubject toStringMethodSubject =
+ greeterClassSubject.uniqueMethodWithName("toString");
+ assertThat(toStringMethodSubject, isPresent());
+
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+
+ MethodSubject mainMethodSubject = testClassSubject.mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+
+ if (disableInliningOfLibraryMethodOverrides) {
+ assertThat(mainMethodSubject, invokesMethod(toStringMethodSubject));
+ } else {
+ assertThat(mainMethodSubject, not(invokesMethod(toStringMethodSubject)));
+ }
+ })
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Greeter().toString());
+ }
+ }
+
+ @NeverClassInline
+ static class Greeter {
+
+ final char[] greeting = new char["Hello world!".length()];
+
+ // A method that returns "Hello world!" and is not a 'SIMPLE' inlining candidate.
+ @Override
+ public String toString() {
+ String greeting = "Hello world!";
+ for (int i = 0; i < greeting.length(); i++) {
+ this.greeting[i] = greeting.charAt(i);
+ }
+ StringBuilder result = new StringBuilder();
+ for (char c : greeting.toCharArray()) {
+ result.append(c);
+ }
+ return result.toString();
+ }
+ }
+}