Misc. updates to allow better logging removal
Bug: 174285670
Change-Id: Ib90d7ae7dba94d91d5fbba961daca37508de1acf
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index 489d0b6..2d91d71 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -125,8 +125,7 @@
this.callSiteOptimizationInfoPropagator = null;
}
- this.libraryMethodSideEffectModelCollection =
- new LibraryMethodSideEffectModelCollection(dexItemFactory());
+ this.libraryMethodSideEffectModelCollection = new LibraryMethodSideEffectModelCollection(this);
this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
if (enableWholeProgramOptimizations() && options().protoShrinking().isProtoShrinkingEnabled()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index fd33483..30ac84b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -440,6 +440,7 @@
createStaticallyKnownType("Ljava/util/logging/Logger;");
public final DexType javaUtilSetType = createStaticallyKnownType("Ljava/util/Set;");
+ public final DexType androidAppActivity = createStaticallyKnownType("Landroid/app/Activity;");
public final DexType androidOsBuildType = createStaticallyKnownType("Landroid/os/Build;");
public final DexType androidOsBuildVersionType =
createStaticallyKnownType("Landroid/os/Build$VERSION;");
@@ -683,6 +684,17 @@
createString("makeConcat")
);
+ public Map<DexMethod, int[]> libraryMethodsNonNullParamOrThrow =
+ buildLibraryMethodsNonNullParamOrThrow();
+
+ private Map<DexMethod, int[]> buildLibraryMethodsNonNullParamOrThrow() {
+ ImmutableMap.Builder<DexMethod, int[]> builder = ImmutableMap.builder();
+ for (DexMethod requireNonNullMethod : objectsMethods.requireNonNullMethods()) {
+ builder.put(requireNonNullMethod, new int[] {0});
+ }
+ return builder.build();
+ }
+
public Set<DexMethod> libraryMethodsReturningReceiver =
ImmutableSet.<DexMethod>builder()
.addAll(stringBufferMethods.appendMethods)
@@ -719,6 +731,7 @@
public Set<DexType> libraryTypesAssumedToBePresent =
ImmutableSet.<DexType>builder()
.add(
+ androidAppActivity,
callableType,
enumType,
npeType,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
index 23342df..b176db1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -165,7 +165,7 @@
finalFieldPuts.forEach(
(field, put) -> {
DexType fieldType = field.field.type;
- Value value = put.value();
+ Value value = put.value().getAliasedValue();
if (unnecessaryStaticPuts.contains(put)) {
if (fieldType == dexItemFactory.stringType) {
fieldsWithStaticValues.put(field, getDexStringValue(value, context.getHolderType()));
@@ -404,7 +404,7 @@
}
DexField fieldReference = put.getField();
DexEncodedField field = context.getHolder().lookupField(fieldReference);
- Value value = put.value();
+ Value value = put.value().getAliasedValue();
TypeElement valueType = value.getType();
if (field != null) {
if (isReadBefore.contains(field)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
index 7f5d173..93a330e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfo.java
@@ -52,6 +52,10 @@
public abstract BitSet getNonNullParamOrThrow();
+ public final boolean hasNonNullParamOnNormalExits() {
+ return getNonNullParamOnNormalExits() != null;
+ }
+
public abstract BitSet getNonNullParamOnNormalExits();
public abstract boolean hasBeenInlinedIntoSingleCallSite();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 9868a64..3aaba06 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -156,8 +156,8 @@
definition, code, feedback, instanceFieldInitializationInfos, timing);
computeMayHaveSideEffects(feedback, definition, code, timing);
computeReturnValueOnlyDependsOnArguments(feedback, definition, code, timing);
- computeNonNullParamOrThrow(feedback, definition, code, timing);
- computeNonNullParamOnNormalExits(feedback, code, timing);
+ BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, definition, code, timing);
+ computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow, timing);
}
private void identifyBridgeInfo(
@@ -1165,18 +1165,19 @@
}
}
- private void computeNonNullParamOrThrow(
+ private BitSet computeNonNullParamOrThrow(
OptimizationFeedback feedback, DexEncodedMethod method, IRCode code, Timing timing) {
timing.begin("Compute non-null-param-or-throw");
- computeNonNullParamOrThrow(feedback, method, code);
+ BitSet nonNullParamOrThrow = computeNonNullParamOrThrow(feedback, method, code);
timing.end();
+ return nonNullParamOrThrow;
}
// Track usage of parameters and compute their nullability and possibility of NPE.
- private void computeNonNullParamOrThrow(
+ private BitSet computeNonNullParamOrThrow(
OptimizationFeedback feedback, DexEncodedMethod method, IRCode code) {
if (method.getOptimizationInfo().getNonNullParamOrThrow() != null) {
- return;
+ return null;
}
List<Value> arguments = code.collectArguments();
BitSet paramsCheckedForNull = new BitSet();
@@ -1196,66 +1197,80 @@
paramsCheckedForNull.set(index);
}
}
- if (paramsCheckedForNull.length() > 0) {
+ if (!paramsCheckedForNull.isEmpty()) {
feedback.setNonNullParamOrThrow(method, paramsCheckedForNull);
+ return paramsCheckedForNull;
}
+ return null;
}
private void computeNonNullParamOnNormalExits(
- OptimizationFeedback feedback, IRCode code, Timing timing) {
+ OptimizationFeedback feedback, IRCode code, BitSet nonNullParamOrThrow, Timing timing) {
timing.begin("Compute non-null-param-on-normal-exits");
- computeNonNullParamOnNormalExits(feedback, code);
+ computeNonNullParamOnNormalExits(feedback, code, nonNullParamOrThrow);
timing.end();
}
- private void computeNonNullParamOnNormalExits(OptimizationFeedback feedback, IRCode code) {
+ private void computeNonNullParamOnNormalExits(
+ OptimizationFeedback feedback, IRCode code, BitSet nonNullParamOrThrow) {
Set<BasicBlock> normalExits = Sets.newIdentityHashSet();
normalExits.addAll(code.computeNormalExitBlocks());
DominatorTree dominatorTree = new DominatorTree(code, MAY_HAVE_UNREACHABLE_BLOCKS);
List<Value> arguments = code.collectArguments();
BitSet facts = new BitSet();
- Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
+ if (nonNullParamOrThrow != null) {
+ facts.or(nonNullParamOrThrow);
+ }
for (int index = 0; index < arguments.size(); index++) {
+ if (facts.get(index)) {
+ continue;
+ }
Value argument = arguments.get(index);
- // Consider reference-type parameter only.
- if (!argument.getType().isReferenceType()) {
- continue;
- }
- // The receiver is always non-null on normal exits.
- if (argument.isThis()) {
+ if (argument.getType().isReferenceType()
+ && isNonNullOnNormalExit(code, argument, dominatorTree, normalExits)) {
facts.set(index);
- continue;
- }
- // Collect basic blocks that check nullability of the parameter.
- nullCheckedBlocks.clear();
- for (Instruction user : argument.uniqueUsers()) {
- if (user.isAssumeWithNonNullAssumption()) {
- nullCheckedBlocks.add(user.asAssume().getBlock());
- }
- if (user.isIf()
- && user.asIf().isZeroTest()
- && (user.asIf().getType() == If.Type.EQ || user.asIf().getType() == If.Type.NE)) {
- nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
- }
- }
- if (!nullCheckedBlocks.isEmpty()) {
- boolean allExitsCovered = true;
- for (BasicBlock normalExit : normalExits) {
- if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
- allExitsCovered = false;
- break;
- }
- }
- if (allExitsCovered) {
- facts.set(index);
- }
}
}
- if (facts.length() > 0) {
+ if (!facts.isEmpty()) {
feedback.setNonNullParamOnNormalExits(code.method(), facts);
}
}
+ private boolean isNonNullOnNormalExit(
+ IRCode code, Value value, DominatorTree dominatorTree, Set<BasicBlock> normalExits) {
+ assert value.getType().isReferenceType();
+
+ // The receiver is always non-null on normal exits.
+ if (value.isThis()) {
+ return true;
+ }
+
+ // Collect basic blocks that check nullability of the parameter.
+ Set<BasicBlock> nullCheckedBlocks = Sets.newIdentityHashSet();
+ for (Instruction user : value.aliasedUsers()) {
+ if (user.isAssumeWithNonNullAssumption()
+ || user.throwsNpeIfValueIsNull(value, appView, code.context())) {
+ nullCheckedBlocks.add(user.getBlock());
+ }
+ if (user.isIf()
+ && user.asIf().isZeroTest()
+ && (user.asIf().getType() == If.Type.EQ || user.asIf().getType() == If.Type.NE)) {
+ nullCheckedBlocks.add(user.asIf().targetFromNonNullObject());
+ }
+ }
+
+ if (nullCheckedBlocks.isEmpty()) {
+ return false;
+ }
+
+ for (BasicBlock normalExit : normalExits) {
+ if (!isNormalExitDominated(normalExit, code, dominatorTree, nullCheckedBlocks)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private boolean isNormalExitDominated(
BasicBlock normalExit,
IRCode code,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
index ae91ac8..3703148 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryMethodSideEffectModelCollection.java
@@ -4,11 +4,13 @@
package com.android.tools.r8.ir.optimize.library;
+import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.LibraryMethod;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.optimize.library.sideeffects.JavaLangObjectsSideEffectCollection;
import com.android.tools.r8.utils.BiPredicateUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@@ -25,23 +27,31 @@
private final Set<DexMethod> nonFinalMethodsWithoutSideEffects;
- public LibraryMethodSideEffectModelCollection(DexItemFactory dexItemFactory) {
- finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(dexItemFactory);
+ public LibraryMethodSideEffectModelCollection(AppView<?> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(appView, dexItemFactory);
unconditionalFinalMethodsWithoutSideEffects =
buildUnconditionalFinalMethodsWithoutSideEffects(dexItemFactory);
nonFinalMethodsWithoutSideEffects = buildNonFinalMethodsWithoutSideEffects(dexItemFactory);
}
private static Map<DexMethod, BiPredicate<DexMethod, List<Value>>>
- buildFinalMethodsWithoutSideEffects(DexItemFactory dexItemFactory) {
+ buildFinalMethodsWithoutSideEffects(AppView<?> appView, DexItemFactory dexItemFactory) {
ImmutableMap.Builder<DexMethod, BiPredicate<DexMethod, List<Value>>> builder =
ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
.put(
+ dexItemFactory.objectsMethods.toStringWithObject,
+ (method, arguments) ->
+ !JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
+ appView, arguments))
+ .put(
dexItemFactory.stringMembers.constructor,
(method, arguments) -> arguments.get(1).isNeverNull())
.put(
dexItemFactory.stringMembers.valueOf,
- (method, arguments) -> arguments.get(0).isNeverNull());
+ (method, arguments) ->
+ !JavaLangObjectsSideEffectCollection.toStringMayHaveSideEffects(
+ appView, arguments));
putAll(
builder,
dexItemFactory.stringBufferMethods.constructorMethods,
@@ -71,8 +81,11 @@
.add(dexItemFactory.shortMembers.toString)
.add(dexItemFactory.stringBufferMethods.toString)
.add(dexItemFactory.stringBuilderMethods.toString)
+ .add(dexItemFactory.stringMembers.length)
.add(dexItemFactory.stringMembers.hashCode)
+ .add(dexItemFactory.stringMembers.isEmpty)
.add(dexItemFactory.stringMembers.toString)
+ .add(dexItemFactory.stringMembers.trim)
.addAll(dexItemFactory.classMethods.getNames)
.addAll(dexItemFactory.boxedValueOfMethods())
.build();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index ecbb3e4..1141303 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -45,6 +45,7 @@
void run(Set<DexEncodedField> finalLibraryFields) {
modelInstanceInitializers();
modelStaticFinalLibraryFields(finalLibraryFields);
+ modelLibraryMethodsNonNullParamOrThrow();
modelLibraryMethodsReturningNonNull();
modelLibraryMethodsReturningReceiver();
modelLibraryMethodsWithoutSideEffects();
@@ -87,6 +88,30 @@
}
}
+ private void modelLibraryMethodsNonNullParamOrThrow() {
+ dexItemFactory.libraryMethodsNonNullParamOrThrow.forEach(
+ (method, nonNullParamOrThrow) -> {
+ DexEncodedMethod definition = lookupMethod(method);
+ if (definition != null) {
+ assert nonNullParamOrThrow.length > 0;
+ int size = nonNullParamOrThrow[nonNullParamOrThrow.length - 1] + 1;
+ BitSet bitSet = new BitSet(size);
+ for (int argumentIndex : nonNullParamOrThrow) {
+ assert argumentIndex < size;
+ bitSet.set(argumentIndex);
+ }
+ feedback.setNonNullParamOrThrow(definition, bitSet);
+
+ // Also set non-null-param-on-normal-exits info.
+ if (definition.getOptimizationInfo().hasNonNullParamOnNormalExits()) {
+ definition.getOptimizationInfo().getNonNullParamOnNormalExits().or(bitSet);
+ } else {
+ feedback.setNonNullParamOnNormalExits(definition, (BitSet) bitSet.clone());
+ }
+ }
+ });
+ }
+
private void modelLibraryMethodsReturningNonNull() {
for (DexMethod method : dexItemFactory.libraryMethodsReturningNonNull) {
DexEncodedMethod definition = lookupMethod(method);
@@ -131,14 +156,6 @@
DexEncodedMethod definition = lookupMethod(requireNonNullMethod);
if (definition != null) {
feedback.methodReturnsArgument(definition, 0);
-
- BitSet nonNullParamOrThrow = new BitSet();
- nonNullParamOrThrow.set(0);
- feedback.setNonNullParamOrThrow(definition, nonNullParamOrThrow);
-
- BitSet nonNullParamOnNormalExits = new BitSet();
- nonNullParamOnNormalExits.set(0);
- feedback.setNonNullParamOnNormalExits(definition, nonNullParamOnNormalExits);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
index a303082..831236b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/ObjectsMethodOptimizer.java
@@ -5,19 +5,15 @@
package com.android.tools.r8.ir.optimize.library;
import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexItemFactory.ObjectsMethods;
-import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.IRCode;
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.shaking.AppInfoWithLiveness;
import java.util.Set;
public class ObjectsMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -90,44 +86,6 @@
invoke.outValue().replaceUsers(object);
}
instructionIterator.removeOrReplaceByDebugLocalRead();
- return;
- }
-
- // Remove Objects.toString() if it is unused and does not have side effects.
- if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
- // Calling toString() on an array does not call toString() on the array elements.
- if (type.isArrayType()) {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- return;
- }
-
- assert type.isClassType();
-
- // Check if this is a library class with a toString() method that does not have side effects.
- DexType classType = type.asClassType().getClassType();
- DexMethod toStringMethodReference =
- dexItemFactory.objectMembers.toString.withHolder(classType, dexItemFactory);
- if (appView
- .getLibraryMethodSideEffectModelCollection()
- .isSideEffectFreeFinalMethod(toStringMethodReference, invoke.arguments())) {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- return;
- }
-
- // Check if this is a program class with a toString() method that does not have side effects.
- AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
- if (appInfo != null) {
- DexClass clazz = appInfo.definitionFor(classType, code.context());
- if (clazz != null && clazz.isEffectivelyFinal(appView)) {
- SingleResolutionResult resolutionResult =
- appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
- if (resolutionResult != null
- && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
- instructionIterator.removeOrReplaceByDebugLocalRead();
- return;
- }
- }
- }
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
index a8fabe3..8c62340 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/StringMethodOptimizer.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexReference;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.type.TypeElement;
import com.android.tools.r8.ir.code.DexItemBasedConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
@@ -19,7 +20,6 @@
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.utils.ValueUtils;
import java.util.Set;
public class StringMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -48,7 +48,7 @@
if (singleTargetReference == dexItemFactory.stringMembers.equals) {
optimizeEquals(code, instructionIterator, invoke.asInvokeVirtual());
} else if (singleTargetReference == dexItemFactory.stringMembers.valueOf) {
- optimizeValueOf(instructionIterator, invoke.asInvokeStatic());
+ optimizeValueOf(code, instructionIterator, invoke.asInvokeStatic(), affectedValues);
}
}
@@ -65,12 +65,30 @@
}
}
- private void optimizeValueOf(InstructionListIterator instructionIterator, InvokeStatic invoke) {
- // Optimize String.valueOf(stringBuilder) if unused.
- if (ValueUtils.isNonNullStringBuilder(invoke.getFirstArgument(), dexItemFactory)) {
- if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
- instructionIterator.removeOrReplaceByDebugLocalRead();
+ private void optimizeValueOf(
+ IRCode code,
+ InstructionListIterator instructionIterator,
+ InvokeStatic invoke,
+ Set<Value> affectedValues) {
+ Value object = invoke.getFirstArgument();
+ TypeElement type = object.getType();
+
+ // Optimize String.valueOf(null) into "null".
+ if (type.isDefinitelyNull()) {
+ instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
+ if (invoke.hasOutValue()) {
+ affectedValues.addAll(invoke.outValue().affectedValues());
}
+ return;
+ }
+
+ // Optimize String.valueOf(nonNullString) into nonNullString.
+ if (type.isDefinitelyNotNull() && type.isStringType(dexItemFactory)) {
+ if (invoke.hasOutValue()) {
+ affectedValues.addAll(invoke.outValue().affectedValues());
+ invoke.outValue().replaceUsers(object);
+ }
+ instructionIterator.removeOrReplaceByDebugLocalRead();
}
}
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
new file mode 100644
index 0000000..5f74465
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/sideeffects/JavaLangObjectsSideEffectCollection.java
@@ -0,0 +1,62 @@
+// Copyright (c) 2021, 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.optimize.library.sideeffects;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
+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.List;
+
+public class JavaLangObjectsSideEffectCollection {
+
+ 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();
+ TypeElement type = arguments.get(0).getType();
+ if (type.isArrayType() || type.isNullType()) {
+ return false;
+ }
+
+ assert type.isClassType();
+
+ // Check if this is a library class with a toString() method that does not have side effects.
+ DexType classType = type.asClassType().getClassType();
+ DexMethod toStringMethodReference =
+ dexItemFactory.objectMembers.toString.withHolder(classType, dexItemFactory);
+ if (appView
+ .getLibraryMethodSideEffectModelCollection()
+ .isSideEffectFreeFinalMethod(toStringMethodReference, arguments)) {
+ return false;
+ }
+
+ if (appView.appInfo().hasLiveness()) {
+ AppInfoWithLiveness appInfo = appView.appInfo().withLiveness();
+
+ // Check if there is an -assumenosideeffects rule for the toString() method.
+ if (appInfo.isAssumeNoSideEffectsMethod(toStringMethodReference)) {
+ return false;
+ }
+
+ // Check if this is a program class with a toString() method that does not have side effects.
+ DexClass clazz = appInfo.definitionFor(classType);
+ if (clazz != null && clazz.isEffectivelyFinal(appView)) {
+ SingleResolutionResult resolutionResult =
+ appInfo.resolveMethodOn(clazz, toStringMethodReference).asSingleResolution();
+ if (resolutionResult != null
+ && !resolutionResult.getResolvedMethod().getOptimizationInfo().mayHaveSideEffects()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index af2dee2..3c6c003 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -299,10 +299,6 @@
// Find Class#get*Name() with a constant-class and replace it with a const-string if possible.
public void rewriteClassGetName(AppView<?> appView, IRCode code) {
- // Conflict with {@link CodeRewriter#collectClassInitializerDefaults}.
- if (code.method().isClassInitializer()) {
- return;
- }
Set<Value> affectedValues = Sets.newIdentityHashSet();
InstructionListIterator it = code.instructionListIterator();
while (it.hasNext()) {
@@ -363,11 +359,16 @@
// while its name is used to compute hash code, which won't be optimized, it's better not to
// compute the name.
if (!appView.options().testing.forceNameReflectionOptimization) {
- EscapeAnalysis escapeAnalysis =
- new EscapeAnalysis(appView, StringOptimizerEscapeAnalysisConfiguration.getInstance());
- if (mayBeRenamed || escapeAnalysis.isEscaping(code, out)) {
+ if (mayBeRenamed) {
continue;
}
+ if (invokedMethod != factory.classMethods.getSimpleName) {
+ EscapeAnalysis escapeAnalysis =
+ new EscapeAnalysis(appView, StringOptimizerEscapeAnalysisConfiguration.getInstance());
+ if (escapeAnalysis.isEscaping(code, out)) {
+ continue;
+ }
+ }
}
String descriptor = baseType.toDescriptorString();
diff --git a/src/test/java/com/android/tools/r8/R8TestRunResult.java b/src/test/java/com/android/tools/r8/R8TestRunResult.java
index bbd5836..028ed02 100644
--- a/src/test/java/com/android/tools/r8/R8TestRunResult.java
+++ b/src/test/java/com/android/tools/r8/R8TestRunResult.java
@@ -38,6 +38,11 @@
}
@Override
+ public boolean isR8TestRunResult() {
+ return true;
+ }
+
+ @Override
protected R8TestRunResult self() {
return this;
}
diff --git a/src/test/java/com/android/tools/r8/SingleTestRunResult.java b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
index 875e50d..d532547 100644
--- a/src/test/java/com/android/tools/r8/SingleTestRunResult.java
+++ b/src/test/java/com/android/tools/r8/SingleTestRunResult.java
@@ -34,6 +34,10 @@
this.result = result;
}
+ public boolean isR8TestRunResult() {
+ return false;
+ }
+
public AndroidApp app() {
return app;
}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
index 5852266..b96ee7f 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/NameThenLengthTest.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.optimize.string;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
@@ -22,7 +23,6 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
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;
@@ -93,17 +93,17 @@
}
private long countStringLength(MethodSubject method) {
- return Streams.stream(method.iterateInstructions(instructionSubject -> {
- if (instructionSubject.isInvoke()) {
- return isStringLength(instructionSubject.getMethod());
- }
- return false;
- })).count();
+ return method
+ .streamInstructions()
+ .filter(
+ instructionSubject ->
+ instructionSubject.isInvoke() && isStringLength(instructionSubject.getMethod()))
+ .count();
}
private long countNonZeroConstNumber(MethodSubject method) {
- return Streams.stream(method.iterateInstructions(InstructionSubject::isConstNumber)).count()
- - Streams.stream(method.iterateInstructions(instr -> instr.isConstNumber(0))).count();
+ return method.streamInstructions().filter(InstructionSubject::isConstNumber).count()
+ - method.streamInstructions().filter(instr -> instr.isConstNumber(0)).count();
}
private void test(
@@ -117,9 +117,13 @@
ClassSubject mainClass = codeInspector.clazz(MAIN);
MethodSubject clinit = mainClass.clinit();
- assertThat(clinit, isPresent());
- assertEquals(expectedStringLengthCountInClinit, countStringLength(clinit));
- assertEquals(expectedConstNumberCountInClinit, countNonZeroConstNumber(clinit));
+ if (result.isR8TestRunResult()) {
+ assertThat(clinit, isAbsent());
+ } else {
+ assertThat(clinit, isPresent());
+ assertEquals(expectedStringLengthCountInClinit, countStringLength(clinit));
+ assertEquals(expectedConstNumberCountInClinit, countNonZeroConstNumber(clinit));
+ }
MethodSubject m = mainClass.uniqueMethodWithName("instanceMethod");
assertThat(m, isPresent());
@@ -154,7 +158,7 @@
.run(parameters.getRuntime(), MAIN)
.assertSuccessWithOutput(JAVA_OUTPUT);
// TODO(b/125303292): NAME_LENGTH is still not computed at compile time.
- test(result, 1, 0, 0, 1);
+ test(result, 0, 0, 0, 1);
}
@Test
@@ -171,7 +175,6 @@
.assertSuccessWithOutput(JAVA_OUTPUT);
// No canonicalization in CF.
int expectedConstNumber = parameters.isCfRuntime() ? 2 : 1;
- // TODO(b/125303292): NAME_LENGTH is still not computed at compile time.
- test(result, 1, 0, 0, expectedConstNumber);
+ test(result, 0, 0, 0, expectedConstNumber);
}
}