Initial class inlining of proto builders
Bug: 142772856, 142762134, 145903274
Change-Id: I24a19fbb359d3520926aafe19b10f386c3ff7d08
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 279bd8d..9afba98 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -335,6 +335,8 @@
continue;
}
}
+ assert !isExtraMethodCall(invoke);
+ return user; // Not eligible.
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 8f880b6..614258b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -18,6 +18,10 @@
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_OF;
import static com.android.tools.r8.ir.code.Opcodes.INSTANCE_PUT;
import static com.android.tools.r8.ir.code.Opcodes.INVOKE_DIRECT;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_INTERFACE;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_STATIC;
+import static com.android.tools.r8.ir.code.Opcodes.INVOKE_VIRTUAL;
+import static com.android.tools.r8.ir.code.Opcodes.NEW_INSTANCE;
import static com.android.tools.r8.ir.code.Opcodes.RETURN;
import static com.android.tools.r8.ir.code.Opcodes.STATIC_GET;
import static com.android.tools.r8.ir.code.Opcodes.THROW;
@@ -49,6 +53,8 @@
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionIterator;
import com.android.tools.r8.ir.code.InvokeDirect;
+import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Return;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.DynamicTypeOptimization;
@@ -396,6 +402,7 @@
|| instancePut.instructionInstanceCanThrow(appView, clazz.type).isThrowing()) {
builder.setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
}
+
Value value = instancePut.value().getAliasedValue();
// TODO(b/142762134): Replace the use of onlyDependsOnArgument() by
// ValueMayDependOnEnvironmentAnalysis.
@@ -429,8 +436,16 @@
builder.merge(singleTarget.getOptimizationInfo().getInstanceInitializerInfo());
for (int i = 1; i < invoke.arguments().size(); i++) {
Value argument = invoke.arguments().get(i).getAliasedValue();
- if (argument == receiver || !argument.onlyDependsOnArgument()) {
- return null;
+ if (argument == receiver) {
+ // In the analysis of the parent constructor, we don't consider the non-receiver
+ // arguments as being aliases of the receiver. Therefore, we explicitly mark
+ // that the receiver escapes from this constructor.
+ builder.setReceiverMayEscapeOutsideConstructorChain();
+ }
+ if (!argument.onlyDependsOnArgument()) {
+ // If the parent constructor assigns this argument into a field, then the value
+ // of the field may depend on the environment.
+ builder.setInstanceFieldInitializationMayDependOnEnvironment();
}
}
builder.setParent(invokedMethod);
@@ -448,6 +463,31 @@
}
break;
+ case INVOKE_INTERFACE:
+ case INVOKE_STATIC:
+ case INVOKE_VIRTUAL:
+ InvokeMethod invoke = instruction.asInvokeMethod();
+ builder.markAllFieldsAsRead().setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ for (Value inValue : invoke.inValues()) {
+ if (inValue.getAliasedValue() == receiver) {
+ builder.setReceiverMayEscapeOutsideConstructorChain();
+ break;
+ }
+ }
+ break;
+
+ case NEW_INSTANCE:
+ {
+ NewInstance newInstance = instruction.asNewInstance();
+ if (newInstance.instructionMayHaveSideEffects(appView, clazz.type)) {
+ // It could trigger a class initializer.
+ builder
+ .markAllFieldsAsRead()
+ .setMayHaveOtherSideEffectsThanInstanceFieldAssignments();
+ }
+ }
+ break;
+
default:
builder
.markAllFieldsAsRead()
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 249e39e..c188c87 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -6,7 +6,6 @@
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
@@ -27,7 +26,9 @@
@Parameters(name = "{1}, minification: {0}")
public static List<Object[]> data() {
- return buildParameters(BooleanUtils.values(), getTestParameters().withDexRuntimes().build());
+ return buildParameters(
+ BooleanUtils.values(),
+ getTestParameters().withDexRuntimes().withAllRuntimesAndApiLevels().build());
}
public KeepNonVisibilityBridgeMethodsTest(boolean minification, TestParameters parameters) {
@@ -50,12 +51,13 @@
" synthetic void registerObserver(...);",
"}")
.allowAccessModification()
+ .enableClassInliningAnnotations()
// TODO(b/120764902): MemberSubject.getOriginalName() is not working without the @NeverMerge
// annotation on DataAdapter.Observer.
.enableMergeAnnotations()
.enableProguardTestOptions()
.minification(minification)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.compile()
.inspect(
inspector -> {
@@ -63,7 +65,7 @@
assertThat(classSubject, isPresent());
MethodSubject subject = classSubject.uniqueMethodWithName("registerObserver");
- assertTrue(subject.isPresent());
+ assertThat(subject, isPresent());
})
.run(parameters.getRuntime(), Main.class)
.assertSuccess();
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
index 148a61e..cc60012 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleDataAdapter.java
@@ -4,8 +4,10 @@
package com.android.tools.r8.bridgeremoval.bridgestokeep;
-public class SimpleDataAdapter
- extends SimpleObservableList<DataAdapter.Observer>
+import com.android.tools.r8.NeverClassInline;
+
+@NeverClassInline
+public class SimpleDataAdapter extends SimpleObservableList<DataAdapter.Observer>
implements DataAdapter {
public SimpleDataAdapter() {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
index 3127150..82362aa 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2BuilderShrinkingTest.java
@@ -7,7 +7,6 @@
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.Assume.assumeTrue;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
@@ -80,10 +79,6 @@
.compile()
.inspect(this::inspect);
- // TODO(b/112437944): Should never allow dynamicMethod() to be inlined unless MethodToInvoke is
- // guaranteed to be different from MethodToInvoke.BUILD_MESSAGE_INFO.
- assumeTrue(mains.size() > 1);
-
for (String main : mains) {
result.run(parameters.getRuntime(), main).assertSuccessWithOutput(getExpectedOutput(main));
}
@@ -160,39 +155,19 @@
}
private void inspect(CodeInspector outputInspector) {
- // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
- // is kept.
- assertThat(outputInspector.clazz(LITE_BUILDER), isPresent());
-
- // TODO(b/112437944): Should be absent.
+ boolean primitivesBuilderShouldBeLive =
+ mains.contains("proto2.BuilderWithReusedSettersTestClass");
assertThat(
- outputInspector.clazz("com.android.tools.r8.proto2.TestProto$NestedMessage$Builder"),
- isNestedMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
-
- // TODO(b/112437944): Should be absent.
- assertThat(
- outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
- isOuterMessageBuilderUsed(mains) ? isPresent() : not(isPresent()));
-
- // TODO(b/112437944): Should only be present if proto2.BuilderWithReusedSettersTestClass.main()
- // is kept.
+ outputInspector.clazz(LITE_BUILDER),
+ primitivesBuilderShouldBeLive ? isPresent() : not(isPresent()));
assertThat(
outputInspector.clazz("com.android.tools.r8.proto2.TestProto$Primitives$Builder"),
- isPrimitivesBuilderUsed(mains) ? isPresent() : not(isPresent()));
- }
-
- private static boolean isNestedMessageBuilderUsed(List<String> mains) {
- return mains.contains("proto2.BuilderWithProtoBuilderSetterTestClass")
- || mains.contains("proto2.BuilderWithProtoSetterTestClass");
- }
-
- private static boolean isOuterMessageBuilderUsed(List<String> mains) {
- return isNestedMessageBuilderUsed(mains);
- }
-
- private static boolean isPrimitivesBuilderUsed(List<String> mains) {
- return mains.contains("proto2.BuilderWithOneofSetterTestClass")
- || mains.contains("proto2.BuilderWithPrimitiveSettersTestClass")
- || mains.contains("proto2.BuilderWithReusedSettersTestClass");
+ primitivesBuilderShouldBeLive ? isPresent() : not(isPresent()));
+ assertThat(
+ outputInspector.clazz("com.android.tools.r8.proto2.TestProto$OuterMessage$Builder"),
+ not(isPresent()));
+ assertThat(
+ outputInspector.clazz("com.android.tools.r8.proto2.TestProto$NestedMessage$Builder"),
+ not(isPresent()));
}
}
diff --git a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
index aed2c26..eb62734 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/Proto2ShrinkingTest.java
@@ -79,7 +79,6 @@
.addKeepMainRule("proto2.TestClass")
.addKeepRuleFiles(PROTOBUF_LITE_PROGUARD_RULES)
.addKeepRules(allGeneratedMessageLiteSubtypesAreInstantiatedRule())
- .addKeepRules(alwaysInlineNewSingularGeneratedExtensionRule())
.addOptionsModification(
options -> {
options.enableFieldBitAccessAnalysis = true;
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index 19b3857..a1ecab4 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -77,14 +77,6 @@
"}");
}
- static String alwaysInlineNewSingularGeneratedExtensionRule() {
- return StringUtils.lines(
- "-alwaysinline class com.google.protobuf.GeneratedMessageLite {",
- " com.google.protobuf.GeneratedMessageLite$GeneratedExtension"
- + " newSingularGeneratedExtension(...);",
- "}");
- }
-
static String keepAllProtosRule() {
return StringUtils.lines(
"-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
new file mode 100644
index 0000000..c86e0fc
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.java
@@ -0,0 +1,93 @@
+// 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.classinliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.NeverPropagateValue;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ClassInlineInstanceInitializerWithIndirectEscapingReceiverTest.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .enableMemberValuePropagationAnnotations()
+ .enableMergeAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertThat(inspector.clazz(Candidate.class), isPresent());
+ assertThat(inspector.clazz(CandidateBase.class), isPresent());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println(new Candidate().get());
+ }
+ }
+
+ @NeverMerge
+ static class CandidateBase {
+
+ CandidateBase() {
+ Escape.escape(getReceiver());
+ }
+
+ @NeverInline
+ Object getReceiver() {
+ return System.currentTimeMillis() >= 0 ? this : null;
+ }
+ }
+
+ static class Candidate extends CandidateBase {
+
+ @NeverInline
+ @NeverPropagateValue
+ String get() {
+ return "Hello world!";
+ }
+ }
+
+ static class Escape {
+
+ @NeverInline
+ static void escape(Object o) {
+ if (System.currentTimeMillis() < 0) {
+ System.out.println(o);
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
index a5fbc59..18188ff 100644
--- a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideTest.java
@@ -66,16 +66,7 @@
for (Class<?> nonEscapingClass : nonEscapingClasses) {
ClassSubject classSubject = inspector.clazz(nonEscapingClass);
assertThat(classSubject, isPresent());
-
- // TODO(b/142772856): None of the non-escaping classes should have a toString() method. It is
- // a requirement that the instance initializers are considered trivial for this to work,
- // though, even when they have a side effect (as long as the receiver does not escape via the
- // side effecting instruction).
- if (nonEscapingClass == DoesNotEscapeWithSubThatDoesNotOverrideSub.class) {
- assertThat(classSubject.uniqueMethodWithName("toString"), not(isPresent()));
- } else {
- assertThat(classSubject.uniqueMethodWithName("toString"), isPresent());
- }
+ assertThat(classSubject.uniqueMethodWithName("toString"), not(isPresent()));
}
}
@@ -135,8 +126,6 @@
@NeverClassInline
static class DoesNotEscape {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscape() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -151,8 +140,6 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatDoesNotOverride {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatDoesNotOverride() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -168,8 +155,6 @@
static class DoesNotEscapeWithSubThatDoesNotOverrideSub
extends DoesNotEscapeWithSubThatDoesNotOverride {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatDoesNotOverrideSub() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -179,8 +164,6 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatOverrides {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatOverrides() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -195,8 +178,6 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatOverridesSub extends DoesNotEscapeWithSubThatOverrides {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatOverridesSub() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -215,8 +196,6 @@
@NeverClassInline
static class DoesNotEscapeWithSubThatOverridesAndEscapes {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatOverridesAndEscapes() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");
@@ -232,8 +211,6 @@
static class DoesNotEscapeWithSubThatOverridesAndEscapesSub
extends DoesNotEscapeWithSubThatOverridesAndEscapes {
- // TODO(b/142772856): Should be classified as a trivial instance initializer although it has a
- // side effect.
DoesNotEscapeWithSubThatOverridesAndEscapesSub() {
// Side effect to ensure that the constructor is not removed from main().
System.out.print("");