Make virtual files for distribution explicit in cycler

Change-Id: Ie97a86d79598a9443b8a2f2fb20bdcf7b0648ff3
diff --git a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
index 92671f2..9978311 100644
--- a/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
+++ b/src/main/java/com/android/tools/r8/dex/InheritanceClassInDexDistributor.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Maps;
 import java.util.ArrayList;
@@ -279,26 +280,29 @@
   }
 
   private final VirtualFile mainDex;
-  private final List<VirtualFile> dexes;
+  private final List<VirtualFile> files;
+  private final List<VirtualFile> filesForDistribution;
   private final BitSet fullDex = new BitSet();
   private final Set<DexProgramClass> classes;
   private final AppView<?> appView;
-  private final int dexIndexOffset;
+  private final IntBox nextFileId;
   private final NamingLens namingLens;
   private final DirectSubClassesInfo directSubClasses;
 
   public InheritanceClassInDexDistributor(
       VirtualFile mainDex,
-      List<VirtualFile> dexes,
+      List<VirtualFile> files,
+      List<VirtualFile> filesForDistribution,
       Set<DexProgramClass> classes,
-      int dexIndexOffset,
+      IntBox nextFileId,
       NamingLens namingLens,
       AppView<?> appView,
       ExecutorService executorService) {
     this.mainDex = mainDex;
-    this.dexes = dexes;
+    this.files = files;
+    this.filesForDistribution = filesForDistribution;
     this.classes = classes;
-    this.dexIndexOffset = dexIndexOffset;
+    this.nextFileId = nextFileId;
     this.namingLens = namingLens;
     this.appView = appView;
     this.executorService = executorService;
@@ -315,6 +319,8 @@
 
     // Allocate member of groups depending on
     // the main dex members
+    VirtualFileCycler cycler =
+        new VirtualFileCycler(files, filesForDistribution, appView, namingLens, nextFileId);
     for (Iterator<ClassGroup> iter = remainingInheritanceGroups.iterator(); iter.hasNext();) {
       ClassGroup group = iter.next();
       if (group.dependsOnMainDexClasses) {
@@ -344,19 +350,19 @@
             new ClassGroup(groupSplit.mainDexIndependents);
 
         Collection<VirtualFile> mainDexInpendentsDexes =
-            assignGroup(mainDexIndependentGroup, Collections.singletonList(mainDex));
+            assignGroup(mainDexIndependentGroup, cycler, Collections.singletonList(mainDex));
 
         Set<DexProgramClass> classesWithLinkingError =
             new HashSet<>(groupSplit.dependentsOfMainDexIndependents);
         classesWithLinkingError.addAll(classesMissingMainDex);
-        assignClassesWithLinkingError(classesWithLinkingError, mainDexInpendentsDexes);
+        assignClassesWithLinkingError(classesWithLinkingError, cycler, mainDexInpendentsDexes);
       }
     }
 
     // Allocate member of groups independents from the main dex members
     for (ClassGroup group : remainingInheritanceGroups) {
       if (!group.dependsOnMainDexClasses) {
-        assignGroup(group, Collections.emptyList());
+        assignGroup(group, cycler, Collections.emptyList());
       }
     }
   }
@@ -369,11 +375,13 @@
     return groupClassNumber;
   }
 
-  private Collection<VirtualFile> assignGroup(ClassGroup group, List<VirtualFile> exclude) {
-    VirtualFileCycler cycler = new VirtualFileCycler(dexes, appView, namingLens, dexIndexOffset);
+  private Collection<VirtualFile> assignGroup(
+      ClassGroup group, VirtualFileCycler cycler, List<VirtualFile> exclude) {
     if (group.members.isEmpty()) {
       return Collections.emptyList();
-    } else if (group.canFitInOneDex()) {
+    }
+    cycler.reset();
+    if (group.canFitInOneDex()) {
       VirtualFile currentDex;
       while (true) {
         currentDex = cycler.nextOrCreate(dex -> !exclude.contains(dex) && !isDexFull(dex));
@@ -399,7 +407,8 @@
       Collection<VirtualFile> newExclude = new HashSet<>(exclude);
       newExclude.add(dexForLinkingClasses);
 
-      Collection<VirtualFile> usedDex = assignClassesWithLinkingError(remaining, newExclude);
+      Collection<VirtualFile> usedDex =
+          assignClassesWithLinkingError(remaining, cycler, newExclude);
       usedDex.add(dexForLinkingClasses);
       return usedDex;
     }
@@ -412,14 +421,11 @@
    * @param classes set of classes to assign, the set will be destroyed during assignment.
    */
   private Collection<VirtualFile> assignClassesWithLinkingError(
-      Set<DexProgramClass> classes, Collection<VirtualFile> exclude) {
-
+      Set<DexProgramClass> classes, VirtualFileCycler cycler, Collection<VirtualFile> exclude) {
     List<ClassGroup> layers = collectNoDirectInheritanceGroups(classes);
-
     Collections.sort(layers);
 
     Collection<VirtualFile> usedDex = new ArrayList<>();
-    VirtualFileCycler cycler = new VirtualFileCycler(dexes, appView, namingLens, dexIndexOffset);
     // Don't modify exclude. Think about modifying the input collection considering this
     // is private API.
     Set<VirtualFile> currentExclude = new HashSet<>(exclude);
@@ -456,10 +462,8 @@
             dexForLayer.commitTransaction();
             break;
           }
-
         }
       }
-
     }
 
     return usedDex;
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 7f39bfc..f0a460f 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -34,10 +34,12 @@
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.IntBox;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SetUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
@@ -46,6 +48,7 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -320,11 +323,7 @@
         if (!combineSyntheticClassesWithPrimaryClass
             || !appView.getSyntheticItems().isSyntheticClass(clazz)) {
           VirtualFile file =
-              new VirtualFile(
-                  virtualFiles.size(),
-                  writer.appView,
-                  writer.namingLens,
-                  clazz);
+              new VirtualFile(virtualFiles.size(), appView, writer.namingLens, clazz);
           virtualFiles.add(file);
           file.addClass(clazz);
           files.put(clazz, file);
@@ -362,7 +361,7 @@
       this.classes = SetUtils.newIdentityHashSet(classes);
 
       // Create the primary dex file. The distribution will add more if needed.
-      mainDexFile = new VirtualFile(0, writer.appView, writer.namingLens);
+      mainDexFile = new VirtualFile(0, appView, writer.namingLens);
       assert virtualFiles.isEmpty();
       virtualFiles.add(mainDexFile);
       addMarkers(mainDexFile);
@@ -435,25 +434,27 @@
       if (featureSplitClasses.isEmpty()) {
         return;
       }
-      List<VirtualFile> filesForDistribution;
       for (Map.Entry<FeatureSplit, Set<DexProgramClass>> featureSplitSetEntry :
           featureSplitClasses.entrySet()) {
         // Add a new virtual file, start from index 0 again
+        IntBox nextFileId = new IntBox();
         VirtualFile featureFile =
             new VirtualFile(
-                0,
-                writer.appView,
+                nextFileId.getAndIncrement(),
+                appView,
                 writer.namingLens,
                 featureSplitSetEntry.getKey());
         virtualFiles.add(featureFile);
         addMarkers(featureFile);
-        filesForDistribution = virtualFiles.subList(virtualFiles.size() - 1, virtualFiles.size());
+        List<VirtualFile> files = virtualFiles;
+        List<VirtualFile> filesForDistribution = ImmutableList.of(featureFile);
         new PackageSplitPopulator(
+                files,
                 filesForDistribution,
                 appView,
                 featureSplitSetEntry.getValue(),
                 originalNames,
-                0,
+                nextFileId,
                 writer.namingLens)
             .run();
       }
@@ -474,6 +475,9 @@
 
     @Override
     public List<VirtualFile> run() throws IOException {
+      assert virtualFiles.size() == 1;
+      assert virtualFiles.get(0).isEmpty();
+
       int totalClassNumber = classes.size();
       // First fill required classes into the main dex file.
       fillForMainDexList(classes);
@@ -483,37 +487,37 @@
       }
 
       List<VirtualFile> filesForDistribution = virtualFiles;
-      int fileIndexOffset = 0;
       boolean multidexLegacy = !mainDexFile.isEmpty();
       if (options.minimalMainDex && multidexLegacy) {
-        assert !virtualFiles.get(0).isEmpty();
         assert virtualFiles.size() == 1;
-        // The main dex file is filtered out, so ensure at least one file for the remaining classes.
-        virtualFiles.add(new VirtualFile(1, writer.appView, writer.namingLens));
-        filesForDistribution = virtualFiles.subList(1, virtualFiles.size());
-        fileIndexOffset = 1;
+        assert !virtualFiles.get(0).isEmpty();
+        // Don't consider the main dex for distribution.
+        filesForDistribution = Collections.emptyList();
       }
 
       Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses =
           removeFeatureSplitClassesGetMapping();
 
+      IntBox nextFileId = new IntBox(1);
       if (multidexLegacy && options.enableInheritanceClassInDexDistributor) {
         new InheritanceClassInDexDistributor(
                 mainDexFile,
+                virtualFiles,
                 filesForDistribution,
                 classes,
-                fileIndexOffset,
+                nextFileId,
                 writer.namingLens,
-                writer.appView,
+                appView,
                 executorService)
             .distribute();
       } else {
         new PackageSplitPopulator(
+                virtualFiles,
                 filesForDistribution,
                 appView,
                 classes,
                 originalNames,
-                fileIndexOffset,
+                nextFileId,
                 writer.namingLens)
             .run();
       }
@@ -820,33 +824,36 @@
   static class VirtualFileCycler {
 
     private final List<VirtualFile> files;
+    private final List<VirtualFile> filesForDistribution;
     private final AppView<?> appView;
     private final NamingLens namingLens;
 
-    private int nextFileId;
+    private final IntBox nextFileId;
     private Iterator<VirtualFile> allFilesCyclic;
     private Iterator<VirtualFile> activeFiles;
-    private FeatureSplit featuresplit;
+    private FeatureSplit featureSplit;
 
     VirtualFileCycler(
         List<VirtualFile> files,
+        List<VirtualFile> filesForDistribution,
         AppView<?> appView,
         NamingLens namingLens,
-        int fileIndexOffset) {
+        IntBox nextFileId) {
       this.files = files;
+      this.filesForDistribution = new ArrayList<>(filesForDistribution);
       this.appView = appView;
       this.namingLens = namingLens;
+      this.nextFileId = nextFileId;
 
-      nextFileId = files.size() + fileIndexOffset;
-      if (files.size() > 0) {
-        featuresplit = files.get(0).getFeatureSplit();
+      if (filesForDistribution.size() > 0) {
+        featureSplit = filesForDistribution.get(0).getFeatureSplit();
       }
 
       reset();
     }
 
     void reset() {
-      allFilesCyclic = Iterators.cycle(files);
+      allFilesCyclic = Iterators.cycle(filesForDistribution);
       restart();
     }
 
@@ -863,11 +870,10 @@
      */
     VirtualFile nextOrCreate() {
       if (hasNext()) {
-        return activeFiles.next();
+        return next();
       } else {
-        VirtualFile newFile = new VirtualFile(nextFileId++, appView, namingLens, featuresplit);
-        files.add(newFile);
-        allFilesCyclic = Iterators.cycle(files);
+        VirtualFile newFile = internalAddFile();
+        allFilesCyclic = Iterators.cycle(filesForDistribution);
         return newFile;
       }
     }
@@ -892,16 +898,29 @@
 
     // Start a new iteration over all files, starting at the current one.
     void restart() {
-      activeFiles = Iterators.limit(allFilesCyclic, files.size());
+      activeFiles = Iterators.limit(allFilesCyclic, filesForDistribution.size());
     }
 
     VirtualFile addFile() {
-      VirtualFile newFile = new VirtualFile(nextFileId++, appView, namingLens, featuresplit);
-      files.add(newFile);
-
+      VirtualFile newFile = internalAddFile();
       reset();
       return newFile;
     }
+
+    private VirtualFile internalAddFile() {
+      VirtualFile newFile =
+          new VirtualFile(nextFileId.getAndIncrement(), appView, namingLens, featureSplit);
+      files.add(newFile);
+      filesForDistribution.add(newFile);
+      return newFile;
+    }
+
+    VirtualFileCycler ensureFile() {
+      if (filesForDistribution.isEmpty()) {
+        addFile();
+      }
+      return this;
+    }
   }
 
   /**
@@ -1029,16 +1048,18 @@
 
     PackageSplitPopulator(
         List<VirtualFile> files,
+        List<VirtualFile> filesForDistribution,
         AppView<?> appView,
         Collection<DexProgramClass> classes,
         Map<DexProgramClass, String> originalNames,
-        int fileIndexOffset,
+        IntBox nextFileId,
         NamingLens namingLens) {
       this.classPartioning = PackageSplitClassPartioning.create(classes, appView, originalNames);
       this.originalNames = originalNames;
       this.dexItemFactory = appView.dexItemFactory();
       this.options = appView.options();
-      this.cycler = new VirtualFileCycler(files, appView, namingLens, fileIndexOffset);
+      this.cycler =
+          new VirtualFileCycler(files, filesForDistribution, appView, namingLens, nextFileId);
     }
 
     static boolean coveredByPrefix(String originalName, String currentPrefix) {
@@ -1064,6 +1085,11 @@
     }
 
     private void addStartupClasses() {
+      List<DexProgramClass> startupClasses = classPartioning.getStartupClasses();
+      if (startupClasses.isEmpty()) {
+        return;
+      }
+
       // In practice, all startup classes should fit in a single dex file, so optimistically try to
       // commit the startup classes using a single transaction.
       VirtualFile virtualFile = cycler.next();
@@ -1099,7 +1125,7 @@
       int transactionStartIndex = 0;
       String currentPrefix = null;
       Object2IntMap<String> packageAssignments = new Object2IntOpenHashMap<>();
-      VirtualFile current = cycler.next();
+      VirtualFile current = cycler.ensureFile().next();
       List<DexProgramClass> classes = classPartioning.getNonStartupClasses();
       List<DexProgramClass> nonPackageClasses = new ArrayList<>();
       for (int classIndex = 0; classIndex < classes.size(); classIndex++) {