Enum unboxing: Support System.identityHashCode

Bug: 166325341
Change-Id: I4ebe8ecf3c511e6722e620cf5205d277b9865845
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 2cf3328..91a70a9 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -170,6 +170,7 @@
   public final DexString endsWithMethodName = createString("endsWith");
   public final DexString equalsMethodName = createString("equals");
   public final DexString hashCodeMethodName = createString("hashCode");
+  public final DexString identityHashCodeName = createString("identityHashCode");
   public final DexString equalsIgnoreCaseMethodName = createString("equalsIgnoreCase");
   public final DexString contentEqualsMethodName = createString("contentEquals");
   public final DexString indexOfMethodName = createString("indexOf");
@@ -225,6 +226,7 @@
   public final DexString fieldDescriptor = createString("Ljava/lang/reflect/Field;");
   public final DexString methodDescriptor = createString("Ljava/lang/reflect/Method;");
   public final DexString enumDescriptor = createString("Ljava/lang/Enum;");
+  public final DexString javaLangSystemDescriptor = createString("Ljava/lang/System;");
   public final DexString annotationDescriptor = createString("Ljava/lang/annotation/Annotation;");
   public final DexString objectsDescriptor = createString("Ljava/util/Objects;");
   public final DexString collectionsDescriptor = createString("Ljava/util/Collections;");
@@ -353,7 +355,7 @@
   public final DexType stringBuilderType = createStaticallyKnownType(stringBuilderDescriptor);
   public final DexType stringBufferType = createStaticallyKnownType(stringBufferDescriptor);
 
-  public final DexType javaLangSystemType = createStaticallyKnownType("Ljava/lang/System;");
+  public final DexType javaLangSystemType = createStaticallyKnownType(javaLangSystemDescriptor);
   public final DexType javaIoPrintStreamType = createStaticallyKnownType("Ljava/io/PrintStream;");
 
   public final DexType varHandleType = createStaticallyKnownType(varHandleDescriptor);
@@ -464,6 +466,7 @@
   public final ClassMethods classMethods = new ClassMethods();
   public final ConstructorMethods constructorMethods = new ConstructorMethods();
   public final EnumMembers enumMembers = new EnumMembers();
+  public final JavaLangSystemMethods javaLangSystemMethods = new JavaLangSystemMethods();
   public final NullPointerExceptionMethods npeMethods = new NullPointerExceptionMethods();
   public final IllegalArgumentExceptionMethods illegalArgumentExceptionMethods =
       new IllegalArgumentExceptionMethods();
@@ -1269,6 +1272,19 @@
     }
   }
 
+  public class JavaLangSystemMethods {
+    public final DexMethod identityHashCode;
+
+    private JavaLangSystemMethods() {
+      identityHashCode =
+          createMethod(
+              javaLangSystemDescriptor,
+              identityHashCodeName,
+              intDescriptor,
+              new DexString[] {objectDescriptor});
+    }
+  }
+
   public class EnumMembers {
 
     public final DexField nameField = createField(enumType, stringType, "name");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index be6229e..73773ba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -798,6 +798,10 @@
       }
       assert dexClass.isLibraryClass();
       if (dexClass.type != factory.enumType) {
+        // System.identityHashCode(Object) is supported for proto enums.
+        if (singleTarget == factory.javaLangSystemMethods.identityHashCode) {
+          return Reason.ELIGIBLE;
+        }
         return Reason.UNSUPPORTED_LIBRARY_CALL;
       }
       // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
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 087e808..9ffc360 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
@@ -33,6 +33,7 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
 import com.android.tools.r8.ir.code.MemberType;
@@ -176,6 +177,14 @@
             convertedEnums.put(invoke, enumType);
             continue;
           }
+        } else if (invokedMethod == factory.javaLangSystemMethods.identityHashCode) {
+          assert invokeStatic.inValues().size() == 1;
+          Value argument = invokeStatic.getArgument(0);
+          DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
+          if (enumType != null) {
+            invokeStatic.outValue().replaceUsers(argument);
+            iterator.removeOrReplaceByDebugLocalRead();
+          }
         }
       }
       // Rewrites direct access to enum values into the corresponding int, $VALUES is not
@@ -185,7 +194,7 @@
         DexType holder = staticGet.getField().holder;
         if (enumsToUnbox.containsEnum(holder)) {
           if (staticGet.outValue() == null) {
-            iterator.removeInstructionIgnoreOutValue();
+            iterator.removeOrReplaceByDebugLocalRead();
             continue;
           }
           EnumValueInfoMap enumValueInfoMap = enumsToUnbox.getEnumValueInfoMap(holder);
@@ -236,7 +245,7 @@
 
   private void replaceEnumInvoke(
       InstructionListIterator iterator,
-      InvokeMethodWithReceiver invokeMethod,
+      InvokeMethod invokeMethod,
       DexMethod method,
       Function<DexMethod, DexEncodedMethod> synthesizor) {
     utilityMethods.computeIfAbsent(method, synthesizor);
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
index 79f9176..1946116 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/OrdinalHashCodeEnumUnboxingTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.enumunboxing;
 
 import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.R8TestRunResult;
 import com.android.tools.r8.TestParameters;
 import java.util.List;
@@ -43,6 +44,7 @@
             .addKeepMainRule(classToTest)
             .addKeepRules(enumKeepRules.getKeepRules())
             .enableNeverClassInliningAnnotations()
+            .enableInliningAnnotations()
             .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
             .allowDiagnosticInfoMessages()
             .setMinApi(parameters.getApiLevel())
@@ -66,8 +68,29 @@
     public static void main(String[] args) {
       System.out.println(MyEnum.A.ordinal());
       System.out.println(0);
-      System.out.println(MyEnum.A.hashCode());
+      System.out.println(ordinal(MyEnum.A));
       System.out.println(0);
+      System.out.println(ordinal(MyEnum.B));
+      System.out.println(1);
+      System.out.println(MyEnum.A.hashCode());
+      System.out.println(MyEnum.A.hashCode());
+      System.out.println(hash(MyEnum.A));
+      System.out.println(System.identityHashCode(MyEnum.A));
+      System.out.println(hash(null));
+      System.out.println(0);
+      Object o = new Object();
+      System.out.println(System.identityHashCode(o));
+      System.out.println(o.hashCode());
+    }
+
+    @NeverInline
+    private static int hash(MyEnum e) {
+      return System.identityHashCode(e);
+    }
+
+    @NeverInline
+    private static int ordinal(MyEnum e) {
+      return e.ordinal();
     }
   }
 }