Use startup list in determination of when to return startup base

Change-Id: I1b1423bb997a8d9e21c361ea79f896b335826ea9
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 839545d..cb15cb2 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.debuginfo.DebugRepresentation;
 import com.android.tools.r8.errors.DexFileOverflowDiagnostic;
 import com.android.tools.r8.errors.InternalCompilerError;
-import com.android.tools.r8.experimental.startup.StartupClass;
 import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
@@ -47,7 +46,6 @@
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
@@ -947,17 +945,7 @@
         }
         StartupOrder startupOrder = appView.appInfoWithClassHierarchy().getStartupOrder();
         SyntheticItems syntheticItems = appView.getSyntheticItems();
-        return clazz -> {
-          if (syntheticItems.isSyntheticClass(clazz)) {
-            return Iterables.any(
-                syntheticItems.getSynthesizingContextTypes(clazz.getType()),
-                startupOrder::containsSyntheticClassesSynthesizedFrom);
-          } else {
-            StartupClass<DexType> startupClass =
-                StartupClass.<DexType>builder().setReference(clazz.getType()).build();
-            return startupOrder.contains(startupClass);
-          }
-        };
+        return clazz -> startupOrder.contains(clazz.getType(), syntheticItems);
       }
 
       public List<DexProgramClass> getStartupClasses() {
diff --git a/src/main/java/com/android/tools/r8/dump/DumpOptions.java b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
index a604608..da85b0e 100644
--- a/src/main/java/com/android/tools/r8/dump/DumpOptions.java
+++ b/src/main/java/com/android/tools/r8/dump/DumpOptions.java
@@ -6,7 +6,6 @@
 
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.dex.Marker.Tool;
-import com.android.tools.r8.experimental.startup.StartupConfiguration;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
 import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibrarySpecification;
 import com.android.tools.r8.shaking.ProguardConfiguration;
@@ -243,11 +242,6 @@
     return featureSplitConfiguration;
   }
 
-  public StartupConfiguration getStartupConfiguration() {
-    // The startup configuration is not included in dumps.
-    return null;
-  }
-
   public String getParsedProguardConfiguration() {
     return proguardConfiguration == null ? null : proguardConfiguration.getParsedConfiguration();
   }
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
index 00f0db2..3566ad1 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/EmptyStartupOrder.java
@@ -17,12 +17,7 @@
   EmptyStartupOrder() {}
 
   @Override
-  public boolean contains(StartupClass<DexType> startupClass) {
-    return false;
-  }
-
-  @Override
-  public boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType) {
+  public boolean contains(DexType type, SyntheticItems syntheticItems) {
     return false;
   }
 
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
index 1b8eab9..673e0de 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/NonEmptyStartupOrder.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.synthesis.SyntheticItems;
 import com.android.tools.r8.utils.LazyBox;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -26,23 +27,42 @@
 
   private final LinkedHashSet<StartupClass<DexType>> startupClasses;
 
+  // Redundant sets to allow efficient querying without boxing.
+  private final Set<DexType> nonSyntheticStartupClasses = Sets.newIdentityHashSet();
+  private final Set<DexType> syntheticStartupClasses = Sets.newIdentityHashSet();
+
   NonEmptyStartupOrder(LinkedHashSet<StartupClass<DexType>> startupClasses) {
     assert !startupClasses.isEmpty();
     this.startupClasses = startupClasses;
+    for (StartupClass<DexType> startupClass : startupClasses) {
+      if (startupClass.isSynthetic()) {
+        syntheticStartupClasses.add(startupClass.getReference());
+      } else {
+        nonSyntheticStartupClasses.add(startupClass.getReference());
+      }
+    }
   }
 
   @Override
-  public boolean contains(StartupClass<DexType> startupClass) {
-    return startupClasses.contains(startupClass);
+  public boolean contains(DexType type, SyntheticItems syntheticItems) {
+    return syntheticItems.isSyntheticClass(type)
+        ? containsSyntheticClass(type, syntheticItems)
+        : containsNonSyntheticClass(type);
   }
 
-  @Override
-  public boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType) {
-    return contains(
-        StartupClass.<DexType>builder()
-            .setReference(synthesizingContextType)
-            .setSynthetic()
-            .build());
+  private boolean containsNonSyntheticClass(DexType type) {
+    return nonSyntheticStartupClasses.contains(type);
+  }
+
+  private boolean containsSyntheticClass(DexType type, SyntheticItems syntheticItems) {
+    assert syntheticItems.isSyntheticClass(type);
+    return Iterables.any(
+        syntheticItems.getSynthesizingContextTypes(type),
+        this::containsSyntheticClassesSynthesizedFrom);
+  }
+
+  private boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType) {
+    return syntheticStartupClasses.contains(synthesizingContextType);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
index 78126fd..5d67d8c 100644
--- a/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
+++ b/src/main/java/com/android/tools/r8/experimental/startup/StartupOrder.java
@@ -33,9 +33,7 @@
     return new EmptyStartupOrder();
   }
 
-  public abstract boolean contains(StartupClass<DexType> startupClass);
-
-  public abstract boolean containsSyntheticClassesSynthesizedFrom(DexType synthesizingContextType);
+  public abstract boolean contains(DexType type, SyntheticItems syntheticItems);
 
   public abstract Collection<StartupClass<DexType>> getClasses();
 
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 b721f6f..2113b52 100644
--- a/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
+++ b/src/main/java/com/android/tools/r8/features/ClassToFeatureSplitMap.java
@@ -8,8 +8,6 @@
 import com.android.tools.r8.ProgramResource;
 import com.android.tools.r8.ProgramResourceProvider;
 import com.android.tools.r8.ResourceException;
-import com.android.tools.r8.experimental.startup.StartupClass;
-import com.android.tools.r8.experimental.startup.StartupConfiguration;
 import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
@@ -31,21 +29,18 @@
 
 public class ClassToFeatureSplitMap {
 
-  private final FeatureSplit baseStartup;
   private final Map<DexType, FeatureSplit> classToFeatureSplitMap;
   private final Map<FeatureSplit, String> representativeStringsForFeatureSplit;
 
   private ClassToFeatureSplitMap(
-      FeatureSplit baseStartup,
       Map<DexType, FeatureSplit> classToFeatureSplitMap,
       Map<FeatureSplit, String> representativeStringsForFeatureSplit) {
-    this.baseStartup = baseStartup;
     this.classToFeatureSplitMap = classToFeatureSplitMap;
     this.representativeStringsForFeatureSplit = representativeStringsForFeatureSplit;
   }
 
   public static ClassToFeatureSplitMap createEmptyClassToFeatureSplitMap() {
-    return new ClassToFeatureSplitMap(FeatureSplit.BASE, new IdentityHashMap<>(), null);
+    return new ClassToFeatureSplitMap(new IdentityHashMap<>(), null);
   }
 
   public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap(
@@ -53,73 +48,42 @@
     return createInitialClassToFeatureSplitMap(
         options.dexItemFactory(),
         options.featureSplitConfiguration,
-        options.getStartupOptions().getStartupConfiguration(),
         options.reporter);
   }
 
   public static ClassToFeatureSplitMap createInitialClassToFeatureSplitMap(
       DexItemFactory dexItemFactory,
       FeatureSplitConfiguration featureSplitConfiguration,
-      StartupConfiguration startupConfiguration,
       Reporter reporter) {
-    if (featureSplitConfiguration == null && startupConfiguration == null) {
+    if (featureSplitConfiguration == null) {
       return createEmptyClassToFeatureSplitMap();
     }
 
     Map<DexType, FeatureSplit> classToFeatureSplitMap = new IdentityHashMap<>();
     Map<FeatureSplit, String> representativeStringsForFeatureSplit = new IdentityHashMap<>();
-    if (featureSplitConfiguration != null) {
-      for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
-        String representativeType = null;
-        for (ProgramResourceProvider programResourceProvider :
-            featureSplit.getProgramResourceProviders()) {
-          try {
-            for (ProgramResource programResource : programResourceProvider.getProgramResources()) {
-              for (String classDescriptor : programResource.getClassDescriptors()) {
-                DexType type = dexItemFactory.createType(classDescriptor);
-                classToFeatureSplitMap.put(type, featureSplit);
-                if (representativeType == null
-                    || classDescriptor.compareTo(representativeType) > 0) {
-                  representativeType = classDescriptor;
-                }
+    for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
+      String representativeType = null;
+      for (ProgramResourceProvider programResourceProvider :
+          featureSplit.getProgramResourceProviders()) {
+        try {
+          for (ProgramResource programResource : programResourceProvider.getProgramResources()) {
+            for (String classDescriptor : programResource.getClassDescriptors()) {
+              DexType type = dexItemFactory.createType(classDescriptor);
+              classToFeatureSplitMap.put(type, featureSplit);
+              if (representativeType == null || classDescriptor.compareTo(representativeType) > 0) {
+                representativeType = classDescriptor;
               }
             }
-          } catch (ResourceException e) {
-            throw reporter.fatalError(e.getMessage());
           }
-        }
-        if (representativeType != null) {
-          representativeStringsForFeatureSplit.put(featureSplit, representativeType);
+        } catch (ResourceException e) {
+          throw reporter.fatalError(e.getMessage());
         }
       }
-    }
-
-    FeatureSplit baseStartup;
-    if (startupConfiguration != null && startupConfiguration.hasStartupClasses()) {
-      StartupClass<DexType> representativeStartupClass = null;
-      for (StartupClass<DexType> startupClass : startupConfiguration.getStartupClasses()) {
-        if (startupClass.isSynthetic()
-            || classToFeatureSplitMap.containsKey(startupClass.getReference())) {
-          continue;
-        }
-        classToFeatureSplitMap.put(startupClass.getReference(), FeatureSplit.BASE_STARTUP);
-        if (representativeStartupClass == null
-            || startupClass
-                    .getReference()
-                    .getDescriptor()
-                    .compareTo(representativeStartupClass.getReference().getDescriptor())
-                > 0) {
-          representativeStartupClass = startupClass;
-        }
+      if (representativeType != null) {
+        representativeStringsForFeatureSplit.put(featureSplit, representativeType);
       }
-      baseStartup = FeatureSplit.BASE_STARTUP;
-      representativeStringsForFeatureSplit.put(
-          baseStartup, representativeStartupClass.getReference().toDescriptorString());
-    } else {
-      baseStartup = FeatureSplit.BASE;
     }
-    return new ClassToFeatureSplitMap(
-        baseStartup, classToFeatureSplitMap, representativeStringsForFeatureSplit);
+    return new ClassToFeatureSplitMap(classToFeatureSplitMap, representativeStringsForFeatureSplit);
   }
 
   public int compareFeatureSplits(FeatureSplit featureSplitA, FeatureSplit featureSplitB) {
@@ -140,14 +104,6 @@
         .compareTo(representativeStringsForFeatureSplit.get(featureSplitB));
   }
 
-  /**
-   * Returns the base startup if there are any startup classes given on input. Otherwise returns
-   * base.
-   */
-  public FeatureSplit getBaseStartup() {
-    return baseStartup;
-  }
-
   public Map<FeatureSplit, Set<DexProgramClass>> getFeatureSplitClasses(
       Set<DexProgramClass> classes, AppView<? extends AppInfoWithClassHierarchy> appView) {
     return getFeatureSplitClasses(
@@ -184,23 +140,32 @@
 
   public FeatureSplit getFeatureSplit(
       DexType type, StartupOrder startupOrder, SyntheticItems syntheticItems) {
-    FeatureSplit feature = classToFeatureSplitMap.get(type);
-    if (feature != null) {
-      assert !syntheticItems.isSyntheticClass(type);
-      return feature;
+    if (syntheticItems == null) {
+      // Called from AndroidApp.dumpProgramResources().
+      assert startupOrder.isEmpty();
+      return classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
     }
-    if (syntheticItems != null) {
-      feature = syntheticItems.getContextualFeatureSplit(type, this);
-      if (feature != null && !feature.isBase()) {
-        return feature;
+    FeatureSplit feature;
+    boolean isSynthetic = syntheticItems.isSyntheticClass(type);
+    if (isSynthetic) {
+      assert !classToFeatureSplitMap.containsKey(type);
+      if (syntheticItems.isSyntheticOfKind(type, k -> k.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
+        // Use the startup base if there is one, such that we don't merge non-startup classes with
+        // the shared utility class in case it is used during startup. The use of base startup
+        // allows for merging startup classes with the shared utility class, however, which could be
+        // bad for startup if the shared utility class is not used during startup.
+        return startupOrder.isEmpty() ? FeatureSplit.BASE : FeatureSplit.BASE_STARTUP;
       }
-      for (DexType context : syntheticItems.getSynthesizingContextTypes(type)) {
-        if (startupOrder.containsSyntheticClassesSynthesizedFrom(context)) {
-          return FeatureSplit.BASE_STARTUP;
-        }
-      }
+      feature = syntheticItems.getContextualFeatureSplitOrDefault(type, FeatureSplit.BASE);
+    } else {
+      feature = classToFeatureSplitMap.getOrDefault(type, FeatureSplit.BASE);
     }
-    return FeatureSplit.BASE;
+    if (feature.isBase()) {
+      return startupOrder.contains(type, syntheticItems)
+          ? FeatureSplit.BASE_STARTUP
+          : FeatureSplit.BASE;
+    }
+    return feature;
   }
 
   // Note, this predicate may be misleading as the map does not include synthetics.
@@ -299,7 +264,7 @@
           assert existing == null || existing == featureSplit;
         });
     return new ClassToFeatureSplitMap(
-        baseStartup, rewrittenClassToFeatureSplitMap, representativeStringsForFeatureSplit);
+        rewrittenClassToFeatureSplitMap, representativeStringsForFeatureSplit);
   }
 
   public ClassToFeatureSplitMap withoutPrunedItems(PrunedItems prunedItems) {
@@ -311,7 +276,7 @@
           }
         });
     return new ClassToFeatureSplitMap(
-        baseStartup, rewrittenClassToFeatureSplitMap, representativeStringsForFeatureSplit);
+        rewrittenClassToFeatureSplitMap, representativeStringsForFeatureSplit);
   }
 
   // Static helpers to avoid verbose predicates.
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 2c2f503..08d9d27 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticItems.java
@@ -433,18 +433,16 @@
     return committed.containsSyntheticInput(clazz.getType());
   }
 
-  public FeatureSplit getContextualFeatureSplit(
-      DexType type, ClassToFeatureSplitMap classToFeatureSplitMap) {
+  public FeatureSplit getContextualFeatureSplitOrDefault(DexType type, FeatureSplit defaultValue) {
+    assert isSyntheticClass(type);
     if (isSyntheticOfKind(type, kinds -> kinds.ENUM_UNBOXING_SHARED_UTILITY_CLASS)) {
-      // Use the startup base if there is one, such that we don't merge non-startup classes with the
-      // shared utility class in case it is used during startup. The use of base startup allows for
-      // merging startup classes with the shared utility class, however, which could be bad for
-      // startup if the shared utility class is not used during startup.
-      return classToFeatureSplitMap.getBaseStartup();
+      return FeatureSplit.BASE;
     }
     List<SynthesizingContext> contexts = getSynthesizingContexts(type);
     if (contexts.isEmpty()) {
-      return null;
+      assert false
+          : "Expected synthetic to have at least one synthesizing context: " + type.getTypeName();
+      return defaultValue;
     }
     assert verifyAllHaveSameFeature(contexts, SynthesizingContext::getFeatureSplit);
     return contexts.get(0).getFeatureSplit();
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 40c5a67..8060f36 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -34,7 +34,6 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.experimental.startup.StartupConfiguration;
 import com.android.tools.r8.experimental.startup.StartupOrder;
 import com.android.tools.r8.features.ClassToFeatureSplitMap;
 import com.android.tools.r8.features.FeatureSplitConfiguration;
@@ -530,7 +529,6 @@
           dumpProgramResources(
               dumpProgramFileName,
               options.getFeatureSplitConfiguration(),
-              options.getStartupConfiguration(),
               nextDexIndex,
               out,
               reporter,
@@ -584,7 +582,6 @@
   private int dumpProgramResources(
       String archiveName,
       FeatureSplitConfiguration featureSplitConfiguration,
-      StartupConfiguration startupConfiguration,
       int nextDexIndex,
       ZipOutputStream out,
       Reporter reporter,
@@ -599,7 +596,7 @@
     try {
       ClassToFeatureSplitMap classToFeatureSplitMap =
           ClassToFeatureSplitMap.createInitialClassToFeatureSplitMap(
-              dexItemFactory, featureSplitConfiguration, startupConfiguration, reporter);
+              dexItemFactory, featureSplitConfiguration, reporter);
       if (featureSplitConfiguration != null) {
         for (FeatureSplit featureSplit : featureSplitConfiguration.getFeatureSplits()) {
           ByteArrayOutputStream archiveByteStream = new ByteArrayOutputStream();