Initial experimental support for horizontal interface merging
Bug: 173990042
Change-Id: I6591e22b2f8ba9788b6e57187bcf6d74c51e46f5
diff --git a/src/main/java/com/android/tools/r8/graph/DexTypeList.java b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
index 07410c0..684dd7d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexTypeList.java
+++ b/src/main/java/com/android/tools/r8/graph/DexTypeList.java
@@ -16,6 +16,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
@@ -66,6 +67,14 @@
return this;
}
+ public DexTypeList map(Function<DexType, DexType> fn) {
+ if (isEmpty()) {
+ return DexTypeList.empty();
+ }
+ DexType[] newTypes = ArrayUtils.map(values, fn, DexType.EMPTY_ARRAY);
+ return newTypes != values ? create(newTypes) : this;
+ }
+
public DexTypeList removeIf(Predicate<DexType> predicate) {
return keepIf(not(predicate));
}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 686d7d5..457ef38 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.graph;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.ir.desugar.LambdaDescriptor;
import com.android.tools.r8.shaking.GraphReporter;
@@ -461,9 +462,8 @@
assert false;
return;
}
- assert !instantiatedLambdas.containsKey(type);
// TODO(b/150277553): Rewrite lambda descriptor.
- instantiatedLambdas.put(type, lambdas);
+ instantiatedLambdas.computeIfAbsent(type, ignoreKey(ArrayList::new)).addAll(lambdas);
});
return this;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
index 880890d..23fc39a 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/ClassMerger.java
@@ -35,6 +35,7 @@
import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
import com.android.tools.r8.shaking.KeepClassInfo;
import com.android.tools.r8.utils.IterableUtils;
+import com.android.tools.r8.utils.SetUtils;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
@@ -247,10 +248,25 @@
private void mergeInterfaces() {
DexTypeList previousInterfaces = group.getTarget().getInterfaces();
Set<DexType> interfaces = Sets.newLinkedHashSet(previousInterfaces);
- group.forEachSource(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
- if (interfaces.size() > previousInterfaces.size()) {
- group.getTarget().setInterfaces(new DexTypeList(interfaces));
+ if (group.isInterfaceGroup()) {
+ // Add all implemented interfaces from the merge group to the target class, ignoring
+ // implemented interfaces that are part of the merge group.
+ Set<DexType> groupTypes =
+ SetUtils.newImmutableSet(
+ builder -> group.forEach(clazz -> builder.accept(clazz.getType())));
+ group.forEachSource(
+ clazz -> {
+ for (DexType itf : clazz.getInterfaces()) {
+ if (!groupTypes.contains(itf)) {
+ interfaces.add(itf);
+ }
+ }
+ });
+ } else {
+ // Add all implemented interfaces from the merge group to the target class.
+ group.forEachSource(clazz -> Iterables.addAll(interfaces, clazz.getInterfaces()));
}
+ group.getTarget().setInterfaces(DexTypeList.create(interfaces));
}
void mergeInstanceFields() {
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
index eff9460..c94039b 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/HorizontalClassMerger.java
@@ -17,21 +17,23 @@
import com.android.tools.r8.horizontalclassmerging.policies.NoClassAnnotationCollisions;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassInitializerWithObservableSideEffects;
import com.android.tools.r8.horizontalclassmerging.policies.NoDeadEnumLiteMaps;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodCollisions;
+import com.android.tools.r8.horizontalclassmerging.policies.NoDefaultInterfaceMethodMerging;
import com.android.tools.r8.horizontalclassmerging.policies.NoDirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoEnums;
import com.android.tools.r8.horizontalclassmerging.policies.NoIndirectRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoInstanceFieldAnnotations;
-import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
import com.android.tools.r8.horizontalclassmerging.policies.NoKotlinMetadata;
import com.android.tools.r8.horizontalclassmerging.policies.NoNativeMethods;
import com.android.tools.r8.horizontalclassmerging.policies.NoServiceLoaders;
import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
+import com.android.tools.r8.horizontalclassmerging.policies.OnlyDirectlyConnectedOrUnrelatedInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.PreserveMethodCharacteristics;
+import com.android.tools.r8.horizontalclassmerging.policies.PreventClassMethodAndDefaultMethodCollisions;
import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoDifferentMainDexGroups;
-import com.android.tools.r8.horizontalclassmerging.policies.PreventMethodImplementation;
import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
import com.android.tools.r8.horizontalclassmerging.policies.SameInstanceFields;
@@ -45,7 +47,7 @@
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
+import java.util.LinkedList;
import java.util.List;
public class HorizontalClassMerger {
@@ -68,12 +70,9 @@
}
public HorizontalClassMergerResult run(RuntimeTypeCheckInfo runtimeTypeCheckInfo, Timing timing) {
- MergeGroup initialGroup = new MergeGroup(appView.appInfo().classesWithDeterministicOrder());
-
// Run the policies on all program classes to produce a final grouping.
List<Policy> policies = getPolicies(runtimeTypeCheckInfo);
- Collection<MergeGroup> groups =
- new PolicyExecutor().run(Collections.singletonList(initialGroup), policies, timing);
+ Collection<MergeGroup> groups = new PolicyExecutor().run(getInitialGroups(), policies, timing);
// If there are no groups, then end horizontal class merging.
if (groups.isEmpty()) {
@@ -126,6 +125,25 @@
return builder.build();
}
+ private List<MergeGroup> getInitialGroups() {
+ MergeGroup initialClassGroup = new MergeGroup();
+ MergeGroup initialInterfaceGroup = new MergeGroup();
+ for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
+ if (clazz.isInterface()) {
+ if (appView.options().horizontalClassMergerOptions().isInterfaceMergingEnabled()) {
+ initialInterfaceGroup.add(clazz);
+ }
+ } else {
+ initialClassGroup.add(clazz);
+ }
+ }
+ List<MergeGroup> initialGroups = new LinkedList<>();
+ initialGroups.add(initialClassGroup);
+ initialGroups.add(initialInterfaceGroup);
+ initialGroups.removeIf(MergeGroup::isTrivial);
+ return initialGroups;
+ }
+
private List<Policy> getPolicies(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
List<SingleClassPolicy> singleClassPolicies =
@@ -136,23 +154,22 @@
new NoEnums(appView),
new NoInnerClasses(),
new NoInstanceFieldAnnotations(),
- new NoInterfaces(),
new NoClassInitializerWithObservableSideEffects(),
new NoNativeMethods(),
new NoKeepRules(appView),
new NoKotlinMetadata(),
new NoServiceLoaders(appView),
new NotVerticallyMergedIntoSubtype(appView),
- new NoDirectRuntimeTypeChecks(runtimeTypeCheckInfo),
+ new NoDirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
new DontInlinePolicy(appViewWithLiveness));
- List<MultiClassPolicy> multiClassPolicies =
+ List<Policy> multiClassPolicies =
ImmutableList.of(
new SameInstanceFields(appView),
new NoClassAnnotationCollisions(),
new CheckAbstractClasses(appView),
new SyntheticItemsPolicy(appView),
new NoIndirectRuntimeTypeChecks(appView, runtimeTypeCheckInfo),
- new PreventMethodImplementation(appView),
+ new PreventClassMethodAndDefaultMethodCollisions(appView),
new PreventMergeIntoDifferentMainDexGroups(appView),
new AllInstantiatedOrUninstantiated(appViewWithLiveness),
new SameParentClass(),
@@ -162,6 +179,9 @@
new RespectPackageBoundaries(appView),
new DontMergeSynchronizedClasses(appViewWithLiveness),
new MinimizeFieldCasts(),
+ new OnlyDirectlyConnectedOrUnrelatedInterfaces(appView),
+ new NoDefaultInterfaceMethodMerging(appView),
+ new NoDefaultInterfaceMethodCollisions(appView),
new LimitGroups(appView));
return ImmutableList.<Policy>builder()
.addAll(singleClassPolicies)
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
index b99cb2f..5c7e60f 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MergeGroup.java
@@ -125,6 +125,11 @@
return size() < 2;
}
+ public boolean isNonTrivial() {
+ return !isTrivial();
+ }
+
+ @Override
public boolean isEmpty() {
return classes.isEmpty();
}
@@ -140,6 +145,7 @@
return classes.iterator();
}
+ @Override
public int size() {
return classes.size();
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
index b21f233..63420fe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicy.java
@@ -4,21 +4,11 @@
package com.android.tools.r8.horizontalclassmerging;
-import java.util.ArrayList;
import java.util.Collection;
public abstract class MultiClassPolicy extends Policy {
/**
- * Remove all groups containing no or only a single class, as there is no point in merging these.
- */
- protected Collection<MergeGroup> removeTrivialGroups(Collection<MergeGroup> groups) {
- assert !(groups instanceof ArrayList);
- groups.removeIf(MergeGroup::isTrivial);
- return groups;
- }
-
- /**
* Apply the multi class policy to a group of program classes.
*
* @param group This is a group of program classes which can currently still be merged.
@@ -27,4 +17,14 @@
* cannot be merged with any other classes they are returned as singleton lists.
*/
public abstract Collection<MergeGroup> apply(MergeGroup group);
+
+ @Override
+ public boolean isMultiClassPolicy() {
+ return true;
+ }
+
+ @Override
+ public MultiClassPolicy asMultiClassPolicy() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
index e06a7ef..067fcd5 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/MultiClassPolicyWithPreprocessing.java
@@ -20,4 +20,14 @@
public abstract Collection<MergeGroup> apply(MergeGroup group, T data);
public abstract T preprocess(Collection<MergeGroup> groups);
+
+ @Override
+ public boolean isMultiClassPolicyWithPreprocessing() {
+ return true;
+ }
+
+ @Override
+ public MultiClassPolicyWithPreprocessing<?> asMultiClassPolicyWithPreprocessing() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
index b984ed5..e5854fe 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/Policy.java
@@ -4,19 +4,78 @@
package com.android.tools.r8.horizontalclassmerging;
+import java.util.ArrayList;
+import java.util.Collection;
+
/**
* The super class of all horizontal class merging policies. Most classes will either implement
* {@link SingleClassPolicy} or {@link MultiClassPolicy}.
*/
public abstract class Policy {
+
/** Counter keeping track of how many classes this policy has removed. For debugging only. */
public int numberOfRemovedClasses;
+ public int numberOfRemovedInterfaces;
+
public void clear() {}
public abstract String getName();
+ public boolean isSingleClassPolicy() {
+ return false;
+ }
+
+ public SingleClassPolicy asSingleClassPolicy() {
+ return null;
+ }
+
+ public boolean isMultiClassPolicy() {
+ return false;
+ }
+
+ public MultiClassPolicy asMultiClassPolicy() {
+ return null;
+ }
+
+ public boolean isMultiClassPolicyWithPreprocessing() {
+ return false;
+ }
+
+ public MultiClassPolicyWithPreprocessing<?> asMultiClassPolicyWithPreprocessing() {
+ return null;
+ }
+
public boolean shouldSkipPolicy() {
return false;
}
+
+ /**
+ * Remove all groups containing no or only a single class, as there is no point in merging these.
+ */
+ protected Collection<MergeGroup> removeTrivialGroups(Collection<MergeGroup> groups) {
+ assert !(groups instanceof ArrayList);
+ groups.removeIf(MergeGroup::isTrivial);
+ return groups;
+ }
+
+ boolean recordRemovedClassesForDebugging(
+ boolean isInterfaceGroup, int previousGroupSize, Collection<MergeGroup> newGroups) {
+ assert previousGroupSize >= 2;
+ int previousNumberOfRemovedClasses = previousGroupSize - 1;
+ int newNumberOfRemovedClasses = 0;
+ for (MergeGroup newGroup : newGroups) {
+ if (newGroup.isNonTrivial()) {
+ newNumberOfRemovedClasses += newGroup.size() - 1;
+ }
+ }
+ assert previousNumberOfRemovedClasses >= newNumberOfRemovedClasses;
+ int change = previousNumberOfRemovedClasses - newNumberOfRemovedClasses;
+ if (isInterfaceGroup) {
+ numberOfRemovedInterfaces += change;
+ } else {
+ numberOfRemovedClasses += change;
+ }
+ return true;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
index 33b6f7f..c1c4f64 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/PolicyExecutor.java
@@ -4,8 +4,8 @@
package com.android.tools.r8.horizontalclassmerging;
-import com.android.tools.r8.utils.IterableUtils;
import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
@@ -17,15 +17,16 @@
*/
public class PolicyExecutor {
- // TODO(b/165506334): if performing mutable operation ensure that linked lists are used
private void applySingleClassPolicy(SingleClassPolicy policy, LinkedList<MergeGroup> groups) {
Iterator<MergeGroup> i = groups.iterator();
while (i.hasNext()) {
MergeGroup group = i.next();
- int previousNumberOfClasses = group.size();
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
group.removeIf(clazz -> !policy.canMerge(clazz));
- policy.numberOfRemovedClasses += previousNumberOfClasses - group.size();
- if (group.size() < 2) {
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, ImmutableList.of(group));
+ if (group.isTrivial()) {
i.remove();
}
}
@@ -37,11 +38,30 @@
LinkedList<MergeGroup> newGroups = new LinkedList<>();
groups.forEach(
group -> {
- int previousNumberOfClasses = group.size();
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
Collection<MergeGroup> policyGroups = policy.apply(group);
policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
- policy.numberOfRemovedClasses +=
- previousNumberOfClasses - IterableUtils.sumInt(policyGroups, MergeGroup::size);
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, policyGroups);
+ newGroups.addAll(policyGroups);
+ });
+ return newGroups;
+ }
+
+ private <T> LinkedList<MergeGroup> applyMultiClassPolicyWithPreprocessing(
+ MultiClassPolicyWithPreprocessing<T> policy, LinkedList<MergeGroup> groups) {
+ // For each group apply the multi class policy and add all the new groups together.
+ T data = policy.preprocess(groups);
+ LinkedList<MergeGroup> newGroups = new LinkedList<>();
+ groups.forEach(
+ group -> {
+ boolean isInterfaceGroup = group.isInterfaceGroup();
+ int previousGroupSize = group.size();
+ Collection<MergeGroup> policyGroups = policy.apply(group, data);
+ policyGroups.forEach(newGroup -> newGroup.applyMetadataFrom(group));
+ assert policy.recordRemovedClassesForDebugging(
+ isInterfaceGroup, previousGroupSize, policyGroups);
newGroups.addAll(policyGroups);
});
return newGroups;
@@ -68,11 +88,15 @@
}
timing.begin(policy.getName());
- if (policy instanceof SingleClassPolicy) {
- applySingleClassPolicy((SingleClassPolicy) policy, linkedGroups);
+ if (policy.isSingleClassPolicy()) {
+ applySingleClassPolicy(policy.asSingleClassPolicy(), linkedGroups);
+ } else if (policy.isMultiClassPolicy()) {
+ linkedGroups = applyMultiClassPolicy(policy.asMultiClassPolicy(), linkedGroups);
} else {
- assert policy instanceof MultiClassPolicy;
- linkedGroups = applyMultiClassPolicy((MultiClassPolicy) policy, linkedGroups);
+ assert policy.isMultiClassPolicyWithPreprocessing();
+ linkedGroups =
+ applyMultiClassPolicyWithPreprocessing(
+ policy.asMultiClassPolicyWithPreprocessing(), linkedGroups);
}
timing.end();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
index b0757c5..db2d8eb 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SingleClassPolicy.java
@@ -13,4 +13,14 @@
* @return {@code false} if the class should not be merged, otherwise {@code true}.
*/
public abstract boolean canMerge(DexProgramClass program);
+
+ @Override
+ public boolean isSingleClassPolicy() {
+ return true;
+ }
+
+ @Override
+ public SingleClassPolicy asSingleClassPolicy() {
+ return this;
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 53ca8c8..34c7362 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
import com.android.tools.r8.graph.TreeFixerBase;
import com.android.tools.r8.ir.conversion.ExtraUnusedNullParameter;
import com.android.tools.r8.shaking.AnnotationFixer;
@@ -115,8 +116,7 @@
public HorizontalClassMergerGraphLens fixupTypeReferences() {
List<DexProgramClass> classes = appView.appInfo().classesWithDeterministicOrder();
Iterables.filter(classes, DexProgramClass::isInterface).forEach(this::fixupInterfaceClass);
-
- classes.forEach(this::fixupProgramClassSuperType);
+ classes.forEach(this::fixupProgramClassSuperTypes);
SubtypingForrestForClasses subtypingForrest = new SubtypingForrestForClasses(appView);
// TODO(b/170078037): parallelize this code segment.
for (DexProgramClass root : subtypingForrest.getProgramRoots()) {
@@ -127,8 +127,9 @@
return lens;
}
- private void fixupProgramClassSuperType(DexProgramClass clazz) {
+ private void fixupProgramClassSuperTypes(DexProgramClass clazz) {
clazz.superType = fixupType(clazz.superType);
+ clazz.setInterfaces(fixupInterfaces(clazz, clazz.getInterfaces()));
}
private BiMap<DexMethodSignature, DexMethodSignature> fixupProgramClass(
@@ -197,10 +198,6 @@
private void fixupInterfaceClass(DexProgramClass iface) {
Set<DexMethodSignature> newDirectMethods = new LinkedHashSet<>();
-
- assert iface.superType == dexItemFactory.objectType;
- iface.superType = mergedClasses.getMergeTargetOrDefault(iface.superType);
-
iface
.getMethodCollection()
.replaceDirectMethods(method -> fixupDirectMethod(newDirectMethods, method));
@@ -210,6 +207,16 @@
lensBuilder.commitPendingUpdates();
}
+ private DexTypeList fixupInterfaces(DexProgramClass clazz, DexTypeList interfaceTypes) {
+ Set<DexType> seen = Sets.newIdentityHashSet();
+ return interfaceTypes.map(
+ interfaceType -> {
+ DexType rewrittenInterfaceType = mapClassType(interfaceType);
+ assert rewrittenInterfaceType != clazz.getType();
+ return seen.add(rewrittenInterfaceType) ? rewrittenInterfaceType : null;
+ });
+ }
+
private DexEncodedMethod fixupProgramMethod(
DexMethod newMethodReference, DexEncodedMethod method) {
DexMethod originalMethodReference = method.getReference();
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
index 0851469..11f8867 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDefaultInterfaceMethodCollisions.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
@@ -197,7 +198,7 @@
});
inheritedClassMethodsPerClass
.keySet()
- .removeIf(type -> !appView.definitionFor(type).isProgramClass());
+ .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
return inheritedClassMethodsPerClass;
}
@@ -252,7 +253,7 @@
});
inheritedDefaultMethodsPerType
.keySet()
- .removeIf(type -> !appView.definitionFor(type).isProgramClass());
+ .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
return inheritedDefaultMethodsPerType;
}
@@ -296,7 +297,7 @@
});
defaultMethodsInheritedBySubclassesPerClass
.keySet()
- .removeIf(type -> !appView.definitionFor(type).isProgramClass());
+ .removeIf(type -> asProgramClassOrNull(appView.definitionFor(type)) == null);
return defaultMethodsInheritedBySubclassesPerClass;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
index 17659f8..5968d79 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoDirectRuntimeTypeChecks.java
@@ -4,14 +4,19 @@
package com.android.tools.r8.horizontalclassmerging.policies;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
+import com.android.tools.r8.utils.InternalOptions;
public class NoDirectRuntimeTypeChecks extends SingleClassPolicy {
+
+ private final InternalOptions options;
private final RuntimeTypeCheckInfo runtimeTypeCheckInfo;
- public NoDirectRuntimeTypeChecks(RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ public NoDirectRuntimeTypeChecks(AppView<?> appView, RuntimeTypeCheckInfo runtimeTypeCheckInfo) {
+ this.options = appView.options();
this.runtimeTypeCheckInfo = runtimeTypeCheckInfo;
}
@@ -24,4 +29,9 @@
public String getName() {
return "NoDirectRuntimeTypeChecks";
}
+
+ @Override
+ public boolean shouldSkipPolicy() {
+ return options.horizontalClassMergerOptions().isIgnoreRuntimeTypeChecksForTestingEnabled();
+ }
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
deleted file mode 100644
index d6032e7..0000000
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/NoInterfaces.java
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2020, 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.horizontalclassmerging.policies;
-
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.horizontalclassmerging.SingleClassPolicy;
-
-public class NoInterfaces extends SingleClassPolicy {
-
- @Override
- public boolean canMerge(DexProgramClass program) {
- return !program.isInterface();
- }
-
- @Override
- public String getName() {
- return "NoInterfaces";
- }
-}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
similarity index 92%
rename from src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
rename to src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
index 5bffb33..9ec219c 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventMethodImplementation.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/PreventClassMethodAndDefaultMethodCollisions.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.horizontalclassmerging.MultiClassPolicy;
import com.android.tools.r8.horizontalclassmerging.SubtypingForrestForClasses;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
+import com.google.common.collect.ImmutableList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
@@ -48,7 +49,7 @@
*
* <p>See: https://docs.oracle.com/javase/specs/jvms/se15/html/jvms-5.html#jvms-5.4.3.3)
*/
-public class PreventMethodImplementation extends MultiClassPolicy {
+public class PreventClassMethodAndDefaultMethodCollisions extends MultiClassPolicy {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final SubtypingForrestForClasses subtypingForrestForClasses;
@@ -62,7 +63,7 @@
@Override
public String getName() {
- return "PreventMethodImplementation";
+ return "PreventClassMethodAndDefaultMethodCollisions";
}
private abstract static class SignaturesCache<C extends DexClass> {
@@ -124,7 +125,8 @@
}
}
- public PreventMethodImplementation(AppView<? extends AppInfoWithClassHierarchy> appView) {
+ public PreventClassMethodAndDefaultMethodCollisions(
+ AppView<? extends AppInfoWithClassHierarchy> appView) {
this.appView = appView;
this.subtypingForrestForClasses = new SubtypingForrestForClasses(appView);
}
@@ -150,6 +152,11 @@
@Override
public Collection<MergeGroup> apply(MergeGroup group) {
+ // This policy is specific to issues that may arise from merging (non-interface) classes.
+ if (group.isInterfaceGroup()) {
+ return ImmutableList.of(group);
+ }
+
DexMethodSignatureSet signatures = DexMethodSignatureSet.createLinked();
for (DexProgramClass clazz : group) {
signatures.addAllMethods(clazz.methods());
diff --git a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
index 5a4ee11..aae7abe 100644
--- a/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/ArrayUtils.java
@@ -93,10 +93,21 @@
* @param clazz target type's Class to cast
* @param original an array of original elements
* @param mapper a mapper that rewrites an original element to a new one, maybe `null`
- * @param <T> target type
* @return an array with written elements
*/
public static <T> T[] map(Class<T[]> clazz, T[] original, Function<T, T> mapper) {
+ return map(original, mapper, clazz.cast(Array.newInstance(clazz.getComponentType(), 0)));
+ }
+
+ /**
+ * Rewrites the input array based on the given function.
+ *
+ * @param original an array of original elements
+ * @param mapper a mapper that rewrites an original element to a new one, maybe `null`
+ * @param emptyArray an empty array
+ * @return an array with written elements
+ */
+ public static <T> T[] map(T[] original, Function<T, T> mapper, T[] emptyArray) {
ArrayList<T> results = null;
for (int i = 0; i < original.length; i++) {
T oldOne = original[i];
@@ -117,11 +128,7 @@
}
}
}
- if (results == null) {
- return original;
- }
- return results.toArray(
- clazz.cast(Array.newInstance(clazz.getComponentType(), results.size())));
+ return results != null ? results.toArray(emptyArray) : original;
}
public static int[] createIdentityArray(int size) {
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 40ce5b8..17aaf3e 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1200,6 +1200,7 @@
|| System.getProperty("com.android.tools.r8.disableHorizontalClassMerging") == null;
private boolean enableConstructorMerging = true;
private boolean enableInterfaceMerging = false;
+ private boolean ignoreRuntimeTypeChecksForTesting = false;
public int maxGroupSize = 30;
@@ -1215,6 +1216,10 @@
this.enable = enable;
}
+ public void enableInterfaceMerging() {
+ enableInterfaceMerging = true;
+ }
+
public int getMaxGroupSize() {
return maxGroupSize;
}
@@ -1231,9 +1236,17 @@
return enable;
}
+ public boolean isIgnoreRuntimeTypeChecksForTestingEnabled() {
+ return ignoreRuntimeTypeChecksForTesting;
+ }
+
public boolean isInterfaceMergingEnabled() {
return enableInterfaceMerging;
}
+
+ public void setIgnoreRuntimeTypeChecksForTesting() {
+ ignoreRuntimeTypeChecksForTesting = true;
+ }
}
public static class ProtoShrinkingOptions {
diff --git a/src/main/java/com/android/tools/r8/utils/MapUtils.java b/src/main/java/com/android/tools/r8/utils/MapUtils.java
index c2cf607..63e881e 100644
--- a/src/main/java/com/android/tools/r8/utils/MapUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/MapUtils.java
@@ -9,6 +9,7 @@
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
+import java.util.function.Supplier;
public class MapUtils {
@@ -26,6 +27,10 @@
return map.values().iterator().next();
}
+ public static <T, R> Function<T, R> ignoreKey(Supplier<R> supplier) {
+ return ignore -> supplier.get();
+ }
+
public static <K, V> Map<K, V> map(
Map<K, V> map,
IntFunction<Map<K, V>> factory,
diff --git a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
index a71c116..8ef5506 100644
--- a/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
+++ b/src/main/java/com/android/tools/r8/utils/collections/DexMethodSignatureSet.java
@@ -44,6 +44,7 @@
return EMPTY;
}
+ @Override
public boolean add(DexMethodSignature signature) {
return backing.add(signature);
}
@@ -135,10 +136,6 @@
return backing.removeAll(collection);
}
- public void removeAll(Iterable<DexMethodSignature> signatures) {
- signatures.forEach(this::remove);
- }
-
public void removeAllMethods(Iterable<DexEncodedMethod> methods) {
methods.forEach(this::remove);
}