Limit the aliases for escape analysis based on root types.
Bug: 134745277
Change-Id: I9d775f536ba3a800b06fb4685013da9e72a7155c
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 4b2cb70..f9dcc57 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -753,4 +753,8 @@
}
return lubType;
}
+
+ public boolean inDifferentHierarchy(DexType type1, DexType type2) {
+ return !isSubtype(type1, type2) && !isSubtype(type2, type1);
+ }
}
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 b77c1f9..466edeb 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
@@ -144,14 +144,22 @@
}
}
// Track aliased value.
- if (user.couldIntroduceAnAlias()) {
+ boolean couldIntroduceTrackedValueAlias = false;
+ for (Value trackedValue : trackedValues) {
+ if (user.couldIntroduceAnAlias(appView, trackedValue)) {
+ couldIntroduceTrackedValueAlias = true;
+ break;
+ }
+ }
+ if (couldIntroduceTrackedValueAlias) {
Value outValue = user.outValue();
- assert outValue != null;
+ assert outValue != null && outValue.getTypeLattice().isReference();
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.getTypeLattice().isReference();
addToWorklist(propagatedValue);
}
}
@@ -187,9 +195,15 @@
}
return true;
}
- // Storing to the argument array.
+ // Storing to non-local array.
if (instr.isArrayPut()) {
- return instr.asArrayPut().array().isArgument();
+ 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;
}
@@ -199,14 +213,6 @@
}
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
- // y <- array-get arr, idx2 // y is not what we want to track.
- // z <- array-get arr, idx1 // but, z is.
- // For now, we don't distinguish such cases, which is conservative.
- if (instr.isArrayGet()) {
- return instr.asArrayGet().dest();
- }
if (instr.isArrayPut()) {
return instr.asArrayPut().array();
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 045acf6..98aa687 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -62,6 +62,13 @@
}
@Override
+ public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
+ assert root != null && root.getTypeLattice().isReference();
+ assert outValue != null;
+ return outValue.getTypeLattice().isReference();
+ }
+
+ @Override
public void buildDex(DexBuilder builder) {
int dest = builder.allocatedRegister(dest(), getNumber());
int array = builder.allocatedRegister(array(), getNumber());
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java
index 5593a7d..823e3d0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Assume.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -29,6 +29,7 @@
super(dest, src);
assert assumption != null;
assert assumption.verifyCorrectnessOfValues(dest, src, appView);
+ assert dest != null;
this.assumption = assumption;
this.origin = origin;
}
@@ -120,13 +121,31 @@
}
@Override
- public boolean isIntroducingAnAlias() {
- return true;
+ public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
+ assert root != null && root.getTypeLattice().isReference();
+ assert outValue != null;
+ TypeLatticeElement outType = outValue.getTypeLattice();
+ if (outType.isPrimitive()) {
+ return false;
+ }
+ if (assumption.isAssumeDynamicType()) {
+ outType = asAssumeDynamicType().assumption.getType();
+ }
+ if (appView.appInfo().hasSubtyping()) {
+ if (outType.isClassType()
+ && root.getTypeLattice().isClassType()
+ && appView.appInfo().withSubtyping().inDifferentHierarchy(
+ outType.asClassTypeLatticeElement().getClassType(),
+ root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+ return false;
+ }
+ }
+ return outType.isReference();
}
@Override
- public boolean couldIntroduceAnAlias() {
- return outValue != null && outValue.getTypeLattice().isReference();
+ public boolean isIntroducingAnAlias() {
+ return true;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 8859225..da70d9f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -63,8 +63,10 @@
}
@Override
- public boolean couldIntroduceAnAlias() {
- return outValue != null && outValue.getTypeLattice().isReference();
+ public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
+ assert root != null && root.getTypeLattice().isReference();
+ assert outValue != null;
+ return outValue.getTypeLattice().isReference();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index d8e7f10..7fc61aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -1211,7 +1211,7 @@
* <p>This is a conservative version of {@link #isIntroducingAnAlias()} so that other analyses,
* e.g., escape analysis, can propagate or track aliased values in a conservative manner.
*/
- public boolean couldIntroduceAnAlias() {
+ public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
return false;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 44864f6..ec77df1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -182,8 +182,25 @@
}
@Override
- public boolean couldIntroduceAnAlias() {
- return outValue != null && outValue.getTypeLattice().isReference();
+ public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
+ assert root != null && root.getTypeLattice().isReference();
+ if (outValue == null) {
+ return false;
+ }
+ TypeLatticeElement outType = outValue.getTypeLattice();
+ if (outType.isPrimitive()) {
+ return false;
+ }
+ if (appView.appInfo().hasSubtyping()) {
+ if (outType.isClassType()
+ && root.getTypeLattice().isClassType()
+ && appView.appInfo().withSubtyping().inDifferentHierarchy(
+ outType.asClassTypeLatticeElement().getClassType(),
+ root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+ return false;
+ }
+ }
+ return outType.isReference();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 821b67d..9eeaf3e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -37,8 +37,8 @@
}
@Override
- public boolean couldIntroduceAnAlias() {
- return outValue != null && outValue.getTypeLattice().isReference();
+ public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
+ throw new Unreachable("as long as we're analyzing SSA IR.");
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index b4cc421..0f7b49e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -51,8 +51,23 @@
}
@Override
- public boolean couldIntroduceAnAlias() {
- return outValue != null && outValue.getTypeLattice().isReference();
+ public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) {
+ assert root != null && root.getTypeLattice().isReference();
+ assert outValue != null;
+ TypeLatticeElement outType = outValue.getTypeLattice();
+ if (outType.isPrimitive()) {
+ return false;
+ }
+ if (appView.appInfo().hasSubtyping()) {
+ if (outType.isClassType()
+ && root.getTypeLattice().isClassType()
+ && appView.appInfo().withSubtyping().inDifferentHierarchy(
+ outType.asClassTypeLatticeElement().getClassType(),
+ root.getTypeLattice().asClassTypeLatticeElement().getClassType())) {
+ return false;
+ }
+ }
+ return outType.isReference();
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index 1ea8b3e..f421fc1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -719,17 +719,6 @@
builderType = builder.getTypeLattice().asClassTypeLatticeElement().getClassType();
}
- // Use of Builder#toString is legitimate.
- // TODO(b/134745277): but, the escape analysis can be filtered this out by types.
- private boolean isUsingToStringAlias(EscapeAnalysis analysis, Value alias) {
- if (!alias.isPhi() && alias.definition.isInvokeMethod()) {
- DexMethod invokedMethod = alias.definition.asInvokeMethod().getInvokedMethod();
- return optimizationConfiguration.isToStringMethod(invokedMethod)
- && alias.definition.inValues().stream().anyMatch(analysis::isValueOfInterestOrAlias);
- }
- return false;
- }
-
private void logEscapingRoute(boolean legitimate) {
if (!legitimate) {
numberOfBuildersThatEscape++;
@@ -742,51 +731,11 @@
EscapeAnalysis escapeAnalysis,
Instruction escapeRoute,
DexMethod context) {
- boolean legitimate;
- if (escapeRoute.isReturn()) {
- legitimate = isUsingToStringAlias(escapeAnalysis, escapeRoute.asReturn().returnValue());
- logEscapingRoute(legitimate);
- return legitimate;
+ if (escapeRoute.isReturn() || escapeRoute.isThrow() || escapeRoute.isStaticPut()) {
+ logEscapingRoute(false);
+ return false;
}
- if (escapeRoute.isThrow()) {
- legitimate = isUsingToStringAlias(escapeAnalysis, escapeRoute.asThrow().exception());
- logEscapingRoute(legitimate);
- return legitimate;
- }
- if (escapeRoute.isStaticPut()) {
- legitimate = isUsingToStringAlias(escapeAnalysis, escapeRoute.asStaticPut().value());
- logEscapingRoute(legitimate);
- return legitimate;
- }
- if (escapeRoute.isArrayPut()) {
- if (escapeRoute.asArrayPut().array().isArgument()) {
- legitimate = isUsingToStringAlias(escapeAnalysis, escapeRoute.asArrayPut().value());
- logEscapingRoute(legitimate);
- return legitimate;
- }
- // Putting the builder (or aliases) into a local array is legitimate.
- // If that local array is used again with array-get, the escape analysis will pick up the
- // out-value of that instruction and keep tracing the value uses anyway.
- return true;
- }
-
if (escapeRoute.isInvokeMethod()) {
- boolean useBuilder = false;
- boolean useToStringAlias = false;
- for (Value arg : escapeRoute.asInvokeMethod().inValues()) {
- // Direct use of the builder should be caught and examined later.
- if (arg == builder) {
- useBuilder = true;
- break;
- }
- useToStringAlias |= isUsingToStringAlias(escapeAnalysis, arg);
- }
- // It's legitimate if a call doesn't use the builder directly, but use aliased values of
- // Builder#toString.
- if (!useBuilder && useToStringAlias) {
- return true;
- }
-
// Program class may call String#intern(). Only allow library calls.
// TODO(b/114002137): For now, we allow only library calls to avoid a case like
// identity(Builder.toString()).intern(); but it's too restrictive.
@@ -801,7 +750,6 @@
DexMethod invokedMethod = invoke.getInvokedMethod();
// Make sure builder's uses are local, i.e., not escaping from the current method.
if (invokedMethod.holder != builderType) {
- numberOfBuildersThatEscape++;
logEscapingRoute(false);
return false;
}
@@ -820,7 +768,6 @@
&& outUser.asInvokeMethodWithReceiver().getInvokedMethod()
== factory.stringMethods.intern) {
numberOfBuildersWhoseResultIsInterned++;
- logEscapingRoute(false);
return false;
}
}
@@ -833,7 +780,6 @@
// Seeing any of them indicates that this code is not trivial.
if (!optimizationConfiguration.isAppendMethod(invokedMethod)) {
numberOfBuildersWithNonTrivialStateChange++;
- logEscapingRoute(false);
return false;
}
if (!optimizationConfiguration.isSupportedAppendMethod(invoke)) {
@@ -843,7 +789,18 @@
// Reaching here means that this invocation is part of trivial patterns we're looking for.
return true;
}
-
+ if (escapeRoute.isArrayPut()) {
+ Value array = escapeRoute.asArrayPut().array().getAliasedValue();
+ boolean legitimate = !array.isPhi() && array.definition.isCreatingArray();
+ logEscapingRoute(legitimate);
+ return legitimate;
+ }
+ if (escapeRoute.isInstancePut()) {
+ Value instance = escapeRoute.asInstancePut().object().getAliasedValue();
+ boolean legitimate = !instance.isPhi() && instance.definition.isNewInstance();
+ logEscapingRoute(legitimate);
+ return legitimate;
+ }
// All other cases are not legitimate.
logEscapingRoute(false);
return false;
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 549eb70..de33a67 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
@@ -409,7 +409,7 @@
}
}
- public static class StringOptimizerEscapeAnalysisConfiguration
+ static class StringOptimizerEscapeAnalysisConfiguration
implements EscapeAnalysisConfiguration {
private static final StringOptimizerEscapeAnalysisConfiguration INSTANCE =
@@ -447,7 +447,12 @@
return false;
}
if (escapeRoute.isArrayPut()) {
- return !escapeRoute.asArrayPut().array().isArgument();
+ Value array = escapeRoute.asArrayPut().array().getAliasedValue();
+ return !array.isPhi() && array.definition.isCreatingArray();
+ }
+ if (escapeRoute.isInstancePut()) {
+ Value instance = escapeRoute.asInstancePut().object().getAliasedValue();
+ return !instance.isPhi() && instance.definition.isNewInstance();
}
// All other cases are not legitimate.
return false;
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 1fde43a..c337379 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
@@ -10,7 +10,6 @@
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.ir.analysis.AnalysisTestBase;
@@ -19,7 +18,6 @@
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.optimize.string.StringOptimizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -75,7 +73,7 @@
instr ->
instr.isThrow()
|| (instr.isInvokeDirect()
- && invokesMethodWithName("<init>").test(instr))));
+ && invokesMethodWithName("<init>").test(instr))));
});
}
@@ -85,22 +83,35 @@
}
@Test
- public void testEscapeViaInstancePut() throws Exception {
+ public void testEscapeViaInstancePut_local() throws Exception {
buildAndCheckIR(
- "escapeViaInstancePut",
+ "escapeViaInstancePut_local",
checkEscapingName(true, invokesMethodWithName("namingInterfaceConsumer")));
}
@Test
- public void testEscapeViaArrayPut() throws Exception {
+ public void testEscapeViaArrayPut_local() throws Exception {
buildAndCheckIR(
- "escapeViaArrayPut",
+ "escapeViaArrayPut_local",
checkEscapingName(true, invokesMethodWithName("namingInterfacesConsumer")));
}
@Test
- public void testEscapeViaArrayArgumentPut() throws Exception {
- buildAndCheckIR("escapeViaArrayArgumentPut", checkEscapingName(true, Instruction::isArrayPut));
+ public void testEscapeViaArrayPut_heap() throws Exception {
+ buildAndCheckIR(
+ "escapeViaArrayPut_heap",
+ checkEscapingName(
+ true,
+ i -> i.isArrayPut() || invokesMethodWithName("namingInterfacesConsumer").test(i)));
+ }
+
+ @Test
+ public void testEscapeViaArrayPut_argument() throws Exception {
+ buildAndCheckIR(
+ "escapeViaArrayPut_argument",
+ checkEscapingName(
+ true,
+ i -> i.isArrayPut() || invokesMethodWithName("namingInterfacesConsumer").test(i)));
}
@Test
@@ -179,7 +190,12 @@
return false;
}
if (escapeRoute.isArrayPut()) {
- return !escapeRoute.asArrayPut().array().isArgument();
+ Value array = escapeRoute.asArrayPut().array().getAliasedValue();
+ return !array.isPhi() && array.definition.isCreatingArray();
+ }
+ if (escapeRoute.isInstancePut()) {
+ Value instance = escapeRoute.asInstancePut().object().getAliasedValue();
+ return !instance.isPhi() && instance.definition.isNewInstance();
}
// All other cases are not legitimate.
return false;
@@ -241,6 +257,8 @@
}
static class TestClass implements NamingInterface {
+ private static NamingInterface[] ARRAY = new NamingInterface[1];
+
static String tag;
String id;
@@ -280,13 +298,13 @@
tag = TestClass.class.getSimpleName();
}
- public static void escapeViaInstancePut() {
+ public static void escapeViaInstancePut_local() {
TestClass instance = new TestClass();
instance.id = instance.getClass().getSimpleName();
Helper.namingInterfaceConsumer(instance);
}
- public static void escapeViaArrayPut() {
+ public static void escapeViaArrayPut_local() {
TestClass instance = new TestClass();
instance.id = instance.getClass().getSimpleName();
NamingInterface[] array = new NamingInterface[1];
@@ -294,7 +312,14 @@
Helper.namingInterfacesConsumer(array);
}
- public static void escapeViaArrayArgumentPut(NamingInterface[] array) {
+ public static void escapeViaArrayPut_heap() {
+ TestClass instance = new TestClass();
+ instance.id = instance.getClass().getSimpleName();
+ ARRAY[0] = instance;
+ Helper.namingInterfacesConsumer(ARRAY);
+ }
+
+ public static void escapeViaArrayPut_argument(NamingInterface[] array) {
TestClass instance = new TestClass();
instance.id = instance.getClass().getSimpleName();
array[0] = instance;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
index 856dd81..fc87c72 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -154,7 +154,9 @@
.addOptionsModification(this::configure)
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 1, 4, 3);
+ // TODO(b/114002137): The lack of subtyping made the escape analysis to regard
+ // StringBuilder#toString as an alias-introducing instruction.
+ test(result, 3, 4, 3);
}
@Test