blob: 7ab727defe034c8e388e32c5ddcc2ac8532503a5 [file] [log] [blame]
// Copyright (c) 2022, 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;
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
import com.android.tools.r8.TextInputStream;
import com.android.tools.r8.TextOutputStream;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
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.GraphLens;
import com.android.tools.r8.graph.PrunedItems;
import com.android.tools.r8.ir.optimize.enums.EnumUnboxingLens;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.profile.AbstractProfile;
import com.android.tools.r8.profile.AbstractProfileRule;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.ThrowingConsumer;
import com.android.tools.r8.utils.TriConsumer;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UncheckedIOException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
public class ArtProfile implements AbstractProfile<ArtProfileClassRule, ArtProfileMethodRule> {
private final Map<DexReference, ArtProfileRule> rules;
ArtProfile(Map<DexReference, ArtProfileRule> rules) {
this.rules = rules;
}
public static Builder builder() {
return new Builder();
}
public static Builder builderForInitialArtProfile(
ArtProfileProvider artProfileProvider, InternalOptions options) {
return new Builder(artProfileProvider, options);
}
@Override
public boolean containsClassRule(DexType type) {
return rules.containsKey(type);
}
@Override
public boolean containsMethodRule(DexMethod method) {
return rules.containsKey(method);
}
public <E extends Exception> void forEachRule(ThrowingConsumer<ArtProfileRule, E> ruleConsumer)
throws E {
for (ArtProfileRule rule : rules.values()) {
ruleConsumer.accept(rule);
}
}
@Override
public <E1 extends Exception, E2 extends Exception> void forEachRule(
ThrowingConsumer<ArtProfileClassRule, E1> classRuleConsumer,
ThrowingConsumer<ArtProfileMethodRule, E2> methodRuleConsumer)
throws E1, E2 {
for (ArtProfileRule rule : rules.values()) {
rule.accept(classRuleConsumer, methodRuleConsumer);
}
}
@Override
public ArtProfileClassRule getClassRule(DexType type) {
return (ArtProfileClassRule) rules.get(type);
}
@Override
public ArtProfileMethodRule getMethodRule(DexMethod method) {
return (ArtProfileMethodRule) rules.get(method);
}
public int size() {
return rules.size();
}
public ArtProfile rewrittenWithLens(AppView<?> appView, GraphLens lens) {
if (lens.isEnumUnboxerLens()) {
return rewrittenWithLens(appView, lens.asEnumUnboxerLens());
}
return transform(
(classRule, classRuleBuilderFactory) -> {
DexType newClassRule = lens.lookupType(classRule.getType());
assert newClassRule.isClassType();
classRuleBuilderFactory.accept(newClassRule);
},
(methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) ->
methodRuleBuilderFactory
.apply(lens.getRenamedMethodSignature(methodRule.getMethod()))
.acceptMethodRuleInfoBuilder(
methodRuleInfoBuilder ->
methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())));
}
public ArtProfile rewrittenWithLens(AppView<?> appView, EnumUnboxingLens lens) {
return transform(
(classRule, classRuleBuilderFactory) -> {
DexType newClassRule = lens.lookupType(classRule.getType());
if (newClassRule.isClassType()) {
classRuleBuilderFactory.accept(newClassRule);
} else {
assert newClassRule.isIntType();
}
},
(methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> {
DexMethod newMethod = lens.getRenamedMethodSignature(methodRule.getMethod());
// When moving non-synthetic methods from an enum class to its enum utility class we also
// add a rule for the utility class.
if (newMethod.getHolderType() != methodRule.getMethod().getHolderType()) {
assert appView
.getSyntheticItems()
.isSyntheticOfKind(
newMethod.getHolderType(), naming -> naming.ENUM_UNBOXING_LOCAL_UTILITY_CLASS);
classRuleBuilderFactory.accept(newMethod.getHolderType());
}
methodRuleBuilderFactory
.apply(newMethod)
.acceptMethodRuleInfoBuilder(
methodRuleInfoBuilder ->
methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo()));
});
}
public ArtProfile rewrittenWithLens(NamingLens lens, DexItemFactory dexItemFactory) {
assert !lens.isIdentityLens();
return transform(
(classRule, classRuleBuilderFactory) ->
classRuleBuilderFactory.accept(lens.lookupType(classRule.getType(), dexItemFactory)),
(methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) ->
methodRuleBuilderFactory
.apply(lens.lookupMethod(methodRule.getMethod(), dexItemFactory))
.acceptMethodRuleInfoBuilder(
methodRuleInfoBuilder ->
methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo())));
}
public ArtProfile withoutMissingItems(AppView<?> appView) {
AppInfo appInfo = appView.appInfo();
return transform(
(classRule, classRuleBuilderFactory) -> {
if (appInfo.hasDefinitionForWithoutExistenceAssert(classRule.getType())) {
classRuleBuilderFactory.accept(classRule.getType());
}
},
(methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> {
DexClass clazz =
appInfo.definitionForWithoutExistenceAssert(methodRule.getMethod().getHolderType());
if (methodRule.getMethod().isDefinedOnClass(clazz)) {
methodRuleBuilderFactory
.apply(methodRule.getMethod())
.acceptMethodRuleInfoBuilder(
methodRuleInfoBuilder ->
methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo()));
}
});
}
public ArtProfile withoutPrunedItems(PrunedItems prunedItems) {
return transform(
(classRule, classRuleBuilderFactory) -> {
if (!prunedItems.isRemoved(classRule.getType())) {
classRuleBuilderFactory.accept(classRule.getType());
}
},
(methodRule, classRuleBuilderFactory, methodRuleBuilderFactory) -> {
if (!prunedItems.isRemoved(methodRule.getMethod())) {
methodRuleBuilderFactory
.apply(methodRule.getMethod())
.acceptMethodRuleInfoBuilder(
methodRuleInfoBuilder ->
methodRuleInfoBuilder.merge(methodRule.getMethodRuleInfo()));
}
});
}
private ArtProfile transform(
BiConsumer<ArtProfileClassRule, Consumer<DexType>> classTransformation,
TriConsumer<
ArtProfileMethodRule,
Consumer<DexType>,
Function<DexMethod, ArtProfileMethodRule.Builder>>
methodTransformation) {
Map<DexReference, ArtProfileRule.Builder> ruleBuilders = new LinkedHashMap<>();
Consumer<DexType> classRuleBuilderFactory =
newType ->
ruleBuilders
.computeIfAbsent(
newType, ignoreKey(() -> ArtProfileClassRule.builder().setType(newType)))
.asClassRuleBuilder();
Function<DexMethod, ArtProfileMethodRule.Builder> methodRuleBuilderFactory =
newMethod ->
ruleBuilders
.computeIfAbsent(
newMethod, ignoreKey(() -> ArtProfileMethodRule.builder().setMethod(newMethod)))
.asMethodRuleBuilder();
forEachRule(
// Supply a factory method for creating a builder. If the current rule should be included in
// the rewritten profile, the caller should call the provided builder factory method to
// create a class rule builder. If two rules are mapped to the same reference, the same rule
// builder is reused so that the two rules are merged into a single rule (with their flags
// merged).
classRule -> classTransformation.accept(classRule, classRuleBuilderFactory),
// As above.
methodRule ->
methodTransformation.accept(
methodRule, classRuleBuilderFactory, methodRuleBuilderFactory));
return builder().addRuleBuilders(ruleBuilders.values()).build();
}
public void supplyConsumer(ArtProfileConsumer consumer, Reporter reporter) {
if (consumer != null) {
TextOutputStream textOutputStream = consumer.getHumanReadableArtProfileConsumer();
if (textOutputStream != null) {
supplyHumanReadableArtProfileConsumer(textOutputStream);
}
ArtProfileRuleConsumer ruleConsumer = consumer.getRuleConsumer();
if (ruleConsumer != null) {
supplyRuleConsumer(ruleConsumer);
}
consumer.finished(reporter);
}
}
private void supplyHumanReadableArtProfileConsumer(TextOutputStream textOutputStream) {
try {
try (OutputStreamWriter outputStreamWriter =
new OutputStreamWriter(
textOutputStream.getOutputStream(), textOutputStream.getCharset())) {
forEachRule(
rule -> {
rule.writeHumanReadableRuleString(outputStreamWriter);
outputStreamWriter.write('\n');
});
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private void supplyRuleConsumer(ArtProfileRuleConsumer ruleConsumer) {
forEachRule(
classRule ->
ruleConsumer.acceptClassRule(
classRule.getClassReference(), classRule.getClassRuleInfo()),
methodRule ->
ruleConsumer.acceptMethodRule(
methodRule.getMethodReference(), methodRule.getMethodRuleInfo()));
}
public static class Builder
implements ArtProfileBuilder,
AbstractProfile.Builder<ArtProfileClassRule, ArtProfileMethodRule, ArtProfile, Builder> {
private final ArtProfileProvider artProfileProvider;
private final DexItemFactory dexItemFactory;
private Reporter reporter;
private final Map<DexReference, ArtProfileRule> rules = new LinkedHashMap<>();
Builder() {
this.artProfileProvider = null;
this.dexItemFactory = null;
this.reporter = null;
}
// Constructor for building the initial ART profile. The input is based on the Reference API, so
// access to the DexItemFactory is needed for conversion into the internal DexReference.
// Moreover, access to the Reporter is needed for diagnostics reporting.
Builder(ArtProfileProvider artProfileProvider, InternalOptions options) {
this.artProfileProvider = artProfileProvider;
this.dexItemFactory = options.dexItemFactory();
this.reporter = options.reporter;
}
@Override
public Builder addRule(AbstractProfileRule rule) {
return addRule(rule.asArtProfileRule());
}
@Override
public Builder addClassRule(ArtProfileClassRule classRule) {
assert !rules.containsKey(classRule.getReference());
rules.put(classRule.getType(), classRule);
return this;
}
@Override
public Builder addMethodRule(ArtProfileMethodRule methodRule) {
assert !rules.containsKey(methodRule.getReference());
rules.put(methodRule.getMethod(), methodRule);
return this;
}
public Builder addRule(ArtProfileRule rule) {
return rule.apply(this::addClassRule, this::addMethodRule);
}
public Builder addRuleBuilders(Collection<ArtProfileRule.Builder> ruleBuilders) {
ruleBuilders.forEach(ruleBuilder -> addRule(ruleBuilder.build()));
return this;
}
@Override
public Builder addClassRule(Consumer<ArtProfileClassRuleBuilder> classRuleBuilderConsumer) {
ArtProfileClassRule.Builder classRuleBuilder = ArtProfileClassRule.builder(dexItemFactory);
classRuleBuilderConsumer.accept(classRuleBuilder);
return addClassRule(classRuleBuilder.build());
}
@Override
public Builder addMethodRule(Consumer<ArtProfileMethodRuleBuilder> methodRuleBuilderConsumer) {
ArtProfileMethodRule.Builder methodRuleBuilder = ArtProfileMethodRule.builder(dexItemFactory);
methodRuleBuilderConsumer.accept(methodRuleBuilder);
return addMethodRule(methodRuleBuilder.build());
}
@Override
public Builder addHumanReadableArtProfile(
TextInputStream textInputStream,
Consumer<HumanReadableArtProfileParserBuilder> parserBuilderConsumer) {
HumanReadableArtProfileParser.Builder parserBuilder =
HumanReadableArtProfileParser.builder()
.setDiagnosticConsumer(reporter::info)
.setReporter(reporter)
.setProfileBuilder(this);
parserBuilderConsumer.accept(parserBuilder);
HumanReadableArtProfileParser parser = parserBuilder.build();
parser.parse(textInputStream, artProfileProvider.getOrigin());
return this;
}
@Override
public ArtProfile build() {
return new ArtProfile(rules);
}
}
}