Unbox enums through Objects#equals
Bug: b/185222995
Change-Id: Iafe49aacef449a1c7f6c7252ba73119c9a442131
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
index bea580f..4838202 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxerImpl.java
@@ -1560,6 +1560,9 @@
addRequiredNameData(enumClass);
return Reason.ELIGIBLE;
}
+ if (singleTargetReference == factory.objectsMethods.equals) {
+ return comparableAsUnboxedValues(invoke);
+ }
return new UnsupportedLibraryInvokeReason(singleTargetReference);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
index be89c4e..53e7b32 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingCfMethods.java
@@ -139,6 +139,38 @@
ImmutableList.of());
}
+ public static CfCode EnumUnboxingMethods_objectEquals(DexItemFactory factory, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 2,
+ 2,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.INT, 0),
+ new CfLoad(ValueType.INT, 1),
+ new CfIfCmp(IfType.NE, ValueType.INT, label1),
+ new CfConstNumber(1, ValueType.INT),
+ new CfGoto(label2),
+ label1,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(
+ new int[] {0, 1}, new FrameType[] {FrameType.intType(), FrameType.intType()})),
+ new CfConstNumber(0, ValueType.INT),
+ label2,
+ new CfFrame(
+ new Int2ObjectAVLTreeMap<>(
+ new int[] {0, 1}, new FrameType[] {FrameType.intType(), FrameType.intType()}),
+ new ArrayDeque<>(Arrays.asList(FrameType.intType()))),
+ new CfReturn(ValueType.INT),
+ label3),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
+
public static CfCode EnumUnboxingMethods_ordinal(DexItemFactory factory, DexMethod method) {
CfLabel label0 = new CfLabel();
CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
index a1f987b..05648c3 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingRewriter.java
@@ -501,6 +501,16 @@
}
} else if (invokedMethod == factory.objectsMethods.toStringWithObject) {
rewriteStringValueOf(invoke, context, convertedEnums, instructionIterator, eventConsumer);
+ } else if (invokedMethod == factory.objectsMethods.equals) {
+ assert invoke.arguments().size() == 2;
+ Value argument = invoke.getFirstArgument();
+ DexType enumType = getEnumClassTypeOrNull(argument, convertedEnums);
+ if (enumType != null) {
+ replaceEnumInvoke(
+ instructionIterator,
+ invoke,
+ getSharedUtilityClass().ensureObjectsEqualsMethod(appView, context, eventConsumer));
+ }
}
return;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
index 3792bd4..37c814d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/SharedEnumUnboxingUtilityClass.java
@@ -72,6 +72,7 @@
ensureCheckNotZeroWithMessageMethod(appView);
ensureCompareToMethod(appView);
ensureEqualsMethod(appView);
+ ensureObjectsEqualsMethod(appView);
ensureOrdinalMethod(appView);
}
@@ -151,6 +152,25 @@
method -> EnumUnboxingCfMethods.EnumUnboxingMethods_equals(dexItemFactory, method));
}
+ public ProgramMethod ensureObjectsEqualsMethod(
+ AppView<AppInfoWithLiveness> appView,
+ ProgramMethod context,
+ EnumUnboxerMethodProcessorEventConsumer eventConsumer) {
+ ProgramMethod method = ensureObjectsEqualsMethod(appView);
+ eventConsumer.acceptEnumUnboxerSharedUtilityClassMethodContext(method, context);
+ return method;
+ }
+
+ private ProgramMethod ensureObjectsEqualsMethod(AppView<AppInfoWithLiveness> appView) {
+ DexItemFactory dexItemFactory = appView.dexItemFactory();
+ return internalEnsureMethod(
+ appView,
+ dexItemFactory.createString("objects$equals"),
+ dexItemFactory.createProto(
+ dexItemFactory.booleanType, dexItemFactory.intType, dexItemFactory.intType),
+ method -> EnumUnboxingCfMethods.EnumUnboxingMethods_objectEquals(dexItemFactory, method));
+ }
+
public ProgramMethod ensureOrdinalMethod(
AppView<AppInfoWithLiveness> appView,
ProgramMethod context,
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
index cc2aa0c..48818ec 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EnumUnboxingMethods.java
@@ -52,6 +52,12 @@
return unboxedEnum1 == unboxedEnum2;
}
+ // Objects#equals is similar to equals without the NPE for null entries.
+ // Objects.equals(null,null) answers true.
+ public static boolean objectEquals(int unboxedEnum1, int unboxedEnum2) {
+ return unboxedEnum1 == unboxedEnum2;
+ }
+
// Methods zeroCheck and zeroCheckMessage are used to replace null checks on unboxed enums.
public static void zeroCheck(int unboxedEnum) {
if (unboxedEnum == 0) {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
index 82689d3..172e118 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/EqualsCompareToEnumUnboxingTest.java
@@ -5,8 +5,10 @@
package com.android.tools.r8.enumunboxing;
import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
import com.android.tools.r8.TestParameters;
import java.util.List;
+import java.util.Objects;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -40,6 +42,7 @@
.addEnumUnboxingInspector(
inspector -> inspector.assertUnboxed(EnumEqualscompareTo.MyEnum.class))
.enableNeverClassInliningAnnotations()
+ .enableInliningAnnotations()
.addKeepRules(enumKeepRules.getKeepRules())
.addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
.setMinApi(parameters)
@@ -59,9 +62,31 @@
public static void main(String[] args) {
equalsTest();
+ objectsEqualsTest();
compareToTest();
}
+ @NeverInline
+ @SuppressWarnings({"ConstantConditions", "EqualsWithItself", "ResultOfMethodCallIgnored"})
+ private static void objectsEqualsTest() {
+ System.out.println(performObjectsEquals(MyEnum.A, MyEnum.B));
+ System.out.println(false);
+ System.out.println(performObjectsEquals(MyEnum.A, MyEnum.A));
+ System.out.println(true);
+ System.out.println(performObjectsEquals(MyEnum.A, null));
+ System.out.println(false);
+ System.out.println(performObjectsEquals(null, MyEnum.A));
+ System.out.println(false);
+ System.out.println(performObjectsEquals(null, null));
+ System.out.println(true);
+ }
+
+ @NeverInline
+ private static boolean performObjectsEquals(MyEnum a, MyEnum b) {
+ return Objects.equals(a, b);
+ }
+
+ @NeverInline
@SuppressWarnings({"ConstantConditions", "EqualsWithItself", "ResultOfMethodCallIgnored"})
private static void equalsTest() {
System.out.println(MyEnum.A.equals(MyEnum.B));
@@ -78,6 +103,7 @@
}
}
+ @NeverInline
@SuppressWarnings({"ConstantConditions", "EqualsWithItself", "ResultOfMethodCallIgnored"})
private static void compareToTest() {
System.out.println(MyEnum.B.compareTo(MyEnum.A) > 0);