blob: 5056ee38597d4746a02e0e532781c1e270756bbf [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.Code;
import com.android.tools.r8.graph.DefaultUseRegistryWithResult;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.FieldResolutionResult;
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.ProgramField;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.graph.bytecodemetadata.BytecodeMetadataProvider;
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.BasicBlockIterator;
import com.android.tools.r8.ir.code.FieldInstruction;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.ir.conversion.IRToLirFinalizer;
import com.android.tools.r8.ir.conversion.PostMethodProcessor;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.ir.optimize.info.CallSiteOptimizationInfo;
import com.android.tools.r8.lightir.LirCode;
import com.android.tools.r8.lightir.LirConstant;
import com.android.tools.r8.optimize.argumentpropagation.reprocessingcriteria.ArgumentPropagatorReprocessingCriteriaCollection;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ArrayUtils;
import com.android.tools.r8.utils.ObjectUtils;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
/** Finds the methods in the program that should be reprocessed due to argument propagation. */
public class ArgumentPropagatorMethodReprocessingEnqueuer {
private final AppView<AppInfoWithLiveness> appView;
private final ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection;
public ArgumentPropagatorMethodReprocessingEnqueuer(
AppView<AppInfoWithLiveness> appView,
ArgumentPropagatorReprocessingCriteriaCollection reprocessingCriteriaCollection) {
this.appView = appView;
this.reprocessingCriteriaCollection = reprocessingCriteriaCollection;
}
/**
* Called indirectly from {@link IRConverter} to add all methods that require reprocessing to
* {@param postMethodProcessorBuilder}.
*/
public void enqueueAndPrepareMethodsForReprocessing(
ArgumentPropagatorGraphLens graphLens,
PostMethodProcessor.Builder postMethodProcessorBuilder,
ExecutorService executorService,
Timing timing)
throws ExecutionException {
timing.begin("Enqueue methods for reprocessing");
// Bring the methods to reprocess set up-to-date with the current graph lens (i.e., the one
// prior to the argument propagator lens, which has not yet been installed!).
timing.begin("Rewrite methods to reprocess");
postMethodProcessorBuilder.rewrittenWithLens(appView);
timing.end();
timing.begin("Enqueue methods with non-trivial info");
enqueueAffectedCallees(graphLens, postMethodProcessorBuilder);
timing.end();
timing.begin("Enqueue affected methods");
if (graphLens != null) {
enqueueAffectedCallers(graphLens, postMethodProcessorBuilder, executorService);
}
timing.end();
timing.begin("Eliminate dead field accesses");
eliminateDeadFieldAccesses(executorService);
timing.end();
timing.end();
}
private void enqueueAffectedCallees(
ArgumentPropagatorGraphLens graphLens,
PostMethodProcessor.Builder postMethodProcessorBuilder) {
GraphLens currentGraphLens = appView.graphLens();
for (DexProgramClass clazz : appView.appInfo().classes()) {
clazz.forEachProgramMethodMatching(
DexEncodedMethod::hasCode,
method -> {
if (method.getDefinition().getCode().isSharedCodeObject()) {
return;
}
if (graphLens != null) {
DexMethod rewrittenMethodSignature =
graphLens.getNextMethodSignature(method.getReference());
if (graphLens.hasPrototypeChanges(rewrittenMethodSignature)) {
assert !appView.appInfo().isNeverReprocessMethod(method);
postMethodProcessorBuilder.add(method, currentGraphLens);
appView.testing().callSiteOptimizationInfoInspector.accept(method);
return;
}
}
CallSiteOptimizationInfo callSiteOptimizationInfo =
method.getOptimizationInfo().getArgumentInfos();
if (reprocessingCriteriaCollection
.getReprocessingCriteria(method)
.shouldReprocess(appView, method, callSiteOptimizationInfo)
&& !appView.appInfo().isNeverReprocessMethod(method)) {
postMethodProcessorBuilder.add(method, currentGraphLens);
appView.testing().callSiteOptimizationInfoInspector.accept(method);
}
});
}
}
@SuppressWarnings("HidingField")
// TODO(b/190154391): This could invalidate the @NeverReprocessMethod testing annotations (non
// critical). If @NeverReprocessMethod is used, we would need to scan the application to mark
// methods as unoptimizable prior to removing parameters from the application.
private void enqueueAffectedCallers(
ArgumentPropagatorGraphLens graphLens,
PostMethodProcessor.Builder postMethodProcessorBuilder,
ExecutorService executorService)
throws ExecutionException {
GraphLens currentGraphLens = appView.graphLens();
Collection<List<ProgramMethod>> methodsToReprocess =
ThreadUtils.processItemsWithResults(
appView.appInfo().classes(),
clazz -> {
List<ProgramMethod> methodsToReprocessInClass = new ArrayList<>();
clazz.forEachProgramMethodMatching(
DexEncodedMethod::hasCode,
method -> {
if (!postMethodProcessorBuilder.contains(method, currentGraphLens)) {
AffectedMethodUseRegistry registry =
new AffectedMethodUseRegistry(appView, method, graphLens);
if (method.registerCodeReferencesWithResult(registry)) {
assert !method.getOptimizationInfo().hasBeenInlinedIntoSingleCallSite();
methodsToReprocessInClass.add(method);
}
}
});
return methodsToReprocessInClass;
},
appView.options().getThreadingModule(),
executorService);
methodsToReprocess.forEach(
methodsToReprocessInClass ->
postMethodProcessorBuilder.addAll(methodsToReprocessInClass, currentGraphLens));
}
private void eliminateDeadFieldAccesses(ExecutorService executorService)
throws ExecutionException {
ThreadUtils.processItems(
appView.appInfo().classes(),
clazz -> {
clazz.forEachProgramMethodMatching(
m -> m.hasCode() && !m.getCode().isSharedCodeObject(),
this::eliminateDeadFieldAccesses);
},
appView.options().getThreadingModule(),
executorService);
}
private void eliminateDeadFieldAccesses(ProgramMethod method) {
Code code = method.getDefinition().getCode();
if (!code.isLirCode()) {
assert appView.isCfByteCodePassThrough(method.getDefinition());
return;
}
LirCode<Integer> lirCode = code.asLirCode();
LirCode<Integer> rewrittenLirCode = eliminateDeadFieldAccesses(method, lirCode);
if (ObjectUtils.notIdentical(lirCode, rewrittenLirCode)) {
method.setCode(rewrittenLirCode, appView);
}
}
private LirCode<Integer> eliminateDeadFieldAccesses(
ProgramMethod method, LirCode<Integer> lirCode) {
if (ArrayUtils.none(
lirCode.getConstantPool(), constant -> isDeadFieldAccess(constant, method))) {
return lirCode;
}
IRCode irCode = lirCode.buildIR(method, appView);
AffectedValues affectedValues = new AffectedValues();
BasicBlockIterator blocks = irCode.listIterator();
Set<BasicBlock> blocksToRemove = Sets.newIdentityHashSet();
while (blocks.hasNext()) {
BasicBlock block = blocks.next();
if (blocksToRemove.contains(block)) {
continue;
}
InstructionListIterator instructionIterator = block.listIterator(irCode);
while (instructionIterator.hasNext()) {
FieldInstruction fieldInstruction = instructionIterator.next().asFieldInstruction();
if (fieldInstruction == null || !isDeadFieldAccess(fieldInstruction.getField(), method)) {
continue;
}
instructionIterator.replaceCurrentInstructionWithThrowNull(
appView, irCode, blocks, blocksToRemove, affectedValues);
}
}
irCode.removeBlocks(blocksToRemove);
affectedValues.narrowingWithAssumeRemoval(appView, irCode);
irCode.removeRedundantBlocks();
return new IRToLirFinalizer(appView)
.finalizeCode(irCode, BytecodeMetadataProvider.empty(), Timing.empty());
}
private boolean isDeadFieldAccess(LirConstant constant, ProgramMethod context) {
if (!(constant instanceof DexField)) {
return false;
}
DexField fieldReference = (DexField) constant;
return isDeadFieldAccess(fieldReference, context);
}
private boolean isDeadFieldAccess(DexField fieldReference, ProgramMethod context) {
ProgramField field = appView.appInfo().resolveField(fieldReference, context).getProgramField();
return field != null && field.getOptimizationInfo().getAbstractValue().isBottom();
}
static class AffectedMethodUseRegistry
extends DefaultUseRegistryWithResult<Boolean, ProgramMethod> {
private final AppView<AppInfoWithLiveness> appViewWithLiveness;
private final ArgumentPropagatorGraphLens graphLens;
AffectedMethodUseRegistry(
AppView<AppInfoWithLiveness> appViewWithLiveness,
ProgramMethod context,
ArgumentPropagatorGraphLens graphLens) {
super(appViewWithLiveness, context, false);
this.appViewWithLiveness = appViewWithLiveness;
this.graphLens = graphLens;
}
private void markAffected() {
setResult(Boolean.TRUE);
}
@Override
public void registerInvokeDirect(DexMethod method) {
registerInvokeMethod(method);
}
@Override
public void registerInvokeInterface(DexMethod method) {
registerInvokeMethod(method);
}
@Override
public void registerInvokeStatic(DexMethod method) {
registerInvokeMethod(method);
}
@Override
public void registerInvokeSuper(DexMethod method) {
registerInvokeMethod(method);
}
@Override
public void registerInvokeVirtual(DexMethod method) {
registerInvokeMethod(method);
}
@SuppressWarnings("ReferenceEquality")
private void registerInvokeMethod(DexMethod method) {
SingleResolutionResult<?> resolutionResult =
appViewWithLiveness
.appInfo()
.unsafeResolveMethodDueToDexFormatLegacy(method)
.asSingleResolution();
if (resolutionResult == null || !resolutionResult.getResolvedHolder().isProgramClass()) {
return;
}
ProgramMethod resolvedMethod = resolutionResult.getResolvedProgramMethod();
DexMethod rewrittenMethodReference =
graphLens.getNextMethodSignature(resolvedMethod.getReference());
if (rewrittenMethodReference != resolvedMethod.getReference()
|| graphLens.hasPrototypeChanges(rewrittenMethodReference)) {
markAffected();
}
}
@Override
public void registerInstanceFieldRead(DexField field) {
registerFieldAccess(field);
}
@Override
public void registerInstanceFieldWrite(DexField field) {
registerFieldAccess(field);
}
@Override
public void registerStaticFieldRead(DexField field) {
registerFieldAccess(field);
}
@Override
public void registerStaticFieldWrite(DexField field) {
registerFieldAccess(field);
}
@SuppressWarnings("ReferenceEquality")
private void registerFieldAccess(DexField field) {
FieldResolutionResult resolutionResult = appViewWithLiveness.appInfo().resolveField(field);
if (resolutionResult.getSingleProgramField() == null) {
return;
}
ProgramField resolvedField = resolutionResult.getSingleProgramField();
DexField rewrittenFieldReference =
graphLens.getNextFieldSignature(resolvedField.getReference());
if (rewrittenFieldReference != resolvedField.getReference()) {
markAffected();
}
}
}
}