blob: 4dcaed1d5e9786374eaaced3a36bc65ef255f5b0 [file] [log] [blame]
// Copyright (c) 2019, 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.shaking;
import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndField;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexDefinition;
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.shaking.RootSetUtils.ConsequentRootSetBuilder;
import com.android.tools.r8.shaking.RootSetUtils.RootSetBuilder;
import com.android.tools.r8.shaking.ifrules.MaterializedSubsequentRulesOptimizer;
import com.android.tools.r8.threading.TaskCollection;
import com.android.tools.r8.utils.MapUtils;
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.UncheckedExecutionException;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.google.common.base.Equivalence.Wrapper;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public class IfRuleEvaluator {
private final AppView<? extends AppInfoWithClassHierarchy> appView;
private final DexItemFactory factory;
private final SubtypingInfo subtypingInfo;
private final Enqueuer enqueuer;
private final ConsequentRootSetBuilder rootSetBuilder;
private final TaskCollection<?> tasks;
IfRuleEvaluator(
AppView<? extends AppInfoWithClassHierarchy> appView,
SubtypingInfo subtypingInfo,
Enqueuer enqueuer,
ConsequentRootSetBuilder rootSetBuilder,
TaskCollection<?> tasks) {
assert tasks.isEmpty();
this.appView = appView;
this.factory = appView.dexItemFactory();
this.subtypingInfo = subtypingInfo;
this.enqueuer = enqueuer;
this.rootSetBuilder = rootSetBuilder;
this.tasks = tasks;
}
public void processActiveIfRulesWithMembers(
Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
Iterable<DexProgramClass> classesWithNewlyLiveMembers,
Predicate<DexProgramClass> isEffectivelyLive)
throws ExecutionException {
MapUtils.removeIf(
ifRules,
(ifRuleWrapper, ifRulesInEquivalence) -> {
ProguardIfRule ifRuleKey = ifRuleWrapper.get();
List<ProguardIfRule> toRemove = new ArrayList<>();
// Depending on which types that trigger the -if rule, the application of the subsequent
// -keep rule may vary (due to back references). So, we need to try all pairs of -if
// rule and live types.
for (DexProgramClass clazz :
ifRuleKey.relevantCandidatesForRule(
appView, subtypingInfo, classesWithNewlyLiveMembers, isEffectivelyLive)) {
assert isEffectivelyLive.test(clazz);
processActiveIfRulesWithMembersAndSameClassPrecondition(
ifRuleKey, ifRulesInEquivalence, clazz, toRemove);
}
if (ifRulesInEquivalence.size() == toRemove.size()) {
return true;
}
toRemove.forEach(ifRulesInEquivalence::remove);
return false;
});
tasks.await();
}
private void processActiveIfRulesWithMembersAndSameClassPrecondition(
ProguardIfRule ifRuleKey,
Set<ProguardIfRule> ifRulesInEquivalence,
DexProgramClass clazz,
List<ProguardIfRule> toRemove) {
// Check if the class matches the if-rule.
if (!evaluateClassForIfRule(ifRuleKey, clazz)) {
return;
}
// When matching an if rule against a type, the if-rule are filled with the current
// capture of wildcards. Propagate this down to member rules with same class part
// equivalence.
for (ProguardIfRule ifRule : ifRulesInEquivalence) {
registerClassCapture(ifRule, clazz, clazz);
List<Pair<ProguardIfRulePreconditionMatch, ProguardKeepRule>> materializedSubsequentRules =
new ArrayList<>();
evaluateIfRuleMembersAndMaterialize(
ifRule,
clazz,
(ifRulePreconditionMatch, materializedSubsequentRule) -> {
materializedSubsequentRules.add(
new Pair<>(ifRulePreconditionMatch, materializedSubsequentRule));
if (canRemoveSubsequentKeepRule(ifRule)) {
toRemove.add(ifRule);
return true;
}
return false;
});
applyMaterializedSubsequentRules(ifRule, materializedSubsequentRules);
}
}
public void processActiveIfRulesWithoutMembers(
Map<Wrapper<ProguardIfRule>, Set<ProguardIfRule>> ifRules,
Map<DexType, DexProgramClass> newlyLiveClasses,
Timing timing)
throws ExecutionException {
List<DexProgramClass> classMatches = new ArrayList<>(newlyLiveClasses.size());
MapUtils.removeIf(
ifRules,
(ifRuleWrapper, ifRulesInEquivalence) -> {
// Depending on which types that trigger the -if rule, the application of the subsequent
// -keep rule may vary (due to back references). So, we need to try all pairs of -if
// rule and live types.
ProguardIfRule ifRuleKey = ifRuleWrapper.get();
evaluateClassesForIfRule(ifRuleKey, newlyLiveClasses, classMatches, timing);
if (!classMatches.isEmpty()) {
ifRulesInEquivalence.removeIf(
ifRule -> processActiveIfRuleWithWithoutMembers(ifRule, classMatches));
classMatches.clear();
}
return ifRulesInEquivalence.isEmpty();
});
tasks.await();
}
/**
* Returns true if the subsequent rule of {@param ifRule} has been fully evaluated, meaning it can
* be safely ignored for the remainder of the current tree shaking.
*/
private boolean processActiveIfRuleWithWithoutMembers(
ProguardIfRule ifRule, List<DexProgramClass> classMatches) {
boolean isSubsequentRuleFullyEvaluated = false;
boolean noBackReferences = canRemoveSubsequentKeepRule(ifRule);
List<Pair<ProguardIfRulePreconditionMatch, ProguardKeepRule>> materializedSubsequentRules =
new ArrayList<>();
for (DexProgramClass clazz : classMatches) {
registerClassCapture(ifRule, clazz, clazz);
ProguardIfRulePreconditionMatch ifRulePreconditionMatch =
new ProguardIfRulePreconditionMatch(ifRule, clazz);
ifRulePreconditionMatch.disallowOptimizationsForReevaluation(enqueuer, rootSetBuilder);
ProguardKeepRule materializedSubsequentRule = ifRule.materialize(factory);
materializedSubsequentRules.add(
new Pair<>(ifRulePreconditionMatch, materializedSubsequentRule));
if (noBackReferences) {
isSubsequentRuleFullyEvaluated = true;
break;
}
}
applyMaterializedSubsequentRules(ifRule, materializedSubsequentRules);
return isSubsequentRuleFullyEvaluated;
}
private void incrementNumberOfProguardIfRuleClassEvaluations() {
if (appView.testing().measureProguardIfRuleEvaluations) {
appView.testing().proguardIfRuleEvaluationData.numberOfProguardIfRuleClassEvaluations++;
}
}
private void incrementNumberOfProguardIfRuleMemberEvaluations() {
if (appView.testing().measureProguardIfRuleEvaluations) {
appView.testing().proguardIfRuleEvaluationData.numberOfProguardIfRuleMemberEvaluations++;
}
}
private boolean canRemoveSubsequentKeepRule(ProguardIfRule rule) {
return !rule.getSubsequentRule().hasBackReferences();
}
/**
* To ensure the matching work correctly going forward, when a class has matched, we have to run
* the capturing again on the member rule to ensure the wild cards are correctly populated.
*
* @param memberRule The member rule to populate with wildcards.
* @param source The source class.
* @param target The target class that can be different when we have vertically merged classes.
*/
private void registerClassCapture(ProguardIfRule memberRule, DexClass source, DexClass target) {
boolean classNameResult = memberRule.getClassNames().matches(source.type);
assert classNameResult;
if (memberRule.hasInheritanceClassName()) {
boolean inheritanceResult = rootSetBuilder.satisfyInheritanceRule(target, memberRule);
assert inheritanceResult;
}
}
private void evaluateClassesForIfRule(
ProguardIfRule rule,
Map<DexType, DexProgramClass> newlyLiveClasses,
List<DexProgramClass> classMatches,
Timing timing) {
timing.begin("Evaluate precondition");
if (rule.getClassNames().hasSingleSpecificType()) {
timing.begin("Evaluate single class");
DexType singleSpecificType = rule.getClassNames().getSingleSpecificType();
DexProgramClass singleSpecificClass = newlyLiveClasses.get(singleSpecificType);
if (singleSpecificClass != null && evaluateClassForIfRule(rule, singleSpecificClass)) {
classMatches.add(singleSpecificClass);
}
timing.end();
} else {
for (DexProgramClass clazz : newlyLiveClasses.values()) {
timing.begin("Evaluate class");
if (evaluateClassForIfRule(rule, clazz)) {
classMatches.add(clazz);
}
timing.end();
}
}
timing.end();
}
/** Determines if {@param clazz} satisfies the given if-rule class specification. */
private boolean evaluateClassForIfRule(ProguardIfRule rule, DexProgramClass clazz) {
incrementNumberOfProguardIfRuleClassEvaluations();
if (!RootSetBuilder.satisfyClassType(rule, clazz)) {
return false;
}
if (!RootSetBuilder.satisfyAccessFlag(rule, clazz)) {
return false;
}
AnnotationMatchResult annotationMatchResult = RootSetBuilder.satisfyAnnotation(rule, clazz);
if (annotationMatchResult == null) {
return false;
}
rootSetBuilder.handleMatchedAnnotation(annotationMatchResult);
if (!rule.getClassNames().matches(clazz.type)) {
return false;
}
if (rule.hasInheritanceClassName()) {
// Try another live type since the current one doesn't satisfy the inheritance rule.
return rootSetBuilder.satisfyInheritanceRule(clazz, rule);
}
return true;
}
private void evaluateIfRuleMembersAndMaterialize(
ProguardIfRule rule,
DexProgramClass clazz,
BiPredicate<ProguardIfRulePreconditionMatch, ProguardKeepRule> materializedIfRuleConsumer) {
incrementNumberOfProguardIfRuleMemberEvaluations();
Collection<ProguardMemberRule> memberKeepRules = rule.getMemberRules();
assert !memberKeepRules.isEmpty();
List<DexClassAndField> fieldsInlinedByJavaC = new ArrayList<>();
Set<DexDefinition> filteredMembers = Sets.newIdentityHashSet();
Iterables.addAll(
filteredMembers,
clazz.fields(
f -> {
// Fields that are javac inlined are unsound as predicates for conditional rules.
// Ignore any such field members and record it for possible reporting later.
if (isFieldInlinedByJavaC(f)) {
fieldsInlinedByJavaC.add(DexClassAndField.create(clazz, f));
return false;
}
// Fields referenced only by -keep may not be referenced, we therefore have to
// filter on both live and referenced.
return (enqueuer.isFieldLive(f)
|| enqueuer.isFieldReferenced(f)
|| f.getOptimizationInfo().valueHasBeenPropagated())
&& appView
.graphLens()
.getOriginalFieldSignature(f.getReference())
.getHolderType()
.isIdenticalTo(clazz.getType());
}));
Iterables.addAll(
filteredMembers,
clazz.methods(
m ->
(enqueuer.isMethodLive(m)
|| enqueuer.isMethodTargeted(m)
|| m.getOptimizationInfo().returnValueHasBeenPropagated())
&& appView
.graphLens()
.getOriginalMethodSignature(m.getReference())
.getHolderType()
.isIdenticalTo(clazz.getType())));
// Check if the rule could hypothetically have matched a javac inlined field.
// If so mark the rule. Reporting happens only if the rule is otherwise unused.
if (!fieldsInlinedByJavaC.isEmpty()) {
for (ProguardMemberRule memberRule : memberKeepRules) {
if (!memberRule.getRuleType().includesFields()) {
continue;
}
for (DexClassAndField field : fieldsInlinedByJavaC) {
if (rootSetBuilder.sideEffectFreeIsRuleSatisfiedByField(memberRule, field)) {
rule.addInlinableFieldMatchingPrecondition(field.getReference());
}
}
}
}
// If the number of member rules to hold is more than live members, we can't make it.
if (filteredMembers.size() < memberKeepRules.size()) {
return;
}
// Depending on which members trigger the -if rule, the application of the subsequent
// -keep rule may vary (due to back references). So, we need to try literally all
// combinations of live members. But, we can at least limit the number of elements per
// combination as the size of member rules to satisfy.
// TODO(b/206086945): Consider ways of reducing the size of this set computation.
for (Set<DexDefinition> combination :
Sets.combinations(filteredMembers, memberKeepRules.size())) {
Collection<DexClassAndField> fieldsInCombination =
DexDefinition.filterDexEncodedField(
combination.stream(), field -> DexClassAndField.create(clazz, field))
.collect(Collectors.toList());
Collection<DexClassAndMethod> methodsInCombination =
DexDefinition.filterDexEncodedMethod(
combination.stream(), method -> DexClassAndMethod.create(clazz, method))
.collect(Collectors.toList());
// Member rules are combined as AND logic: if found unsatisfied member rule, this
// combination of live members is not a good fit.
ProgramMethodSet methodsSatisfyingRule = ProgramMethodSet.create();
boolean satisfied =
memberKeepRules.stream()
.allMatch(
memberRule -> {
if (rootSetBuilder.ruleSatisfiedByFields(memberRule, fieldsInCombination)) {
return true;
}
DexClassAndMethod methodSatisfyingRule =
rootSetBuilder.getMethodSatisfyingRule(memberRule, methodsInCombination);
if (methodSatisfyingRule != null) {
methodsSatisfyingRule.add(methodSatisfyingRule.asProgramMethod());
return true;
}
return false;
});
if (satisfied) {
ProguardIfRulePreconditionMatch ifRulePreconditionMatch =
new ProguardIfRulePreconditionMatch(rule, clazz, methodsSatisfyingRule);
ifRulePreconditionMatch.disallowOptimizationsForReevaluation(enqueuer, rootSetBuilder);
ProguardKeepRule materializedSubsequentRule = rule.materialize(factory);
if (materializedIfRuleConsumer.test(ifRulePreconditionMatch, materializedSubsequentRule)) {
return;
}
}
}
}
private boolean isFieldInlinedByJavaC(DexEncodedField field) {
if (enqueuer.getMode().isFinalTreeShaking()) {
// Ignore any field value in the final tree shaking pass so it remains consistent with the
// initial pass.
return field.getIsInlinableByJavaC();
}
return field.getOrComputeIsInlinableByJavaC(factory);
}
private void applyMaterializedSubsequentRules(
ProguardIfRule ifRule,
List<Pair<ProguardIfRulePreconditionMatch, ProguardKeepRule>> materializedSubsequentRules) {
materializedSubsequentRules =
MaterializedSubsequentRulesOptimizer.optimize(ifRule, materializedSubsequentRules);
for (Pair<ProguardIfRulePreconditionMatch, ProguardKeepRule> pair :
materializedSubsequentRules) {
ProguardIfRulePreconditionMatch ifRulePreconditionMatch = pair.getFirst();
ProguardKeepRule materializedSubsequentRule = pair.getSecond();
applyMaterializedSubsequentRule(materializedSubsequentRule, ifRulePreconditionMatch);
}
}
private void applyMaterializedSubsequentRule(
ProguardKeepRule materializedSubsequentRule,
ProguardIfRulePreconditionMatch ifRulePreconditionMatch) {
try {
rootSetBuilder.runPerRule(tasks, materializedSubsequentRule, ifRulePreconditionMatch);
} catch (ExecutionException e) {
throw new UncheckedExecutionException(e);
}
}
}