blob: 894a54c75e6381db3198fccfc540a9cf39afdbf3 [file] [log] [blame]
// Copyright (c) 2024, 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.propagation;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.conversion.IRConverter;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.AbstractFunction;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteMonomorphicMethodState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.ConcreteValueState;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FieldStateCollection;
import com.android.tools.r8.optimize.argumentpropagation.codescanner.FlowGraphStateProvider;
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.ValueState;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.ThreadUtils;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
public class InFlowPropagator {
final AppView<AppInfoWithLiveness> appView;
final IRConverter converter;
final FieldStateCollection fieldStates;
final MethodStateCollectionByReference methodStates;
public InFlowPropagator(
AppView<AppInfoWithLiveness> appView,
IRConverter converter,
FieldStateCollection fieldStates,
MethodStateCollectionByReference methodStates) {
this.appView = appView;
this.converter = converter;
this.fieldStates = fieldStates;
this.methodStates = methodStates;
}
public void run(ExecutorService executorService) throws ExecutionException {
// Compute strongly connected components so that we can compute the fixpoint of multiple flow
// graphs in parallel.
List<FlowGraph> flowGraphs = computeStronglyConnectedFlowGraphs();
processFlowGraphs(flowGraphs, executorService);
// Account for the fact that fields that are read before they are written also needs to include
// the default value in the field state. We only need to analyze if a given field is read before
// it is written if the field has a non-trivial state in the flow graph. Therefore, we only
// perform this analysis after having computed the initial fixpoint(s). The hypothesis is that
// many fields will have reached the unknown state after the initial fixpoint, meaning there is
// fewer fields to analyze.
Map<FlowGraph, Deque<FlowGraphNode>> worklists =
includeDefaultValuesInFieldStates(flowGraphs, executorService);
// Since the inclusion of default values changes the flow graphs, we need to repeat the
// fixpoint.
processWorklists(worklists, executorService);
// The algorithm only changes the parameter states of each monomorphic method state. In case any
// of these method states have effectively become unknown, we replace them by the canonicalized
// unknown method state.
postProcessMethodStates(executorService);
}
private List<FlowGraph> computeStronglyConnectedFlowGraphs() {
// Build a graph with an edge from parameter p -> parameter p' if all argument information for p
// must be included in the argument information for p'.
FlowGraph flowGraph =
FlowGraph.builder(appView, converter, fieldStates, methodStates)
.addClasses(appView.appInfo().classes())
.build();
List<Set<FlowGraphNode>> stronglyConnectedComponents =
flowGraph.computeStronglyConnectedComponents();
return ListUtils.map(stronglyConnectedComponents, FlowGraph::new);
}
private Map<FlowGraph, Deque<FlowGraphNode>> includeDefaultValuesInFieldStates(
List<FlowGraph> flowGraphs, ExecutorService executorService) throws ExecutionException {
DefaultFieldValueJoiner joiner = new DefaultFieldValueJoiner(appView, flowGraphs);
return joiner.joinDefaultFieldValuesForFieldsWithReadBeforeWrite(executorService);
}
private void processFlowGraphs(List<FlowGraph> flowGraphs, ExecutorService executorService)
throws ExecutionException {
ThreadUtils.processItems(
flowGraphs, this::process, appView.options().getThreadingModule(), executorService);
}
private void processWorklists(
Map<FlowGraph, Deque<FlowGraphNode>> worklists, ExecutorService executorService)
throws ExecutionException {
ThreadUtils.processMap(
worklists, this::process, appView.options().getThreadingModule(), executorService);
}
private void process(FlowGraph flowGraph) {
// Build a worklist containing all the nodes.
Deque<FlowGraphNode> worklist = new ArrayDeque<>();
flowGraph.forEachNode(worklist::add);
process(flowGraph, worklist);
}
private void process(FlowGraph flowGraph, Deque<FlowGraphNode> worklist) {
// Repeatedly propagate argument information through edges in the flow graph until there are no
// more changes.
// TODO(b/190154391): Consider a path p1 -> p2 -> p3 in the graph. If we process p2 first, then
// p3, and then p1, then the processing of p1 could cause p2 to change, which means that we
// need to reprocess p2 and then p3. If we always process leaves in the graph first, we would
// process p1, then p2, then p3, and then be done.
while (!worklist.isEmpty()) {
FlowGraphNode node = worklist.removeLast();
node.unsetInWorklist();
propagate(flowGraph, node, worklist);
}
}
private void propagate(FlowGraph flowGraph, FlowGraphNode node, Deque<FlowGraphNode> worklist) {
if (node.isBottom()) {
return;
}
if (node.isUnknown()) {
assert !node.hasPredecessors();
for (FlowGraphNode successorNode : node.getSuccessors()) {
assert !successorNode.isUnknown();
successorNode.clearPredecessors(node);
successorNode.setStateToUnknown();
successorNode.addToWorkList(worklist);
}
node.clearDanglingSuccessors();
} else {
propagateNode(flowGraph, node, worklist);
}
}
private void propagateNode(
FlowGraph flowGraph, FlowGraphNode node, Deque<FlowGraphNode> worklist) {
ConcreteValueState state = node.getState().asConcrete();
node.removeSuccessorIf(
(successorNode, transferFunctions) -> {
assert !successorNode.isUnknown();
for (AbstractFunction transferFunction : transferFunctions) {
FlowGraphStateProvider flowGraphStateProvider =
FlowGraphStateProvider.create(flowGraph, transferFunction);
ValueState transferState = transferFunction.apply(flowGraphStateProvider, state);
if (transferState.isBottom()) {
// Nothing to propagate.
} else if (transferState.isUnknown()) {
successorNode.setStateToUnknown();
successorNode.addToWorkList(worklist);
} else {
ConcreteValueState concreteTransferState = transferState.asConcrete();
successorNode.addState(
appView, concreteTransferState, () -> successorNode.addToWorkList(worklist));
}
// If this successor has become unknown, there is no point in continuing to propagate
// flow to it from any of its predecessors. We therefore clear the predecessors to
// improve performance of the fixpoint computation.
if (successorNode.isUnknown()) {
successorNode.clearPredecessors(node);
return true;
}
assert !successorNode.isEffectivelyUnknown();
}
return false;
});
}
private void postProcessMethodStates(ExecutorService executorService) throws ExecutionException {
ThreadUtils.processItems(
appView.appInfo().classes(),
this::postProcessMethodStates,
appView.options().getThreadingModule(),
executorService);
}
private void postProcessMethodStates(DexProgramClass clazz) {
clazz.forEachProgramMethod(this::postProcessMethodState);
}
private void postProcessMethodState(ProgramMethod method) {
ConcreteMethodState methodState = methodStates.get(method).asConcrete();
if (methodState == null) {
return;
}
assert methodState.isMonomorphic();
ConcreteMonomorphicMethodState monomorphicMethodState = methodState.asMonomorphic();
if (monomorphicMethodState.isEffectivelyBottom()) {
methodStates.set(method, MethodState.bottom());
} else if (monomorphicMethodState.isEffectivelyUnknown()) {
methodStates.set(method, MethodState.unknown());
}
}
}