blob: fbeff68539ea05b99313dc317fcbb34481913c9a [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.ir.conversion;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.GraphLense;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.MethodProcessingId.Factory.ReservedMethodProcessingIds;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.info.OptimizationFeedback;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.IROrdering;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.LongLivedProgramMethodSetBuilder;
import com.android.tools.r8.utils.collections.ProgramMethodSet;
import com.android.tools.r8.utils.collections.SortedProgramMethodSet;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class PostMethodProcessor implements MethodProcessor {
private final AppView<AppInfoWithLiveness> appView;
private final Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap;
private final Deque<SortedProgramMethodSet> waves;
private SortedProgramMethodSet wave;
private final ProgramMethodSet processed = ProgramMethodSet.create();
private PostMethodProcessor(
AppView<AppInfoWithLiveness> appView,
Map<DexEncodedMethod, Collection<CodeOptimization>> methodsMap,
CallGraph callGraph) {
this.appView = appView;
this.methodsMap = methodsMap;
this.waves = createWaves(appView, callGraph);
}
@Override
public Phase getPhase() {
return Phase.POST;
}
@Override
public boolean shouldApplyCodeRewritings(ProgramMethod method) {
assert !wave.contains(method);
return !processed.contains(method);
}
public static class Builder {
private final Collection<CodeOptimization> defaultCodeOptimizations;
private final LongLivedProgramMethodSetBuilder<?> methodsToReprocess =
LongLivedProgramMethodSetBuilder.create();
private final Map<DexEncodedMethod, Collection<CodeOptimization>> optimizationsMap =
new IdentityHashMap<>();
Builder(Collection<CodeOptimization> defaultCodeOptimizations) {
this.defaultCodeOptimizations = defaultCodeOptimizations;
}
private void put(
ProgramMethodSet methodsToRevisit, Collection<CodeOptimization> codeOptimizations) {
if (codeOptimizations.isEmpty()) {
// Nothing to conduct.
return;
}
for (ProgramMethod method : methodsToRevisit) {
methodsToReprocess.add(method);
optimizationsMap
.computeIfAbsent(
method.getDefinition(),
// Optimization order might matter, hence a collection that preserves orderings.
k -> new LinkedHashSet<>())
.addAll(codeOptimizations);
}
}
public void put(ProgramMethodSet methodsToRevisit) {
put(methodsToRevisit, defaultCodeOptimizations);
}
public void put(PostOptimization postOptimization) {
Collection<CodeOptimization> codeOptimizations =
postOptimization.codeOptimizationsForPostProcessing();
if (codeOptimizations == null) {
codeOptimizations = defaultCodeOptimizations;
}
put(postOptimization.methodsToRevisit(), codeOptimizations);
}
// Some optimizations may change methods, creating new instances of the encoded methods with a
// new signature. The compiler needs to update the set of methods that must be reprocessed
// according to the graph lens.
public void rewrittenWithLens(AppView<AppInfoWithLiveness> appView, GraphLense applied) {
methodsToReprocess.rewrittenWithLens(appView, applied);
Map<DexEncodedMethod, Collection<CodeOptimization>> newOptimizationsMap =
new IdentityHashMap<>();
optimizationsMap.forEach(
(method, optimizations) ->
newOptimizationsMap.put(
appView.graphLense().mapDexEncodedMethod(method, appView, applied),
optimizations));
optimizationsMap.clear();
optimizationsMap.putAll(newOptimizationsMap);
}
PostMethodProcessor build(
AppView<AppInfoWithLiveness> appView, ExecutorService executorService, Timing timing)
throws ExecutionException {
if (!appView.appInfo().reprocess.isEmpty()) {
ProgramMethodSet set = ProgramMethodSet.create();
appView
.appInfo()
.reprocess
.forEach(
reference -> {
DexEncodedMethod definition = appView.definitionFor(reference);
if (definition != null) {
DexProgramClass clazz =
appView.definitionForHolder(definition).asProgramClass();
set.createAndAdd(clazz, definition);
}
});
put(set);
}
if (methodsToReprocess.isEmpty()) {
// Nothing to revisit.
return null;
}
CallGraph callGraph =
new PartialCallGraphBuilder(
appView, methodsToReprocess.build(appView, appView.graphLense()))
.build(executorService, timing);
return new PostMethodProcessor(appView, optimizationsMap, callGraph);
}
}
private Deque<SortedProgramMethodSet> createWaves(AppView<?> appView, CallGraph callGraph) {
IROrdering shuffle = appView.options().testing.irOrdering;
Deque<SortedProgramMethodSet> waves = new ArrayDeque<>();
int waveCount = 1;
while (!callGraph.isEmpty()) {
SortedProgramMethodSet wave = callGraph.extractRoots();
waves.addLast(wave);
if (Log.ENABLED && Log.isLoggingEnabledFor(PostMethodProcessor.class)) {
Log.info(getClass(), "Wave #%d: %d", waveCount++, wave.size());
}
}
return waves;
}
@Override
public boolean isProcessedConcurrently(ProgramMethod method) {
return wave != null && wave.contains(method);
}
void forEachWave(OptimizationFeedback feedback, ExecutorService executorService)
throws ExecutionException {
while (!waves.isEmpty()) {
wave = waves.removeFirst();
assert wave.size() > 0;
ReservedMethodProcessingIds methodProcessingIds =
appView.methodProcessingIdFactory().reserveIds(wave);
ThreadUtils.processItems(
wave,
(method, index) -> {
Collection<CodeOptimization> codeOptimizations = methodsMap.get(method.getDefinition());
assert codeOptimizations != null && !codeOptimizations.isEmpty();
forEachMethod(
method, codeOptimizations, feedback, methodProcessingIds.get(method, index));
},
executorService);
processed.addAll(wave);
}
}
private void forEachMethod(
ProgramMethod method,
Collection<CodeOptimization> codeOptimizations,
OptimizationFeedback feedback,
MethodProcessingId methodProcessingId) {
// TODO(b/140766440): Make IRConverter#process receive a list of CodeOptimization to conduct.
// Then, we can share IRCode creation there.
if (appView.options().skipIR) {
feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
return;
}
IRCode code = method.buildIR(appView);
if (code == null) {
feedback.markProcessed(method.getDefinition(), ConstraintWithTarget.NEVER);
return;
}
// TODO(b/140768815): Reprocessing may trigger more methods to revisit. Update waves on-the-fly.
for (CodeOptimization codeOptimization : codeOptimizations) {
codeOptimization.optimize(code, feedback, this, methodProcessingId);
}
}
}