blob: 3df986616b13c054f4d2668bd0572ee8359acb6e [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.profile.art.rewriting;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.profile.AbstractProfile;
import com.android.tools.r8.profile.AbstractProfileClassRule;
import com.android.tools.r8.profile.AbstractProfileMethodRule;
import com.android.tools.r8.profile.AbstractProfileRule;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
/** Mutable extension of an existing profile. */
public abstract class ProfileAdditions<
Additions extends
ProfileAdditions<
Additions,
ClassRule,
ClassRuleBuilder,
MethodRule,
MethodRuleBuilder,
ProfileRule,
Profile,
ProfileBuilder>,
ClassRule extends AbstractProfileClassRule,
ClassRuleBuilder extends AbstractProfileClassRule.Builder<ClassRule>,
MethodRule extends AbstractProfileMethodRule,
MethodRuleBuilder extends AbstractProfileMethodRule.Builder<MethodRule, MethodRuleBuilder>,
ProfileRule extends AbstractProfileRule,
Profile extends AbstractProfile<ClassRule, MethodRule>,
ProfileBuilder extends
AbstractProfile.Builder<ClassRule, MethodRule, Profile, ProfileBuilder>> {
public interface ProfileAdditionsBuilder {
default ProfileAdditionsBuilder addRule(ProgramDefinition definition) {
return addRule(definition.getReference());
}
default ProfileAdditionsBuilder addRule(DexReference reference) {
if (reference.isDexType()) {
return addClassRule(reference.asDexType());
} else {
assert reference.isDexMethod();
return addMethodRule(reference.asDexMethod());
}
}
ProfileAdditionsBuilder addClassRule(DexType type);
ProfileAdditionsBuilder addMethodRule(DexMethod method);
default void removeMovedMethodRule(ProgramMethod oldMethod, ProgramMethod newMethod) {
removeMovedMethodRule(oldMethod.getReference(), newMethod);
}
void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod);
}
protected Profile profile;
final Map<DexType, ClassRuleBuilder> classRuleAdditions = new ConcurrentHashMap<>();
final Map<DexMethod, MethodRuleBuilder> methodRuleAdditions = new ConcurrentHashMap<>();
private final Set<DexMethod> methodRuleRemovals = Sets.newConcurrentHashSet();
private final NestedMethodRuleAdditionsGraph nestedMethodRuleAdditionsGraph =
new NestedMethodRuleAdditionsGraph();
protected ProfileAdditions(Profile profile) {
this.profile = profile;
}
public void applyIfContextIsInProfile(DexType context, Consumer<? super Additions> fn) {
if (profile.containsClassRule(context) || classRuleAdditions.containsKey(context)) {
fn.accept(self());
}
}
public void applyIfContextIsInProfile(
DexMethod context, Consumer<ProfileAdditionsBuilder> builderConsumer) {
MethodRule contextMethodRule = profile.getMethodRule(context);
if (contextMethodRule != null) {
builderConsumer.accept(
new ProfileAdditionsBuilder() {
@Override
public ProfileAdditionsBuilder addClassRule(DexType type) {
ProfileAdditions.this.addClassRule(type);
return this;
}
@Override
public ProfileAdditionsBuilder addMethodRule(DexMethod method) {
ProfileAdditions.this.addMethodRule(
method, methodRuleBuilder -> methodRuleBuilder.join(contextMethodRule));
return this;
}
@Override
public void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod) {
ProfileAdditions.this.removeMovedMethodRule(oldMethod, newMethod);
}
});
} else if (methodRuleAdditions.containsKey(context)) {
builderConsumer.accept(
new ProfileAdditionsBuilder() {
@Override
public ProfileAdditionsBuilder addClassRule(DexType type) {
ProfileAdditions.this.addClassRule(type);
return this;
}
@Override
public ProfileAdditionsBuilder addMethodRule(DexMethod method) {
MethodRuleBuilder contextRuleBuilder = methodRuleAdditions.get(context);
ProfileAdditions.this.addMethodRule(
method, methodRuleBuilder -> methodRuleBuilder.join(contextRuleBuilder));
nestedMethodRuleAdditionsGraph.recordMethodRuleInfoFlagsLargerThan(method, context);
return this;
}
@Override
public void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod) {
ProfileAdditions.this.removeMovedMethodRule(oldMethod, newMethod);
}
});
}
}
public Additions addClassRule(DexClass clazz) {
addClassRule(clazz.getType());
return self();
}
public void addClassRule(DexType type) {
if (profile.containsClassRule(type)) {
return;
}
// Create profile rule for class.
classRuleAdditions.computeIfAbsent(type, this::createClassRuleBuilder);
}
public Additions addMethodRule(
DexClassAndMethod method, Consumer<? super MethodRuleBuilder> methodRuleBuilderConsumer) {
return addMethodRule(method.getReference(), methodRuleBuilderConsumer);
}
public Additions addMethodRule(
DexMethod method, Consumer<? super MethodRuleBuilder> methodRuleBuilderConsumer) {
// Create profile rule for method.
MethodRuleBuilder methodRuleBuilder =
methodRuleAdditions.computeIfAbsent(method, this::createMethodRuleBuilder);
// Setup the rule.
synchronized (methodRuleBuilder) {
methodRuleBuilderConsumer.accept(methodRuleBuilder);
}
return self();
}
void removeMovedMethodRule(DexMethod oldMethod, ProgramMethod newMethod) {
assert profile.containsMethodRule(oldMethod) || methodRuleAdditions.containsKey(oldMethod);
assert methodRuleAdditions.containsKey(newMethod.getReference());
methodRuleRemovals.add(oldMethod);
}
public Profile createNewProfile() {
if (!hasAdditions()) {
assert !hasRemovals();
return profile;
}
nestedMethodRuleAdditionsGraph.propagateMethodRuleInfoFlags(methodRuleAdditions);
// Add existing rules to new profile.
ProfileBuilder profileBuilder = createProfileBuilder();
profile.forEachRule(
profileBuilder::addClassRule,
methodRule -> {
if (methodRuleRemovals.contains(methodRule.getReference())) {
return;
}
MethodRuleBuilder methodRuleBuilder =
methodRuleAdditions.remove(methodRule.getReference());
if (methodRuleBuilder != null) {
MethodRule newMethodRule = methodRuleBuilder.join(methodRule).build();
profileBuilder.addMethodRule(newMethodRule);
} else {
profileBuilder.addMethodRule(methodRule);
}
});
// Sort and add additions to new profile. Sorting is needed since the additions to this
// collection may be concurrent.
List<AbstractProfileRule> ruleAdditionsSorted =
new ArrayList<>(classRuleAdditions.size() + methodRuleAdditions.size());
classRuleAdditions
.values()
.forEach(classRuleBuilder -> ruleAdditionsSorted.add(classRuleBuilder.build()));
methodRuleAdditions
.values()
.forEach(methodRuleBuilder -> ruleAdditionsSorted.add(methodRuleBuilder.build()));
ruleAdditionsSorted.sort(getRuleComparator());
ruleAdditionsSorted.forEach(profileBuilder::addRule);
return profileBuilder.build();
}
public boolean hasAdditions() {
return !classRuleAdditions.isEmpty() || !methodRuleAdditions.isEmpty();
}
private boolean hasRemovals() {
return !methodRuleRemovals.isEmpty();
}
public Additions rewriteMethodReferences(Function<DexMethod, DexMethod> methodFn) {
Additions rewrittenAdditions = create();
assert methodRuleRemovals.isEmpty();
rewrittenAdditions.classRuleAdditions.putAll(classRuleAdditions);
methodRuleAdditions.forEach(
(method, methodRuleBuilder) -> {
DexMethod newMethod = methodFn.apply(method);
MethodRuleBuilder existingMethodRuleBuilder =
rewrittenAdditions.methodRuleAdditions.put(
newMethod, methodRuleBuilder.setMethod(newMethod));
assert existingMethodRuleBuilder == null;
});
return rewrittenAdditions;
}
public abstract Additions create();
public abstract ClassRuleBuilder createClassRuleBuilder(DexType type);
public abstract MethodRuleBuilder createMethodRuleBuilder(DexMethod method);
public abstract ProfileBuilder createProfileBuilder();
public abstract Comparator<AbstractProfileRule> getRuleComparator();
public abstract Additions self();
public void setProfile(Profile profile) {
this.profile = profile;
}
private class NestedMethodRuleAdditionsGraph {
private final Map<DexMethod, Set<DexMethod>> successors = new ConcurrentHashMap<>();
private final Map<DexMethod, Set<DexMethod>> predecessors = new ConcurrentHashMap<>();
void recordMethodRuleInfoFlagsLargerThan(DexMethod largerFlags, DexMethod smallerFlags) {
predecessors
.computeIfAbsent(largerFlags, ignoreKey(Sets::newConcurrentHashSet))
.add(smallerFlags);
successors
.computeIfAbsent(smallerFlags, ignoreKey(Sets::newConcurrentHashSet))
.add(largerFlags);
}
void propagateMethodRuleInfoFlags(Map<DexMethod, MethodRuleBuilder> methodRuleAdditions) {
List<DexMethod> leaves =
successors.keySet().stream()
.filter(method -> predecessors.getOrDefault(method, Collections.emptySet()).isEmpty())
.collect(Collectors.toList());
WorkList<DexMethod> worklist = WorkList.newIdentityWorkList(leaves);
while (worklist.hasNext()) {
DexMethod method = worklist.next();
MethodRuleBuilder methodRuleBuilder = methodRuleAdditions.get(method);
for (DexMethod successor : successors.getOrDefault(method, Collections.emptySet())) {
MethodRuleBuilder successorMethodRuleBuilder = methodRuleAdditions.get(successor);
// If this assertion fails, that means we have synthetics with multiple
// synthesizing contexts, which are not guaranteed to be processed before the
// synthetic itself. In that case this assertion should simply be removed.
assert successorMethodRuleBuilder.isGreaterThanOrEqualTo(methodRuleBuilder);
successorMethodRuleBuilder.join(methodRuleBuilder);
// Note: no need to addIgnoringSeenSet() since the graph will not have cycles. Indeed, it
// should never be the case that a method m2(), which is synthesized from method context
// m1(), would itself be a synthesizing context for m1().
worklist.addIfNotSeen(successor);
}
}
}
}
}