Ensure shared enum unboxing utility methods

Bug: 190098858
Change-Id: I58203702d011db39179d512523c410a342895fdc
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 fe3049f..98d016d 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
@@ -62,7 +62,6 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
-import java.util.function.Function;
 
 public class EnumUnboxingRewriter {
 
@@ -79,12 +78,6 @@
 
   private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
 
-  private final DexMethod ordinalUtilityMethod;
-  private final DexMethod equalsUtilityMethod;
-  private final DexMethod compareToUtilityMethod;
-  private final DexMethod zeroCheckMethod;
-  private final DexMethod zeroCheckMessageMethod;
-
   EnumUnboxingRewriter(
       AppView<AppInfoWithLiveness> appView,
       IRConverter converter,
@@ -98,35 +91,6 @@
     this.enumUnboxingLens = enumUnboxingLens;
     this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
     this.utilityClasses = utilityClasses;
-
-    // Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
-    DexType sharedEnumUnboxingUtilityType = utilityClasses.getSharedUtilityClass().getType();
-    this.ordinalUtilityMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.intType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
-    this.equalsUtilityMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.booleanType, factory.intType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
-    this.compareToUtilityMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.intType, factory.intType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
-    // Custom methods for Object#getClass without outValue and Objects.requireNonNull.
-    this.zeroCheckMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.voidType, factory.intType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
-    this.zeroCheckMessageMethod =
-        factory.createMethod(
-            sharedEnumUnboxingUtilityType,
-            factory.createProto(factory.voidType, factory.intType, factory.stringType),
-            ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
   }
 
   private LocalEnumUnboxingUtilityClass getLocalUtilityClass(DexType enumType) {
@@ -171,50 +135,57 @@
         // - toString is non-final, implemented in java.lang.Object, java.lang.Enum and possibly
         //   also in the unboxed enum class.
         if (instruction.isInvokeMethodWithReceiver()) {
-          InvokeMethodWithReceiver invokeMethod = instruction.asInvokeMethodWithReceiver();
-          DexType enumType = getEnumTypeOrNull(invokeMethod.getReceiver(), convertedEnums);
-          DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+          InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+          DexType enumType = getEnumTypeOrNull(invoke.getReceiver(), convertedEnums);
+          DexMethod invokedMethod = invoke.getInvokedMethod();
           if (enumType != null) {
             if (invokedMethod == factory.enumMembers.ordinalMethod
                 || invokedMethod.match(factory.enumMembers.hashCode)) {
               replaceEnumInvoke(
-                  iterator, invokeMethod, ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
+                  iterator,
+                  invoke,
+                  getSharedUtilityClass().ensureOrdinalMethod(appView, converter, methodProcessor));
               continue;
             } else if (invokedMethod.match(factory.enumMembers.equals)) {
               replaceEnumInvoke(
-                  iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
+                  iterator,
+                  invoke,
+                  getSharedUtilityClass().ensureEqualsMethod(appView, converter, methodProcessor));
               continue;
             } else if (invokedMethod == factory.enumMembers.compareTo
                 || invokedMethod == factory.enumMembers.compareToWithObject) {
               replaceEnumInvoke(
                   iterator,
-                  invokeMethod,
+                  invoke,
                   getSharedUtilityClass()
                       .ensureCompareToMethod(appView, converter, methodProcessor));
               continue;
             } else if (invokedMethod == factory.enumMembers.nameMethod) {
-              rewriteNameMethod(iterator, invokeMethod, enumType);
+              rewriteNameMethod(iterator, invoke, enumType);
               continue;
             } else if (invokedMethod.match(factory.enumMembers.toString)) {
               DexMethod lookupMethod = enumUnboxingLens.lookupMethod(invokedMethod);
               // If the lookupMethod is different, then a toString method was on the enumType
               // class, which was moved, and the lens code rewriter will rewrite the invoke to
               // that method.
-              if (invokeMethod.isInvokeSuper() || lookupMethod == invokedMethod) {
-                rewriteNameMethod(iterator, invokeMethod, enumType);
+              if (invoke.isInvokeSuper() || lookupMethod == invokedMethod) {
+                rewriteNameMethod(iterator, invoke, enumType);
                 continue;
               }
             } else if (invokedMethod == factory.objectMembers.getClass) {
-              assert !invokeMethod.hasOutValue() || !invokeMethod.outValue().hasAnyUsers();
+              assert !invoke.hasOutValue() || !invoke.outValue().hasAnyUsers();
               replaceEnumInvoke(
-                  iterator, invokeMethod, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+                  iterator,
+                  invoke,
+                  getSharedUtilityClass()
+                      .ensureCheckNotZeroMethod(appView, converter, methodProcessor));
               continue;
             }
           } else if (invokedMethod == factory.stringBuilderMethods.appendObject
               || invokedMethod == factory.stringBufferMethods.appendObject) {
             // Rewrites stringBuilder.append(enumInstance) as if it was
             // stringBuilder.append(String.valueOf(unboxedEnumInstance));
-            Value enumArg = invokeMethod.getArgument(1);
+            Value enumArg = invoke.getArgument(1);
             DexType enumArgType = getEnumTypeOrNull(enumArg, convertedEnums);
             if (enumArgType != null) {
               DexMethod stringValueOfMethod = computeStringValueOfUtilityMethod(enumArgType);
@@ -223,17 +194,17 @@
                       .setMethod(stringValueOfMethod)
                       .setSingleArgument(enumArg)
                       .setFreshOutValue(appView, code)
-                      .setPosition(invokeMethod)
+                      .setPosition(invoke)
                       .build();
               DexMethod newAppendMethod =
                   invokedMethod == factory.stringBuilderMethods.appendObject
                       ? factory.stringBuilderMethods.appendString
                       : factory.stringBufferMethods.appendString;
               List<Value> arguments =
-                  ImmutableList.of(invokeMethod.getReceiver(), toStringInvoke.outValue());
+                  ImmutableList.of(invoke.getReceiver(), toStringInvoke.outValue());
               InvokeVirtual invokeAppendString =
-                  new InvokeVirtual(newAppendMethod, invokeMethod.clearOutValue(), arguments);
-              invokeAppendString.setPosition(invokeMethod.getPosition());
+                  new InvokeVirtual(newAppendMethod, invoke.clearOutValue(), arguments);
+              invokeAppendString.setPosition(invoke.getPosition());
               iterator.replaceCurrentInstruction(toStringInvoke);
               if (block.hasCatchHandlers()) {
                 iterator
@@ -248,7 +219,13 @@
           }
         } else if (instruction.isInvokeStatic()) {
           rewriteInvokeStatic(
-              instruction.asInvokeStatic(), code, context, convertedEnums, iterator, affectedPhis);
+              instruction.asInvokeStatic(),
+              code,
+              context,
+              convertedEnums,
+              iterator,
+              affectedPhis,
+              methodProcessor);
         }
         if (instruction.isStaticGet()) {
           StaticGet staticGet = instruction.asStaticGet();
@@ -301,7 +278,8 @@
           InstanceGet instanceGet = instruction.asInstanceGet();
           DexType holder = instanceGet.getField().holder;
           if (unboxedEnumsData.isUnboxedEnum(holder)) {
-            DexMethod fieldMethod = computeInstanceFieldMethod(instanceGet.getField());
+            DexMethod fieldMethod =
+                computeInstanceFieldMethod(instanceGet.getField(), methodProcessor);
             Value rewrittenOutValue =
                 code.createValue(
                     TypeElement.fromDexType(
@@ -350,7 +328,8 @@
       ProgramMethod context,
       Map<Instruction, DexType> convertedEnums,
       InstructionListIterator instructionIterator,
-      Set<Phi> affectedPhis) {
+      Set<Phi> affectedPhis,
+      MethodProcessor methodProcessor) {
     DexClassAndMethod singleTarget = invoke.lookupSingleTarget(appView, context);
     if (singleTarget == null) {
       return;
@@ -394,7 +373,10 @@
         DexType enumType = getEnumTypeOrNull(argument, convertedEnums);
         if (enumType != null) {
           replaceEnumInvoke(
-              instructionIterator, invoke, zeroCheckMethod, m -> synthesizeZeroCheckMethod());
+              instructionIterator,
+              invoke,
+              getSharedUtilityClass()
+                  .ensureCheckNotZeroMethod(appView, converter, methodProcessor));
         }
       } else if (invokedMethod == factory.objectsMethods.requireNonNullWithMessage) {
         assert invoke.arguments().size() == 2;
@@ -404,8 +386,8 @@
           replaceEnumInvoke(
               instructionIterator,
               invoke,
-              zeroCheckMessageMethod,
-              m -> synthesizeZeroCheckMessageMethod());
+              getSharedUtilityClass()
+                  .ensureCheckNotZeroWithMessageMethod(appView, converter, methodProcessor));
         }
       }
       return;
@@ -496,32 +478,24 @@
     return iterator.insertConstIntInstruction(code, options, 0);
   }
 
-  private DexMethod computeInstanceFieldMethod(DexField field) {
+  private DexMethod computeInstanceFieldMethod(DexField field, MethodProcessor methodProcessor) {
     EnumInstanceFieldKnownData enumFieldKnownData =
         unboxedEnumsData.getInstanceFieldData(field.holder, field);
     if (enumFieldKnownData.isOrdinal()) {
-      utilityMethods.computeIfAbsent(ordinalUtilityMethod, m -> synthesizeOrdinalMethod());
-      return ordinalUtilityMethod;
+      return getSharedUtilityClass()
+          .ensureOrdinalMethod(appView, converter, methodProcessor)
+          .getReference();
     }
     return computeInstanceFieldUtilityMethod(field.holder, field);
   }
 
   private void replaceEnumInvoke(
       InstructionListIterator iterator, InvokeMethod invoke, ProgramMethod method) {
-    replaceEnumInvoke(iterator, invoke, method.getReference(), null);
-  }
-
-  private void replaceEnumInvoke(
-      InstructionListIterator iterator,
-      InvokeMethod invoke,
-      DexMethod method,
-      Function<DexMethod, DexEncodedMethod> synthesizor) {
-    if (synthesizor != null) {
-      utilityMethods.computeIfAbsent(method, synthesizor);
-    }
     InvokeStatic replacement =
         new InvokeStatic(
-            method, invoke.hasUnusedOutValue() ? null : invoke.outValue(), invoke.arguments());
+            method.getReference(),
+            invoke.hasUnusedOutValue() ? null : invoke.outValue(),
+            invoke.arguments());
     assert !replacement.hasOutValue()
         || !replacement.getInvokedMethod().getReturnType().isVoidType();
     iterator.replaceCurrentInstruction(replacement);
@@ -696,38 +670,6 @@
     return synthesizeUtilityMethod(cfCode, method);
   }
 
-  private DexEncodedMethod synthesizeZeroCheckMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), zeroCheckMethod);
-    return synthesizeUtilityMethod(cfCode, zeroCheckMethod);
-  }
-
-  private DexEncodedMethod synthesizeZeroCheckMessageMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(
-            appView.options(), zeroCheckMessageMethod);
-    return synthesizeUtilityMethod(cfCode, zeroCheckMessageMethod);
-  }
-
-  private DexEncodedMethod synthesizeOrdinalMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), ordinalUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, ordinalUtilityMethod);
-  }
-
-  private DexEncodedMethod synthesizeEqualsMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), equalsUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, equalsUtilityMethod);
-  }
-
-  private DexEncodedMethod synthesizeCompareToMethod() {
-    CfCode cfCode =
-        EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
-            appView.options(), compareToUtilityMethod);
-    return synthesizeUtilityMethod(cfCode, compareToUtilityMethod);
-  }
-
   private DexEncodedMethod synthesizeUtilityMethod(CfCode cfCode, DexMethod method) {
     return new DexEncodedMethod(
         method,
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 3e556e6..8fac402 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
@@ -25,6 +25,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.FieldAccessFlags;
 import com.android.tools.r8.graph.GenericSignature.FieldTypeSignature;
@@ -39,6 +41,7 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackSimple;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.ConsumerUtils;
 import com.google.common.collect.ImmutableList;
@@ -72,11 +75,87 @@
         appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder);
   }
 
+  public ProgramMethod ensureCheckNotZeroMethod(
+      AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      MethodProcessor methodProcessor) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        converter,
+        methodProcessor,
+        dexItemFactory.createString("checkNotZero"),
+        dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheck(appView.options(), method));
+  }
+
+  public ProgramMethod ensureCheckNotZeroWithMessageMethod(
+      AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      MethodProcessor methodProcessor) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        converter,
+        methodProcessor,
+        dexItemFactory.createString("checkNotZero"),
+        dexItemFactory.createProto(
+            dexItemFactory.voidType, dexItemFactory.intType, dexItemFactory.stringType),
+        method ->
+            EnumUnboxingCfMethods.EnumUnboxingMethods_zeroCheckMessage(appView.options(), method));
+  }
+
   public ProgramMethod ensureCompareToMethod(
       AppView<AppInfoWithLiveness> appView,
       IRConverter converter,
       MethodProcessor methodProcessor) {
     DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        converter,
+        methodProcessor,
+        dexItemFactory.enumMembers.compareTo.getName(),
+        dexItemFactory.createProto(
+            dexItemFactory.intType, dexItemFactory.intType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(appView.options(), method));
+  }
+
+  public ProgramMethod ensureEqualsMethod(
+      AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      MethodProcessor methodProcessor) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        converter,
+        methodProcessor,
+        dexItemFactory.enumMembers.equals.getName(),
+        dexItemFactory.createProto(
+            dexItemFactory.booleanType, dexItemFactory.intType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_equals(appView.options(), method));
+  }
+
+  public ProgramMethod ensureOrdinalMethod(
+      AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      MethodProcessor methodProcessor) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    return internalEnsureMethod(
+        appView,
+        converter,
+        methodProcessor,
+        dexItemFactory.enumMembers.ordinalMethod.getName(),
+        dexItemFactory.createProto(dexItemFactory.intType, dexItemFactory.intType),
+        method -> EnumUnboxingCfMethods.EnumUnboxingMethods_ordinal(appView.options(), method));
+  }
+
+  private ProgramMethod internalEnsureMethod(
+      AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      MethodProcessor methodProcessor,
+      DexString methodName,
+      DexProto methodProto,
+      SyntheticCodeGenerator codeGenerator) {
     // TODO(b/191957637): Consider creating free flowing static methods instead. The synthetic
     //  infrastructure needs to be augmented with a new method ensureFixedMethod() or
     //  ensureFixedFreeFlowingMethod() for this, if we want to create only one utility method (and
@@ -84,9 +163,8 @@
     return appView
         .getSyntheticItems()
         .ensureFixedClassMethod(
-            dexItemFactory.enumMembers.compareTo.getName(),
-            dexItemFactory.createProto(
-                dexItemFactory.intType, dexItemFactory.intType, dexItemFactory.intType),
+            methodName,
+            methodProto,
             SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
             synthesizingContext,
             appView,
@@ -94,10 +172,7 @@
             methodBuilder ->
                 methodBuilder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
-                    .setCode(
-                        method ->
-                            EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
-                                appView.options(), method))
+                    .setCode(codeGenerator)
                     .setClassFileVersion(CfVersion.V1_6),
             newMethod ->
                 converter.processDesugaredMethod(