Generalize escape analysis
This CL generalizes the escape analysis in such a way that it is now possible to customize which escape instructions for a given value that are considered legitimate. This is achieved by passing a configuration object to the escape analysis.
Change-Id: I07d9f5b20d2cff11625997294c8e9fe0be95cdba
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/escape/DefaultEscapeAnalysisConfiguration.java b/src/main/java/com/android/tools/r8/ir/analysis/escape/DefaultEscapeAnalysisConfiguration.java
new file mode 100644
index 0000000..162120b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/escape/DefaultEscapeAnalysisConfiguration.java
@@ -0,0 +1,27 @@
+// 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.analysis.escape;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Instruction;
+
+public class DefaultEscapeAnalysisConfiguration implements EscapeAnalysisConfiguration {
+
+ private static final DefaultEscapeAnalysisConfiguration INSTANCE =
+ new DefaultEscapeAnalysisConfiguration();
+
+ private DefaultEscapeAnalysisConfiguration() {}
+
+ public static DefaultEscapeAnalysisConfiguration getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isLegitimateEscapeRoute(
+ AppView<?> appView, Instruction escapeRoute, DexMethod context) {
+ return false;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
index 685a251..84aeadc 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysis.java
@@ -3,30 +3,90 @@
// 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.DexEncodedMethod;
+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.
*
- * 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>()`.
+ * <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.
- public static Set<Instruction> escape(IRCode code, Value valueOfInterest) {
+ private void run(IRCode code, Value valueOfInterest, Predicate<Instruction> stoppingCriterion) {
+ assert valueOfInterest.getTypeLattice().isReference();
+ assert trackedValues.isEmpty();
+ assert valuesToTrack.isEmpty();
+
// Sanity check: value (or its containing block) belongs to the code.
BasicBlock block =
valueOfInterest.isPhi()
@@ -35,51 +95,77 @@
assert code.blocks.contains(block);
List<Value> arguments = code.collectArguments();
- ImmutableSet.Builder<Instruction> builder = ImmutableSet.builder();
- Set<Value> trackedValues = Sets.newIdentityHashSet();
- Deque<Value> valuesToTrack = new ArrayDeque<>();
- valuesToTrack.push(valueOfInterest);
+ addToWorklist(valueOfInterest);
+
while (!valuesToTrack.isEmpty()) {
- Value v = valuesToTrack.poll();
+ Value alias = valuesToTrack.poll();
+ assert alias != null;
+
// Make sure we are not tracking values over and over again.
- if (!trackedValues.add(v)) {
- continue;
- }
- v.uniquePhiUsers().forEach(valuesToTrack::push);
- for (Instruction user : v.uniqueUsers()) {
- // Users in the same block need one more filtering.
- if (user.getBlock() == block) {
- // When the value of interest has the definition
- if (!valueOfInterest.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(valueOfInterest.definition)) {
- continue;
- }
- }
- }
- if (isDirectlyEscaping(user, code.method, arguments)) {
- builder.add(user);
- continue;
- }
- // Track aliased value.
- if (user.couldIntroduceAnAlias()) {
- Value outValue = user.outValue();
- assert outValue != null;
- valuesToTrack.push(outValue);
- }
- // Track propagated values through which the value of interest can escape indirectly.
- Value propagatedValue = getPropagatedSubject(v, user);
- if (propagatedValue != null && propagatedValue != v) {
- valuesToTrack.push(propagatedValue);
- }
+ assert trackedValues.contains(alias);
+
+ boolean stopped =
+ processValue(valueOfInterest, alias, block, code, arguments, stoppingCriterion);
+ if (stopped) {
+ break;
}
}
- return builder.build();
+
+ trackedValues.clear();
+ valuesToTrack.clear();
}
- private static boolean isDirectlyEscaping(
- Instruction instr, DexEncodedMethod invocationContext, List<Value> arguments) {
+ 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, user, code.method.method)
+ && isDirectlyEscaping(user, code.method.method, arguments)) {
+ if (stoppingCriterion.test(user)) {
+ return true;
+ }
+ }
+ // Track aliased value.
+ if (user.couldIntroduceAnAlias()) {
+ Value outValue = user.outValue();
+ assert outValue != null;
+ addToWorklist(outValue);
+ }
+ // Track propagated values through which the value of interest can escape indirectly.
+ Value propagatedValue = getPropagatedSubject(alias, user);
+ if (propagatedValue != null && propagatedValue != alias) {
+ 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;
@@ -96,7 +182,7 @@
if (instr.isInvokeMethod()) {
DexMethod invokedMethod = instr.asInvokeMethod().getInvokedMethod();
// Filter out the recursion with exactly same arguments.
- if (invokedMethod == invocationContext.method) {
+ if (invokedMethod == context) {
return !instr.inValues().equals(arguments);
}
return true;
@@ -108,6 +194,10 @@
return false;
}
+ protected boolean isValueOfInterestOrAlias(Value value) {
+ return trackedValues.contains(value);
+ }
+
private static Value getPropagatedSubject(Value src, Instruction instr) {
// We may need to bind array index if we want to track array-get precisely:
// array-put arr, idx1, x
@@ -116,12 +206,13 @@
// For now, we don't distinguish such cases, which is conservative.
if (instr.isArrayGet()) {
return instr.asArrayGet().dest();
- } else if (instr.isArrayPut()) {
+ }
+ if (instr.isArrayPut()) {
return instr.asArrayPut().array();
- } else if (instr.isInstancePut()) {
+ }
+ if (instr.isInstancePut()) {
return instr.asInstancePut().object();
}
return null;
}
-
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysisConfiguration.java b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysisConfiguration.java
new file mode 100644
index 0000000..6443dd9
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysisConfiguration.java
@@ -0,0 +1,14 @@
+// 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.analysis.escape;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Instruction;
+
+public interface EscapeAnalysisConfiguration {
+
+ boolean isLegitimateEscapeRoute(AppView<?> appView, Instruction escapeRoute, DexMethod context);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 334bfb2..b2d7276 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -3157,8 +3157,7 @@
Long2ReferenceMap<List<ConstNumber>> constantsByValue = new Long2ReferenceOpenHashMap<>();
// Initialize `constantsByValue`.
- Iterable<Instruction> instructions = code::instructionIterator;
- for (Instruction instruction : instructions) {
+ for (Instruction instruction : code.instructions()) {
if (instruction.isConstNumber()) {
ConstNumber constNumber = instruction.asConstNumber();
if (constNumber.outValue().hasLocalInfo()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 9718df9..069c729 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -11,15 +11,14 @@
import static com.android.tools.r8.ir.optimize.ReflectionOptimizer.computeClassName;
import static com.android.tools.r8.utils.DescriptorUtils.INNER_CLASS_SEPARATOR;
-import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.escape.EscapeAnalysis;
+import com.android.tools.r8.ir.analysis.escape.EscapeAnalysisConfiguration;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
import com.android.tools.r8.ir.code.ConstClass;
@@ -33,8 +32,6 @@
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.optimize.ReflectionOptimizer.ClassNameComputationInfo;
-import com.google.common.annotations.VisibleForTesting;
-import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
@@ -231,10 +228,12 @@
// b/120138731: Filter out local uses, which are likely one-time name computation. In such
// case, the result of this optimization can lead to a regression if the corresponding class
// is in a deep package hierarchy.
- if (!appView.options().testing.forceNameReflectionOptimization
- && !hasPotentialReadOutside(
- appView.appInfo(), code.method, EscapeAnalysis.escape(code, out))) {
- continue;
+ if (!appView.options().testing.forceNameReflectionOptimization) {
+ EscapeAnalysis escapeAnalysis =
+ new EscapeAnalysis(appView, StringOptimizerEscapeAnalysisConfiguration.getInstance());
+ if (escapeAnalysis.isEscaping(code, out)) {
+ continue;
+ }
}
assert invoke.inValues().size() == 1;
@@ -351,36 +350,6 @@
}
}
- @VisibleForTesting
- public static boolean hasPotentialReadOutside(
- AppInfo appInfo, DexEncodedMethod invocationContext, Set<Instruction> escapingInstructions) {
- for (Instruction instr : escapingInstructions) {
- if (instr.isReturn() || instr.isThrow() || instr.isStaticPut()) {
- return true;
- }
- if (instr.isInvokeMethod()) {
- DexMethod invokedMethod = instr.asInvokeMethod().getInvokedMethod();
- DexClass holder = appInfo.definitionFor(invokedMethod.holder);
- // For most cases, library call is not interesting, e.g.,
- // System.out.println(...), String.valueOf(...), etc.
- // If it's too broad, we can introduce black-list.
- if (holder == null || holder.isNotProgramClass()) {
- continue;
- }
- // Heuristic: if the call target has the same method name, it could be still local.
- if (invokedMethod.name == invocationContext.method.name) {
- continue;
- }
- // Add more cases to filter out, if any.
- return true;
- }
- if (instr.isArrayPut()) {
- return instr.asArrayPut().array().isArgument();
- }
- }
- return false;
- }
-
// String#valueOf(null) -> "null"
// String#valueOf(String s) -> s
// str.toString() -> str
@@ -441,4 +410,44 @@
}
}
+ public static class StringOptimizerEscapeAnalysisConfiguration
+ implements EscapeAnalysisConfiguration {
+
+ private static final StringOptimizerEscapeAnalysisConfiguration INSTANCE =
+ new StringOptimizerEscapeAnalysisConfiguration();
+
+ private StringOptimizerEscapeAnalysisConfiguration() {}
+
+ public static StringOptimizerEscapeAnalysisConfiguration getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean isLegitimateEscapeRoute(
+ AppView<?> appView, Instruction escapeRoute, DexMethod context) {
+ if (escapeRoute.isReturn() || escapeRoute.isThrow() || escapeRoute.isStaticPut()) {
+ return false;
+ }
+ if (escapeRoute.isInvokeMethod()) {
+ DexMethod invokedMethod = escapeRoute.asInvokeMethod().getInvokedMethod();
+ DexClass holder = appView.definitionFor(invokedMethod.holder);
+ // For most cases, library call is not interesting, e.g.,
+ // System.out.println(...), String.valueOf(...), etc.
+ // If it's too broad, we can introduce black-list.
+ if (holder == null || holder.isNotProgramClass()) {
+ return true;
+ }
+ // Heuristic: if the call target has the same method name, it could be still local.
+ if (invokedMethod.name == context.name) {
+ return true;
+ }
+ // Add more cases to filter out, if any.
+ return false;
+ }
+ if (escapeRoute.isArrayPut()) {
+ return !escapeRoute.asArrayPut().array().isArgument();
+ }
+ return true;
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/utils/Box.java b/src/main/java/com/android/tools/r8/utils/Box.java
new file mode 100644
index 0000000..fd2b68d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/Box.java
@@ -0,0 +1,18 @@
+// 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.utils;
+
+public class Box<T> {
+
+ private T value;
+
+ public void set(T value) {
+ this.value = value;
+ }
+
+ public boolean isSet() {
+ return value != null;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
index b53d969..b2acadc 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/AnalysisTestBase.java
@@ -9,6 +9,7 @@
import com.android.tools.r8.TestBase;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.utils.AndroidApp;
@@ -26,7 +27,7 @@
private final String className;
private final InternalOptions options = new InternalOptions();
- public AppInfo appInfo;
+ public AppView<?> appView;
public AnalysisTestBase(Class<?> clazz) throws Exception {
this.app = testForD8().release().addProgramClasses(clazz).compile().app;
@@ -45,9 +46,12 @@
@Before
public void setup() throws Exception {
- appInfo =
- new AppInfo(
- new ApplicationReader(app, options, new Timing("AnalysisTestBase.appReader")).read());
+ appView =
+ AppView.createForR8(
+ new AppInfo(
+ new ApplicationReader(app, options, new Timing("AnalysisTestBase.appReader"))
+ .read()),
+ options);
}
public void buildAndCheckIR(String methodName, Consumer<IRCode> irInspector) throws Exception {
@@ -60,8 +64,7 @@
public static <T extends Instruction> T getMatchingInstruction(
IRCode code, Predicate<Instruction> predicate) {
Instruction result = null;
- Iterable<Instruction> instructions = code::instructionIterator;
- for (Instruction instruction : instructions) {
+ for (Instruction instruction : code.instructions()) {
if (predicate.test(instruction)) {
if (result != null) {
fail();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysisForNameReflectionTest.java b/src/test/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysisForNameReflectionTest.java
index 3f4353a..de19967 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysisForNameReflectionTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/escape/EscapeAnalysisForNameReflectionTest.java
@@ -3,12 +3,12 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.analysis.escape;
-import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.AnalysisTestBase;
import com.android.tools.r8.ir.code.IRCode;
@@ -28,145 +28,141 @@
public class EscapeAnalysisForNameReflectionTest extends AnalysisTestBase {
public EscapeAnalysisForNameReflectionTest() throws Exception {
- super(TestClass.class.getTypeName(),
- TestClass.class, Helper.class, NamingInterface.class);
+ super(TestClass.class.getTypeName(), TestClass.class, Helper.class, NamingInterface.class);
+ }
+
+ private static Predicate<Instruction> invokesMethodWithName(String name) {
+ return instruction ->
+ instruction.isInvokeMethod()
+ && instruction.asInvokeMethod().getInvokedMethod().name.toString().equals(name);
}
@Test
public void testEscapeViaReturn() throws Exception {
- buildAndCheckIR("escapeViaReturn", checkEscapingName(appInfo, true, instr -> {
- return instr.isReturn();
- }));
+ buildAndCheckIR("escapeViaReturn", checkEscapingName(true, Instruction::isReturn));
}
@Test
public void testEscapeViaThrow() throws Exception {
- buildAndCheckIR("escapeViaThrow", code -> {
- NewInstance e = getMatchingInstruction(code, Instruction::isNewInstance);
- assertNotNull(e);
- Value v = e.outValue();
- assertNotNull(v);
- Set<Instruction> escapingInstructions = EscapeAnalysis.escape(code, v);
- assertTrue(
- StringOptimizer.hasPotentialReadOutside(appInfo, code.method, escapingInstructions));
- assertTrue(escapingInstructions.stream().allMatch(instr -> {
- return instr.isThrow()
- || (instr.isInvokeDirect()
- && instr.asInvokeDirect().getInvokedMethod().name.toString().equals("<init>"));
- }));
- });
+ buildAndCheckIR(
+ "escapeViaThrow",
+ code -> {
+ NewInstance e = getMatchingInstruction(code, Instruction::isNewInstance);
+ assertNotNull(e);
+ Value v = e.outValue();
+ assertNotNull(v);
+ EscapeAnalysis escapeAnalysis =
+ new EscapeAnalysis(
+ appView,
+ StringOptimizer.StringOptimizerEscapeAnalysisConfiguration.getInstance());
+ Set<Instruction> escapeRoutes = escapeAnalysis.computeEscapeRoutes(code, v);
+ assertEquals(1, escapeRoutes.size());
+ assertTrue(
+ escapeRoutes.stream()
+ .allMatch(
+ instr ->
+ instr.isThrow()
+ || (instr.isInvokeDirect()
+ && instr
+ .asInvokeDirect()
+ .getInvokedMethod()
+ .name
+ .toString()
+ .equals("<init>"))));
+ });
}
@Test
public void testEscapeViaStaticPut() throws Exception {
- buildAndCheckIR("escapeViaStaticPut", checkEscapingName(appInfo, true, instr -> {
- return instr.isStaticPut();
- }));
+ buildAndCheckIR("escapeViaStaticPut", checkEscapingName(true, Instruction::isStaticPut));
}
@Test
public void testEscapeViaInstancePut() throws Exception {
- buildAndCheckIR("escapeViaInstancePut", checkEscapingName(appInfo, true, instr -> {
- return instr.isInvokeMethod()
- && instr.asInvokeMethod().getInvokedMethod().name.toString()
- .equals("namingInterfaceConsumer");
- }));
+ buildAndCheckIR(
+ "escapeViaInstancePut",
+ checkEscapingName(true, invokesMethodWithName("namingInterfaceConsumer")));
}
@Test
public void testEscapeViaArrayPut() throws Exception {
- buildAndCheckIR("escapeViaArrayPut", checkEscapingName(appInfo, true, instr -> {
- return instr.isInvokeMethod()
- && instr.asInvokeMethod().getInvokedMethod().name.toString()
- .equals("namingInterfacesConsumer");
- }));
+ buildAndCheckIR(
+ "escapeViaArrayPut",
+ checkEscapingName(true, invokesMethodWithName("namingInterfacesConsumer")));
}
@Test
public void testEscapeViaArrayArgumentPut() throws Exception {
- buildAndCheckIR("escapeViaArrayArgumentPut", checkEscapingName(appInfo, true, instr -> {
- return instr.isArrayPut();
- }));
+ buildAndCheckIR("escapeViaArrayArgumentPut", checkEscapingName(true, Instruction::isArrayPut));
}
@Test
public void testEscapeViaListPut() throws Exception {
- buildAndCheckIR("escapeViaListPut", checkEscapingName(appInfo, false, instr -> {
- return instr.isInvokeMethod()
- && instr.asInvokeMethod().getInvokedMethod().name.toString().equals("add");
- }));
+ buildAndCheckIR(
+ "escapeViaListPut",
+ checkEscapingName(
+ false,
+ instr -> {
+ throw new Unreachable();
+ }));
}
@Test
public void testEscapeViaListArgumentPut() throws Exception {
- buildAndCheckIR("escapeViaListArgumentPut", checkEscapingName(appInfo, false, instr -> {
- return instr.isInvokeMethod()
- && instr.asInvokeMethod().getInvokedMethod().name.toString().equals("add");
- }));
+ buildAndCheckIR(
+ "escapeViaListArgumentPut",
+ checkEscapingName(
+ false,
+ instr -> {
+ throw new Unreachable();
+ }));
}
@Test
public void testEscapeViaArrayGet() throws Exception {
- buildAndCheckIR("escapeViaArrayGet", checkEscapingName(appInfo, true, instr -> {
- return instr.isInvokeMethod()
- && instr.asInvokeMethod().getInvokedMethod().name.toString()
- .equals("namingInterfaceConsumer");
- }));
+ buildAndCheckIR(
+ "escapeViaArrayGet",
+ checkEscapingName(true, invokesMethodWithName("namingInterfaceConsumer")));
}
@Test
public void testHandlePhiAndAlias() throws Exception {
- buildAndCheckIR("handlePhiAndAlias", checkEscapingName(appInfo, true, instr -> {
- return instr.isInvokeMethod()
- && instr.asInvokeMethod().getInvokedMethod().name.toString().equals("stringConsumer");
- }));
+ buildAndCheckIR(
+ "handlePhiAndAlias", checkEscapingName(true, invokesMethodWithName("stringConsumer")));
}
@Test
public void testToString() throws Exception {
- buildAndCheckIR("toString", checkEscapingName(appInfo, false, instr -> {
- return instr.isInvokeMethod()
- && instr.asInvokeMethod().getInvokedMethod().name.toString().equals("toString");
- }));
+ buildAndCheckIR("toString", checkEscapingName(true, Instruction::isReturn));
}
@Test
public void testEscapeViaRecursion() throws Exception {
- buildAndCheckIR("escapeViaRecursion", checkEscapingName(appInfo, false, instr -> {
- return instr.isInvokeStatic();
- }));
+ buildAndCheckIR("escapeViaRecursion", checkEscapingName(true, Instruction::isReturn));
}
@Test
public void testEscapeViaLoopAndBoxing() throws Exception {
- buildAndCheckIR("escapeViaLoopAndBoxing", checkEscapingName(appInfo, true, instr -> {
- return instr.isReturn()
- || (instr.isInvokeMethod()
- && instr.asInvokeMethod().getInvokedMethod().name.toString().equals("toString"));
- }));
+ buildAndCheckIR(
+ "escapeViaLoopAndBoxing",
+ checkEscapingName(
+ true, instr -> instr.isReturn() || invokesMethodWithName("toString").test(instr)));
}
- private static Consumer<IRCode> checkEscapingName(
- AppInfo appInfo,
- boolean expectedHeuristicResult,
- Predicate<Instruction> instructionTester) {
+ private Consumer<IRCode> checkEscapingName(
+ boolean expectedHeuristicResult, Predicate<Instruction> instructionTester) {
+ assert instructionTester != null;
return code -> {
InvokeVirtual simpleNameCall = getSimpleNameCall(code);
assertNotNull(simpleNameCall);
Value v = simpleNameCall.outValue();
assertNotNull(v);
- Set<Instruction> escapingInstructions = EscapeAnalysis.escape(code, v);
- assertEquals(
- expectedHeuristicResult,
- StringOptimizer.hasPotentialReadOutside(appInfo, code.method, escapingInstructions));
- if (instructionTester == null) {
- // Implicitly expecting the absence of escaping points.
- assertTrue(escapingInstructions.isEmpty());
- } else {
- // Otherwise, test all escaping instructions.
- assertFalse(escapingInstructions.isEmpty());
- assertTrue(escapingInstructions.stream().allMatch(instructionTester));
- }
+ EscapeAnalysis escapeAnalysis =
+ new EscapeAnalysis(
+ appView, StringOptimizer.StringOptimizerEscapeAnalysisConfiguration.getInstance());
+ Set<Instruction> escapeRoutes = escapeAnalysis.computeEscapeRoutes(code, v);
+ assertNotEquals(expectedHeuristicResult, escapeRoutes.isEmpty());
+ assertTrue(escapeRoutes.stream().allMatch(instructionTester));
};
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
index 8b28c5d..2d50834 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ArrayTypeTest.java
@@ -9,7 +9,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.ir.analysis.AnalysisTestBase;
import com.android.tools.r8.ir.code.ArrayGet;
import com.android.tools.r8.ir.code.ArrayPut;
@@ -47,8 +46,7 @@
private static Consumer<IRCode> arrayTestInspector() {
return code -> {
- Iterable<Instruction> instructions = code::instructionIterator;
- for (Instruction instruction : instructions) {
+ for (Instruction instruction : code.instructions()) {
if (instruction.isArrayGet() || instruction.isArrayPut()) {
Value array, value;
if (instruction.isArrayGet()) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
index 798f1b0..06ea239 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/ConstrainedPrimitiveTypeTest.java
@@ -84,8 +84,7 @@
private static Consumer<IRCode> testInspector(
TypeLatticeElement expectedType, int expectedNumberOfConstNumberInstructions) {
return code -> {
- Iterable<Instruction> instructions = code::instructionIterator;
- for (Instruction instruction : instructions) {
+ for (Instruction instruction : code.instructions()) {
if (instruction.isConstNumber()) {
ConstNumber constNumberInstruction = instruction.asConstNumber();
assertEquals(expectedType, constNumberInstruction.outValue().getTypeLattice());
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
index 12c1329..ffa586b 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/UnconstrainedPrimitiveTypeTest.java
@@ -104,8 +104,7 @@
private static Consumer<IRCode> testInspector(
TypeLatticeElement expectedType, int expectedNumberOfConstNumberInstructions) {
return code -> {
- Iterable<Instruction> instructions = code::instructionIterator;
- for (Instruction instruction : instructions) {
+ for (Instruction instruction : code.instructions()) {
if (instruction.isConstNumber()) {
ConstNumber constNumberInstruction = instruction.asConstNumber();
assertEquals(expectedType, constNumberInstruction.outValue().getTypeLattice());