Allow clinits that create arrays of instances to be postponed
Bug: 142762129
Change-Id: Ifbfa2654f7e4242325ff6486f013501e25ba8432
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index d654c87..96dada1 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -29,6 +29,8 @@
private final IRCode code;
private final DexType context;
+ private final Set<Value> knownNotToDependOnEnvironment = Sets.newIdentityHashSet();
+
public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
this.appView = appView;
this.code = code;
@@ -36,20 +38,57 @@
}
public boolean valueMayDependOnEnvironment(Value value) {
+ return valueMayDependOnEnvironment(value, Sets.newIdentityHashSet());
+ }
+
+ private boolean valueMayDependOnEnvironment(
+ Value value, Set<Value> assumedNotToDependOnEnvironment) {
Value root = value.getAliasedValue();
+ if (assumedNotToDependOnEnvironment.contains(root)) {
+ return false;
+ }
+ if (knownNotToDependOnEnvironment.contains(root)) {
+ return false;
+ }
if (root.isConstant()) {
return false;
}
- if (isConstantArrayThroughoutMethod(root)) {
+ if (isConstantArrayThroughoutMethod(root, assumedNotToDependOnEnvironment)) {
return false;
}
- if (isNewInstanceWithoutEnvironmentDependentFields(root)) {
+ if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
return false;
}
return true;
}
+ private boolean valueMayNotDependOnEnvironmentAssumingArrayDoesNotDependOnEnvironment(
+ Value value, Value array, Set<Value> assumedNotToDependOnEnvironment) {
+ assert !value.hasAliasedValue();
+ assert !array.hasAliasedValue();
+
+ if (assumedNotToDependOnEnvironment.add(array)) {
+ boolean valueMayDependOnEnvironment =
+ valueMayDependOnEnvironment(value, assumedNotToDependOnEnvironment);
+ boolean changed = assumedNotToDependOnEnvironment.remove(array);
+ assert changed;
+ return !valueMayDependOnEnvironment;
+ }
+ return !valueMayDependOnEnvironment(value, assumedNotToDependOnEnvironment);
+ }
+
+ /**
+ * Used to identify if an array is "constant" in the sense that none of the values written into
+ * the array may depend on the environment.
+ *
+ * <p>Examples include {@code new int[] {1,2,3}} and {@code new Object[]{new Object()}}.
+ */
public boolean isConstantArrayThroughoutMethod(Value value) {
+ return isConstantArrayThroughoutMethod(value, Sets.newIdentityHashSet());
+ }
+
+ private boolean isConstantArrayThroughoutMethod(
+ Value value, Set<Value> assumedNotToDependOnEnvironment) {
Value root = value.getAliasedValue();
if (root.isPhi()) {
// Would need to track the aliases, just give up.
@@ -96,6 +135,7 @@
// Allow array-put and new-array-filled-data instructions that immediately follow the array
// creation.
+ Set<Value> arrayValues = Sets.newIdentityHashSet();
Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
for (Instruction instruction : definition.getBlock().instructionsAfter(definition)) {
@@ -118,10 +158,19 @@
return false;
}
- if (!arrayPut.value().isConstant()) {
+ // Check if the value being written into the array may depend on the environment.
+ //
+ // When analyzing if the value may depend on the environment, we assume that the current
+ // array does not depend on the environment. Otherwise, we would classify the value as
+ // possibly depending on the environment since it could escape via the array and then
+ // be mutated indirectly.
+ Value rhs = arrayPut.value().getAliasedValue();
+ if (!valueMayNotDependOnEnvironmentAssumingArrayDoesNotDependOnEnvironment(
+ rhs, root, assumedNotToDependOnEnvironment)) {
return false;
}
+ arrayValues.add(rhs);
consumedInstructions.add(arrayPut);
continue;
}
@@ -149,10 +198,21 @@
// Currently, we only allow the array to flow into static-put instructions that are not
// followed by an instruction that may have side effects. Instructions that do not have any
// side effects are ignored because they cannot mutate the array.
- return !valueMayBeMutatedBeforeMethodExit(root, consumedInstructions);
+ if (valueMayBeMutatedBeforeMethodExit(
+ root, assumedNotToDependOnEnvironment, consumedInstructions)) {
+ return false;
+ }
+
+ if (assumedNotToDependOnEnvironment.isEmpty()) {
+ knownNotToDependOnEnvironment.add(root);
+ knownNotToDependOnEnvironment.addAll(arrayValues);
+ }
+
+ return true;
}
- private boolean isNewInstanceWithoutEnvironmentDependentFields(Value value) {
+ private boolean isNewInstanceWithoutEnvironmentDependentFields(
+ Value value, Set<Value> assumedNotToDependOnEnvironment) {
assert !value.hasAliasedValue();
if (value.isPhi() || !value.definition.isNewInstance()) {
@@ -203,16 +263,26 @@
// Check that none of the arguments to the constructor depend on the environment.
for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
Value argument = constructorInvoke.arguments().get(i);
- if (valueMayDependOnEnvironment(argument)) {
+ if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
return false;
}
}
// Finally, check that the object does not escape.
- return !valueMayBeMutatedBeforeMethodExit(value, ImmutableSet.of(constructorInvoke));
+ if (valueMayBeMutatedBeforeMethodExit(
+ value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
+ return false;
+ }
+
+ if (assumedNotToDependOnEnvironment.isEmpty()) {
+ knownNotToDependOnEnvironment.add(value);
+ }
+
+ return true;
}
- private boolean valueMayBeMutatedBeforeMethodExit(Value value, Set<Instruction> whitelist) {
+ private boolean valueMayBeMutatedBeforeMethodExit(
+ Value value, Set<Value> assumedNotToDependOnEnvironment, Set<Instruction> whitelist) {
assert !value.hasAliasedValue();
if (value.numberOfPhiUsers() > 0) {
@@ -226,6 +296,14 @@
continue;
}
+ if (user.isArrayPut()) {
+ ArrayPut arrayPut = user.asArrayPut();
+ if (value == arrayPut.value()
+ && !valueMayDependOnEnvironment(arrayPut.array(), assumedNotToDependOnEnvironment)) {
+ continue;
+ }
+ }
+
if (user.isStaticPut()) {
StaticPut staticPut = user.asStaticPut();
if (visited.contains(staticPut)) {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayOfObjectsCreationCanBePostponedTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayOfObjectsCreationCanBePostponedTest.java
new file mode 100644
index 0000000..065e069
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayOfObjectsCreationCanBePostponedTest.java
@@ -0,0 +1,81 @@
+// 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.sideeffect;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArrayOfObjectsCreationCanBePostponedTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ArrayOfObjectsCreationCanBePostponedTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(ArrayOfObjectsCreationCanBePostponedTest.class)
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("Hello world!");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject classSubject = inspector.clazz(A.class);
+ assertThat(classSubject, isPresent());
+
+ // We should have been able to inline A.inlineable() since A.<clinit>() can safely be postponed.
+ assertThat(classSubject.uniqueMethodWithName("inlineable"), not(isPresent()));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ A.inlineable();
+ System.out.println(A.values[0].getMessage());
+ }
+ }
+
+ static class A {
+
+ static A[] values = new A[] {new A(" world!")};
+
+ String message;
+
+ A(String message) {
+ this.message = message;
+ }
+
+ static void inlineable() {
+ System.out.print("Hello");
+ }
+
+ String getMessage() {
+ return message;
+ }
+ }
+}