Merge commit '1932e57c7bf58f96aed8c3e06fad612c6e6f817d' into dev-release
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index dc7ac7b..3a9c0b4 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 3,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "1.1.4",
+  "version": "1.1.5",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "support_all_callbacks_from_library": true,
@@ -77,12 +77,6 @@
         "java.time.": "j$.time.",
         "java.util.Desugar": "j$.util.Desugar"
       },
-      "backport": {
-        "java.lang.Double8": "java.lang.Double",
-        "java.lang.Integer8": "java.lang.Integer",
-        "java.lang.Long8": "java.lang.Long",
-        "java.lang.Math8": "java.lang.Math"
-      },
       "retarget_lib_member": {
         "java.util.Date#toInstant": "java.util.DesugarDate",
         "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar",
diff --git a/src/main/java/com/android/tools/r8/L8Command.java b/src/main/java/com/android/tools/r8/L8Command.java
index b552bb2..4c235d9 100644
--- a/src/main/java/com/android/tools/r8/L8Command.java
+++ b/src/main/java/com/android/tools/r8/L8Command.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
+import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -315,6 +316,9 @@
         }
         r8Builder.addProguardConfiguration(
             libraryConfiguration.getExtraKeepRules(), Origin.unknown());
+        // TODO(b/180903899): Remove rule when -dontwarn sun.misc.Unsafe is part of config.
+        r8Builder.addProguardConfiguration(
+            ImmutableList.of("-dontwarn sun.misc.Unsafe"), Origin.unknown());
         r8Builder.addProguardConfigurationFiles(proguardConfigFiles);
         r8Builder.setDisableDesugaring(true);
         r8Builder.skipDump();
diff --git a/src/main/java/com/android/tools/r8/annotations/SynthesizedClassMap.java b/src/main/java/com/android/tools/r8/annotations/SynthesizedClassMap.java
deleted file mode 100644
index b46a2ba..0000000
--- a/src/main/java/com/android/tools/r8/annotations/SynthesizedClassMap.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2017, 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.annotations;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Retention(RetentionPolicy.CLASS)
-@Target(ElementType.TYPE)
-public @interface SynthesizedClassMap {
-  Class<?>[] value() default {};
-}
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 8f56827..614ac4d 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -467,7 +467,7 @@
 
       // Pull out the classes that should go into feature splits.
       Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
-          classToFeatureSplitMap.getFeatureSplitClasses(classes);
+          classToFeatureSplitMap.getFeatureSplitClasses(classes, appView.getSyntheticItems());
       if (featureSplitClasses.size() > 0) {
         for (Set<DexProgramClass> featureClasses : featureSplitClasses.values()) {
           classes.removeAll(featureClasses);
@@ -475,14 +475,17 @@
       }
       List<DexProgramClass> toRemove = new ArrayList<>();
       for (DexProgramClass dexProgramClass : classes) {
-        Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom();
-        if (!synthesizedFrom.isEmpty()) {
-          DexProgramClass from = Iterables.getFirst(synthesizedFrom, null);
-          FeatureSplit featureSplit = classToFeatureSplitMap.getFeatureSplit(from);
-          if (!featureSplit.isBase()) {
-            Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit);
-            dexProgramClasses.add(dexProgramClass);
-            toRemove.add(dexProgramClass);
+        if (appView.getSyntheticItems().isLegacySyntheticClass(dexProgramClass)) {
+          Collection<DexProgramClass> synthesizedFrom = dexProgramClass.getSynthesizedFrom();
+          if (!synthesizedFrom.isEmpty()) {
+            DexProgramClass from = Iterables.getFirst(synthesizedFrom, null);
+            FeatureSplit featureSplit =
+                classToFeatureSplitMap.getFeatureSplit(from, appView.getSyntheticItems());
+            if (!featureSplit.isBase()) {
+              Set<DexProgramClass> dexProgramClasses = featureSplitClasses.get(featureSplit);
+              dexProgramClasses.add(dexProgramClass);
+              toRemove.add(dexProgramClass);
+            }
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
index 1ea1595..d96643d 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -17,11 +17,15 @@
 import com.android.tools.r8.graph.ProgramDefinition;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.google.common.collect.Sets;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
+import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
 
@@ -88,9 +92,9 @@
     return result;
   }
 
-  public int compareFeatureSplitsForDexTypes(DexType a, DexType b) {
-    FeatureSplit featureSplitA = getFeatureSplit(a);
-    FeatureSplit featureSplitB = getFeatureSplit(b);
+  public int compareFeatureSplitsForDexTypes(DexType a, DexType b, SyntheticItems syntheticItems) {
+    FeatureSplit featureSplitA = getFeatureSplit(a, syntheticItems);
+    FeatureSplit featureSplitB = getFeatureSplit(b, syntheticItems);
     assert featureSplitA != null;
     assert featureSplitB != null;
     if (featureSplitA == featureSplitB) {
@@ -109,10 +113,10 @@
   }
 
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
-      Set<DexProgramClass> classes) {
+      Set<DexProgramClass> classes, SyntheticItems syntheticItems) {
     Map<FeatureSplit, Set<DexProgramClass>> result = new IdentityHashMap<>();
     for (DexProgramClass clazz : classes) {
-      FeatureSplit featureSplit = getFeatureSplit(clazz);
+      FeatureSplit featureSplit = getFeatureSplit(clazz, syntheticItems);
       if (featureSplit != null && !featureSplit.isBase()) {
         result.computeIfAbsent(featureSplit, ignore -> Sets.newIdentityHashSet()).add(clazz);
       }
@@ -120,41 +124,61 @@
     return result;
   }
 
-  public FeatureSplit getFeatureSplit(ProgramDefinition clazz) {
-    return getFeatureSplit(clazz.getContextType());
+  public FeatureSplit getFeatureSplit(ProgramDefinition clazz, SyntheticItems syntheticItems) {
+    return getFeatureSplit(clazz.getContextType(), syntheticItems);
   }
 
-  public FeatureSplit getFeatureSplit(DexType type) {
+  public FeatureSplit getFeatureSplit(DexType type, SyntheticItems syntheticItems) {
+    Collection<DexType> contexts = syntheticItems.getSynthesizingContexts(type);
+    if (!contexts.isEmpty()) {
+      Iterator<DexType> iterator = contexts.iterator();
+      DexType context = iterator.next();
+      FeatureSplit feature = classToFeatureSplitMap.getOrDefault(context, FeatureSplit.BASE);
+      assert verifyConsistentFeatureContexts(iterator, feature);
+      return feature;
+    }
     return classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
   }
 
+  private boolean verifyConsistentFeatureContexts(
+      Iterator<DexType> contextIterator, FeatureSplit feature) {
+    while (contextIterator.hasNext()) {
+      assert feature
+          == classToFeatureSplitMap.getOrDefault(contextIterator.next(), FeatureSplit.BASE);
+    }
+    return true;
+  }
+
   public boolean isEmpty() {
     return classToFeatureSplitMap.isEmpty();
   }
 
-  public boolean isInBase(DexProgramClass clazz) {
-    return getFeatureSplit(clazz).isBase();
+  public boolean isInBase(DexProgramClass clazz, SyntheticItems syntheticItems) {
+    return getFeatureSplit(clazz, syntheticItems).isBase();
   }
 
-  public boolean isInBaseOrSameFeatureAs(DexProgramClass clazz, ProgramDefinition context) {
-    FeatureSplit split = getFeatureSplit(clazz);
-    return split.isBase() || split == getFeatureSplit(context);
+  public boolean isInBaseOrSameFeatureAs(
+      DexProgramClass clazz, ProgramDefinition context, SyntheticItems syntheticItems) {
+    FeatureSplit split = getFeatureSplit(clazz, syntheticItems);
+    return split.isBase() || split == getFeatureSplit(context, syntheticItems);
   }
 
-  public boolean isInFeature(DexProgramClass clazz) {
-    return !isInBase(clazz);
+  public boolean isInFeature(DexProgramClass clazz, SyntheticItems syntheticItems) {
+    return !isInBase(clazz, syntheticItems);
   }
 
-  public boolean isInSameFeatureOrBothInBase(ProgramMethod a, ProgramMethod b) {
-    return isInSameFeatureOrBothInBase(a.getHolder(), b.getHolder());
+  public boolean isInSameFeatureOrBothInBase(
+      ProgramMethod a, ProgramMethod b, SyntheticItems syntheticItems) {
+    return isInSameFeatureOrBothInBase(a.getHolder(), b.getHolder(), syntheticItems);
   }
 
-  public boolean isInSameFeatureOrBothInBase(DexProgramClass a, DexProgramClass b) {
-    return getFeatureSplit(a) == getFeatureSplit(b);
+  public boolean isInSameFeatureOrBothInBase(
+      DexProgramClass a, DexProgramClass b, SyntheticItems syntheticItems) {
+    return getFeatureSplit(a, syntheticItems) == getFeatureSplit(b, syntheticItems);
   }
 
-  public boolean isInSameFeatureOrBothInBase(DexType a, DexType b) {
-    return getFeatureSplit(a) == getFeatureSplit(b);
+  public boolean isInSameFeatureOrBothInBase(DexType a, DexType b, SyntheticItems syntheticItems) {
+    return getFeatureSplit(a, syntheticItems) == getFeatureSplit(b, syntheticItems);
   }
 
   public ClassToFeatureSplitMap rewrittenWithLens(GraphLens lens) {
@@ -187,4 +211,14 @@
         });
     return classToFeatureSplitMapAfterPruning;
   }
+
+  // Static helpers to avoid verbose predicates.
+
+  private static ClassToFeatureSplitMap getMap(AppView<AppInfoWithLiveness> appView) {
+    return appView.appInfo().getClassToFeatureSplitMap();
+  }
+
+  public static boolean isInFeature(DexProgramClass clazz, AppView<AppInfoWithLiveness> appView) {
+    return getMap(appView).isInFeature(clazz, appView.getSyntheticItems());
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AccessControl.java b/src/main/java/com/android/tools/r8/graph/AccessControl.java
index f872a56..51ff1e2 100644
--- a/src/main/java/com/android/tools/r8/graph/AccessControl.java
+++ b/src/main/java/com/android/tools/r8/graph/AccessControl.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.OptionalBool;
 
 /**
@@ -18,16 +19,21 @@
       DexClass clazz,
       ProgramDefinition context,
       AppView<? extends AppInfoWithClassHierarchy> appView) {
-    return isClassAccessible(clazz, context, appView.appInfo().getClassToFeatureSplitMap());
+    return isClassAccessible(
+        clazz, context, appView.appInfo().getClassToFeatureSplitMap(), appView.getSyntheticItems());
   }
 
   public static OptionalBool isClassAccessible(
-      DexClass clazz, ProgramDefinition context, ClassToFeatureSplitMap classToFeatureSplitMap) {
+      DexClass clazz,
+      ProgramDefinition context,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     if (!clazz.isPublic() && !clazz.getType().isSamePackage(context.getContextType())) {
       return OptionalBool.FALSE;
     }
     if (clazz.isProgramClass()
-        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(clazz.asProgramClass(), context)) {
+        && !classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+            clazz.asProgramClass(), context, syntheticItems)) {
       return OptionalBool.UNKNOWN;
     }
     return OptionalBool.TRUE;
@@ -60,7 +66,11 @@
       AppInfoWithClassHierarchy appInfo) {
     AccessFlags<?> memberFlags = member.getDefinition().getAccessFlags();
     OptionalBool classAccessibility =
-        isClassAccessible(initialResolutionHolder, context, appInfo.getClassToFeatureSplitMap());
+        isClassAccessible(
+            initialResolutionHolder,
+            context,
+            appInfo.getClassToFeatureSplitMap(),
+            appInfo.getSyntheticItems());
     if (classAccessibility.isFalse()) {
       return OptionalBool.FALSE;
     }
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index bb61928..2cfb4d9 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -105,12 +105,14 @@
     }
     // Check if service is defined feature
     DexProgramClass serviceClass = appView.definitionForProgramType(serviceType);
-    if (serviceClass != null && classToFeatureSplitMap.isInFeature(serviceClass)) {
+    if (serviceClass != null
+        && classToFeatureSplitMap.isInFeature(serviceClass, appView.getSyntheticItems())) {
       return true;
     }
     for (DexType implementationType : featureImplementations.get(FeatureSplit.BASE)) {
       DexProgramClass implementationClass = appView.definitionForProgramType(implementationType);
-      if (implementationClass != null && classToFeatureSplitMap.isInFeature(implementationClass)) {
+      if (implementationClass != null
+          && classToFeatureSplitMap.isInFeature(implementationClass, appView.getSyntheticItems())) {
         return true;
       }
     }
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 3483fbb..f2959bb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotation.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexValue.DexValueAnnotation;
 import com.android.tools.r8.graph.DexValue.DexValueArray;
 import com.android.tools.r8.graph.DexValue.DexValueInt;
@@ -22,10 +21,7 @@
 import com.android.tools.r8.utils.structural.StructuralMapping;
 import com.android.tools.r8.utils.structural.StructuralSpecification;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.TreeSet;
 import java.util.function.Function;
 
 public class DexAnnotation extends DexItem implements StructuralItem<DexAnnotation> {
@@ -96,8 +92,7 @@
     }
     if (annotation == options.itemFactory.dalvikFastNativeAnnotation
         || annotation == options.itemFactory.dalvikCriticalNativeAnnotation
-        || annotation == options.itemFactory.annotationSynthesizedClass
-        || annotation == options.itemFactory.annotationSynthesizedClassMap) {
+        || annotation == options.itemFactory.annotationSynthesizedClass) {
       return true;
     }
     if (options.processCovariantReturnTypeAnnotations) {
@@ -363,43 +358,6 @@
     return new DexValueString(factory.createString(string));
   }
 
-  public static Collection<DexType> readAnnotationSynthesizedClassMap(
-      DexProgramClass clazz, DexItemFactory dexItemFactory) {
-    DexAnnotation foundAnnotation =
-        clazz.annotations().getFirstMatching(dexItemFactory.annotationSynthesizedClassMap);
-    if (foundAnnotation != null) {
-      if (foundAnnotation.annotation.elements.length != 1) {
-        throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
-      }
-      DexAnnotationElement value = foundAnnotation.annotation.elements[0];
-      if (value.name != dexItemFactory.valueString) {
-        throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
-      }
-      DexValueArray existingList = value.value.asDexValueArray();
-      if (existingList == null) {
-        throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
-      }
-      Collection<DexType> synthesized = new ArrayList<>(existingList.values.length);
-      for (DexValue element : existingList.getValues()) {
-        if (!element.isDexValueType()) {
-          throw new CompilationError(getInvalidSynthesizedClassMapMessage(clazz, foundAnnotation));
-        }
-        synthesized.add(element.asDexValueType().value);
-      }
-      return synthesized;
-    }
-    return Collections.emptyList();
-  }
-
-  private static String getInvalidSynthesizedClassMapMessage(
-      DexProgramClass annotatedClass,
-      DexAnnotation invalidAnnotation) {
-    return annotatedClass.toSourceString()
-        + " is annotated with invalid "
-        + invalidAnnotation.annotation.type.toString()
-        + ": " + invalidAnnotation.toString();
-  }
-
   public static DexAnnotation createAnnotationSynthesizedClass(
       SyntheticKind kind, DexType synthesizingContext, DexItemFactory dexItemFactory) {
     DexAnnotationElement kindElement =
@@ -456,26 +414,6 @@
     return new Pair<>(kind, valueElement.value.asDexValueType().getValue());
   }
 
-  public static DexAnnotation createAnnotationSynthesizedClassMap(
-      TreeSet<DexType> synthesized,
-      DexItemFactory dexItemFactory) {
-    DexValueType[] values = synthesized.stream()
-        .map(DexValueType::new)
-        .toArray(DexValueType[]::new);
-    DexValueArray array = new DexValueArray(values);
-    DexAnnotationElement pair =
-        new DexAnnotationElement(dexItemFactory.createString("value"), array);
-    return new DexAnnotation(
-        VISIBILITY_BUILD,
-        new DexEncodedAnnotation(
-            dexItemFactory.annotationSynthesizedClassMap, new DexAnnotationElement[]{pair}));
-  }
-
-  public static boolean isSynthesizedClassMapAnnotation(DexAnnotation annotation,
-      DexItemFactory factory) {
-    return annotation.annotation.type == factory.annotationSynthesizedClassMap;
-  }
-
   public DexAnnotation rewrite(Function<DexEncodedAnnotation, DexEncodedAnnotation> rewriter) {
     DexEncodedAnnotation rewritten = rewriter.apply(annotation);
     if (rewritten == annotation) {
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 0752d20..3f6eff3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -605,8 +605,6 @@
   public final DexType annotationThrows = createStaticallyKnownType("Ldalvik/annotation/Throws;");
   public final DexType annotationSynthesizedClass =
       createStaticallyKnownType("Lcom/android/tools/r8/annotations/SynthesizedClass;");
-  public final DexType annotationSynthesizedClassMap =
-      createStaticallyKnownType("Lcom/android/tools/r8/annotations/SynthesizedClassMap;");
   public final DexType annotationCovariantReturnType =
       createStaticallyKnownType("Ldalvik/annotation/codegen/CovariantReturnType;");
   public final DexType annotationCovariantReturnTypes =
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
index 9e4dd2b..20caa50 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/SameFeatureSplit.java
@@ -19,6 +19,9 @@
 
   @Override
   public FeatureSplit getMergeKey(DexProgramClass clazz) {
-    return appView.appInfo().getClassToFeatureSplitMap().getFeatureSplit(clazz);
+    return appView
+        .appInfo()
+        .getClassToFeatureSplitMap()
+        .getFeatureSplit(clazz, appView.getSyntheticItems());
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index 969b5d5..33e414f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.ir.optimize.inliner.WhyAreYouNotInliningReporter;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -152,10 +153,11 @@
       return false;
     }
 
+    SyntheticItems syntheticItems = appView.getSyntheticItems();
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(singleTarget, method)) {
+    if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(singleTarget, method, syntheticItems)) {
       // Still allow inlining if we inline from the base into a feature.
-      if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder())) {
+      if (!classToFeatureSplitMap.isInBase(singleTarget.getHolder(), syntheticItems)) {
         whyAreYouNotInliningReporter.reportInliningAcrossFeatureSplit();
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 8dc0dd3..0f12d78 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.contexts.CompilationContext.ProcessorContext;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClasspathMethod;
 import com.android.tools.r8.graph.Code;
@@ -1287,7 +1288,7 @@
         code -> {
           ProgramMethod context = code.context();
           assert !context.getDefinition().getCode().isOutlineCode();
-          if (appView.appInfo().getClassToFeatureSplitMap().isInFeature(context.getHolder())) {
+          if (ClassToFeatureSplitMap.isInFeature(context.getHolder(), appView)) {
             return;
           }
           for (BasicBlock block : code.blocks) {
@@ -1306,7 +1307,7 @@
   public void identifyOutlineSites(IRCode code) {
     ProgramMethod context = code.context();
     assert !context.getDefinition().getCode().isOutlineCode();
-    assert !appView.appInfo().getClassToFeatureSplitMap().isInFeature(context.getHolder());
+    assert !ClassToFeatureSplitMap.isInFeature(context.getHolder(), appView);
     for (BasicBlock block : code.blocks) {
       new OutlineSiteIdentifier(context, block).process();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
index afdf56a..289b2ba 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ReflectionOptimizer.java
@@ -246,7 +246,8 @@
 
     // Make sure the (base) type is visible.
     ClassToFeatureSplitMap classToFeatureSplitMap = appView.appInfo().getClassToFeatureSplitMap();
-    if (AccessControl.isClassAccessible(baseClass, context, classToFeatureSplitMap)
+    if (AccessControl.isClassAccessible(
+            baseClass, context, classToFeatureSplitMap, appView.getSyntheticItems())
         .isPossiblyFalse()) {
       return;
     }
@@ -254,7 +255,8 @@
     // If the type is guaranteed to be visible, it must be in the same feature as the current method
     // or in the base.
     assert !baseClass.isProgramClass()
-        || classToFeatureSplitMap.isInBaseOrSameFeatureAs(baseClass.asProgramClass(), context);
+        || classToFeatureSplitMap.isInBaseOrSameFeatureAs(
+            baseClass.asProgramClass(), context, appView.getSyntheticItems());
 
     consumer.accept(type, baseClass);
   }
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
index 0b340af..84acf70 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodBuilder.java
@@ -177,6 +177,7 @@
       DexType parameter = sourceParameters[i];
       ValueType parameterType = ValueType.fromDexType(parameter);
       instructions.add(new CfLoad(parameterType, maxLocals));
+      maxStack += parameterType.requiredRegisters();
       maxLocals += parameterType.requiredRegisters();
       maybeInsertArgumentCast(i, parameter, instructions);
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 762617e..057a80f 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -115,10 +115,6 @@
         return isAnnotationTypeLive;
 
       case DexAnnotation.VISIBILITY_BUILD:
-        if (DexAnnotation.isSynthesizedClassMapAnnotation(annotation, dexItemFactory)) {
-          // TODO(sgjesse) When should these be removed?
-          return true;
-        }
         if (!config.runtimeInvisibleAnnotations) {
           return false;
         }
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
index 58315d4..a60967f 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -91,6 +91,11 @@
     return this == NONE;
   }
 
+  public boolean isMainDexTypeThatShouldIncludeDependencies(DexType type) {
+    // Dependencies of 'type' are only needed if 'type' is a direct/executed main-dex type.
+    return classList.contains(type) || tracedRoots.contains(type);
+  }
+
   public boolean isMainDex(ProgramDefinition definition) {
     return isFromList(definition) || isTracedRoot(definition) || isDependency(definition);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
index c2d6a7c..ba25312 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -73,10 +73,6 @@
     // if the same type is in newMissingClasses in which case it is reported regardless.
     private final Set<DexType> newIgnoredMissingClasses = Sets.newIdentityHashSet();
 
-    private Builder() {
-      this(Sets.newIdentityHashSet());
-    }
-
     private Builder(Set<DexType> alreadyMissingClasses) {
       this.alreadyMissingClasses = alreadyMissingClasses;
     }
@@ -133,9 +129,7 @@
           getMissingClassesToBeReported(appView, synthesizingContextOracle);
       if (!missingClassesToBeReported.isEmpty()) {
         MissingDefinitionsDiagnostic diagnostic = createDiagnostic(missingClassesToBeReported);
-        InternalOptions options = appView.options();
-        // TODO(b/180903899): Remove L8 special handling when -dontwarn sun.misc.Unsafe is in place.
-        if (options.ignoreMissingClasses || options.isDesugaredLibraryCompilation()) {
+        if (appView.options().ignoreMissingClasses) {
           appView.reporter().warning(diagnostic);
         } else {
           throw appView.reporter().fatalError(diagnostic);
@@ -270,7 +264,6 @@
               dexItemFactory.annotationMethodParameters,
               dexItemFactory.annotationSourceDebugExtension,
               dexItemFactory.annotationSynthesizedClass,
-              dexItemFactory.annotationSynthesizedClassMap,
               dexItemFactory.annotationThrows,
               dexItemFactory.serializedLambdaType)
           .addAll(dexItemFactory.getJavaConversionTypes())
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 4963f21..afc9e50 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -357,7 +357,7 @@
 
     if (!appInfo
         .getClassToFeatureSplitMap()
-        .isInSameFeatureOrBothInBase(sourceClass, targetClass)) {
+        .isInSameFeatureOrBothInBase(sourceClass, targetClass, appView.getSyntheticItems())) {
       return false;
     }
     if (appView.appServices().allServiceTypes().contains(sourceClass.type)
diff --git a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
index 82f3204..bff9202 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.MainDexInfo;
 import java.util.Comparator;
-import java.util.Set;
 
 /**
  * A synthesizing context is a description of the context that gives rise to a synthetic item.
@@ -123,14 +122,15 @@
     appView.rewritePrefix.rewriteType(hygienicType, rewrittenType);
   }
 
+  // TODO(b/180074885): Remove this once main-dex is traced at the end of of compilation.
   void addIfDerivedFromMainDexClass(
-      DexProgramClass externalSyntheticClass,
-      MainDexInfo mainDexInfo,
-      Set<DexType> allMainDexTypes) {
+      DexProgramClass externalSyntheticClass, MainDexInfo mainDexInfo) {
+    if (mainDexInfo.isMainDex(externalSyntheticClass)) {
+      return;
+    }
     // The input context type (not the annotated context) determines if the derived class is to be
-    // in main dex.
-    // TODO(b/168584485): Once resolved allMainDexTypes == mainDexClasses.
-    if (allMainDexTypes.contains(inputContextType)) {
+    // in main dex, as it is the input context type that is traced as part of main-dex tracing.
+    if (mainDexInfo.isMainDexTypeThatShouldIncludeDependencies(inputContextType)) {
       mainDexInfo.addSyntheticClass(externalSyntheticClass);
     }
   }
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 9520d57..f6577d3 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -66,7 +66,10 @@
   public abstract C getHolder();
 
   final HashCode computeHash(
-      RepresentativeMap map, boolean intermediate, ClassToFeatureSplitMap classToFeatureSplitMap) {
+      RepresentativeMap map,
+      boolean intermediate,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     Hasher hasher = Hashing.murmur3_128().newHasher();
     if (getKind().isFixedSuffixSynthetic) {
       // Fixed synthetics are non-shareable. Its unique type is used as the hash key.
@@ -81,7 +84,7 @@
     if (!classToFeatureSplitMap.isEmpty()) {
       hasher.putInt(
           classToFeatureSplitMap
-              .getFeatureSplit(getContext().getSynthesizingContextType())
+              .getFeatureSplit(getContext().getSynthesizingContextType(), syntheticItems)
               .hashCode());
     }
 
@@ -95,15 +98,17 @@
       D other,
       boolean includeContext,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
-    return compareTo(other, includeContext, graphLens, classToFeatureSplitMap) == 0;
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
+    return compareTo(other, includeContext, graphLens, classToFeatureSplitMap, syntheticItems) == 0;
   }
 
   int compareTo(
       D other,
       boolean includeContext,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     DexType thisType = getHolder().getType();
     DexType otherType = other.getHolder().getType();
     if (getKind().isFixedSuffixSynthetic) {
@@ -120,10 +125,10 @@
       DexType synthesizingContextType = this.getContext().getSynthesizingContextType();
       DexType otherSynthesizingContextType = other.getContext().getSynthesizingContextType();
       if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(
-          synthesizingContextType, otherSynthesizingContextType)) {
+          synthesizingContextType, otherSynthesizingContextType, syntheticItems)) {
         int order =
             classToFeatureSplitMap.compareFeatureSplitsForDexTypes(
-                synthesizingContextType, otherSynthesizingContextType);
+                synthesizingContextType, otherSynthesizingContextType, syntheticItems);
         assert order != 0;
         return order;
       }
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 60dca8f..2e70db6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticFinalization.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -32,11 +31,9 @@
 import com.android.tools.r8.utils.collections.BidirectionalManyToOneRepresentativeMap;
 import com.android.tools.r8.utils.collections.BidirectionalOneToOneHashMap;
 import com.android.tools.r8.utils.structural.RepresentativeMap;
-import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.hash.HashCode;
 import java.util.ArrayList;
@@ -48,7 +45,6 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.TreeSet;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 
@@ -203,9 +199,11 @@
     public int compareToIncludingContext(
         EquivalenceGroup<T> other,
         GraphLens graphLens,
-        ClassToFeatureSplitMap classToFeatureSplitMap) {
+        ClassToFeatureSplitMap classToFeatureSplitMap,
+        SyntheticItems syntheticItems) {
       return getRepresentative()
-          .compareTo(other.getRepresentative(), true, graphLens, classToFeatureSplitMap);
+          .compareTo(
+              other.getRepresentative(), true, graphLens, classToFeatureSplitMap, syntheticItems);
     }
 
     @Override
@@ -219,11 +217,14 @@
   }
 
   private final InternalOptions options;
-  private final CommittedSyntheticsCollection synthetics;
+  private final SyntheticItems synthetics;
+  private final CommittedSyntheticsCollection committed;
 
-  SyntheticFinalization(InternalOptions options, CommittedSyntheticsCollection synthetics) {
+  SyntheticFinalization(
+      InternalOptions options, SyntheticItems synthetics, CommittedSyntheticsCollection committed) {
     this.options = options;
     this.synthetics = synthetics;
+    this.committed = committed;
   }
 
   public static void finalize(AppView<AppInfo> appView) {
@@ -257,7 +258,6 @@
   Result computeFinalSynthetics(AppView<?> appView) {
     assert verifyNoNestedSynthetics();
     DexApplication application;
-    MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
     Builder lensBuilder = new Builder();
     ImmutableMap.Builder<DexType, SyntheticMethodReference> finalMethodsBuilder =
         ImmutableMap.builder();
@@ -269,8 +269,8 @@
       application =
           buildLensAndProgram(
               appView,
-              computeEquivalences(appView, synthetics.getNonLegacyMethods().values(), generators),
-              computeEquivalences(appView, synthetics.getNonLegacyClasses().values(), generators),
+              computeEquivalences(appView, committed.getNonLegacyMethods().values(), generators),
+              computeEquivalences(appView, committed.getNonLegacyClasses().values(), generators),
               lensBuilder,
               (clazz, reference) -> {
                 finalSyntheticProgramDefinitions.add(clazz);
@@ -285,13 +285,8 @@
     ImmutableMap<DexType, SyntheticProgramClassReference> finalClasses =
         finalClassesBuilder.build();
 
-    handleSynthesizedClassMapping(
-        finalSyntheticProgramDefinitions, application, options, mainDexInfo, lensBuilder.typeMap);
-
-    assert appView.appInfo().getMainDexInfo() == mainDexInfo;
-
     Set<DexType> prunedSynthetics = Sets.newIdentityHashSet();
-    synthetics.forEachNonLegacyItem(
+    committed.forEachNonLegacyItem(
         reference -> {
           DexType type = reference.getHolder();
           if (!finalMethods.containsKey(type) && !finalClasses.containsKey(type)) {
@@ -304,7 +299,7 @@
             SyntheticItems.INVALID_ID_AFTER_SYNTHETIC_FINALIZATION,
             application,
             new CommittedSyntheticsCollection(
-                synthetics.getLegacyTypes(), finalMethods, finalClasses),
+                committed.getLegacyTypes(), finalMethods, finalClasses),
             ImmutableList.of()),
         lensBuilder.build(appView.graphLens(), appView.dexItemFactory()),
         PrunedItems.builder()
@@ -330,114 +325,25 @@
             intermediate,
             appView.dexItemFactory(),
             appView.graphLens(),
-            classToFeatureSplitMap);
+            classToFeatureSplitMap,
+            synthetics);
     return computeActualEquivalences(
         potentialEquivalences, generators, appView, intermediate, classToFeatureSplitMap);
   }
 
   private boolean isNotSyntheticType(DexType type) {
-    return !synthetics.containsNonLegacyType(type);
+    return !committed.containsNonLegacyType(type);
   }
 
   private boolean verifyNoNestedSynthetics() {
     // Check that a context is never itself synthetic class.
-    synthetics.forEachNonLegacyItem(
+    committed.forEachNonLegacyItem(
         item -> {
           assert isNotSyntheticType(item.getContext().getSynthesizingContextType());
         });
     return true;
   }
 
-  private void handleSynthesizedClassMapping(
-      List<DexProgramClass> finalSyntheticClasses,
-      DexApplication application,
-      InternalOptions options,
-      MainDexInfo mainDexInfo,
-      Map<DexType, DexType> derivedMainDexTypesToIgnore) {
-    boolean includeSynthesizedClassMappingInOutput = shouldAnnotateSynthetics(options);
-    if (includeSynthesizedClassMappingInOutput) {
-      updateSynthesizedClassMapping(application, finalSyntheticClasses);
-    }
-    updateMainDexListWithSynthesizedClassMap(application, mainDexInfo, derivedMainDexTypesToIgnore);
-    if (!includeSynthesizedClassMappingInOutput) {
-      clearSynthesizedClassMapping(application);
-    }
-  }
-
-  private void updateSynthesizedClassMapping(
-      DexApplication application, List<DexProgramClass> finalSyntheticClasses) {
-    ListMultimap<DexProgramClass, DexProgramClass> originalToSynthesized =
-        ArrayListMultimap.create();
-    for (DexType type : synthetics.getLegacyTypes()) {
-      DexProgramClass clazz = DexProgramClass.asProgramClassOrNull(application.definitionFor(type));
-      if (clazz != null) {
-        for (DexProgramClass origin : clazz.getSynthesizedFrom()) {
-          originalToSynthesized.put(origin, clazz);
-        }
-      }
-    }
-    for (DexProgramClass clazz : finalSyntheticClasses) {
-      for (DexProgramClass origin : clazz.getSynthesizedFrom()) {
-        originalToSynthesized.put(origin, clazz);
-      }
-    }
-    for (Map.Entry<DexProgramClass, Collection<DexProgramClass>> entry :
-        originalToSynthesized.asMap().entrySet()) {
-      DexProgramClass original = entry.getKey();
-      // Use a tree set to make sure that we have an ordering on the types.
-      // These types are put in an array in annotations in the output and we
-      // need a consistent ordering on them.
-      TreeSet<DexType> synthesized = new TreeSet<>(DexType::compareTo);
-      entry.getValue().stream()
-          .map(dexProgramClass -> dexProgramClass.type)
-          .forEach(synthesized::add);
-      synthesized.addAll(
-          DexAnnotation.readAnnotationSynthesizedClassMap(original, application.dexItemFactory));
-
-      DexAnnotation updatedAnnotation =
-          DexAnnotation.createAnnotationSynthesizedClassMap(
-              synthesized, application.dexItemFactory);
-
-      original.setAnnotations(original.annotations().getWithAddedOrReplaced(updatedAnnotation));
-    }
-  }
-
-  private void updateMainDexListWithSynthesizedClassMap(
-      DexApplication application,
-      MainDexInfo mainDexInfo,
-      Map<DexType, DexType> derivedMainDexTypesToIgnore) {
-    if (mainDexInfo.isEmpty()) {
-      return;
-    }
-    List<DexProgramClass> newMainDexClasses = new ArrayList<>();
-    mainDexInfo.forEachExcludingDependencies(
-        dexType -> {
-          DexProgramClass programClass =
-              DexProgramClass.asProgramClassOrNull(application.definitionFor(dexType));
-          if (programClass != null) {
-            Collection<DexType> derived =
-                DexAnnotation.readAnnotationSynthesizedClassMap(
-                    programClass, application.dexItemFactory);
-            for (DexType type : derived) {
-              DexType mappedType = derivedMainDexTypesToIgnore.getOrDefault(type, type);
-              DexProgramClass syntheticClass =
-                  DexProgramClass.asProgramClassOrNull(application.definitionFor(mappedType));
-              if (syntheticClass != null) {
-                newMainDexClasses.add(syntheticClass);
-              }
-            }
-          }
-        });
-    newMainDexClasses.forEach(mainDexInfo::addSyntheticClass);
-  }
-
-  private void clearSynthesizedClassMapping(DexApplication application) {
-    for (DexProgramClass clazz : application.classes()) {
-      clazz.setAnnotations(
-          clazz.annotations().getWithout(application.dexItemFactory.annotationSynthesizedClassMap));
-    }
-  }
-
   private static DexApplication buildLensAndProgram(
       AppView<?> appView,
       Map<DexType, EquivalenceGroup<SyntheticMethodDefinition>> syntheticMethodGroups,
@@ -448,23 +354,8 @@
     DexApplication application = appView.appInfo().app();
     DexItemFactory factory = appView.dexItemFactory();
     List<DexProgramClass> newProgramClasses = new ArrayList<>();
-
-    // TODO(b/168584485): Remove this once class-mapping support is removed.
-    Set<DexType> derivedMainDexTypes = Sets.newIdentityHashSet();
-    MainDexInfo mainDexInfo = appView.appInfo().getMainDexInfo();
-    mainDexInfo.forEachExcludingDependencies(
-        mainDexType -> {
-          derivedMainDexTypes.add(mainDexType);
-          DexProgramClass mainDexClass =
-              DexProgramClass.asProgramClassOrNull(
-                  appView.appInfo().definitionForWithoutExistenceAssert(mainDexType));
-          if (mainDexClass != null) {
-            derivedMainDexTypes.addAll(
-                DexAnnotation.readAnnotationSynthesizedClassMap(mainDexClass, factory));
-          }
-        });
-
     Set<DexType> pruned = Sets.newIdentityHashSet();
+
     syntheticMethodGroups.forEach(
         (syntheticType, syntheticGroup) -> {
           SyntheticMethodDefinition representative = syntheticGroup.getRepresentative();
@@ -565,8 +456,7 @@
             addMainDexAndSynthesizedFromForMember(
                 member,
                 externalSyntheticClass,
-                mainDexInfo,
-                derivedMainDexTypes,
+                appView.appInfo().getMainDexInfo(),
                 appForLookup::programDefinitionFor);
           }
         });
@@ -587,8 +477,7 @@
             addMainDexAndSynthesizedFromForMember(
                 member,
                 externalSyntheticClass,
-                mainDexInfo,
-                derivedMainDexTypes,
+                appView.appInfo().getMainDexInfo(),
                 appForLookup::programDefinitionFor);
           }
         });
@@ -638,11 +527,8 @@
       SyntheticDefinition<?, ?, ?> member,
       DexProgramClass externalSyntheticClass,
       MainDexInfo mainDexInfo,
-      Set<DexType> derivedMainDexTypes,
       Function<DexType, DexProgramClass> definitions) {
-    member
-        .getContext()
-        .addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexInfo, derivedMainDexTypes);
+    member.getContext().addIfDerivedFromMainDexClass(externalSyntheticClass, mainDexInfo);
     // TODO(b/168584485): Remove this once class-mapping support is removed.
     DexProgramClass from = definitions.apply(member.getContext().getSynthesizingContextType());
     if (from != null) {
@@ -655,7 +541,6 @@
     // This is currently also disabled on CF to CF desugaring to avoid missing class references to
     // the annotated classes.
     // TODO(b/147485959): Find an alternative encoding for synthetics to avoid missing-class refs.
-    // TODO(b/168584485): Remove support for main-dex tracing with the class-map annotation.
     return options.intermediate && !options.cfToCfDesugar;
   }
 
@@ -670,10 +555,12 @@
     potentialEquivalences.forEach(
         members -> {
           List<List<T>> groups =
-              groupEquivalent(members, intermediate, appView.graphLens(), classToFeatureSplitMap);
+              groupEquivalent(
+                  members, intermediate, appView.graphLens(), classToFeatureSplitMap, synthetics);
           for (List<T> group : groups) {
             T representative =
-                findDeterministicRepresentative(group, appView.graphLens(), classToFeatureSplitMap);
+                findDeterministicRepresentative(
+                    group, appView.graphLens(), classToFeatureSplitMap, synthetics);
             // The representative is required to be the first element of the group.
             group.remove(representative);
             group.add(0, representative);
@@ -691,7 +578,8 @@
           // representative which is equal to 'context' here (see assert below).
           groups.sort(
               (a, b) ->
-                  a.compareToIncludingContext(b, appView.graphLens(), classToFeatureSplitMap));
+                  a.compareToIncludingContext(
+                      b, appView.graphLens(), classToFeatureSplitMap, synthetics));
           for (int i = 0; i < groups.size(); i++) {
             EquivalenceGroup<T> group = groups.get(i);
             assert group
@@ -702,7 +590,11 @@
             // of the synthetic name will be non-deterministic between the two.
             assert i == 0
                 || checkGroupsAreDistinct(
-                    groups.get(i - 1), group, appView.graphLens(), classToFeatureSplitMap);
+                    groups.get(i - 1),
+                    group,
+                    appView.graphLens(),
+                    classToFeatureSplitMap,
+                    synthetics);
             SyntheticKind kind = group.members.get(0).getKind();
             DexType representativeType =
                 createExternalType(kind, externalSyntheticTypePrefix, generators, appView);
@@ -716,14 +608,15 @@
       List<T> potentialEquivalence,
       boolean intermediate,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     List<List<T>> groups = new ArrayList<>();
     // Each other member is in a shared group if it is actually equivalent to the first member.
     for (T synthetic : potentialEquivalence) {
       boolean requireNewGroup = true;
       for (List<T> group : groups) {
         if (synthetic.isEquivalentTo(
-            group.get(0), intermediate, graphLens, classToFeatureSplitMap)) {
+            group.get(0), intermediate, graphLens, classToFeatureSplitMap, syntheticItems)) {
           requireNewGroup = false;
           group.add(synthetic);
           break;
@@ -742,15 +635,20 @@
       EquivalenceGroup<T> g1,
       EquivalenceGroup<T> g2,
       GraphLens graphLens,
-      ClassToFeatureSplitMap classToFeatureSplitMap) {
-    int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap);
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
+    int order = g1.compareToIncludingContext(g2, graphLens, classToFeatureSplitMap, syntheticItems);
     assert order != 0;
-    assert order != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap);
+    assert order
+        != g2.compareToIncludingContext(g1, graphLens, classToFeatureSplitMap, syntheticItems);
     return true;
   }
 
   private static <T extends SyntheticDefinition<?, T, ?>> T findDeterministicRepresentative(
-      List<T> members, GraphLens graphLens, ClassToFeatureSplitMap classToFeatureSplitMap) {
+      List<T> members,
+      GraphLens graphLens,
+      ClassToFeatureSplitMap classToFeatureSplitMap,
+      SyntheticItems syntheticItems) {
     // Pick a deterministic member as representative.
     T smallest = members.get(0);
     for (int i = 1; i < members.size(); i++) {
@@ -794,7 +692,8 @@
           boolean intermediate,
           DexItemFactory factory,
           GraphLens graphLens,
-          ClassToFeatureSplitMap classToFeatureSplitMap) {
+          ClassToFeatureSplitMap classToFeatureSplitMap,
+          SyntheticItems syntheticItems) {
     if (definitions.isEmpty()) {
       return Collections.emptyList();
     }
@@ -817,7 +716,8 @@
     RepresentativeMap map = t -> syntheticTypes.contains(t) ? factory.voidType : t;
     Map<HashCode, List<T>> equivalences = new HashMap<>(definitions.size());
     for (T definition : definitions.values()) {
-      HashCode hash = definition.computeHash(map, intermediate, classToFeatureSplitMap);
+      HashCode hash =
+          definition.computeHash(map, intermediate, classToFeatureSplitMap, syntheticItems);
       equivalences.computeIfAbsent(hash, k -> new ArrayList<>()).add(definition);
     }
     return equivalences.values();
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 8263f40..9922011 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -84,6 +84,11 @@
 
   private final PendingSynthetics pending = new PendingSynthetics();
 
+  // Empty collection for use only in tests and utilities.
+  public static SyntheticItems empty() {
+    return new SyntheticItems(-1, CommittedSyntheticsCollection.empty());
+  }
+
   // Only for use from initial AppInfo/AppInfoWithClassHierarchy create functions. */
   public static CommittedItems createInitialSyntheticItems(DexApplication application) {
     return new CommittedItems(
@@ -215,6 +220,18 @@
     return isSyntheticClass(clazz.type);
   }
 
+  public Collection<DexType> getSynthesizingContexts(DexType type) {
+    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(type);
+    if (reference != null) {
+      return Collections.singletonList(reference.getContext().getSynthesizingContextType());
+    }
+    SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(type);
+    if (definition != null) {
+      return Collections.singletonList(definition.getContext().getSynthesizingContextType());
+    }
+    return Collections.emptyList();
+  }
+
   // TODO(b/180091213): Implement this and remove client provided the oracle.
   public Set<DexReference> getSynthesizingContexts(
       DexProgramClass clazz, SynthesizingContextOracle oracle) {
@@ -445,6 +462,7 @@
 
   Result computeFinalSynthetics(AppView<?> appView) {
     assert !hasPendingSyntheticClasses();
-    return new SyntheticFinalization(appView.options(), committed).computeFinalSynthetics(appView);
+    return new SyntheticFinalization(appView.options(), this, committed)
+        .computeFinalSynthetics(appView);
   }
 }
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 25e59ae..1b80f41 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -63,7 +63,7 @@
     }
   }
 
-  private static final String SYNTHETIC_CLASS_SEPARATOR = "-$$";
+  private static final String SYNTHETIC_CLASS_SEPARATOR = "$$";
   /**
    * The internal synthetic class separator is only used for representing synthetic items during
    * compilation. In particular, this separator must never be used to write synthetic classes to the
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 21c3447..1534b4a 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -42,6 +42,7 @@
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.origin.PathOrigin;
 import com.android.tools.r8.shaking.FilteredClassPath;
+import com.android.tools.r8.synthesis.SyntheticItems;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.io.ByteStreams;
@@ -611,7 +612,8 @@
                       classDescriptor -> {
                         if (featureSplitConfiguration != null) {
                           DexType type = dexItemFactory.createType(classDescriptor);
-                          FeatureSplit featureSplit = classToFeatureSplitMap.getFeatureSplit(type);
+                          FeatureSplit featureSplit =
+                              classToFeatureSplitMap.getFeatureSplit(type, SyntheticItems.empty());
                           if (featureSplit != null && !featureSplit.isBase()) {
                             return featureSplitArchiveOutputStreams.get(featureSplit);
                           }
diff --git a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
index 76e6f33..15344f9 100644
--- a/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/TypeReferenceUtils.java
@@ -11,6 +11,9 @@
 
   private static final Comparator<TypeReference> COMPARATOR =
       (type, other) -> {
+        if (type == other) {
+          return 0;
+        }
         // Handle null inputs (void).
         if (type == null) {
           return -1;
diff --git a/src/test/examplesAndroidO/multidex004/ref-list-1.txt b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
index c817c33..bd195b3 100644
--- a/src/test/examplesAndroidO/multidex004/ref-list-1.txt
+++ b/src/test/examplesAndroidO/multidex004/ref-list-1.txt
@@ -1,4 +1,4 @@
-Lmultidex004/MainActivity-$$ExternalSyntheticLambda0;
+Lmultidex004/MainActivity$$ExternalSyntheticLambda0;
 Lmultidex004/MainActivity;
 Lmultidex004/VersionInterface;
 Lmultidex004/VersionStatic;
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 82cfebf..43bbd99 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -361,15 +361,25 @@
   abstract D8IncrementalTestRunner test(String testName, String packageName, String mainClass);
 
   @Override
-  protected void testIntermediateWithMainDexList(String packageName, Path input,
-      int expectedMainDexListSize, String... mainDexClasses) throws Throwable {
+  protected void testIntermediateWithMainDexList(
+      String packageName,
+      Path input,
+      int expectedMainDexListSize,
+      List<String> mainDexClasses,
+      List<String> mainDexOverApproximation)
+      throws Throwable {
     // Skip those tests.
     Assume.assumeTrue(false);
   }
 
   @Override
-  protected Path buildDexThroughIntermediate(String packageName, Path input, OutputMode outputMode,
-      AndroidApiLevel minApi, String... mainDexClasses) throws Throwable {
+  protected Path buildDexThroughIntermediate(
+      String packageName,
+      Path input,
+      OutputMode outputMode,
+      AndroidApiLevel minApi,
+      List<String> mainDexClasses)
+      throws Throwable {
     // tests using this should already been skipped.
     throw new Unreachable();
   }
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 4c88d91..4913bc5 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
@@ -86,4 +87,16 @@
     builder.addMainDexRulesFiles(mainDexRuleFiles);
     return self();
   }
+
+  public D8TestBuilder addMainDexRules(String... rules) {
+    builder.addMainDexRules(Arrays.asList(rules), Origin.unknown());
+    return self();
+  }
+
+  public D8TestBuilder addMainDexKeepClassRules(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      addMainDexRules("-keep class " + clazz.getTypeName());
+    }
+    return self();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 2773cf1..52480be 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -9,15 +9,19 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.StringUtils.BraceType;
 import com.android.tools.r8.utils.TestDescriptionWatcher;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
@@ -27,6 +31,8 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Sets.SetView;
 import com.google.common.io.ByteStreams;
 import java.io.IOException;
 import java.io.InputStream;
@@ -35,13 +41,12 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.ExecutionException;
+import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
@@ -114,6 +119,24 @@
       return withBuilderTransformation(builder -> builder.addMainDexClasses(classes));
     }
 
+    C withMainDexKeepClassRules(List<String> classes) {
+      return withBuilderTransformation(
+          builder -> {
+            if (builder instanceof D8Command.Builder) {
+              ((D8Command.Builder) builder)
+                  .addMainDexRules(
+                      ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown());
+            } else if (builder instanceof R8Command.Builder) {
+              ((R8Command.Builder) builder)
+                  .addMainDexRules(
+                      ListUtils.map(classes, c -> "-keep class " + c), Origin.unknown());
+            } else {
+              fail("Unexpected builder type: " + builder.getClass());
+            }
+            return builder;
+          });
+    }
+
     C withInterfaceMethodDesugaring(OffOrAuto behavior) {
       return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior);
     }
@@ -465,15 +488,20 @@
     testIntermediateWithMainDexList(
         "lambdadesugaring",
         1,
-        "lambdadesugaring.LambdaDesugaring$I");
+        ImmutableList.of("lambdadesugaring.LambdaDesugaring$I"),
+        ImmutableList.of());
   }
 
   @Test
   public void testLambdaDesugaringWithMainDexList2() throws Throwable {
     // Main dex class has many lambdas.
-    testIntermediateWithMainDexList("lambdadesugaring",
-        33,
-        "lambdadesugaring.LambdaDesugaring$Refs$B");
+    testIntermediateWithMainDexList(
+        "lambdadesugaring",
+        // TODO(b/180074885): Over approximation not present in R8.
+        this instanceof R8RunExamplesAndroidOTest ? 51 : 52,
+        ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$B"),
+        // TODO(b/180074885): Over approximation due to invoke-dynamic reference adds as dependency.
+        ImmutableList.of("lambdadesugaring.LambdaDesugaring$Refs$D"));
   }
 
   @Test
@@ -483,7 +511,11 @@
         "interfacemethods",
         Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION),
         2,
-        "interfacemethods.I1");
+        ImmutableList.of("interfacemethods.I1"),
+        // TODO(b/180074885): Over approximation due to including I1-CC by being derived from I1,
+        //  but after desugaring I1 does not reference I1$-CC (the static method is moved), so it
+        //  is incorrect to include I1-CC in the main dex.
+        ImmutableList.of("interfacemethods.I1$-CC"));
   }
 
 
@@ -494,7 +526,11 @@
         "interfacemethods",
         Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION),
         2,
-        "interfacemethods.I2");
+        ImmutableList.of("interfacemethods.I2"),
+        // TODO(b/180074885): Over approximation due to including I2$-CC by being derived from I2,
+        //  but after desugaring I2 does not reference I2$-CC (the default method is moved), so it
+        //  is incorrect to include I2$-CC in the main dex.
+        ImmutableList.of("interfacemethods.I2$-CC"));
   }
 
   @Test
@@ -510,20 +546,23 @@
   private void testIntermediateWithMainDexList(
       String packageName,
       int expectedMainDexListSize,
-      String... mainDexClasses)
+      List<String> mainDexClasses,
+      List<String> mainDexOverApproximation)
       throws Throwable {
     testIntermediateWithMainDexList(
         packageName,
         Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION),
         expectedMainDexListSize,
-        mainDexClasses);
+        mainDexClasses,
+        mainDexOverApproximation);
   }
 
   protected void testIntermediateWithMainDexList(
       String packageName,
       Path input,
       int expectedMainDexListSize,
-      String... mainDexClasses)
+      List<String> mainDexClasses,
+      List<String> mainDexOverApproximation)
       throws Throwable {
     AndroidApiLevel minApi = AndroidApiLevel.K;
 
@@ -534,16 +573,18 @@
             .withMinApiLevel(minApi)
             .withOptionConsumer(option -> option.minimalMainDex = true)
             .withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false)
-            .withMainDexClass(mainDexClasses)
+            .withMainDexKeepClassRules(mainDexClasses)
             .withKeepAll();
     Path fullDexes = temp.getRoot().toPath().resolve(packageName + "full" + ZIP_EXTENSION);
     full.build(input, fullDexes);
 
     // Builds with intermediate in both output mode.
-    Path dexesThroughIndexedIntermediate = buildDexThroughIntermediate(
-        packageName, input, OutputMode.DexIndexed, minApi, mainDexClasses);
-    Path dexesThroughFilePerInputClassIntermediate = buildDexThroughIntermediate(
-        packageName, input, OutputMode.DexFilePerClassFile, minApi, mainDexClasses);
+    Path dexesThroughIndexedIntermediate =
+        buildDexThroughIntermediate(
+            packageName, input, OutputMode.DexIndexed, minApi, mainDexClasses);
+    Path dexesThroughFilePerInputClassIntermediate =
+        buildDexThroughIntermediate(
+            packageName, input, OutputMode.DexFilePerClassFile, minApi, mainDexClasses);
 
     // Collect main dex types.
     CodeInspector fullInspector = getMainDexInspector(fullDexes);
@@ -551,20 +592,45 @@
         getMainDexInspector(dexesThroughIndexedIntermediate);
     CodeInspector filePerInputClassIntermediateInspector =
         getMainDexInspector(dexesThroughFilePerInputClassIntermediate);
-    Collection<String> fullMainClasses = new HashSet<>();
+    Set<String> fullMainClasses = new HashSet<>();
     fullInspector.forAllClasses(
         clazz -> fullMainClasses.add(clazz.getFinalDescriptor()));
-    Collection<String> indexedIntermediateMainClasses = new HashSet<>();
+    Set<String> indexedIntermediateMainClasses = new HashSet<>();
     indexedIntermediateInspector.forAllClasses(
         clazz -> indexedIntermediateMainClasses.add(clazz.getFinalDescriptor()));
-    Collection<String> filePerInputClassIntermediateMainClasses = new HashSet<>();
+    Set<String> filePerInputClassIntermediateMainClasses = new HashSet<>();
     filePerInputClassIntermediateInspector.forAllClasses(
         clazz -> filePerInputClassIntermediateMainClasses.add(clazz.getFinalDescriptor()));
 
     // Check.
     Assert.assertEquals(expectedMainDexListSize, fullMainClasses.size());
-    Assert.assertEquals(fullMainClasses, indexedIntermediateMainClasses);
-    Assert.assertEquals(fullMainClasses, filePerInputClassIntermediateMainClasses);
+    SetView<String> adjustedFull =
+        Sets.difference(
+            fullMainClasses,
+            new HashSet<>(
+                ListUtils.map(mainDexOverApproximation, DescriptorUtils::javaTypeToDescriptor)));
+    assertEqualSets(adjustedFull, indexedIntermediateMainClasses);
+    assertEqualSets(adjustedFull, filePerInputClassIntermediateMainClasses);
+  }
+
+  <T> void assertEqualSets(Set<T> expected, Set<T> actual) {
+    SetView<T> missing = Sets.difference(expected, actual);
+    SetView<T> unexpected = Sets.difference(actual, expected);
+    if (missing.isEmpty() && unexpected.isEmpty()) {
+      return;
+    }
+    StringBuilder builder = new StringBuilder("Sets differ.");
+    if (!missing.isEmpty()) {
+      builder.append("\nMissing items: [\n  ");
+      StringUtils.append(builder, missing, "\n  ", BraceType.NONE);
+      builder.append("\n]");
+    }
+    if (!unexpected.isEmpty()) {
+      builder.append("\nUnexpected items: [\n  ");
+      StringUtils.append(builder, unexpected, "\n  ", BraceType.NONE);
+      builder.append("\n]");
+    }
+    fail(builder.toString());
   }
 
   protected Path buildDexThroughIntermediate(
@@ -572,7 +638,7 @@
       Path input,
       OutputMode outputMode,
       AndroidApiLevel minApi,
-      String... mainDexClasses)
+      List<String> mainDexClasses)
       throws Throwable {
     Path intermediateDex =
         temp.getRoot().toPath().resolve(packageName + "intermediate" + ZIP_EXTENSION);
@@ -591,7 +657,7 @@
         test(packageName + "dex", packageName, "N/A")
             .withOptionConsumer(option -> option.minimalMainDex = true)
             .withOptionConsumer(option -> option.enableInheritanceClassInDexDistributor = false)
-            .withMainDexClass(mainDexClasses)
+            .withMainDexKeepClassRules(mainDexClasses)
             .withMinApiLevel(minApi)
             .withKeepAll();
 
@@ -644,8 +710,7 @@
     }
   }
 
-  protected CodeInspector getMainDexInspector(Path zip)
-      throws IOException, ExecutionException {
+  protected CodeInspector getMainDexInspector(Path zip) throws IOException {
     try (ZipFile zipFile = new ZipFile(zip.toFile(), StandardCharsets.UTF_8)) {
       try (InputStream in =
           zipFile.getInputStream(zipFile.getEntry(ToolHelper.DEFAULT_DEX_FILENAME))) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontallyMergedClassInliningTest.java b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontallyMergedClassInliningTest.java
new file mode 100644
index 0000000..0ca2179
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/horizontal/HorizontallyMergedClassInliningTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, 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.classmerging.horizontal;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isAbsent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class HorizontallyMergedClassInliningTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public HorizontallyMergedClassInliningTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(Main.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> inspector.assertIsCompleteMergeGroup(A.class, B.class, C.class))
+        .allowAccessModification()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(
+            inspector -> {
+              assertThat(inspector.clazz(A.class), isAbsent());
+              assertThat(inspector.clazz(B.class), isAbsent());
+              assertThat(inspector.clazz(C.class), isAbsent());
+            })
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput("Hello world!");
+  }
+
+  static class Main {
+
+    public static void main(String[] args) {
+      print(new A().build());
+      print(new B().build());
+      print(new C().build());
+    }
+
+    static void print(String[] strings) {
+      for (String string : strings) {
+        System.out.print(string);
+      }
+    }
+  }
+
+  static class A {
+
+    String[] build() {
+      return new String[] {"H", "e", "l", "l"};
+    }
+  }
+
+  static class B {
+
+    String[] build() {
+      return new String[] {"o", " ", "w", "o"};
+    }
+  }
+
+  static class C {
+
+    String[] build() {
+      return new String[] {"r", "l", "d", "!"};
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
index 05f84af..81f0674 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/BackportMainDexTest.java
@@ -153,7 +153,7 @@
     testForD8(parameters.getBackend())
         .addProgramClasses(CLASSES)
         .setMinApi(parameters.getApiLevel())
-        .addMainDexListClasses(MiniAssert.class, TestClass.class, User2.class)
+        .addMainDexRules(keepMainProguardConfiguration(TestClass.class))
         .setProgramConsumer(mainDexConsumer)
         .compile()
         .inspect(this::checkExpectedSynthetics)
@@ -185,44 +185,8 @@
     testForD8()
         .addProgramFiles(perClassOutput)
         .setMinApi(parameters.getApiLevel())
-        .addMainDexListClasses(MiniAssert.class, TestClass.class, User2.class)
-        .setProgramConsumer(mainDexConsumer)
-        .compile()
-        .inspect(this::checkExpectedSynthetics)
-        .run(parameters.getRuntime(), TestClass.class, getRunArgs())
-        .assertSuccessWithOutput(EXPECTED);
-    checkMainDex(mainDexConsumer);
-  }
-
-  // TODO(b/168584485): This test should be removed once support is dropped.
-  @Test
-  public void testD8MergingWithTraceCf() throws Exception {
-    assumeTrue(parameters.isDexRuntime());
-    Path out1 =
-        testForD8()
-            .addProgramClasses(User1.class)
-            .addClasspathClasses(CLASSES)
-            .setIntermediate(true)
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .writeToZip();
-
-    Path out2 =
-        testForD8()
-            .addProgramClasses(User2.class)
-            .addClasspathClasses(CLASSES)
-            .setIntermediate(true)
-            .setMinApi(parameters.getApiLevel())
-            .compile()
-            .writeToZip();
-
-    MainDexConsumer mainDexConsumer = new MainDexConsumer();
-    testForD8(parameters.getBackend())
-        .addProgramClasses(TestClass.class, MiniAssert.class)
-        .addProgramFiles(out1, out2)
-        .setMinApi(parameters.getApiLevel())
-        .addMainDexListClassReferences(
-            traceMainDex(CLASSES, Collections.emptyList()).getMainDexList())
+        // Trace the classes run by main which will pick up their dependencies.
+        .addMainDexRules(keepMainProguardConfiguration(TestClass.class))
         .setProgramConsumer(mainDexConsumer)
         .compile()
         .inspect(this::checkExpectedSynthetics)
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
index af73519..20c2b51 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/SyntheticDistributionTest.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -55,7 +56,6 @@
             r8FullTestBuilder
                 .noMinification()
                 .enableInliningAnnotations()
-                .addInliningAnnotations()
                 .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O));
     ThrowingConsumer<R8TestCompileResult, Exception> ensureLambdaNotInBase =
         r8TestCompileResult -> {
@@ -87,6 +87,10 @@
             .addFeatureSplit(FeatureClass.class)
             .addFeatureSplit(Feature2Class.class)
             .addKeepFeatureMainRules(BaseSuperClass.class, FeatureClass.class, Feature2Class.class)
+            .addKeepMethodRules(
+                Reference.methodFromMethod(
+                    BaseSuperClass.class.getDeclaredMethod(
+                        "keptApplyLambda", MyFunction.class, String.class)))
             .noMinification()
             .enableInliningAnnotations()
             .setMinApi(parameters.getApiLevel())
@@ -108,6 +112,10 @@
     }
 
     public abstract String getFromFeature();
+
+    public String keptApplyLambda(MyFunction fn, String arg) {
+      return fn.apply(arg);
+    }
   }
 
   public interface MyFunction {
@@ -133,7 +141,7 @@
 
     @NeverInline
     private String useTheLambda(MyFunction f) {
-      return f.apply("42");
+      return keptApplyLambda(f, "42");
     }
   }
 
@@ -159,7 +167,7 @@
 
     @NeverInline
     private String useTheLambda(MyFunction f) {
-      return f.apply("43");
+      return keptApplyLambda(f, "43");
     }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
index 1bbcb9e..d3d99ee 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListOutputTest.java
@@ -116,12 +116,11 @@
 
   @Test
   public void testD8DesugaredLambdasInMainDexList() throws Exception {
-    Path mainDexList = writeTextToTempFile(testClassMainDexName);
     TestMainDexListConsumer consumer = new TestMainDexListConsumer();
     testForD8()
         .setMinApi(AndroidApiLevel.K)
         .addProgramClasses(ImmutableList.of(TestClass.class, MyConsumer.class))
-        .addMainDexListFiles(ImmutableList.of(mainDexList))
+        .addMainDexListClasses(TestClass.class)
         .setMainDexListConsumer(consumer)
         .compile();
     assertTrue(consumer.called);
@@ -129,7 +128,6 @@
 
   @Test
   public void testD8DesugaredLambdasInMainDexListMerging() throws Exception {
-    Path mainDexList = writeTextToTempFile(testClassMainDexName);
     // Build intermediate dex code first.
     Path dexOutput =
         testForD8()
@@ -143,7 +141,7 @@
     testForD8()
         .setMinApi(AndroidApiLevel.K)
         .addProgramFiles(dexOutput)
-        .addMainDexListFiles(ImmutableList.of(mainDexList))
+        .addMainDexKeepClassRules(TestClass.class)
         .setMainDexListConsumer(consumer)
         .compile();
     assertTrue(consumer.called);
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
index 9fa7fe9..5e88d7d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexWithSynthesizedClassesTest.java
@@ -72,7 +72,7 @@
     D8TestCompileResult compileResult =
         testForD8()
             .addProgramFiles(intermediateResult.writeToZip())
-            .addMainDexListClasses(TestClass.class, A.class)
+            .addMainDexKeepClassRules(TestClass.class, A.class)
             .setMinApiThreshold(parameters.getApiLevel())
             .compile();
     checkCompilationResult(compileResult);
diff --git a/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java b/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java
index a203729..4eb6d15 100644
--- a/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java
+++ b/src/test/java/com/android/tools/r8/repackage/RepackageFeatureWithSyntheticsTest.java
@@ -12,7 +12,6 @@
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
@@ -37,12 +36,16 @@
   private static final List<Class<?>> FIRST_CLASSES =
       ImmutableList.of(FIRST_FOO, FIRST_PKG_PRIVATE);
 
+  private static final Class<?> FIRST_FIRST_FOO =
+      com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first.Foo
+          .class;
+
+  private static final Class<?> FIRST_FIRST_PKG_PRIVATE =
+      com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first
+          .PkgProtectedMethod.class;
+
   private static final List<Class<?>> FIRST_FIRST_CLASSES =
-      ImmutableList.of(
-          com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first.Foo
-              .class,
-          com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first.first
-              .PkgProtectedMethod.class);
+      ImmutableList.of(FIRST_FIRST_FOO, FIRST_FIRST_PKG_PRIVATE);
 
   private static List<Class<?>> getTestClasses() {
     return ImmutableList.<Class<?>>builder()
@@ -84,11 +87,7 @@
             .addProgramClasses(getTestClasses())
             .addFeatureSplit(getFeatureClasses().toArray(new Class<?>[0]))
             .addKeepMainRule(TestClass.class)
-            .addKeepClassAndMembersRules(
-                com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first
-                    .PkgProtectedMethod.class,
-                com.android.tools.r8.repackage.testclasses.repackagefeaturewithsynthetics.first
-                    .first.PkgProtectedMethod.class)
+            .addKeepClassAndMembersRules(FIRST_PKG_PRIVATE, FIRST_FIRST_PKG_PRIVATE)
             .addKeepClassAndMembersRules(I.class)
             .addKeepMethodRules(
                 Reference.methodFromMethod(TestClass.class.getDeclaredMethod("run", I.class)))
@@ -106,20 +105,21 @@
     // Check that the first.Foo is repackaged but that the pkg private access class is not.
     // The implies that the lambda which is doing a package private access cannot be repackaged.
     // If it is, the access will fail resulting in a runtime exception.
-    CodeInspector baseInspector = compileResult.inspector();
-    assertThat(FIRST_FOO, isRepackagedAsExpected(baseInspector, "first"));
-    assertThat(FIRST_PKG_PRIVATE, isNotRepackaged(baseInspector));
-    assertEquals(
-        getTestClasses().size() + expectedSyntheticsInBase, baseInspector.allClasses().size());
-
-    // The feature first.first.Foo is not repackaged and neither is the lambda.
-    // TODO(b/180086194): first.first.Foo should have been repackaged, but is currently identified
-    //   as being in 'base' when inlining takes place.
-    CodeInspector featureInspector = new CodeInspector(compileResult.getFeature(0));
-    getFeatureClasses().forEach(c -> assertThat(c, isNotRepackaged(featureInspector)));
-    assertEquals(
-        getFeatureClasses().size() + expectedSyntheticsInFeature,
-        featureInspector.allClasses().size());
+    compileResult.inspect(
+        baseInspector -> {
+          assertThat(FIRST_FOO, isRepackagedAsExpected(baseInspector, "first"));
+          assertThat(FIRST_PKG_PRIVATE, isNotRepackaged(baseInspector));
+          assertEquals(
+              getTestClasses().size() + expectedSyntheticsInBase,
+              baseInspector.allClasses().size());
+        },
+        featureInspector -> {
+          assertThat(FIRST_FIRST_FOO, isRepackagedAsExpected(featureInspector, "first$1"));
+          assertThat(FIRST_FIRST_PKG_PRIVATE, isNotRepackaged(featureInspector));
+          assertEquals(
+              getFeatureClasses().size() + expectedSyntheticsInFeature,
+              featureInspector.allClasses().size());
+        });
 
     compileResult
         .addFeatureSplitsToRunClasspathFiles()
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
index a5c623c..91fb002 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/HorizontallyMergedClassesInspector.java
@@ -133,6 +133,11 @@
     return this;
   }
 
+  public HorizontallyMergedClassesInspector assertIsCompleteMergeGroup(Class<?>... classes) {
+    return assertIsCompleteMergeGroup(
+        Stream.of(classes).map(Reference::classFromClass).collect(Collectors.toList()));
+  }
+
   public HorizontallyMergedClassesInspector assertIsCompleteMergeGroup(String... typeNames) {
     return assertIsCompleteMergeGroup(
         Stream.of(typeNames).map(Reference::classFromTypeName).collect(Collectors.toList()));