Store the feature of synthetics in their context description.

Bug: 179174151
Change-Id: I2ff924a3386a43ce0225ced093de7b5d514a0404
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index fa7e21d..1db8182 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -646,7 +646,7 @@
       // TODO(b/181636450): Reconsider the code mapping setup now that synthetics are never
       //  duplicated in outputs.
       boolean isSharedSynthetic =
-          appView.getSyntheticItems().getSynthesizingContexts(clazz.getType()).size() > 1;
+          appView.getSyntheticItems().getSynthesizingContextTypes(clazz.getType()).size() > 1;
       clazz.forEachMethod(
           method -> {
             DexCode code =
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 889fc27..9b7c9da 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -690,7 +690,7 @@
     writeEncodedFields(clazz.staticFields());
     writeEncodedFields(clazz.instanceFields());
     boolean isSharedSynthetic =
-        appInfo.getSyntheticItems().getSynthesizingContexts(clazz.getType()).size() > 1;
+        appInfo.getSyntheticItems().getSynthesizingContextTypes(clazz.getType()).size() > 1;
     writeEncodedMethods(clazz.directMethods(), isSharedSynthetic);
     writeEncodedMethods(clazz.virtualMethods(), isSharedSynthetic);
   }
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 e44973a..fa17d7c 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -330,7 +330,7 @@
       // Assign dedicated virtual files for all program classes.
       for (DexProgramClass clazz : appView.appInfo().classes()) {
         Collection<DexType> contexts =
-            appView.getSyntheticItems().getSynthesizingContexts(clazz.getType());
+            appView.getSyntheticItems().getSynthesizingContextTypes(clazz.getType());
         // TODO(b/181636450): Simplify this making use of the assumption that synthetics are never
         //  duplicated.
         if (!combineSyntheticClassesWithPrimaryClass || contexts.isEmpty()) {
@@ -354,7 +354,7 @@
       }
       for (DexProgramClass synthetic : synthetics) {
         for (DexType context :
-            appView.getSyntheticItems().getSynthesizingContexts(synthetic.getType())) {
+            appView.getSyntheticItems().getSynthesizingContextTypes(synthetic.getType())) {
           DexProgramClass inputType = appView.definitionForProgramType(context);
           VirtualFile file = files.get(inputType);
           file.addClass(synthetic);
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 c8c462b..f460b50 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -22,10 +22,8 @@
 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;
 
@@ -95,6 +93,10 @@
   public int compareFeatureSplitsForDexTypes(DexType a, DexType b, SyntheticItems syntheticItems) {
     FeatureSplit featureSplitA = getFeatureSplit(a, syntheticItems);
     FeatureSplit featureSplitB = getFeatureSplit(b, syntheticItems);
+    return compareFeatureSplits(featureSplitA, featureSplitB);
+  }
+
+  public int compareFeatureSplits(FeatureSplit featureSplitA, FeatureSplit featureSplitB) {
     assert featureSplitA != null;
     assert featureSplitB != null;
     if (featureSplitA == featureSplitB) {
@@ -129,26 +131,20 @@
   }
 
   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);
+    FeatureSplit feature = classToFeatureSplitMap.get(type);
+    if (feature != null) {
       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);
+    feature = syntheticItems.getContextualFeatureSplit(type);
+    if (feature != null) {
+      return feature;
     }
-    return true;
+    return FeatureSplit.BASE;
   }
 
+  // Note, this predicate may be misleading as the map does not include synthetics.
+  // In practice it should not be an issue as there should not be a way to have a feature shrink
+  // to only contain synthetic content. At a minimum the entry points of the feature must remain.
   public boolean isEmpty() {
     return classToFeatureSplitMap.isEmpty();
   }
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 c8cc19e..88a4755 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/SyntheticArgumentClass.java
@@ -53,7 +53,7 @@
     private DexProgramClass synthesizeClass(DexProgramClass context, SyntheticKind syntheticKind) {
       return appView
           .getSyntheticItems()
-          .createFixedClass(syntheticKind, context, appView.dexItemFactory(), builder -> {});
+          .createFixedClass(syntheticKind, context, appView, builder -> {});
     }
 
     public SyntheticArgumentClass build(Collection<MergeGroup> mergeGroups) {
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 832fd8c..cc28692 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
@@ -1442,7 +1442,7 @@
           .createMethod(
               SyntheticNaming.SyntheticKind.BACKPORT,
               methodProcessingContext.createUniqueContext(),
-              appView.dexItemFactory(),
+              appView,
               builder ->
                   builder
                       .setProto(getProto(appView.dexItemFactory()))
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
index 5770e22..b9a8880 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/RecordRewriter.java
@@ -228,7 +228,7 @@
         .createMethod(
             SyntheticNaming.SyntheticKind.RECORD_HELPER,
             methodProcessingContext.createUniqueContext(),
-            factory,
+            appView,
             builder ->
                 builder
                     .setProto(helperProto)
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 0899b2d..180c2aa 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
@@ -25,6 +25,7 @@
 import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -455,7 +456,7 @@
                 .createMethod(
                     SyntheticNaming.SyntheticKind.STATIC_INTERFACE_CALL,
                     methodProcessingContext.createUniqueContext(),
-                    factory,
+                    appView,
                     syntheticMethodBuilder ->
                         syntheticMethodBuilder
                             .setProto(invokedMethod.proto)
@@ -907,18 +908,18 @@
 
   private static void recordCompanionClassReference(
       AppView<?> appView, DexClassAndMethod method, DexMethod rewritten) {
+    ClasspathOrLibraryClass context = method.getHolder().asClasspathOrLibraryClass();
     // If the interface class is a program class, we shouldn't need to synthesize the companion
     // class on the classpath.
-    if (method.getHolder().isProgramClass()) {
+    if (context == null) {
       return;
     }
-
     appView
         .getSyntheticItems()
         .ensureDirectMethodOnSyntheticClasspathClassWhileMigrating(
             SyntheticKind.COMPANION_CLASS,
             rewritten.getHolderType(),
-            method.getHolder(),
+            context,
             appView,
             rewritten,
             builder ->
@@ -1149,7 +1150,7 @@
     if (consumer != null) {
       Origin dependencyOrigin = dependency.getOrigin();
       java.util.Collection<DexType> dependents =
-          appInfo.getSyntheticItems().getSynthesizingContexts(dependent.getType());
+          appInfo.getSyntheticItems().getSynthesizingContextTypes(dependent.getType());
       if (dependents.isEmpty()) {
         reportDependencyEdge(consumer, dependencyOrigin, dependent);
       } else {
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 667397b..ad1cacb 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
@@ -148,7 +148,7 @@
             .createClass(
                 SyntheticNaming.SyntheticKind.LAMBDA,
                 methodProcessingContext.createUniqueContext(),
-                appView.dexItemFactory(),
+                appView,
                 builder -> box.set(new LambdaClass(builder, appView, this, context, descriptor)));
     // Immediately set the actual program class on the lambda.
     LambdaClass lambdaClass = box.get();
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 a3bdc56..a7f094e 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
@@ -325,7 +325,7 @@
                       .createFixedClass(
                           SyntheticKind.INIT_TYPE_ARGUMENT,
                           method.asProgramMethod().getHolder(),
-                          dexItemFactory,
+                          appView,
                           builder -> {})
                       .getType();
                 } else {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrCloseResourceInstructionDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrCloseResourceInstructionDesugaring.java
index 30e7702..212259a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrCloseResourceInstructionDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/twr/TwrCloseResourceInstructionDesugaring.java
@@ -71,7 +71,7 @@
         .createMethod(
             SyntheticKind.TWR_CLOSE_RESOURCE,
             methodProcessingContext.createUniqueContext(),
-            appView.dexItemFactory(),
+            appView,
             methodBuilder ->
                 methodBuilder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
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 2f32c5c..f7a59dd 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
@@ -1367,7 +1367,7 @@
               .createMethod(
                   SyntheticKind.OUTLINE,
                   methodProcessingContext.createUniqueContext(),
-                  appView.dexItemFactory(),
+                  appView,
                   builder -> {
                     builder
                         .setAccessFlags(
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 d532b9b..9b9dc08 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
@@ -193,7 +193,7 @@
             .createMethod(
                 SyntheticKind.SERVICE_LOADER,
                 methodProcessingContext.createUniqueContext(),
-                appView.dexItemFactory(),
+                appView,
                 builder ->
                     builder
                         .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
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 687a9bf..953a78c 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
@@ -38,7 +38,7 @@
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.TO_STRING_IF_NOT_NULL,
             methodProcessingContext.createUniqueContext(),
-            dexItemFactory,
+            appView,
             builder ->
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
@@ -65,7 +65,7 @@
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.THROW_CCE_IF_NOT_NULL,
             positionContext,
-            dexItemFactory,
+            appView,
             builder ->
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
@@ -93,7 +93,7 @@
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.THROW_IAE,
             methodProcessingContext.createUniqueContext(),
-            dexItemFactory,
+            appView,
             builder ->
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
@@ -119,7 +119,7 @@
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.THROW_ICCE,
             methodProcessingContext.createUniqueContext(),
-            dexItemFactory,
+            appView,
             builder ->
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
@@ -147,7 +147,7 @@
         syntheticItems.createMethod(
             SyntheticNaming.SyntheticKind.THROW_NSME,
             methodProcessingContext.createUniqueContext(),
-            dexItemFactory,
+            appView,
             builder ->
                 builder
                     .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
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 96e263c..5be14a3 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexInfo.java
@@ -113,7 +113,7 @@
       return false;
     }
     DexType type = reference.getContextType();
-    for (DexType context : synthetics.getSynthesizingContexts(type)) {
+    for (DexType context : synthetics.getSynthesizingContextTypes(type)) {
       if (items.contains(context)) {
         return true;
       }
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 0330dca..aa87461 100644
--- a/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MissingClasses.java
@@ -191,7 +191,9 @@
 
         // Rewrite the synthetic context to its synthesizing contexts.
         Set<DexReference> synthesizingContextReferences =
-            appView.getSyntheticItems().getSynthesizingContexts(clazz, synthesizingContextOracle);
+            appView
+                .getSyntheticItems()
+                .getSynthesizingContextReferences(clazz, synthesizingContextOracle);
         assert synthesizingContextReferences != null;
         assert !synthesizingContextReferences.isEmpty();
         for (DexReference synthesizingContextReference : synthesizingContextReferences) {
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 04ec40a..6a611dc 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SynthesizingContext.java
@@ -6,8 +6,9 @@
 import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromDescriptor;
 import static com.android.tools.r8.utils.DescriptorUtils.getDescriptorFromClassBinaryName;
 
+import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.graph.AppView;
-import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLens.NonIdentityGraphLens;
@@ -37,37 +38,55 @@
   private final DexType inputContextType;
   private final Origin inputContextOrigin;
 
-  static SynthesizingContext fromNonSyntheticInputContext(DexClass context) {
+  private final FeatureSplit featureSplit;
+
+  static SynthesizingContext fromNonSyntheticInputContext(ClasspathOrLibraryClass context) {
     // A context that is itself non-synthetic is the single context, thus both the input context
     // and synthesizing context coincide.
     return new SynthesizingContext(
-        context.getContextType(), context.getContextType(), context.getOrigin());
+        context.getContextType(),
+        context.getContextType(),
+        context.getOrigin(),
+        // Synthesizing from a non-program context is just considered to be "base".
+        FeatureSplit.BASE);
   }
 
   static SynthesizingContext fromType(DexType type) {
-    return new SynthesizingContext(type, type, Origin.unknown());
+    // This method should only be used for synthesizing from a non-program context!
+    // Thus we have no origin info and place the context in the "base" feature.
+    return new SynthesizingContext(type, type, Origin.unknown(), FeatureSplit.BASE);
   }
 
-  static SynthesizingContext fromNonSyntheticInputContext(ProgramDefinition context) {
+  static SynthesizingContext fromNonSyntheticInputContext(
+      ProgramDefinition context, FeatureSplit featureSplit) {
     // A context that is itself non-synthetic is the single context, thus both the input context
     // and synthesizing context coincide.
     return new SynthesizingContext(
-        context.getContextType(), context.getContextType(), context.getOrigin());
+        context.getContextType(), context.getContextType(), context.getOrigin(), featureSplit);
   }
 
   static SynthesizingContext fromSyntheticInputClass(
-      DexProgramClass clazz, DexType synthesizingContextType) {
+      DexProgramClass clazz, DexType synthesizingContextType, AppView<?> appView) {
     assert synthesizingContextType != null;
     // A context that is itself synthetic must denote a synthesizing context from which to ensure
     // hygiene. This synthesizing context type is encoded on the synthetic for intermediate builds.
-    return new SynthesizingContext(synthesizingContextType, clazz.type, clazz.origin);
+    FeatureSplit featureSplit =
+        appView
+            .appInfoForDesugaring()
+            .getClassToFeatureSplitMap()
+            .getFeatureSplit(clazz, appView.getSyntheticItems());
+    return new SynthesizingContext(synthesizingContextType, clazz.type, clazz.origin, featureSplit);
   }
 
   private SynthesizingContext(
-      DexType synthesizingContextType, DexType inputContextType, Origin inputContextOrigin) {
+      DexType synthesizingContextType,
+      DexType inputContextType,
+      Origin inputContextOrigin,
+      FeatureSplit featureSplit) {
     this.synthesizingContextType = synthesizingContextType;
     this.inputContextType = inputContextType;
     this.inputContextOrigin = inputContextOrigin;
+    this.featureSplit = featureSplit;
   }
 
   @Override
@@ -81,10 +100,18 @@
         .compare(this, other);
   }
 
+  DexType getSynthesizingContextType() {
+    return synthesizingContextType;
+  }
+
   Origin getInputContextOrigin() {
     return inputContextOrigin;
   }
 
+  FeatureSplit getFeatureSplit() {
+    return featureSplit;
+  }
+
   SynthesizingContext rewrite(NonIdentityGraphLens lens) {
     DexType rewrittenInputeContextType = lens.lookupType(inputContextType);
     DexType rewrittenSynthesizingContextType = lens.lookupType(synthesizingContextType);
@@ -92,11 +119,10 @@
             && rewrittenSynthesizingContextType == synthesizingContextType
         ? this
         : new SynthesizingContext(
-            rewrittenSynthesizingContextType, rewrittenInputeContextType, inputContextOrigin);
-  }
-
-  DexType getSynthesizingContextType() {
-    return synthesizingContextType;
+            rewrittenSynthesizingContextType,
+            rewrittenInputeContextType,
+            inputContextOrigin,
+            featureSplit);
   }
 
   void registerPrefixRewriting(DexType hygienicType, AppView<?> appView) {
@@ -128,7 +154,10 @@
 
   @Override
   public String toString() {
-    return "SynthesizingContext{" + getSynthesizingContextType() + "}";
+    return "SynthesizingContext{"
+        + getSynthesizingContextType()
+        + (featureSplit != FeatureSplit.BASE ? ", feature:" + featureSplit : "")
+        + "}";
   }
 
   // TODO(b/181858113): Remove once deprecated main-dex-list is removed.
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 f6577d3..3c91d68 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticDefinition.java
@@ -81,13 +81,7 @@
       // single context.
       getContext().getSynthesizingContextType().hashWithTypeEquivalence(hasher, map);
     }
-    if (!classToFeatureSplitMap.isEmpty()) {
-      hasher.putInt(
-          classToFeatureSplitMap
-              .getFeatureSplit(getContext().getSynthesizingContextType(), syntheticItems)
-              .hashCode());
-    }
-
+    hasher.putInt(context.getFeatureSplit().hashCode());
     internalComputeHash(hasher, map);
     return hasher.hash();
   }
@@ -121,17 +115,12 @@
         return order;
       }
     }
-    if (!classToFeatureSplitMap.isEmpty()) {
-      DexType synthesizingContextType = this.getContext().getSynthesizingContextType();
-      DexType otherSynthesizingContextType = other.getContext().getSynthesizingContextType();
-      if (!classToFeatureSplitMap.isInSameFeatureOrBothInBase(
-          synthesizingContextType, otherSynthesizingContextType, syntheticItems)) {
-        int order =
-            classToFeatureSplitMap.compareFeatureSplitsForDexTypes(
-                synthesizingContextType, otherSynthesizingContextType, syntheticItems);
-        assert order != 0;
-        return order;
-      }
+    if (getContext().getFeatureSplit() != other.getContext().getFeatureSplit()) {
+      int order =
+          classToFeatureSplitMap.compareFeatureSplits(
+              context.getFeatureSplit(), other.getContext().getFeatureSplit());
+      assert order != 0;
+      return order;
     }
     RepresentativeMap map = null;
     // If the synthetics have been moved include the original types in the equivalence.
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 b90083a..6a539f0 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -3,9 +3,12 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import com.android.tools.r8.FeatureSplit;
 import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
+import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ClasspathOrLibraryClass;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClasspathClass;
@@ -123,8 +126,7 @@
         synthetics.committed.builderForSyntheticInputs();
     // TODO(b/158159959): Consider identifying synthetics in the input reader to speed this up.
     for (DexProgramClass clazz : appView.appInfo().classes()) {
-      SyntheticMarker marker =
-          SyntheticMarker.stripMarkerFromClass(clazz, appView.dexItemFactory());
+      SyntheticMarker marker = SyntheticMarker.stripMarkerFromClass(clazz, appView);
       if (marker.isSyntheticMethods()) {
         clazz.forEachProgramMethod(
             // TODO(b/158159959): Support having multiple methods per class.
@@ -260,7 +262,37 @@
     return isSyntheticClass(clazz.type);
   }
 
-  public Collection<DexType> getSynthesizingContexts(DexType type) {
+  public FeatureSplit getContextualFeatureSplit(DexType type) {
+    List<SynthesizingContext> contexts = getSynthesizingContexts(type);
+    if (contexts.isEmpty()) {
+      return null;
+    }
+    assert verifyAllContextsHaveSameFeature(contexts);
+    return contexts.get(0).getFeatureSplit();
+  }
+
+  private boolean verifyAllContextsHaveSameFeature(List<SynthesizingContext> contexts) {
+    assert !contexts.isEmpty();
+    FeatureSplit featureSplit = contexts.get(0).getFeatureSplit();
+    for (int i = 1; i < contexts.size(); i++) {
+      assert featureSplit == contexts.get(i).getFeatureSplit();
+    }
+    return true;
+  }
+
+  private List<SynthesizingContext> getSynthesizingContexts(DexType type) {
+    SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(type);
+    if (reference != null) {
+      return Collections.singletonList(reference.getContext());
+    }
+    SyntheticDefinition<?, ?, ?> definition = pending.nonLegacyDefinitions.get(type);
+    if (definition != null) {
+      return Collections.singletonList(definition.getContext());
+    }
+    return Collections.emptyList();
+  }
+
+  public Collection<DexType> getSynthesizingContextTypes(DexType type) {
     SyntheticReference<?, ?, ?> reference = committed.getNonLegacyItem(type);
     if (reference != null) {
       return Collections.singletonList(reference.getContext().getSynthesizingContextType());
@@ -281,7 +313,7 @@
   }
 
   // TODO(b/180091213): Implement this and remove client provided the oracle.
-  public Set<DexReference> getSynthesizingContexts(
+  public Set<DexReference> getSynthesizingContextReferences(
       DexProgramClass clazz, SynthesizingContextOracle oracle) {
     assert isSyntheticClass(clazz);
     return oracle.getSynthesizingContexts(clazz);
@@ -317,7 +349,14 @@
     return ListUtils.map(pending.legacyClasses.values(), LegacySyntheticDefinition::getDefinition);
   }
 
-  private SynthesizingContext getSynthesizingContext(ProgramDefinition context) {
+  private SynthesizingContext getSynthesizingContext(
+      ProgramDefinition context, AppView<?> appView) {
+    return getSynthesizingContext(
+        context, appView.appInfoForDesugaring().getClassToFeatureSplitMap());
+  }
+
+  private SynthesizingContext getSynthesizingContext(
+      ProgramDefinition context, ClassToFeatureSplitMap featureSplits) {
     DexType contextType = context.getContextType();
     SyntheticDefinition<?, ?, ?> existingDefinition = pending.nonLegacyDefinitions.get(contextType);
     if (existingDefinition != null) {
@@ -328,7 +367,8 @@
       return existingReference.getContext();
     }
     // This context is not nested in an existing synthetic context so create a new "leaf" context.
-    return SynthesizingContext.fromNonSyntheticInputContext(context);
+    FeatureSplit featureSplit = featureSplits.getFeatureSplit(context, this);
+    return SynthesizingContext.fromNonSyntheticInputContext(context, featureSplit);
   }
 
   // Addition and creation of synthetic items.
@@ -358,16 +398,16 @@
   public DexProgramClass createClass(
       SyntheticKind kind,
       UniqueContext context,
-      DexItemFactory factory,
+      AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn) {
     // 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());
+    SynthesizingContext outerContext = getSynthesizingContext(context.getClassContext(), appView);
     DexType type =
         SyntheticNaming.createInternalType(
-            kind, outerContext, context.getSyntheticSuffix(), factory);
+            kind, outerContext, context.getSyntheticSuffix(), appView.dexItemFactory());
     SyntheticProgramClassBuilder classBuilder =
-        new SyntheticProgramClassBuilder(type, outerContext, factory);
+        new SyntheticProgramClassBuilder(type, outerContext, appView.dexItemFactory());
     fn.accept(classBuilder);
     DexProgramClass clazz = classBuilder.build();
     addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
@@ -378,14 +418,14 @@
   public DexProgramClass createFixedClass(
       SyntheticKind kind,
       DexProgramClass context,
-      DexItemFactory factory,
+      AppView<?> appView,
       Consumer<SyntheticProgramClassBuilder> fn) {
     // 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);
-    DexType type = SyntheticNaming.createFixedType(kind, outerContext, factory);
+    SynthesizingContext outerContext = getSynthesizingContext(context, appView);
+    DexType type = SyntheticNaming.createFixedType(kind, outerContext, appView.dexItemFactory());
     SyntheticProgramClassBuilder classBuilder =
-        new SyntheticProgramClassBuilder(type, outerContext, factory);
+        new SyntheticProgramClassBuilder(type, outerContext, appView.dexItemFactory());
     fn.accept(classBuilder);
     DexProgramClass clazz = classBuilder.build();
     addPendingDefinition(new SyntheticProgramClassDefinition(kind, outerContext, clazz));
@@ -409,7 +449,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);
+      SynthesizingContext outerContext = getSynthesizingContext(context, appView);
       SyntheticProgramClassBuilder classBuilder =
           new SyntheticProgramClassBuilder(legacyType, outerContext, appView.dexItemFactory());
       fn.accept(classBuilder);
@@ -436,7 +476,7 @@
   // like a hygienic synthetic, but use the legacyType passed as parameter instead of the
   // hygienic type.
   private DexClasspathClass ensureFixedClasspathClassWhileMigrating(
-      SyntheticKind kind, DexType legacyType, DexClass context, AppView<?> appView) {
+      SyntheticKind kind, DexType legacyType, ClasspathOrLibraryClass context, AppView<?> appView) {
     synchronized (context) {
       DexClass dexClass = appView.definitionFor(legacyType);
       if (dexClass != null) {
@@ -460,7 +500,7 @@
   public void ensureDirectMethodOnSyntheticClasspathClassWhileMigrating(
       SyntheticKind kind,
       DexType legacyType,
-      DexClass context,
+      ClasspathOrLibraryClass context,
       AppView<?> appView,
       DexMethod method,
       Consumer<SyntheticMethodBuilder> builderConsumer) {
@@ -498,25 +538,26 @@
   public ProgramMethod createMethod(
       SyntheticKind kind,
       UniqueContext context,
-      DexItemFactory factory,
+      AppView<?> appView,
       Consumer<SyntheticMethodBuilder> fn) {
-    return createMethod(kind, context.getClassContext(), factory, fn, context::getSyntheticSuffix);
+    return createMethod(kind, context.getClassContext(), appView, fn, context::getSyntheticSuffix);
   }
 
   private ProgramMethod createMethod(
       SyntheticKind kind,
       ProgramDefinition context,
-      DexItemFactory factory,
+      AppView<?> appView,
       Consumer<SyntheticMethodBuilder> fn,
       Supplier<String> syntheticIdSupplier) {
     assert nextSyntheticId != INVALID_ID_AFTER_SYNTHETIC_FINALIZATION;
     // 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);
+    SynthesizingContext outerContext = getSynthesizingContext(context, appView);
     DexType type =
-        SyntheticNaming.createInternalType(kind, outerContext, syntheticIdSupplier.get(), factory);
+        SyntheticNaming.createInternalType(
+            kind, outerContext, syntheticIdSupplier.get(), appView.dexItemFactory());
     SyntheticProgramClassBuilder classBuilder =
-        new SyntheticProgramClassBuilder(type, outerContext, factory);
+        new SyntheticProgramClassBuilder(type, outerContext, appView.dexItemFactory());
     DexProgramClass clazz =
         classBuilder
             .addMethod(fn.andThen(m -> m.setName(SyntheticNaming.INTERNAL_SYNTHETIC_METHOD_PREFIX)))
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 03b7e36..d68558d 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticMarker.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.synthesis;
 
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.ClassAccessFlags;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -28,26 +29,27 @@
                     kind, context.getSynthesizingContextType(), factory)));
   }
 
-  public static SyntheticMarker stripMarkerFromClass(
-      DexProgramClass clazz, DexItemFactory factory) {
-    SyntheticMarker marker = internalStripMarkerFromClass(clazz, factory);
+  public static SyntheticMarker stripMarkerFromClass(DexProgramClass clazz, AppView<?> appView) {
+    SyntheticMarker marker = internalStripMarkerFromClass(clazz, appView);
     assert marker != NO_MARKER
-        || DexAnnotation.getSynthesizedClassAnnotationContextType(clazz.annotations(), factory)
+        || DexAnnotation.getSynthesizedClassAnnotationContextType(
+                clazz.annotations(), appView.dexItemFactory())
             == null;
     return marker;
   }
 
   private static SyntheticMarker internalStripMarkerFromClass(
-      DexProgramClass clazz, DexItemFactory factory) {
+      DexProgramClass clazz, AppView<?> appView) {
     ClassAccessFlags flags = clazz.accessFlags;
-    if (clazz.superType != factory.objectType) {
+    if (clazz.superType != appView.dexItemFactory().objectType) {
       return NO_MARKER;
     }
     if (!flags.isSynthetic() || flags.isAbstract() || flags.isEnum()) {
       return NO_MARKER;
     }
     Pair<SyntheticKind, DexType> info =
-        DexAnnotation.getSynthesizedClassAnnotationContextType(clazz.annotations(), factory);
+        DexAnnotation.getSynthesizedClassAnnotationContextType(
+            clazz.annotations(), appView.dexItemFactory());
     if (info == null) {
       return NO_MARKER;
     }
@@ -65,7 +67,8 @@
       }
     }
     clazz.setAnnotations(DexAnnotationSet.empty());
-    return new SyntheticMarker(kind, SynthesizingContext.fromSyntheticInputClass(clazz, context));
+    return new SyntheticMarker(
+        kind, SynthesizingContext.fromSyntheticInputClass(clazz, context, appView));
   }
 
   private static final SyntheticMarker NO_MARKER = new SyntheticMarker(null, null);