Merge commit '65918686c5697b32d3d8cbb9f3624d4b55e50f15' into dev-release
diff --git a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
index 9b48471..558386a 100644
--- a/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
+++ b/src/library_desugar/jdk11/desugar_jdk_libs_alternative_3.json
@@ -1,5 +1,5 @@
 {
-  "identifier": "com.tools.android:desugar_jdk_libs_alternative_3:1.0.12",
+  "identifier": "com.tools.android:desugar_jdk_libs_alternative_3:2.0.0",
   "configuration_format_version": 100,
   "required_compilation_api_level": 30,
   "synthesized_library_classes_package_prefix": "j$.",
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 885ada8..d479e46 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -70,7 +70,6 @@
 import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.optimize.ClassAndMemberPublicizer;
 import com.android.tools.r8.optimize.MemberRebindingAnalysis;
-import com.android.tools.r8.optimize.MemberRebindingIdentityLens;
 import com.android.tools.r8.optimize.MemberRebindingIdentityLensFactory;
 import com.android.tools.r8.optimize.VisibilityBridgeRemover;
 import com.android.tools.r8.optimize.bridgehoisting.BridgeHoisting;
@@ -290,7 +289,6 @@
         DirectMappedDexApplication application = applicationReader.read(executorService).toDirect();
         options.loadMachineDesugaredLibrarySpecification(timing, application);
         MainDexInfo mainDexInfo = applicationReader.readMainDexClassesForR8(application);
-
         // Now that the dex-application is fully loaded, close any internal archive providers.
         inputApp.closeInternalArchiveProviders();
 
@@ -712,9 +710,15 @@
         assert !options.isShrinking();
       }
 
-      MemberRebindingIdentityLens memberRebindingLens =
-          MemberRebindingIdentityLensFactory.create(appView, executorService);
-      appView.setGraphLens(memberRebindingLens);
+      // Insert a member rebinding oracle in the graph to ensure that all subsequent rewritings of
+      // the application has an applied oracle for looking up non-rebound references.
+      appView.setGraphLens(MemberRebindingIdentityLensFactory.create(appView, executorService));
+
+      if (appView.appInfo().hasLiveness()) {
+        SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService);
+      } else {
+        SyntheticFinalization.finalizeWithClassHierarchy(appView, executorService);
+      }
 
       // Read any -applymapping input to allow for repackaging to not relocate the classes.
       timing.begin("read -applymapping file");
@@ -728,24 +732,13 @@
         RepackagingLens lens =
             new Repackaging(appView.withLiveness()).run(appBuilder, executorService, timing);
         if (lens != null) {
-          // Specify to use the member rebinding lens as the parent lens during the rewriting. This
-          // is needed to ensure that the rebound references are available during lens lookups.
-          // TODO(b/168282032): This call-site should not have to think about the parent lens that
-          //  is used for the rewriting. Once the new member rebinding lens replaces the old member
-          //  rebinding analysis it should be possible to clean this up.
-          appView.rewriteWithLensAndApplication(
-              lens, appBuilder.build(), memberRebindingLens.getPrevious());
+          appView.rewriteWithLensAndApplication(lens, appBuilder.build());
         }
       }
       if (appView.appInfo().hasLiveness()) {
         assert Repackaging.verifyIdentityRepackaging(appView.withLiveness());
       }
 
-      if (appView.appInfo().hasLiveness()) {
-        SyntheticFinalization.finalizeWithLiveness(appView.withLiveness(), executorService);
-      } else {
-        SyntheticFinalization.finalizeWithClassHierarchy(appView, executorService);
-      }
 
       // Clear the reference type lattice element cache. This is required since class merging may
       // need to build IR.
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 6a2d78d..5725955 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.graph.UseRegistry;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.CommittedItems;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
@@ -156,7 +155,7 @@
   public void processClass(DexProgramClass clazz) {
     if (appView
         .getSyntheticItems()
-        .isSyntheticOfKind(clazz.getType(), SyntheticKind.API_MODEL_OUTLINE)) {
+        .isSyntheticOfKind(clazz.getType(), kinds -> kinds.API_MODEL_OUTLINE)) {
       return;
     }
     findReferencedLibraryClasses(clazz.type);
@@ -235,7 +234,7 @@
         .appInfo()
         .getSyntheticItems()
         .ensureFixedClassFromType(
-            SyntheticKind.API_MODEL_STUB,
+            kinds -> kinds.API_MODEL_STUB,
             libraryClass.getType(),
             appView,
             classBuilder -> {
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index b740f3d..db99838 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -795,6 +795,7 @@
     while (firstUnappliedLens.getPrevious() != appliedLens) {
       GraphLens previousLens = firstUnappliedLens.getPrevious();
       assert previousLens.isNonIdentityLens();
+      assert previousLens != appView.codeLens();
       firstUnappliedLens = previousLens.asNonIdentityLens();
     }
 
@@ -802,22 +803,24 @@
     // TODO(b/182129249): Once the member rebinding phase has been removed, the MemberRebindingLens
     //  should be removed and all uses of FieldRebindingIdentityLens should be replaced by
     //  MemberRebindingIdentityLens.
-    NonIdentityGraphLens appliedMemberRebindingLens =
-        firstUnappliedLens.findPrevious(
-            previous ->
-                previous.isMemberRebindingLens() || previous.isMemberRebindingIdentityLens());
-    GraphLens newMemberRebindingLens;
-    if (appliedMemberRebindingLens != null) {
-      newMemberRebindingLens =
-          appliedMemberRebindingLens.isMemberRebindingLens()
-              ? appliedMemberRebindingLens
-                  .asMemberRebindingLens()
-                  .toRewrittenFieldRebindingLens(appView, appliedLens)
-              : appliedMemberRebindingLens
-                  .asMemberRebindingIdentityLens()
-                  .toRewrittenMemberRebindingIdentityLens(appView, appliedLens);
-    } else {
-      newMemberRebindingLens = GraphLens.getIdentityLens();
+    GraphLens newMemberRebindingLens = GraphLens.getIdentityLens();
+    if (!firstUnappliedLens.isMemberRebindingLens()
+        && !firstUnappliedLens.isMemberRebindingIdentityLens()) {
+      NonIdentityGraphLens appliedMemberRebindingLens =
+          firstUnappliedLens.findPreviousUntil(
+              previous ->
+                  previous.isMemberRebindingLens() || previous.isMemberRebindingIdentityLens(),
+              previous -> previous == appView.codeLens());
+      if (appliedMemberRebindingLens != null) {
+        newMemberRebindingLens =
+            appliedMemberRebindingLens.isMemberRebindingLens()
+                ? appliedMemberRebindingLens
+                    .asMemberRebindingLens()
+                    .toRewrittenFieldRebindingLens(appView, appliedLens)
+                : appliedMemberRebindingLens
+                    .asMemberRebindingIdentityLens()
+                    .toRewrittenMemberRebindingIdentityLens(appView, appliedLens);
+      }
     }
 
     firstUnappliedLens.withAlternativeParentLens(
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
index 366ad9c..5a2bffc 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.DexValue.DexValueType;
 import com.android.tools.r8.ir.desugar.CovariantReturnTypeAnnotationTransformer;
-import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Pair;
@@ -391,9 +391,7 @@
   public static DexAnnotation createAnnotationSynthesizedClass(
       SyntheticKind kind, DexItemFactory dexItemFactory) {
     DexAnnotationElement kindElement =
-        new DexAnnotationElement(
-            dexItemFactory.kindString,
-            new DexValueString(dexItemFactory.createString(kind.descriptor)));
+        new DexAnnotationElement(dexItemFactory.kindString, DexValueInt.create(kind.getId()));
     DexAnnotationElement[] elements = new DexAnnotationElement[] {kindElement};
     return new DexAnnotation(
         VISIBILITY_BUILD,
@@ -401,12 +399,12 @@
   }
 
   public static boolean hasSynthesizedClassAnnotation(
-      DexAnnotationSet annotations, DexItemFactory factory) {
-    return getSynthesizedClassAnnotationInfo(annotations, factory) != null;
+      DexAnnotationSet annotations, DexItemFactory factory, SyntheticItems synthetics) {
+    return getSynthesizedClassAnnotationInfo(annotations, factory, synthetics) != null;
   }
 
   public static SyntheticKind getSynthesizedClassAnnotationInfo(
-      DexAnnotationSet annotations, DexItemFactory factory) {
+      DexAnnotationSet annotations, DexItemFactory factory, SyntheticItems synthetics) {
     if (annotations.size() != 1) {
       return null;
     }
@@ -423,12 +421,11 @@
     if (kindElement.name != factory.kindString) {
       return null;
     }
-    if (!kindElement.value.isDexValueString()) {
+    if (!kindElement.value.isDexValueInt()) {
       return null;
     }
     SyntheticKind kind =
-        SyntheticNaming.SyntheticKind.fromDescriptor(
-            kindElement.value.asDexValueString().getValue().toString());
+        synthetics.getNaming().fromId(kindElement.value.asDexValueInt().getValue());
     return kind;
   }
 
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 34e4809..6259d4c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -35,6 +35,7 @@
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.ArrayUtils;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
@@ -682,6 +683,12 @@
   public final StringConcatFactoryMembers stringConcatFactoryMembers =
       new StringConcatFactoryMembers();
 
+  private final SyntheticNaming syntheticNaming = new SyntheticNaming();
+
+  public SyntheticNaming getSyntheticNaming() {
+    return syntheticNaming;
+  }
+
   public final BiMap<DexType, DexType> primitiveToBoxed = HashBiMap.create(
       ImmutableMap.<DexType, DexType>builder()
           .put(booleanType, boxedBooleanType)
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLens.java b/src/main/java/com/android/tools/r8/graph/GraphLens.java
index 952c31d..188211d 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLens.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLens.java
@@ -400,7 +400,7 @@
 
   public abstract DexType lookupType(DexType type, GraphLens applied);
 
-  // This overload can be used when the graph lens is known to be context insensitive.
+  @Deprecated
   public final DexMethod lookupMethod(DexMethod method) {
     assert verifyIsContextFreeForMethod(method);
     return lookupMethod(method, null, null).getReference();
@@ -410,22 +410,47 @@
     return lookupMethod(method, context.getReference(), Type.DIRECT);
   }
 
+  public final MethodLookupResult lookupInvokeDirect(
+      DexMethod method, ProgramMethod context, GraphLens codeLens) {
+    return lookupMethod(method, context.getReference(), Type.DIRECT, codeLens);
+  }
+
   public final MethodLookupResult lookupInvokeInterface(DexMethod method, ProgramMethod context) {
     return lookupMethod(method, context.getReference(), Type.INTERFACE);
   }
 
+  public final MethodLookupResult lookupInvokeInterface(
+      DexMethod method, ProgramMethod context, GraphLens codeLens) {
+    return lookupMethod(method, context.getReference(), Type.INTERFACE, codeLens);
+  }
+
   public final MethodLookupResult lookupInvokeStatic(DexMethod method, ProgramMethod context) {
     return lookupMethod(method, context.getReference(), Type.STATIC);
   }
 
+  public final MethodLookupResult lookupInvokeStatic(
+      DexMethod method, ProgramMethod context, GraphLens codeLens) {
+    return lookupMethod(method, context.getReference(), Type.STATIC, codeLens);
+  }
+
   public final MethodLookupResult lookupInvokeSuper(DexMethod method, ProgramMethod context) {
     return lookupMethod(method, context.getReference(), Type.SUPER);
   }
 
+  public final MethodLookupResult lookupInvokeSuper(
+      DexMethod method, ProgramMethod context, GraphLens codeLens) {
+    return lookupMethod(method, context.getReference(), Type.SUPER, codeLens);
+  }
+
   public final MethodLookupResult lookupInvokeVirtual(DexMethod method, ProgramMethod context) {
     return lookupMethod(method, context.getReference(), Type.VIRTUAL);
   }
 
+  public final MethodLookupResult lookupInvokeVirtual(
+      DexMethod method, ProgramMethod context, GraphLens codeLens) {
+    return lookupMethod(method, context.getReference(), Type.VIRTUAL, codeLens);
+  }
+
   public final MethodLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
     return lookupMethod(method, context, type, null);
   }
@@ -816,6 +841,13 @@
       return previous.isNonIdentityLens() ? previous.asNonIdentityLens().find(predicate) : null;
     }
 
+    public final <T extends NonIdentityGraphLens> T findPreviousUntil(
+        Predicate<NonIdentityGraphLens> predicate,
+        Predicate<NonIdentityGraphLens> stoppingCriterion) {
+      T found = findPrevious(predicate.or(stoppingCriterion));
+      return (found == null || stoppingCriterion.test(found)) ? null : found;
+    }
+
     public final void withAlternativeParentLens(GraphLens lens, Action action) {
       GraphLens oldParent = getPrevious();
       previousLens = lens;
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index ee7bc18..afbda8d 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -124,7 +124,9 @@
     }
     reader.accept(
         new CreateDexClassVisitor<>(origin, classKind, reader.b, application, classConsumer),
-        new Attribute[] {SyntheticMarker.getMarkerAttributePrototype()},
+        new Attribute[] {
+          SyntheticMarker.getMarkerAttributePrototype(application.getFactory().getSyntheticNaming())
+        },
         parsingOptions);
 
     // Read marker.
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
index 88a4755..ec19e83 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -8,7 +8,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -50,21 +50,22 @@
       this.appView = appView;
     }
 
-    private DexProgramClass synthesizeClass(DexProgramClass context, SyntheticKind syntheticKind) {
+    private DexProgramClass synthesizeClass(
+        DexProgramClass context, SyntheticKindSelector syntheticKindSelector) {
       return appView
           .getSyntheticItems()
-          .createFixedClass(syntheticKind, context, appView, builder -> {});
+          .createFixedClass(syntheticKindSelector, context, appView, builder -> {});
     }
 
     public SyntheticArgumentClass build(Collection<MergeGroup> mergeGroups) {
       DexProgramClass context = getDeterministicContext(mergeGroups);
       List<DexType> syntheticArgumentTypes = new ArrayList<>();
       syntheticArgumentTypes.add(
-          synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1).getType());
+          synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_1).getType());
       syntheticArgumentTypes.add(
-          synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_2).getType());
+          synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_2).getType());
       syntheticArgumentTypes.add(
-          synthesizeClass(context, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3).getType());
+          synthesizeClass(context, kinds -> kinds.HORIZONTAL_INIT_TYPE_ARGUMENT_3).getType());
       return new SyntheticArgumentClass(syntheticArgumentTypes);
     }
 
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 9cf8f31..3da2a7e 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
@@ -1313,7 +1313,7 @@
     codeRewriter.rewriteKnownArrayLengthCalls(code);
     timing.end();
     timing.begin("Natural Int Loop Remover");
-    naturalIntLoopRemover.run(code);
+    naturalIntLoopRemover.run(appView, code);
     timing.end();
     timing.begin("Rewrite AssertionError");
     codeRewriter.rewriteAssertionErrorTwoArgumentConstructor(code, options);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index b799580b..eb5a30b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.desugar;
 
+import com.android.tools.r8.androidapi.ComputedApiLevel;
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
@@ -40,6 +41,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -94,7 +96,7 @@
         && getMethodProviderOrNull(instruction.asInvoke().getMethod()) != null
         && !appView
             .getSyntheticItems()
-            .isSyntheticOfKind(context.getContextType(), SyntheticKind.BACKPORT_WITH_FORWARDING);
+            .isSyntheticOfKind(context.getContextType(), kinds -> kinds.BACKPORT_WITH_FORWARDING);
   }
 
   public static List<DexMethod> generateListOfBackportedMethods(
@@ -148,6 +150,8 @@
 
   private static final class RewritableMethods {
 
+    private final Map<DexType, AndroidApiLevel> typeMinApi;
+
     private final AppView<?> appView;
 
     // Map backported method to a provider for creating the actual target method (with code).
@@ -155,7 +159,7 @@
 
     RewritableMethods(InternalOptions options, AppView<?> appView) {
       this.appView = appView;
-
+      this.typeMinApi = initializeTypeMinApi(appView.dexItemFactory());
       if (!options.shouldBackportMethods()) {
         return;
       }
@@ -164,65 +168,125 @@
 
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.K)) {
         initializeAndroidKMethodProviders(factory);
+        if (typeIsAbsentOrPresentWithoutBackportsFrom(factory.objectsType, AndroidApiLevel.K)) {
+          initializeAndroidKObjectsMethodProviders(factory);
+        }
       }
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.N)) {
         initializeAndroidNMethodProviders(factory);
+        if (typeIsAbsentOrPresentWithoutBackportsFrom(factory.objectsType, AndroidApiLevel.N)) {
+          initializeAndroidNObjectsMethodProviders(factory);
+          if (typeIsPresent(factory.supplierType)) {
+            initializeAndroidNObjectsMethodProviderWithSupplier(factory);
+          }
+        }
       }
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.O)) {
         initializeAndroidOMethodProviders(factory);
       }
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.R)) {
-        initializeAndroidRMethodProviders(factory);
+        if (typeIsPresentWithoutBackportsFrom(factory.setType, AndroidApiLevel.R)) {
+          initializeAndroidRSetListMapMethodProviders(factory);
+        }
+        if (typeIsAbsentOrPresentWithoutBackportsFrom(factory.objectsType, AndroidApiLevel.R)) {
+          initializeAndroidRObjectsMethodProviders(factory);
+          if (typeIsPresent(factory.supplierType)) {
+            initializeAndroidRObjectsMethodProviderWithSupplier(factory);
+          }
+        }
       }
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.S)) {
         initializeAndroidSMethodProviders(factory);
+        if (typeIsPresentWithoutBackportsFrom(factory.setType, AndroidApiLevel.S)) {
+          initializeAndroidSSetListMapMethodProviders(factory);
+        }
       }
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.Sv2)) {
         initializeAndroidSv2MethodProviders(factory);
       }
       if (options.getMinApiLevel().isLessThan(AndroidApiLevel.T)) {
         initializeAndroidTMethodProviders(factory);
-      }
-
-      // The following providers are implemented at API level T. For backporting they require
-      // the java.util.Optional class to be present, either through library desugaring or natively.
-      // If the java.util.Optional class is not present, we do not backport to avoid confusion in
-      // error messages.
-      if (appView.typeRewriter.hasRewrittenType(factory.optionalType, appView)
-          || options.getMinApiLevel().betweenBothIncluded(AndroidApiLevel.N, AndroidApiLevel.Sv2)) {
-        initializeAndroidOptionalTMethodProviders(factory);
-      }
-
-      // The following providers are currently not implemented at any API level in Android. For
-      // backporting they require the java.util.stream.Stream class to be present, either through
-      // library desugaring or natively. If the class is not present, we do not desugar to avoid
-      // confusion in error messages.
-      if (appView.typeRewriter.hasRewrittenType(factory.streamType, appView)
-          || options.getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
-        initializeStreamMethodProviders(factory);
-      }
-
-      // The following providers are currently not implemented at any API level in Android. For
-      // backporting they require the java.util.function.Predicate class to be present, either
-      // through library desugaring or natively. If the class is not present, we do not desugar to
-      // avoid confusion in error messages.
-      if (appView.typeRewriter.hasRewrittenType(factory.predicateType, appView)
-          || options.getMinApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.N)) {
-        initializePredicateMethodProviders(factory);
-      }
-
-      if (appView.typeRewriter.hasRewrittenType(factory.supplierType, appView)) {
-        // TODO(b/191188594): Consider adding the Objects method from R here, or instead
-        //  rely on desugared library to support them.
-        initializeObjectsMethodProviders(factory);
+        if (typeIsPresentWithoutBackportsFrom(factory.optionalType, AndroidApiLevel.T)) {
+          initializeAndroidOptionalTMethodProviders(factory);
+        }
       }
 
       // These are currently not implemented at any API level in Android.
+      if (typeIsPresentWithoutNeverIntroducedBackports(factory.streamType)) {
+        initializeStreamMethodProviders(factory);
+      }
+      if (typeIsPresentWithoutNeverIntroducedBackports(factory.predicateType)) {
+        initializePredicateMethodProviders(factory);
+      }
       initializeJava9MethodProviders(factory);
       initializeJava10MethodProviders(factory);
       initializeJava11MethodProviders(factory);
     }
 
+    private Map<DexType, AndroidApiLevel> initializeTypeMinApi(DexItemFactory factory) {
+      ImmutableMap.Builder<DexType, AndroidApiLevel> builder = ImmutableMap.builder();
+      builder.put(factory.objectsType, AndroidApiLevel.K);
+      builder.put(factory.optionalType, AndroidApiLevel.N);
+      builder.put(factory.predicateType, AndroidApiLevel.N);
+      builder.put(factory.setType, AndroidApiLevel.B);
+      builder.put(factory.streamType, AndroidApiLevel.N);
+      builder.put(factory.supplierType, AndroidApiLevel.N);
+      ImmutableMap<DexType, AndroidApiLevel> typeMinApi = builder.build();
+      assert minApiMatchDatabaseMinApi(typeMinApi);
+      return typeMinApi;
+    }
+
+    private boolean minApiMatchDatabaseMinApi(ImmutableMap<DexType, AndroidApiLevel> typeMinApi) {
+      // TODO(b/224954240): Remove the assertion and always use the apiDatabase.
+      typeMinApi.forEach(
+          (type, api) -> {
+            ComputedApiLevel apiLevel =
+                appView
+                    .apiLevelCompute()
+                    .computeApiLevelForLibraryReference(type, ComputedApiLevel.unknown());
+            if (!apiLevel.isKnownApiLevel()) {
+              // API database is missing.
+              return;
+            }
+            AndroidApiLevel theApi = apiLevel.asKnownApiLevel().getApiLevel();
+            if (appView.typeRewriter.hasRewrittenType(type, appView)) {
+              assert theApi.equals(appView.options().getMinApiLevel());
+              return;
+            }
+            assert theApi.equals(api.max(appView.options().getMinApiLevel()));
+          });
+      return true;
+    }
+
+    private boolean typeIsAbsentOrPresentWithoutBackportsFrom(
+        DexType type, AndroidApiLevel apiLevel) {
+      return !typeIsPresent(type) || typeIsPresentWithoutBackportsFrom(type, apiLevel);
+    }
+
+    private boolean typeIsPresentWithoutNeverIntroducedBackports(DexType type) {
+      return typeIsPresentWithoutBackportsFrom(type, AndroidApiLevel.ANDROID_PLATFORM);
+    }
+
+    private boolean typeIsPresentWithoutBackportsFrom(DexType type, AndroidApiLevel methodsMinAPI) {
+      if (appView.typeRewriter.hasRewrittenType(type, appView)) {
+        // Desugared library is enabled, the methods are present if desugared library specifies it.
+        return methodsMinAPI.isGreaterThan(AndroidApiLevel.N)
+            && !appView.options().machineDesugaredLibrarySpecification.includesJDK11Methods();
+      }
+      // TODO(b/224954240): Always use the apiDatabase when always available.
+      if (!appView.options().getMinApiLevel().isGreaterThanOrEqualTo(typeMinApi.get(type))) {
+        // If the class is not present, we do not backport to avoid confusion in error messages.
+        return false;
+      }
+      return appView.options().getMinApiLevel().isLessThan(methodsMinAPI);
+    }
+
+    private boolean typeIsPresent(DexType type) {
+      // TODO(b/224954240): Always use the apiDatabase when always available.
+      return appView.options().getMinApiLevel().isGreaterThanOrEqualTo(typeMinApi.get(type))
+          || appView.typeRewriter.hasRewrittenType(type, appView);
+    }
+
     boolean isEmpty() {
       return rewritable.isEmpty();
     }
@@ -231,54 +295,11 @@
       rewritable.keySet().forEach(consumer);
     }
 
-    private void initializeAndroidKMethodProviders(DexItemFactory factory) {
-      // Byte
-      DexType type = factory.boxedByteType;
-      // int Byte.compare(byte a, byte b)
-      DexString name = factory.createString("compare");
-      DexProto proto = factory.createProto(factory.intType, factory.byteType, factory.byteType);
-      DexMethod method = factory.createMethod(type, proto, name);
-      addProvider(new MethodGenerator(method, BackportedMethods::ByteMethods_compare));
-
-      // Short
-      type = factory.boxedShortType;
-      // int Short.compare(short a, short b)
-      name = factory.createString("compare");
-      proto = factory.createProto(factory.intType, factory.shortType, factory.shortType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(new MethodGenerator(method, BackportedMethods::ShortMethods_compare));
-
-      // Integer
-      type = factory.boxedIntType;
-      // int Integer.compare(int a, int b)
-      name = factory.createString("compare");
-      proto = factory.createProto(factory.intType, factory.intType, factory.intType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(new MethodGenerator(method, BackportedMethods::IntegerMethods_compare));
-
-      // Long
-      type = factory.boxedLongType;
-      // int Long.compare(long a, long b)
-      name = factory.createString("compare");
-      proto = factory.createProto(factory.intType, factory.longType, factory.longType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(new InvokeRewriter(method, LongMethodRewrites.rewriteCompare()));
-
-      // Boolean
-      type = factory.boxedBooleanType;
-      // int Boolean.compare(boolean a, boolean b)
-      name = factory.createString("compare");
-      proto = factory.createProto(factory.intType, factory.booleanType, factory.booleanType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(new MethodGenerator(method, BackportedMethods::BooleanMethods_compare));
-
-      // Character
-      type = factory.boxedCharType;
-      // int Character.compare(char a, char b)
-      name = factory.createString("compare");
-      proto = factory.createProto(factory.intType, factory.charType, factory.charType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(new MethodGenerator(method, BackportedMethods::CharacterMethods_compare));
+    private void initializeAndroidKObjectsMethodProviders(DexItemFactory factory) {
+      DexType type;
+      DexString name;
+      DexProto proto;
+      DexMethod method;
 
       // Objects
       type = factory.objectsType;
@@ -342,6 +363,56 @@
       addProvider(
           new MethodGenerator(
               method, BackportedMethods::ObjectsMethods_toStringDefault, "toStringDefault"));
+    }
+
+    private void initializeAndroidKMethodProviders(DexItemFactory factory) {
+      // Byte
+      DexType type = factory.boxedByteType;
+      // int Byte.compare(byte a, byte b)
+      DexString name = factory.createString("compare");
+      DexProto proto = factory.createProto(factory.intType, factory.byteType, factory.byteType);
+      DexMethod method = factory.createMethod(type, proto, name);
+      addProvider(new MethodGenerator(method, BackportedMethods::ByteMethods_compare));
+
+      // Short
+      type = factory.boxedShortType;
+      // int Short.compare(short a, short b)
+      name = factory.createString("compare");
+      proto = factory.createProto(factory.intType, factory.shortType, factory.shortType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(new MethodGenerator(method, BackportedMethods::ShortMethods_compare));
+
+      // Integer
+      type = factory.boxedIntType;
+      // int Integer.compare(int a, int b)
+      name = factory.createString("compare");
+      proto = factory.createProto(factory.intType, factory.intType, factory.intType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(new MethodGenerator(method, BackportedMethods::IntegerMethods_compare));
+
+      // Long
+      type = factory.boxedLongType;
+      // int Long.compare(long a, long b)
+      name = factory.createString("compare");
+      proto = factory.createProto(factory.intType, factory.longType, factory.longType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(new InvokeRewriter(method, LongMethodRewrites.rewriteCompare()));
+
+      // Boolean
+      type = factory.boxedBooleanType;
+      // int Boolean.compare(boolean a, boolean b)
+      name = factory.createString("compare");
+      proto = factory.createProto(factory.intType, factory.booleanType, factory.booleanType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(new MethodGenerator(method, BackportedMethods::BooleanMethods_compare));
+
+      // Character
+      type = factory.boxedCharType;
+      // int Character.compare(char a, char b)
+      name = factory.createString("compare");
+      proto = factory.createProto(factory.intType, factory.charType, factory.charType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(new MethodGenerator(method, BackportedMethods::CharacterMethods_compare));
 
       // Collections
       type = factory.collectionsType;
@@ -367,6 +438,27 @@
           new MethodGenerator(method, BackportedMethods::CollectionsMethods_emptyListIterator));
     }
 
+    private void initializeAndroidNObjectsMethodProviders(DexItemFactory factory) {
+      DexString name;
+      DexProto proto;
+      DexMethod method;
+
+      // Objects
+      DexType type = factory.objectsType;
+
+      // boolean Objects.isNull(Object o)
+      name = factory.createString("isNull");
+      proto = factory.createProto(factory.booleanType, factory.objectType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(new MethodGenerator(method, BackportedMethods::ObjectsMethods_isNull));
+
+      // boolean Objects.nonNull(Object a)
+      name = factory.createString("nonNull");
+      proto = factory.createProto(factory.booleanType, factory.objectType);
+      method = factory.createMethod(type, proto, name);
+      addProvider(new MethodGenerator(method, BackportedMethods::ObjectsMethods_nonNull));
+    }
+
     private void initializeAndroidNMethodProviders(DexItemFactory factory) {
       // Byte
       DexType type = factory.boxedByteType;
@@ -540,21 +632,6 @@
       method = factory.createMethod(type, proto, name);
       addProvider(new InvokeRewriter(method, NumericMethodRewrites.rewriteAsIdentity()));
 
-      // Objects
-      type = factory.objectsType;
-
-      // boolean Objects.isNull(Object o)
-      name = factory.createString("isNull");
-      proto = factory.createProto(factory.booleanType, factory.objectType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(new MethodGenerator(method, BackportedMethods::ObjectsMethods_isNull));
-
-      // boolean Objects.nonNull(Object a)
-      name = factory.createString("nonNull");
-      proto = factory.createProto(factory.booleanType, factory.objectType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(new MethodGenerator(method, BackportedMethods::ObjectsMethods_nonNull));
-
       // Math & StrictMath, which have some symmetric, binary-compatible APIs
       DexType[] mathTypes = {factory.mathType, factory.strictMathType};
       for (DexType mathType : mathTypes) {
@@ -876,7 +953,20 @@
               method, BackportedMethods::StringMethods_joinIterable, "joinIterable"));
     }
 
-    private void initializeAndroidRMethodProviders(DexItemFactory factory) {
+    private void initializeAndroidRObjectsMethodProviderWithSupplier(DexItemFactory factory) {
+      // Objects
+      DexType type = factory.objectsType;
+
+      // T Objects.requireNonNullElseGet(T, Supplier<? extends T>)
+      DexString name = factory.createString("requireNonNullElseGet");
+      DexProto proto =
+          factory.createProto(factory.objectType, factory.objectType, factory.supplierType);
+      DexMethod method = factory.createMethod(type, proto, name);
+      addProvider(
+          new MethodGenerator(method, BackportedMethods::ObjectsMethods_requireNonNullElseGet));
+    }
+
+    private void initializeAndroidRObjectsMethodProviders(DexItemFactory factory) {
       DexType type;
       DexString name;
       DexProto proto;
@@ -892,13 +982,6 @@
       addProvider(
           new MethodGenerator(method, BackportedMethods::ObjectsMethods_requireNonNullElse));
 
-      // T Objects.requireNonNullElseGet(T, Supplier<? extends T>)
-      name = factory.createString("requireNonNullElseGet");
-      proto = factory.createProto(factory.objectType, factory.objectType, factory.supplierType);
-      method = factory.createMethod(type, proto, name);
-      addProvider(
-          new MethodGenerator(method, BackportedMethods::ObjectsMethods_requireNonNullElseGet));
-
       // int Objects.checkIndex(int, int)
       name = factory.createString("checkIndex");
       proto = factory.createProto(factory.intType, factory.intType, factory.intType);
@@ -919,6 +1002,13 @@
       method = factory.createMethod(type, proto, name);
       addProvider(
           new MethodGenerator(method, BackportedMethods::ObjectsMethods_checkFromIndexSize));
+    }
+
+    private void initializeAndroidRSetListMapMethodProviders(DexItemFactory factory) {
+      DexType type;
+      DexString name;
+      DexProto proto;
+      DexMethod method;
 
       // List<E> List.of(<args>) for 0 to 10 arguments and List.of(E[])
       type = factory.listType;
@@ -989,7 +1079,7 @@
       addProvider(new MethodGenerator(method, BackportedMethods::CollectionMethods_mapEntry));
     }
 
-    private void initializeAndroidSMethodProviders(DexItemFactory factory) {
+    private void initializeAndroidSSetListMapMethodProviders(DexItemFactory factory) {
       DexType type;
       DexString name;
       DexProto proto;
@@ -1027,6 +1117,13 @@
       addProvider(
           new MethodGenerator(
               method, BackportedMethods::CollectionsMethods_copyOfMap, "copyOfMap"));
+    }
+
+    private void initializeAndroidSMethodProviders(DexItemFactory factory) {
+      DexType type;
+      DexString name;
+      DexProto proto;
+      DexMethod method;
 
       // Byte
       type = factory.boxedByteType;
@@ -1481,7 +1578,7 @@
       addProvider(new MethodGenerator(method, BackportedMethods::PredicateMethods_not, "not"));
     }
 
-    private void initializeObjectsMethodProviders(DexItemFactory factory) {
+    private void initializeAndroidNObjectsMethodProviderWithSupplier(DexItemFactory factory) {
       // Objects
       DexType type = factory.objectsType;
 
@@ -1495,26 +1592,6 @@
     }
 
     private void addProvider(MethodProvider generator) {
-      if (appView.options().machineDesugaredLibrarySpecification.isSupported(generator.method)) {
-        // TODO(b/174453232): Remove this after the configuration file format has bee updated
-        // with the "rewrite_method" section.
-        if (generator.method.getHolderType() == appView.dexItemFactory().objectsType) {
-          // Still backport the new API level 30 methods and Objects.requireNonNull taking
-          // one argument.
-          String methodName = generator.method.getName().toString();
-          if (!methodName.equals("requireNonNull")
-              && !methodName.equals("requireNonNullElse")
-              && !methodName.equals("requireNonNullElseGet")
-              && !methodName.equals("checkIndex")
-              && !methodName.equals("checkFromToIndex")
-              && !methodName.equals("checkFromIndexSize")) {
-            return;
-          }
-          if (methodName.equals("requireNonNull") && generator.method.getArity() != 1) {
-            return;
-          }
-        }
-      }
       MethodProvider replaced = rewritable.put(generator.method, generator);
       assert replaced == null;
     }
@@ -1575,8 +1652,8 @@
       this.methodName = methodName;
     }
 
-    protected SyntheticKind getSyntheticKind() {
-      return SyntheticNaming.SyntheticKind.BACKPORT;
+    protected SyntheticKind getSyntheticKind(SyntheticNaming naming) {
+      return naming.BACKPORT;
     }
 
     @Override
@@ -1596,7 +1673,7 @@
       return appView
           .getSyntheticItems()
           .createMethod(
-              getSyntheticKind(),
+              this::getSyntheticKind,
               methodProcessingContext.createUniqueContext(),
               appView,
               builder ->
@@ -1644,8 +1721,8 @@
     }
 
     @Override
-    protected SyntheticKind getSyntheticKind() {
-      return SyntheticKind.BACKPORT_WITH_FORWARDING;
+    protected SyntheticKind getSyntheticKind(SyntheticNaming naming) {
+      return naming.BACKPORT_WITH_FORWARDING;
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index aa2ecfa..e642655 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -28,7 +28,6 @@
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.ImmutableList;
@@ -171,7 +170,7 @@
     return appView
         .getSyntheticItems()
         .createMethod(
-            SyntheticKind.API_MODEL_OUTLINE,
+            kinds -> kinds.API_MODEL_OUTLINE,
             context,
             appView,
             syntheticMethodBuilder -> {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
index 1d55e7e..8853e8b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/constantdynamic/ConstantDynamicInstructionDesugaring.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.Box;
 import java.util.Collection;
 import java.util.HashMap;
@@ -117,7 +116,7 @@
         appView
             .getSyntheticItems()
             .createClass(
-                SyntheticKind.CONST_DYNAMIC,
+                kinds -> kinds.CONST_DYNAMIC,
                 methodProcessingContext.createUniqueContext(),
                 appView,
                 builder ->
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
index 520b5ea..f041fe3 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPIConverter.java
@@ -36,7 +36,6 @@
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
 import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConversionCfCodeProvider;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Iterables;
@@ -126,7 +125,7 @@
   static boolean isAPIConversionSyntheticType(
       DexType type, DesugaredLibraryWrapperSynthesizer wrapperSynthesizor, AppView<?> appView) {
     return wrapperSynthesizor.isSyntheticWrapper(type)
-        || appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.API_CONVERSION);
+        || appView.getSyntheticItems().isSyntheticOfKind(type, kinds -> kinds.API_CONVERSION);
   }
 
   public static boolean isVivifiedType(DexType type) {
@@ -429,7 +428,7 @@
         appView
             .getSyntheticItems()
             .createMethod(
-                SyntheticKind.API_CONVERSION_PARAMETERS,
+                kinds -> kinds.API_CONVERSION_PARAMETERS,
                 methodProcessingContext.createUniqueContext(),
                 appView,
                 builder ->
@@ -534,7 +533,7 @@
         appView
             .getSyntheticItems()
             .createMethod(
-                SyntheticKind.API_CONVERSION,
+                kinds -> kinds.API_CONVERSION,
                 methodProcessingContext.createUniqueContext(),
                 appView,
                 builder ->
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
index 60a2281..d69a158 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryEnumConversionSynthesizer.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.synthesis.SyntheticClasspathClassBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import com.google.common.collect.Iterables;
 
@@ -116,7 +115,7 @@
     DexProgramClass enumConversion =
         appView
             .getSyntheticItems()
-            .getExistingFixedClass(SyntheticKind.ENUM_CONVERSION, clazz, appView);
+            .getExistingFixedClass(kinds -> kinds.ENUM_CONVERSION, clazz, appView);
     DexMethod method =
         factory.createMethod(
             enumConversion.type, factory.createProto(destType, srcType), factory.convertMethodName);
@@ -137,7 +136,7 @@
     return appView
         .getSyntheticItems()
         .ensureFixedClass(
-            SyntheticKind.ENUM_CONVERSION,
+            kinds -> kinds.ENUM_CONVERSION,
             programContext,
             appView,
             builder -> buildEnumMethodsWithCode(builder, enumFields, type, vivifiedType),
@@ -150,14 +149,14 @@
     if (context.isProgramClass()) {
       return appView
           .getSyntheticItems()
-          .getExistingFixedClass(SyntheticKind.ENUM_CONVERSION, context, appView);
+          .getExistingFixedClass(kinds -> kinds.ENUM_CONVERSION, context, appView);
     }
     DexType type = context.type;
     DexType vivifiedType = vivifiedTypeFor(context.type, appView);
     return appView
         .getSyntheticItems()
         .ensureFixedClasspathClass(
-            SyntheticKind.ENUM_CONVERSION,
+            kinds -> kinds.ENUM_CONVERSION,
             context.asClasspathOrLibraryClass(),
             appView,
             builder -> buildEnumMethodsWithoutCode(builder, type, vivifiedType),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index e1fbb96..79f1ff2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -40,8 +40,8 @@
 import com.android.tools.r8.position.MethodPosition;
 import com.android.tools.r8.position.Position;
 import com.android.tools.r8.synthesis.SyntheticClassBuilder;
+import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -108,8 +108,8 @@
   }
 
   public boolean isSyntheticWrapper(DexType type) {
-    return appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.WRAPPER)
-        || appView.getSyntheticItems().isSyntheticOfKind(type, SyntheticKind.VIVIFIED_WRAPPER);
+    return appView.getSyntheticItems().isSyntheticOfKind(type, kinds -> kinds.WRAPPER)
+        || appView.getSyntheticItems().isSyntheticOfKind(type, kinds -> kinds.VIVIFIED_WRAPPER);
   }
 
   public boolean shouldConvert(DexType type, DexMethod method) {
@@ -202,7 +202,7 @@
         appView
             .getSyntheticItems()
             .createMethod(
-                SyntheticKind.ARRAY_CONVERSION,
+                kinds -> kinds.ARRAY_CONVERSION,
                 contextSupplier.get(),
                 appView,
                 builder ->
@@ -351,7 +351,7 @@
     DexType vivifiedType = vivifiedTypeFor(type);
     DexClass wrapper =
         ensureClasspathWrapper(
-            SyntheticKind.WRAPPER,
+            kinds -> kinds.WRAPPER,
             vivifiedType,
             type,
             classpathOrLibraryContext,
@@ -363,7 +363,7 @@
                     DesugaredLibraryWrapperSynthesizer::codeForClasspathMethod));
     DexClass vivifiedWrapper =
         ensureClasspathWrapper(
-            SyntheticKind.VIVIFIED_WRAPPER,
+            kinds -> kinds.VIVIFIED_WRAPPER,
             type,
             vivifiedType,
             classpathOrLibraryContext,
@@ -382,8 +382,8 @@
     DexClass vivifiedWrapper;
     DexClass wrapper;
     assert appView.options().isDesugaredLibraryCompilation();
-    wrapper = getExistingProgramWrapper(context, SyntheticKind.WRAPPER);
-    vivifiedWrapper = getExistingProgramWrapper(context, SyntheticKind.VIVIFIED_WRAPPER);
+    wrapper = getExistingProgramWrapper(context, kinds -> kinds.WRAPPER);
+    vivifiedWrapper = getExistingProgramWrapper(context, kinds -> kinds.VIVIFIED_WRAPPER);
     DexField wrapperField = getWrapperUniqueField(wrapper);
     DexField vivifiedWrapperField = getWrapperUniqueField(vivifiedWrapper);
     return new WrapperConversions(
@@ -391,8 +391,9 @@
         getConversion(vivifiedWrapper, wrapperField.type, vivifiedWrapperField.type));
   }
 
-  private DexProgramClass getExistingProgramWrapper(DexClass context, SyntheticKind kind) {
-    return appView.getSyntheticItems().getExistingFixedClass(kind, context, appView);
+  private DexProgramClass getExistingProgramWrapper(
+      DexClass context, SyntheticKindSelector kindSelector) {
+    return appView.getSyntheticItems().getExistingFixedClass(kindSelector, context, appView);
   }
 
   private DexMethod getConversion(DexClass wrapper, DexType returnType, DexType argType) {
@@ -412,7 +413,7 @@
   }
 
   private DexProgramClass ensureProgramWrapper(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       DexType wrappingType,
       DexType wrappedType,
       DexProgramClass programContext,
@@ -422,7 +423,7 @@
     return appView
         .getSyntheticItems()
         .ensureFixedClass(
-            kind,
+            kindSelector,
             programContext,
             appView,
             builder -> buildWrapper(wrappingType, wrappedType, programContext, builder),
@@ -432,7 +433,7 @@
   }
 
   private DexClasspathClass ensureClasspathWrapper(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       DexType wrappingType,
       DexType wrappedType,
       ClasspathOrLibraryClass classpathOrLibraryContext,
@@ -442,7 +443,7 @@
     return appView
         .getSyntheticItems()
         .ensureFixedClasspathClass(
-            kind,
+            kindSelector,
             classpathOrLibraryContext,
             appView,
             builder -> {
@@ -459,7 +460,10 @@
   }
 
   private void getExistingProgramConversionMethod(
-      SyntheticKind kind, DexProgramClass context, DexClass wrapper, DexClass reverseWrapper) {
+      SyntheticKindSelector kindSelector,
+      DexProgramClass context,
+      DexClass wrapper,
+      DexClass reverseWrapper) {
     DexField wrapperField = getWrapperUniqueField(wrapper);
     DexField reverseWrapperField = getWrapperUniqueField(reverseWrapper);
     DexProto proto = factory.createProto(reverseWrapperField.type, wrapperField.type);
@@ -468,7 +472,7 @@
         .ensureFixedClassMethod(
             factory.convertMethodName,
             proto,
-            kind,
+            kindSelector,
             context,
             appView,
             ignored -> {},
@@ -694,18 +698,18 @@
     DexProgramClass programContext = context.asProgramClass();
     DexClass wrapper =
         ensureProgramWrapper(
-            SyntheticKind.WRAPPER, vivifiedTypeFor(type), type, programContext, eventConsumer);
+            kinds -> kinds.WRAPPER, vivifiedTypeFor(type), type, programContext, eventConsumer);
     DexClass vivifiedWrapper =
         ensureProgramWrapper(
-            SyntheticKind.VIVIFIED_WRAPPER,
+            kinds -> kinds.VIVIFIED_WRAPPER,
             type,
             vivifiedTypeFor(type),
             programContext,
             eventConsumer);
     getExistingProgramConversionMethod(
-        SyntheticKind.WRAPPER, programContext, wrapper, vivifiedWrapper);
+        kinds -> kinds.WRAPPER, programContext, wrapper, vivifiedWrapper);
     getExistingProgramConversionMethod(
-        SyntheticKind.VIVIFIED_WRAPPER, programContext, vivifiedWrapper, wrapper);
+        kinds -> kinds.VIVIFIED_WRAPPER, programContext, vivifiedWrapper, wrapper);
   }
 
   private void ensureProgramWrappersVirtualMethods(
@@ -713,7 +717,7 @@
       Iterable<DexMethod> methods,
       CfClassSynthesizerDesugaringEventConsumer eventConsumer,
       ClassSynthesisDesugaringContext processingContext) {
-    DexProgramClass wrapper = getExistingProgramWrapper(context, SyntheticKind.WRAPPER);
+    DexProgramClass wrapper = getExistingProgramWrapper(context, kinds -> kinds.WRAPPER);
     DexEncodedField wrapperField = getWrapperUniqueEncodedField(wrapper);
     wrapper.addVirtualMethods(
         synthesizeVirtualMethodsForTypeWrapper(
@@ -726,7 +730,7 @@
                     eventConsumer,
                     () -> processingContext.createUniqueContext(wrapper))));
     DexProgramClass vivifiedWrapper =
-        getExistingProgramWrapper(context, SyntheticKind.VIVIFIED_WRAPPER);
+        getExistingProgramWrapper(context, kinds -> kinds.VIVIFIED_WRAPPER);
     DexEncodedField vivifiedWrapperField = getWrapperUniqueEncodedField(vivifiedWrapper);
     vivifiedWrapper.addVirtualMethods(
         synthesizeVirtualMethodsForVivifiedTypeWrapper(
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
index d578eed..1f52b3d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineDesugaredLibrarySpecification.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.SemanticVersion;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -20,6 +21,8 @@
   private final MachineTopLevelFlags topLevelFlags;
   private final MachineRewritingFlags rewritingFlags;
 
+  private int leadingVersionNumberCache = -1;
+
   public static MachineDesugaredLibrarySpecification empty() {
     return new MachineDesugaredLibrarySpecification(
         false, MachineTopLevelFlags.empty(), MachineRewritingFlags.builder().build()) {
@@ -175,4 +178,16 @@
   public AndroidApiLevel getRequiredCompilationApiLevel() {
     return topLevelFlags.getRequiredCompilationAPILevel();
   }
+
+  private int getLeadingVersionNumber() {
+    if (leadingVersionNumberCache != -1) {
+      return leadingVersionNumberCache;
+    }
+    String[] split = topLevelFlags.getIdentifier().split(":");
+    return leadingVersionNumberCache = SemanticVersion.parse(split[split.length - 1]).getMajor();
+  }
+
+  public boolean includesJDK11Methods() {
+    return getLeadingVersionNumber() >= 2;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
index 6102e1a..1fa4b75 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/retargeter/DesugaredLibraryRetargeterSyntheticHelper.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.retargeter.DesugaredLibraryRetargeterSynthesizerEventConsumer.DesugaredLibraryRetargeterL8SynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticClassBuilder;
+import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import java.util.LinkedHashMap;
 
@@ -46,7 +47,7 @@
             .ensureFixedClasspathMethodFromType(
                 retarget.getName(),
                 retarget.getProto(),
-                SyntheticKind.RETARGET_STUB,
+                kinds -> kinds.RETARGET_STUB,
                 retarget.getHolderType(),
                 appView,
                 ignored -> {},
@@ -70,14 +71,20 @@
     return ensureRetargetMethod(forwardingMethod(descriptor), eventConsumer);
   }
 
+  private boolean verifyKind(DerivedMethod method, SyntheticKindSelector kindSelector) {
+    SyntheticKind kind = kindSelector.select(appView.getSyntheticItems().getNaming());
+    assert method.getHolderKind().equals(kind);
+    return true;
+  }
+
   private DexMethod emulatedHolderDispatchMethod(DexType holder, DerivedMethod method) {
-    assert method.getHolderKind() == SyntheticKind.RETARGET_CLASS;
+    assert verifyKind(method, kinds -> kinds.RETARGET_CLASS);
     DexProto newProto = appView.dexItemFactory().prependHolderToProto(method.getMethod());
     return appView.dexItemFactory().createMethod(holder, newProto, method.getName());
   }
 
   DexMethod emulatedInterfaceDispatchMethod(DexType holder, DerivedMethod method) {
-    assert method.getHolderKind() == SyntheticKind.RETARGET_INTERFACE;
+    assert verifyKind(method, kinds -> kinds.RETARGET_INTERFACE);
     return appView.dexItemFactory().createMethod(holder, method.getProto(), method.getName());
   }
 
@@ -102,7 +109,7 @@
           appView
               .getSyntheticItems()
               .getExistingFixedClass(
-                  emulatedDispatchMethod.getHolderKind(), holderContext, appView);
+                  ignored -> emulatedDispatchMethod.getHolderKind(), holderContext, appView);
       DexMethod dispatchMethod =
           emulatedHolderDispatchMethod(syntheticClass.type, emulatedDispatchMethod);
       assert syntheticClass.lookupMethod(dispatchMethod) != null;
@@ -115,7 +122,7 @@
           appView
               .getSyntheticItems()
               .ensureFixedClasspathClass(
-                  SyntheticKind.RETARGET_CLASS,
+                  kinds -> kinds.RETARGET_CLASS,
                   context,
                   appView,
                   classBuilder ->
@@ -140,7 +147,7 @@
     appView
         .getSyntheticItems()
         .ensureFixedClass(
-            emulatedDispatchMethod.getHolderKind(),
+            ignored -> emulatedDispatchMethod.getHolderKind(),
             holderContext,
             appView,
             classBuilder -> buildHolderDispatchMethod(classBuilder, itfClass, descriptor, null),
@@ -156,14 +163,14 @@
     if (appView.options().isDesugaredLibraryCompilation()) {
       return appView
           .getSyntheticItems()
-          .getExistingFixedClass(itfMethod.getHolderKind(), itfContext, appView);
+          .getExistingFixedClass(ignored -> itfMethod.getHolderKind(), itfContext, appView);
     }
     ClasspathOrLibraryClass context = itfContext.asClasspathOrLibraryClass();
     assert context != null;
     return appView
         .getSyntheticItems()
         .ensureFixedClasspathClass(
-            SyntheticKind.RETARGET_INTERFACE,
+            kinds -> kinds.RETARGET_INTERFACE,
             context,
             appView,
             classBuilder -> buildInterfaceDispatchMethod(classBuilder, descriptor),
@@ -180,7 +187,7 @@
     return appView
         .getSyntheticItems()
         .ensureFixedClass(
-            itfMethod.getHolderKind(),
+            ignore -> itfMethod.getHolderKind(),
             itfContext,
             appView,
             classBuilder -> buildInterfaceDispatchMethod(classBuilder, descriptor),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
index 182202b..5f385c5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineEmulatedInterfaceConverter.java
@@ -16,7 +16,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedInterfaceDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -68,7 +68,8 @@
 
   private EmulatedDispatchMethodDescriptor computeEmulatedDispatchDescriptor(
       DexMethod method, HumanRewritingFlags rewritingFlags, AppInfoWithClassHierarchy appInfo) {
-    DerivedMethod forwardingMethod = new DerivedMethod(method, SyntheticKind.COMPANION_CLASS);
+    SyntheticNaming syntheticNaming = appInfo.getSyntheticItems().getNaming();
+    DerivedMethod forwardingMethod = new DerivedMethod(method, syntheticNaming.COMPANION_CLASS);
     DexMethod itfDexMethod =
         appInfo
             .dexItemFactory()
@@ -78,7 +79,7 @@
                 method.getName());
     DerivedMethod interfaceMethod = new DerivedMethod(itfDexMethod);
     DerivedMethod dispatchMethod =
-        new DerivedMethod(method, SyntheticKind.EMULATED_INTERFACE_CLASS);
+        new DerivedMethod(method, syntheticNaming.EMULATED_INTERFACE_CLASS);
     LinkedHashMap<DexType, DerivedMethod> dispatchCases = getDispatchCases(rewritingFlags, method);
     return new EmulatedDispatchMethodDescriptor(
         interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases);
@@ -109,6 +110,7 @@
       }
     }
     if (subInterfaces != null) {
+      SyntheticNaming syntheticNaming = appInfo.getSyntheticItems().getNaming();
       for (int i = subInterfaces.size() - 1; i >= 0; i--) {
         DexClass subInterfaceClass = appInfo.definitionFor(subInterfaces.get(i));
         assert subInterfaceClass != null;
@@ -119,7 +121,8 @@
           assert result.isDefaultMethod();
           DexMethod reference = result.getReference();
           extraDispatchCases.put(
-              subInterfaceClass.type, new DerivedMethod(reference, SyntheticKind.COMPANION_CLASS));
+              subInterfaceClass.type,
+              new DerivedMethod(reference, syntheticNaming.COMPANION_CLASS));
         }
       }
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
index 6db6518..f64a3d0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineRetargetConverter.java
@@ -15,7 +15,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.TraversalContinuation;
 import com.google.common.collect.Sets;
 import java.util.LinkedHashMap;
@@ -123,11 +123,12 @@
       return;
     }
     // TODO(b/184026720): Implement library boundaries.
+    SyntheticNaming syntheticNaming = appInfo.getSyntheticItems().getNaming();
     DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
     DerivedMethod interfaceMethod =
-        new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_INTERFACE);
+        new DerivedMethod(src.getReference(), syntheticNaming.RETARGET_INTERFACE);
     DerivedMethod dispatchMethod =
-        new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_CLASS);
+        new DerivedMethod(src.getReference(), syntheticNaming.RETARGET_CLASS);
     LinkedHashMap<DexType, DerivedMethod> dispatchCases = new LinkedHashMap<>();
     builder.putEmulatedVirtualRetarget(
         src.getReference(),
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringForTesting.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringForTesting.java
index 6636e1e..65379b5 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringForTesting.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringForTesting.java
@@ -4,12 +4,12 @@
 
 package com.android.tools.r8.ir.desugar.itf;
 
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
-
 public class InterfaceDesugaringForTesting {
 
+  public static final String EMULATED_INTERFACE_CLASS_SUFFIX = "$-EL";
+
   public static String getEmulateLibraryClassNameSuffix() {
-    return SyntheticKind.EMULATED_INTERFACE_CLASS.descriptor;
+    return EMULATED_INTERFACE_CLASS_SUFFIX;
   }
 
   public static String getCompanionClassNameSuffix() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
index 5d00719..dc739bb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceDesugaringSyntheticHelper.java
@@ -41,6 +41,7 @@
 import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedInterfaceDescriptor;
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.ClasspathEmulatedInterfaceSynthesizerEventConsumer;
 import com.android.tools.r8.synthesis.SyntheticClassBuilder;
+import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
 import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
@@ -120,8 +121,14 @@
     return true;
   }
 
+  public boolean verifyKind(DerivedMethod method, SyntheticKindSelector kindSelector) {
+    SyntheticKind kind = kindSelector.select(appView.getSyntheticItems().getNaming());
+    assert method.getHolderKind().equals(kind);
+    return true;
+  }
+
   DexMethod emulatedInterfaceDispatchMethod(DerivedMethod method, DexType holder) {
-    assert method.getHolderKind() == SyntheticKind.EMULATED_INTERFACE_CLASS;
+    assert verifyKind(method, kinds -> kinds.EMULATED_INTERFACE_CLASS);
     DexProto newProto = appView.dexItemFactory().prependHolderToProto(method.getMethod());
     return appView.dexItemFactory().createMethod(holder, newProto, method.getName());
   }
@@ -168,7 +175,7 @@
     return appView
         .getSyntheticItems()
         .ensureFixedClasspathClassFromType(
-            SyntheticKind.EMULATED_INTERFACE_MARKER_CLASS,
+            kinds -> kinds.EMULATED_INTERFACE_MARKER_CLASS,
             type,
             appView,
             SyntheticClassBuilder::setInterface,
@@ -247,7 +254,7 @@
     if (method.getHolderKind() == null) {
       return method.getMethod();
     }
-    assert method.getHolderKind() == SyntheticKind.COMPANION_CLASS;
+    assert verifyKind(method, kinds -> kinds.COMPANION_CLASS);
     DexClassAndMethod resolvedMethod =
         appView.appInfoForDesugaring().resolveMethod(method.getMethod(), true).getResolutionPair();
     return ensureDefaultAsMethodOfCompanionClassStub(resolvedMethod).getReference();
@@ -256,20 +263,20 @@
   DexClassAndMethod ensureEmulatedInterfaceDispatchMethod(
       DerivedMethod emulatedDispatchMethod,
       ClasspathEmulatedInterfaceSynthesizerEventConsumer eventConsumer) {
-    assert emulatedDispatchMethod.getHolderKind() == SyntheticKind.EMULATED_INTERFACE_CLASS;
+    assert verifyKind(emulatedDispatchMethod, kinds -> kinds.EMULATED_INTERFACE_CLASS);
     DexClassAndMethod method =
         appView
             .appInfoForDesugaring()
             .resolveMethod(emulatedDispatchMethod.getMethod(), true)
             .getResolutionPair();
-    assert emulatedDispatchMethod.getHolderKind() == SyntheticKind.EMULATED_INTERFACE_CLASS;
+    assert verifyKind(emulatedDispatchMethod, kinds -> kinds.EMULATED_INTERFACE_CLASS);
     if (method.isProgramMethod()) {
       assert appView.options().isDesugaredLibraryCompilation();
       DexProgramClass emulatedInterface =
           appView
               .getSyntheticItems()
               .getExistingFixedClass(
-                  SyntheticKind.EMULATED_INTERFACE_CLASS,
+                  kinds -> kinds.EMULATED_INTERFACE_CLASS,
                   method.asProgramMethod().getHolder(),
                   appView);
       DexMethod emulatedInterfaceMethod =
@@ -286,7 +293,7 @@
         .ensureFixedClasspathClassMethod(
             emulatedInterfaceMethod.getName(),
             emulatedInterfaceMethod.getProto(),
-            SyntheticKind.EMULATED_INTERFACE_CLASS,
+            kinds -> kinds.EMULATED_INTERFACE_CLASS,
             method.getHolder().asClasspathOrLibraryClass(),
             appView,
             classBuilder -> {},
@@ -420,7 +427,7 @@
         .ensureFixedClasspathClassMethod(
             companionMethodReference.getName(),
             companionMethodReference.getProto(),
-            SyntheticKind.COMPANION_CLASS,
+            kinds -> kinds.COMPANION_CLASS,
             context,
             appView,
             classBuilder -> {},
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
index 85efd2b..2ae7d7d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceMethodRewriter.java
@@ -43,7 +43,6 @@
 import com.android.tools.r8.ir.synthetic.ForwardMethodBuilder;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.MethodPosition;
-import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.collections.ProgramMethodSet;
 import com.android.tools.r8.utils.structural.Ordered;
@@ -382,7 +381,7 @@
                     appView
                         .getSyntheticItems()
                         .createMethod(
-                            SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
+                            kind -> kind.STATIC_INTERFACE_CALL,
                             methodProcessingContext.createUniqueContext(),
                             appView,
                             syntheticMethodBuilder ->
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
index 06f2d76..5d14741 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/InterfaceProcessor.java
@@ -26,7 +26,6 @@
 import com.android.tools.r8.graph.NestedGraphLens;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.Pair;
 import com.android.tools.r8.utils.collections.BidirectionalManyToManyRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
@@ -96,7 +95,7 @@
         .ensureFixedClassMethod(
             methodName,
             methodProto,
-            SyntheticKind.COMPANION_CLASS,
+            kinds -> kinds.COMPANION_CLASS,
             iface,
             appView,
             builder ->
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
index 8d14980..b8b50d9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/itf/ProgramEmulatedInterfaceSynthesizer.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.ir.desugar.itf.EmulatedInterfaceSynthesizerEventConsumer.L8ProgramEmulatedInterfaceSynthesizerEventConsumer;
 import com.android.tools.r8.ir.synthetic.EmulateDispatchSyntheticCfCodeProvider;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder;
-import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.synthesis.SyntheticProgramClassBuilder;
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.util.LinkedHashMap;
@@ -48,7 +47,7 @@
     return appView
         .getSyntheticItems()
         .ensureFixedClass(
-            SyntheticNaming.SyntheticKind.EMULATED_INTERFACE_CLASS,
+            kinds -> kinds.EMULATED_INTERFACE_CLASS,
             emulatedInterface,
             appView,
             builder ->
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
index f7e8d1a..d336a16 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/lambda/LambdaInstructionDesugaring.java
@@ -30,7 +30,6 @@
 import com.android.tools.r8.ir.desugar.LambdaClass;
 import com.android.tools.r8.ir.desugar.LambdaDescriptor;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
-import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.Box;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
@@ -165,7 +164,7 @@
         appView
             .getSyntheticItems()
             .createClass(
-                SyntheticNaming.SyntheticKind.LAMBDA,
+                kinds -> kinds.LAMBDA,
                 methodProcessingContext.createUniqueContext(),
                 appView,
                 builder ->
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
index 8f658a3..4da9973 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/nest/NestBasedAccessDesugaring.java
@@ -36,7 +36,6 @@
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.ProgramAdditions;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -444,7 +443,7 @@
                   return appView
                       .getSyntheticItems()
                       .createFixedClass(
-                          SyntheticKind.INIT_TYPE_ARGUMENT,
+                          kinds -> kinds.INIT_TYPE_ARGUMENT,
                           method.asProgramMethod().getHolder(),
                           appView,
                           builder -> {})
@@ -454,7 +453,7 @@
                   return appView
                       .getSyntheticItems()
                       .ensureFixedClasspathClass(
-                          SyntheticKind.INIT_TYPE_ARGUMENT,
+                          kinds -> kinds.INIT_TYPE_ARGUMENT,
                           method.asClasspathMethod().getHolder(),
                           appView,
                           ignored -> {},
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
index a6fcf97..3f17800 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/records/RecordDesugaring.java
@@ -51,7 +51,6 @@
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordEqualsCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.RecordCfCodeProvider.RecordGetFieldsAsObjectsCfCodeProvider;
 import com.android.tools.r8.ir.synthetic.SyntheticCfCodeProvider;
-import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
@@ -285,7 +284,7 @@
     return appView
         .getSyntheticItems()
         .createMethod(
-            SyntheticNaming.SyntheticKind.RECORD_HELPER,
+            kinds -> kinds.RECORD_HELPER,
             methodProcessingContext.createUniqueContext(),
             appView,
             builder ->
@@ -374,7 +373,7 @@
     appView
         .getSyntheticItems()
         .ensureFixedClassFromType(
-            SyntheticNaming.SyntheticKind.RECORD_TAG,
+            kinds -> kinds.RECORD_TAG,
             factory.recordType,
             appView,
             builder -> {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
index 476859a..017ba89 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrInstructionDesugaring.java
@@ -22,7 +22,7 @@
 import com.android.tools.r8.ir.desugar.FreshLocalProvider;
 import com.android.tools.r8.ir.desugar.LocalStackAllocator;
 import com.android.tools.r8.ir.desugar.backports.BackportedMethods;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+import com.android.tools.r8.synthesis.SyntheticItems.SyntheticKindSelector;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
 import java.util.Collection;
@@ -82,7 +82,7 @@
     DexProto proto =
         factory.createProto(factory.voidType, factory.throwableType, factory.throwableType);
     return createAndCallSyntheticMethod(
-        SyntheticKind.BACKPORT,
+        kinds -> kinds.BACKPORT,
         proto,
         BackportedMethods::ThrowableMethods_addSuppressed,
         methodProcessingContext,
@@ -98,7 +98,7 @@
         factory.createProto(
             factory.createArrayType(1, factory.throwableType), factory.throwableType);
     return createAndCallSyntheticMethod(
-        SyntheticKind.BACKPORT,
+        kinds -> kinds.BACKPORT,
         proto,
         BackportedMethods::ThrowableMethods_getSuppressed,
         methodProcessingContext,
@@ -111,7 +111,7 @@
       MethodProcessingContext methodProcessingContext) {
     // Synthesize a new method.
     return createAndCallSyntheticMethod(
-        SyntheticKind.TWR_CLOSE_RESOURCE,
+        kinds -> kinds.TWR_CLOSE_RESOURCE,
         twrCloseResourceProto,
         BackportedMethods::CloseResourceMethod_closeResourceImpl,
         methodProcessingContext,
@@ -120,7 +120,7 @@
   }
 
   private ImmutableList<CfInstruction> createAndCallSyntheticMethod(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       DexProto proto,
       BiFunction<InternalOptions, DexMethod, CfCode> generator,
       MethodProcessingContext methodProcessingContext,
@@ -130,7 +130,7 @@
         appView
             .getSyntheticItems()
             .createMethod(
-                kind,
+                kindSelector,
                 methodProcessingContext.createUniqueContext(),
                 appView,
                 builder ->
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
index 9e0908a..0951ff9 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NaturalIntLoopRemover.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.optimize;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
@@ -28,7 +29,10 @@
  */
 public class NaturalIntLoopRemover {
 
-  public void run(IRCode code) {
+  public void run(AppView<?> appView, IRCode code) {
+    if (!appView.testing().enableExperimentalLoopUnrolling) {
+      return;
+    }
     boolean loopRemoved = false;
     for (BasicBlock comparisonBlockCandidate : code.blocks) {
       if (isComparisonBlock(comparisonBlockCandidate)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
index 3edb51f..f04e328 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/OutlinerImpl.java
@@ -68,7 +68,6 @@
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
 import com.android.tools.r8.utils.ListUtils;
@@ -1525,7 +1524,7 @@
           appView
               .getSyntheticItems()
               .createMethod(
-                  SyntheticKind.OUTLINE,
+                  kinds -> kinds.OUTLINE,
                   methodProcessingContext.createUniqueContext(),
                   appView,
                   builder -> {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
index 260a5a3..1288379 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
@@ -24,7 +24,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.desugar.ServiceLoaderSourceCode;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.ListUtils;
 import com.google.common.collect.ImmutableList;
@@ -198,7 +197,7 @@
         appView
             .getSyntheticItems()
             .createMethod(
-                SyntheticKind.SERVICE_LOADER,
+                kinds -> kinds.SERVICE_LOADER,
                 methodProcessingContext.createUniqueContext(),
                 appView,
                 builder ->
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
index 5639925..257fd7d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UtilityMethodsForCodeOptimizations.java
@@ -17,7 +17,6 @@
 import com.android.tools.r8.ir.conversion.MethodProcessor;
 import com.android.tools.r8.ir.optimize.templates.CfUtilityMethodsForCodeOptimizations;
 import com.android.tools.r8.synthesis.SyntheticItems;
-import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.InternalOptions;
 
 public class UtilityMethodsForCodeOptimizations {
@@ -36,7 +35,7 @@
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
-            SyntheticNaming.SyntheticKind.TO_STRING_IF_NOT_NULL,
+            kinds -> kinds.TO_STRING_IF_NOT_NULL,
             methodProcessingContext.createUniqueContext(),
             appView,
             builder ->
@@ -65,7 +64,7 @@
     UniqueContext positionContext = methodProcessingContext.createUniqueContext();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
-            SyntheticNaming.SyntheticKind.THROW_CCE_IF_NOT_NULL,
+            kinds -> kinds.THROW_CCE_IF_NOT_NULL,
             positionContext,
             appView,
             builder ->
@@ -95,7 +94,7 @@
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
-            SyntheticNaming.SyntheticKind.THROW_IAE,
+            kinds -> kinds.THROW_IAE,
             methodProcessingContext.createUniqueContext(),
             appView,
             builder ->
@@ -123,7 +122,7 @@
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
-            SyntheticNaming.SyntheticKind.THROW_ICCE,
+            kinds -> kinds.THROW_ICCE,
             methodProcessingContext.createUniqueContext(),
             appView,
             builder ->
@@ -153,7 +152,7 @@
     SyntheticItems syntheticItems = appView.getSyntheticItems();
     ProgramMethod syntheticMethod =
         syntheticItems.createMethod(
-            SyntheticNaming.SyntheticKind.THROW_NSME,
+            kinds -> kinds.THROW_NSME,
             methodProcessingContext.createUniqueContext(),
             appView,
             builder ->
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 3c8bdbc..e19e0b6 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
@@ -48,7 +48,6 @@
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackIgnore;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.ImmutableArrayUtils;
 import com.android.tools.r8.utils.OptionalBool;
 import com.android.tools.r8.utils.SetUtils;
@@ -178,7 +177,7 @@
               appView
                   .getSyntheticItems()
                   .createMethod(
-                      SyntheticKind.ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD,
+                      kinds -> kinds.ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD,
                       // Use the context of the checkNotNull() method to ensure the method is placed
                       // in the same feature split.
                       processorContext
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
index ed8958d..6741924 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/LocalEnumUnboxingUtilityClass.java
@@ -22,7 +22,6 @@
 import com.android.tools.r8.ir.synthetic.EnumUnboxingCfCodeProvider;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.synthesis.SyntheticMethodBuilder.SyntheticCodeGenerator;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 
 public class LocalEnumUnboxingUtilityClass extends EnumUnboxingUtilityClass {
 
@@ -123,7 +122,7 @@
         .ensureFixedClassMethod(
             methodName,
             methodProto,
-            SyntheticKind.ENUM_UNBOXING_LOCAL_UTILITY_CLASS,
+            kinds -> kinds.ENUM_UNBOXING_LOCAL_UTILITY_CLASS,
             getSynthesizingContext(),
             appView,
             emptyConsumer(),
@@ -174,7 +173,7 @@
           appView
               .getSyntheticItems()
               .createFixedClass(
-                  SyntheticKind.ENUM_UNBOXING_LOCAL_UTILITY_CLASS,
+                  kinds -> kinds.ENUM_UNBOXING_LOCAL_UTILITY_CLASS,
                   enumToUnbox,
                   appView,
                   builder -> builder.setUseSortedMethodBacking(true));
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 096c2cf..55d5c50 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
@@ -36,7 +36,6 @@
 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;
 import java.util.ArrayList;
@@ -139,7 +138,7 @@
         .ensureFixedClassMethod(
             methodName,
             methodProto,
-            SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
+            kinds -> kinds.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
             getSynthesizingContext(),
             appView,
             ConsumerUtils.emptyConsumer(),
@@ -202,7 +201,7 @@
           appView
               .getSyntheticItems()
               .createFixedClass(
-                  SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
+                  kinds -> kinds.ENUM_UNBOXING_SHARED_UTILITY_CLASS,
                   synthesizingContext,
                   appView,
                   classBuilder -> {
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 3bc7392..1ba7556 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -5,7 +5,7 @@
 
 import static com.android.tools.r8.graph.DexApplication.classesWithDeterministicOrder;
 import static com.android.tools.r8.utils.StringUtils.EMPTY_CHAR_ARRAY;
-import static com.android.tools.r8.utils.SymbolGenerationUtils.PRIMITIVE_TYPE_NAMES;
+import static com.android.tools.r8.utils.SymbolGenerationUtils.RESERVED_NAMES;
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
@@ -132,7 +132,7 @@
                 SymbolGenerationUtils.numberToIdentifier(state.incrementNameIndex(), mixedCasing);
           } while (obfuscationDictionaryForLookup.contains(nextString));
         }
-      } while (PRIMITIVE_TYPE_NAMES.contains(nextString));
+      } while (RESERVED_NAMES.contains(nextString));
       nextName.append(nextString);
       return nextName.toString();
     }
diff --git a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
index 4599085..1ab29eb 100644
--- a/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/argumentpropagation/ArgumentPropagatorProgramOptimizer.java
@@ -39,7 +39,6 @@
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.KeepFieldInfo;
 import com.android.tools.r8.shaking.KeepMethodInfo;
-import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
 import com.android.tools.r8.utils.AccessUtils;
 import com.android.tools.r8.utils.BooleanBox;
 import com.android.tools.r8.utils.IntBox;
@@ -812,7 +811,7 @@
           appView
               .getSyntheticItems()
               .createClass(
-                  SyntheticKind.NON_FIXED_INIT_TYPE_ARGUMENT,
+                  kinds -> kinds.NON_FIXED_INIT_TYPE_ARGUMENT,
                   processorContext.createMethodProcessingContext(method).createUniqueContext(),
                   appView)
               .getType();
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingAnnotationTracer.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingAnnotationTracer.java
index f52b8b7..d119f1c 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingAnnotationTracer.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingAnnotationTracer.java
@@ -13,16 +13,19 @@
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 
 public class RepackagingAnnotationTracer {
 
   private final AppInfoWithClassHierarchy appInfo;
+  private final GraphLens graphLens;
   private final RepackagingUseRegistry registry;
 
   public RepackagingAnnotationTracer(
       AppView<? extends AppInfoWithClassHierarchy> appView, RepackagingUseRegistry registry) {
     this.appInfo = appView.appInfo();
+    this.graphLens = appView.graphLens();
     this.registry = registry;
   }
 
@@ -39,7 +42,7 @@
   }
 
   private void traceEncodedAnnotation(DexEncodedAnnotation annotation) {
-    registry.registerTypeReference(annotation.type);
+    registry.registerTypeReference(annotation.type, graphLens);
     annotation.forEachElement(this::traceAnnotationElement);
   }
 
@@ -94,11 +97,14 @@
         break;
 
       case METHOD_TYPE:
-        value.asDexValueMethodType().getValue().forEachType(registry::registerTypeReference);
+        value
+            .asDexValueMethodType()
+            .getValue()
+            .forEachType(type -> registry.registerTypeReference(type, graphLens));
         break;
 
       case TYPE:
-        registry.registerTypeReference(value.asDexValueType().getValue());
+        registry.registerTypeReference(value.asDexValueType().getValue(), graphLens);
         break;
 
       default:
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
index 9854d69..e17be11 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingConstraintGraph.java
@@ -105,8 +105,8 @@
         new RepackagingUseRegistry(appView, this, clazz, libraryBoundaryNode);
 
     // Trace the references to the immediate super types.
-    registry.registerTypeReference(clazz.getSuperType());
-    clazz.interfaces.forEach(registry::registerTypeReference);
+    registry.registerTypeReference(clazz.getSuperType(), appView.graphLens());
+    clazz.interfaces.forEach(type -> registry.registerTypeReference(type, appView.graphLens()));
 
     // Trace the references from the class annotations.
     new RepackagingAnnotationTracer(appView, registry).trace(clazz.annotations());
@@ -114,10 +114,10 @@
     // Trace the references in the nest host and/or members.
     if (clazz.isInANest()) {
       if (clazz.isNestHost()) {
-        clazz.forEachNestMember(registry::registerTypeReference);
+        clazz.forEachNestMember(type -> registry.registerTypeReference(type, appView.graphLens()));
       } else {
         assert clazz.isNestMember();
-        registry.registerTypeReference(clazz.getNestHost());
+        registry.registerTypeReference(clazz.getNestHost(), appView.graphLens());
       }
     }
 
@@ -139,7 +139,7 @@
         new RepackagingUseRegistry(appView, this, field, libraryBoundaryNode);
 
     // Trace the type of the field.
-    registry.registerTypeReference(field.getReference().getType());
+    registry.registerTypeReference(field.getReference().getType(), appView.graphLens());
 
     // Trace the references in the field annotations.
     new RepackagingAnnotationTracer(appView, registry).trace(field.getDefinition().annotations());
@@ -151,7 +151,9 @@
         new RepackagingUseRegistry(appView, this, method, libraryBoundaryNode);
 
     // Trace the type references in the method signature.
-    definition.getProto().forEachType(registry::registerTypeReference);
+    definition
+        .getProto()
+        .forEachType(type -> registry.registerTypeReference(type, appView.graphLens()));
 
     // Check if this overrides a package-private method.
     DexClass superClass =
diff --git a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
index 5c5a857..1339cbf 100644
--- a/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
+++ b/src/main/java/com/android/tools/r8/repackaging/RepackagingUseRegistry.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMember;
 import com.android.tools.r8.graph.DexField;
@@ -16,6 +17,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
+import com.android.tools.r8.graph.GraphLens;
 import com.android.tools.r8.graph.InitClassLens;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.MemberResolutionResult;
@@ -36,10 +38,13 @@
 
   private final AppInfoWithLiveness appInfo;
   private final InternalOptions options;
+  private final GraphLens graphLens;
   private final RepackagingConstraintGraph constraintGraph;
   private final InitClassLens initClassLens;
   private final RepackagingConstraintGraph.Node node;
   private final RepackagingConstraintGraph.Node missingTypeNode;
+  private final GraphLens codeLens;
+  private final ProgramMethod methodContext;
 
   public RepackagingUseRegistry(
       AppView<AppInfoWithLiveness> appView,
@@ -51,8 +56,18 @@
     this.options = appView.options();
     this.constraintGraph = constraintGraph;
     this.initClassLens = appView.initClassLens();
+    this.graphLens = appView.graphLens();
     this.node = constraintGraph.getNode(context.getDefinition());
     this.missingTypeNode = missingTypeNode;
+    GraphLens codeLens = appView.graphLens();
+    if (context.isMethod()) {
+      Code code = context.asMethod().getDefinition().getCode();
+      if (code != null) {
+        codeLens = code.getCodeLens(appView);
+      }
+    }
+    this.codeLens = codeLens;
+    methodContext = context.isMethod() ? context.asMethod() : null;
   }
 
   private boolean isOnlyAccessibleFromSamePackage(DexClass referencedClass) {
@@ -60,11 +75,8 @@
     if (accessFlags.isPackagePrivate()) {
       return true;
     }
-    if (accessFlags.isProtected()
-        && !appInfo.isSubtype(getContext().getContextType(), referencedClass.getType())) {
-      return true;
-    }
-    return false;
+    return accessFlags.isProtected()
+        && !appInfo.isSubtype(getContext().getContextType(), referencedClass.getType());
   }
 
   private boolean isOnlyAccessibleFromSamePackage(
@@ -92,7 +104,7 @@
   }
 
   public void registerFieldAccess(DexField field) {
-    registerMemberAccess(appInfo.resolveField(field), false);
+    registerMemberAccess(appInfo.resolveField(graphLens.lookupField(field)), false);
   }
 
   public ProgramMethod registerMethodReference(DexMethod method) {
@@ -189,32 +201,46 @@
 
   @Override
   public void registerInitClass(DexType type) {
-    registerFieldAccess(initClassLens.getInitClassField(type));
+    registerMemberAccess(
+        appInfo.resolveField(initClassLens.getInitClassField(graphLens.lookupClassType(type))),
+        false);
   }
 
   @Override
   public void registerInvokeVirtual(DexMethod invokedMethod) {
-    registerMemberAccessForInvoke(appInfo.resolveMethod(invokedMethod, false));
+    registerMemberAccessForInvoke(
+        appInfo.resolveMethod(
+            graphLens.lookupInvokeVirtual(invokedMethod, methodContext, codeLens).getReference(),
+            false));
   }
 
   @Override
   public void registerInvokeDirect(DexMethod invokedMethod) {
-    registerMemberAccessForInvoke(appInfo.unsafeResolveMethodDueToDexFormat(invokedMethod));
+    registerMemberAccessForInvoke(
+        appInfo.unsafeResolveMethodDueToDexFormat(
+            graphLens.lookupInvokeDirect(invokedMethod, methodContext, codeLens).getReference()));
   }
 
   @Override
   public void registerInvokeStatic(DexMethod invokedMethod) {
-    registerMemberAccessForInvoke(appInfo.unsafeResolveMethodDueToDexFormat(invokedMethod));
+    registerMemberAccessForInvoke(
+        appInfo.unsafeResolveMethodDueToDexFormat(
+            graphLens.lookupInvokeStatic(invokedMethod, methodContext, codeLens).getReference()));
   }
 
   @Override
   public void registerInvokeInterface(DexMethod invokedMethod) {
-    registerMemberAccessForInvoke(appInfo.resolveMethod(invokedMethod, true));
+    registerMemberAccessForInvoke(
+        appInfo.resolveMethod(
+            graphLens.lookupInvokeInterface(invokedMethod, methodContext, codeLens).getReference(),
+            true));
   }
 
   @Override
   public void registerInvokeSuper(DexMethod invokedMethod) {
-    registerMemberAccessForInvoke(appInfo.unsafeResolveMethodDueToDexFormat(invokedMethod));
+    registerMemberAccessForInvoke(
+        appInfo.unsafeResolveMethodDueToDexFormat(
+            graphLens.lookupInvokeSuper(invokedMethod, methodContext, codeLens).getReference()));
   }
 
   @Override
@@ -229,7 +255,7 @@
 
   @Override
   public void registerNewInstance(DexType type) {
-    registerTypeAccess(type);
+    registerTypeAccess(graphLens.lookupClassType(type));
   }
 
   @Override
@@ -244,12 +270,16 @@
 
   @Override
   public void registerTypeReference(DexType type) {
-    registerTypeAccess(type);
+    registerTypeReference(type, codeLens);
+  }
+
+  public void registerTypeReference(DexType type, GraphLens applied) {
+    registerTypeAccess(graphLens.lookupType(type, applied));
   }
 
   @Override
   public void registerInstanceOf(DexType type) {
-    registerTypeAccess(type);
+    registerTypeAccess(graphLens.lookupType(type));
   }
 
   public void registerEnclosingMethodAttribute(EnclosingMethodAttribute enclosingMethodAttribute) {
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingProvider.java b/src/main/java/com/android/tools/r8/retrace/MappingProvider.java
new file mode 100644
index 0000000..18bf9ee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingProvider.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, 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.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.MappingProviderInternal;
+
+@Keep
+public abstract class MappingProvider extends MappingProviderInternal {}
diff --git a/src/main/java/com/android/tools/r8/retrace/MappingProviderBuilder.java b/src/main/java/com/android/tools/r8/retrace/MappingProviderBuilder.java
new file mode 100644
index 0000000..f9dbd492
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/MappingProviderBuilder.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2022, 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.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+
+@Keep
+public abstract class MappingProviderBuilder<
+    P extends MappingProvider, T extends MappingProviderBuilder<P, T>> {
+
+  public abstract T self();
+
+  public abstract T setAllowExperimental(boolean allowExperimental);
+
+  public abstract T setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler);
+
+  public abstract P build();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java b/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java
new file mode 100644
index 0000000..6e8d4fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, 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.retrace;
+
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.retrace.internal.ProguardMappingProviderBuilderImpl;
+
+@Keep
+public abstract class ProguardMappingProvider extends MappingProvider {
+
+  public static Builder builder() {
+    return new ProguardMappingProviderBuilderImpl();
+  }
+
+  @Keep
+  public abstract static class Builder
+      extends MappingProviderBuilder<ProguardMappingProvider, Builder> {
+
+    public abstract Builder setProguardMapProducer(ProguardMapProducer proguardMapProducer);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index b4467ea..61ba441 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -314,11 +314,17 @@
       // The setup of a retracer should likely also follow a builder pattern instead of having
       // static create methods. That would avoid the need to method overload the construction here
       // and the default create would become the default build of a retracer.
+      MappingProvider mappingProvider =
+          ProguardMappingProvider.builder()
+              .setProguardMapProducer(options.getProguardMapProducer())
+              .setDiagnosticsHandler(diagnosticsHandler)
+              .setAllowExperimental(allowExperimentalMapping)
+              .build();
       RetracerImpl retracer =
-          RetracerImpl.create(
-              options.getProguardMapProducer(),
-              options.getDiagnosticsHandler(),
-              allowExperimentalMapping);
+          RetracerImpl.builder()
+              .setMappingProvider(mappingProvider)
+              .setDiagnosticsHandler(diagnosticsHandler)
+              .build();
       retracer
           .getMapVersions()
           .forEach(
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 2cbe2a9..9a4eadf 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -72,11 +72,22 @@
 
   static Retracer createDefault(
       ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
-    return RetracerImpl.create(proguardMapProducer, diagnosticsHandler, false);
+    try {
+      ProguardMappingProvider mappingProvider =
+          ProguardMappingProvider.builder()
+              .setProguardMapProducer(proguardMapProducer)
+              .setDiagnosticsHandler(diagnosticsHandler)
+              .build();
+      return Retracer.builder()
+          .setMappingProvider(mappingProvider)
+          .setDiagnosticsHandler(diagnosticsHandler)
+          .build();
+    } catch (Exception e) {
+      throw new InvalidMappingFileException(e);
+    }
   }
 
-  static Retracer createExperimental(
-      ProguardMapProducer proguardMapProducer, DiagnosticsHandler diagnosticsHandler) {
-    return RetracerImpl.create(proguardMapProducer, diagnosticsHandler, true);
+  static RetracerBuilder builder() {
+    return RetracerImpl.builder();
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java b/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java
new file mode 100644
index 0000000..efd56ce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetracerBuilder.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, 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.retrace;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.Keep;
+
+@Keep
+public interface RetracerBuilder {
+
+  RetracerBuilder setMappingProvider(MappingProvider mappingProvider);
+
+  RetracerBuilder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler);
+
+  Retracer build();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java b/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
deleted file mode 100644
index b243ff5..0000000
--- a/src/main/java/com/android/tools/r8/retrace/internal/DirectClassNameMapperProguardMapProducer.java
+++ /dev/null
@@ -1,19 +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.retrace.internal;
-
-import com.android.tools.r8.naming.ClassNameMapper;
-import com.android.tools.r8.retrace.ProguardMapProducer;
-import java.io.BufferedReader;
-
-public interface DirectClassNameMapperProguardMapProducer extends ProguardMapProducer {
-
-  ClassNameMapper getClassNameMapper();
-
-  @Override
-  default BufferedReader get() {
-    throw new RuntimeException("Should not be called for DirectClassNameMapperProguardMapProducer");
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
new file mode 100644
index 0000000..c0739c0
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, 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.retrace.internal;
+
+import com.android.tools.r8.naming.ClassNameMapper;
+
+public abstract class MappingProviderInternal {
+
+  abstract ClassNameMapper getClassNameMapper();
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
new file mode 100644
index 0000000..8162f5c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -0,0 +1,58 @@
+// Copyright (c) 2022, 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.retrace.internal;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.retrace.InvalidMappingFileException;
+import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingProvider;
+import java.io.BufferedReader;
+
+public class ProguardMappingProviderBuilderImpl extends ProguardMappingProvider.Builder {
+
+  private ProguardMapProducer proguardMapProducer;
+  private boolean allowExperimental = false;
+  private DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
+
+  @Override
+  public ProguardMappingProvider.Builder self() {
+    return this;
+  }
+
+  @Override
+  public ProguardMappingProvider.Builder setAllowExperimental(boolean allowExperimental) {
+    this.allowExperimental = allowExperimental;
+    return self();
+  }
+
+  @Override
+  public ProguardMappingProvider.Builder setDiagnosticsHandler(
+      DiagnosticsHandler diagnosticsHandler) {
+    this.diagnosticsHandler = diagnosticsHandler;
+    return self();
+  }
+
+  @Override
+  public ProguardMappingProvider.Builder setProguardMapProducer(
+      ProguardMapProducer proguardMapProducer) {
+    this.proguardMapProducer = proguardMapProducer;
+    return self();
+  }
+
+  @Override
+  public ProguardMappingProvider build() {
+    try {
+      return new ProguardMappingProviderImpl(
+          ClassNameMapper.mapperFromBufferedReader(
+              new BufferedReader(proguardMapProducer.get()),
+              diagnosticsHandler,
+              true,
+              allowExperimental));
+    } catch (Exception e) {
+      throw new InvalidMappingFileException(e);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
new file mode 100644
index 0000000..8ea72fa
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
@@ -0,0 +1,26 @@
+// Copyright (c) 2022, 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.retrace.internal;
+
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.retrace.ProguardMappingProvider;
+
+/**
+ * IntelliJ highlights the class as being invalid because it cannot see getClassNameMapper is
+ * defined on the class for some reason.
+ */
+public class ProguardMappingProviderImpl extends ProguardMappingProvider {
+
+  private final ClassNameMapper classNameMapper;
+
+  public ProguardMappingProviderImpl(ClassNameMapper classNameMapper) {
+    this.classNameMapper = classNameMapper;
+  }
+
+  @Override
+  ClassNameMapper getClassNameMapper() {
+    return classNameMapper;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index 6cac8dd..b01aa7f 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -11,12 +11,11 @@
 import com.android.tools.r8.references.FieldReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
-import com.android.tools.r8.retrace.InvalidMappingFileException;
-import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.RetraceFrameResult;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.Retracer;
-import java.io.BufferedReader;
+import com.android.tools.r8.retrace.RetracerBuilder;
 import java.util.OptionalInt;
 import java.util.Set;
 
@@ -26,34 +25,12 @@
   private final ClassNameMapper classNameMapper;
   private final DiagnosticsHandler diagnosticsHandler;
 
-  public RetracerImpl(ClassNameMapper classNameMapper, DiagnosticsHandler diagnosticsHandler) {
+  private RetracerImpl(ClassNameMapper classNameMapper, DiagnosticsHandler diagnosticsHandler) {
     this.classNameMapper = classNameMapper;
     this.diagnosticsHandler = diagnosticsHandler;
     assert classNameMapper != null;
   }
 
-  public static RetracerImpl create(
-      ProguardMapProducer proguardMapProducer,
-      DiagnosticsHandler diagnosticsHandler,
-      boolean allowExperimentalMapping) {
-    if (proguardMapProducer instanceof DirectClassNameMapperProguardMapProducer) {
-      return new RetracerImpl(
-          ((DirectClassNameMapperProguardMapProducer) proguardMapProducer).getClassNameMapper(),
-          diagnosticsHandler);
-    }
-    try {
-      ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromBufferedReader(
-              new BufferedReader(proguardMapProducer.get()),
-              diagnosticsHandler,
-              true,
-              allowExperimentalMapping);
-      return new RetracerImpl(classNameMapper, diagnosticsHandler);
-    } catch (Throwable throwable) {
-      throw new InvalidMappingFileException(throwable);
-    }
-  }
-
   public DiagnosticsHandler getDiagnosticsHandler() {
     return diagnosticsHandler;
   }
@@ -109,4 +86,34 @@
   public Set<MapVersionMappingInformation> getMapVersions() {
     return classNameMapper.getMapVersions();
   }
+
+  public static Builder builder() {
+    return new Builder();
+  }
+
+  public static class Builder implements RetracerBuilder {
+
+    private MappingProvider mappingProvider;
+    private DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
+
+    private Builder() {}
+
+    @Override
+    public Builder setMappingProvider(MappingProvider mappingProvider) {
+      this.mappingProvider = mappingProvider;
+      return this;
+    }
+
+    @Override
+    public Builder setDiagnosticsHandler(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+      return this;
+    }
+
+    @Override
+    public RetracerImpl build() {
+      return new RetracerImpl(
+          ((MappingProviderInternal) mappingProvider).getClassNameMapper(), diagnosticsHandler);
+    }
+  }
 }
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 1125b65..c3950c4 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -155,7 +155,6 @@
 import com.google.common.collect.Sets.SetView;
 import it.unimi.dsi.fastutil.objects.Object2BooleanArrayMap;
 import it.unimi.dsi.fastutil.objects.Object2BooleanMap;
-import java.lang.reflect.InvocationHandler;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -246,7 +245,7 @@
   // Don't hold a direct pointer to app info (use appView).
   private AppInfoWithClassHierarchy appInfo;
   private final AppView<AppInfoWithClassHierarchy> appView;
-  private final DexItemFactory dexItemFactory;
+  private final EnqueuerDeferredTracing deferredTracing;
   private final ExecutorService executorService;
   private SubtypingInfo subtypingInfo;
   private final InternalOptions options;
@@ -462,7 +461,7 @@
     InternalOptions options = appView.options();
     this.appInfo = appView.appInfo();
     this.appView = appView.withClassHierarchy();
-    this.dexItemFactory = appView.dexItemFactory();
+    this.deferredTracing = new EnqueuerDeferredTracing();
     this.executorService = executorService;
     this.subtypingInfo = subtypingInfo;
     this.forceProguardCompatibility = options.forceProguardCompatibility;
@@ -1460,282 +1459,339 @@
   }
 
   void traceInstanceFieldRead(DexField field, ProgramMethod currentMethod) {
-    traceInstanceFieldRead(field, currentMethod, FieldReadType.READ);
+    traceInstanceFieldRead(field, currentMethod, FieldAccessMetadata.DEFAULT);
   }
 
   void traceInstanceFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
-    traceInstanceFieldRead(field, currentMethod, FieldReadType.READ_FROM_METHOD_HANDLE);
+    traceInstanceFieldRead(field, currentMethod, FieldAccessMetadata.FROM_METHOD_HANDLE);
   }
 
   void traceInstanceFieldReadFromRecordMethodHandle(DexField field, ProgramMethod currentMethod) {
-    traceInstanceFieldRead(field, currentMethod, FieldReadType.READ_FROM_RECORD_METHOD_HANDLE);
+    traceInstanceFieldRead(field, currentMethod, FieldAccessMetadata.FROM_RECORD_METHOD_HANDLE);
   }
 
-  private enum FieldReadType {
-    READ,
-    READ_FROM_METHOD_HANDLE,
-    READ_FROM_RECORD_METHOD_HANDLE
+  enum FieldAccessKind {
+    INSTANCE_READ,
+    INSTANCE_WRITE,
+    STATIC_READ,
+    STATIC_WRITE;
+
+    boolean isRead() {
+      return this == INSTANCE_READ || this == STATIC_READ;
+    }
+
+    boolean isStatic() {
+      return this == STATIC_READ || this == STATIC_WRITE;
+    }
+
+    boolean isWrite() {
+      return !isRead();
+    }
+  }
+
+  static class FieldAccessMetadata {
+
+    private static int FROM_METHOD_HANDLE_MASK = 1;
+    private static int FROM_RECORD_METHOD_HANDLE_MASK = 2;
+
+    static FieldAccessMetadata DEFAULT = new FieldAccessMetadata(0);
+    static FieldAccessMetadata FROM_METHOD_HANDLE =
+        new FieldAccessMetadata(FROM_METHOD_HANDLE_MASK);
+    static FieldAccessMetadata FROM_RECORD_METHOD_HANDLE =
+        new FieldAccessMetadata(FROM_RECORD_METHOD_HANDLE_MASK);
+
+    private final int flags;
+
+    FieldAccessMetadata(int flags) {
+      this.flags = flags;
+    }
+
+    boolean isFromMethodHandle() {
+      return (flags & FROM_METHOD_HANDLE_MASK) != 0;
+    }
+
+    boolean isFromRecordMethodHandle() {
+      return (flags & FROM_RECORD_METHOD_HANDLE_MASK) != 0;
+    }
   }
 
   private void traceInstanceFieldRead(
-      DexField fieldReference, ProgramMethod currentMethod, FieldReadType readType) {
+      DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
     if (!registerFieldRead(fieldReference, currentMethod)) {
       return;
     }
 
-    resolveField(fieldReference, currentMethod)
-        .visitFieldResolutionResults(
-            resolutionResult -> {
-              fieldAccessAnalyses.forEach(
-                  analysis ->
-                      analysis.traceInstanceFieldRead(
-                          fieldReference, resolutionResult, currentMethod, workList));
+    FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+    if (deferredTracing.deferTracingOfFieldAccess(
+        fieldReference, resolutionResult, currentMethod, FieldAccessKind.INSTANCE_READ, metadata)) {
+      return;
+    }
 
-              ProgramField field = resolutionResult.getProgramField();
-              if (field == null) {
-                // No need to trace into the non-program code.
-                return;
-              }
+    resolutionResult.visitFieldResolutionResults(
+        singleResolutionResult -> {
+          fieldAccessAnalyses.forEach(
+              analysis ->
+                  analysis.traceInstanceFieldRead(
+                      fieldReference, singleResolutionResult, currentMethod, workList));
 
-              assert !mode.isFinalTreeShaking()
-                      || !field.getDefinition().getOptimizationInfo().isDead()
-                  : "Unexpected reference in `"
-                      + currentMethod.toSourceString()
-                      + "` to field marked dead: "
-                      + field.getReference().toSourceString();
+          ProgramField field = singleResolutionResult.getProgramField();
+          if (field == null) {
+            // No need to trace into the non-program code.
+            return;
+          }
 
-              if (readType == FieldReadType.READ_FROM_METHOD_HANDLE) {
-                fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
-              } else if (readType == FieldReadType.READ_FROM_RECORD_METHOD_HANDLE) {
-                fieldAccessInfoCollection
-                    .get(field.getReference())
-                    .setReadFromRecordInvokeDynamic();
-              }
+          assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+              : "Unexpected reference in `"
+                  + currentMethod.toSourceString()
+                  + "` to field marked dead: "
+                  + field.getReference().toSourceString();
 
-              if (Log.ENABLED) {
-                Log.verbose(getClass(), "Register Iget `%s`.", fieldReference);
-              }
+          if (metadata.isFromMethodHandle()) {
+            fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
+          } else if (metadata.isFromRecordMethodHandle()) {
+            fieldAccessInfoCollection.get(field.getReference()).setReadFromRecordInvokeDynamic();
+          }
 
-              if (field.getReference() != fieldReference) {
-                // Mark the initial resolution holder as live.
-                markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
-              }
+          if (Log.ENABLED) {
+            Log.verbose(getClass(), "Register Iget `%s`.", fieldReference);
+          }
 
-              workList.enqueueMarkFieldAsReachableAction(
-                  field, currentMethod, KeepReason.fieldReferencedIn(currentMethod));
-            },
-            failedResolution -> {
-              // Must trace the types from the field reference even if it does not exist.
-              traceFieldReference(fieldReference, failedResolution, currentMethod);
-              noClassMerging.add(fieldReference.getHolderType());
-            });
+          if (field.getReference() != fieldReference) {
+            // Mark the initial resolution holder as live.
+            markTypeAsLive(singleResolutionResult.getInitialResolutionHolder(), currentMethod);
+          }
+
+          workList.enqueueMarkFieldAsReachableAction(
+              field, currentMethod, KeepReason.fieldReferencedIn(currentMethod));
+        },
+        failedResolution -> {
+          // Must trace the types from the field reference even if it does not exist.
+          traceFieldReference(fieldReference, failedResolution, currentMethod);
+          noClassMerging.add(fieldReference.getHolderType());
+        });
   }
 
   void traceInstanceFieldWrite(DexField field, ProgramMethod currentMethod) {
-    traceInstanceFieldWrite(field, currentMethod, false);
+    traceInstanceFieldWrite(field, currentMethod, FieldAccessMetadata.DEFAULT);
   }
 
   void traceInstanceFieldWriteFromMethodHandle(DexField field, ProgramMethod currentMethod) {
-    traceInstanceFieldWrite(field, currentMethod, true);
+    traceInstanceFieldWrite(field, currentMethod, FieldAccessMetadata.FROM_METHOD_HANDLE);
   }
 
   private void traceInstanceFieldWrite(
-      DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
+      DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
     if (!registerFieldWrite(fieldReference, currentMethod)) {
       return;
     }
 
-    resolveField(fieldReference, currentMethod)
-        .visitFieldResolutionResults(
-            resolutionResult -> {
-              fieldAccessAnalyses.forEach(
-                  analysis ->
-                      analysis.traceInstanceFieldWrite(
-                          fieldReference, resolutionResult, currentMethod, workList));
+    FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+    if (deferredTracing.deferTracingOfFieldAccess(
+        fieldReference,
+        resolutionResult,
+        currentMethod,
+        FieldAccessKind.INSTANCE_WRITE,
+        metadata)) {
+      return;
+    }
 
-              ProgramField field = resolutionResult.getProgramField();
-              if (field == null) {
-                // No need to trace into the non-program code.
-                return;
-              }
+    resolutionResult.visitFieldResolutionResults(
+        singleResolutionResult -> {
+          fieldAccessAnalyses.forEach(
+              analysis ->
+                  analysis.traceInstanceFieldWrite(
+                      fieldReference, singleResolutionResult, currentMethod, workList));
 
-              assert !mode.isFinalTreeShaking()
-                      || !field.getDefinition().getOptimizationInfo().isDead()
-                  : "Unexpected reference in `"
-                      + currentMethod.toSourceString()
-                      + "` to field marked dead: "
-                      + field.getReference().toSourceString();
+          ProgramField field = singleResolutionResult.getProgramField();
+          if (field == null) {
+            // No need to trace into the non-program code.
+            return;
+          }
 
-              if (fromMethodHandle) {
-                fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
-              }
+          assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+              : "Unexpected reference in `"
+                  + currentMethod.toSourceString()
+                  + "` to field marked dead: "
+                  + field.getReference().toSourceString();
 
-              if (Log.ENABLED) {
-                Log.verbose(getClass(), "Register Iput `%s`.", fieldReference);
-              }
+          if (metadata.isFromMethodHandle()) {
+            fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
+          }
 
-              if (field.getReference() != fieldReference) {
-                // Mark the initial resolution holder as live.
-                markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
-              }
+          if (Log.ENABLED) {
+            Log.verbose(getClass(), "Register Iput `%s`.", fieldReference);
+          }
 
-              KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
-              workList.enqueueMarkFieldAsReachableAction(field, currentMethod, reason);
-            },
-            failedResolution -> {
-              // Must trace the types from the field reference even if it does not exist.
-              traceFieldReference(fieldReference, failedResolution, currentMethod);
-              noClassMerging.add(fieldReference.getHolderType());
-            });
+          if (field.getReference() != fieldReference) {
+            // Mark the initial resolution holder as live.
+            markTypeAsLive(singleResolutionResult.getInitialResolutionHolder(), currentMethod);
+          }
+
+          KeepReason reason = KeepReason.fieldReferencedIn(currentMethod);
+          workList.enqueueMarkFieldAsReachableAction(field, currentMethod, reason);
+        },
+        failedResolution -> {
+          // Must trace the types from the field reference even if it does not exist.
+          traceFieldReference(fieldReference, failedResolution, currentMethod);
+          noClassMerging.add(fieldReference.getHolderType());
+        });
   }
 
   void traceStaticFieldRead(DexField field, ProgramMethod currentMethod) {
-    traceStaticFieldRead(field, currentMethod, false);
+    traceStaticFieldRead(field, currentMethod, FieldAccessMetadata.DEFAULT);
   }
 
   void traceStaticFieldReadFromMethodHandle(DexField field, ProgramMethod currentMethod) {
-    traceStaticFieldRead(field, currentMethod, true);
+    traceStaticFieldRead(field, currentMethod, FieldAccessMetadata.FROM_METHOD_HANDLE);
   }
 
   private void traceStaticFieldRead(
-      DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
+      DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
     if (!registerFieldRead(fieldReference, currentMethod)) {
       return;
     }
 
-    resolveField(fieldReference, currentMethod)
-        .visitFieldResolutionResults(
-            resolutionResult -> {
-              fieldAccessAnalyses.forEach(
-                  analysis ->
-                      analysis.traceStaticFieldRead(
-                          fieldReference, resolutionResult, currentMethod, workList));
+    FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+    if (deferredTracing.deferTracingOfFieldAccess(
+        fieldReference, resolutionResult, currentMethod, FieldAccessKind.STATIC_READ, metadata)) {
+      return;
+    }
 
-              ProgramField field = resolutionResult.getProgramField();
-              if (field == null) {
-                // No need to trace into the non-program code.
-                return;
-              }
+    resolutionResult.visitFieldResolutionResults(
+        singleResolutionResult -> {
+          fieldAccessAnalyses.forEach(
+              analysis ->
+                  analysis.traceStaticFieldRead(
+                      fieldReference, singleResolutionResult, currentMethod, workList));
 
-              assert !mode.isFinalTreeShaking()
-                      || !field.getDefinition().getOptimizationInfo().isDead()
-                  : "Unexpected reference in `"
-                      + currentMethod.toSourceString()
-                      + "` to field marked dead: "
-                      + field.getReference().toSourceString();
+          ProgramField field = singleResolutionResult.getProgramField();
+          if (field == null) {
+            // No need to trace into the non-program code.
+            return;
+          }
 
-              if (fromMethodHandle) {
-                fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
-              }
+          assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+              : "Unexpected reference in `"
+                  + currentMethod.toSourceString()
+                  + "` to field marked dead: "
+                  + field.getReference().toSourceString();
 
-              if (Log.ENABLED) {
-                Log.verbose(getClass(), "Register Sget `%s`.", fieldReference);
-              }
+          if (metadata.isFromMethodHandle()) {
+            fieldAccessInfoCollection.get(field.getReference()).setReadFromMethodHandle();
+          }
 
-              // If it is a dead proto extension field, don't trace onwards.
-              boolean skipTracing =
-                  appView.withGeneratedExtensionRegistryShrinker(
-                      shrinker ->
-                          shrinker.isDeadProtoExtensionField(
-                              field, fieldAccessInfoCollection, keepInfo),
-                      false);
-              if (skipTracing) {
-                addDeadProtoTypeCandidate(field.getHolder());
-                return;
-              }
+          if (Log.ENABLED) {
+            Log.verbose(getClass(), "Register Sget `%s`.", fieldReference);
+          }
 
-              if (field.getReference() != fieldReference) {
-                // Mark the initial resolution holder as live. Note that this should only be done if
-                // the field
-                // is not a dead proto field (in which case we bail-out above).
-                markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
-              }
-
-              markFieldAsLive(field, currentMethod);
-            },
-            failedResolution -> {
-              // Must trace the types from the field reference even if it does not exist.
-              traceFieldReference(fieldReference, failedResolution, currentMethod);
-              noClassMerging.add(fieldReference.getHolderType());
-              // Record field reference for generated extension registry shrinking.
+          // If it is a dead proto extension field, don't trace onwards.
+          boolean skipTracing =
               appView.withGeneratedExtensionRegistryShrinker(
                   shrinker ->
-                      shrinker.handleFailedOrUnknownFieldResolution(
-                          fieldReference, currentMethod, mode));
-            });
+                      shrinker.isDeadProtoExtensionField(
+                          field, fieldAccessInfoCollection, keepInfo),
+                  false);
+          if (skipTracing) {
+            addDeadProtoTypeCandidate(field.getHolder());
+            return;
+          }
+
+          if (field.getReference() != fieldReference) {
+            // Mark the initial resolution holder as live. Note that this should only be done if
+            // the field
+            // is not a dead proto field (in which case we bail-out above).
+            markTypeAsLive(singleResolutionResult.getInitialResolutionHolder(), currentMethod);
+          }
+
+          markFieldAsLive(field, currentMethod);
+        },
+        failedResolution -> {
+          // Must trace the types from the field reference even if it does not exist.
+          traceFieldReference(fieldReference, failedResolution, currentMethod);
+          noClassMerging.add(fieldReference.getHolderType());
+          // Record field reference for generated extension registry shrinking.
+          appView.withGeneratedExtensionRegistryShrinker(
+              shrinker ->
+                  shrinker.handleFailedOrUnknownFieldResolution(
+                      fieldReference, currentMethod, mode));
+        });
   }
 
   void traceStaticFieldWrite(DexField field, ProgramMethod currentMethod) {
-    traceStaticFieldWrite(field, currentMethod, false);
+    traceStaticFieldWrite(field, currentMethod, FieldAccessMetadata.DEFAULT);
   }
 
   void traceStaticFieldWriteFromMethodHandle(DexField field, ProgramMethod currentMethod) {
-    traceStaticFieldWrite(field, currentMethod, true);
+    traceStaticFieldWrite(field, currentMethod, FieldAccessMetadata.FROM_METHOD_HANDLE);
   }
 
   private void traceStaticFieldWrite(
-      DexField fieldReference, ProgramMethod currentMethod, boolean fromMethodHandle) {
+      DexField fieldReference, ProgramMethod currentMethod, FieldAccessMetadata metadata) {
     if (!registerFieldWrite(fieldReference, currentMethod)) {
       return;
     }
 
-    resolveField(fieldReference, currentMethod)
-        .visitFieldResolutionResults(
-            resolutionResult -> {
-              fieldAccessAnalyses.forEach(
-                  analysis ->
-                      analysis.traceStaticFieldWrite(
-                          fieldReference, resolutionResult, currentMethod, workList));
+    FieldResolutionResult resolutionResult = resolveField(fieldReference, currentMethod);
+    if (deferredTracing.deferTracingOfFieldAccess(
+        fieldReference, resolutionResult, currentMethod, FieldAccessKind.STATIC_WRITE, metadata)) {
+      return;
+    }
 
-              ProgramField field = resolutionResult.getProgramField();
-              if (field == null) {
-                // No need to trace into the non-program code.
-                return;
-              }
+    resolutionResult.visitFieldResolutionResults(
+        singleResolutionResult -> {
+          fieldAccessAnalyses.forEach(
+              analysis ->
+                  analysis.traceStaticFieldWrite(
+                      fieldReference, singleResolutionResult, currentMethod, workList));
 
-              assert !mode.isFinalTreeShaking()
-                      || !field.getDefinition().getOptimizationInfo().isDead()
-                  : "Unexpected reference in `"
-                      + currentMethod.toSourceString()
-                      + "` to field marked dead: "
-                      + field.getReference().toSourceString();
+          ProgramField field = singleResolutionResult.getProgramField();
+          if (field == null) {
+            // No need to trace into the non-program code.
+            return;
+          }
 
-              if (fromMethodHandle) {
-                fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
-              }
+          assert !mode.isFinalTreeShaking() || !field.getDefinition().getOptimizationInfo().isDead()
+              : "Unexpected reference in `"
+                  + currentMethod.toSourceString()
+                  + "` to field marked dead: "
+                  + field.getReference().toSourceString();
 
-              if (Log.ENABLED) {
-                Log.verbose(getClass(), "Register Sput `%s`.", fieldReference);
-              }
+          if (metadata.isFromMethodHandle()) {
+            fieldAccessInfoCollection.get(field.getReference()).setWrittenFromMethodHandle();
+          }
 
-              if (appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking) {
-                // If it is a dead proto extension field, don't trace onwards.
-                boolean skipTracing =
-                    appView.withGeneratedExtensionRegistryShrinker(
-                        shrinker ->
-                            shrinker.isDeadProtoExtensionField(
-                                field, fieldAccessInfoCollection, keepInfo),
-                        false);
-                if (skipTracing) {
-                  addDeadProtoTypeCandidate(field.getHolder());
-                  return;
-                }
-              }
+          if (Log.ENABLED) {
+            Log.verbose(getClass(), "Register Sput `%s`.", fieldReference);
+          }
 
-              if (field.getReference() != fieldReference) {
-                // Mark the initial resolution holder as live. Note that this should only be done if
-                // the field
-                // is not a dead proto field (in which case we bail-out above).
-                markTypeAsLive(resolutionResult.getInitialResolutionHolder(), currentMethod);
-              }
+          if (appView.options().protoShrinking().enableGeneratedExtensionRegistryShrinking) {
+            // If it is a dead proto extension field, don't trace onwards.
+            boolean skipTracing =
+                appView.withGeneratedExtensionRegistryShrinker(
+                    shrinker ->
+                        shrinker.isDeadProtoExtensionField(
+                            field, fieldAccessInfoCollection, keepInfo),
+                    false);
+            if (skipTracing) {
+              addDeadProtoTypeCandidate(field.getHolder());
+              return;
+            }
+          }
 
-              markFieldAsLive(field, currentMethod);
-            },
-            failedResolution -> {
-              // Must trace the types from the field reference even if it does not exist.
-              traceFieldReference(fieldReference, failedResolution, currentMethod);
-              noClassMerging.add(fieldReference.getHolderType());
-            });
+          if (field.getReference() != fieldReference) {
+            // Mark the initial resolution holder as live. Note that this should only be done if
+            // the field
+            // is not a dead proto field (in which case we bail-out above).
+            markTypeAsLive(singleResolutionResult.getInitialResolutionHolder(), currentMethod);
+          }
+
+          markFieldAsLive(field, currentMethod);
+        },
+        failedResolution -> {
+          // Must trace the types from the field reference even if it does not exist.
+          traceFieldReference(fieldReference, failedResolution, currentMethod);
+          noClassMerging.add(fieldReference.getHolderType());
+        });
   }
 
   private DexMethod getInvokeSuperTarget(DexMethod method, ProgramMethod currentMethod) {
@@ -3760,7 +3816,8 @@
     return true;
   }
 
-  private EnqueuerResult createEnqueuerResult(AppInfoWithClassHierarchy appInfo) {
+  private EnqueuerResult createEnqueuerResult(AppInfoWithClassHierarchy appInfo)
+      throws ExecutionException {
     // Compute the set of dead proto types.
     deadProtoTypeCandidates.removeIf(this::isTypeLive);
     Set<DexType> deadProtoTypes =
@@ -3828,6 +3885,7 @@
               : ImmutableSet.of(syntheticClass.getType());
         };
     amendKeepInfoWithCompanionMethods();
+    deferredTracing.rewriteApplication(executorService);
     AppInfoWithLiveness appInfoWithLiveness =
         new AppInfoWithLiveness(
             appInfo.getSyntheticItems().commit(app),
@@ -4052,6 +4110,12 @@
           continue;
         }
 
+        // Allow deferred tracing to enqueue worklist items.
+        if (deferredTracing.enqueueWorklistActions(workList)) {
+          assert !workList.isEmpty();
+          continue;
+        }
+
         // Notify each analysis that a fixpoint has been reached, and give each analysis an
         // opportunity to add items to the worklist.
         analyses.forEach(analysis -> analysis.notifyFixpoint(this, workList, timing));
diff --git a/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
new file mode 100644
index 0000000..a09a358
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/EnqueuerDeferredTracing.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2022, 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.shaking;
+
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.FieldResolutionResult;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.shaking.Enqueuer.FieldAccessKind;
+import com.android.tools.r8.shaking.Enqueuer.FieldAccessMetadata;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class EnqueuerDeferredTracing {
+
+  /**
+   * Returns true if the {@link Enqueuer} should not trace the given field reference.
+   *
+   * <p>If for some reason the field reference should be traced after all, a worklist item can be
+   * enqueued upon reaching a (preliminary) fixpoint in {@link
+   * #enqueueWorklistActions(EnqueuerWorklist)}, which will cause tracing to continue.
+   */
+  public boolean deferTracingOfFieldAccess(
+      DexField fieldReference,
+      FieldResolutionResult resolutionResult,
+      ProgramMethod context,
+      FieldAccessKind kind,
+      FieldAccessMetadata metadata) {
+    return false;
+  }
+
+  /**
+   * Called when the {@link EnqueuerWorklist} is empty, to allow additional tracing before ending
+   * tree shaking.
+   */
+  public boolean enqueueWorklistActions(EnqueuerWorklist worklist) {
+    return false;
+  }
+
+  /**
+   * Called when tree shaking has ended, to allow rewriting the application according to the tracing
+   * that has not been performed (e.g., rewriting of dead field instructions).
+   */
+  public void rewriteApplication(ExecutorService executorService) throws ExecutionException {
+    // Intentionally empty.
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 91efed4..06ceb79 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -48,7 +48,7 @@
       consumer.accept(type);
       // Super and interfaces are live, no need to add them.
       if (!DexAnnotation.hasSynthesizedClassAnnotation(
-          clazz.annotations(), appView.dexItemFactory())) {
+          clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems())) {
         traceAnnotationsDirectDependencies(clazz.annotations());
       }
       clazz.forEachField(field -> consumer.accept(field.getReference().type));
diff --git a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
index ef9ae30..177fde7 100644
--- a/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
+++ b/src/main/java/com/android/tools/r8/synthesis/CommittedSyntheticsCollection.java
@@ -107,7 +107,7 @@
       ImmutableSet<DexType> allSyntheticInputs =
           newSyntheticInputs == null ? parent.syntheticInputs : newSyntheticInputs.build();
       return new CommittedSyntheticsCollection(
-          allNonLegacyMethods, allNonLegacyClasses, allSyntheticInputs);
+          parent.naming, allNonLegacyMethods, allNonLegacyClasses, allSyntheticInputs);
     }
   }
 
@@ -122,8 +122,7 @@
     return ImmutableMap.copyOf(newSynthetics);
   }
 
-  private static final CommittedSyntheticsCollection EMPTY =
-      new CommittedSyntheticsCollection(ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of());
+  private final SyntheticNaming naming;
 
   /** Mapping from synthetic type to its synthetic method item description. */
   private final ImmutableMap<DexType, List<SyntheticMethodReference>> nonLegacyMethods;
@@ -135,15 +134,21 @@
   public final ImmutableSet<DexType> syntheticInputs;
 
   public CommittedSyntheticsCollection(
+      SyntheticNaming naming,
       ImmutableMap<DexType, List<SyntheticMethodReference>> nonLegacyMethods,
       ImmutableMap<DexType, List<SyntheticProgramClassReference>> nonLegacyClasses,
       ImmutableSet<DexType> syntheticInputs) {
+    this.naming = naming;
     this.nonLegacyMethods = nonLegacyMethods;
     this.nonLegacyClasses = nonLegacyClasses;
     this.syntheticInputs = syntheticInputs;
     assert verifySyntheticInputsSubsetOfSynthetics();
   }
 
+  SyntheticNaming getNaming() {
+    return naming;
+  }
+
   private boolean verifySyntheticInputsSubsetOfSynthetics() {
     Set<DexType> synthetics =
         ImmutableSet.<DexType>builder()
@@ -158,8 +163,9 @@
     return true;
   }
 
-  public static CommittedSyntheticsCollection empty() {
-    return EMPTY;
+  public static CommittedSyntheticsCollection empty(SyntheticNaming naming) {
+    return new CommittedSyntheticsCollection(
+        naming, ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of());
   }
 
   Builder builder() {
@@ -234,7 +240,7 @@
     if (removed.isEmpty()) {
       return this;
     }
-    Builder builder = CommittedSyntheticsCollection.empty().builder();
+    Builder builder = CommittedSyntheticsCollection.empty(naming).builder();
     boolean changed = false;
     for (SyntheticMethodReference reference : IterableUtils.flatten(nonLegacyMethods.values())) {
       if (removed.contains(reference.getHolder())) {
@@ -264,6 +270,7 @@
   CommittedSyntheticsCollection rewriteWithLens(NonIdentityGraphLens lens) {
     ImmutableSet.Builder<DexType> syntheticInputsBuilder = ImmutableSet.builder();
     return new CommittedSyntheticsCollection(
+        naming,
         rewriteItems(nonLegacyMethods, lens, syntheticInputsBuilder),
         rewriteItems(nonLegacyClasses, lens, syntheticInputsBuilder),
         syntheticInputsBuilder.build());
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
index 574f518..097df5d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -70,9 +70,9 @@
       ClassToFeatureSplitMap classToFeatureSplitMap,
       SyntheticItems syntheticItems) {
     HasherWrapper hasher = HasherWrapper.murmur3128Hasher();
-    hasher.putInt(kind.id);
-    if (getKind().isFixedSuffixSynthetic) {
-      // Fixed synthetics are non-shareable. Its unique type is used as the hash key.
+    hasher.putInt(kind.getId());
+    if (!getKind().isShareable()) {
+      // Non-shareable synthetics should use its assumed unique type as the hash.
       getHolder().getType().hash(hasher);
       return hasher.hash();
     }
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
index fdaaaee..07f65be 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -270,7 +270,8 @@
         new CommittedItems(
             SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
             application,
-            new CommittedSyntheticsCollection(finalMethods, finalClasses, finalInputSynthetics),
+            new CommittedSyntheticsCollection(
+                synthetics.getNaming(), finalMethods, finalClasses, finalInputSynthetics),
             ImmutableList.of()),
         syntheticFinalizationGraphLens,
         PrunedItems.builder().setPrunedApp(application).addRemovedClasses(prunedSynthetics).build(),
@@ -717,7 +718,7 @@
       AppView<?> appView,
       Predicate<DexType> reserved) {
     DexItemFactory factory = appView.dexItemFactory();
-    if (kind.isFixedSuffixSynthetic) {
+    if (kind.isFixedSuffixSynthetic()) {
       return SyntheticNaming.createExternalType(kind, externalSyntheticTypePrefix, "", factory);
     }
     NumberGenerator generator =
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 7740805..48ff52a 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -55,9 +55,15 @@
 
   static final int INVALID_ID_AFTER_SYNTHETIC_FINALIZATION = -1;
 
+  private final SyntheticNaming naming;
+
   /** Globally incremented id for the next internal synthetic class. */
   private int nextSyntheticId;
 
+  public SyntheticNaming getNaming() {
+    return naming;
+  }
+
   /** Collection of pending items. */
   private static class PendingSynthetics {
 
@@ -100,13 +106,16 @@
 
   // Empty collection for use only in tests and utilities.
   public static SyntheticItems empty() {
-    return new SyntheticItems(-1, CommittedSyntheticsCollection.empty());
+    return new SyntheticItems(-1, CommittedSyntheticsCollection.empty(null));
   }
 
   // Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
   public static CommittedItems createInitialSyntheticItems(DexApplication application) {
     return new CommittedItems(
-        0, application, CommittedSyntheticsCollection.empty(), ImmutableList.of());
+        0,
+        application,
+        CommittedSyntheticsCollection.empty(application.dexItemFactory().getSyntheticNaming()),
+        ImmutableList.of());
   }
 
   // Only for conversion to a mutable synthetic items collection.
@@ -117,6 +126,7 @@
   private SyntheticItems(int nextSyntheticId, CommittedSyntheticsCollection committed) {
     this.nextSyntheticId = nextSyntheticId;
     this.committed = committed;
+    this.naming = committed.getNaming();
   }
 
   public static void collectSyntheticInputs(AppView<?> appView) {
@@ -190,7 +200,7 @@
     if (clazz != null) {
       assert kind != null;
       assert !baseDefinitionFor.apply(type).hasClassResolutionResult()
-              || kind.mayOverridesNonProgramType
+              || kind.isMayOverridesNonProgramType()
           : "Pending synthetic definition also present in the active program: " + type;
       return clazz;
     }
@@ -245,11 +255,11 @@
     Iterable<SyntheticReference<?, ?, ?>> references = committed.getNonLegacyItems(clazz.getType());
     if (!Iterables.isEmpty(references)) {
       assert Iterables.size(references) == 1;
-      return references.iterator().next().getKind() == SyntheticKind.LAMBDA;
+      return references.iterator().next().getKind() == naming.LAMBDA;
     }
     SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(clazz.getType());
     if (definition != null) {
-      return definition.getKind() == SyntheticKind.LAMBDA;
+      return definition.getKind() == naming.LAMBDA;
     }
     assert false;
     return false;
@@ -268,7 +278,8 @@
     return isSyntheticClass(clazz.type);
   }
 
-  public boolean isSyntheticOfKind(DexType type, SyntheticKind kind) {
+  public boolean isSyntheticOfKind(DexType type, SyntheticKindSelector kindSelector) {
+    SyntheticKind kind = kindSelector.select(naming);
     return pending.containsTypeOfKind(type, kind) || committed.containsTypeOfKind(type, kind);
   }
 
@@ -278,7 +289,7 @@
 
   public FeatureSplit getContextualFeatureSplit(
       DexType type, ClassToFeatureSplitMap classToFeatureSplitMap) {
-    if (isSyntheticOfKind(type, SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
+    if (isSyntheticOfKind(type, kinds -> kinds.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
       // Use the startup base if there is one, such that we don't merge non-startup classes with the
       // shared utility class in case it is used during startup. The use of base startup allows for
       // merging startup classes with the shared utility class, however, which could be bad for
@@ -341,14 +352,14 @@
         committed
             .getNonLegacyMethods()
             .getOrDefault(method.getHolderType(), Collections.emptyList())) {
-      if (reference.getKind() == SyntheticKind.STATIC_INTERFACE_CALL) {
+      if (reference.getKind().equals(naming.STATIC_INTERFACE_CALL)) {
         return true;
       }
     }
     SyntheticDefinition<?, ?, ?> definition =
         pending.nonLegacyDefinitions.get(method.getHolderType());
     if (definition != null) {
-      return definition.getKind() == SyntheticKind.STATIC_INTERFACE_CALL;
+      return definition.getKind().equals(naming.STATIC_INTERFACE_CALL);
     }
     return false;
   }
@@ -364,7 +375,7 @@
     if (definition != null) {
       references = Iterables.concat(references, IterableUtils.singleton(definition.toReference()));
     }
-    if (Iterables.any(references, reference -> reference.getKind() == SyntheticKind.LAMBDA)) {
+    if (Iterables.any(references, reference -> reference.getKind().equals(naming.LAMBDA))) {
       assert ifIsLambda.test(clazz);
     } else {
       assert ifNotLambda.test(clazz);
@@ -489,15 +500,16 @@
   }
 
   public DexProgramClass createClass(
-      SyntheticKind kind, UniqueContext context, AppView<?> appView) {
-    return createClass(kind, context, appView, ConsumerUtils.emptyConsumer());
+      SyntheticKindSelector kindSelector, UniqueContext context, AppView<?> appView) {
+    return createClass(kindSelector, context, appView, ConsumerUtils.emptyConsumer());
   }
 
   public DexProgramClass createClass(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       UniqueContext context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn) {
+    SyntheticKind kind = kindSelector.select(naming);
     // Obtain the outer synthesizing context in the case the context itself is synthetic.
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = getSynthesizingContext(context.getClassContext(), appView);
@@ -511,10 +523,11 @@
 
   // TODO(b/172194101): Make this take a unique context.
   public DexProgramClass createFixedClass(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       DexProgramClass context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn) {
+    SyntheticKind kind = kindSelector.select(naming);
     SynthesizingContext outerContext = internalGetOuterContext(context, appView);
     Function<SynthesizingContext, DexType> contextToType =
         c -> SyntheticNaming.createFixedType(kind, c, appView.dexItemFactory());
@@ -523,8 +536,9 @@
   }
 
   public DexProgramClass getExistingFixedClass(
-      SyntheticKind kind, DexClass context, AppView<?> appView) {
-    assert kind.isFixedSuffixSynthetic;
+      SyntheticKindSelector kindSelector, DexClass context, AppView<?> appView) {
+    SyntheticKind kind = kindSelector.select(naming);
+    assert kind.isFixedSuffixSynthetic();
     SynthesizingContext outerContext = internalGetOuterContext(context, appView);
     DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
     DexClass clazz = appView.definitionFor(type);
@@ -542,18 +556,24 @@
         : SynthesizingContext.fromNonSyntheticInputContext(context.asClasspathOrLibraryClass());
   }
 
+  @FunctionalInterface
+  public interface SyntheticKindSelector {
+    SyntheticKind select(SyntheticNaming naming);
+  }
+
   /**
    * Ensure that a fixed synthetic class exists.
    *
    * <p>This method is thread safe and will synchronize based on the context of the fixed synthetic.
    */
   public DexProgramClass ensureFixedClass(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       DexClass context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn,
       Consumer<DexProgramClass> onCreationConsumer) {
-    assert kind.isFixedSuffixSynthetic;
+    SyntheticKind kind = kindSelector.select(naming);
+    assert kind.isFixedSuffixSynthetic();
     SynthesizingContext outerContext = internalGetOuterContext(context, appView);
     return internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
   }
@@ -561,7 +581,7 @@
   public ProgramMethod ensureFixedClassMethod(
       DexString name,
       DexProto proto,
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       ProgramDefinition context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> buildClassCallback,
@@ -569,7 +589,7 @@
     return ensureFixedClassMethod(
         name,
         proto,
-        kind,
+        kindSelector,
         context,
         appView,
         buildClassCallback,
@@ -580,15 +600,16 @@
   public ProgramMethod ensureFixedClassMethod(
       DexString name,
       DexProto proto,
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       ProgramDefinition context,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> buildClassCallback,
       Consumer<SyntheticMethodBuilder> buildMethodCallback,
       Consumer<ProgramMethod> newMethodCallback) {
+    SyntheticKind kind = kindSelector.select(naming);
     DexProgramClass clazz =
         ensureFixedClass(
-            kind, context.getContextClass(), appView, buildClassCallback, emptyConsumer());
+            kindSelector, context.getContextClass(), appView, buildClassCallback, emptyConsumer());
     DexMethod methodReference = appView.dexItemFactory().createMethod(clazz.getType(), proto, name);
     DexEncodedMethod methodDefinition =
         internalEnsureMethod(
@@ -642,18 +663,19 @@
   }
 
   public DexClasspathClass ensureFixedClasspathClassFromType(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       DexType contextType,
       AppView<?> appView,
       Consumer<SyntheticClasspathClassBuilder> classConsumer,
       Consumer<DexClasspathClass> onCreationConsumer) {
+    SyntheticKind kind = kindSelector.select(naming);
     SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
     return internalEnsureFixedClasspathClass(
         kind, classConsumer, onCreationConsumer, outerContext, appView);
   }
 
   public DexClasspathClass ensureFixedClasspathClass(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       ClasspathOrLibraryClass context,
       AppView<?> appView,
       Consumer<SyntheticClasspathClassBuilder> classConsumer,
@@ -662,13 +684,13 @@
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = SynthesizingContext.fromNonSyntheticInputContext(context);
     return internalEnsureFixedClasspathClass(
-        kind, classConsumer, onCreationConsumer, outerContext, appView);
+        kindSelector.select(naming), classConsumer, onCreationConsumer, outerContext, appView);
   }
 
   public ClasspathMethod ensureFixedClasspathMethodFromType(
       DexString methodName,
       DexProto methodProto,
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       DexType contextType,
       AppView<?> appView,
       Consumer<SyntheticClasspathClassBuilder> classConsumer,
@@ -676,15 +698,15 @@
       Consumer<SyntheticMethodBuilder> buildMethodCallback) {
     DexClasspathClass clazz =
         ensureFixedClasspathClassFromType(
-            kind, contextType, appView, classConsumer, onCreationConsumer);
+            kindSelector, contextType, appView, classConsumer, onCreationConsumer);
     return internalEnsureFixedClasspathMethod(
-        methodName, methodProto, kind, appView, buildMethodCallback, clazz);
+        methodName, methodProto, kindSelector.select(naming), appView, buildMethodCallback, clazz);
   }
 
   public ClasspathMethod ensureFixedClasspathClassMethod(
       DexString methodName,
       DexProto methodProto,
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       ClasspathOrLibraryClass context,
       AppView<?> appView,
       Consumer<SyntheticClasspathClassBuilder> buildClassCallback,
@@ -692,9 +714,9 @@
       Consumer<SyntheticMethodBuilder> buildMethodCallback) {
     DexClasspathClass clazz =
         ensureFixedClasspathClass(
-            kind, context, appView, buildClassCallback, onClassCreationCallback);
+            kindSelector, context, appView, buildClassCallback, onClassCreationCallback);
     return internalEnsureFixedClasspathMethod(
-        methodName, methodProto, kind, appView, buildMethodCallback, clazz);
+        methodName, methodProto, kindSelector.select(naming), appView, buildMethodCallback, clazz);
   }
 
   private ClasspathMethod internalEnsureFixedClasspathMethod(
@@ -748,26 +770,28 @@
   }
 
   public DexProgramClass ensureFixedClassFromType(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       DexType contextType,
       AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn,
       Consumer<DexProgramClass> onCreationConsumer) {
+    SyntheticKind kind = kindSelector.select(naming);
     SynthesizingContext outerContext = SynthesizingContext.fromType(contextType);
     return internalEnsureFixedProgramClass(kind, fn, onCreationConsumer, outerContext, appView);
   }
 
   /** Create a single synthetic method item. */
   public ProgramMethod createMethod(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       UniqueContext context,
       AppView<?> appView,
       Consumer<SyntheticMethodBuilder> fn) {
-    return createMethod(kind, context.getClassContext(), appView, fn, context::getSyntheticSuffix);
+    return createMethod(
+        kindSelector, context.getClassContext(), appView, fn, context::getSyntheticSuffix);
   }
 
   private ProgramMethod createMethod(
-      SyntheticKind kind,
+      SyntheticKindSelector kindSelector,
       ProgramDefinition context,
       AppView<?> appView,
       Consumer<SyntheticMethodBuilder> fn,
@@ -776,6 +800,7 @@
     // Obtain the outer synthesizing context in the case the context itself is synthetic.
     // This is to ensure a flat input-type -> synthetic-item mapping.
     SynthesizingContext outerContext = getSynthesizingContext(context, appView);
+    SyntheticKind kind = kindSelector.select(naming);
     DexType type =
         SyntheticNaming.createInternalType(
             kind, outerContext, syntheticIdSupplier.get(), appView.dexItemFactory());
@@ -832,7 +857,7 @@
         if (!removedClasses.contains(definition.getHolder().getType())) {
           if (definition.isProgramDefinition()) {
             committedProgramTypesBuilder.add(definition.getHolder().getType());
-            if (definition.getKind().mayOverridesNonProgramType) {
+            if (definition.getKind().isMayOverridesNonProgramType()) {
               appBuilder.addProgramClassPotentiallyOverridingNonProgramClass(
                   definition.asProgramDefinition().getHolder());
             } else {
@@ -868,7 +893,7 @@
       // single kind of a synthetic which is required for marking synthetics. This check could be
       // relaxed to ensure that all kinds are equivalent if merging is possible.
       assert !it.hasNext();
-      SyntheticMarker.writeMarkerAttribute(writer, kind);
+      SyntheticMarker.writeMarkerAttribute(writer, kind, appView.getSyntheticItems());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
index d660753..c21954b 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -25,12 +25,13 @@
   private static final String SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME =
       "com.android.tools.r8.SynthesizedClass";
 
-  public static Attribute getMarkerAttributePrototype() {
-    return MarkerAttribute.PROTOTYPE;
+  public static Attribute getMarkerAttributePrototype(SyntheticNaming syntheticNaming) {
+    return new MarkerAttribute(null, syntheticNaming);
   }
 
-  public static void writeMarkerAttribute(ClassWriter writer, SyntheticKind kind) {
-    writer.visitAttribute(new MarkerAttribute(kind));
+  public static void writeMarkerAttribute(
+      ClassWriter writer, SyntheticKind kind, SyntheticItems syntheticItems) {
+    writer.visitAttribute(new MarkerAttribute(kind, syntheticItems.getNaming()));
   }
 
   public static SyntheticMarker readMarkerAttribute(Attribute attribute) {
@@ -43,13 +44,13 @@
 
   private static class MarkerAttribute extends Attribute {
 
-    private static final MarkerAttribute PROTOTYPE = new MarkerAttribute(null);
-
     private SyntheticKind kind;
+    private final SyntheticNaming syntheticNaming;
 
-    public MarkerAttribute(SyntheticKind kind) {
+    public MarkerAttribute(SyntheticKind kind, SyntheticNaming syntheticNaming) {
       super(SYNTHETIC_MARKER_ATTRIBUTE_TYPE_NAME);
       this.kind = kind;
+      this.syntheticNaming = syntheticNaming;
     }
 
     @Override
@@ -62,16 +63,16 @@
         Label[] labels) {
       short id = classReader.readShort(offset);
       assert id >= 0;
-      SyntheticKind kind = SyntheticKind.fromId(id);
-      return new MarkerAttribute(kind);
+      SyntheticKind kind = syntheticNaming.fromId(id);
+      return new MarkerAttribute(kind, syntheticNaming);
     }
 
     @Override
     protected ByteVector write(
         ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
       ByteVector byteVector = new ByteVector();
-      assert 0 <= kind.id && kind.id <= Short.MAX_VALUE;
-      byteVector.putShort(kind.id);
+      assert 0 <= kind.getId() && kind.getId() <= Short.MAX_VALUE;
+      byteVector.putShort(kind.getId());
       return byteVector;
     }
   }
@@ -103,7 +104,7 @@
     SyntheticMarker marker = internalStripMarkerFromClass(clazz, appView);
     assert marker != NO_MARKER
         || !DexAnnotation.hasSynthesizedClassAnnotation(
-            clazz.annotations(), appView.dexItemFactory());
+            clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems());
     return marker;
   }
 
@@ -117,12 +118,12 @@
     }
     SyntheticKind kind =
         DexAnnotation.getSynthesizedClassAnnotationInfo(
-            clazz.annotations(), appView.dexItemFactory());
+            clazz.annotations(), appView.dexItemFactory(), appView.getSyntheticItems());
     if (kind == null) {
       return NO_MARKER;
     }
     assert clazz.annotations().size() == 1;
-    if (kind.isSingleSyntheticMethod) {
+    if (kind.isSingleSyntheticMethod()) {
       if (!clazz.interfaces.isEmpty()) {
         return NO_MARKER;
       }
@@ -161,11 +162,11 @@
   }
 
   public boolean isSyntheticMethods() {
-    return kind != null && kind.isSingleSyntheticMethod;
+    return kind != null && kind.isSingleSyntheticMethod();
   }
 
   public boolean isSyntheticClass() {
-    return kind != null && !kind.isSingleSyntheticMethod;
+    return kind != null && !kind.isSingleSyntheticMethod();
   }
 
   public SyntheticKind getKind() {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
index ec8a010..d2daa6c 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodBuilder.java
@@ -145,22 +145,21 @@
             .setOptimizationInfo(optimizationInfo)
             .applyIf(!checkAndroidApiLevels, DexEncodedMethod.Builder::disableAndroidApiLevelCheck)
             .build();
-    assert isValidSyntheticMethod(method, syntheticKind);
+    assert !syntheticKind.isSingleSyntheticMethod()
+        || isValidSingleSyntheticMethod(method, syntheticKind);
     return method;
   }
 
   /**
-   * Predicate for what is a "supported" synthetic method.
+   * Predicate for what is a "supported" single synthetic method.
    *
-   * <p>This method is used when identifying synthetic methods in the program input and should be as
-   * narrow as possible.
-   *
-   * <p>Methods in fixed suffix synthetics are identified differently (through the class name) and
-   * can have different properties.
+   * <p>This method is used when identifying single synthetic methods in the program input and
+   * should be as narrow as possible.
    */
-  public static boolean isValidSyntheticMethod(
+  public static boolean isValidSingleSyntheticMethod(
       DexEncodedMethod method, SyntheticKind syntheticKind) {
-    return isValidSingleSyntheticMethod(method) || syntheticKind.isFixedSuffixSynthetic;
+    assert syntheticKind.isSingleSyntheticMethod();
+    return isValidSingleSyntheticMethod(method);
   }
 
   public static boolean isValidSingleSyntheticMethod(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
index d00eb21..2129b9d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMethodDefinition.java
@@ -11,7 +11,7 @@
 import java.util.function.Consumer;
 
 /**
- * Definition of a synthetic method item.
+ * Definition of a single synthetic method item.
  *
  * <p>This class is internal to the synthetic items collection, thus package-protected.
  */
@@ -24,6 +24,7 @@
 
   SyntheticMethodDefinition(SyntheticKind kind, SynthesizingContext context, ProgramMethod method) {
     super(kind, context);
+    assert kind.isSingleSyntheticMethod();
     this.method = method;
   }
 
@@ -70,7 +71,7 @@
 
   @Override
   public boolean isValid() {
-    return SyntheticMethodBuilder.isValidSyntheticMethod(method.getDefinition(), getKind());
+    return SyntheticMethodBuilder.isValidSingleSyntheticMethod(method.getDefinition(), getKind());
   }
 
   @Override
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 657f7d5..e95c6a1 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -6,14 +6,140 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.itf.InterfaceDesugaringForTesting;
 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;
+import com.android.tools.r8.utils.structural.Equatable;
+import com.android.tools.r8.utils.structural.Ordered;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
 
 public class SyntheticNaming {
 
+  public SyntheticNaming() {}
+
+  private KindGenerator generator = new KindGenerator();
+
+  // Global synthetics.
+  public final SyntheticKind RECORD_TAG = generator.forGlobalClass();
+  public final SyntheticKind API_MODEL_STUB = generator.forGlobalClass();
+
+  // Classpath only synthetics in the global type namespace.
+  public final SyntheticKind RETARGET_STUB = generator.forGlobalClasspathClass();
+  public final SyntheticKind EMULATED_INTERFACE_MARKER_CLASS = generator.forGlobalClasspathClass();
+
+  // Fixed suffix synthetics. Each has a hygienic prefix type.
+  public final SyntheticKind ENUM_UNBOXING_LOCAL_UTILITY_CLASS =
+      generator.forFixedClass("$EnumUnboxingLocalUtility");
+  public final SyntheticKind ENUM_UNBOXING_SHARED_UTILITY_CLASS =
+      generator.forFixedClass("$EnumUnboxingSharedUtility");
+  public final SyntheticKind COMPANION_CLASS = generator.forFixedClass("$-CC");
+  public final SyntheticKind EMULATED_INTERFACE_CLASS =
+      generator.forFixedClass(InterfaceDesugaringForTesting.EMULATED_INTERFACE_CLASS_SUFFIX);
+  public final SyntheticKind RETARGET_CLASS = generator.forFixedClass("RetargetClass");
+  public final SyntheticKind RETARGET_INTERFACE = generator.forFixedClass("RetargetInterface");
+  public final SyntheticKind WRAPPER = generator.forFixedClass("$Wrapper");
+  public final SyntheticKind VIVIFIED_WRAPPER = generator.forFixedClass("$VivifiedWrapper");
+  public final SyntheticKind INIT_TYPE_ARGUMENT = generator.forFixedClass("-IA");
+  public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_1 =
+      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$1");
+  public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_2 =
+      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$2");
+  public final SyntheticKind HORIZONTAL_INIT_TYPE_ARGUMENT_3 =
+      generator.forFixedClass(SYNTHETIC_CLASS_SEPARATOR + "IA$3");
+  public final SyntheticKind ENUM_CONVERSION = generator.forFixedClass("$EnumConversion");
+
+  // Locally generated synthetic classes.
+  public final SyntheticKind LAMBDA = generator.forInstanceClass("Lambda");
+
+  // TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors.
+  public final SyntheticKind NON_FIXED_INIT_TYPE_ARGUMENT =
+      generator.forNonSharableInstanceClass("$IA");
+  public final SyntheticKind CONST_DYNAMIC = generator.forInstanceClass("$Condy");
+
+  // Method synthetics.
+  public final SyntheticKind ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD =
+      generator.forSingleMethod("CheckNotZero");
+  public final SyntheticKind RECORD_HELPER = generator.forSingleMethod("Record");
+  public final SyntheticKind BACKPORT = generator.forSingleMethod("Backport");
+  public final SyntheticKind BACKPORT_WITH_FORWARDING =
+      generator.forSingleMethod("BackportWithForwarding");
+  public final SyntheticKind STATIC_INTERFACE_CALL =
+      generator.forSingleMethod("StaticInterfaceCall");
+  public final SyntheticKind TO_STRING_IF_NOT_NULL = generator.forSingleMethod("ToStringIfNotNull");
+  public final SyntheticKind THROW_CCE_IF_NOT_NULL = generator.forSingleMethod("ThrowCCEIfNotNull");
+  public final SyntheticKind THROW_IAE = generator.forSingleMethod("ThrowIAE");
+  public final SyntheticKind THROW_ICCE = generator.forSingleMethod("ThrowICCE");
+  public final SyntheticKind THROW_NSME = generator.forSingleMethod("ThrowNSME");
+  public final SyntheticKind TWR_CLOSE_RESOURCE = generator.forSingleMethod("TwrCloseResource");
+  public final SyntheticKind SERVICE_LOADER = generator.forSingleMethod("ServiceLoad");
+  public final SyntheticKind OUTLINE = generator.forSingleMethod("Outline");
+  public final SyntheticKind API_CONVERSION = generator.forSingleMethod("APIConversion");
+  public final SyntheticKind API_CONVERSION_PARAMETERS =
+      generator.forSingleMethod("APIConversionParameters");
+  public final SyntheticKind ARRAY_CONVERSION = generator.forSingleMethod("$ArrayConversion");
+  public final SyntheticKind API_MODEL_OUTLINE = generator.forSingleMethod("ApiModelOutline");
+
+  private final List<SyntheticKind> ALL_KINDS = generator.getAllKinds();
+
+  public Collection<SyntheticKind> kinds() {
+    return ALL_KINDS;
+  }
+
+  public SyntheticKind fromId(int id) {
+    if (0 < id && id <= ALL_KINDS.size()) {
+      return ALL_KINDS.get(id - 1);
+    }
+    return null;
+  }
+
+  private static class KindGenerator {
+    private int nextId = 1;
+    private List<SyntheticKind> kinds = new ArrayList<>();
+
+    private int getNextId() {
+      return nextId++;
+    }
+
+    private SyntheticKind register(SyntheticKind kind) {
+      kinds.add(kind);
+      return kind;
+    }
+
+    SyntheticKind forSingleMethod(String descriptor) {
+      return register(new SyntheticMethodKind(getNextId(), descriptor));
+    }
+
+    // TODO(b/214901256): Remove once fixed.
+    SyntheticKind forNonSharableInstanceClass(String descriptor) {
+      return register(new SyntheticClassKind(getNextId(), descriptor, false));
+    }
+
+    SyntheticKind forInstanceClass(String descriptor) {
+      return register(new SyntheticClassKind(getNextId(), descriptor, true));
+    }
+
+    SyntheticKind forFixedClass(String descriptor) {
+      return register(new SyntheticFixedClassKind(getNextId(), descriptor, false, false));
+    }
+
+    SyntheticKind forGlobalClass() {
+      return register(new SyntheticFixedClassKind(getNextId(), "", true, true));
+    }
+
+    SyntheticKind forGlobalClasspathClass() {
+      return register(new SyntheticFixedClassKind(getNextId(), "", false, false));
+    }
+
+    List<SyntheticKind> getAllKinds() {
+      List<SyntheticKind> kinds = this.kinds;
+      this.kinds = null;
+      return kinds;
+    }
+  }
+
   /**
    * Enumeration of all kinds of synthetic items.
    *
@@ -23,129 +149,169 @@
    * will be put into any non-minified synthetic name and thus the kind "descriptor" must be a
    * distinct for each kind.
    */
-  public enum SyntheticKind {
-    // Class synthetics.
-    ENUM_UNBOXING_LOCAL_UTILITY_CLASS("$EnumUnboxingLocalUtility", 24, false, true),
-    ENUM_UNBOXING_SHARED_UTILITY_CLASS("$EnumUnboxingSharedUtility", 25, false, true),
-    RECORD_TAG("", 1, false, true, true),
-    COMPANION_CLASS("$-CC", 2, false, true),
-    EMULATED_INTERFACE_CLASS("$-EL", 3, false, true),
-    RETARGET_CLASS("RetargetClass", 20, false, true),
-    RETARGET_STUB("", 36, false, true),
-    RETARGET_INTERFACE("RetargetInterface", 21, false, true),
-    WRAPPER("$Wrapper", 22, false, true),
-    VIVIFIED_WRAPPER("$VivifiedWrapper", 23, false, true),
-    LAMBDA("Lambda", 4, false),
-    INIT_TYPE_ARGUMENT("-IA", 5, false, true),
-    HORIZONTAL_INIT_TYPE_ARGUMENT_1(SYNTHETIC_CLASS_SEPARATOR + "IA$1", 6, false, true),
-    HORIZONTAL_INIT_TYPE_ARGUMENT_2(SYNTHETIC_CLASS_SEPARATOR + "IA$2", 7, false, true),
-    HORIZONTAL_INIT_TYPE_ARGUMENT_3(SYNTHETIC_CLASS_SEPARATOR + "IA$3", 8, false, true),
-    NON_FIXED_INIT_TYPE_ARGUMENT("$IA", 35, false),
-    // Method synthetics.
-    ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
-    RECORD_HELPER("Record", 9, true),
-    BACKPORT("Backport", 10, true),
-    BACKPORT_WITH_FORWARDING("BackportWithForwarding", 34, true),
-    STATIC_INTERFACE_CALL("StaticInterfaceCall", 11, true),
-    TO_STRING_IF_NOT_NULL("ToStringIfNotNull", 12, true),
-    THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", 13, true),
-    THROW_IAE("ThrowIAE", 14, true),
-    THROW_ICCE("ThrowICCE", 15, true),
-    THROW_NSME("ThrowNSME", 16, true),
-    TWR_CLOSE_RESOURCE("TwrCloseResource", 17, true),
-    SERVICE_LOADER("ServiceLoad", 18, true),
-    OUTLINE("Outline", 19, true),
-    API_CONVERSION("APIConversion", 26, true),
-    API_CONVERSION_PARAMETERS("APIConversionParameters", 28, true),
-    EMULATED_INTERFACE_MARKER_CLASS("", 29, false, true, true),
-    CONST_DYNAMIC("$Condy", 30, false),
-    ENUM_CONVERSION("$EnumConversion", 31, false, true),
-    ARRAY_CONVERSION("$ArrayConversion", 37, true, false),
-    API_MODEL_OUTLINE("ApiModelOutline", 32, true, false, false),
-    API_MODEL_STUB("", 33, false, true, true);
+  public abstract static class SyntheticKind implements Ordered<SyntheticKind> {
 
-    static {
-      assert verifyNoOverlappingIds();
-    }
+    private final int id;
+    private final String descriptor;
 
-    public final String descriptor;
-    public final int id;
-    public final boolean isSingleSyntheticMethod;
-    public final boolean isFixedSuffixSynthetic;
-    public final boolean mayOverridesNonProgramType;
-
-    SyntheticKind(String descriptor, int id, boolean isSingleSyntheticMethod) {
-      this(descriptor, id, isSingleSyntheticMethod, false);
-    }
-
-    SyntheticKind(
-        String descriptor,
-        int id,
-        boolean isSingleSyntheticMethod,
-        boolean isFixedSuffixSynthetic) {
-      this(descriptor, id, isSingleSyntheticMethod, isFixedSuffixSynthetic, false);
-    }
-
-    SyntheticKind(
-        String descriptor,
-        int id,
-        boolean isSingleSyntheticMethod,
-        boolean isFixedSuffixSynthetic,
-        boolean mayOverridesNonProgramType) {
-      this.descriptor = descriptor;
+    SyntheticKind(int id, String descriptor) {
       this.id = id;
-      this.isSingleSyntheticMethod = isSingleSyntheticMethod;
-      this.isFixedSuffixSynthetic = isFixedSuffixSynthetic;
-      this.mayOverridesNonProgramType = mayOverridesNonProgramType;
+      this.descriptor = descriptor;
     }
 
-    public boolean allowSyntheticContext() {
-      return this == RECORD_TAG;
+    @Override
+    public int compareTo(SyntheticKind other) {
+      return Integer.compare(id, other.getId());
     }
 
-    public boolean isGlobal() {
-      return isFixedSuffixSynthetic && descriptor.isEmpty();
+    @Override
+    public int hashCode() {
+      return id;
     }
 
+    @Override
+    public boolean equals(Object o) {
+      return Equatable.equalsImpl(this, o);
+    }
+
+    public int getId() {
+      return id;
+    }
+
+    public String getDescriptor() {
+      return descriptor;
+    }
+
+    public abstract boolean isShareable();
+
+    public abstract boolean isSingleSyntheticMethod();
+
+    public abstract boolean isFixedSuffixSynthetic();
+
+    public abstract boolean isGlobal();
+
+    public abstract boolean isMayOverridesNonProgramType();
+
+    public abstract boolean allowSyntheticContext();
+  }
+
+  private static class SyntheticMethodKind extends SyntheticKind {
+
+    public SyntheticMethodKind(int id, String descriptor) {
+      super(id, descriptor);
+    }
+
+    @Override
     public boolean isShareable() {
-      if (isFixedSuffixSynthetic) {
-        // Fixed synthetics are non-shareable. Ordered by their unique type.
-        return false;
-      }
-      if (this == NON_FIXED_INIT_TYPE_ARGUMENT) {
-        // TODO(b/214901256): Sharing of synthetic classes may lead to duplicate method errors.
-        return false;
-      }
+      // Single methods may always be shared.
       return true;
     }
 
-    public static SyntheticKind fromDescriptor(String descriptor) {
-      for (SyntheticKind kind : values()) {
-        if (kind.descriptor.equals(descriptor)) {
-          return kind;
-        }
-      }
-      return null;
-    }
-
-    public static SyntheticKind fromId(int id) {
-      for (SyntheticKind kind : values()) {
-        if (kind.id == id) {
-          return kind;
-        }
-      }
-      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;
-      }
+    @Override
+    public boolean isSingleSyntheticMethod() {
       return true;
     }
+
+    @Override
+    public boolean isFixedSuffixSynthetic() {
+      return false;
+    }
+
+    @Override
+    public boolean isGlobal() {
+      return false;
+    }
+
+    @Override
+    public boolean isMayOverridesNonProgramType() {
+      return false;
+    }
+
+    @Override
+    public boolean allowSyntheticContext() {
+      return false;
+    }
+  }
+
+  private static class SyntheticClassKind extends SyntheticKind {
+
+    // TODO(b/214901256): Remove once fixed.
+    private final boolean sharable;
+
+    public SyntheticClassKind(int id, String descriptor, boolean sharable) {
+      super(id, descriptor);
+      this.sharable = sharable;
+    }
+
+    @Override
+    public boolean isShareable() {
+      return sharable;
+    }
+
+    @Override
+    public final boolean isSingleSyntheticMethod() {
+      return false;
+    }
+
+    @Override
+    public boolean isFixedSuffixSynthetic() {
+      return false;
+    }
+
+    @Override
+    public boolean isGlobal() {
+      return false;
+    }
+
+    @Override
+    public boolean isMayOverridesNonProgramType() {
+      return false;
+    }
+
+    @Override
+    public boolean allowSyntheticContext() {
+      return false;
+    }
+  }
+
+  private static class SyntheticFixedClassKind extends SyntheticClassKind {
+    private final boolean mayOverridesNonProgramType;
+    private final boolean allowSyntheticContext;
+
+    private SyntheticFixedClassKind(
+        int id,
+        String descriptor,
+        boolean mayOverridesNonProgramType,
+        boolean allowSyntheticContext) {
+      super(id, descriptor, false);
+      this.mayOverridesNonProgramType = mayOverridesNonProgramType;
+      this.allowSyntheticContext = allowSyntheticContext;
+    }
+
+    @Override
+    public boolean isShareable() {
+      return false;
+    }
+
+    @Override
+    public boolean isFixedSuffixSynthetic() {
+      return true;
+    }
+
+    @Override
+    public boolean isGlobal() {
+      return getDescriptor().isEmpty();
+    }
+
+    @Override
+    public boolean isMayOverridesNonProgramType() {
+      return mayOverridesNonProgramType;
+    }
+
+    @Override
+    public boolean allowSyntheticContext() {
+      return allowSyntheticContext;
+    }
   }
 
   private static final String SYNTHETIC_CLASS_SEPARATOR = "$$";
@@ -169,22 +335,22 @@
     String binaryName = type.toBinaryName();
     int index =
         binaryName.lastIndexOf(
-            kind.isFixedSuffixSynthetic ? kind.descriptor : SYNTHETIC_CLASS_SEPARATOR);
+            kind.isFixedSuffixSynthetic() ? kind.descriptor : SYNTHETIC_CLASS_SEPARATOR);
     if (index < 0) {
       throw new Unreachable("Unexpected failure to compute an synthetic prefix");
     }
     return binaryName.substring(0, index);
   }
 
-  public static DexType createFixedType(
+  static DexType createFixedType(
       SyntheticKind kind, SynthesizingContext context, DexItemFactory factory) {
-    assert kind.isFixedSuffixSynthetic;
+    assert kind.isFixedSuffixSynthetic();
     return createType("", kind, context.getSynthesizingContextType(), "", factory);
   }
 
   static DexType createInternalType(
       SyntheticKind kind, SynthesizingContext context, String id, DexItemFactory factory) {
-    assert !kind.isFixedSuffixSynthetic;
+    assert !kind.isFixedSuffixSynthetic();
     return createType(
         INTERNAL_SYNTHETIC_CLASS_SEPARATOR,
         kind,
@@ -195,9 +361,9 @@
 
   static DexType createExternalType(
       SyntheticKind kind, String externalSyntheticTypePrefix, String id, DexItemFactory factory) {
-    assert kind.isFixedSuffixSynthetic == id.isEmpty();
+    assert kind.isFixedSuffixSynthetic() == id.isEmpty();
     return createType(
-        kind.isFixedSuffixSynthetic ? "" : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR,
+        kind.isFixedSuffixSynthetic() ? "" : EXTERNAL_SYNTHETIC_CLASS_SEPARATOR,
         kind,
         externalSyntheticTypePrefix,
         id,
@@ -257,14 +423,9 @@
         createDescriptor(EXTERNAL_SYNTHETIC_CLASS_SEPARATOR, kind, context.getBinaryName(), id));
   }
 
-  public static boolean isInternalStaticInterfaceCall(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(
-        reference, Phase.INTERNAL, SyntheticKind.STATIC_INTERFACE_CALL);
-  }
-
   static boolean isSynthetic(ClassReference clazz, Phase phase, SyntheticKind kind) {
     String typeName = clazz.getTypeName();
-    if (kind.isFixedSuffixSynthetic) {
+    if (kind.isFixedSuffixSynthetic()) {
       assert phase == null;
       return clazz.getBinaryName().endsWith(kind.descriptor);
     }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
index a231b0f..e5557d9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalArchiveClassFileProvider.java
@@ -8,10 +8,12 @@
 
 import com.android.tools.r8.ClassFileResourceProvider;
 import com.android.tools.r8.ProgramResource;
-import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.ProgramResource.Kind;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.origin.ArchiveEntryOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
+import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
@@ -81,36 +83,18 @@
     if (!descriptors.contains(descriptor)) {
       return null;
     }
-    return new ProgramResource() {
-
-      private final Origin entryOrigin =
-          new ArchiveEntryOrigin(getZipEntryNameFromDescriptor(descriptor), origin);
-
-      @Override
-      public Origin getOrigin() {
-        return entryOrigin;
+    try {
+      ZipEntry zipEntry = getZipEntryFromDescriptor(descriptor);
+      try (InputStream inputStream = getOpenZipFile().getInputStream(zipEntry)) {
+        return ProgramResource.fromBytes(
+            new ArchiveEntryOrigin(zipEntry.getName(), origin),
+            Kind.CF,
+            ByteStreams.toByteArray(inputStream),
+            Collections.singleton(descriptor));
       }
-
-      @Override
-      public Kind getKind() {
-        return Kind.CF;
-      }
-
-      @Override
-      public Set<String> getClassDescriptors() {
-        return Collections.singleton(descriptor);
-      }
-
-      @Override
-      public InputStream getByteStream() throws ResourceException {
-        try {
-          ZipEntry zipEntry = getZipEntryFromDescriptor(descriptor);
-          return getOpenZipFile().getInputStream(zipEntry);
-        } catch (IOException e) {
-          throw new ResourceException(getOrigin(), "Failed to read '" + descriptor + "'");
-        }
-      }
-    };
+    } catch (IOException e) {
+      throw new CompilationError("Failed to read '" + descriptor, origin);
+    }
   }
 
   private ZipFile getOpenZipFile() throws IOException {
@@ -134,11 +118,8 @@
     openedZipFile = null;
   }
 
-  private static String getZipEntryNameFromDescriptor(String descriptor) {
-    return descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION;
-  }
-
   private ZipEntry getZipEntryFromDescriptor(String descriptor) throws IOException {
-    return getOpenZipFile().getEntry(getZipEntryNameFromDescriptor(descriptor));
+    return getOpenZipFile()
+        .getEntry(descriptor.substring(1, descriptor.length() - 1) + CLASS_EXTENSION);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index e359381..dcf8dbd 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1764,6 +1764,7 @@
     public boolean enableEnumUnboxingDebugLogs = false;
     public boolean forceRedundantConstNumberRemoval = false;
     public boolean enableExperimentalDesugaredLibraryKeepRuleGenerator = false;
+    public boolean enableExperimentalLoopUnrolling = false;
     public boolean invertConditionals = false;
     public boolean placeExceptionalBlocksLast = false;
     public boolean dontCreateMarkerInD8 = false;
diff --git a/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
index 3729ead..ea895e9 100644
--- a/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SymbolGenerationUtils.java
@@ -17,9 +17,9 @@
     DONT_USE_MIXED_CASE
   }
 
-  public static Set<String> PRIMITIVE_TYPE_NAMES =
+  public static Set<String> RESERVED_NAMES =
       Sets.newHashSet(
-          "boolean", "byte", "char", "double", "float", "int", "long", "short", "void", "it");
+          "boolean", "byte", "char", "double", "float", "int", "long", "short", "void", "it", "by");
 
   // These letters are used not creating fresh names to output and not for parsing dex/class files.
   private static final char[] IDENTIFIER_CHARACTERS =
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
index 527d2c0..f926a22 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
@@ -36,12 +36,12 @@
 
   public ObjectsBackportJava9Test(TestParameters parameters) {
     super(parameters, Objects.class, TEST_JAR, TEST_CLASS);
-    // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
-    // an actual API level, migrate these tests to ObjectsBackportTest.
-
     // Objects.checkFromIndexSize, Objects.checkFromToIndex, Objects.checkIndex,
     // Objects.requireNonNullElse and Objects.requireNonNullElseGet added in API 30.
     registerTarget(AndroidApiLevel.R, 28);
+    registerTarget(AndroidApiLevel.N, 0);
+    // Objects.requireNonNullElseGet is not desugared if Supplier is absent.
+    registerTarget(AndroidApiLevel.B, 4);
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
index 3209290..ef978a7 100644
--- a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/ObjectsTest.java
@@ -245,7 +245,7 @@
         onlyIf(invokeJavaUtilObjects, invokesObjectsRequireNonNull("java.util.Objects")));
     assertThat(
         testClass.uniqueMethodWithName("objectsRequireNonNull"),
-        onlyIf(parameters.getApiLevel().isLessThan(AndroidApiLevel.K), invokesClassGetClass()));
+        onlyIf(invokeJDollarUtilObjects, invokesObjectsRequireNonNull("j$.util.Objects")));
 
     assertThat(
         testClass.uniqueMethodWithName("objectsRequireNonNullWithMessage"),
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1Iterations.java b/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1Iterations.java
index dc3ca19..6bbfb60 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1Iterations.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/loops/LoopWith1Iterations.java
@@ -33,6 +33,7 @@
         .setMinApi(parameters.getApiLevel())
         .addProgramClasses(Main.class)
         .addKeepMainRule(Main.class)
+        .addOptionsModification(options -> options.testing.enableExperimentalLoopUnrolling = true)
         .enableInliningAnnotations()
         .noMinification()
         .compile()
diff --git a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingLambdaRepackageTest.java b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingLambdaRepackageTest.java
index c85c55b..6c799bd 100644
--- a/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingLambdaRepackageTest.java
+++ b/src/test/java/com/android/tools/r8/naming/applymapping/ApplyMappingLambdaRepackageTest.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.naming.applymapping;
 
-import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
@@ -50,14 +50,8 @@
             .addApplyMapping(firstRunResult.proguardMap())
             .compile()
             .run(parameters.getRuntime(), Main.class);
-    if (parameters.isDexRuntime()) {
-      // TODO(b/218793832): Should be the same map.
-      assertNotEquals(firstRunResult.proguardMap(), secondRunResult.proguardMap());
-    }
-    secondRunResult
-        .assertSuccessWithOutputLinesIf(parameters.isCfRuntime(), "Hello World")
-        // TODO(b/218793832): Should not fail with an error.
-        .assertFailureWithErrorThatThrowsIf(parameters.isDexRuntime(), IllegalAccessError.class);
+    assertEquals(firstRunResult.proguardMap(), secondRunResult.proguardMap());
+    secondRunResult.assertSuccessWithOutputLines("Hello World");
   }
 
   @NeverClassInline
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java
index 1a3bfca..7611b1c 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageWithSyntheticItemTest.java
@@ -64,11 +64,7 @@
                       .filter(item -> item.getFinalName().startsWith("foo"))
                       .collect(Collectors.toList());
               assertEquals(1, classesStartingWithfoo.size());
-              // TODO(b/172014416): We should not be able to look this up through the repackage name
-              String expectedOriginalNamePrefix =
-                  isFlattenPackageHierarchy()
-                      ? "foo.a.RepackageWithSyntheticItemTest$A"
-                      : "foo.RepackageWithSyntheticItemTest$A";
+              String expectedOriginalNamePrefix = typeName(A.class) + "$$ExternalSyntheticLambda0";
               assertThat(
                   classesStartingWithfoo.get(0).getOriginalName(),
                   containsString(expectedOriginalNamePrefix));
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
index d67f6ee..4e8d81e 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
@@ -6,11 +6,12 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingProvider;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -62,9 +63,11 @@
 
     @Test
     public void test() {
-      Retracer retracer =
-          Retracer.createExperimental(
-              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      MappingProvider mappingProvider =
+          ProguardMappingProvider.builder()
+              .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+              .build();
+      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
index 94824c5..ca3bb00 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
@@ -6,12 +6,13 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingProvider;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -70,9 +71,11 @@
 
     @Test
     public void test() {
-      Retracer retracer =
-          Retracer.createExperimental(
-              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      MappingProvider mappingProvider =
+          ProguardMappingProvider.builder()
+              .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+              .build();
+      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
       // Retrace the first outline.
       RetraceStackTraceContext outlineContext =
           retraceOutline(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
index 6aaa0fa..4401dfa 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
@@ -6,11 +6,12 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingProvider;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -61,9 +62,11 @@
 
     @Test
     public void test() {
-      Retracer retracer =
-          Retracer.createExperimental(
-              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      MappingProvider mappingProvider =
+          ProguardMappingProvider.builder()
+              .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+              .build();
+      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
index e4ef644..5ed27fae 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
@@ -7,11 +7,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingProvider;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetracedMethodReference;
@@ -61,9 +62,11 @@
 
     @Test
     public void test() {
-      Retracer retracer =
-          Retracer.createExperimental(
-              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      MappingProvider mappingProvider =
+          ProguardMappingProvider.builder()
+              .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+              .build();
+      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
       List<RetraceFrameElement> outlineRetraced =
           retracer
               .retraceFrame(
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
index 5175ecb..9cd18f5 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
@@ -7,11 +7,12 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 
-import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingProvider;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
@@ -79,9 +80,11 @@
 
     @Test
     public void testUsingObfuscatedName() {
-      Retracer retracer =
-          Retracer.createExperimental(
-              ProguardMapProducer.fromString(mapping), new DiagnosticsHandler() {});
+      MappingProvider mappingProvider =
+          ProguardMappingProvider.builder()
+              .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+              .build();
+      Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
       List<RetraceThrownExceptionElement> npeRetraced =
           retracer.retraceThrownException(renamedException).stream().collect(Collectors.toList());
       assertEquals(1, npeRetraced.size());
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
index 66bdc03..61ddca6 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
@@ -11,7 +11,9 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.retrace.MappingProvider;
 import com.android.tools.r8.retrace.ProguardMapProducer;
+import com.android.tools.r8.retrace.ProguardMappingProvider;
 import com.android.tools.r8.retrace.RetraceFrameElement;
 import com.android.tools.r8.retrace.RetraceStackTraceContext;
 import com.android.tools.r8.retrace.RetraceThrownExceptionElement;
@@ -58,9 +60,16 @@
     @Test
     public void testFirstStackLineIsRemoved() {
       TestDiagnosticsHandler testDiagnosticsHandler = new TestDiagnosticsHandler();
+      MappingProvider mappingProvider =
+          ProguardMappingProvider.builder()
+              .setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+              .setDiagnosticsHandler(testDiagnosticsHandler)
+              .build();
       Retracer retracer =
-          Retracer.createExperimental(
-              ProguardMapProducer.fromString(mapping), testDiagnosticsHandler);
+          Retracer.builder()
+              .setMappingProvider(mappingProvider)
+              .setDiagnosticsHandler(testDiagnosticsHandler)
+              .build();
 
       List<RetraceThrownExceptionElement> npeRetraced =
           retracer.retraceThrownException(Reference.classFromDescriptor(npeDescriptor)).stream()
diff --git a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
index f85290d..369d584 100644
--- a/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/KeepAnnotatedMemberTest.java
@@ -10,7 +10,6 @@
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.R8;
@@ -292,8 +291,7 @@
             .apply(this::configureHorizontalClassMerging)
             .compile()
             .graphInspector();
-    assertRetainedClassesEqual(
-        referenceInspector, ifHasMemberThenKeepClassInspector, true, true, true, true);
+    assertRetainedClassesEqual(referenceInspector, ifHasMemberThenKeepClassInspector);
   }
 
   private void configureHorizontalClassMerging(R8FullTestBuilder testBuilder) {
@@ -313,58 +311,27 @@
 
   private void assertRetainedClassesEqual(
       GraphInspector referenceResult, GraphInspector conditionalResult) {
-    assertRetainedClassesEqual(referenceResult, conditionalResult, false, false, false, false);
-  }
-
-  private void assertRetainedClassesEqual(
-      GraphInspector referenceResult,
-      GraphInspector conditionalResult,
-      boolean expectReferenceIsLarger,
-      boolean expectReferenceIsLargerOnlyBySynthetics,
-      boolean expectConditionalIsLarger,
-      boolean expectConditionalIsLargerOnlyBySynthetics) {
     Set<String> referenceClasses =
         new TreeSet<>(
             referenceResult.codeInspector().allClasses().stream()
                 .map(FoundClassSubject::getOriginalName)
                 .collect(Collectors.toSet()));
-
     Set<String> conditionalClasses =
         conditionalResult.codeInspector().allClasses().stream()
             .map(FoundClassSubject::getOriginalName)
             .collect(Collectors.toSet());
-    {
-      Set<String> notInReference =
-          new TreeSet<>(Sets.difference(conditionalClasses, referenceClasses));
-      if (expectConditionalIsLarger) {
-        assertFalse("Expected classes in -if rule to retain more.", notInReference.isEmpty());
-        if (expectConditionalIsLargerOnlyBySynthetics) {
-          assertAllClassesAreSynthetics(notInReference);
-        }
-      } else {
-        assertEquals(
-            "Classes in -if rule that are not in -keepclassmembers rule",
-            Collections.emptySet(),
-            notInReference);
-      }
-    }
-    {
-      Set<String> notInConditional =
-          new TreeSet<>(Sets.difference(referenceClasses, conditionalClasses));
-      if (expectReferenceIsLarger) {
-        assertFalse(
-            "Expected classes in -keepclassmembers rule to retain more.",
-            notInConditional.isEmpty());
-        if (expectReferenceIsLargerOnlyBySynthetics) {
-          assertAllClassesAreSynthetics(notInConditional);
-        }
-      } else {
-        assertEquals(
-            "Classes in -keepclassmembers rule that are not in -if rule",
-            Collections.emptySet(),
-            notInConditional);
-      }
-    }
+    Set<String> notInReference =
+        new TreeSet<>(Sets.difference(conditionalClasses, referenceClasses));
+    assertEquals(
+        "Classes in -if rule that are not in -keepclassmembers rule",
+        Collections.emptySet(),
+        notInReference);
+    Set<String> notInConditional =
+        new TreeSet<>(Sets.difference(referenceClasses, conditionalClasses));
+    assertEquals(
+        "Classes in -keepclassmembers rule that are not in -if rule",
+        Collections.emptySet(),
+        notInConditional);
   }
 
   private void assertAllClassesAreSynthetics(Set<String> classNames) {
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 5977b9a..b53f301 100644
--- a/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -16,6 +16,10 @@
 
 public class SyntheticItemsTestUtils {
 
+  // Private copy of the synthetic namings. This is not the compiler instance, but checking on the
+  // id/descriptor content is safe.
+  private static final SyntheticNaming naming = new SyntheticNaming();
+
   public static String syntheticMethodName() {
     return SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_NAME;
   }
@@ -38,8 +42,7 @@
   }
 
   public static MethodReference syntheticBackportMethod(Class<?> clazz, int id, Method method) {
-    ClassReference syntheticHolder =
-        syntheticClass(clazz, SyntheticNaming.SyntheticKind.BACKPORT, id);
+    ClassReference syntheticHolder = syntheticClass(clazz, naming.BACKPORT, id);
     MethodReference originalMethod = Reference.methodFromMethod(method);
     return Reference.methodFromDescriptor(
         syntheticHolder.getDescriptor(),
@@ -48,15 +51,15 @@
   }
 
   public static ClassReference syntheticOutlineClass(Class<?> clazz, int id) {
-    return syntheticClass(clazz, SyntheticKind.OUTLINE, id);
+    return syntheticClass(clazz, naming.OUTLINE, id);
   }
 
   public static ClassReference syntheticOutlineClass(ClassReference clazz, int id) {
-    return syntheticClass(clazz, SyntheticKind.OUTLINE, id);
+    return syntheticClass(clazz, naming.OUTLINE, id);
   }
 
   public static ClassReference syntheticLambdaClass(Class<?> clazz, int id) {
-    return syntheticClass(clazz, SyntheticNaming.SyntheticKind.LAMBDA, id);
+    return syntheticClass(clazz, naming.LAMBDA, id);
   }
 
   public static MethodReference syntheticLambdaMethod(Class<?> clazz, int id, Method method) {
@@ -69,16 +72,15 @@
   }
 
   public static boolean isEnumUnboxingSharedUtilityClass(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(
-        reference, null, SyntheticKind.ENUM_UNBOXING_SHARED_UTILITY_CLASS);
+    return SyntheticNaming.isSynthetic(reference, null, naming.ENUM_UNBOXING_SHARED_UTILITY_CLASS);
   }
 
   public static boolean isExternalSynthetic(ClassReference reference) {
-    for (SyntheticKind kind : SyntheticKind.values()) {
+    for (SyntheticKind kind : naming.kinds()) {
       if (kind.isGlobal()) {
         continue;
       }
-      if (kind.isFixedSuffixSynthetic) {
+      if (kind.isFixedSuffixSynthetic()) {
         if (SyntheticNaming.isSynthetic(reference, null, kind)) {
           return true;
         }
@@ -92,52 +94,48 @@
   }
 
   public static boolean isInternalLambda(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(reference, Phase.INTERNAL, SyntheticKind.LAMBDA);
+    return SyntheticNaming.isSynthetic(reference, Phase.INTERNAL, naming.LAMBDA);
   }
 
   public static boolean isExternalLambda(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.LAMBDA);
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, naming.LAMBDA);
   }
 
   public static boolean isExternalStaticInterfaceCall(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(
-        reference, Phase.EXTERNAL, SyntheticKind.STATIC_INTERFACE_CALL);
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, naming.STATIC_INTERFACE_CALL);
   }
 
   public static boolean isExternalTwrCloseMethod(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.TWR_CLOSE_RESOURCE);
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, naming.TWR_CLOSE_RESOURCE);
   }
 
   public static boolean isMaybeExternalSuppressedExceptionMethod(ClassReference reference) {
     // The suppressed exception methods are grouped with the backports.
-    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.BACKPORT);
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, naming.BACKPORT);
   }
 
   public static boolean isExternalOutlineClass(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, SyntheticKind.OUTLINE);
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, naming.OUTLINE);
   }
 
   public static boolean isInitializerTypeArgument(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.INIT_TYPE_ARGUMENT);
+    return SyntheticNaming.isSynthetic(reference, null, naming.INIT_TYPE_ARGUMENT);
   }
 
   public static boolean isExternalNonFixedInitializerTypeArgument(ClassReference reference) {
     return SyntheticNaming.isSynthetic(
-        reference, Phase.EXTERNAL, SyntheticKind.NON_FIXED_INIT_TYPE_ARGUMENT);
+        reference, Phase.EXTERNAL, naming.NON_FIXED_INIT_TYPE_ARGUMENT);
   }
 
   public static boolean isHorizontalInitializerTypeArgument(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(
-            reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
-        || SyntheticNaming.isSynthetic(
-            reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
-        || SyntheticNaming.isSynthetic(
-            reference, null, SyntheticKind.HORIZONTAL_INIT_TYPE_ARGUMENT_3);
+    return SyntheticNaming.isSynthetic(reference, null, naming.HORIZONTAL_INIT_TYPE_ARGUMENT_1)
+        || SyntheticNaming.isSynthetic(reference, null, naming.HORIZONTAL_INIT_TYPE_ARGUMENT_2)
+        || SyntheticNaming.isSynthetic(reference, null, naming.HORIZONTAL_INIT_TYPE_ARGUMENT_3);
   }
 
   public static boolean isWrapper(ClassReference reference) {
-    return SyntheticNaming.isSynthetic(reference, null, SyntheticKind.WRAPPER)
-        || SyntheticNaming.isSynthetic(reference, null, SyntheticKind.VIVIFIED_WRAPPER);
+    return SyntheticNaming.isSynthetic(reference, null, naming.WRAPPER)
+        || SyntheticNaming.isSynthetic(reference, null, naming.VIVIFIED_WRAPPER);
   }
 
   public static Matcher<String> containsInternalSyntheticReference() {
@@ -149,7 +147,6 @@
   }
 
   public static boolean isInternalThrowNSME(MethodReference method) {
-    return SyntheticNaming.isSynthetic(
-        method.getHolderClass(), Phase.INTERNAL, SyntheticKind.THROW_NSME);
+    return SyntheticNaming.isSynthetic(method.getHolderClass(), Phase.INTERNAL, naming.THROW_NSME);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index d753ca5..d9869b2 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -36,8 +36,7 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.Retracer;
-import com.android.tools.r8.retrace.internal.DirectClassNameMapperProguardMapProducer;
-import com.android.tools.r8.retrace.internal.RetracerImpl;
+import com.android.tools.r8.retrace.internal.ProguardMappingProviderImpl;
 import com.android.tools.r8.synthesis.SyntheticItemsTestUtils;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.BiMapContainer;
@@ -177,7 +176,11 @@
 
   public Retracer getRetracer() {
     if (lazyRetracer == null) {
-      lazyRetracer = new RetracerImpl(mapping, new TestDiagnosticMessagesImpl());
+      lazyRetracer =
+          Retracer.builder()
+              .setMappingProvider(new ProguardMappingProviderImpl(mapping))
+              .setDiagnosticsHandler(new TestDiagnosticMessagesImpl())
+              .build();
     }
     return lazyRetracer;
   }
@@ -525,24 +528,11 @@
   }
 
   public Retracer retrace() {
-    return Retracer.createDefault(
-        new InternalProguardMapProducer(
-            mapping == null ? ClassNameMapper.builder().build() : mapping),
-        new TestDiagnosticMessagesImpl());
-  }
-
-  public static class InternalProguardMapProducer
-      implements DirectClassNameMapperProguardMapProducer {
-
-    public final ClassNameMapper prebuiltMapper;
-
-    public InternalProguardMapProducer(ClassNameMapper prebuiltMapper) {
-      this.prebuiltMapper = prebuiltMapper;
-    }
-
-    @Override
-    public ClassNameMapper getClassNameMapper() {
-      return prebuiltMapper;
-    }
+    return Retracer.builder()
+        .setMappingProvider(
+            new ProguardMappingProviderImpl(
+                mapping == null ? ClassNameMapper.builder().build() : mapping))
+        .setDiagnosticsHandler(new TestDiagnosticMessagesImpl())
+        .build();
   }
 }
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index 26b5e24..431fdbe 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-3238b42ace7a8e81abf0336c069645c5b97dd470
\ No newline at end of file
+2ebfdd6c7d270471cb38baa355d5d137af2f7dd1
\ No newline at end of file