blob: 125f71913315d154d226bbe16d4cbc6babb5c52f [file] [log] [blame]
// Copyright (c) 2023, 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.optimize;
import static com.android.tools.r8.ir.optimize.info.OptimizationFeedback.getSimpleFeedback;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.contexts.CompilationContext.MainThreadContext;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.MethodConversionOptions;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeAnalyzer;
import com.android.tools.r8.ir.optimize.info.bridge.BridgeInfo;
import com.android.tools.r8.ir.optimize.info.bridge.VirtualBridgeInfo;
import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
import com.android.tools.r8.profile.rewriting.ConcreteProfileCollectionAdditions;
import com.android.tools.r8.profile.rewriting.ProfileCollectionAdditions;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.TestingOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.DexMethodSignatureMap;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
public class BridgeHoistingToSharedSyntheticSuperClass {
private final AppView<AppInfoWithLiveness> appView;
BridgeHoistingToSharedSyntheticSuperClass(AppView<AppInfoWithLiveness> appView) {
this.appView = appView;
}
public static void run(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
InternalOptions options = appView.options();
if (!options.isOptimizing() || !options.isShrinking()) {
return;
}
if (!appView.options().canInitNewInstanceUsingSuperclassConstructor()) {
// TODO(b/309575527): Extend to all runtimes.
return;
}
TestingOptions testingOptions = options.getTestingOptions();
if (!testingOptions.enableBridgeHoistingToSharedSyntheticSuperclass) {
return;
}
timing.time(
"BridgeHoistingToSharedSyntheticSuperClass",
() ->
new BridgeHoistingToSharedSyntheticSuperClass(appView)
.internalRun(executorService, timing));
}
private void internalRun(ExecutorService executorService, Timing timing)
throws ExecutionException {
Collection<Group> groups = createInitialGroups(appView);
groups = refineGroups(groups);
if (!groups.isEmpty()) {
rewriteApplication(groups);
commitPendingSyntheticClasses();
updateArtProfiles(groups);
new BridgeHoisting(appView).run(executorService, timing);
}
appView.dexItemFactory().clearTypeElementsCache();
}
/** Returns the set of (non-singleton) groups that have the same superclass. */
private Collection<Group> createInitialGroups(AppView<AppInfoWithLiveness> appView) {
Map<DexClass, Group> groups = new LinkedHashMap<>();
for (DexProgramClass clazz : appView.appInfo().classesWithDeterministicOrder()) {
if (!clazz.hasSuperType()) {
continue;
}
DexClass superclass = appView.definitionFor(clazz.getSuperType());
if (superclass != null) {
groups.computeIfAbsent(superclass, ignoreKey(Group::new)).addClass(clazz);
}
}
groups.values().removeIf(Group::isSingleton);
return groups.values();
}
private Collection<Group> refineGroups(Collection<Group> groups) {
Collection<Group> newGroups = new ArrayList<>();
for (Group group : groups) {
Iterables.addAll(newGroups, refineGroup(group));
}
return newGroups;
}
/**
* Splits the group into a collection of smaller groups that should receive a shared superclass.
*
* <p>For each class, this creates a specification of the bridges (a mapping from bridge method
* signatures to their bridge implementation). Two classes are selected for getting a shared
* synthetic super class if the bridge specification of one is a subset of the other (i.e., a
* subset of the bridges can be shared and there are no bridges with the same signature that have
* different behavior).
*/
private Iterable<Group> refineGroup(Group group) {
List<Group> newGroups = new ArrayList<>();
for (DexProgramClass clazz : group) {
BridgeSpecification bridgeSpecification = getBridgeSpecification(clazz);
if (bridgeSpecification.isEmpty()) {
continue;
}
Group targetGroup = getGroupForClass(newGroups, clazz, bridgeSpecification);
if (targetGroup == null) {
newGroups.add(new Group(clazz, bridgeSpecification));
}
}
// Only introduce a shared super class for non-singleton groups that do not already have a
// shared superclass in the first place.
return Iterables.filter(
newGroups, newGroup -> !newGroup.isSingleton() && newGroup.size() < group.size());
}
// TODO(b/309575527): Avoid building IR for all methods.
private BridgeSpecification getBridgeSpecification(DexProgramClass clazz) {
BridgeSpecification bridgeSpecification = new BridgeSpecification();
clazz.forEachProgramVirtualMethodMatching(
DexEncodedMethod::hasCode,
method -> {
IRCode code = method.buildIR(appView, MethodConversionOptions.nonConverting());
BridgeInfo bridgeInfo = BridgeAnalyzer.analyzeMethod(method.getDefinition(), code);
if (bridgeInfo != null) {
getSimpleFeedback().setBridgeInfo(method, bridgeInfo);
if (bridgeInfo.isVirtualBridgeInfo()) {
bridgeSpecification.addBridge(
method.getMethodSignature(), bridgeInfo.asVirtualBridgeInfo());
}
}
});
return bridgeSpecification;
}
private Group getGroupForClass(
Collection<Group> groups, DexProgramClass clazz, BridgeSpecification bridgeSpecification) {
for (Group group : groups) {
if (bridgeSpecification.lessThanOrEquals(group.getBridgeSpecification())) {
group.addClass(clazz);
return group;
} else if (group.getBridgeSpecification().lessThanOrEquals(bridgeSpecification)) {
group.addClass(clazz);
group.setBridgeSpecification(bridgeSpecification);
return group;
}
}
return null;
}
private void rewriteApplication(Collection<Group> groups) {
MainThreadContext mainThreadContext =
appView.createProcessorContext().createMainThreadContext();
for (Group group : groups) {
DexProgramClass representative = ListUtils.first(group.getClasses());
Set<DexType> interfaces = SetUtils.newIdentityHashSet(representative.getInterfaces());
for (DexProgramClass clazz : Iterables.skip(group.getClasses(), 1)) {
interfaces.removeIf(type -> !clazz.getInterfaces().contains(type));
}
DexProgramClass syntheticSuperclass =
appView
.getSyntheticItems()
.createClass(
kinds -> kinds.SHARED_SUPER_CLASS,
mainThreadContext.createUniqueContext(representative),
appView,
classBuilder -> {
classBuilder
.setAbstract()
.setSuperType(representative.getSuperType())
.setInterfaces(ListUtils.sort(interfaces, Comparator.naturalOrder()));
group
.getBridgeSpecification()
.forEach(
(bridge, target) ->
classBuilder.addMethod(
methodBuilder ->
methodBuilder
.setAccessFlags(
MethodAccessFlags.builder()
.setAbstract()
.setPublic()
.build())
// TODO(b/309575527): Set correct api level.
.setApiLevelForDefinition(appView.computedMinApiLevel())
// TODO(b/309575527): Set correct library override info.
.setIsLibraryMethodOverride(OptionalBool.FALSE)
.setName(target.getName())
.setProto(target.getProto())));
});
for (DexProgramClass clazz : group) {
clazz.setSuperType(syntheticSuperclass.getType());
}
}
}
private void commitPendingSyntheticClasses() {
assert appView.getSyntheticItems().hasPendingSyntheticClasses();
appView.setAppInfo(
appView.appInfo().rebuildWithLiveness(appView.getSyntheticItems().commit(appView.app())));
}
private void updateArtProfiles(Collection<Group> groups) {
ConcreteProfileCollectionAdditions profileCollectionAdditions =
ProfileCollectionAdditions.create(appView).asConcrete();
if (profileCollectionAdditions == null) {
return;
}
for (Group group : groups) {
for (DexProgramClass clazz : group) {
profileCollectionAdditions.applyIfContextIsInProfile(
clazz, additionsBuilder -> additionsBuilder.addClassRule(clazz.getSuperType()));
group
.getBridgeSpecification()
.forEach(
(bridge, target) -> {
DexEncodedMethod targetMethod = clazz.getMethodCollection().getMethod(target);
if (targetMethod != null) {
profileCollectionAdditions.applyIfContextIsInProfile(
targetMethod.getReference(),
additionsBuilder ->
additionsBuilder.addMethodRule(
target.withHolder(clazz.getSuperType(), appView.dexItemFactory())));
}
});
}
}
profileCollectionAdditions.commit(appView);
}
private static class Group implements Iterable<DexProgramClass> {
private final List<DexProgramClass> classes;
private BridgeSpecification bridgeSpecification;
public Group() {
this.classes = new ArrayList<>();
this.bridgeSpecification = null;
}
public Group(DexProgramClass clazz, BridgeSpecification bridgeSpecification) {
this.classes = ListUtils.newArrayList(clazz);
this.bridgeSpecification = bridgeSpecification;
}
void addClass(DexProgramClass clazz) {
classes.add(clazz);
}
BridgeSpecification getBridgeSpecification() {
return bridgeSpecification;
}
List<DexProgramClass> getClasses() {
return classes;
}
void setBridgeSpecification(BridgeSpecification bridgeSpecification) {
this.bridgeSpecification = bridgeSpecification;
}
boolean isSingleton() {
return size() == 1;
}
@Override
public Iterator<DexProgramClass> iterator() {
return classes.iterator();
}
public int size() {
return classes.size();
}
}
private static class BridgeSpecification {
private final DexMethodSignatureMap<DexMethodSignature> bridges =
DexMethodSignatureMap.create();
void addBridge(DexMethodSignature method, VirtualBridgeInfo bridgeInfo) {
bridges.put(method, bridgeInfo.getInvokedMethod().getSignature());
}
boolean containsBridgeWithTarget(DexMethodSignature method, DexMethodSignature target) {
return target.equals(bridges.get(method));
}
void forEach(BiConsumer<? super DexMethodSignature, ? super DexMethodSignature> consumer) {
bridges.forEach(consumer);
}
boolean isEmpty() {
return bridges.isEmpty();
}
boolean lessThanOrEquals(BridgeSpecification bridgeSpecification) {
if (size() > bridgeSpecification.size()) {
return false;
}
for (Entry<DexMethodSignature, DexMethodSignature> entry : bridges.entrySet()) {
DexMethodSignature method = entry.getKey();
DexMethodSignature target = entry.getValue();
if (!bridgeSpecification.containsBridgeWithTarget(method, target)) {
return false;
}
}
return true;
}
int size() {
return bridges.size();
}
}
}