Synthesize toStringIfNotNull() to optimize StringBuilder.append(Object)
Change-Id: I4a59c54c4a18f31248fb63364fe370496b6df24d
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index 247e2e3..1b55f13 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -19,6 +19,35 @@
public class UtilityMethodsForCodeOptimizations {
+ public static UtilityMethodForCodeOptimizations synthesizeToStringIfNotNullMethod(
+ AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
+ InternalOptions options = appView.options();
+ if (options.isGeneratingClassFiles()) {
+ // TODO(b/172194277): Allow synthetics when generating CF.
+ return null;
+ }
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ ProgramMethod syntheticMethod =
+ syntheticItems.createMethod(
+ context,
+ dexItemFactory,
+ builder ->
+ builder
+ .setProto(proto)
+ .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+ .setCode(method -> getToStringIfNotNullCodeTemplate(method, options)),
+ methodProcessingId);
+ return new UtilityMethodForCodeOptimizations(syntheticMethod);
+ }
+
+ private static CfCode getToStringIfNotNullCodeTemplate(
+ DexMethod method, InternalOptions options) {
+ return CfUtilityMethodsForCodeOptimizations
+ .CfUtilityMethodsForCodeOptimizationsTemplates_toStringIfNotNull(options, method);
+ }
+
public static UtilityMethodForCodeOptimizations synthesizeThrowClassCastExceptionIfNotNullMethod(
AppView<?> appView, ProgramMethod context, MethodProcessingId methodProcessingId) {
InternalOptions options = appView.options();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
index 5c38b07..25916f9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMemberOptimizer.java
@@ -119,34 +119,33 @@
new IdentityHashMap<>();
while (instructionIterator.hasNext()) {
Instruction instruction = instructionIterator.next();
- if (instruction.isInvokeMethod()) {
- InvokeMethod invoke = instruction.asInvokeMethod();
- DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
- if (singleTarget != null) {
- optimizeInvoke(
- code, instructionIterator, invoke, singleTarget, affectedValues, optimizationStates);
- }
+ if (!instruction.isInvokeMethod()) {
+ continue;
}
+
+ InvokeMethod invoke = instruction.asInvokeMethod();
+ DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, code.context());
+ if (singleTarget == null) {
+ continue;
+ }
+
+ LibraryMethodModelCollection<?> optimizer =
+ libraryMethodModelCollections.get(singleTarget.getHolderType());
+ if (optimizer == null) {
+ continue;
+ }
+
+ LibraryMethodModelCollection.State optimizationState =
+ optimizationStates.computeIfAbsent(
+ optimizer,
+ libraryMethodModelCollection ->
+ libraryMethodModelCollection.createInitialState(
+ methodProcessor, methodProcessingId));
+ optimizer.optimize(
+ code, instructionIterator, invoke, singleTarget, affectedValues, optimizationState);
}
if (!affectedValues.isEmpty()) {
new TypeAnalysis(appView).narrowing(affectedValues);
}
}
-
- private void optimizeInvoke(
- IRCode code,
- InstructionListIterator instructionIterator,
- InvokeMethod invoke,
- DexClassAndMethod singleTarget,
- Set<Value> affectedValues,
- Map<LibraryMethodModelCollection<?>, LibraryMethodModelCollection.State> optimizationStates) {
- LibraryMethodModelCollection<?> optimizer =
- libraryMethodModelCollections.getOrDefault(
- singleTarget.getHolderType(), NopLibraryMethodModelCollection.getInstance());
- LibraryMethodModelCollection.State optimizationState =
- optimizationStates.computeIfAbsent(
- optimizer, LibraryMethodModelCollection::createInitialState);
- optimizer.optimize(
- code, instructionIterator, invoke, singleTarget, affectedValues, optimizationState);
- }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
index 463f7e7..5cb30b3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodModelCollection.java
@@ -10,13 +10,16 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.library.LibraryMethodModelCollection.State;
import java.util.Set;
/** Used to model the behavior of library methods for optimization purposes. */
public interface LibraryMethodModelCollection<T extends State> {
- default T createInitialState() {
+ default T createInitialState(
+ MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
index 6754170..8b36204 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StatelessLibraryMethodModelCollection.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
import com.android.tools.r8.ir.optimize.library.StatelessLibraryMethodModelCollection.State;
import java.util.Set;
@@ -16,7 +18,8 @@
implements LibraryMethodModelCollection<State> {
@Override
- public final State createInitialState() {
+ public final State createInitialState(
+ MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
index a88bae9..9cccb5d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringBuilderMethodOptimizer.java
@@ -23,6 +23,10 @@
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.InvokeVirtual;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.MethodProcessingId;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations;
+import com.android.tools.r8.ir.optimize.UtilityMethodsForCodeOptimizations.UtilityMethodForCodeOptimizations;
import com.android.tools.r8.ir.optimize.library.StringBuilderMethodOptimizer.State;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.WorkList;
@@ -33,20 +37,23 @@
public class StringBuilderMethodOptimizer implements LibraryMethodModelCollection<State> {
+ private final AppView<?> appView;
private final DexItemFactory dexItemFactory;
private final InternalOptions options;
private final StringBuildingMethods stringBuilderMethods;
StringBuilderMethodOptimizer(AppView<?> appView) {
DexItemFactory dexItemFactory = appView.dexItemFactory();
+ this.appView = appView;
this.dexItemFactory = dexItemFactory;
this.options = appView.options();
this.stringBuilderMethods = dexItemFactory.stringBuilderMethods;
}
@Override
- public State createInitialState() {
- return new State();
+ public State createInitialState(
+ MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
+ return new State(methodProcessor, methodProcessingId);
}
@Override
@@ -65,12 +72,13 @@
if (invoke.isInvokeMethodWithReceiver()) {
InvokeMethodWithReceiver invokeWithReceiver = invoke.asInvokeMethodWithReceiver();
if (stringBuilderMethods.isAppendMethod(singleTarget.getReference())) {
- optimizeAppend(instructionIterator, invokeWithReceiver, singleTarget, state);
+ optimizeAppend(code, instructionIterator, invokeWithReceiver, singleTarget, state);
}
}
}
private void optimizeAppend(
+ IRCode code,
InstructionListIterator instructionIterator,
InvokeMethodWithReceiver invoke,
DexClassAndMethod singleTarget,
@@ -88,27 +96,52 @@
} else if (stringBuilderMethods.isAppendObjectMethod(appendMethod)) {
Value object = invoke.getArgument(1);
if (object.isNeverNull()) {
+ // Replace the instruction by java.lang.Object.toString().
instructionIterator.replaceCurrentInstruction(
InvokeVirtual.builder()
- .setSingleArgument(object)
.setMethod(dexItemFactory.objectMembers.toString)
+ .setSingleArgument(object)
.build());
} else if (options.canUseJavaUtilObjects()) {
+ // Replace the instruction by java.util.Objects.toString().
instructionIterator.replaceCurrentInstruction(
InvokeStatic.builder()
- .setSingleArgument(object)
.setMethod(dexItemFactory.objectsMethods.toStringWithObject)
+ .setSingleArgument(object)
.build());
// Allow the java.util.Objects optimizer to optimize the newly added toString().
instructionIterator.previous();
+ } else {
+ // Replace the instruction by toStringIfNotNull().
+ UtilityMethodForCodeOptimizations toStringIfNotNullMethod =
+ UtilityMethodsForCodeOptimizations.synthesizeToStringIfNotNullMethod(
+ appView, code.context(), state.methodProcessingId);
+ // TODO(b/172194277): Allow synthetics when generating CF.
+ if (toStringIfNotNullMethod != null) {
+ toStringIfNotNullMethod.optimize(state.methodProcessor);
+ InvokeStatic replacement =
+ InvokeStatic.builder()
+ .setMethod(toStringIfNotNullMethod.getMethod())
+ .setSingleArgument(object)
+ .build();
+ instructionIterator.replaceCurrentInstruction(replacement);
+ }
}
}
}
class State implements LibraryMethodModelCollection.State {
+ final MethodProcessor methodProcessor;
+ final MethodProcessingId methodProcessingId;
+
final Reference2BooleanMap<Value> unusedBuilders = new Reference2BooleanOpenHashMap<>();
+ State(MethodProcessor methodProcessor, MethodProcessingId methodProcessingId) {
+ this.methodProcessor = methodProcessor;
+ this.methodProcessingId = methodProcessingId;
+ }
+
boolean isUnusedBuilder(Value value) {
if (!unusedBuilders.containsKey(value)) {
computeIsUnusedBuilder(value);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
index 9484007..9625b93 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizations.java
@@ -72,4 +72,40 @@
ImmutableList.of(),
ImmutableList.of());
}
+
+ public static CfCode CfUtilityMethodsForCodeOptimizationsTemplates_toStringIfNotNull(
+ InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 1,
+ 1,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfIf(If.Type.EQ, ValueType.OBJECT, label2),
+ label1,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.objectType,
+ options.itemFactory.createProto(options.itemFactory.stringType),
+ options.itemFactory.createString("toString")),
+ false),
+ new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+ label2,
+ new CfFrame(
+ new Int2ReferenceAVLTreeMap<>(
+ new int[] {0},
+ new FrameType[] {FrameType.initialized(options.itemFactory.objectType)}),
+ new ArrayDeque<>(Arrays.asList())),
+ new CfReturnVoid(),
+ label3),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendMaybeNullObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendMaybeNullObjectTest.java
index 7d23755..78c0a10 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendMaybeNullObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendMaybeNullObjectTest.java
@@ -42,7 +42,9 @@
MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
assertThat(
mainMethod,
- notIf(instantiatesClass(StringBuilder.class), canUseJavaUtilObjects(parameters)));
+ notIf(
+ instantiatesClass(StringBuilder.class),
+ canUseJavaUtilObjects(parameters) || parameters.isDexRuntime()));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithEmptyOutput();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
index 72c3721..d8ed11c 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/UnusedStringBuilderWithAppendObjectTest.java
@@ -42,7 +42,9 @@
MethodSubject mainMethod = inspector.clazz(Main.class).mainMethod();
assertThat(
mainMethod,
- notIf(instantiatesClass(StringBuilder.class), canUseJavaUtilObjects(parameters)));
+ notIf(
+ instantiatesClass(StringBuilder.class),
+ canUseJavaUtilObjects(parameters) || parameters.isDexRuntime()));
})
.run(parameters.getRuntime(), Main.class)
.assertSuccessWithEmptyOutput();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
index d8d7c85..8f7c579 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/templates/CfUtilityMethodsForCodeOptimizationsTemplates.java
@@ -6,6 +6,12 @@
public class CfUtilityMethodsForCodeOptimizationsTemplates {
+ public static void toStringIfNotNull(Object o) {
+ if (o != null) {
+ o.toString();
+ }
+ }
+
public static void throwClassCastExceptionIfNotNull(Object o) {
if (o != null) {
throw new ClassCastException();