blob: 51c1acaee466b8089b2b43b5a2130f06e401c891 [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 static com.android.tools.r8.graph.DexClassAndMethod.asProgramMethodOrNull;
import com.android.tools.r8.classmerging.ClassMergerSharedData;
import com.android.tools.r8.classmerging.Policy;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.horizontalclassmerging.code.SyntheticInitializerConverter;
import com.android.tools.r8.ir.conversion.LirConverter;
import com.android.tools.r8.naming.IdentifierMinifier;
import com.android.tools.r8.profile.art.ArtProfileCompletenessChecker;
import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
import com.android.tools.r8.shaking.KeepInfoCollection;
import com.android.tools.r8.shaking.RuntimeTypeCheckInfo;
import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.utils.InternalOptions.HorizontalClassMergerOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.TraversalContinuation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
public class HorizontalClassMerger {
private final AppView<?> appView;
private final HorizontalClassMergerOptions options;
private HorizontalClassMerger(AppView<?> appView) {
this.appView = appView;
this.options = appView.options().horizontalClassMergerOptions();
}
public static HorizontalClassMerger createForFinalClassMerging(
AppView<? extends AppInfoWithClassHierarchy> appView) {
return new HorizontalClassMerger(appView);
}
public static HorizontalClassMerger createForD8ClassMerging(AppView<?> appView) {
assert appView.options().horizontalClassMergerOptions().isRestrictedToSynthetics();
return new HorizontalClassMerger(appView);
}
public void runIfNecessary(ExecutorService executorService, Timing timing)
throws ExecutionException {
runIfNecessary(executorService, timing, null);
}
public void runIfNecessary(
ExecutorService executorService, Timing timing, RuntimeTypeCheckInfo runtimeTypeCheckInfo)
throws ExecutionException {
timing.begin("HorizontalClassMerger");
if (shouldRun()) {
run(runtimeTypeCheckInfo, executorService, timing);
assert ArtProfileCompletenessChecker.verify(appView);
// Clear type elements cache after IR building.
appView.dexItemFactory().clearTypeElementsCache();
appView.notifyOptimizationFinishedForTesting();
} else {
appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
}
assert ArtProfileCompletenessChecker.verify(appView);
timing.end();
}
private boolean shouldRun() {
return options.isEnabled(appView.getWholeProgramOptimizations())
&& !appView.hasCfByteCodePassThroughMethods();
}
private void run(
RuntimeTypeCheckInfo runtimeTypeCheckInfo,
ExecutorService executorService,
Timing timing)
throws ExecutionException {
// Run the policies on all program classes to produce a final grouping.
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
appView.enableWholeProgramOptimizations()
? ImmediateProgramSubtypingInfo.createWithDeterministicOrder(
appView.withClassHierarchy())
: null;
List<Policy> policies =
PolicyScheduler.getPolicies(appView, immediateSubtypingInfo, runtimeTypeCheckInfo);
Collection<HorizontalMergeGroup> groups =
new HorizontalClassMergerPolicyExecutor()
.run(getInitialGroups(), policies, executorService, timing);
// If there are no groups, then end horizontal class merging.
if (groups.isEmpty()) {
appView.setHorizontallyMergedClasses(HorizontallyMergedClasses.empty());
return;
}
ClassMergerSharedData classMergerSharedData = new ClassMergerSharedData(appView);
HorizontalClassMergerGraphLens.Builder lensBuilder =
new HorizontalClassMergerGraphLens.Builder();
// Determine which classes need a class id field.
List<ClassMerger.Builder> classMergerBuilders = createClassMergerBuilders(groups);
initializeClassIdFields(classMergerBuilders);
// Ensure that all allocations of classes that end up needing a class id use a constructor on
// the class itself.
new UndoConstructorInlining(appView, classMergerSharedData, immediateSubtypingInfo)
.runIfNecessary(groups, executorService, timing);
// Merge classes.
List<ClassMerger> classMergers = createClassMergers(classMergerBuilders, lensBuilder);
ProfileCollectionAdditions profileCollectionAdditions =
ProfileCollectionAdditions.create(appView);
SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder =
SyntheticInitializerConverter.builder(appView);
List<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfos = new ArrayList<>();
PrunedItems prunedItems =
applyClassMergers(
classMergers,
classMergerSharedData,
profileCollectionAdditions,
syntheticInitializerConverterBuilder,
virtuallyMergedMethodsKeepInfos::add);
SyntheticInitializerConverter syntheticInitializerConverter =
syntheticInitializerConverterBuilder.build();
assert syntheticInitializerConverter.isEmpty() || appView.enableWholeProgramOptimizations();
syntheticInitializerConverter.convertClassInitializers(executorService);
// Generate the graph lens.
HorizontallyMergedClasses mergedClasses =
HorizontallyMergedClasses.builder().addMergeGroups(groups).build();
appView.setHorizontallyMergedClasses(mergedClasses);
HorizontalClassMergerGraphLens horizontalClassMergerGraphLens =
createLens(
classMergerSharedData,
immediateSubtypingInfo,
mergedClasses,
lensBuilder,
executorService,
timing);
profileCollectionAdditions =
profileCollectionAdditions.rewriteMethodReferences(
horizontalClassMergerGraphLens::getNextMethodToInvoke);
assert verifyNoCyclesInInterfaceHierarchies(appView, groups);
FieldAccessInfoCollectionModifier fieldAccessInfoCollectionModifier = null;
if (appView.enableWholeProgramOptimizations()) {
fieldAccessInfoCollectionModifier = createFieldAccessInfoCollectionModifier(groups);
} else {
assert groups.stream().noneMatch(HorizontalMergeGroup::hasClassIdField);
}
// Set the new graph lens before finalizing any synthetic code.
appView.setGraphLens(horizontalClassMergerGraphLens);
// Must rewrite AppInfoWithLiveness before pruning the merged classes, to ensure that allocation
// sites, fields accesses, etc. are correctly transferred to the target classes.
DexApplication newApplication = getNewApplication(mergedClasses);
if (appView.enableWholeProgramOptimizations()) {
// Finalize synthetic code.
transformIncompleteCode(groups, horizontalClassMergerGraphLens, executorService);
// Prune keep info.
AppView<AppInfoWithClassHierarchy> appViewWithClassHierarchy = appView.withClassHierarchy();
KeepInfoCollection keepInfo = appView.getKeepInfo();
keepInfo.mutate(mutator -> mutator.removeKeepInfoForMergedClasses(prunedItems));
appView.rewriteWithLens(horizontalClassMergerGraphLens, executorService, timing);
LirConverter.rewriteLirWithLens(appViewWithClassHierarchy, timing, executorService);
new IdentifierMinifier(appViewWithClassHierarchy)
.rewriteDexItemBasedConstStringInStaticFields(executorService);
if (appView.hasLiveness()) {
AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
appViewWithLiveness.setAppInfo(
appViewWithLiveness.appInfo().rebuildWithLiveness(newApplication));
} else {
appViewWithClassHierarchy.setAppInfo(
appViewWithClassHierarchy.appInfo().rebuildWithClassHierarchy(newApplication));
}
appView.clearCodeRewritings(executorService, timing);
} else {
SyntheticItems syntheticItems = appView.appInfo().getSyntheticItems();
assert !syntheticItems.hasPendingSyntheticClasses();
appView
.withoutClassHierarchy()
.setAppInfo(
new AppInfo(
syntheticItems.commitRewrittenWithLens(
newApplication, horizontalClassMergerGraphLens, timing),
appView
.appInfo()
.getMainDexInfo()
.rewrittenWithLens(syntheticItems, horizontalClassMergerGraphLens, timing)));
appView.rewriteWithD8Lens(horizontalClassMergerGraphLens, timing);
}
// Amend art profile collection.
profileCollectionAdditions
.setArtProfileCollection(appView.getArtProfileCollection())
.setStartupProfile(appView.getStartupProfile())
.commit(appView);
// Record where the synthesized $r8$classId fields are read and written.
if (fieldAccessInfoCollectionModifier != null) {
fieldAccessInfoCollectionModifier.modify(appView.withLiveness());
}
appView.pruneItems(
prunedItems.toBuilder().setPrunedApp(appView.app()).build(), executorService, timing);
amendKeepInfo(horizontalClassMergerGraphLens, virtuallyMergedMethodsKeepInfos);
}
private void amendKeepInfo(
HorizontalClassMergerGraphLens horizontalClassMergerGraphLens,
List<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfos) {
if (!appView.enableWholeProgramOptimizations()) {
assert virtuallyMergedMethodsKeepInfos.isEmpty();
return;
}
appView
.getKeepInfo()
.mutate(
keepInfo -> {
for (VirtuallyMergedMethodsKeepInfo virtuallyMergedMethodsKeepInfo :
virtuallyMergedMethodsKeepInfos) {
DexMethod representative = virtuallyMergedMethodsKeepInfo.getRepresentative();
DexMethod mergedMethodReference =
horizontalClassMergerGraphLens.getNextMethodToInvoke(representative);
ProgramMethod mergedMethod =
asProgramMethodOrNull(appView.definitionFor(mergedMethodReference));
if (mergedMethod != null) {
keepInfo.joinMethod(
mergedMethod,
info -> info.merge(virtuallyMergedMethodsKeepInfo.getKeepInfo()));
continue;
}
assert false;
}
});
}
private FieldAccessInfoCollectionModifier createFieldAccessInfoCollectionModifier(
Collection<HorizontalMergeGroup> groups) {
FieldAccessInfoCollectionModifier.Builder builder =
new FieldAccessInfoCollectionModifier.Builder();
for (HorizontalMergeGroup group : groups) {
if (group.hasClassIdField()) {
DexProgramClass target = group.getTarget();
target.forEachProgramInstanceInitializerMatching(
definition -> definition.getCode().isHorizontalClassMergerCode(),
method -> builder.recordFieldWrittenInContext(group.getClassIdField(), method));
target.forEachProgramVirtualMethodMatching(
definition ->
definition.hasCode() && definition.getCode().isHorizontalClassMergerCode(),
method -> builder.recordFieldReadInContext(group.getClassIdField(), method));
}
}
return builder.build();
}
private void transformIncompleteCode(
Collection<HorizontalMergeGroup> groups,
HorizontalClassMergerGraphLens horizontalClassMergerGraphLens,
ExecutorService executorService)
throws ExecutionException {
if (!appView.hasClassHierarchy()) {
assert verifyNoIncompleteCode(groups, executorService);
return;
}
ThreadUtils.processItems(
groups,
group -> {
DexProgramClass target = group.getTarget();
target.forEachProgramMethodMatching(
definition ->
definition.hasCode()
&& definition.getCode().isIncompleteHorizontalClassMergerCode(),
method -> {
// Transform the code object to CfCode. This may return null if the code object does
// not have support for generating CfCode. In this case, the call to toCfCode() will
// lens rewrite the references of the code object using the lens.
//
// This should be changed to generate non-null LirCode always.
IncompleteHorizontalClassMergerCode code =
(IncompleteHorizontalClassMergerCode) method.getDefinition().getCode();
Code newCode =
code.toLirCode(
appView.withClassHierarchy(), method, horizontalClassMergerGraphLens);
if (newCode != null) {
method.setCode(newCode, appView);
}
});
},
appView.options().getThreadingModule(),
executorService);
}
private boolean verifyNoIncompleteCode(
Collection<HorizontalMergeGroup> groups, ExecutorService executorService)
throws ExecutionException {
ThreadUtils.processItems(
groups,
group -> {
assert !group
.getTarget()
.methods(
method ->
method.hasCode()
&& method.getCode().isIncompleteHorizontalClassMergerCode())
.iterator()
.hasNext()
: "Expected no incomplete code";
},
appView.options().getThreadingModule(),
executorService);
return true;
}
private DexApplication getNewApplication(HorizontallyMergedClasses mergedClasses) {
// We must forcefully remove the merged classes from the application, since we won't run tree
// shaking before writing the application.
return appView
.app()
.builder()
.removeProgramClasses(clazz -> mergedClasses.isMergeSource(clazz.getType()))
.build();
}
// TODO(b/270398965): Replace LinkedList.
@SuppressWarnings("JdkObsolete")
private List<HorizontalMergeGroup> getInitialGroups() {
HorizontalMergeGroup initialClassGroup = new HorizontalMergeGroup();
HorizontalMergeGroup initialInterfaceGroup = new HorizontalMergeGroup();
for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
if (clazz.isInterface()) {
initialInterfaceGroup.add(clazz);
} else {
initialClassGroup.add(clazz);
}
}
List<HorizontalMergeGroup> initialGroups = new LinkedList<>();
initialGroups.add(initialClassGroup);
initialGroups.add(initialInterfaceGroup);
initialGroups.removeIf(HorizontalMergeGroup::isTrivial);
return initialGroups;
}
private List<ClassMerger.Builder> createClassMergerBuilders(
Collection<HorizontalMergeGroup> groups) {
List<ClassMerger.Builder> classMergerBuilders = new ArrayList<>(groups.size());
for (HorizontalMergeGroup group : groups) {
assert group.isNonTrivial();
assert group.hasInstanceFieldMap();
assert group.hasTarget();
classMergerBuilders.add(new ClassMerger.Builder(appView, group));
}
return classMergerBuilders;
}
private void initializeClassIdFields(List<ClassMerger.Builder> classMergerBuilders) {
for (ClassMerger.Builder classMergerBuilder : classMergerBuilders) {
classMergerBuilder.initializeVirtualMethodMergers().initializeClassIdField();
}
}
/**
* Prepare horizontal class merging by determining which virtual methods and constructors need to
* be merged and how the merging should be performed.
*/
private List<ClassMerger> createClassMergers(
List<ClassMerger.Builder> classMergerBuilders,
HorizontalClassMergerGraphLens.Builder lensBuilder) {
List<ClassMerger> classMergers = new ArrayList<>(classMergerBuilders.size());
for (ClassMerger.Builder classMergerBuilder : classMergerBuilders) {
classMergers.add(classMergerBuilder.build(lensBuilder));
}
appView.dexItemFactory().clearTypeElementsCache();
return classMergers;
}
/** Merges all class groups using {@link ClassMerger}. */
private PrunedItems applyClassMergers(
Collection<ClassMerger> classMergers,
ClassMergerSharedData classMergerSharedData,
ProfileCollectionAdditions profileCollectionAdditions,
SyntheticInitializerConverter.Builder syntheticInitializerConverterBuilder,
Consumer<VirtuallyMergedMethodsKeepInfo> virtuallyMergedMethodsKeepInfoConsumer) {
PrunedItems.Builder prunedItemsBuilder = PrunedItems.builder().setPrunedApp(appView.app());
for (ClassMerger merger : classMergers) {
merger.mergeGroup(
classMergerSharedData,
profileCollectionAdditions,
prunedItemsBuilder,
syntheticInitializerConverterBuilder,
virtuallyMergedMethodsKeepInfoConsumer);
}
appView.dexItemFactory().clearTypeElementsCache();
return prunedItemsBuilder.build();
}
/**
* Fix all references to merged classes using the {@link HorizontalClassMergerTreeFixer}.
* Construct a graph lens containing all changes performed by horizontal class merging.
*/
private HorizontalClassMergerGraphLens createLens(
ClassMergerSharedData classMergerSharedData,
ImmediateProgramSubtypingInfo immediateSubtypingInfo,
HorizontallyMergedClasses mergedClasses,
HorizontalClassMergerGraphLens.Builder lensBuilder,
ExecutorService executorService,
Timing timing)
throws ExecutionException {
return new HorizontalClassMergerTreeFixer(
appView, classMergerSharedData, immediateSubtypingInfo, mergedClasses, lensBuilder)
.run(executorService, timing);
}
private static boolean verifyNoCyclesInInterfaceHierarchies(
AppView<?> appView, Collection<HorizontalMergeGroup> groups) {
for (HorizontalMergeGroup group : groups) {
if (group.isClassGroup()) {
continue;
}
assert appView.hasClassHierarchy();
DexProgramClass interfaceClass = group.getTarget();
appView
.withClassHierarchy()
.appInfo()
.traverseSuperTypes(
interfaceClass,
(superType, subclass, isInterface) -> {
assert superType.isNotIdenticalTo(interfaceClass.getType())
: "Interface " + interfaceClass.getTypeName() + " inherits from itself";
return TraversalContinuation.doContinue();
});
}
return true;
}
}