Assume that class initialization of library interfaces do not have side effects
Bug: 132683206
Change-Id: I3c8d7c113c9e6bb26755847de9076e8a1fa00f72
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index fb96f76..3f3577a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -718,23 +718,30 @@
return appView.appInfo().isExternalizable(type);
}
- public boolean classInitializationMayHaveSideEffects(DexDefinitionSupplier definitions) {
- return classInitializationMayHaveSideEffects(definitions, Predicates.alwaysFalse());
+ public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
+ return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse());
}
public boolean classInitializationMayHaveSideEffects(
- DexDefinitionSupplier definitions, Predicate<DexType> ignore) {
- if (ignore.test(type)
- || definitions.dexItemFactory().libraryTypesWithoutStaticInitialization.contains(type)) {
+ AppView<?> appView, Predicate<DexType> ignore) {
+ if (ignore.test(type)) {
return false;
}
+ if (isLibraryClass()) {
+ if (isInterface()) {
+ return appView.options().libraryInterfacesMayHaveStaticInitialization;
+ }
+ if (appView.dexItemFactory().libraryClassesWithoutStaticInitialization.contains(type)) {
+ return false;
+ }
+ }
if (hasNonTrivialClassInitializer()) {
return true;
}
if (defaultValuesForStaticFieldsMayTriggerAllocation()) {
return true;
}
- return initializationOfParentTypesMayHaveSideEffects(definitions, ignore);
+ return initializationOfParentTypesMayHaveSideEffects(appView, ignore);
}
public Iterable<DexType> allImmediateSupertypes() {
@@ -746,18 +753,18 @@
return () -> iterator;
}
- public boolean initializationOfParentTypesMayHaveSideEffects(DexDefinitionSupplier definitions) {
- return initializationOfParentTypesMayHaveSideEffects(definitions, Predicates.alwaysFalse());
+ public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
+ return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse());
}
public boolean initializationOfParentTypesMayHaveSideEffects(
- DexDefinitionSupplier definitions, Predicate<DexType> ignore) {
+ AppView<?> appView, Predicate<DexType> ignore) {
for (DexType iface : interfaces.values) {
- if (iface.classInitializationMayHaveSideEffects(definitions, ignore)) {
+ if (iface.classInitializationMayHaveSideEffects(appView, ignore)) {
return true;
}
}
- if (superType != null && superType.classInitializationMayHaveSideEffects(definitions, ignore)) {
+ if (superType != null && superType.classInitializationMayHaveSideEffects(appView, ignore)) {
return true;
}
return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index c1c8cc6..cb9d725 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -419,9 +419,8 @@
public Set<DexType> libraryTypesAssumedToBePresent =
ImmutableSet.of(objectType, stringBufferType, stringBuilderType);
- public final Set<DexType> libraryTypesWithoutStaticInitialization =
- ImmutableSet.of(
- iteratorType, objectType, serializableType, stringBufferType, stringBuilderType);
+ public final Set<DexType> libraryClassesWithoutStaticInitialization =
+ ImmutableSet.of(objectType, stringBufferType, stringBuilderType);
private boolean skipNameValidationForTesting = false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 801d6f8..51c6825 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -45,24 +45,24 @@
return false;
}
- public boolean classInitializationMayHaveSideEffects(DexDefinitionSupplier definitions) {
- return classInitializationMayHaveSideEffects(definitions, Predicates.alwaysFalse());
+ public boolean classInitializationMayHaveSideEffects(AppView<?> appView) {
+ return classInitializationMayHaveSideEffects(appView, Predicates.alwaysFalse());
}
public boolean classInitializationMayHaveSideEffects(
- DexDefinitionSupplier definitions, Predicate<DexType> ignore) {
- DexClass clazz = definitions.definitionFor(this);
- return clazz == null || clazz.classInitializationMayHaveSideEffects(definitions, ignore);
+ AppView<?> appView, Predicate<DexType> ignore) {
+ DexClass clazz = appView.definitionFor(this);
+ return clazz == null || clazz.classInitializationMayHaveSideEffects(appView, ignore);
}
- public boolean initializationOfParentTypesMayHaveSideEffects(AppInfo appInfo) {
- return initializationOfParentTypesMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+ public boolean initializationOfParentTypesMayHaveSideEffects(AppView<?> appView) {
+ return initializationOfParentTypesMayHaveSideEffects(appView, Predicates.alwaysFalse());
}
public boolean initializationOfParentTypesMayHaveSideEffects(
- AppInfo appInfo, Predicate<DexType> ignore) {
- DexClass clazz = appInfo.definitionFor(this);
- return clazz == null || clazz.initializationOfParentTypesMayHaveSideEffects(appInfo, ignore);
+ AppView<?> appView, Predicate<DexType> ignore) {
+ DexClass clazz = appView.definitionFor(this);
+ return clazz == null || clazz.initializationOfParentTypesMayHaveSideEffects(appView, ignore);
}
public boolean isAlwaysNull(AppView<AppInfoWithLiveness> appView) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 93e98db..56f852b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -177,7 +177,7 @@
target.getOptimizationInfo().mayHaveSideEffects()
// Verify that calling the target method won't lead to class initialization.
|| target.method.holder.classInitializationMayHaveSideEffects(
- appView.appInfo(),
+ appView,
// Types that are a super type of `context` are guaranteed to be initialized
// already.
type -> appView.isSubtype(context, type).isTrue());
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 d255669..4cf7120 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
@@ -153,7 +153,7 @@
//
// For simplicity, we are conservative and consider all interfaces, not only the ones with
// default methods.
- return !clazz.classInitializationMayHaveSideEffects(appView.appInfo());
+ return !clazz.classInitializationMayHaveSideEffects(appView);
}
private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 615b4c1..dd1064c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -330,7 +330,7 @@
DexClass holderDefinition = appView.definitionFor(field.holder);
if (holderDefinition != null
&& holderDefinition.accessFlags.isFinal()
- && !field.holder.initializationOfParentTypesMayHaveSideEffects(appView.appInfo())) {
+ && !field.holder.initializationOfParentTypesMayHaveSideEffects(appView)) {
Value outValue = current.dest();
DexEncodedMethod classInitializer = holderDefinition.getClassInitializer();
if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index ec95f89..650d737 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -493,7 +493,7 @@
if (field.field.holder != code.method.method.holder) {
DexClass enclosingClass = appView.definitionFor(code.method.method.holder);
if (enclosingClass == null
- || enclosingClass.classInitializationMayHaveSideEffects(appView.appInfo())) {
+ || enclosingClass.classInitializationMayHaveSideEffects(appView)) {
return;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 5140cfd..1e85790 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -233,6 +233,6 @@
}
// Check for static initializers in this class or any of interfaces it implements.
- return !clazz.initializationOfParentTypesMayHaveSideEffects(appView.appInfo());
+ return !clazz.initializationOfParentTypesMayHaveSideEffects(appView);
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 8b4bedb..b526695 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -284,7 +284,7 @@
|| appView.appInfo().noSideEffects.keySet().contains(method.method))) {
return MergeGroup.DONT_MERGE;
}
- if (clazz.classInitializationMayHaveSideEffects(appView.appInfo())) {
+ if (clazz.classInitializationMayHaveSideEffects(appView)) {
// This could have a negative impact on inlining.
//
// See {@link com.android.tools.r8.ir.optimize.DefaultInliningOracle#canInlineStaticInvoke}
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 8383f5c..085acee 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -429,8 +429,8 @@
// We basically can't move the clinit, since it is not called when implementing classes have
// their clinit called - except when the interface has a default method.
if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
- || targetClass.classInitializationMayHaveSideEffects(appInfo, type -> type == clazz.type)
- || (clazz.isInterface() && clazz.classInitializationMayHaveSideEffects(appInfo))) {
+ || targetClass.classInitializationMayHaveSideEffects(appView, type -> type == clazz.type)
+ || (clazz.isInterface() && clazz.classInitializationMayHaveSideEffects(appView))) {
// TODO(herhut): Handle class initializers.
if (Log.ENABLED) {
AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
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 ef1fb01..4d0de15 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -141,6 +141,8 @@
// Flag to toggle if DEX code objects should pass-through without IR processing.
public boolean passthroughDexCode = false;
+ public boolean libraryInterfacesMayHaveStaticInitialization = false;
+
// Optimization-related flags. These should conform to -dontoptimize and disableAllOptimizations.
public boolean enableDynamicTypeOptimization = true;
public boolean enableHorizontalClassMerging = true;
diff --git a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
index fd5e09a..d59f954 100644
--- a/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/InliningAfterStaticClassMergerTest.java
@@ -78,11 +78,12 @@
CodeInspector inspector =
testForR8(parameters.getBackend())
.addProgramClasses(
- TestClass.class,
- StaticMergeCandidateA.class,
- StaticMergeCandidateB.class)
+ TestClass.class, StaticMergeCandidateA.class, StaticMergeCandidateB.class)
.addKeepMainRule(TestClass.class)
+ .addOptionsModification(
+ options -> options.libraryInterfacesMayHaveStaticInitialization = true)
.noMinification()
+ .setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(expected)
.inspector();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/initializedclasses/LibraryClassInitializationTest.java b/src/test/java/com/android/tools/r8/ir/analysis/initializedclasses/LibraryClassInitializationTest.java
new file mode 100644
index 0000000..b2ddc66
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/initializedclasses/LibraryClassInitializationTest.java
@@ -0,0 +1,56 @@
+// 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.analysis.initializedclasses;
+
+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.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.Serializable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LibraryClassInitializationTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().build();
+ }
+
+ public LibraryClassInitializationTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ CodeInspector inspector =
+ testForR8(parameters.getBackend())
+ .addInnerClasses(LibraryClassInitializationTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getRuntime())
+ .compile()
+ .inspector();
+ assertThat(inspector.clazz(ClassWithoutClassInitialization.class), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ // The instantiation can only be removed if we assume that the class initialization of
+ // Serializable may not have side effects.
+ new ClassWithoutClassInitialization();
+ }
+ }
+
+ static class ClassWithoutClassInitialization implements Serializable {}
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java b/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
index 9ef1a4a..8bb936d 100644
--- a/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
+++ b/src/test/java/com/android/tools/r8/naming/NonMemberClassTest.java
@@ -113,6 +113,8 @@
.addKeepAttributes("InnerClasses", "EnclosingMethod")
.enableInliningAnnotations()
.setMinApi(parameters.getRuntime())
+ .addOptionsModification(options -> options.enableClassInlining = false)
+ .compile()
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(EXPECTED_OUTPUT)
.inspect(this::inspect);