Support Enum.compareTo(Object) in enum unboxer

This also moves the synthesis of the shared enum unboxing utility class and the compareTo utility method to the synthetic infrastructure.

Bug: 190098858
Change-Id: I237ee2f67afaebe12eea79eb02f80f4ce60a5030
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index 36d67f5..49cab56 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import static com.android.tools.r8.dex.Constants.ACC_STATIC;
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_PREFIX;
 import static com.android.tools.r8.graph.DexCode.FAKE_THIS_SUFFIX;
 
@@ -896,15 +895,11 @@
 
   private Int2ReferenceSortedMap<FrameType> computeInitialLocals(
       DexType context, DexEncodedMethod method, RewrittenPrototypeDescription protoTypeChanges) {
-    int accessFlags =
-        protoTypeChanges.isEmpty()
-            ? method.accessFlags.modifiedFlags
-            : method.accessFlags.originalFlags;
     Int2ReferenceSortedMap<FrameType> initialLocals = new Int2ReferenceAVLTreeMap<>();
     int index = 0;
     if (method.isInstanceInitializer()) {
       initialLocals.put(index++, FrameType.uninitializedThis());
-    } else if (!MethodAccessFlags.isSet(ACC_STATIC, accessFlags)) {
+    } else if (!method.getAccessFlags().isStatic()) {
       initialLocals.put(index++, FrameType.initialized(context));
     }
     ArgumentInfoCollection argumentsInfo = protoTypeChanges.getArgumentInfoCollection();
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 90c145e..25cf31b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -1546,6 +1546,8 @@
     public final DexMethod nameMethod;
     public final DexMethod toString;
     public final DexMethod compareTo;
+    public final DexMethod compareToWithObject =
+        createMethod(enumType, createProto(intType, objectType), "compareTo");
     public final DexMethod equals;
     public final DexMethod hashCode;
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
index 4ba4555..9bcee66 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/D8MethodProcessor.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -55,6 +56,11 @@
   }
 
   @Override
+  public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+    return processorContext.createMethodProcessingContext(method);
+  }
+
+  @Override
   public boolean isProcessedConcurrently(ProgramMethod method) {
     // In D8 all methods are considered independently compiled.
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index d15bccf..fcfcb1f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -750,6 +750,11 @@
     }
     timing.end();
 
+    if (enumUnboxer != null) {
+      // TODO(b/190098858): Uncomment when methods are synthesized on-the-fly.
+      // enumUnboxer.unsetRewriter();
+    }
+
     // All the code that should be impacted by the lenses inserted between phase 1 and phase 2
     // have now been processed and rewritten, we clear code lens rewriting so that the class
     // staticizer and phase 3 does not perform again the rewriting.
@@ -1206,7 +1211,7 @@
     if (appView.graphLens().hasCodeRewritings()) {
       assert lensCodeRewriter != null;
       timing.begin("Lens rewrite");
-      lensCodeRewriter.rewrite(code, context);
+      lensCodeRewriter.rewrite(code, context, methodProcessor);
       timing.end();
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 70a2316..0cf836d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -140,9 +140,11 @@
   }
 
   /** Replace type appearances, invoke targets and field accesses with actual definitions. */
-  public void rewrite(IRCode code, ProgramMethod method) {
+  public void rewrite(IRCode code, ProgramMethod method, MethodProcessor methodProcessor) {
     Set<Phi> affectedPhis =
-        enumUnboxer != null ? enumUnboxer.rewriteCode(code) : Sets.newIdentityHashSet();
+        enumUnboxer != null
+            ? enumUnboxer.rewriteCode(code, methodProcessor)
+            : Sets.newIdentityHashSet();
     GraphLens graphLens = appView.graphLens();
     DexItemFactory factory = appView.dexItemFactory();
     // Rewriting types that affects phi can cause us to compute TOP for cyclic phi's. To solve this
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
index 140e0ce..9857098 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/MethodProcessor.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.graph.ProgramMethod;
 
 public abstract class MethodProcessor {
@@ -11,6 +12,8 @@
     return false;
   }
 
+  public abstract MethodProcessingContext createMethodProcessingContext(ProgramMethod method);
+
   public abstract boolean isProcessedConcurrently(ProgramMethod method);
 
   public abstract boolean shouldApplyCodeRewritings(ProgramMethod method);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
index f637225..e40cae1 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OneTimeMethodProcessor.java
@@ -45,6 +45,11 @@
   }
 
   @Override
+  public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+    return processorContext.createMethodProcessingContext(method);
+  }
+
+  @Override
   public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index fd1ecdf..a8e5508 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -43,6 +43,11 @@
   }
 
   @Override
+  public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+    return processorContext.createMethodProcessingContext(method);
+  }
+
+  @Override
   public boolean shouldApplyCodeRewritings(ProgramMethod method) {
     assert !wave.contains(method);
     return !processed.contains(method);
@@ -132,8 +137,7 @@
         assert feedback.noUpdatesLeft();
         ThreadUtils.processItems(
             wave,
-            method ->
-                consumer.accept(method, processorContext.createMethodProcessingContext(method)),
+            method -> consumer.accept(method, createMethodProcessingContext(method)),
             executorService);
         feedback.updateVisibleOptimizationInfo();
         processed.addAll(wave);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 8969cba..9bd75fe 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -41,6 +41,8 @@
   private final PostMethodProcessor.Builder postMethodProcessorBuilder;
   private final Deque<SortedProgramMethodSet> waves;
 
+  private ProcessorContext processorContext;
+
   private PrimaryMethodProcessor(
       AppView<AppInfoWithLiveness> appView,
       PostMethodProcessor.Builder postMethodProcessorBuilder,
@@ -62,6 +64,11 @@
   }
 
   @Override
+  public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+    return processorContext.createMethodProcessingContext(method);
+  }
+
+  @Override
   public boolean isPrimaryMethodProcessor() {
     return true;
   }
@@ -125,7 +132,7 @@
     TimingMerger merger =
         timing.beginMerger("primary-processor", ThreadUtils.getNumberOfThreads(executorService));
     while (!waves.isEmpty()) {
-      ProcessorContext processorContext = appView.createProcessorContext();
+      processorContext = appView.createProcessorContext();
       wave = waves.removeFirst();
       assert !wave.isEmpty();
       assert waveExtension.isEmpty();
@@ -135,9 +142,7 @@
             ThreadUtils.processItemsWithResults(
                 wave,
                 method -> {
-                  Timing time =
-                      consumer.apply(
-                          method, processorContext.createMethodProcessingContext(method));
+                  Timing time = consumer.apply(method, createMethodProcessingContext(method));
                   time.end();
                   return time;
                 },
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 9e82361..e4d045d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -732,7 +732,7 @@
 
       if (inliningIRProvider.shouldApplyCodeRewritings(target)) {
         assert lensCodeRewriter != null;
-        lensCodeRewriter.rewrite(code, target);
+        lensCodeRewriter.rewrite(code, target, inliningIRProvider.getMethodProcessor());
       }
       if (options.testing.inlineeIrModifier != null) {
         options.testing.inlineeIrModifier.accept(code);
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 7e0da20..ec450c9 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
@@ -71,6 +71,7 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.conversion.PostMethodProcessor;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.enums.EnumDataMap.EnumData;
@@ -533,12 +534,12 @@
         utilityClass -> utilityClass.getDefinition().forEachProgramMethod(postBuilder::add));
 
     fieldAccessInfoCollectionModifierBuilder.build().modify(appView);
-    enumUnboxerRewriter = new EnumUnboxingRewriter(appView, enumDataMap, utilityClasses);
     EnumUnboxingTreeFixer.Result treeFixerResult =
         new EnumUnboxingTreeFixer(appView, enumDataMap, enumClassesToUnbox, utilityClasses)
             .fixupTypeReferences(converter, executorService);
     EnumUnboxingLens enumUnboxingLens = treeFixerResult.getLens();
-    enumUnboxerRewriter.setEnumUnboxingLens(enumUnboxingLens);
+    enumUnboxerRewriter =
+        new EnumUnboxingRewriter(appView, converter, enumUnboxingLens, enumDataMap, utilityClasses);
     appView.setUnboxedEnums(enumDataMap);
     GraphLens previousLens = appView.graphLens();
     appView.rewriteWithLensAndApplication(enumUnboxingLens, appBuilder.build());
@@ -1287,8 +1288,13 @@
       return new UnsupportedLibraryInvokeReason(singleTargetReference);
     }
     // TODO(b/147860220): EnumSet and EnumMap may be interesting to model.
-    if (singleTargetReference == factory.enumMembers.compareTo) {
-      return Reason.ELIGIBLE;
+    if (singleTargetReference == factory.enumMembers.compareTo
+        || singleTargetReference == factory.enumMembers.compareToWithObject) {
+      DexProgramClass otherEnumClass =
+          getEnumUnboxingCandidateOrNull(invoke.getLastArgument().getType());
+      if (otherEnumClass == enumClass || invoke.getLastArgument().getType().isNullType()) {
+        return Reason.ELIGIBLE;
+      }
     } else if (singleTargetReference == factory.enumMembers.equals) {
       return Reason.ELIGIBLE;
     } else if (singleTargetReference == factory.enumMembers.nameMethod
@@ -1423,11 +1429,11 @@
     return false;
   }
 
-  public Set<Phi> rewriteCode(IRCode code) {
+  public Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
     // This has no effect during primary processing since the enumUnboxerRewriter is set
     // in between primary and post processing.
     if (enumUnboxerRewriter != null) {
-      return enumUnboxerRewriter.rewriteCode(code);
+      return enumUnboxerRewriter.rewriteCode(code, methodProcessor);
     }
     return Sets.newIdentityHashSet();
   }
@@ -1438,4 +1444,8 @@
       enumUnboxerRewriter.synthesizeEnumUnboxingUtilityMethods(converter, executorService);
     }
   }
+
+  public void unsetRewriter() {
+    enumUnboxerRewriter = null;
+  }
 }
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 6b8a013..ccc044e 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
@@ -42,6 +42,7 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.enums.EnumInstanceFieldData.EnumInstanceFieldKnownData;
 import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -69,10 +70,11 @@
   private static final CfVersion REQUIRED_CLASS_FILE_VERSION = CfVersion.V1_8;
 
   private final AppView<AppInfoWithLiveness> appView;
+  private final IRConverter converter;
   private final DexItemFactory factory;
   private final InternalOptions options;
   private final EnumDataMap unboxedEnumsData;
-  private EnumUnboxingLens enumUnboxingLens;
+  private final EnumUnboxingLens enumUnboxingLens;
   private final EnumUnboxingUtilityClasses utilityClasses;
 
   private final Map<DexMethod, DexEncodedMethod> utilityMethods = new ConcurrentHashMap<>();
@@ -85,11 +87,15 @@
 
   EnumUnboxingRewriter(
       AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      EnumUnboxingLens enumUnboxingLens,
       EnumDataMap unboxedEnumsInstanceFieldData,
       EnumUnboxingUtilityClasses utilityClasses) {
     this.appView = appView;
+    this.converter = converter;
     this.factory = appView.dexItemFactory();
     this.options = appView.options();
+    this.enumUnboxingLens = enumUnboxingLens;
     this.unboxedEnumsData = unboxedEnumsInstanceFieldData;
     this.utilityClasses = utilityClasses;
 
@@ -131,11 +137,7 @@
     return utilityClasses.getSharedUtilityClass();
   }
 
-  public void setEnumUnboxingLens(EnumUnboxingLens enumUnboxingLens) {
-    this.enumUnboxingLens = enumUnboxingLens;
-  }
-
-  Set<Phi> rewriteCode(IRCode code) {
+  Set<Phi> rewriteCode(IRCode code, MethodProcessor methodProcessor) {
     // We should not process the enum methods, they will be removed and they may contain invalid
     // rewriting rules.
     if (unboxedEnumsData.isEmpty()) {
@@ -182,9 +184,13 @@
               replaceEnumInvoke(
                   iterator, invokeMethod, equalsUtilityMethod, m -> synthesizeEqualsMethod());
               continue;
-            } else if (invokedMethod == factory.enumMembers.compareTo) {
+            } else if (invokedMethod == factory.enumMembers.compareTo
+                || invokedMethod == factory.enumMembers.compareToWithObject) {
               replaceEnumInvoke(
-                  iterator, invokeMethod, compareToUtilityMethod, m -> synthesizeCompareToMethod());
+                  iterator,
+                  invokeMethod,
+                  getSharedUtilityClass()
+                      .ensureCompareToMethod(appView, converter, methodProcessor));
               continue;
             } else if (invokedMethod == factory.enumMembers.nameMethod) {
               rewriteNameMethod(iterator, invokeMethod, enumType);
@@ -466,11 +472,18 @@
   }
 
   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) {
-    utilityMethods.computeIfAbsent(method, synthesizor);
+    if (synthesizor != null) {
+      utilityMethods.computeIfAbsent(method, synthesizor);
+    }
     InvokeStatic replacement =
         new InvokeStatic(
             method, invoke.hasUnusedOutValue() ? null : invoke.outValue(), invoke.arguments());
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
index 1753375..05d4955 100644
--- 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
@@ -73,7 +73,7 @@
       SharedEnumUnboxingUtilityClass sharedUtilityClass =
           SharedEnumUnboxingUtilityClass.builder(
                   appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder)
-              .build(appBuilder);
+              .build();
       ImmutableMap<DexType, LocalEnumUnboxingUtilityClass> localUtilityClasses =
           createLocalUtilityClasses(enumsToUnbox, appBuilder);
       this.localUtilityClasses = localUtilityClasses;
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 19adc9f..3e556e6 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
@@ -26,21 +26,21 @@
 import com.android.tools.r8.graph.DexItemFactory;
 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.GenericSignature.FieldTypeSignature;
 import com.android.tools.r8.graph.GenericSignature.MethodTypeSignature;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
-import com.android.tools.r8.graph.ProgramField;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.origin.SynthesizedOrigin;
+import com.android.tools.r8.ir.conversion.IRConverter;
+import com.android.tools.r8.ir.conversion.MethodProcessor;
+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.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.utils.ConsumerUtils;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -50,17 +50,16 @@
 
 public class SharedEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
 
-  public static final String ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX =
-      "$r8$EnumUnboxingSharedUtility";
-
   private final DexProgramClass sharedUtilityClass;
-  private final ProgramField valuesField;
+  private final DexProgramClass synthesizingContext;
   private final ProgramMethod valuesMethod;
 
   public SharedEnumUnboxingUtilityClass(
-      DexProgramClass sharedUtilityClass, ProgramField valuesField, ProgramMethod valuesMethod) {
+      DexProgramClass sharedUtilityClass,
+      DexProgramClass synthesizingContext,
+      ProgramMethod valuesMethod) {
     this.sharedUtilityClass = sharedUtilityClass;
-    this.valuesField = valuesField;
+    this.synthesizingContext = synthesizingContext;
     this.valuesMethod = valuesMethod;
   }
 
@@ -73,15 +72,46 @@
         appView, enumDataMap, enumsToUnbox, fieldAccessInfoCollectionModifierBuilder);
   }
 
+  public ProgramMethod ensureCompareToMethod(
+      AppView<AppInfoWithLiveness> appView,
+      IRConverter converter,
+      MethodProcessor methodProcessor) {
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    // 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
+    //  not one per use context).
+    return appView
+        .getSyntheticItems()
+        .ensureFixedClassMethod(
+            dexItemFactory.enumMembers.compareTo.getName(),
+            dexItemFactory.createProto(
+                dexItemFactory.intType, dexItemFactory.intType, dexItemFactory.intType),
+            SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
+            synthesizingContext,
+            appView,
+            ConsumerUtils.emptyConsumer(),
+            methodBuilder ->
+                methodBuilder
+                    .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
+                    .setCode(
+                        method ->
+                            EnumUnboxingCfMethods.EnumUnboxingMethods_compareTo(
+                                appView.options(), method))
+                    .setClassFileVersion(CfVersion.V1_6),
+            newMethod ->
+                converter.processDesugaredMethod(
+                    newMethod,
+                    OptimizationFeedbackSimple.getInstance(),
+                    methodProcessor,
+                    methodProcessor.createMethodProcessingContext(newMethod)));
+  }
+
   @Override
   public DexProgramClass getDefinition() {
     return sharedUtilityClass;
   }
 
-  public ProgramField getValuesField() {
-    return valuesField;
-  }
-
   public ProgramMethod getValuesMethod() {
     return valuesMethod;
   }
@@ -95,12 +125,10 @@
     private final AppView<AppInfoWithLiveness> appView;
     private final DexItemFactory dexItemFactory;
     private final EnumDataMap enumDataMap;
-    private final Set<DexProgramClass> enumsToUnbox;
     private final FieldAccessInfoCollectionModifier.Builder
         fieldAccessInfoCollectionModifierBuilder;
-    private final DexType sharedUtilityClassType;
+    private final DexProgramClass synthesizingContext;
 
-    private DexEncodedField valuesField;
     private DexEncodedMethod valuesMethod;
 
     private Builder(
@@ -108,52 +136,40 @@
         EnumDataMap enumDataMap,
         Set<DexProgramClass> enumsToUnbox,
         FieldAccessInfoCollectionModifier.Builder fieldAccessInfoCollectionModifierBuilder) {
+      DexProgramClass synthesizingContext = findDeterministicContextType(enumsToUnbox);
       this.appView = appView;
       this.dexItemFactory = appView.dexItemFactory();
       this.enumDataMap = enumDataMap;
-      this.enumsToUnbox = enumsToUnbox;
       this.fieldAccessInfoCollectionModifierBuilder = fieldAccessInfoCollectionModifierBuilder;
-      this.sharedUtilityClassType =
-          EnumUnboxingUtilityClasses.Builder.getUtilityClassType(
-              findDeterministicContextType(enumsToUnbox),
-              ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX,
-              dexItemFactory);
-
-      assert appView.appInfo().definitionForWithoutExistenceAssert(sharedUtilityClassType) == null;
+      this.synthesizingContext = synthesizingContext;
     }
 
-    SharedEnumUnboxingUtilityClass build(DirectMappedDexApplication.Builder appBuilder) {
+    SharedEnumUnboxingUtilityClass build() {
       DexProgramClass clazz = createClass();
-      appBuilder.addSynthesizedClass(clazz);
-      appView.appInfo().addSynthesizedClassToBase(clazz, enumsToUnbox);
       return new SharedEnumUnboxingUtilityClass(
-          clazz, new ProgramField(clazz, valuesField), new ProgramMethod(clazz, valuesMethod));
+          clazz, synthesizingContext, new ProgramMethod(clazz, valuesMethod));
     }
 
     private DexProgramClass createClass() {
-      DexEncodedField valuesField = createValuesField(sharedUtilityClassType);
-      return new DexProgramClass(
-          sharedUtilityClassType,
-          null,
-          new SynthesizedOrigin("enum unboxing", EnumUnboxer.class),
-          ClassAccessFlags.createPublicFinalSynthetic(),
-          dexItemFactory.objectType,
-          DexTypeList.empty(),
-          null,
-          null,
-          Collections.emptyList(),
-          null,
-          Collections.emptyList(),
-          ClassSignature.noSignature(),
-          DexAnnotationSet.empty(),
-          new DexEncodedField[] {valuesField},
-          DexEncodedField.EMPTY_ARRAY,
-          new DexEncodedMethod[] {
-            createClassInitializer(valuesField), createValuesMethod(valuesField)
-          },
-          DexEncodedMethod.EMPTY_ARRAY,
-          dexItemFactory.getSkipNameValidationForTesting(),
-          DexProgramClass::checksumFromType);
+      DexProgramClass clazz =
+          appView
+              .getSyntheticItems()
+              .createFixedClass(
+                  SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
+                  synthesizingContext,
+                  appView,
+                  classBuilder -> {
+                    DexType sharedUtilityClassType = classBuilder.getType();
+                    DexEncodedField valuesField = createValuesField(sharedUtilityClassType);
+                    classBuilder
+                        .setDirectMethods(
+                            ImmutableList.of(
+                                createClassInitializer(sharedUtilityClassType, valuesField),
+                                createValuesMethod(sharedUtilityClassType, valuesField)))
+                        .setStaticFields(ImmutableList.of(valuesField));
+                  });
+      assert clazz.getAccessFlags().equals(ClassAccessFlags.createPublicFinalSynthetic());
+      return clazz;
     }
 
     // Fields.
@@ -172,25 +188,26 @@
       fieldAccessInfoCollectionModifierBuilder
           .recordFieldReadInUnknownContext(valuesField.getReference())
           .recordFieldWriteInUnknownContext(valuesField.getReference());
-      this.valuesField = valuesField;
       return valuesField;
     }
 
     // Methods.
 
-    private DexEncodedMethod createClassInitializer(DexEncodedField valuesField) {
+    private DexEncodedMethod createClassInitializer(
+        DexType sharedUtilityClassType, DexEncodedField valuesField) {
       return new DexEncodedMethod(
           dexItemFactory.createClassInitializer(sharedUtilityClassType),
           MethodAccessFlags.createForClassInitializer(),
           MethodTypeSignature.noSignature(),
           DexAnnotationSet.empty(),
           ParameterAnnotationsList.empty(),
-          createClassInitializerCode(valuesField),
+          createClassInitializerCode(sharedUtilityClassType, valuesField),
           DexEncodedMethod.D8_R8_SYNTHESIZED,
           CfVersion.V1_6);
     }
 
-    private CfCode createClassInitializerCode(DexEncodedField valuesField) {
+    private CfCode createClassInitializerCode(
+        DexType sharedUtilityClassType, DexEncodedField valuesField) {
       int maxValuesArraySize = enumDataMap.getMaxValuesSize();
       int numberOfInstructions = 4 + maxValuesArraySize * 4;
       List<CfInstruction> instructions = new ArrayList<>(numberOfInstructions);
@@ -217,7 +234,8 @@
           Collections.emptyList());
     }
 
-    private DexEncodedMethod createValuesMethod(DexEncodedField valuesField) {
+    private DexEncodedMethod createValuesMethod(
+        DexType sharedUtilityClassType, DexEncodedField valuesField) {
       DexEncodedMethod valuesMethod =
           new DexEncodedMethod(
               dexItemFactory.createMethod(
@@ -228,14 +246,15 @@
               MethodTypeSignature.noSignature(),
               DexAnnotationSet.empty(),
               ParameterAnnotationsList.empty(),
-              createValuesMethodCode(valuesField),
+              createValuesMethodCode(sharedUtilityClassType, valuesField),
               DexEncodedMethod.D8_R8_SYNTHESIZED,
               CfVersion.V1_6);
       this.valuesMethod = valuesMethod;
       return valuesMethod;
     }
 
-    private CfCode createValuesMethodCode(DexEncodedField valuesField) {
+    private CfCode createValuesMethodCode(
+        DexType sharedUtilityClassType, DexEncodedField valuesField) {
       int maxStack = 5;
       int maxLocals = 2;
       int argumentLocalSlot = 0;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
index dba0cde..35afed1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/inliner/InliningIRProvider.java
@@ -60,6 +60,10 @@
     assert existing == null;
   }
 
+  public MethodProcessor getMethodProcessor() {
+    return methodProcessor;
+  }
+
   public boolean verifyIRCacheIsEmpty() {
     assert cache.isEmpty();
     return true;
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
index f7e235f..f9bdee5 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import static com.android.tools.r8.utils.ConsumerUtils.emptyConsumer;
+
 import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
@@ -568,15 +570,37 @@
       DexString name,
       DexProto proto,
       SyntheticKind kind,
-      DexProgramClass context,
+      ProgramDefinition context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> buildClassCallback,
       Consumer<SyntheticMethodBuilder> buildMethodCallback) {
+    return ensureFixedClassMethod(
+        name,
+        proto,
+        kind,
+        context,
+        appView,
+        buildClassCallback,
+        buildMethodCallback,
+        emptyConsumer());
+  }
+
+  public ProgramMethod ensureFixedClassMethod(
+      DexString name,
+      DexProto proto,
+      SyntheticKind kind,
+      ProgramDefinition context,
+      AppView<?> appView,
+      Consumer<SyntheticProgramClassBuilder> buildClassCallback,
+      Consumer<SyntheticMethodBuilder> buildMethodCallback,
+      Consumer<ProgramMethod> newMethodCallback) {
     DexProgramClass clazz =
-        ensureFixedClass(kind, context, appView, buildClassCallback, ignored -> {});
+        ensureFixedClass(
+            kind, context.getContextClass(), appView, buildClassCallback, emptyConsumer());
     DexMethod methodReference = appView.dexItemFactory().createMethod(clazz.getType(), proto, name);
     DexEncodedMethod methodDefinition =
-        internalEnsureMethod(methodReference, clazz, kind, appView, buildMethodCallback);
+        internalEnsureMethod(
+            methodReference, clazz, kind, appView, buildMethodCallback, newMethodCallback);
     return new ProgramMethod(clazz, methodDefinition);
   }
 
@@ -637,16 +661,18 @@
     DexMethod methodReference =
         appView.dexItemFactory().createMethod(clazz.getType(), methodProto, methodName);
     DexEncodedMethod methodDefinition =
-        internalEnsureMethod(methodReference, clazz, kind, appView, buildMethodCallback);
+        internalEnsureMethod(
+            methodReference, clazz, kind, appView, buildMethodCallback, emptyConsumer());
     return DexClassAndMethod.create(clazz, methodDefinition);
   }
 
-  private DexEncodedMethod internalEnsureMethod(
+  private <T extends DexClassAndMethod> DexEncodedMethod internalEnsureMethod(
       DexMethod methodReference,
       DexClass clazz,
       SyntheticKind kind,
       AppView<?> appView,
-      Consumer<SyntheticMethodBuilder> buildMethodCallback) {
+      Consumer<SyntheticMethodBuilder> buildMethodCallback,
+      Consumer<T> newMethodCallback) {
     MethodCollection methodCollection = clazz.getMethodCollection();
     DexEncodedMethod methodDefinition = methodCollection.getMethod(methodReference);
     if (methodDefinition != null) {
@@ -667,6 +693,7 @@
       //  and the creation of the method code. The code can then be constructed outside the lock.
       methodDefinition = builder.build();
       methodCollection.addMethod(methodDefinition);
+      newMethodCallback.accept((T) DexClassAndMethod.create(clazz, methodDefinition));
       return methodDefinition;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index c142281..edee559 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -9,6 +9,8 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.DescriptorUtils;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 
 public class SyntheticNaming {
 
@@ -23,6 +25,7 @@
    */
   public enum SyntheticKind {
     // Class synthetics.
+    ENUM_UNBOXING_SHARED_UTILITY_CLASS("$EnumUnboxingSharedUtility", 24, false, true),
     RECORD_TAG("", 1, false, true, true),
     COMPANION_CLASS("$-CC", 2, false, true),
     EMULATED_INTERFACE_CLASS("$-EL", 3, false, true),
@@ -48,6 +51,10 @@
     SERVICE_LOADER("ServiceLoad", 18, true),
     OUTLINE("Outline", 19, true);
 
+    static {
+      assert verifyNoOverlappingIds();
+    }
+
     public final String descriptor;
     public final int id;
     public final boolean isSingleSyntheticMethod;
@@ -100,6 +107,16 @@
       }
       return null;
     }
+
+    private static boolean verifyNoOverlappingIds() {
+      Int2ReferenceMap<SyntheticKind> idToKind = new Int2ReferenceOpenHashMap<>();
+      for (SyntheticKind kind : values()) {
+        SyntheticKind kindWithSameId = idToKind.put(kind.id, kind);
+        assert kindWithSameId == null
+            : "Synthetic kind " + idToKind + " has same id as " + kindWithSameId;
+      }
+      return true;
+    }
   }
 
   private static final String SYNTHETIC_CLASS_SEPARATOR = "$$";
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 e7b5dce..e77f42a 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingEnumUnboxingTest.java
@@ -9,8 +9,8 @@
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.enumunboxing.examplelib1.JavaLibrary1;
-import com.android.tools.r8.ir.optimize.enums.SharedEnumUnboxingUtilityClass;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
@@ -87,11 +87,9 @@
     assertTrue(
         codeInspector.allClasses().stream()
             .anyMatch(
-                c ->
-                    c.getOriginalName()
-                        .contains(
-                            SharedEnumUnboxingUtilityClass
-                                .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
+                clazz ->
+                    SyntheticItemsTestUtils.isEnumUnboxingSharedUtilityClass(
+                        clazz.getOriginalReference())));
   }
 
   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 4ef092d..e7651fb 100644
--- a/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
+++ b/src/test/java/com/android/tools/r8/enumunboxing/DoubleProcessingMergeEnumUnboxingTest.java
@@ -10,8 +10,8 @@
 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.SharedEnumUnboxingUtilityClass;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
@@ -92,11 +92,9 @@
     assertTrue(
         codeInspector.allClasses().stream()
             .anyMatch(
-                c ->
-                    c.getOriginalName()
-                        .contains(
-                            SharedEnumUnboxingUtilityClass
-                                .ENUM_UNBOXING_SHARED_UTILITY_CLASS_SUFFIX)));
+                clazz ->
+                    SyntheticItemsTestUtils.isEnumUnboxingSharedUtilityClass(
+                        clazz.getOriginalReference())));
   }
 
   static class App {
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
index ec665d3..252b11d 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldBitAccessInfoTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -212,6 +213,11 @@
   static class PrimaryMethodProcessorMock extends MethodProcessorWithWave {
 
     @Override
+    public MethodProcessingContext createMethodProcessingContext(ProgramMethod method) {
+      throw new Unreachable();
+    }
+
+    @Override
     public boolean shouldApplyCodeRewritings(ProgramMethod method) {
       return false;
     }
diff --git a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index dfcf7e8..1b4ec31 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -65,6 +65,11 @@
         originalMethod.getMethodDescriptor());
   }
 
+  public static boolean isEnumUnboxingSharedUtilityClass(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(
+        reference, null, SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS);
+  }
+
   public static boolean isExternalSynthetic(ClassReference reference) {
     for (SyntheticKind kind : SyntheticKind.values()) {
       if (kind == SyntheticKind.RECORD_TAG) {