blob: b73ba99b06cd851dd55e161b4ad90cb28a49001a [file] [log] [blame]
// Copyright (c) 2021, 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.argumentpropagation;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethodSignature;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ImmediateProgramSubtypingInfo;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.AbstractValueSupplier;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
import com.android.tools.r8.ir.conversion.PrimaryR8IRConverter;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.MethodStateCollectionByReference;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.VirtualRootMethodsAnalysis;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
import com.android.tools.r8.optimize.argumentpropagation.unusedarguments.EffectivelyUnusedArgumentsAnalysis;
import com.android.tools.r8.optimize.argumentpropagation.utils.ProgramClassesBidirectedGraph;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.android.tools.r8.utils.collections.DexMethodSignatureSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
/** Optimization that propagates information about arguments from call sites to method entries. */
public class ArgumentPropagator {
private final AppView<AppInfoWithLiveness> appView;
/**
* Collects information about arguments from call sites, meanwhile pruning redundant information.
*
* <p>The data held by this instance is incomplete and should not be used for optimization until
* processed by {@link ArgumentPropagatorOptimizationInfoPopulator}.
*/
private ArgumentPropagatorCodeScanner codeScanner;
/** Collects and solves constraints for effectively unused argument removal. */
private EffectivelyUnusedArgumentsAnalysis effectivelyUnusedArgumentsAnalysis;
/**
* Analyzes the uses of arguments in methods to determine when reprocessing of methods will likely
* not lead to any additional code optimizations.
*/
private ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection;
public ArgumentPropagator(AppView<AppInfoWithLiveness> appView) {
assert appView.enableWholeProgramOptimizations();
assert appView.options().isOptimizing();
assert appView.options().callSiteOptimizationOptions().isEnabled();
this.appView = appView;
}
/**
* Called by {@link IRConverter} *before* the primary optimization pass to setup the scanner for
* collecting argument information from the code objects.
*/
public void initializeCodeScanner(ExecutorService executorService, Timing timing)
throws ExecutionException {
assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
timing.begin("Argument propagator");
timing.begin("Initialize code scanner");
reprocessingCriteriaCollection = new ArgumentPropagatorReprocessingCriteriaCollection(appView);
codeScanner = new ArgumentPropagatorCodeScanner(appView, reprocessingCriteriaCollection);
effectivelyUnusedArgumentsAnalysis = new EffectivelyUnusedArgumentsAnalysis(appView);
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
List<Set<DexProgramClass>> stronglyConnectedProgramClasses =
new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
.computeStronglyConnectedComponents();
ThreadUtils.processItems(
stronglyConnectedProgramClasses,
classes -> {
// Disable argument propagation for methods that should not be optimized by setting their
// method state to unknown.
new ArgumentPropagatorUnoptimizableFieldsAndMethods(
appView,
immediateSubtypingInfo,
codeScanner.getFieldStates(),
codeScanner.getMethodStates())
.run(classes);
// Compute the mapping from virtual methods to their root virtual method and the set of
// monomorphic virtual methods.
new VirtualRootMethodsAnalysis(appView, immediateSubtypingInfo)
.initializeVirtualRootMethods(classes, codeScanner);
// Find the virtual methods in the strongly connected component that only have monomorphic
// calls.
effectivelyUnusedArgumentsAnalysis.initializeOptimizableVirtualMethods(classes);
},
appView.options().getThreadingModule(),
executorService);
timing.end();
timing.end();
}
/** Called by {@link IRConverter} prior to finalizing methods. */
public void scan(
ProgramMethod method, IRCode code, MethodProcessor methodProcessor, Timing timing) {
if (codeScanner != null) {
assert methodProcessor.isPrimaryMethodProcessor();
AbstractValueSupplier abstractValueSupplier =
value -> value.getAbstractValue(appView, method);
codeScanner.scan(method, code, abstractValueSupplier, timing);
assert effectivelyUnusedArgumentsAnalysis != null;
effectivelyUnusedArgumentsAnalysis.scan(method, code);
assert reprocessingCriteriaCollection != null;
reprocessingCriteriaCollection.analyzeArgumentUses(method, code);
} else {
assert !methodProcessor.isPrimaryMethodProcessor();
assert effectivelyUnusedArgumentsAnalysis == null;
assert !methodProcessor.isPostMethodProcessor() || reprocessingCriteriaCollection == null;
}
}
public void publishDelayedReprocessingCriteria() {
assert reprocessingCriteriaCollection != null;
reprocessingCriteriaCollection.publishDelayedReprocessingCriteria();
}
public void transferArgumentInformation(ProgramMethod from, ProgramMethod to) {
assert codeScanner != null;
MethodStateCollectionByReference methodStates = codeScanner.getMethodStates();
MethodState methodState = methodStates.remove(from);
if (!methodState.isBottom()) {
methodStates.addMethodState(appView, to, methodState);
}
}
public void tearDownCodeScanner(
PrimaryR8IRConverter converter,
PostMethodProcessor.Builder postMethodProcessorBuilder,
ExecutorService executorService,
Timing timing)
throws ExecutionException {
assert !appView.getSyntheticItems().hasPendingSyntheticClasses();
assert reprocessingCriteriaCollection.verifyNoDelayedReprocessingCriteria();
timing.begin("Argument propagator");
// Compute the strongly connected program components for parallel execution.
timing.begin("Compute components");
ImmediateProgramSubtypingInfo immediateSubtypingInfo =
ImmediateProgramSubtypingInfo.create(appView);
List<Set<DexProgramClass>> stronglyConnectedProgramComponents =
new ProgramClassesBidirectedGraph(appView, immediateSubtypingInfo)
.computeStronglyConnectedComponents();
timing.end();
// Set the optimization info on each method.
Map<Set<DexProgramClass>, DexMethodSignatureSet> interfaceDispatchOutsideProgram =
new IdentityHashMap<>();
populateParameterOptimizationInfo(
converter,
immediateSubtypingInfo,
stronglyConnectedProgramComponents,
(stronglyConnectedProgramComponent, signature) -> {
interfaceDispatchOutsideProgram
.computeIfAbsent(
stronglyConnectedProgramComponent, (unused) -> DexMethodSignatureSet.create())
.add(signature);
},
postMethodProcessorBuilder,
executorService,
timing);
// Using the computed optimization info, build a graph lens that describes the mapping from
// methods with constant parameters to methods with the constant parameters removed.
Set<DexProgramClass> affectedClasses = SetUtils.newConcurrentHashSet();
ArgumentPropagatorGraphLens graphLens =
new ArgumentPropagatorProgramOptimizer(
appView, immediateSubtypingInfo, interfaceDispatchOutsideProgram)
.run(stronglyConnectedProgramComponents, affectedClasses::add, executorService, timing);
// Find all the code objects that need reprocessing.
new ArgumentPropagatorMethodReprocessingEnqueuer(appView, reprocessingCriteriaCollection)
.enqueueAndPrepareMethodsForReprocessing(
graphLens, postMethodProcessorBuilder, executorService, timing);
reprocessingCriteriaCollection = null;
// Finally, apply the graph lens to the program (i.e., remove constant parameters from method
// definitions).
new ArgumentPropagatorApplicationFixer(appView, graphLens)
.fixupApplication(affectedClasses, executorService, timing);
timing.end();
// Ensure determinism of method-to-reprocess set.
appView.testing().checkDeterminism(postMethodProcessorBuilder::dump);
appView.notifyOptimizationFinishedForTesting();
}
/**
* Called by {@link IRConverter} *after* the primary optimization pass to populate the parameter
* optimization info.
*/
private void populateParameterOptimizationInfo(
PrimaryR8IRConverter converter,
ImmediateProgramSubtypingInfo immediateSubtypingInfo,
List<Set<DexProgramClass>> stronglyConnectedProgramComponents,
BiConsumer<Set<DexProgramClass>, DexMethodSignature> interfaceDispatchOutsideProgram,
PostMethodProcessor.Builder postMethodProcessorBuilder,
ExecutorService executorService,
Timing timing)
throws ExecutionException {
// Unset the scanner since all code objects have been scanned at this point.
assert appView.isAllCodeProcessed();
FieldStateCollection fieldStates = codeScanner.getFieldStates();
MethodStateCollectionByReference methodStates = codeScanner.getMethodStates();
appView.testing().argumentPropagatorEventConsumer.acceptCodeScannerResult(methodStates);
codeScanner = null;
postMethodProcessorBuilder.rewrittenWithLens(appView);
timing.begin("Compute optimization info");
new ArgumentPropagatorOptimizationInfoPropagator(
appView,
converter,
immediateSubtypingInfo,
fieldStates,
methodStates,
stronglyConnectedProgramComponents,
interfaceDispatchOutsideProgram)
.propagateOptimizationInfo(executorService, timing);
// TODO(b/296030319): Also publish the computed optimization information for fields.
new ArgumentPropagatorOptimizationInfoPopulator(
appView, converter, fieldStates, methodStates, postMethodProcessorBuilder)
.populateOptimizationInfo(executorService, timing);
timing.end();
timing.begin("Compute unused arguments");
effectivelyUnusedArgumentsAnalysis.computeEffectivelyUnusedArguments();
effectivelyUnusedArgumentsAnalysis = null;
timing.end();
}
/**
* Called by {@link IRConverter} at the end of a wave if a method is pruned by an optimization.
*
* <p>We only prune (1) direct single caller methods and (2) isX()/asX() virtual method overrides.
* For (2), we always transfer the argument information for the isX()/asX() method to its parent
* method using {@link #transferArgumentInformation(ProgramMethod, ProgramMethod)}, which unsets
* the argument information for the override.
*
* <p>Therefore, we assert that we only find a method state for direct methods.
*/
public void onMethodPruned(ProgramMethod method) {
if (codeScanner != null) {
MethodState methodState = codeScanner.getMethodStates().removeOrElse(method, null);
assert methodState == null || method.getDefinition().belongsToDirectPool();
}
assert effectivelyUnusedArgumentsAnalysis != null;
effectivelyUnusedArgumentsAnalysis.onMethodPruned(method);
}
public void onMethodCodePruned(ProgramMethod method) {
assert effectivelyUnusedArgumentsAnalysis != null;
effectivelyUnusedArgumentsAnalysis.onMethodCodePruned(method);
}
}