Improve unused toString() removal
Change-Id: I6db043d4cd781c2a587fc062f9f881b3c4a16a4b
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 6bedacf..531c78a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -122,9 +122,9 @@
this.callSiteOptimizationInfoPropagator = null;
}
- this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
this.libraryMethodSideEffectModelCollection =
new LibraryMethodSideEffectModelCollection(dexItemFactory());
+ this.libraryMemberOptimizer = new LibraryMemberOptimizer(this);
if (enableWholeProgramOptimizations() && options().protoShrinking().isProtoShrinkingEnabled()) {
this.protoShrinker = new ProtoShrinker(withLiveness());
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 a93000e..385fb58 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -23,7 +23,6 @@
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.ReferenceTypeElement;
import com.android.tools.r8.ir.analysis.type.TypeElement;
-import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.Position;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.desugar.NestBasedAccessDesugaring;
@@ -453,13 +452,16 @@
public final StringBuildingMethods stringBufferMethods =
new StringBuildingMethods(stringBufferType);
public final BooleanMembers booleanMembers = new BooleanMembers();
+ public final ByteMembers byteMembers = new ByteMembers();
+ public final CharMembers charMembers = new CharMembers();
public final FloatMembers floatMembers = new FloatMembers();
public final IntegerMembers integerMembers = new IntegerMembers();
+ public final LongMembers longMembers = new LongMembers();
public final ObjectsMethods objectsMethods = new ObjectsMethods();
public final ObjectMembers objectMembers = new ObjectMembers();
+ public final ShortMembers shortMembers = new ShortMembers();
public final StringMembers stringMembers = new StringMembers();
- public final LongMembers longMembers = new LongMembers();
- public final DoubleMethods doubleMethods = new DoubleMethods();
+ public final DoubleMembers doubleMembers = new DoubleMembers();
public final ThrowableMethods throwableMethods = new ThrowableMethods();
public final AssertionErrorMethods assertionErrorMethods = new AssertionErrorMethods();
public final ClassMethods classMethods = new ClassMethods();
@@ -715,7 +717,22 @@
public Set<DexType> libraryClassesWithoutStaticInitialization =
ImmutableSet.of(
- boxedBooleanType, enumType, npeType, objectType, stringBufferType, stringBuilderType);
+ boxedBooleanType,
+ boxedByteType,
+ boxedCharType,
+ boxedDoubleType,
+ boxedFloatType,
+ boxedIntType,
+ boxedLongType,
+ boxedNumberType,
+ boxedShortType,
+ boxedVoidType,
+ enumType,
+ npeType,
+ objectType,
+ stringBufferType,
+ stringBuilderType,
+ stringType);
private boolean skipNameValidationForTesting = false;
@@ -731,35 +748,12 @@
return dexMethod == metafactoryMethod || dexMethod == metafactoryAltMethod;
}
- public interface LibraryMembers {
+ public abstract static class LibraryMembers {
- void forEachFinalField(Consumer<DexField> consumer);
+ public void forEachFinalField(Consumer<DexField> consumer) {}
}
- public class BooleanMembers implements LibraryMembers {
-
- public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
- public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
- public final DexField TYPE = createField(boxedBooleanType, classType, "TYPE");
-
- public final DexMethod booleanValue =
- createMethod(boxedBooleanType, createProto(booleanType), "booleanValue");
- public final DexMethod parseBoolean =
- createMethod(boxedBooleanType, createProto(booleanType, stringType), "parseBoolean");
- public final DexMethod valueOf =
- createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
-
- private BooleanMembers() {}
-
- @Override
- public void forEachFinalField(Consumer<DexField> consumer) {
- consumer.accept(FALSE);
- consumer.accept(TRUE);
- consumer.accept(TYPE);
- }
- }
-
- public class AndroidOsBuildMembers implements LibraryMembers {
+ public class AndroidOsBuildMembers extends LibraryMembers {
public final DexField BOOTLOADER = createField(androidOsBuildType, stringType, "BOOTLOADER");
public final DexField BRAND = createField(androidOsBuildType, stringType, "BRAND");
@@ -805,7 +799,7 @@
}
}
- public class AndroidOsBuildVersionMembers implements LibraryMembers {
+ public class AndroidOsBuildVersionMembers extends LibraryMembers {
public final DexField CODENAME = createField(androidOsBuildVersionType, stringType, "CODENAME");
public final DexField RELEASE = createField(androidOsBuildVersionType, stringType, "RELEASE");
@@ -824,7 +818,7 @@
}
}
- public class AndroidOsBundleMembers implements LibraryMembers {
+ public class AndroidOsBundleMembers extends LibraryMembers {
public final DexField CREATOR =
createField(androidOsBundleType, androidOsParcelableCreatorType, "CREATOR");
@@ -837,7 +831,7 @@
}
}
- public class AndroidSystemOsConstantsMembers implements LibraryMembers {
+ public class AndroidSystemOsConstantsMembers extends LibraryMembers {
public final DexField S_IRUSR = createField(androidSystemOsConstantsType, intType, "S_IRUSR");
public final DexField S_IXUSR = createField(androidSystemOsConstantsType, intType, "S_IXUSR");
@@ -849,7 +843,7 @@
}
}
- public class AndroidViewViewMembers implements LibraryMembers {
+ public class AndroidViewViewMembers extends LibraryMembers {
public final DexField TRANSLATION_Z =
createField(androidViewViewType, androidUtilPropertyType, "TRANSLATION_Z");
@@ -872,10 +866,54 @@
}
}
- public class FloatMembers implements LibraryMembers {
+ public class BooleanMembers extends LibraryMembers {
+
+ public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
+ public final DexField TRUE = createField(boxedBooleanType, boxedBooleanType, "TRUE");
+ public final DexField TYPE = createField(boxedBooleanType, classType, "TYPE");
+
+ public final DexMethod booleanValue =
+ createMethod(boxedBooleanType, createProto(booleanType), "booleanValue");
+ public final DexMethod parseBoolean =
+ createMethod(boxedBooleanType, createProto(booleanType, stringType), "parseBoolean");
+ public final DexMethod valueOf =
+ createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
+ public final DexMethod toString =
+ createMethod(boxedBooleanType, createProto(stringType), "toString");
+
+ private BooleanMembers() {}
+
+ @Override
+ public void forEachFinalField(Consumer<DexField> consumer) {
+ consumer.accept(FALSE);
+ consumer.accept(TRUE);
+ consumer.accept(TYPE);
+ }
+ }
+
+ public class ByteMembers extends LibraryMembers {
+
+ public final DexMethod toString =
+ createMethod(boxedByteType, createProto(stringType), "toString");
+
+ private ByteMembers() {}
+ }
+
+ public class CharMembers extends LibraryMembers {
+
+ public final DexMethod toString =
+ createMethod(boxedCharType, createProto(stringType), "toString");
+
+ private CharMembers() {}
+ }
+
+ public class FloatMembers extends LibraryMembers {
public final DexField TYPE = createField(boxedFloatType, classType, "TYPE");
+ public final DexMethod toString =
+ createMethod(boxedFloatType, createProto(stringType), "toString");
+
private FloatMembers() {}
@Override
@@ -884,7 +922,7 @@
}
}
- public class JavaIoFileMembers implements LibraryMembers {
+ public class JavaIoFileMembers extends LibraryMembers {
public final DexField pathSeparator = createField(javaIoFileType, stringType, "pathSeparator");
public final DexField separator = createField(javaIoFileType, stringType, "separator");
@@ -896,7 +934,7 @@
}
}
- public class JavaMathBigIntegerMembers implements LibraryMembers {
+ public class JavaMathBigIntegerMembers extends LibraryMembers {
public final DexField ONE = createField(javaMathBigIntegerType, javaMathBigIntegerType, "ONE");
public final DexField ZERO =
@@ -909,7 +947,7 @@
}
}
- public class JavaNioByteOrderMembers implements LibraryMembers {
+ public class JavaNioByteOrderMembers extends LibraryMembers {
public final DexField LITTLE_ENDIAN =
createField(javaNioByteOrderType, javaNioByteOrderType, "LITTLE_ENDIAN");
@@ -937,7 +975,7 @@
}
}
- public class JavaUtilComparatorMembers implements LibraryMembers {
+ public class JavaUtilComparatorMembers extends LibraryMembers {
public final DexField EMPTY_LIST =
createField(javaUtilCollectionsType, javaUtilListType, "EMPTY_LIST");
@@ -951,7 +989,7 @@
}
}
- public class JavaUtilConcurrentTimeUnitMembers implements LibraryMembers {
+ public class JavaUtilConcurrentTimeUnitMembers extends LibraryMembers {
public final DexField DAYS =
createField(javaUtilConcurrentTimeUnitType, javaUtilConcurrentTimeUnitType, "DAYS");
@@ -980,7 +1018,7 @@
}
}
- public class JavaUtilLocaleMembers implements LibraryMembers {
+ public class JavaUtilLocaleMembers extends LibraryMembers {
public final DexField ENGLISH = createField(javaUtilLocaleType, javaUtilLocaleType, "ENGLISH");
public final DexField ROOT = createField(javaUtilLocaleType, javaUtilLocaleType, "ROOT");
@@ -994,7 +1032,7 @@
}
}
- public class JavaUtilLoggingLevelMembers implements LibraryMembers {
+ public class JavaUtilLoggingLevelMembers extends LibraryMembers {
public final DexField CONFIG =
createField(javaUtilLoggingLevelType, javaUtilLoggingLevelType, "CONFIG");
@@ -1020,11 +1058,13 @@
}
}
- public class LongMembers implements LibraryMembers {
+ public class LongMembers extends LibraryMembers {
public final DexField TYPE = createField(boxedLongType, classType, "TYPE");
public final DexMethod compare;
+ public final DexMethod toString =
+ createMethod(boxedLongType, createProto(stringType), "toString");
private LongMembers() {
compare = createMethod(boxedLongDescriptor,
@@ -1037,11 +1077,14 @@
}
}
- public class DoubleMethods {
+ public class DoubleMembers {
public final DexMethod isNaN;
- private DoubleMethods() {
+ public final DexMethod toString =
+ createMethod(boxedDoubleType, createProto(stringType), "toString");
+
+ private DoubleMembers() {
isNaN =
createMethod(
boxedDoubleDescriptor,
@@ -1051,10 +1094,13 @@
}
}
- public class IntegerMembers implements LibraryMembers {
+ public class IntegerMembers extends LibraryMembers {
public final DexField TYPE = createField(boxedIntType, classType, "TYPE");
+ public final DexMethod toString =
+ createMethod(boxedIntType, createProto(stringType), "toString");
+
@Override
public void forEachFinalField(Consumer<DexField> consumer) {
consumer.accept(TYPE);
@@ -1445,7 +1491,15 @@
}
}
- public class StringMembers implements LibraryMembers {
+ public class ShortMembers extends LibraryMembers {
+
+ public final DexMethod toString =
+ createMethod(boxedShortType, createProto(stringType), "toString");
+
+ private ShortMembers() {}
+ }
+
+ public class StringMembers extends LibraryMembers {
public final DexField CASE_INSENSITIVE_ORDER =
createField(stringType, javaUtilComparatorType, "CASE_INSENSITIVE_ORDER");
@@ -1454,6 +1508,8 @@
public final DexMethod length;
public final DexMethod concat;
+ public final DexMethod constructor =
+ createMethod(stringType, createProto(voidType, stringType), constructorMethodName);
public final DexMethod contains;
public final DexMethod startsWith;
public final DexMethod endsWith;
@@ -1625,22 +1681,22 @@
return constructorMethods.contains(method);
}
- public boolean constructorInvokeIsSideEffectFree(InvokeMethod invoke) {
- DexMethod invokedMethod = invoke.getInvokedMethod();
- if (invokedMethod == charSequenceConstructor) {
- // Performs callbacks on the given CharSequence, which may have side effects.
- TypeElement charSequenceType = invoke.getArgument(1).getType();
- return charSequenceType.isClassType()
- && charSequenceType.asClassType().getClassType() == stringType;
- }
-
+ public boolean constructorInvokeIsSideEffectFree(
+ DexMethod invokedMethod, List<Value> arguments) {
if (invokedMethod == defaultConstructor) {
return true;
}
+ if (invokedMethod == charSequenceConstructor) {
+ // Performs callbacks on the given CharSequence, which may have side effects.
+ TypeElement charSequenceType = arguments.get(1).getType();
+ return charSequenceType.isClassType()
+ && charSequenceType.asClassType().getClassType() == stringType;
+ }
+
if (invokedMethod == intConstructor) {
// NegativeArraySizeException - if the capacity argument is less than 0.
- Value capacityValue = invoke.inValues().get(1);
+ Value capacityValue = arguments.get(1);
if (capacityValue.hasValueRange()) {
return capacityValue.getValueRange().getMin() >= 0;
}
@@ -1649,7 +1705,7 @@
if (invokedMethod == stringConstructor) {
// NullPointerException - if str is null.
- Value strValue = invoke.inValues().get(1);
+ Value strValue = arguments.get(1);
return !strValue.getType().isNullable();
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
index 095333f..a2c8d57 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeElement.java
@@ -274,6 +274,15 @@
return false;
}
+ public final boolean isClassType(DexType type) {
+ assert type.isClassType();
+ return isClassType() && asClassType().getClassType() == type;
+ }
+
+ public final boolean isStringType(DexItemFactory dexItemFactory) {
+ return isClassType(dexItemFactory.stringType);
+ }
+
public ClassTypeElement asClassType() {
return null;
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 7fc0d77..e8ba6bd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -129,6 +129,10 @@
return outValue != null;
}
+ public boolean hasUnusedOutValue() {
+ return !hasOutValue() || !outValue().hasAnyUsers();
+ }
+
public Value outValue() {
return outValue;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 520a408..fabcd3e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -2460,13 +2460,35 @@
assert code.isConsistentSSA();
}
+ static class ControlFlowSimplificationResult {
+ private boolean anyAffectedValues;
+ private boolean anySimplifications;
+
+ private ControlFlowSimplificationResult(boolean anyAffectedValues, boolean anySimplifications) {
+ assert !anyAffectedValues || anySimplifications;
+ this.anyAffectedValues = anyAffectedValues;
+ this.anySimplifications = anySimplifications;
+ }
+
+ public boolean anyAffectedValues() {
+ return anyAffectedValues;
+ }
+
+ public boolean anySimplifications() {
+ return anySimplifications;
+ }
+ }
+
public boolean simplifyControlFlow(IRCode code) {
boolean anyAffectedValues = rewriteSwitch(code);
- anyAffectedValues |= simplifyIf(code);
+ anyAffectedValues |= simplifyIf(code).anyAffectedValues();
return anyAffectedValues;
}
- public boolean simplifyIf(IRCode code) {
+ public ControlFlowSimplificationResult simplifyIf(IRCode code) {
+ BasicBlockBehavioralSubsumption behavioralSubsumption =
+ new BasicBlockBehavioralSubsumption(appView, code);
+ boolean simplified = false;
for (BasicBlock block : code.blocks) {
// Skip removed (= unreachable) blocks.
if (block.getNumber() != 0 && block.getPredecessors().isEmpty()) {
@@ -2474,114 +2496,34 @@
}
if (block.exit().isIf()) {
flipIfBranchesIfNeeded(code, block);
- rewriteIfWithConstZero(code, block);
+ if (rewriteIfWithConstZero(code, block)) {
+ simplified = true;
+ }
if (simplifyKnownBooleanCondition(code, block)) {
+ simplified = true;
continue;
}
// Simplify if conditions when possible.
If theIf = block.exit().asIf();
-
if (theIf.isZeroTest()) {
- simplifyIfZeroTest(code, block, theIf);
- continue;
+ if (simplifyIfZeroTest(code, block, theIf)) {
+ simplified = true;
+ continue;
+ }
+ } else {
+ if (simplifyNonIfZeroTest(code, block, theIf)) {
+ simplified = true;
+ continue;
+ }
}
- Value lhs = theIf.lhs();
- Value lhsRoot = lhs.getAliasedValue();
- Value rhs = theIf.rhs();
- Value rhsRoot = rhs.getAliasedValue();
-
- if (lhsRoot == rhsRoot) {
- // Comparing the same value.
- simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(0));
- } else if (lhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)
- && rhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)) {
- // Comparing two newly created objects.
- assert theIf.getType() == Type.EQ || theIf.getType() == Type.NE;
- simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
- } else if (lhsRoot.isConstNumber() && rhsRoot.isConstNumber()) {
- // Zero test with a constant of comparison between between two constants.
- ConstNumber left = lhsRoot.getConstInstruction().asConstNumber();
- ConstNumber right = rhsRoot.getConstInstruction().asConstNumber();
- BasicBlock target = theIf.targetFromCondition(left, right);
- simplifyIfWithKnownCondition(code, block, theIf, target);
- } else if (lhs.hasValueRange() && rhs.hasValueRange()) {
- // Zero test with a value range, or comparison between between two values,
- // each with a value ranges.
- LongInterval leftRange = lhs.getValueRange();
- LongInterval rightRange = rhs.getValueRange();
- // Two overlapping ranges. Check for single point overlap.
- if (!leftRange.overlapsWith(rightRange)) {
- // No overlap.
- int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
- simplifyIfWithKnownCondition(code, block, theIf, cond);
- } else {
- // The two intervals overlap. We can simplify if they overlap at the end points.
- switch (theIf.getType()) {
- case LT:
- case GE:
- // [a, b] < [c, d] is always false when a == d.
- // [a, b] >= [c, d] is always true when a == d.
- // In both cases 0 condition will choose the right branch.
- if (leftRange.getMin() == rightRange.getMax()) {
- simplifyIfWithKnownCondition(code, block, theIf, 0);
- }
- break;
- case GT:
- case LE:
- // [a, b] > [c, d] is always false when b == c.
- // [a, b] <= [c, d] is always true when b == c.
- // In both cases 0 condition will choose the right branch.
- if (leftRange.getMax() == rightRange.getMin()) {
- simplifyIfWithKnownCondition(code, block, theIf, 0);
- }
- break;
- case EQ:
- case NE:
- // Since there is overlap EQ and NE cannot be determined.
- break;
- }
- }
- } else if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
- ProgramMethod context = code.context();
- AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
- if (abstractValue.isSingleConstClassValue()) {
- AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
- if (otherAbstractValue.isSingleConstClassValue()) {
- SingleConstClassValue singleConstClassValue = abstractValue.asSingleConstClassValue();
- SingleConstClassValue otherSingleConstClassValue =
- otherAbstractValue.asSingleConstClassValue();
- simplifyIfWithKnownCondition(
- code,
- block,
- theIf,
- BooleanUtils.intValue(
- singleConstClassValue.getType() != otherSingleConstClassValue.getType()));
- }
- } else if (abstractValue.isSingleFieldValue()) {
- AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
- if (otherAbstractValue.isSingleFieldValue()) {
- SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
- SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue();
- if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
- simplifyIfWithKnownCondition(code, block, theIf, 0);
- } else {
- DexClass holder = appView.definitionForHolder(singleFieldValue.getField());
- DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder);
- if (field != null && field.isEnum()) {
- DexClass otherHolder =
- appView.definitionForHolder(otherSingleFieldValue.getField());
- DexEncodedField otherField =
- otherSingleFieldValue.getField().lookupOnClass(otherHolder);
- if (otherField != null && otherField.isEnum()) {
- simplifyIfWithKnownCondition(code, block, theIf, 1);
- }
- }
- }
- }
- }
+ // Unable to determine which branch will be taken. Check if the true target can safely be
+ // rewritten to the false target.
+ if (behavioralSubsumption.isSubsumedBy(theIf.getTrueTarget(), theIf.fallthroughBlock())) {
+ simplifyIfWithKnownCondition(code, block, theIf, theIf.fallthroughBlock());
+ simplified = true;
}
}
}
@@ -2590,17 +2532,17 @@
new TypeAnalysis(appView).narrowing(affectedValues);
}
assert code.isConsistentSSA();
- return !affectedValues.isEmpty();
+ return new ControlFlowSimplificationResult(!affectedValues.isEmpty(), simplified);
}
- private void simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) {
+ private boolean simplifyIfZeroTest(IRCode code, BasicBlock block, If theIf) {
Value lhs = theIf.lhs();
Value lhsRoot = lhs.getAliasedValue();
if (lhsRoot.isConstNumber()) {
ConstNumber cond = lhsRoot.getConstInstruction().asConstNumber();
BasicBlock target = theIf.targetFromCondition(cond);
simplifyIfWithKnownCondition(code, block, theIf, target);
- return;
+ return true;
}
if (theIf.isNullTest()) {
@@ -2608,12 +2550,12 @@
if (lhs.isAlwaysNull(appView)) {
simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNullObject());
- return;
+ return true;
}
if (lhs.isNeverNull()) {
simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromNonNullObject());
- return;
+ return true;
}
}
@@ -2623,7 +2565,7 @@
// Interval doesn't contain zero at all.
int sign = Long.signum(interval.getMin());
simplifyIfWithKnownCondition(code, block, theIf, sign);
- return;
+ return true;
}
// Interval contains zero.
@@ -2635,7 +2577,7 @@
// In both cases a zero condition takes the right branch.
if (interval.getMin() == 0) {
simplifyIfWithKnownCondition(code, block, theIf, 0);
- return;
+ return true;
}
break;
@@ -2646,7 +2588,7 @@
// In both cases a zero condition takes the right branch.
if (interval.getMax() == 0) {
simplifyIfWithKnownCondition(code, block, theIf, 0);
- return;
+ return true;
}
break;
@@ -2658,6 +2600,125 @@
break;
}
}
+ return false;
+ }
+
+ private boolean simplifyNonIfZeroTest(IRCode code, BasicBlock block, If theIf) {
+ Value lhs = theIf.lhs();
+ Value lhsRoot = lhs.getAliasedValue();
+ Value rhs = theIf.rhs();
+ Value rhsRoot = rhs.getAliasedValue();
+ if (lhsRoot == rhsRoot) {
+ // Comparing the same value.
+ simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(0));
+ return true;
+ }
+
+ if (lhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)
+ && rhsRoot.isDefinedByInstructionSatisfying(Instruction::isCreatingInstanceOrArray)) {
+ // Comparing two newly created objects.
+ assert theIf.getType() == Type.EQ || theIf.getType() == Type.NE;
+ simplifyIfWithKnownCondition(code, block, theIf, theIf.targetFromCondition(1));
+ return true;
+ }
+
+ if (lhsRoot.isConstNumber() && rhsRoot.isConstNumber()) {
+ // Zero test with a constant of comparison between between two constants.
+ ConstNumber left = lhsRoot.getConstInstruction().asConstNumber();
+ ConstNumber right = rhsRoot.getConstInstruction().asConstNumber();
+ BasicBlock target = theIf.targetFromCondition(left, right);
+ simplifyIfWithKnownCondition(code, block, theIf, target);
+ return true;
+ }
+
+ if (lhs.hasValueRange() && rhs.hasValueRange()) {
+ // Zero test with a value range, or comparison between between two values,
+ // each with a value ranges.
+ LongInterval leftRange = lhs.getValueRange();
+ LongInterval rightRange = rhs.getValueRange();
+ // Two overlapping ranges. Check for single point overlap.
+ if (!leftRange.overlapsWith(rightRange)) {
+ // No overlap.
+ int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
+ simplifyIfWithKnownCondition(code, block, theIf, cond);
+ return true;
+ }
+
+ // The two intervals overlap. We can simplify if they overlap at the end points.
+ switch (theIf.getType()) {
+ case LT:
+ case GE:
+ // [a, b] < [c, d] is always false when a == d.
+ // [a, b] >= [c, d] is always true when a == d.
+ // In both cases 0 condition will choose the right branch.
+ if (leftRange.getMin() == rightRange.getMax()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 0);
+ return true;
+ }
+ break;
+ case GT:
+ case LE:
+ // [a, b] > [c, d] is always false when b == c.
+ // [a, b] <= [c, d] is always true when b == c.
+ // In both cases 0 condition will choose the right branch.
+ if (leftRange.getMax() == rightRange.getMin()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 0);
+ return true;
+ }
+ break;
+ case EQ:
+ case NE:
+ // Since there is overlap EQ and NE cannot be determined.
+ break;
+ }
+ }
+
+ if (theIf.getType() == Type.EQ || theIf.getType() == Type.NE) {
+ ProgramMethod context = code.context();
+ AbstractValue abstractValue = lhs.getAbstractValue(appView, context);
+ if (abstractValue.isSingleConstClassValue()) {
+ AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
+ if (otherAbstractValue.isSingleConstClassValue()) {
+ SingleConstClassValue singleConstClassValue = abstractValue.asSingleConstClassValue();
+ SingleConstClassValue otherSingleConstClassValue =
+ otherAbstractValue.asSingleConstClassValue();
+ simplifyIfWithKnownCondition(
+ code,
+ block,
+ theIf,
+ BooleanUtils.intValue(
+ singleConstClassValue.getType() != otherSingleConstClassValue.getType()));
+ return true;
+ }
+ return false;
+ }
+
+ if (abstractValue.isSingleFieldValue()) {
+ AbstractValue otherAbstractValue = rhs.getAbstractValue(appView, context);
+ if (otherAbstractValue.isSingleFieldValue()) {
+ SingleFieldValue singleFieldValue = abstractValue.asSingleFieldValue();
+ SingleFieldValue otherSingleFieldValue = otherAbstractValue.asSingleFieldValue();
+ if (singleFieldValue.getField() == otherSingleFieldValue.getField()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 0);
+ return true;
+ }
+
+ DexClass holder = appView.definitionForHolder(singleFieldValue.getField());
+ DexEncodedField field = singleFieldValue.getField().lookupOnClass(holder);
+ if (field != null && field.isEnum()) {
+ DexClass otherHolder = appView.definitionForHolder(otherSingleFieldValue.getField());
+ DexEncodedField otherField =
+ otherSingleFieldValue.getField().lookupOnClass(otherHolder);
+ if (otherField != null && otherField.isEnum()) {
+ simplifyIfWithKnownCondition(code, block, theIf, 1);
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return false;
}
private void simplifyIfWithKnownCondition(
@@ -3181,30 +3242,31 @@
assert block.exit().asGoto().getTarget() == target;
}
- private void rewriteIfWithConstZero(IRCode code, BasicBlock block) {
+ private boolean rewriteIfWithConstZero(IRCode code, BasicBlock block) {
If theIf = block.exit().asIf();
if (theIf.isZeroTest()) {
- return;
+ return false;
}
- List<Value> inValues = theIf.inValues();
- Value leftValue = inValues.get(0);
- Value rightValue = inValues.get(1);
+ Value leftValue = theIf.lhs();
+ Value rightValue = theIf.rhs();
if (leftValue.isConstNumber() || rightValue.isConstNumber()) {
if (leftValue.isConstNumber()) {
if (leftValue.getConstInstruction().asConstNumber().isZero()) {
If ifz = new If(theIf.getType().forSwappedOperands(), rightValue);
block.replaceLastInstruction(ifz, code);
assert block.exit() == ifz;
+ return true;
}
- } else {
- if (rightValue.getConstInstruction().asConstNumber().isZero()) {
- If ifz = new If(theIf.getType(), leftValue);
- block.replaceLastInstruction(ifz, code);
- assert block.exit() == ifz;
- }
+ } else if (rightValue.getConstInstruction().asConstNumber().isZero()) {
+ If ifz = new If(theIf.getType(), leftValue);
+ block.replaceLastInstruction(ifz, code);
+ assert block.exit() == ifz;
+ return true;
}
}
+
+ return false;
}
private boolean flipIfBranchesIfNeeded(IRCode code, BasicBlock block) {
@@ -3736,7 +3798,7 @@
&& value.definition.asNumberConversion().to == NumericType.DOUBLE) {
InvokeStatic invokeIsNaN =
new InvokeStatic(
- dexItemFactory.doubleMethods.isNaN, null, ImmutableList.of(value));
+ dexItemFactory.doubleMembers.isNaN, null, ImmutableList.of(value));
invokeIsNaN.setPosition(instruction.getPosition());
// Insert the invoke before the current instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 26aa681..0ecfc91 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -53,7 +53,8 @@
removeDeadInstructions(worklist, code, block);
removeDeadPhis(worklist, code, block);
}
- } while (removeUnneededCatchHandlers(code));
+ } while (codeRewriter.simplifyIf(code).anySimplifications()
+ || removeUnneededCatchHandlers(code));
assert code.isConsistentSSA();
timing.end();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
index ed477a6..e62b2c1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/OptimizationFeedbackSimple.java
@@ -122,7 +122,7 @@
@Override
public void methodMayNotHaveSideEffects(DexEncodedMethod method) {
- // Ignored.
+ method.getMutableOptimizationInfo().markMayNotHaveSideEffects();
}
@Override
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 25916f9..01c4cf7 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
@@ -135,6 +135,13 @@
continue;
}
+ if (invoke.hasUnusedOutValue()
+ && !singleTarget.getDefinition().isInstanceInitializer()
+ && !invoke.instructionMayHaveSideEffects(appView, code.context())) {
+ instructionIterator.removeOrReplaceByDebugLocalRead();
+ continue;
+ }
+
LibraryMethodModelCollection.State optimizationState =
optimizationStates.computeIfAbsent(
optimizer,
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 70ace2b..071108d 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,41 +4,41 @@
package com.android.tools.r8.ir.optimize.library;
-import static com.google.common.base.Predicates.alwaysFalse;
-import static com.google.common.base.Predicates.alwaysTrue;
-
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.utils.BiPredicateUtils;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.function.Predicate;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
public class LibraryMethodSideEffectModelCollection {
- private final Map<DexMethod, Predicate<InvokeMethod>> finalMethodsWithoutSideEffects;
+ private final Map<DexMethod, BiPredicate<DexMethod, List<Value>>> finalMethodsWithoutSideEffects;
+ private final Set<DexMethod> unconditionalFinalMethodsWithoutSideEffects;
+
private final Set<DexMethod> nonFinalMethodsWithoutSideEffects;
public LibraryMethodSideEffectModelCollection(DexItemFactory dexItemFactory) {
finalMethodsWithoutSideEffects = buildFinalMethodsWithoutSideEffects(dexItemFactory);
+ unconditionalFinalMethodsWithoutSideEffects =
+ buildUnconditionalFinalMethodsWithoutSideEffects(dexItemFactory);
nonFinalMethodsWithoutSideEffects = buildNonFinalMethodsWithoutSideEffects(dexItemFactory);
}
- private static Map<DexMethod, Predicate<InvokeMethod>> buildFinalMethodsWithoutSideEffects(
- DexItemFactory dexItemFactory) {
- ImmutableMap.Builder<DexMethod, Predicate<InvokeMethod>> builder =
- ImmutableMap.<DexMethod, Predicate<InvokeMethod>>builder()
- .put(dexItemFactory.enumMembers.constructor, alwaysTrue())
- .put(dexItemFactory.npeMethods.init, alwaysTrue())
- .put(dexItemFactory.npeMethods.initWithMessage, alwaysTrue())
- .put(dexItemFactory.objectMembers.constructor, alwaysTrue())
- .put(dexItemFactory.objectMembers.getClass, alwaysTrue())
- .put(dexItemFactory.stringBuilderMethods.toString, alwaysTrue())
- .put(dexItemFactory.stringMembers.hashCode, alwaysTrue());
- putAll(builder, dexItemFactory.classMethods.getNames, alwaysTrue());
+ private static Map<DexMethod, BiPredicate<DexMethod, List<Value>>>
+ buildFinalMethodsWithoutSideEffects(DexItemFactory dexItemFactory) {
+ ImmutableMap.Builder<DexMethod, BiPredicate<DexMethod, List<Value>>> builder =
+ ImmutableMap.<DexMethod, BiPredicate<DexMethod, List<Value>>>builder()
+ .put(
+ dexItemFactory.stringMembers.constructor,
+ (method, arguments) -> arguments.get(0).isNeverNull());
putAll(
builder,
dexItemFactory.stringBufferMethods.constructorMethods,
@@ -47,10 +47,34 @@
builder,
dexItemFactory.stringBuilderMethods.constructorMethods,
dexItemFactory.stringBuilderMethods::constructorInvokeIsSideEffectFree);
- putAll(builder, dexItemFactory.boxedValueOfMethods(), alwaysTrue());
return builder.build();
}
+ private static Set<DexMethod> buildUnconditionalFinalMethodsWithoutSideEffects(
+ DexItemFactory dexItemFactory) {
+ return ImmutableSet.<DexMethod>builder()
+ .add(dexItemFactory.booleanMembers.toString)
+ .add(dexItemFactory.byteMembers.toString)
+ .add(dexItemFactory.charMembers.toString)
+ .add(dexItemFactory.doubleMembers.toString)
+ .add(dexItemFactory.enumMembers.constructor)
+ .add(dexItemFactory.floatMembers.toString)
+ .add(dexItemFactory.integerMembers.toString)
+ .add(dexItemFactory.longMembers.toString)
+ .add(dexItemFactory.npeMethods.init)
+ .add(dexItemFactory.npeMethods.initWithMessage)
+ .add(dexItemFactory.objectMembers.constructor)
+ .add(dexItemFactory.objectMembers.getClass)
+ .add(dexItemFactory.shortMembers.toString)
+ .add(dexItemFactory.stringBufferMethods.toString)
+ .add(dexItemFactory.stringBuilderMethods.toString)
+ .add(dexItemFactory.stringMembers.hashCode)
+ .add(dexItemFactory.stringMembers.toString)
+ .addAll(dexItemFactory.classMethods.getNames)
+ .addAll(dexItemFactory.boxedValueOfMethods())
+ .build();
+ }
+
private static Set<DexMethod> buildNonFinalMethodsWithoutSideEffects(
DexItemFactory dexItemFactory) {
return ImmutableSet.of(
@@ -59,25 +83,31 @@
dexItemFactory.objectMembers.toString);
}
- private static void putAll(
- ImmutableMap.Builder<DexMethod, Predicate<InvokeMethod>> builder,
- Iterable<DexMethod> methods,
- Predicate<InvokeMethod> predicate) {
- for (DexMethod method : methods) {
- builder.put(method, predicate);
+ private static <K, V> void putAll(ImmutableMap.Builder<K, V> builder, Iterable<K> keys, V value) {
+ for (K key : keys) {
+ builder.put(key, value);
}
}
+ public void forEachSideEffectFreeFinalMethod(Consumer<DexMethod> consumer) {
+ unconditionalFinalMethodsWithoutSideEffects.forEach(consumer);
+ }
+
public boolean isCallToSideEffectFreeFinalMethod(InvokeMethod invoke) {
- return finalMethodsWithoutSideEffects
- .getOrDefault(invoke.getInvokedMethod(), alwaysFalse())
- .test(invoke);
+ return isSideEffectFreeFinalMethod(invoke.getInvokedMethod(), invoke.arguments());
+ }
+
+ public boolean isSideEffectFreeFinalMethod(DexMethod method, List<Value> arguments) {
+ return unconditionalFinalMethodsWithoutSideEffects.contains(method)
+ || finalMethodsWithoutSideEffects
+ .getOrDefault(method, BiPredicateUtils.alwaysFalse())
+ .test(method, arguments);
}
// This intentionally takes the invoke instruction since the determination of whether a library
// method has side effects may depend on the arguments.
public boolean isSideEffectFree(InvokeMethod invoke, LibraryMethod singleTarget) {
- return isCallToSideEffectFreeFinalMethod(invoke)
+ return isSideEffectFreeFinalMethod(singleTarget.getReference(), invoke.arguments())
|| nonFinalMethodsWithoutSideEffects.contains(singleTarget.getReference());
}
}
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 9b41f18..ecbb3e4 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
@@ -47,6 +47,7 @@
modelStaticFinalLibraryFields(finalLibraryFields);
modelLibraryMethodsReturningNonNull();
modelLibraryMethodsReturningReceiver();
+ modelLibraryMethodsWithoutSideEffects();
modelRequireNonNullMethods();
}
@@ -113,6 +114,18 @@
}
}
+ private void modelLibraryMethodsWithoutSideEffects() {
+ appView
+ .getLibraryMethodSideEffectModelCollection()
+ .forEachSideEffectFreeFinalMethod(
+ method -> {
+ DexEncodedMethod definition = lookupMethod(method);
+ if (definition != null) {
+ feedback.methodMayNotHaveSideEffects(definition);
+ }
+ });
+ }
+
private void modelRequireNonNullMethods() {
for (DexMethod requireNonNullMethod : dexItemFactory.objectsMethods.requireNonNullMethods()) {
DexEncodedMethod definition = lookupMethod(requireNonNullMethod);
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 5fc8d62..a303082 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,15 +5,19 @@
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.utils.ValueUtils;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
import java.util.Set;
public class ObjectsMethodOptimizer extends StatelessLibraryMethodModelCollection {
@@ -67,9 +71,11 @@
InstructionListIterator instructionIterator,
InvokeMethod invoke,
Set<Value> affectedValues) {
- // Optimize Objects.toString(null) into "null".
Value object = invoke.getFirstArgument();
- if (object.getType().isDefinitelyNull()) {
+ TypeElement type = object.getType();
+
+ // Optimize Objects.toString(null) into "null".
+ if (type.isDefinitelyNull()) {
instructionIterator.replaceCurrentInstructionWithConstString(appView, code, "null");
if (invoke.hasOutValue()) {
affectedValues.addAll(invoke.outValue().affectedValues());
@@ -77,10 +83,50 @@
return;
}
- // Remove Objects.toString(stringBuilder) if unused.
- if (ValueUtils.isStringBuilder(invoke.getFirstArgument(), dexItemFactory)) {
- if (!invoke.hasOutValue() || !invoke.outValue().hasNonDebugUsers()) {
+ // Optimize Objects.toString(nonNullString) into nonNullString.
+ if (type.isDefinitelyNotNull() && type.isStringType(dexItemFactory)) {
+ if (invoke.hasOutValue()) {
+ affectedValues.addAll(invoke.outValue().affectedValues());
+ 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/utils/BiPredicateUtils.java b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
new file mode 100644
index 0000000..f470d35
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/BiPredicateUtils.java
@@ -0,0 +1,18 @@
+// 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.utils;
+
+import java.util.function.BiPredicate;
+
+public class BiPredicateUtils {
+
+ public static <S, T> BiPredicate<S, T> alwaysFalse() {
+ return (s, t) -> false;
+ }
+
+ public static <S, T> BiPredicate<S, T> alwaysTrue() {
+ return (s, t) -> true;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
index 4813749..27f097b 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/RedundantConstNumberRemovalTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.code.BasicBlock;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
@@ -39,6 +40,7 @@
return getTestParameters()
.withCfRuntimes()
.withDexRuntimesStartingFromExcluding(Version.V4_4_4)
+ .withApiLevelsStartingAtIncluding(AndroidApiLevel.L)
.build();
}
@@ -64,7 +66,7 @@
.enableInliningAnnotations()
.addOptionsModification(
internalOptions -> internalOptions.enableRedundantConstNumberOptimization = true)
- .setMinApi(parameters.getRuntime())
+ .setMinApi(parameters.getApiLevel())
.run(TestClass.class)
.assertSuccessWithOutput(expectedOutput);
@@ -112,15 +114,10 @@
assertEquals(1, code.blocks.size());
// The block only has three instructions.
BasicBlock entryBlock = code.entryBlock();
- assertEquals(3, entryBlock.getInstructions().size());
+ assertEquals(2, entryBlock.getInstructions().size());
// The first one is the `argument` instruction.
Instruction argument = entryBlock.getInstructions().getFirst();
assertTrue(argument.isArgument());
- // The next one is a `const-number` instruction is not used for anything.
- // TODO(christofferqa): D8 should be able to get rid of the unused const-number instruction.
- Instruction unused = entryBlock.getInstructions().get(1);
- assertTrue(unused.isConstNumber());
- assertEquals(0, unused.outValue().numberOfAllUsers());
// The `return` instruction returns the argument.
Instruction ret = entryBlock.getInstructions().getLast();
assertTrue(ret.isReturn());
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
index 8dcd7bc..0902756 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByConstantInOneConstructorTest.java
@@ -51,11 +51,7 @@
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
- if (parameters.isCfRuntime()) {
- assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
- } else {
- assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
- }
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
index 9bedfb1..c3a453d 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/membervaluepropagation/fields/FieldInitializedByDifferentConstantsInMultipleConstructorsTest.java
@@ -51,11 +51,7 @@
ClassSubject testClassSubject = inspector.clazz(TestClass.class);
assertThat(testClassSubject, isPresent());
assertThat(testClassSubject.uniqueMethodWithName("live"), isPresent());
- if (parameters.isCfRuntime()) {
- assertThat(testClassSubject.uniqueMethodWithName("dead"), isPresent());
- } else {
- assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
- }
+ assertThat(testClassSubject.uniqueMethodWithName("dead"), not(isPresent()));
}
static class TestClass {
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/ObjectsToStringOnLibraryClassTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/ObjectsToStringOnLibraryClassTest.java
new file mode 100644
index 0000000..3e852df
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/ObjectsToStringOnLibraryClassTest.java
@@ -0,0 +1,100 @@
+// 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.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Objects;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class ObjectsToStringOnLibraryClassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ObjectsToStringOnLibraryClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForR8(parameters.getBackend())
+ .addInnerClasses(getClass())
+ .addKeepMainRule(Main.class)
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(
+ inspector -> {
+ MethodSubject mainMethodSubject = inspector.clazz(Main.class).mainMethod();
+ assertThat(mainMethodSubject, isPresent());
+ assertTrue(
+ mainMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvoke)
+ .allMatch(
+ x ->
+ x.getMethod()
+ .getName()
+ .toSourceString()
+ .equals("currentTimeMillis")));
+ })
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithEmptyOutput();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ boolean unknown = System.currentTimeMillis() > 0;
+
+ // Boolean.
+ Objects.toString(unknown ? Boolean.FALSE : null);
+ Objects.toString(unknown ? Boolean.TRUE : null);
+
+ // Byte.
+ Objects.toString(unknown ? Byte.valueOf((byte) 0) : null);
+
+ // Char.
+ Objects.toString(unknown ? Character.valueOf((char) 0) : null);
+
+ // Double.
+ Objects.toString(unknown ? Double.valueOf(0) : null);
+
+ // Float.
+ Objects.toString(unknown ? Float.valueOf(0) : null);
+
+ // Integer.
+ Objects.toString(unknown ? Integer.valueOf(0) : null);
+
+ // Long.
+ Objects.toString(unknown ? Long.valueOf(0) : null);
+
+ // Short.
+ Objects.toString(unknown ? Short.valueOf((short) 0) : null);
+
+ // String.
+ Objects.toString(unknown ? "null" : null);
+ Objects.toString(unknown ? new String("null") : null);
+
+ // StringBuffer, StringBuilder.
+ Objects.toString(unknown ? new StringBuffer() : null);
+ Objects.toString(unknown ? new StringBuilder() : null);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index d9b3cd4..61387ea 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -91,31 +91,37 @@
assumeTrue(backend == Backend.DEX);
validateSequence(
methodThrowToBeInlined.iterateInstructions(),
+
// 'if' with "foo#1" is flipped.
InstructionSubject::isIfEqz,
// 'if' with "foo#2" is removed along with the constant.
- // 'if' with "foo#3" is removed so now we have unconditional call.
- insn -> insn.isConstString("StringConstants::foo#3", JumboStringMode.DISALLOW),
- InstructionSubject::isInvokeStatic,
- InstructionSubject::isThrow,
+ // 'if' with "foo#3" is removed so now we have an unconditional call inside the branch.
+ InstructionSubject::isIfEq,
- // 'if's with "foo#4" and "foo#5" are flipped, but their throwing branches
- // are not moved to the end of the code (area for improvement?).
+ // 'if' with "foo#4" is flipped, but the throwing branch is not moved to the end of the code
+ // (area for improvement?).
insn -> insn.isConstString("StringConstants::foo#4", JumboStringMode.DISALLOW),
InstructionSubject::isIfEqz, // Flipped if
InstructionSubject::isGoto, // Jump around throwing branch.
InstructionSubject::isInvokeStatic, // Throwing branch.
InstructionSubject::isThrow,
+
+ // 'if's with "foo#5" are flipped.
insn -> insn.isConstString("StringConstants::foo#5", JumboStringMode.DISALLOW),
InstructionSubject::isIfEqz, // Flipped if
InstructionSubject::isReturnVoid, // Final return statement.
InstructionSubject::isInvokeStatic, // Throwing branch.
InstructionSubject::isThrow,
- // After 'if' with "foo#1" flipped, always throwing branch
- // moved here along with the constant.
+ // 'if' with "foo#3" is removed so now we have an unconditional call.
+ insn -> insn.isConstString("StringConstants::foo#3", JumboStringMode.DISALLOW),
+ InstructionSubject::isInvokeStatic,
+ InstructionSubject::isThrow,
+
+ // After 'if' with "foo#1" flipped, the always throwing branch is moved here along with the
+ // constant.
insn -> insn.isConstString("StringConstants::foo#1", JumboStringMode.DISALLOW),
InstructionSubject::isInvokeStatic,
InstructionSubject::isThrow);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 4e6a3f5..3a9976f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -196,6 +196,11 @@
}
@Override
+ public boolean isIfEq() {
+ return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IF_ICMPEQ;
+ }
+
+ @Override
public boolean isIfEqz() {
return instruction instanceof CfIf && ((CfIf) instruction).getOpcode() == Opcodes.IFEQ;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index 26944f9..4b9f733 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -316,6 +316,11 @@
}
@Override
+ public boolean isIfEq() {
+ return instruction instanceof IfEq;
+ }
+
+ @Override
public boolean isIfEqz() {
return instruction instanceof IfEqz;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index e6e26fd..56436e9 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -75,6 +75,8 @@
boolean isIfNez();
+ boolean isIfEq();
+
boolean isIfEqz();
boolean isReturn();