blob: 43e484db1d4749872edaa4a79691741f38c37f19 [file] [log] [blame]
// Copyright (c) 2018, 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.analysis.escape;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.utils.Box;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
/**
* Escape analysis that collects instructions where the value of interest can escape from the given
* method, i.e., can be stored outside, passed as arguments, etc.
*
* <p>Note: at this point, the analysis always returns a non-empty list for values that are defined
* by a new-instance IR, since they could escape via the corresponding direct call to `<init>()`.
*/
public class EscapeAnalysis {
private final AppView<?> appView;
private final EscapeAnalysisConfiguration configuration;
// Data structure to ensure termination in case of cycles in the data flow graph.
private final Set<Value> trackedValues = Sets.newIdentityHashSet();
// Worklist containing values that are alias of the value of interest.
private final Deque<Value> valuesToTrack = new ArrayDeque<>();
public EscapeAnalysis(AppView<?> appView) {
this(appView, DefaultEscapeAnalysisConfiguration.getInstance());
}
public EscapeAnalysis(AppView<?> appView, EscapeAnalysisConfiguration configuration) {
this.appView = appView;
this.configuration = configuration;
}
/**
* Main entry point for running the escape analysis. This method is used to determine if
* `valueOfInterest` escapes from the given method. Returns true as soon as the value is known to
* escape, hence it is more efficient to use this method over {@link #computeEscapeRoutes(IRCode,
* Value)}.
*/
public boolean isEscaping(IRCode code, Value valueOfInterest) {
Box<Instruction> firstEscapeRoute = new Box<>();
run(
code,
valueOfInterest,
escapeRoute -> {
firstEscapeRoute.set(escapeRoute);
return true;
});
return firstEscapeRoute.isSet();
}
/**
* Main entry point for running the escape analysis. This method is used to determine if
* `valueOfInterest` escapes from the given method. Returns the set of instructions from which the
* value may escape. Clients that only need to know if the value escapes or not should use {@link
* #isEscaping(IRCode, Value)} for better performance.
*/
public Set<Instruction> computeEscapeRoutes(IRCode code, Value valueOfInterest) {
ImmutableSet.Builder<Instruction> escapeRoutes = ImmutableSet.builder();
run(
code,
valueOfInterest,
escapeRoute -> {
escapeRoutes.add(escapeRoute);
return false;
});
return escapeRoutes.build();
}
// Returns the set of instructions where the value of interest can escape from the code.
private void run(IRCode code, Value valueOfInterest, Predicate<Instruction> stoppingCriterion) {
assert valueOfInterest.getType().isReferenceType();
assert trackedValues.isEmpty();
assert valuesToTrack.isEmpty();
// Sanity check: value (or its containing block) belongs to the code.
BasicBlock block =
valueOfInterest.isPhi()
? valueOfInterest.asPhi().getBlock()
: valueOfInterest.definition.getBlock();
assert code.blocks.contains(block);
List<Value> arguments = code.collectArguments();
addToWorklist(valueOfInterest);
while (!valuesToTrack.isEmpty()) {
Value alias = valuesToTrack.poll();
assert alias != null;
// Make sure we are not tracking values over and over again.
assert trackedValues.contains(alias);
boolean stopped =
processValue(valueOfInterest, alias, block, code, arguments, stoppingCriterion);
if (stopped) {
break;
}
}
trackedValues.clear();
valuesToTrack.clear();
}
private boolean processValue(
Value root,
Value alias,
BasicBlock block,
IRCode code,
List<Value> arguments,
Predicate<Instruction> stoppingCriterion) {
for (Value phi : alias.uniquePhiUsers()) {
addToWorklist(phi);
}
for (Instruction user : alias.uniqueUsers()) {
// Users in the same block need one more filtering.
if (user.getBlock() == block) {
// When the value of interest has the definition
if (!root.isPhi()) {
List<Instruction> instructions = block.getInstructions();
// Make sure we're not considering instructions prior to the value of interest.
if (instructions.indexOf(user) < instructions.indexOf(root.definition)) {
continue;
}
}
}
if (!configuration.isLegitimateEscapeRoute(appView, this, user, code.method.method)
&& isDirectlyEscaping(user, code.method.method, arguments)) {
if (stoppingCriterion.test(user)) {
return true;
}
}
// Track aliased value.
boolean couldIntroduceTrackedValueAlias = false;
for (Value trackedValue : trackedValues) {
if (user.couldIntroduceAnAlias(appView, trackedValue)) {
couldIntroduceTrackedValueAlias = true;
break;
}
}
if (couldIntroduceTrackedValueAlias) {
Value outValue = user.outValue();
assert outValue != null && outValue.getType().isReferenceType();
addToWorklist(outValue);
}
// Track propagated values through which the value of interest can escape indirectly.
Value propagatedValue = getPropagatedSubject(alias, user);
if (propagatedValue != null && propagatedValue != alias) {
assert propagatedValue.getType().isReferenceType();
addToWorklist(propagatedValue);
}
}
return false;
}
private void addToWorklist(Value value) {
assert value != null;
if (trackedValues.add(value)) {
valuesToTrack.push(value);
}
}
private boolean isDirectlyEscaping(Instruction instr, DexMethod context, List<Value> arguments) {
// As return value.
if (instr.isReturn()) {
return true;
}
// Throwing an exception.
if (instr.isThrow()) {
return true;
}
// Storing to the static field.
if (instr.isStaticPut()) {
return true;
}
// Passing as arguments.
if (instr.isInvokeMethod()) {
DexMethod invokedMethod = instr.asInvokeMethod().getInvokedMethod();
// Filter out the recursion with exactly same arguments.
if (invokedMethod == context) {
return !instr.inValues().equals(arguments);
}
return true;
}
// Storing to non-local array.
if (instr.isArrayPut()) {
Value array = instr.asArrayPut().array().getAliasedValue();
return array.isPhi() || !array.definition.isCreatingArray();
}
// Storing to non-local instance.
if (instr.isInstancePut()) {
Value instance = instr.asInstancePut().object().getAliasedValue();
return instance.isPhi() || !instance.definition.isNewInstance();
}
return false;
}
public boolean isValueOfInterestOrAlias(Value value) {
return trackedValues.contains(value);
}
private static Value getPropagatedSubject(Value src, Instruction instr) {
if (instr.isArrayPut()) {
return instr.asArrayPut().array();
}
if (instr.isInstancePut()) {
return instr.asInstancePut().object();
}
return null;
}
}