Remove trivial StringBuilders if not used.
Bug: 137038659, 114002137
Change-Id: Ia77b0f07ddf0414c30ddfd109bcc1e2a563b8147
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 f647eb4..152fcd8 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
@@ -35,7 +35,6 @@
import it.unimi.dsi.fastutil.objects.Object2IntArrayMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
@@ -89,6 +88,7 @@
private int numberOfBuildersWithNonStringArg = 0;
private int numberOfBuildersWithMergingPoints = 0;
private int numberOfBuildersWithNonDeterministicArg = 0;
+ private int numberOfDeadBuilders = 0;
private int numberOfBuildersSimplified = 0;
private final Object2IntMap<Integer> histogramOfLengthOfAppendChains;
private final Object2IntMap<Integer> histogramOfLengthOfEndResult;
@@ -151,8 +151,8 @@
"# builders w/ merging points: %s", numberOfBuildersWithMergingPoints);
Log.info(getClass(),
"# builders w/ non-deterministic arg: %s", numberOfBuildersWithNonDeterministicArg);
- Log.info(getClass(),
- "# builders simplified: %s", numberOfBuildersSimplified);
+ Log.info(getClass(), "# dead builders : %s", numberOfDeadBuilders);
+ Log.info(getClass(), "# builders simplified: %s", numberOfBuildersSimplified);
assert histogramOfLengthOfAppendChains != null;
Log.info(getClass(), "------ histogram of StringBuilder append chain lengths ------");
histogramOfLengthOfAppendChains.forEach((chainSize, count) -> {
@@ -500,9 +500,10 @@
}
}
- // StringBuilders that are simplified by this analysis. Will be used to clean up uses of the
- // builders, such as creation, <init>, and append calls.
- final Set<Value> simplifiedBuilders = new HashSet<>();
+ // StringBuilders that are simplified by this analysis or simply dead (e.g., after applying
+ // -assumenosideeffects to logging calls, then logging messages built by builders are dead).
+ // Will be used to clean up uses of the builders, such as creation, <init>, and append calls.
+ final Set<Value> simplifiedBuilders = Sets.newIdentityHashSet();
private StringConcatenationAnalysis applyConcatenationResults(Set<Value> candidateBuilders) {
InstructionIterator it = code.instructionIterator();
@@ -514,6 +515,15 @@
InvokeVirtual invoke = instr.asInvokeVirtual();
assert invoke.inValues().size() == 1;
Value builder = invoke.getReceiver().getAliasedValue();
+ assert invoke.hasOutValue();
+ if (invoke.outValue().isDead(appView, code)) {
+ it.removeOrReplaceByDebugLocalRead();
+ // Although this builder is not simplified by this analysis, add that to the set so that
+ // it can be removed at the final clean-up phase.
+ simplifiedBuilders.add(builder);
+ numberOfDeadBuilders++;
+ continue;
+ }
Map<Instruction, BuilderState> perInstrState = builderStates.get(builder);
assert perInstrState != null;
BuilderState builderState = perInstrState.get(instr);
@@ -547,6 +557,11 @@
if (!candidateBuilders.contains(builder)) {
return false;
}
+ // If the result of toString() is no longer used, computing the compile-time constant is
+ // even not necessary.
+ if (invoke.outValue().isDead(appView, code)) {
+ return true;
+ }
Map<Instruction, BuilderState> perInstrState = builderStates.get(builder);
if (perInstrState == null) {
return false;
@@ -834,7 +849,7 @@
newState.previous = this;
newState.addition = addition;
if (this.nexts == null) {
- this.nexts = new HashSet<>();
+ this.nexts = Sets.newIdentityHashSet();
}
this.nexts.add(newState);
return newState;
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 fc87c72..1e4e3d7 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
@@ -161,7 +161,7 @@
@Test
public void testR8() throws Exception {
- assumeTrue("CF does not canonicalize constants.", parameters.isDexRuntime());
+ assumeTrue("CF does not rewrite move results.", parameters.isDexRuntime());
R8TestRunResult result =
testForR8(parameters.getBackend())
@@ -178,6 +178,7 @@
// TODO(b/114002137): Once enabled, remove this test-specific setting.
private void configure(InternalOptions options) {
+ assert !options.enableStringConcatenationOptimization;
options.enableStringConcatenationOptimization = true;
}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/StringBuildersAfterAssumenosideeffectsTest.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/StringBuildersAfterAssumenosideeffectsTest.java
index 2114ad6..8ff68aa 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/StringBuildersAfterAssumenosideeffectsTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/StringBuildersAfterAssumenosideeffectsTest.java
@@ -6,13 +6,14 @@
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.assertNotEquals;
+import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.NeverClassInline;
import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -40,6 +41,8 @@
@Test
public void testR8() throws Exception {
+ assumeTrue("CF does not rewrite move results.", parameters.isDexRuntime());
+
testForR8(parameters.getBackend())
.addInnerClasses(StringBuildersAfterAssumenosideeffectsTest.class)
.enableClassInliningAnnotations()
@@ -49,6 +52,7 @@
"-assumenosideeffects class * implements " + TestLogger.class.getTypeName() + " {",
" void info(...);",
"}")
+ .addOptionsModification(this::configure)
.noMinification()
.setMinApi(parameters.getRuntime())
.run(parameters.getRuntime(), MAIN)
@@ -67,12 +71,17 @@
Streams.stream(mainMethod.iterateInstructions(
i -> i.isInvoke() && i.getMethod().name.toString().equals("info"))).count());
- // TODO(b/137038659): StringBuilders (and all other calls) can be gone.
- assertNotEquals(
+ assertEquals(
0,
Streams.stream(mainMethod.iterateInstructions(
i -> i.isInvoke()
- && i.getMethod().holder.toDescriptorString().contains("StringBuilder"))));
+ && i.getMethod().holder.toDescriptorString().contains("StringBuilder"))).count());
+ }
+
+ // TODO(b/114002137): Once enabled, remove this test-specific setting.
+ private void configure(InternalOptions options) {
+ assert !options.enableStringConcatenationOptimization;
+ options.enableStringConcatenationOptimization = true;
}
interface TestLogger {