Revert "Better side effect analysis for StringConcat optimizations"
This reverts commit 1ed138a3e738b79b3e904a13eb110d8a1d1c452b.
Reason for revert: Possibly blocking R8 roll to studio-main due to test failure: //tools/base/build-system/integration-test/connected:MinifyInstrumentLibConnectedTest
Original change's description:
> Better side effect analysis for StringConcat optimizations
>
> Chrome size with enableStringConcatInstruction:
> before: 21875128
> after: 21875080
>
> Bug: 467374229
> Change-Id: Ib17972b385c8d0507ef069d7a9bbc0852286c144
Bug: 467374229
Change-Id: I469ffcfed1aee58b8207e3b52168f86f28e8a005
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 9ac375a..73930b4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1576,62 +1576,6 @@
}
/**
- * Visits all instructions between from & to (non-inclusive) and returns whether the predicate
- * returned true for any of them.
- *
- * <p>This assumes that {@code from} dominates {@code to}.
- */
- public boolean anyInstructionBetween(
- Instruction from, Instruction to, Predicate<Instruction> predicate) {
- BasicBlock fromBlock = from.getBlock();
- BasicBlock toBlock = to.getBlock();
- if (fromBlock == toBlock) {
- assert from.comesBefore(to);
- for (Instruction cur = from.getNext(); cur != to; cur = cur.getNext()) {
- if (predicate.test(cur)) {
- return true;
- }
- }
- return false;
- }
-
- for (Instruction cur = from.getNext(); cur != null; cur = cur.getNext()) {
- if (predicate.test(cur)) {
- return true;
- }
- }
-
- for (Instruction cur = to.getPrev(); cur != null; cur = cur.getPrev()) {
- if (predicate.test(cur)) {
- return true;
- }
- }
-
- int color = reserveMarkingColor();
- try {
- fromBlock.mark(color);
- toBlock.mark(color);
- Deque<BasicBlock> worklist = new ArrayDeque<>(toBlock.getPredecessors());
-
- while (!worklist.isEmpty()) {
- BasicBlock block = worklist.poll();
- if (block.isMarked(color)) {
- continue;
- }
- for (Instruction unused : block.getInstructions(predicate)) {
- return true;
- }
- block.mark(color);
- worklist.addAll(block.getPredecessors());
- }
- } finally {
- returnMarkingColor(color);
- }
-
- return false;
- }
-
- /**
* Returns the set of blocks that are reachable from the given source. The source itself is only
* included if there is a path from the given block to itself.
*/
diff --git a/src/main/java/com/android/tools/r8/ir/code/StringConcat.java b/src/main/java/com/android/tools/r8/ir/code/StringConcat.java
index 93d8732..60e7a82 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StringConcat.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StringConcat.java
@@ -17,7 +17,6 @@
import com.android.tools.r8.ir.optimize.Inliner;
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
-import com.android.tools.r8.ir.optimize.library.sideeffects.JavaLangObjectsSideEffectCollection;
import com.android.tools.r8.lightir.LirBuilder;
import com.android.tools.r8.utils.internal.exceptions.Unreachable;
import java.util.Arrays;
@@ -38,8 +37,6 @@
// It's invalid for two constants to be adjacent (they should have been merged in this case).
private List<DexString> argConstants;
- private Boolean cachedMightCallToStringWithSideEffects;
-
private StringConcat(
Value outValue, List<Value> inValues, DexType[] argTypes, List<DexString> argConstants) {
super(outValue, inValues);
@@ -119,7 +116,6 @@
inValues.addAll(newInValues);
argTypes = newArgTypes;
argConstants = newArgConstants;
- cachedMightCallToStringWithSideEffects = null;
assertValidState();
}
@@ -186,19 +182,22 @@
return true;
}
+ public static boolean toStringMayHaveSideEffects(
+ DexItemFactory dexItemFactory, TypeElement type) {
+ // TODO(467374229): Inspect the actual toString() for side effects.
+ return type.isClassType()
+ && !type.isStringType(dexItemFactory)
+ && dexItemFactory.getPrimitiveFromBoxed(type.toDexType(dexItemFactory)) == null;
+ }
+
public boolean mightCallToStringWithSideEffects(AppView<?> appView) {
- if (cachedMightCallToStringWithSideEffects != null) {
- return cachedMightCallToStringWithSideEffects;
- }
- boolean ret = false;
+ // TODO(agrieve): check if the toString method of each argType may have side effects.
for (Value value : inValues) {
- if (JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(appView, value)) {
- ret = true;
- break;
+ if (toStringMayHaveSideEffects(appView.dexItemFactory(), value.getType())) {
+ return true;
}
}
- cachedMightCallToStringWithSideEffects = ret;
- return ret;
+ return false;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringConcatOptimizer.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringConcatOptimizer.java
index ebb15c1..3f00f2b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/StringConcatOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/StringConcatOptimizer.java
@@ -21,10 +21,8 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
-import com.android.tools.r8.ir.optimize.library.sideeffects.JavaLangObjectsSideEffectCollection;
import com.android.tools.r8.utils.ValueUtils;
import com.android.tools.r8.utils.internal.exceptions.Unreachable;
-import it.unimi.dsi.fastutil.ints.IntArrayList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -65,10 +63,11 @@
Instruction current = iterator.next();
StringConcat stringConcat = current.asStringConcat();
if (stringConcat != null) {
- if (mergeStringConcats(stringConcat, code)) {
+ if (mergeStringConcats(stringConcat)) {
hasChanged = true;
}
if (!stringConcat.hasUsedOutValue()) {
+ // TODO: Might be worth an option to assume all toString() overloads are side-effect free.
// TODO: Maybe should replace with directly toString() calls rather than concatenating
// (concatenation is likely smaller size once outlined).
removeAllSideEffectFreeInValues(stringConcat, dexItemFactory);
@@ -81,50 +80,33 @@
return CodeRewriterResult.hasChanged(hasChanged);
}
- private boolean hasAnyStringConcatOperands(StringConcat stringConcat) {
+ private boolean hasMergableStringConcats(StringConcat stringConcat) {
for (Value value : stringConcat.inValues()) {
if (value.getAliasedValue().isDefinedByInstructionSatisfying(Instruction::isStringConcat)
&& value.hasSingleUniqueUserAndNoOtherUsers()) {
return true;
}
}
-
return false;
}
/**
- * If any inValue is from a StringConcat, merges it into this instance if its safe to do so.
+ * If any inValue is from a StringConcat, merges it into this instance.
*
* @return Whether any instructions were merged.
*/
- private boolean mergeStringConcats(StringConcat stringConcat, IRCode code) {
- if (!hasAnyStringConcatOperands(stringConcat)) {
- return false;
- }
- // Since the order of operands does not necessarily align with the order of their defining
- // instructions, any side effect could impact other values. Rather than trying to check the
- // order of the operands, just bail out in this case.
- if (stringConcat.mightCallToStringWithSideEffects(appView)) {
+ public boolean mergeStringConcats(StringConcat stringConcat) {
+ if (!hasMergableStringConcats(stringConcat)) {
return false;
}
- IntArrayList indicesToMerge = computeMergableConcats(stringConcat, code);
- if (indicesToMerge.isEmpty()) {
- return false;
- }
-
- mergeConcats(stringConcat, indicesToMerge);
- return true;
- }
-
- private void mergeConcats(StringConcat stringConcat, IntArrayList toMergeIndices) {
DexType[] argTypes = stringConcat.getArgTypes();
List<DexString> argConstants = stringConcat.getArgConstants();
List<Value> inValues = stringConcat.inValues();
+
StringConcatBuilder builder = new StringConcatBuilder();
- int inValueIdx = -1;
- int numMerged = 0;
- int nextMergeValueIdx = toMergeIndices.getInt(numMerged);
+
+ int inValueIdx = 0;
for (int argIdx = 0; argIdx < argTypes.length; ++argIdx) {
DexString curString = argConstants.get(argIdx);
if (curString != null) {
@@ -132,12 +114,23 @@
continue;
}
- Value value = inValues.get(++inValueIdx);
- if (inValueIdx != nextMergeValueIdx) {
+ Value value = inValues.get(inValueIdx++);
+ boolean safeToMerge =
+ value.isDefinedByInstructionSatisfying(Instruction::isStringConcat)
+ && value.hasSingleUniqueUserAndNoOtherUsers();
+
+ StringConcat otherStringConcat = null;
+ if (safeToMerge) {
+ // TODO(467374229): We could also allow this if we verify that the order of the toString()
+ // calls does not change when merged.
+ otherStringConcat = value.getDefinition().asStringConcat();
+ safeToMerge = !otherStringConcat.mightCallToStringWithSideEffects(appView);
+ }
+
+ if (!safeToMerge) {
builder.addValue(value, argTypes[argIdx]);
continue;
}
- StringConcat otherStringConcat = value.getDefinition().asStringConcat();
value.clearUsers();
DexType[] otherTypes = otherStringConcat.getArgTypes();
List<DexString> otherConstants = otherStringConcat.getArgConstants();
@@ -156,43 +149,10 @@
}
}
otherStringConcat.removeOrReplaceByDebugLocalRead();
-
- numMerged += 1;
- if (numMerged < toMergeIndices.size()) {
- nextMergeValueIdx = toMergeIndices.getInt(numMerged);
- }
}
builder.apply(stringConcat);
- }
-
- private IntArrayList computeMergableConcats(StringConcat stringConcat, IRCode code) {
- DexType[] argTypes = stringConcat.getArgTypes();
- List<DexString> argConstants = stringConcat.getArgConstants();
- List<Value> inValues = stringConcat.inValues();
- IntArrayList toMergeIndices = new IntArrayList();
-
- int inValueIdx = -1;
- for (int argIdx = 0; argIdx < argTypes.length; ++argIdx) {
- DexString curString = argConstants.get(argIdx);
- if (curString != null) {
- continue;
- }
- Value value = inValues.get(++inValueIdx);
-
- // No need to check getAliasedValue since StringConcat are always @NonNull.
- if (value.isDefinedByInstructionSatisfying(Instruction::isStringConcat)
- && value.hasSingleUniqueUserAndNoOtherUsers()) {
- StringConcat other = value.getDefinition().asStringConcat();
- if (!code.anyInstructionBetween(
- other,
- stringConcat,
- ins -> ins.instructionMayHaveSideEffects(appView, code.context()))) {
- toMergeIndices.add(inValueIdx);
- }
- }
- }
- return toMergeIndices;
+ return true;
}
private boolean canSimplifyArgs(StringConcat stringConcat) {
@@ -256,16 +216,15 @@
Instruction instruction = aliasedValue.getDefinition();
// Translate foo.toString() -> foo.
+ // Check for non-null since null.toString() throws, but "" + null == "null".
if (isToStringInvoke(instruction)) {
Value receiver = instruction.getFirstOperand();
// TODO(467374229): We could also allow this if we verify that the order of the
// toString() call does not change.
Value aliasedReceiver = receiver.getAliasedValue();
if (!aliasedReceiver.isPhi()
- // Check for non-null since null.toString() throws, but "" + null == "null".
&& receiver.isNeverNull()
- && !JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
- appView, receiver)) {
+ && !StringConcat.toStringMayHaveSideEffects(dexItemFactory, receiver.getType())) {
ValueUtils.removeAliasChain(value, aliasedValue);
if (!aliasedValue.hasAnyUsers()) {
// Remove the toString() instruction.
@@ -353,7 +312,7 @@
List<Value> inValues = stringConcat.inValues();
List<Value> newInValues = new ArrayList<>(inValues.size());
for (Value value : inValues) {
- if (JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(appView, value)) {
+ if (StringConcat.toStringMayHaveSideEffects(dexItemFactory, value.getType())) {
newInValues.add(value);
} else {
value.removeUser(stringConcat);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
index 3fc4def..0c65d04 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -13,25 +13,15 @@
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import java.util.Collections;
import java.util.List;
public class JavaLangObjectsSideEffectCollection {
- public static boolean toStringMayHaveSideEffects(AppView<?> appView, Value value) {
- return toStringMayHaveSideEffects(appView, Collections.singletonList(value));
- }
-
- /** Returns whether String.valueOf(value) might have side-effects. */
public static boolean toStringMayHaveSideEffects(AppView<?> appView, List<Value> arguments) {
// Calling toString() on an array does not call toString() on the array elements.
DexItemFactory dexItemFactory = appView.dexItemFactory();
- Value value = arguments.get(0);
- TypeElement type = value.getType();
- if (type.isPrimitiveType()
- || type.isStringType(dexItemFactory)
- || type.isArrayType()
- || value.isAlwaysNull(appView)) {
+ TypeElement type = arguments.get(0).getType();
+ if (type.isArrayType() || type.isNullType()) {
return false;
}
diff --git a/src/test/java11/com/android/tools/r8/jdk11/string/StringConcatTest.java b/src/test/java11/com/android/tools/r8/jdk11/string/StringConcatTest.java
index 84a8fa5..a5c0eaa 100644
--- a/src/test/java11/com/android/tools/r8/jdk11/string/StringConcatTest.java
+++ b/src/test/java11/com/android/tools/r8/jdk11/string/StringConcatTest.java
@@ -4,8 +4,9 @@
package com.android.tools.r8.jdk11.string;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.Jdk9TestUtils;
@@ -190,10 +191,10 @@
method = mainClass.uniqueMethodWithOriginalName("mergeStringsWithSideEffects_sameOrder");
assertTrue(method.isPresent());
- assertEquals(2, countStringBuilderOrInvokeDynamic(method));
+ assertEquals(3, countStringBuilderOrInvokeDynamic(method));
method = mainClass.uniqueMethodWithOriginalName("noOutValues_noSideEffects");
- assertFalse("Empty method should be removed.", method.isPresent());
+ assertThat("Empty method should be removed.", method, isPresent());
}
static class Main {