blob: adc4f12a404d1049eeacde34660a00f9864d007c [file] [log] [blame]
// 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;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DirectMappedDexApplication;
import com.android.tools.r8.horizontalclassmerging.policies.DontMergeSynchronizedClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoAbstractClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoAnnotations;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassesOrMembersWithAnnotations;
import com.android.tools.r8.horizontalclassmerging.policies.NoClassesWithInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoFields;
import com.android.tools.r8.horizontalclassmerging.policies.NoInnerClasses;
import com.android.tools.r8.horizontalclassmerging.policies.NoInterfaces;
import com.android.tools.r8.horizontalclassmerging.policies.NoKeepRules;
import com.android.tools.r8.horizontalclassmerging.policies.NoRuntimeTypeChecks;
import com.android.tools.r8.horizontalclassmerging.policies.NoStaticClassInitializer;
import com.android.tools.r8.horizontalclassmerging.policies.NotEntryPoint;
import com.android.tools.r8.horizontalclassmerging.policies.NotMatchedByNoHorizontalClassMerging;
import com.android.tools.r8.horizontalclassmerging.policies.NotVerticallyMergedIntoSubtype;
import com.android.tools.r8.horizontalclassmerging.policies.PreventChangingVisibility;
import com.android.tools.r8.horizontalclassmerging.policies.PreventMergeIntoMainDex;
import com.android.tools.r8.horizontalclassmerging.policies.RespectPackageBoundaries;
import com.android.tools.r8.horizontalclassmerging.policies.SameFeatureSplit;
import com.android.tools.r8.horizontalclassmerging.policies.SameNestHost;
import com.android.tools.r8.horizontalclassmerging.policies.SameParentClass;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.ClassMergingEnqueuerExtension;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.MainDexTracingResult;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class HorizontalClassMerger {
private final AppView<AppInfoWithLiveness> appView;
private final PolicyExecutor policyExecutor;
public HorizontalClassMerger(
AppView<AppInfoWithLiveness> appView,
MainDexTracingResult mainDexTracingResult,
ClassMergingEnqueuerExtension classMergingEnqueuerExtension) {
this.appView = appView;
List<Policy> policies =
ImmutableList.of(
new NotMatchedByNoHorizontalClassMerging(appView),
new NoFields(),
// TODO(b/166071504): Allow merging of classes that implement interfaces.
new NoInterfaces(),
new NoClassesWithInterfaces(),
new NoAnnotations(),
new NoAbstractClasses(),
new NoClassesOrMembersWithAnnotations(),
new NoInnerClasses(),
new NoStaticClassInitializer(),
new NoKeepRules(appView),
new NotVerticallyMergedIntoSubtype(appView),
new NoRuntimeTypeChecks(classMergingEnqueuerExtension),
new NotEntryPoint(appView.dexItemFactory()),
new PreventMergeIntoMainDex(appView, mainDexTracingResult),
new SameParentClass(),
new SameNestHost(),
new PreventChangingVisibility(),
new SameFeatureSplit(appView),
new RespectPackageBoundaries(appView),
new DontMergeSynchronizedClasses(appView)
// TODO: add policies
);
this.policyExecutor = new SimplePolicyExecutor(policies);
}
// TODO(b/165577835): replace Collection<DexProgramClass> with MergeGroup
public HorizontalClassMergerGraphLens run(DirectMappedDexApplication.Builder appBuilder) {
Map<FieldMultiset, Collection<DexProgramClass>> classes = new HashMap<>();
// Group classes by same field signature using the hash map.
for (DexProgramClass clazz : appView.appInfo().app().classesWithDeterministicOrder()) {
classes.computeIfAbsent(new FieldMultiset(clazz), ignore -> new ArrayList<>()).add(clazz);
}
// Run the policies on all collected classes to produce a final grouping.
Collection<Collection<DexProgramClass>> groups = policyExecutor.run(classes.values());
// If there are no groups, then end horizontal class merging.
if (groups.isEmpty()) {
return null;
}
HorizontallyMergedClasses.Builder mergedClassesBuilder =
new HorizontallyMergedClasses.Builder();
HorizontalClassMergerGraphLens.Builder lensBuilder =
new HorizontalClassMergerGraphLens.Builder();
FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder =
new FieldAccessInfoCollectionModifier.Builder();
// Set up a class merger for each group.
Collection<ClassMerger> classMergers =
initializeClassMergers(
mergedClassesBuilder, lensBuilder, fieldAccessChangesBuilder, groups);
Iterable<DexProgramClass> allMergeClasses =
Iterables.concat(Iterables.transform(classMergers, ClassMerger::getClasses));
// Merge the classes.
SyntheticArgumentClass syntheticArgumentClass =
new SyntheticArgumentClass.Builder().build(appView, appBuilder, allMergeClasses);
applyClassMergers(classMergers, syntheticArgumentClass);
// Generate the class lens.
HorizontallyMergedClasses mergedClasses = mergedClassesBuilder.build();
appView.setHorizontallyMergedClasses(mergedClasses);
return createLens(
mergedClasses, lensBuilder, fieldAccessChangesBuilder, syntheticArgumentClass);
}
/**
* Prepare horizontal class merging by determining which virtual methods and constructors need to
* be merged and how the merging should be performed.
*/
private Collection<ClassMerger> initializeClassMergers(
HorizontallyMergedClasses.Builder mergedClassesBuilder,
HorizontalClassMergerGraphLens.Builder lensBuilder,
FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
Collection<Collection<DexProgramClass>> groups) {
Collection<ClassMerger> classMergers = new ArrayList<>();
// TODO(b/166577694): Replace Collection<DexProgramClass> with MergeGroup
for (Collection<DexProgramClass> group : groups) {
assert !group.isEmpty();
DexProgramClass target = group.stream().findFirst().get();
group.remove(target);
ClassMerger merger =
new ClassMerger.Builder(target)
.addClassesToMerge(group)
.build(appView, mergedClassesBuilder, lensBuilder, fieldAccessChangesBuilder);
classMergers.add(merger);
}
return classMergers;
}
/** Merges all class groups using {@link ClassMerger}. */
private void applyClassMergers(
Collection<ClassMerger> classMergers, SyntheticArgumentClass syntheticArgumentClass) {
for (ClassMerger merger : classMergers) {
merger.mergeGroup(syntheticArgumentClass);
}
}
/**
* Fix all references to merged classes using the {@link TreeFixer}. Construct a graph lens
* containing all changes performed by horizontal class merging.
*/
private HorizontalClassMergerGraphLens createLens(
HorizontallyMergedClasses mergedClasses,
HorizontalClassMergerGraphLens.Builder lensBuilder,
FieldAccessInfoCollectionModifier.Builder fieldAccessChangesBuilder,
SyntheticArgumentClass syntheticArgumentClass) {
HorizontalClassMergerGraphLens lens =
new TreeFixer(
appView,
mergedClasses,
lensBuilder,
fieldAccessChangesBuilder,
syntheticArgumentClass)
.fixupTypeReferences();
return lens;
}
}