Regard null outValue as unused StringBuilder.
Bug: 114002137, 137038659
Change-Id: I190c875c41755c130a77594c829db8e7e2a7661d
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 516ef27..ad890ec 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
@@ -567,11 +567,10 @@
InvokeVirtual invoke = instr.asInvokeVirtual();
assert invoke.inValues().size() == 1;
Value builder = invoke.getReceiver().getAliasedValue();
- assert invoke.hasOutValue();
Value outValue = invoke.outValue();
- if (outValue.isDead(appView, code)) {
+ if (outValue == null || outValue.isDead(appView, code)) {
// If the out value is still used but potentially dead, replace it with a dummy string.
- if (outValue.isUsed()) {
+ if (outValue != null && outValue.isUsed()) {
Value dummy =
code.createValue(
TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
@@ -610,9 +609,6 @@
return false;
}
InvokeVirtual invoke = instr.asInvokeVirtual();
- if (!invoke.hasOutValue()) {
- return false;
- }
DexMethod invokedMethod = invoke.getInvokedMethod();
if (!optimizationConfiguration.isToStringMethod(invokedMethod)) {
return false;
@@ -624,7 +620,7 @@
}
// If the result of toString() is no longer used, computing the compile-time constant is
// even not necessary.
- if (invoke.outValue().isDead(appView, code)) {
+ if (!invoke.hasOutValue() || invoke.outValue().isDead(appView, code)) {
return true;
}
Map<Instruction, BuilderState> perInstrState = builderStates.get(builder);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
index 627ea8e..f186803 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizerAnalysisTest.java
@@ -37,6 +37,22 @@
}
@Test
+ public void testUnusedBuilder() throws Exception {
+ buildAndCheckIR(
+ "unusedBuilder",
+ checkOptimizerStates(appView, optimizer -> {
+ assertEquals(1, optimizer.analysis.builderStates.size());
+ for (Value builder : optimizer.analysis.builderStates.keySet()) {
+ Map<Instruction, BuilderState> perBuilderState =
+ optimizer.analysis.builderStates.get(builder);
+ checkBuilderState(optimizer, perBuilderState, "42", true);
+ }
+ assertEquals(1, optimizer.analysis.deadBuilders.size());
+ assertEquals(0, optimizer.analysis.simplifiedBuilders.size());
+ }));
+ }
+
+ @Test
public void testTrivialSequence() throws Exception {
buildAndCheckIR(
"trivialSequence",
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 e8fa90f..1b16aa6 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
@@ -4,6 +4,7 @@
package com.android.tools.r8.ir.optimize.string;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
@@ -19,7 +20,6 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject.JumboStringMode;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
-import com.google.common.collect.Streams;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -28,19 +28,32 @@
public class StringConcatenationTest extends TestBase {
private static final Class<?> MAIN = StringConcatenationTestClass.class;
private static final String JAVA_OUTPUT = StringUtils.lines(
+ // trivialSequence
"xyz",
+ // builderWithInitialValue
"Hello,R8",
+ // builderWithCapacity
"42",
+ // nonStringArgs
"42",
+ // typeConversion
"0.14 0 false null",
+ // typeConversion_withPhis
"3.14 3 0",
+ // nestedBuilders_appendBuilderItself
"Hello,R8",
+ // nestedBuilders_appendBuilderResult
"Hello,R8",
+ // simplePhi
"Hello,",
"Hello,D8",
+ // phiAtInit
"Hello,R8",
+ // phiWithDifferentInits
"Hello,R8",
+ // loop
"na;na;na;na;na;na;na;na;Batman!",
+ // loopWithBuilder
"na;na;na;na;na;na;na;na;Batman!"
);
@@ -64,96 +77,92 @@
.assertSuccessWithOutput(JAVA_OUTPUT);
}
- private void test(
- TestRunResult result,
- int expectedStringCountInTrivialSequence,
- int expectedStringCountInBuilderWithInitialValue,
- int expectedStringCountInBuilderWithCapacity,
- int expectedStringCountInNonStringArgs,
- int expectedStringCountInTypeConversion,
- int expectedStringCountInNestedBuilderAppendItself,
- int expectedStringCountInNestedBuilderAppendResult)
- throws Exception {
+ private void test(TestRunResult result, boolean isR8, boolean isReleaseMode) throws Exception {
+ // TODO(b/114002137): The lack of subtyping made the escape analysis to regard
+ // StringBuilder#toString as an alias-introducing instruction.
+ // For now, debug v.s. release mode of D8 have the same result.
+
+ // Smaller is better in general. If the counter part is zero, that means non-string arguments
+ // are used, and in that case bigger is better.
+ // If the fixed count is used, that means StringBuilderOptimization should keep things as-is
+ // or there would be no observable differences (# of builders could be different).
+ int expectedCount;
+
CodeInspector codeInspector = result.inspector();
ClassSubject mainClass = codeInspector.clazz(MAIN);
- MethodSubject method = mainClass.uniqueMethodWithName("trivialSequence");
+ MethodSubject method = mainClass.uniqueMethodWithName("unusedBuilder");
+ if (isR8) {
+ assertThat(method, not(isPresent()));
+ } else {
+ assertThat(method, isPresent());
+ assertEquals(0, countConstString(method));
+ }
+
+ method = mainClass.uniqueMethodWithName("trivialSequence");
assertThat(method, isPresent());
- long count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(expectedStringCountInTrivialSequence, count);
+ expectedCount = isR8 ? 1 : 3;
+ assertEquals(expectedCount, countConstString(method));
method = mainClass.uniqueMethodWithName("builderWithInitialValue");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(expectedStringCountInBuilderWithInitialValue, count);
+ expectedCount = isR8 ? 1 : 3;
+ assertEquals(expectedCount, countConstString(method));
method = mainClass.uniqueMethodWithName("builderWithCapacity");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(expectedStringCountInBuilderWithCapacity, count);
+ expectedCount = isR8 ? 1 : 0;
+ assertEquals(expectedCount, countConstString(method));
method = mainClass.uniqueMethodWithName("nonStringArgs");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(expectedStringCountInNonStringArgs, count);
+ expectedCount = isR8 ? 1 : 0;
+ assertEquals(expectedCount, countConstString(method));
method = mainClass.uniqueMethodWithName("typeConversion");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(expectedStringCountInTypeConversion, count);
+ expectedCount = isR8 ? 1 : 0;
+ assertEquals(expectedCount, countConstString(method));
method = mainClass.uniqueMethodWithName("typeConversion_withPhis");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(0, count);
+ assertEquals(0, countConstString(method));
method = mainClass.uniqueMethodWithName("nestedBuilders_appendBuilderItself");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(expectedStringCountInNestedBuilderAppendItself, count);
+ // TODO(b/113859361): merge builders
+ expectedCount = 3;
+ assertEquals(expectedCount, countConstString(method));
method = mainClass.uniqueMethodWithName("nestedBuilders_appendBuilderResult");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(expectedStringCountInNestedBuilderAppendResult, count);
+ // TODO(b/113859361): merge builders
+ expectedCount = 3;
+ assertEquals(expectedCount, countConstString(method));
method = mainClass.uniqueMethodWithName("simplePhi");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(4, count);
+ assertEquals(4, countConstString(method));
method = mainClass.uniqueMethodWithName("phiAtInit");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(3, count);
+ assertEquals(3, countConstString(method));
method = mainClass.uniqueMethodWithName("phiWithDifferentInits");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(3, count);
+ assertEquals(3, countConstString(method));
method = mainClass.uniqueMethodWithName("loop");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(3, count);
+ assertEquals(3, countConstString(method));
method = mainClass.uniqueMethodWithName("loopWithBuilder");
assertThat(method, isPresent());
- count = Streams.stream(method.iterateInstructions(
- i -> i.isConstString(JumboStringMode.ALLOW))).count();
- assertEquals(2, count);
+ assertEquals(2, countConstString(method));
+ }
+
+ private long countConstString(MethodSubject method) {
+ return method.streamInstructions().filter(i -> i.isConstString(JumboStringMode.ALLOW)).count();
}
@Test
@@ -167,7 +176,7 @@
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 3, 3, 0, 0, 0, 3, 3);
+ test(result, false, false);
result =
testForD8()
@@ -176,9 +185,7 @@
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- // TODO(b/114002137): The lack of subtyping made the escape analysis to regard
- // StringBuilder#toString as an alias-introducing instruction.
- test(result, 3, 3, 0, 0, 0, 3, 3);
+ test(result, false, true);
}
@Test
@@ -194,6 +201,6 @@
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
- test(result, 1, 1, 1, 1, 1, 3, 3);
+ test(result, true, true);
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
index e00d97a..980148b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
@@ -7,6 +7,14 @@
class StringConcatenationTestClass {
@NeverInline
+ public static void unusedBuilder() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(4);
+ builder.append(2);
+ builder.toString();
+ }
+
+ @NeverInline
public static void trivialSequence() {
String x = "x";
String y = "y";
@@ -149,6 +157,7 @@
}
public static void main(String[] args) {
+ unusedBuilder();
trivialSequence();
builderWithInitialValue();
builderWithCapacity();