Enum unboxing: Synthesize utility class per unboxed enum

Bug: 185224410
Change-Id: I2a7d6e95000bbcece1feddbfea93f0def6852fce
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 e9c4127..7bc7e56 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
@@ -38,7 +38,6 @@
 import com.android.tools.r8.graph.FieldResolutionResult;
 import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ProgramPackageCollection;
 import com.android.tools.r8.graph.ResolutionResult;
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.ir.analysis.fieldvalueanalysis.StaticFieldValues;
@@ -128,8 +127,6 @@
   // Map the enum candidates with their dependencies, i.e., the methods to reprocess for the given
   // enum if the optimization eventually decides to unbox it.
   private final EnumUnboxingCandidateInfoCollection enumUnboxingCandidatesInfo;
-  private final ProgramPackageCollection enumsToUnboxWithPackageRequirement =
-      ProgramPackageCollection.createEmpty();
   private final Map<DexType, EnumStaticFieldValues> staticFieldValuesMap =
       new ConcurrentHashMap<>();
   private final ProgramMethodSet methodsDependingOnLibraryModelisation =
@@ -481,18 +478,15 @@
     DirectMappedDexApplication.Builder appBuilder = appView.appInfo().app().asDirect().builder();
     FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder =
         FieldAccessInfoCollectionModifier.builder();
-    UnboxedEnumMemberRelocator relocator =
-        UnboxedEnumMemberRelocator.builder(appView)
+    EnumUnboxingUtilityClasses utilityClasses =
+        EnumUnboxingUtilityClasses.builder(appView)
             .synthesizeEnumUnboxingUtilityClasses(
-                enumClassesToUnbox,
-                enumsToUnboxWithPackageRequirement,
-                appBuilder,
-                fieldAccessInfoCollectionModifierBuilder)
+                enumClassesToUnbox, appBuilder, fieldAccessInfoCollectionModifierBuilder)
             .build();
     fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
-    enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, relocator);
+    enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, utilityClasses);
     EnumUnboxingLens enumUnboxingLens =
-        new EnumUnboxingTreeFixer(appView, enumsToUnbox, relocator, enumUnboxerRewriter)
+        new EnumUnboxingTreeFixer(appView, enumsToUnbox, utilityClasses, enumUnboxerRewriter)
             .fixupTypeReferences();
     enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
     appView.setUnboxedEnums(enumDataMap);
@@ -539,7 +533,6 @@
 
   public EnumDataMap finishAnalysis() {
     analyzeInitializers();
-    analyzeAccessibility();
     EnumDataMap enumDataMap = analyzeEnumInstances();
     if (debugLogEnabled) {
       // Remove all enums that have been reported as being unboxable.
@@ -741,39 +734,6 @@
     return OptionalInt.empty();
   }
 
-  private void analyzeAccessibility() {
-    // Unboxing an enum will require to move its methods to a different class, which may impact
-    // accessibility. For a quick analysis we simply reuse the inliner analysis.
-    enumUnboxingCandidatesInfo.forEachCandidate(
-        enumClass -> {
-          Constraint classConstraint = analyzeAccessibilityInClass(enumClass);
-          if (classConstraint == Constraint.NEVER) {
-            markEnumAsUnboxable(Reason.ACCESSIBILITY, enumClass);
-          } else if (classConstraint == Constraint.PACKAGE) {
-            enumsToUnboxWithPackageRequirement.addProgramClass(enumClass);
-          }
-        });
-  }
-
-  private Constraint analyzeAccessibilityInClass(DexProgramClass enumClass) {
-    Constraint classConstraint = Constraint.ALWAYS;
-    EnumAccessibilityUseRegistry useRegistry = null;
-    for (DexEncodedMethod method : enumClass.methods()) {
-      // Enum initializer are analyzed in analyzeInitializers instead.
-      if (!method.isInitializer()) {
-        if (useRegistry == null) {
-          useRegistry = new EnumAccessibilityUseRegistry(factory);
-        }
-        Constraint methodConstraint = constraintForEnumUnboxing(method, useRegistry);
-        classConstraint = classConstraint.meet(methodConstraint);
-        if (classConstraint == Constraint.NEVER) {
-          return classConstraint;
-        }
-      }
-    }
-    return classConstraint;
-  }
-
   public Constraint constraintForEnumUnboxing(
       DexEncodedMethod method, EnumAccessibilityUseRegistry useRegistry) {
     return useRegistry.computeConstraint(method.asProgramMethod(appView));
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 bbe0796..9b159f2 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
@@ -71,8 +71,8 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final EnumDataMap unboxedEnumsData;
-  private final UnboxedEnumMemberRelocator relocator;
   private EnumUnboxingLens enumUnboxingLens;
+  private final EnumUnboxingUtilityClasses utilityClasses;
 
   private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
 
@@ -86,44 +86,45 @@
   EnumUnboxingRewriter(
       AppView<AppInfoWithLiveness> appView,
       EnumDataMap unboxedEnumsInstanceFieldData,
-      UnboxedEnumMemberRelocator relocator) {
+      EnumUnboxingUtilityClasses utilityClasses) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
-    this.relocator = relocator;
+    this.utilityClasses = utilityClasses;
 
     // Custom methods for java.lang.Enum methods ordinal, equals and compareTo.
-    DexType defaultEnumUnboxingUtility = relocator.getDefaultEnumUnboxingUtility();
+    DexType sharedEnumUnboxingUtilityType =
+        utilityClasses.getSharedEnumUnboxingUtilityClass().getType();
     this.ordinalUtilityMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "ordinal");
     this.equalsUtilityMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.booleanType, factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "equals");
     this.compareToUtilityMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.intType, factory.intType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "compareTo");
     // Custom methods for generated field $VALUES initialization.
     this.valuesUtilityMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.intArrayType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "values");
     // Custom methods for Object#getClass without outValue and Objects.requireNonNull.
     this.zeroCheckMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.voidType, factory.intType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheck");
     this.zeroCheckMessageMethod =
         factory.createMethod(
-            defaultEnumUnboxingUtility,
+            sharedEnumUnboxingUtilityType,
             factory.createProto(factory.voidType, factory.intType, factory.stringType),
             ENUM_UNBOXING_UTILITY_METHOD_PREFIX + "zeroCheckMessage");
   }
@@ -465,7 +466,8 @@
   }
 
   private DexField createValuesField(DexType enumType) {
-    return createValuesField(enumType, relocator.getNewMemberLocationFor(enumType), factory);
+    return createValuesField(
+        enumType, utilityClasses.getLocalEnumUnboxingUtilityClass(enumType), factory);
   }
 
   static DexField createValuesField(
@@ -478,7 +480,7 @@
 
   private DexMethod createValuesMethod(DexType enumType) {
     return factory.createMethod(
-        relocator.getNewMemberLocationFor(enumType),
+        utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
         factory.createProto(factory.intArrayType),
         "$$values$$method$" + compatibleName(enumType));
   }
@@ -503,7 +505,7 @@
             + compatibleName(enumType);
     DexMethod fieldMethod =
         factory.createMethod(
-            relocator.getNewMemberLocationFor(enumType),
+            utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
             factory.createProto(field.type, factory.intType),
             methodName);
     utilityMethods.computeIfAbsent(
@@ -517,7 +519,7 @@
     String methodName = "string$valueOf$" + compatibleName(enumType);
     DexMethod fieldMethod =
         factory.createMethod(
-            relocator.getNewMemberLocationFor(enumType),
+            utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
             factory.createProto(factory.stringType, factory.intType),
             methodName);
     AbstractValue nullString =
@@ -532,7 +534,7 @@
     assert unboxedEnumsData.isUnboxedEnum(enumType);
     DexMethod valueOf =
         factory.createMethod(
-            relocator.getNewMemberLocationFor(enumType),
+            utilityClasses.getLocalEnumUnboxingUtilityClass(enumType),
             factory.createProto(factory.intType, factory.stringType),
             "valueOf" + compatibleName(enumType));
     utilityMethods.computeIfAbsent(valueOf, m -> synthesizeValueOfUtilityMethod(m, enumType));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
index a29e290..d36d9b7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingTreeFixer.java
@@ -38,18 +38,18 @@
   private final AppView<AppInfoWithLiveness> appView;
   private final DexItemFactory factory;
   private final Set<DexType> enumsToUnbox;
-  private final UnboxedEnumMemberRelocator relocator;
+  private final EnumUnboxingUtilityClasses utilityClasses;
   private final EnumUnboxingRewriter enumUnboxerRewriter;
 
   EnumUnboxingTreeFixer(
       AppView<AppInfoWithLiveness> appView,
       Set<DexType> enumsToUnbox,
-      UnboxedEnumMemberRelocator relocator,
+      EnumUnboxingUtilityClasses utilityClasses,
       EnumUnboxingRewriter enumUnboxerRewriter) {
     this.appView = appView;
     this.factory = appView.dexItemFactory();
     this.enumsToUnbox = enumsToUnbox;
-    this.relocator = relocator;
+    this.utilityClasses = utilityClasses;
     this.enumUnboxerRewriter = enumUnboxerRewriter;
   }
 
@@ -57,7 +57,7 @@
     assert enumUnboxerRewriter != null;
     // Fix all methods and fields using enums to unbox.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      if (enumsToUnbox.contains(clazz.type)) {
+      if (enumsToUnbox.contains(clazz.getType())) {
         // Clear the initializers and move the static methods to the new location.
         Set<DexEncodedMethod> methodsToRemove = Sets.newIdentityHashSet();
         clazz
@@ -67,7 +67,7 @@
                   if (m.isInitializer()) {
                     clearEnumToUnboxMethod(m);
                   } else {
-                    DexType newHolder = relocator.getNewMemberLocationFor(clazz.type);
+                    DexType newHolder = utilityClasses.getLocalEnumUnboxingUtilityClass(clazz);
                     List<DexEncodedMethod> movedMethods =
                         unboxedEnumsMethods.computeIfAbsent(newHolder, k -> new ArrayList<>());
                     movedMethods.add(fixupEncodedMethodToUtility(m, newHolder));
@@ -178,16 +178,15 @@
       newMethod =
           factory.createInstanceInitializerWithFreshProto(
               newMethod,
-              relocator.getDefaultEnumUnboxingUtility(),
+              utilityClasses.getSharedEnumUnboxingUtilityClass().getType(),
               tryMethod -> holder.lookupMethod(tryMethod) == null);
     } else {
       int index = 0;
       while (holder.lookupMethod(newMethod) != null) {
         newMethod =
-            factory.createMethod(
-                newMethod.holder,
-                newMethod.proto,
-                encodedMethod.getName().toString() + "$enumunboxing$" + index++);
+            newMethod.withName(
+                encodedMethod.getName().toString() + "$enumunboxing$" + index++,
+                appView.dexItemFactory());
       }
     }
     return newMethod;
@@ -202,7 +201,7 @@
       DexField field = encodedField.getReference();
       DexType newType = fixupType(field.type);
       if (newType != field.type) {
-        DexField newField = factory.createField(field.holder, newType, field.name);
+        DexField newField = field.withType(newType, factory);
         lensBuilder.move(field, newField);
         DexEncodedField newEncodedField =
             encodedField.toTypeSubstitutedField(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
new file mode 100644
index 0000000..dda4779
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxingUtilityClasses.java
@@ -0,0 +1,209 @@
+// Copyright (c) 2020, 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.enums;
+
+import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.DexAnnotationSet;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
+import com.android.tools.r8.graph.FieldAccessFlags;
+import com.android.tools.r8.graph.GenericSignature.ClassSignature;
+import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableMap;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class EnumUnboxingUtilityClasses {
+
+  public static final String ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX =
+      "$r8$EnumUnboxingLocalUtility";
+  public static final String ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX =
+      "$r8$EnumUnboxingSharedUtility";
+
+  // Synthetic classes for utilities specific to the unboxing of a single enum.
+  private final ImmutableMap<DexType, DexProgramClass> localEnumUnboxingUtilityClasses;
+
+  // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
+  // methods (ordinal(I), equals(II), etc.).
+  private final DexProgramClass sharedEnumUnboxingUtilityClass;
+
+  private EnumUnboxingUtilityClasses(
+      DexProgramClass sharedEnumUnboxingUtilityClass,
+      ImmutableMap<DexType, DexProgramClass> localEnumUnboxingUtilityClasses) {
+    this.sharedEnumUnboxingUtilityClass = sharedEnumUnboxingUtilityClass;
+    this.localEnumUnboxingUtilityClasses = localEnumUnboxingUtilityClasses;
+  }
+
+  public DexType getLocalEnumUnboxingUtilityClass(DexProgramClass enumClass) {
+    return getLocalEnumUnboxingUtilityClass(enumClass.getType());
+  }
+
+  public DexType getLocalEnumUnboxingUtilityClass(DexType enumType) {
+    DexProgramClass localEnumUnboxingUtilityClass = localEnumUnboxingUtilityClasses.get(enumType);
+    assert localEnumUnboxingUtilityClass != null;
+    return localEnumUnboxingUtilityClass.getType();
+  }
+
+  public DexProgramClass getSharedEnumUnboxingUtilityClass() {
+    return sharedEnumUnboxingUtilityClass;
+  }
+
+  public static Builder builder(AppView<AppInfoWithLiveness> appView) {
+    return new Builder(appView);
+  }
+
+  public static class Builder {
+
+    private final AppView<?> appView;
+    private final Map<DexType, DexProgramClass> localEnumUnboxingUtilityClasses =
+        new IdentityHashMap<>();
+    private DexProgramClass sharedEnumUnboxingUtilityClass;
+
+    public Builder(AppView<AppInfoWithLiveness> appView) {
+      this.appView = appView;
+    }
+
+    public Builder synthesizeEnumUnboxingUtilityClasses(
+        Set<DexProgramClass> enumsToUnbox,
+        DirectMappedDexApplication.Builder appBuilder,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+      synthesizeLocalUtilityClasses(
+          enumsToUnbox, appBuilder, fieldAccessInfoCollectionModifierBuilder);
+      synthesizeSharedUtilityClass(enumsToUnbox, appBuilder);
+      return this;
+    }
+
+    public EnumUnboxingUtilityClasses build() {
+      return new EnumUnboxingUtilityClasses(
+          sharedEnumUnboxingUtilityClass, ImmutableMap.copyOf(localEnumUnboxingUtilityClasses));
+    }
+
+    private void synthesizeLocalUtilityClasses(
+        Set<DexProgramClass> enumsToUnbox,
+        DirectMappedDexApplication.Builder appBuilder,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+      for (DexProgramClass enumToUnbox : enumsToUnbox) {
+        synthesizeLocalUtilityClass(
+            enumToUnbox, appBuilder, fieldAccessInfoCollectionModifierBuilder);
+      }
+    }
+
+    private void synthesizeLocalUtilityClass(
+        DexProgramClass enumToUnbox,
+        DirectMappedDexApplication.Builder appBuilder,
+        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+      DexType localUtilityClassType = getLocalUtilityClassType(enumToUnbox);
+      assert appView.appInfo().definitionForWithoutExistenceAssert(localUtilityClassType) == null;
+
+      // Required fields.
+      DexField reference =
+          createValuesField(enumToUnbox.getType(), localUtilityClassType, appView.dexItemFactory());
+      DexEncodedField staticField =
+          new DexEncodedField(reference, FieldAccessFlags.createPublicStaticSynthetic());
+      fieldAccessInfoCollectionModifierBuilder
+          .recordFieldReadInUnknownContext(reference)
+          .recordFieldWriteInUnknownContext(reference);
+
+      DexProgramClass localUtilityClass =
+          new DexProgramClass(
+              localUtilityClassType,
+              null,
+              new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+              ClassAccessFlags.createPublicFinalSynthetic(),
+              appView.dexItemFactory().objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              ClassSignature.noSignature(),
+              DexAnnotationSet.empty(),
+              new DexEncodedField[] {staticField},
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              appView.dexItemFactory().getSkipNameValidationForTesting(),
+              DexProgramClass::checksumFromType);
+      appBuilder.addSynthesizedClass(localUtilityClass);
+      appView.appInfo().addSynthesizedClass(localUtilityClass, enumToUnbox);
+      localEnumUnboxingUtilityClasses.put(enumToUnbox.getType(), localUtilityClass);
+    }
+
+    private void synthesizeSharedUtilityClass(
+        Set<DexProgramClass> enumsToUnbox, DirectMappedDexApplication.Builder appBuilder) {
+      DexType type = getSharedUtilityClassType(findDeterministicContextType(enumsToUnbox));
+      assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
+
+      DexProgramClass syntheticClass =
+          new DexProgramClass(
+              type,
+              null,
+              new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
+              ClassAccessFlags.createPublicFinalSynthetic(),
+              appView.dexItemFactory().objectType,
+              DexTypeList.empty(),
+              null,
+              null,
+              Collections.emptyList(),
+              null,
+              Collections.emptyList(),
+              ClassSignature.noSignature(),
+              DexAnnotationSet.empty(),
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedField.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              DexEncodedMethod.EMPTY_ARRAY,
+              appView.dexItemFactory().getSkipNameValidationForTesting(),
+              DexProgramClass::checksumFromType);
+      appBuilder.addSynthesizedClass(syntheticClass);
+      appView.appInfo().addSynthesizedClassToBase(syntheticClass, enumsToUnbox);
+      sharedEnumUnboxingUtilityClass = syntheticClass;
+    }
+
+    private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
+      DexProgramClass deterministicContext = null;
+      for (DexProgramClass context : contexts) {
+        if (deterministicContext == null) {
+          deterministicContext = context;
+        } else if (context.type.compareTo(deterministicContext.type) < 0) {
+          deterministicContext = context;
+        }
+      }
+      return deterministicContext;
+    }
+
+    private DexType getLocalUtilityClassType(DexProgramClass context) {
+      return getUtilityClassType(context, ENUM_UNBOXING_LOCAL_UTILITY_CLASS_SUFFIX);
+    }
+
+    private DexType getSharedUtilityClassType(DexProgramClass context) {
+      return getUtilityClassType(context, ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX);
+    }
+
+    private DexType getUtilityClassType(DexProgramClass context, String suffix) {
+      return appView
+          .dexItemFactory()
+          .createType(
+              DescriptorUtils.getDescriptorFromClassBinaryName(
+                  DescriptorUtils.getBinaryNameFromDescriptor(
+                          context.getType().toDescriptorString())
+                      + suffix));
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
deleted file mode 100644
index 54c416a..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/UnboxedEnumMemberRelocator.java
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright (c) 2020, 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.enums;
-
-import static com.android.tools.r8.ir.optimize.enums.EnumUnboxingRewriter.createValuesField;
-
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.ClassAccessFlags;
-import com.android.tools.r8.graph.DexAnnotationSet;
-import com.android.tools.r8.graph.DexEncodedField;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.graph.DexField;
-import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.DexTypeList;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
-import com.android.tools.r8.graph.FieldAccessFlags;
-import com.android.tools.r8.graph.GenericSignature.ClassSignature;
-import com.android.tools.r8.graph.ProgramPackage;
-import com.android.tools.r8.graph.ProgramPackageCollection;
-import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.android.tools.r8.shaking.FieldAccessInfoCollectionModifier;
-import com.android.tools.r8.utils.SetUtils;
-import com.google.common.collect.ImmutableMap;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class UnboxedEnumMemberRelocator {
-
-  public static final String ENUM_UNBOXING_UTILITY_CLASS_SUFFIX = "$r8$EnumUnboxingUtility";
-
-  // Default enum unboxing utility synthetic class used to hold all the shared unboxed enum
-  // methods (ordinal(I), equals(II), etc.) and the unboxed enums members which were free to be
-  // placed anywhere.
-  private final DexType defaultEnumUnboxingUtility;
-  // Some unboxed enum members have to be placed in a specific package, in this case, we keep a
-  // map from unboxed enum types to synthetic classes, so that all members of unboxed enums in the
-  // keys are moved to the corresponding value.
-  private final ImmutableMap<DexType, DexType> relocationMap;
-
-  public DexType getDefaultEnumUnboxingUtility() {
-    return defaultEnumUnboxingUtility;
-  }
-
-  public DexType getNewMemberLocationFor(DexType enumType) {
-    return relocationMap.getOrDefault(enumType, defaultEnumUnboxingUtility);
-  }
-
-  private UnboxedEnumMemberRelocator(
-      DexType defaultEnumUnboxingUtility, ImmutableMap<DexType, DexType> relocationMap) {
-    this.defaultEnumUnboxingUtility = defaultEnumUnboxingUtility;
-    this.relocationMap = relocationMap;
-  }
-
-  public static Builder builder(AppView<?> appView) {
-    return new Builder(appView);
-  }
-
-  public static class Builder {
-    private DexProgramClass defaultEnumUnboxingUtility;
-    private Map<DexType, DexType> relocationMap = new IdentityHashMap<>();
-    private final AppView<?> appView;
-
-    public Builder(AppView<?> appView) {
-      this.appView = appView;
-    }
-
-    public Builder synthesizeEnumUnboxingUtilityClasses(
-        Set<DexProgramClass> enumsToUnbox,
-        ProgramPackageCollection enumsToUnboxWithPackageRequirement,
-        DirectMappedDexApplication.Builder appBuilder,
-        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
-      Set<DexProgramClass> enumsToUnboxWithoutPackageRequirement =
-          SetUtils.newIdentityHashSet(enumsToUnbox);
-      enumsToUnboxWithoutPackageRequirement.removeIf(enumsToUnboxWithPackageRequirement::contains);
-      defaultEnumUnboxingUtility =
-          synthesizeUtilityClass(
-              enumsToUnbox,
-              enumsToUnboxWithoutPackageRequirement,
-              appBuilder,
-              fieldAccessInfoCollectionModifierBuilder);
-      if (!enumsToUnboxWithPackageRequirement.isEmpty()) {
-        synthesizeRelocationMap(
-            enumsToUnbox,
-            enumsToUnboxWithPackageRequirement,
-            appBuilder,
-            fieldAccessInfoCollectionModifierBuilder);
-      }
-      return this;
-    }
-
-    public UnboxedEnumMemberRelocator build() {
-      return new UnboxedEnumMemberRelocator(
-          defaultEnumUnboxingUtility.getType(), ImmutableMap.copyOf(relocationMap));
-    }
-
-    private void synthesizeRelocationMap(
-        Set<DexProgramClass> contexts,
-        ProgramPackageCollection enumsToUnboxWithPackageRequirement,
-        DirectMappedDexApplication.Builder appBuilder,
-        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
-      for (ProgramPackage programPackage : enumsToUnboxWithPackageRequirement) {
-        Set<DexProgramClass> enumsToUnboxInPackage = programPackage.classesInPackage();
-        DexProgramClass enumUtilityClass =
-            synthesizeUtilityClass(
-                contexts,
-                enumsToUnboxInPackage,
-                appBuilder,
-                fieldAccessInfoCollectionModifierBuilder);
-        if (enumUtilityClass != defaultEnumUnboxingUtility) {
-          for (DexProgramClass enumToUnbox : enumsToUnboxInPackage) {
-            assert !relocationMap.containsKey(enumToUnbox.type);
-            relocationMap.put(enumToUnbox.type, enumUtilityClass.getType());
-          }
-        }
-      }
-    }
-
-    private DexProgramClass synthesizeUtilityClass(
-        Set<DexProgramClass> contexts,
-        Set<DexProgramClass> relocatedEnums,
-        DirectMappedDexApplication.Builder appBuilder,
-        FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
-      DexProgramClass deterministicContext = findDeterministicContextType(contexts);
-      String descriptorString = deterministicContext.getType().toDescriptorString();
-      String descriptorPrefix = descriptorString.substring(0, descriptorString.length() - 1);
-      String syntheticClassDescriptor = descriptorPrefix + ENUM_UNBOXING_UTILITY_CLASS_SUFFIX + ";";
-      DexType type = appView.dexItemFactory().createType(syntheticClassDescriptor);
-
-      // Required fields.
-      List<DexEncodedField> staticFields = new ArrayList<>(relocatedEnums.size());
-      for (DexProgramClass relocatedEnum : relocatedEnums) {
-        DexField reference =
-            createValuesField(relocatedEnum.getType(), type, appView.dexItemFactory());
-        staticFields.add(
-            new DexEncodedField(reference, FieldAccessFlags.createPublicStaticSynthetic()));
-        fieldAccessInfoCollectionModifierBuilder
-            .recordFieldReadInUnknownContext(reference)
-            .recordFieldWriteInUnknownContext(reference);
-      }
-      staticFields.sort(Comparator.comparing(DexEncodedField::getReference));
-
-      // The defaultEnumUnboxingUtility depends on all unboxable enums, and other synthetic types
-      // depend on a subset of the unboxable enums, the deterministicContextType can therefore
-      // be found twice, and in that case the same utility class can be used for both.
-      if (defaultEnumUnboxingUtility != null && type == defaultEnumUnboxingUtility.getType()) {
-        defaultEnumUnboxingUtility.appendStaticFields(staticFields);
-        return defaultEnumUnboxingUtility;
-      }
-      assert appView.appInfo().definitionForWithoutExistenceAssert(type) == null;
-      DexProgramClass syntheticClass =
-          new DexProgramClass(
-              type,
-              null,
-              new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
-              ClassAccessFlags.fromSharedAccessFlags(
-                  Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-              appView.dexItemFactory().objectType,
-              DexTypeList.empty(),
-              null,
-              null,
-              Collections.emptyList(),
-              null,
-              Collections.emptyList(),
-              ClassSignature.noSignature(),
-              DexAnnotationSet.empty(),
-              staticFields.toArray(DexEncodedField.EMPTY_ARRAY),
-              DexEncodedField.EMPTY_ARRAY,
-              DexEncodedMethod.EMPTY_ARRAY,
-              DexEncodedMethod.EMPTY_ARRAY,
-              appView.dexItemFactory().getSkipNameValidationForTesting(),
-              DexProgramClass::checksumFromType);
-      appBuilder.addSynthesizedClass(syntheticClass);
-      appView.appInfo().addSynthesizedClassToBase(syntheticClass, contexts);
-      return syntheticClass;
-    }
-
-    private DexProgramClass findDeterministicContextType(Set<DexProgramClass> contexts) {
-      DexProgramClass deterministicContext = null;
-      for (DexProgramClass context : contexts) {
-        if (deterministicContext == null) {
-          deterministicContext = context;
-        } else if (context.type.compareTo(deterministicContext.type) < 0) {
-          deterministicContext = context;
-        }
-      }
-      return deterministicContext;
-    }
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 4378c0f..75bb5f5 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -8,7 +8,6 @@
 import static com.android.tools.r8.ir.desugar.LambdaDescriptor.isLambdaMetafactoryMethod;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.emulateInterfaceLibraryMethod;
 import static com.android.tools.r8.ir.desugar.itf.InterfaceMethodRewriter.getEmulateLibraryInterfaceClassType;
-import static com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.identifyIdentifier;
 import static com.android.tools.r8.naming.IdentifierNameStringUtils.isReflectionMethod;
 import static com.android.tools.r8.shaking.AnnotationRemover.shouldKeepAnnotation;
@@ -1744,7 +1743,6 @@
     assert !mode.isFinalMainDexTracing()
             || !options.testing.checkForNotExpandingMainDexTracingResult
             || appView.appInfo().getMainDexInfo().isTracedRoot(clazz, appView.getSyntheticItems())
-            || clazz.toSourceString().contains(ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)
         : "Class " + clazz.toSourceString() + " was not a main dex root in the first round";
 
     // Mark types in inner-class attributes referenced.
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
index 0326e0b..faf1061 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -8,9 +8,8 @@
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.enumunboxing.DoubleProcessingEnumUnboxingTest.App.AppEnum;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
-import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingUtilityClasses;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -78,19 +77,20 @@
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(this::assertUtilityClassPresent)
+        .inspect(this::assertSharedUtilityClassPresent)
         .run(parameters.getRuntime(), App.class)
         .assertSuccess()
         .inspectStdOut(this::assertLines2By2Correct);
   }
 
-  private void assertUtilityClassPresent(CodeInspector codeInspector) {
+  private void assertSharedUtilityClassPresent(CodeInspector codeInspector) {
     assertTrue(
         codeInspector.allClasses().stream()
             .anyMatch(
                 c ->
                     c.getOriginalName()
-                        .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
+                        .contains(
+                            EnumUnboxingUtilityClasses.ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
   }
 
   static class App {
diff --git a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
index 44cdff0..58d220f 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -10,7 +10,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
 import com.android.tools.r8.enumunboxing.examplelib2.JavaLibrary2;
-import com.android.tools.r8.ir.optimize.enums.UnboxedEnumMemberRelocator;
+import com.android.tools.r8.ir.optimize.enums.EnumUnboxingUtilityClasses;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
@@ -68,7 +68,7 @@
         .addOptionsModification(opt -> enableEnumOptions(opt, enumValueOptimization))
         .setMinApi(parameters.getApiLevel())
         .compile()
-        .inspect(this::assertUtilityClassPresent)
+        .inspect(this::assertSharedUtilityClassPresent)
         .run(parameters.getRuntime(), App.class)
         .assertSuccess()
         .inspectStdOut(this::assertLines2By2Correct);
@@ -88,13 +88,14 @@
         .writeToZip();
   }
 
-  private void assertUtilityClassPresent(CodeInspector codeInspector) {
+  private void assertSharedUtilityClassPresent(CodeInspector codeInspector) {
     assertTrue(
         codeInspector.allClasses().stream()
             .anyMatch(
                 c ->
                     c.getOriginalName()
-                        .contains(UnboxedEnumMemberRelocator.ENUM_UNBOXING_UTILITY_CLASS_SUFFIX)));
+                        .contains(
+                            EnumUnboxingUtilityClasses.ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
   }
 
   static class App {