Merge "Add proguard helper scripts, also:"
diff --git a/.gitignore b/.gitignore
index 1529cd0..1f468a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,7 +34,7 @@
 third_party/jdwp-tests
 third_party/photos/*
 !third_party/photos/*.sha1
-third_party/proguard/
+third_party/proguard/*
 !third_party/proguard/*.sha1
 third_party/proguardsettings.tar.gz
 third_party/proguardsettings/
diff --git a/build.gradle b/build.gradle
index f3f3dff..dfb4623 100644
--- a/build.gradle
+++ b/build.gradle
@@ -113,7 +113,6 @@
                 "android_jar/lib-v25.tar.gz",
                 "android_jar/lib-v26.tar.gz",
                 "proguard/proguard5.2.1.tar.gz",
-                "proguard/proguard5.3.3.tar.gz",
                 "gradle/gradle.tar.gz",
                 "jdwp-tests.tar.gz",
                 "jasmin.tar.gz",
@@ -167,6 +166,7 @@
         "youtube/youtube.android_12.17.tar.gz",
         "youtube/youtube.android_12.22.tar.gz",
         "proguardsettings.tar.gz",
+        "proguard/proguard_internal_159423826.tar.gz",
     ],
 ]
 
diff --git a/src/main/java/com/android/tools/r8/BaseOutput.java b/src/main/java/com/android/tools/r8/BaseOutput.java
index cb4d473..a1fbb8e 100644
--- a/src/main/java/com/android/tools/r8/BaseOutput.java
+++ b/src/main/java/com/android/tools/r8/BaseOutput.java
@@ -49,6 +49,7 @@
    * Write the output resources to a zip-archive or directory.
    *
    * @param output Path to existing directory or non-existing zip-archive.
+   * @param overwrite true to allow overwriting existing files with outputs.
    */
-  abstract public void write(Path output) throws IOException;
+  abstract public void write(Path output, boolean overwrite) throws IOException;
 }
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 5a8ff11..522f6f0 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -60,11 +60,12 @@
    * @return the compilation result.
    */
   public static D8Output run(D8Command command) throws IOException {
-    CompilationResult result = runForTesting(command.getInputApp(), command.getInternalOptions());
+    InternalOptions options = command.getInternalOptions();
+    CompilationResult result = runForTesting(command.getInputApp(), options);
     assert result != null;
     D8Output output = new D8Output(result.androidApp, command.getOutputMode());
     if (command.getOutputPath() != null) {
-      output.write(command.getOutputPath());
+      output.write(command.getOutputPath(), options.overwriteOutputs);
     }
     return output;
   }
@@ -80,12 +81,13 @@
    * @return the compilation result.
    */
   public static D8Output run(D8Command command, ExecutorService executor) throws IOException {
+    InternalOptions options = command.getInternalOptions();
     CompilationResult result = runForTesting(
-        command.getInputApp(), command.getInternalOptions(), executor);
+        command.getInputApp(), options, executor);
     assert result != null;
     D8Output output = new D8Output(result.androidApp, command.getOutputMode());
     if (command.getOutputPath() != null) {
-      output.write(command.getOutputPath());
+      output.write(command.getOutputPath(), options.overwriteOutputs);
     }
     return output;
   }
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index c06438f..62cb5a5 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -194,7 +194,7 @@
     assert !internal.debug;
     internal.debug = getMode() == CompilationMode.DEBUG;
     internal.minApiLevel = getMinApiLevel();
-    internal.fillDexFiles = true;
+    internal.overwriteOutputs = true;
     // Assert and fixup defaults.
     assert !internal.skipMinification;
     internal.skipMinification = true;
diff --git a/src/main/java/com/android/tools/r8/D8Output.java b/src/main/java/com/android/tools/r8/D8Output.java
index 1910e9b..4ba7067 100644
--- a/src/main/java/com/android/tools/r8/D8Output.java
+++ b/src/main/java/com/android/tools/r8/D8Output.java
@@ -16,7 +16,7 @@
   }
 
   @Override
-  public void write(Path output) throws IOException {
-    getAndroidApp().write(output, getOutputMode());
+  public void write(Path output, boolean overwrite) throws IOException {
+    getAndroidApp().write(output, getOutputMode(), overwrite);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7dd2d33..fbcd34c 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -385,14 +385,17 @@
   static void writeOutputs(R8Command command, InternalOptions options, AndroidApp outputApp)
       throws IOException {
     if (command.getOutputPath() != null) {
-      outputApp.write(command.getOutputPath(), options.outputMode);
+      outputApp.write(command.getOutputPath(), options.outputMode, options.overwriteOutputs);
     }
 
     if (options.printMapping && !options.skipMinification) {
       assert outputApp.hasProguardMap();
       try (Closer closer = Closer.create()) {
-        OutputStream mapOut =
-            openPathWithDefault(closer, options.printMappingFile, true, System.out);
+        OutputStream mapOut = openPathWithDefault(
+            closer,
+            options.printMappingFile,
+            options.overwriteOutputs,
+            System.out);
         outputApp.writeProguardMap(closer, mapOut);
       }
     }
@@ -400,7 +403,7 @@
       assert outputApp.hasProguardSeeds();
       try (Closer closer = Closer.create()) {
         OutputStream seedsOut =
-            openPathWithDefault(closer, options.seedsFile, true, System.out);
+            openPathWithDefault(closer, options.seedsFile, options.overwriteOutputs, System.out);
         outputApp.writeProguardSeeds(closer, seedsOut);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index 9b44546..34834f2 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -346,6 +346,7 @@
     internal.useTreeShaking = useTreeShaking();
     assert !internal.ignoreMissingClasses;
     internal.ignoreMissingClasses = ignoreMissingClasses;
+    internal.overwriteOutputs = true;
 
     // TODO(zerny): Consider which other proguard options should be given flags.
     assert internal.packagePrefix.length() == 0;
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index c174a31..4a499ec 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -484,7 +484,7 @@
       }
       writeZipWithClasses(inputs, result, output);
     } else {
-      result.write(output);
+      result.write(output, true);
     }
   }
 
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 414af25..7476792 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -3,6 +3,9 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.dex;
 
+import com.android.tools.r8.dex.VirtualFile.FilePerClassDistributor;
+import com.android.tools.r8.dex.VirtualFile.FillFilesDistributor;
+import com.android.tools.r8.dex.VirtualFile.PackageMapDistributor;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
@@ -20,6 +23,7 @@
 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.OutputMode;
 import com.android.tools.r8.utils.PackageDistribution;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -113,8 +117,19 @@
       application.dexItemFactory.sort(namingLens);
       SortAnnotations sortAnnotations = new SortAnnotations();
       application.classes().forEach((clazz) -> clazz.addDependencies(sortAnnotations));
-      Map<Integer, VirtualFile> newFiles =
-          new VirtualFile.Distributor(this, packageDistribution, executorService).run();
+
+      // Distribute classes into dex files.
+      VirtualFile.Distributor distributor = null;
+      if (options.outputMode == OutputMode.FilePerClass) {
+        assert packageDistribution == null :
+            "Cannot combine package distribution definition with file-per-class option.";
+        distributor = new FilePerClassDistributor(this);
+      } else if (packageDistribution != null) {
+        distributor = new PackageMapDistributor(this, packageDistribution, executorService);
+      } else {
+        distributor = new FillFilesDistributor(this);
+      }
+      Map<Integer, VirtualFile> newFiles = distributor.run();
 
       // Write the dex files and the Proguard mapping file in parallel.
       LinkedHashMap<VirtualFile, Future<byte[]>> dexDataFutures = new LinkedHashMap<>();
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 a0ce2e2..3db0502 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -23,8 +23,6 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.FileUtils;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OutputMode;
 import com.android.tools.r8.utils.PackageDistribution;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.Iterators;
@@ -55,6 +53,11 @@
 
 public class VirtualFile {
 
+  enum FillStrategy {
+    FILL_MAX,
+    LEAVE_SPACE_FOR_GROWTH,
+  }
+
   private static final int MAX_ENTRIES = (Short.MAX_VALUE << 1) + 1;
   /**
    * When distributing classes across files we aim to leave some space. The amount of space left is
@@ -115,38 +118,6 @@
     return originalNames;
   }
 
-  private static TreeSet<DexProgramClass> sortClassesByPackage(Set<DexProgramClass> classes,
-      Map<DexProgramClass, String> originalNames) {
-    TreeSet<DexProgramClass> sortedClasses = new TreeSet<>(
-        (DexProgramClass a, DexProgramClass b) -> {
-      String originalA = originalNames.get(a);
-      String originalB = originalNames.get(b);
-      int indexA = originalA.lastIndexOf('.');
-      int indexB = originalB.lastIndexOf('.');
-      if (indexA == -1 && indexB == -1) {
-        // Empty package, compare the class names.
-        return originalA.compareTo(originalB);
-      }
-      if (indexA == -1) {
-        // Empty package name comes first.
-        return -1;
-      }
-      if (indexB == -1) {
-        // Empty package name comes first.
-        return 1;
-      }
-      String prefixA = originalA.substring(0, indexA);
-      String prefixB = originalB.substring(0, indexB);
-      int result = prefixA.compareTo(prefixB);
-      if (result != 0) {
-        return result;
-      }
-      return originalA.compareTo(originalB);
-    });
-    sortedClasses.addAll(classes);
-    return sortedClasses;
-  }
-
   private static String extractPrefixToken(int prefixLength, String className, boolean addStar) {
     int index = 0;
     int lastIndex = 0;
@@ -190,11 +161,11 @@
     return isFull(transaction.getNumberOfMethods(), transaction.getNumberOfFields(), MAX_ENTRIES);
   }
 
-  private boolean isFilledEnough(InternalOptions options) {
+  private boolean isFilledEnough(FillStrategy fillStrategy) {
     return isFull(
         transaction.getNumberOfMethods(),
         transaction.getNumberOfFields(),
-        options.fillDexFiles ? MAX_ENTRIES : MAX_PREFILL_ENTRIES);
+        fillStrategy == FillStrategy.FILL_MAX ? MAX_ENTRIES : MAX_PREFILL_ENTRIES);
   }
 
   public void abortTransaction() {
@@ -213,58 +184,49 @@
     return indexedItems.classes;
   }
 
-  public static class Distributor {
-    private final ApplicationWriter writer;
-    private final PackageDistribution packageDistribution;
-    private final ExecutorService executorService;
+  public abstract static class Distributor {
+    protected final DexApplication application;
+    protected final ApplicationWriter writer;
+    protected final Map<Integer, VirtualFile> nameToFileMap = new LinkedHashMap<>();
 
-    private final Map<Integer, VirtualFile> nameToFileMap = new LinkedHashMap<>();
-    public Distributor(
-        ApplicationWriter writer,
-        PackageDistribution packageDistribution,
-        ExecutorService executorService) {
+    public Distributor(ApplicationWriter writer) {
+      this.application = writer.application;
       this.writer = writer;
-      this.packageDistribution = packageDistribution;
-      this.executorService = executorService;
+    }
+
+    public abstract Map<Integer, VirtualFile> run() throws ExecutionException, IOException;
+  }
+
+  public static class FilePerClassDistributor extends Distributor {
+
+    public FilePerClassDistributor(ApplicationWriter writer) {
+      super(writer);
     }
 
     public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
-      // Strategy for distributing classes for write out:
-      // 1. Place all files in the package distribution file in the proposed files (if any).
-      // 2. Place the remaining files based on their packages in sorted order.
-      DexApplication application = writer.application;
-      InternalOptions options = writer.options;
-      Map<Integer, VirtualFile> nameToFileMap = new LinkedHashMap<>();
-
-      if (options.outputMode == OutputMode.FilePerClass) {
-        assert packageDistribution == null :
-            "Cannot combine package distribution definition with file-per-class option.";
-        // Assign dedicated virtual files for all program classes.
-        for (DexProgramClass clazz : application.classes()) {
-          VirtualFile file = new VirtualFile(nameToFileMap.size(), writer.namingLens);
-          nameToFileMap.put(nameToFileMap.size(), file);
-          file.addClass(clazz);
-          file.commitTransaction();
-        }
-        return nameToFileMap;
+      // Assign dedicated virtual files for all program classes.
+      for (DexProgramClass clazz : application.classes()) {
+        VirtualFile file = new VirtualFile(nameToFileMap.size(), writer.namingLens);
+        nameToFileMap.put(nameToFileMap.size(), file);
+        file.addClass(clazz);
+        file.commitTransaction();
       }
+      return nameToFileMap;
+    }
+  }
 
-      if (packageDistribution != null) {
-        int maxReferencedIndex = packageDistribution.maxReferencedIndex();
-        for (int index = 0; index <= maxReferencedIndex; index++) {
-          VirtualFile file = new VirtualFile(index, writer.namingLens);
-          nameToFileMap.put(index, file);
-        }
-      } else {
-        // If we had no map we default to 1 file, the package populator will add more if needed.
-        nameToFileMap.put(0, new VirtualFile(0, writer.namingLens));
-      }
-      Set<DexProgramClass> classes = Sets.newHashSet(application.classes());
+  public abstract static class DistributorBase extends Distributor {
+    protected Set<DexProgramClass> classes;
+    protected Map<DexProgramClass, String> originalNames;
 
-      // Compute the original names.
-      Map<DexProgramClass, String> originalNames = computeOriginalNameMapping(classes,
-          application.getProguardMap());
+    public DistributorBase(ApplicationWriter writer) {
+      super(writer);
 
+      classes = Sets.newHashSet(application.classes());
+      originalNames = computeOriginalNameMapping(classes, application.getProguardMap());
+    }
+
+    protected void fillForMainDexList(Set<DexProgramClass> classes) {
       if (application.mainDexList != null) {
         VirtualFile mainDexFile = nameToFileMap.get(0);
         for (DexType type : application.mainDexList) {
@@ -285,11 +247,128 @@
           mainDexFile.commitTransaction();
         }
       }
+    }
+
+    TreeSet<DexProgramClass> sortClassesByPackage(Set<DexProgramClass> classes,
+        Map<DexProgramClass, String> originalNames) {
+      TreeSet<DexProgramClass> sortedClasses = new TreeSet<>(
+          (DexProgramClass a, DexProgramClass b) -> {
+            String originalA = originalNames.get(a);
+            String originalB = originalNames.get(b);
+            int indexA = originalA.lastIndexOf('.');
+            int indexB = originalB.lastIndexOf('.');
+            if (indexA == -1 && indexB == -1) {
+              // Empty package, compare the class names.
+              return originalA.compareTo(originalB);
+            }
+            if (indexA == -1) {
+              // Empty package name comes first.
+              return -1;
+            }
+            if (indexB == -1) {
+              // Empty package name comes first.
+              return 1;
+            }
+            String prefixA = originalA.substring(0, indexA);
+            String prefixB = originalB.substring(0, indexB);
+            int result = prefixA.compareTo(prefixB);
+            if (result != 0) {
+              return result;
+            }
+            return originalA.compareTo(originalB);
+          });
+      sortedClasses.addAll(classes);
+      return sortedClasses;
+    }
+  }
+
+  public static class FillFilesDistributor extends DistributorBase {
+    public FillFilesDistributor(ApplicationWriter writer) {
+      super(writer);
+    }
+
+    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+      // Strategy for distributing classes for write out:
+      // 1. Place the remaining files based on their packages in sorted order.
+
+      // Start with 1 file. The package populator will add more if needed.
+      nameToFileMap.put(0, new VirtualFile(0, writer.namingLens));
+
+      // First fill required classes into the main dex file.
+      fillForMainDexList(classes);
 
       // Sort the classes based on the original names.
       // This with make classes from the same package be adjacent.
       classes = sortClassesByPackage(classes, originalNames);
 
+      new PackageSplitPopulator(
+          nameToFileMap, classes, originalNames, null, application.dexItemFactory,
+          FillStrategy.FILL_MAX, writer.namingLens)
+          .call();
+      return nameToFileMap;
+    }
+  }
+
+  public static class PackageMapDistributor extends DistributorBase {
+    private final PackageDistribution packageDistribution;
+    private final ExecutorService executorService;
+
+    public PackageMapDistributor(
+        ApplicationWriter writer,
+        PackageDistribution packageDistribution,
+        ExecutorService executorService) {
+      super(writer);
+      this.packageDistribution = packageDistribution;
+      this.executorService = executorService;
+    }
+
+    public Map<Integer, VirtualFile> run() throws ExecutionException, IOException {
+      // Strategy for distributing classes for write out:
+      // 1. Place all files in the package distribution file in the proposed files (if any).
+      // 2. Place the remaining files based on their packages in sorted order.
+
+      int maxReferencedIndex = packageDistribution.maxReferencedIndex();
+      for (int index = 0; index <= maxReferencedIndex; index++) {
+        VirtualFile file = new VirtualFile(index, writer.namingLens);
+        nameToFileMap.put(index, file);
+      }
+
+      // First fill required classes into the main dex file.
+      fillForMainDexList(classes);
+
+      // Sort the classes based on the original names.
+      // This with make classes from the same package be adjacent.
+      classes = sortClassesByPackage(classes, originalNames);
+
+      Set<String> usedPrefixes = fillForDistribution(classes, originalNames);
+
+      // TODO(zerny): Add the package map to AndroidApp and refactor its generation.
+      Map<String, Integer> newAssignments;
+      if (classes.isEmpty()) {
+        newAssignments = Collections.emptyMap();
+      } else {
+        newAssignments =
+            new PackageSplitPopulator(
+                nameToFileMap, classes, originalNames, usedPrefixes, application.dexItemFactory,
+                FillStrategy.LEAVE_SPACE_FOR_GROWTH, writer.namingLens)
+                .call();
+        if (!newAssignments.isEmpty() && nameToFileMap.size() > 1) {
+          System.err.println(" * The used package map is missing entries. The following default "
+              + "mappings have been used:");
+          writeAssignments(newAssignments, new OutputStreamWriter(System.err));
+          System.err.println(" * Consider updating the map.");
+        }
+      }
+
+      Path newPackageMap = Paths.get("package.map");
+      System.out.println(" - " + newPackageMap.toString());
+      PackageDistribution.writePackageToFileMap(newPackageMap, newAssignments, packageDistribution);
+
+      return nameToFileMap;
+    }
+
+    private Set<String> fillForDistribution(Set<DexProgramClass> classes,
+        Map<DexProgramClass, String> originalNames) throws ExecutionException {
       Set<String> usedPrefixes = null;
       if (packageDistribution != null) {
         ArrayList<Future<List<DexProgramClass>>> futures = new ArrayList<>(nameToFileMap.size());
@@ -301,41 +380,17 @@
         }
         ThreadUtils.awaitFutures(futures).forEach(classes::removeAll);
       }
+      return usedPrefixes;
+    }
 
-      // TODO(zerny): Add the package map to AndroidApp and refactor its generation.
-      Path newPackageMap = Paths.get("package.map");
-      Map<String, Integer> newAssignments;
-      if (classes.isEmpty()) {
-        newAssignments = Collections.emptyMap();
-      } else {
-        newAssignments =
-            new PackageSplitPopulator(
-                nameToFileMap, classes, originalNames, usedPrefixes, application.dexItemFactory,
-                options, writer.namingLens)
-                .call();
-        if (!newAssignments.isEmpty() && nameToFileMap.size() > 1) {
-          if (packageDistribution == null) {
-            System.out.println(" * Consider using a package map to improve patch sizes.");
-          } else {
-            System.err.println(" * The used package map is missing entries. The following default "
-                + "mappings have been used:");
-            Writer output = new OutputStreamWriter(System.err);
-            for (Entry<String, Integer> entry : newAssignments.entrySet()) {
-              output.write("    ");
-              PackageDistribution.formatEntry(entry, output);
-              output.write("\n");
-            }
-            output.flush();
-            System.err.println(" * Consider updating the map.");
-          }
-        }
+    private void writeAssignments(Map<String, Integer> assignments, Writer output)
+        throws IOException{
+      for (Entry<String, Integer> entry : assignments.entrySet()) {
+        output.write("    ");
+        PackageDistribution.formatEntry(entry, output);
+        output.write("\n");
       }
-      if (packageDistribution != null || nameToFileMap.size() > 1) {
-        System.out.println(" - " + newPackageMap.toString());
-        PackageDistribution.writePackageToFileMap(
-            newPackageMap, newAssignments, packageDistribution);
-      }
-      return nameToFileMap;
+      output.flush();
     }
   }
 
@@ -685,7 +740,7 @@
     private final Map<DexProgramClass, String> originalNames;
     private final Set<String> previousPrefixes;
     private final DexItemFactory dexItemFactory;
-    private final InternalOptions options;
+    private final FillStrategy fillStrategy;
     private final NamingLens namingLens;
 
     PackageSplitPopulator(
@@ -694,14 +749,14 @@
         Map<DexProgramClass, String> originalNames,
         Set<String> previousPrefixes,
         DexItemFactory dexItemFactory,
-        InternalOptions options,
+        FillStrategy fillStrategy,
         NamingLens namingLens) {
       this.files = files;
       this.classes = new ArrayList<>(classes);
       this.originalNames = originalNames;
       this.previousPrefixes = previousPrefixes;
       this.dexItemFactory = dexItemFactory;
-      this.options = options;
+      this.fillStrategy = fillStrategy;
       this.namingLens = namingLens;
     }
 
@@ -765,7 +820,7 @@
           nonPackageClasses.add(clazz);
           continue;
         }
-        if (current.isFilledEnough(options) || current.isFull()) {
+        if (current.isFilledEnough(fillStrategy) || current.isFull()) {
           current.abortTransaction();
           // We allow for a final rollback that has at most 20% of classes in it.
           // This is a somewhat random number that was empirically chosen.
@@ -816,7 +871,7 @@
       VirtualFile current;
       current = activeFiles.next();
       for (DexProgramClass clazz : nonPackageClasses) {
-        if (current.isFilledEnough(options)) {
+        if (current.isFilledEnough(fillStrategy)) {
           current = getVirtualFile(activeFiles);
         }
         current.addClass(clazz);
@@ -837,8 +892,9 @@
 
     private VirtualFile getVirtualFile(Iterator<VirtualFile> activeFiles) {
       VirtualFile current = null;
-      while (activeFiles.hasNext() && (current = activeFiles.next()).isFilledEnough(options)) {}
-      if (current == null || current.isFilledEnough(options)) {
+      while (activeFiles.hasNext()
+          && (current = activeFiles.next()).isFilledEnough(fillStrategy)) {}
+      if (current == null || current.isFilledEnough(fillStrategy)) {
         current = new VirtualFile(files.size(), namingLens);
         files.put(files.size(), current);
       }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 965d770..35ebaf2 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -73,6 +73,11 @@
     return compilationState != CompilationState.NOT_PROCESSED;
   }
 
+  public boolean cannotInline() {
+    return compilationState == CompilationState.NOT_PROCESSED
+        || compilationState == CompilationState.PROCESSED_NOT_INLINING_CANDIDATE;
+  }
+
   public boolean isInliningCandidate(DexEncodedMethod container, boolean alwaysInline) {
     if (container.accessFlags.isStatic() && container.accessFlags.isConstructor()) {
       // This will probably never happen but never inline a class initializer.
@@ -102,7 +107,8 @@
     return compilationState == PROCESSED_INLINING_CANDIDATE_PUBLIC;
   }
 
-  public void markProcessed(Constraint state) {
+  public boolean markProcessed(Constraint state) {
+    CompilationState prevCompilationState = compilationState;
     switch (state) {
       case ALWAYS:
         compilationState = PROCESSED_INLINING_CANDIDATE_PUBLIC;
@@ -117,6 +123,7 @@
         compilationState = PROCESSED_NOT_INLINING_CANDIDATE;
         break;
     }
+    return prevCompilationState != compilationState;
   }
 
   public void markNotProcessed() {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 87edec8..14104d7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -91,7 +91,7 @@
   }
 
   protected int argumentRegisterValue(int i, DexBuilder builder) {
-    assert requiredArgumentRegisters() > 5;
+    assert needsRangedInvoke(builder);
     if (i < arguments().size()) {
       // If argument values flow into ranged invokes, all the ranged invoke arguments
       // are arguments to this method in order. Therefore, we use the incoming registers
@@ -168,9 +168,45 @@
     if (requiredArgumentRegisters() > 5) {
       return Constants.U16BIT_MAX;
     }
+    if (argumentsAreConsecutiveInputArguments()) {
+      return Constants.U16BIT_MAX;
+    }
     return Constants.U4BIT_MAX;
   }
 
+  private boolean argumentsAreConsecutiveInputArguments() {
+    if (arguments().size() == 0) {
+      return false;
+    }
+    Value current = arguments().get(0);
+    if (!current.isArgument()) {
+      return false;
+    }
+    for (int i = 1; i < arguments().size(); i++) {
+      Value next = arguments().get(i);
+      if (current.getNextConsecutive() != next) {
+        return false;
+      }
+      current = next;
+    }
+    return true;
+  }
+
+  private boolean argumentsAreConsecutiveInputArgumentsWithHighRegisters(
+      DexBuilder builder) {
+    if (!argumentsAreConsecutiveInputArguments()) {
+      return false;
+    }
+    Value lastArgument = arguments().get(arguments().size() - 1);
+    return builder.argumentOrAllocateRegister(lastArgument, getNumber()) > Constants.U4BIT_MAX;
+  }
+
+  protected boolean needsRangedInvoke(DexBuilder builder) {
+    return requiredArgumentRegisters() > 5
+        || hasHighArgumentRegister(builder)
+        || argumentsAreConsecutiveInputArgumentsWithHighRegisters(builder);
+  }
+
   @Override
   public int maxOutValueRegister() {
     return Constants.U8BIT_MAX;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 2c7ea4b..d0d0739 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -50,7 +50,7 @@
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
     builder.requestOutgoingRegisters(argumentRegisters);
-    if (argumentRegisters > 5 || hasHighArgumentRegister(builder)) {
+    if (needsRangedInvoke(builder)) {
       assert argumentsConsecutive(builder);
       int firstRegister = argumentRegisterValue(0, builder);
       instruction = new InvokeCustomRange(firstRegister, argumentRegisters, getCallSite());
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index 95776f4..f11d62f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -39,7 +39,7 @@
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
     builder.requestOutgoingRegisters(argumentRegisters);
-    if (argumentRegisters > 5 || hasHighArgumentRegister(builder)) {
+    if (needsRangedInvoke(builder)) {
       assert argumentsConsecutive(builder);
       int firstRegister = argumentRegisterValue(0, builder);
       instruction = new InvokeDirectRange(firstRegister, argumentRegisters, getInvokedMethod());
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index f1c1d65..9d0c01f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -38,7 +38,7 @@
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
     builder.requestOutgoingRegisters(argumentRegisters);
-    if (argumentRegisters > 5 || hasHighArgumentRegister(builder)) {
+    if (needsRangedInvoke(builder)) {
       assert argumentsConsecutive(builder);
       int firstRegister = argumentRegisterValue(0, builder);
       instruction = new InvokeInterfaceRange(firstRegister, argumentRegisters, getInvokedMethod());
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 0725fed..2912c1a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -49,7 +49,7 @@
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
     builder.requestOutgoingRegisters(argumentRegisters);
-    if (argumentRegisters > 5 || hasHighArgumentRegister(builder)) {
+    if (needsRangedInvoke(builder)) {
       assert argumentsConsecutive(builder);
       int firstRegister = argumentRegisterValue(0, builder);
       instruction = new FilledNewArrayRange(firstRegister, argumentRegisters, type);
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 9c87144..efc12db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -47,7 +47,7 @@
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
     builder.requestOutgoingRegisters(argumentRegisters);
-    if (argumentRegisters > 5 || hasHighArgumentRegister(builder)) {
+    if (needsRangedInvoke(builder)) {
       assert argumentsConsecutive(builder);
       int firstRegister = argumentRegisterValue(0, builder);
       instruction = new InvokePolymorphicRange(
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 0c4fe14..dc22e2e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -38,7 +38,7 @@
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
     builder.requestOutgoingRegisters(argumentRegisters);
-    if (argumentRegisters > 5 || hasHighArgumentRegister(builder)) {
+    if (needsRangedInvoke(builder)) {
       assert argumentsConsecutive(builder);
       int firstRegister = argumentRegisterValue(0, builder);
       instruction = new InvokeStaticRange(firstRegister, argumentRegisters, getInvokedMethod());
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index 2e1e3e7..fc79c11 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -38,7 +38,7 @@
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
     builder.requestOutgoingRegisters(argumentRegisters);
-    if (argumentRegisters > 5 || hasHighArgumentRegister(builder)) {
+    if (needsRangedInvoke(builder)) {
       assert argumentsConsecutive(builder);
       int firstRegister = argumentRegisterValue(0, builder);
       instruction = new InvokeSuperRange(firstRegister, argumentRegisters, getInvokedMethod());
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 339ae71..3988980 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -38,7 +38,7 @@
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
     builder.requestOutgoingRegisters(argumentRegisters);
-    if (argumentRegisters > 5 || hasHighArgumentRegister(builder)) {
+    if (needsRangedInvoke(builder)) {
       assert argumentsConsecutive(builder);
       int firstRegister = argumentRegisterValue(0, builder);
       instruction = new InvokeVirtualRange(firstRegister, argumentRegisters, getInvokedMethod());
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index bd48134..c506867 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -19,6 +19,8 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -134,10 +136,14 @@
 
     private final List<DexEncodedMethod> leaves;
     private final boolean brokeCycles;
+    private final Map<DexEncodedMethod, Set<DexEncodedMethod>> cycleBreakingCalls;
 
-    private Leaves(List<DexEncodedMethod> leaves, boolean brokeCycles) {
+    private Leaves(List<DexEncodedMethod> leaves, boolean brokeCycles,
+        Map<DexEncodedMethod, Set<DexEncodedMethod>> cycleBreakingCalls) {
       this.leaves = leaves;
       this.brokeCycles = brokeCycles;
+      this.cycleBreakingCalls = cycleBreakingCalls;
+      assert brokeCycles == (cycleBreakingCalls.size() != 0);
     }
 
     public int size() {
@@ -152,31 +158,38 @@
       return brokeCycles;
     }
 
+    /**
+     * Calls that were broken to produce the leaves.
+     *
+     * If {@link Leaves#breakCycles()} return <code>true</code> this provides the calls for each
+     * leaf that were broken.
+     *
+     * NOTE: The broken calls are not confined to the set of leaves.
+     */
+    public Map<DexEncodedMethod, Set<DexEncodedMethod>> getCycleBreakingCalls() {
+      return cycleBreakingCalls;
+    }
+
     @Override
     public String toString() {
       StringBuilder builder = new StringBuilder();
       builder.append("Leaves: ");
       builder.append(leaves.size());
       builder.append("\n");
-      builder.append(brokeCycles ? "Cycles broken" : "No cycles broken");
+      builder.append(brokeCycles ? "Call cycles broken" : "No call cycles broken");
       return builder.toString();
     }
   }
 
-  private final GraphLense graphLense;
   private final Map<DexEncodedMethod, Node> nodes = new LinkedHashMap<>();
   private List<Node> leaves = null;
   private Set<DexEncodedMethod> singleCallSite = Sets.newIdentityHashSet();
   private Set<DexEncodedMethod> doubleCallSite = Sets.newIdentityHashSet();
 
-  private CallGraph(GraphLense graphLense) {
-    this.graphLense = graphLense;
-  }
-
   public static CallGraph build(DexApplication application, AppInfoWithSubtyping appInfo,
       GraphLense graphLense) {
 
-    CallGraph graph = new CallGraph(graphLense);
+    CallGraph graph = new CallGraph();
 
     for (DexClass clazz : application.classes()) {
       for (DexEncodedMethod method : clazz.directMethods()) {
@@ -279,18 +292,19 @@
    * <p>
    *
    * @return object with the leaves as a List of {@link DexEncodedMethod} and <code>boolean</code>
-   * indication of whether cycels where broken to produce leaves. <code>null</code> if the graph is
+   * indication of whether cycles were broken to produce leaves. <code>null</code> if the graph is
    * empty.
    */
   public Leaves pickLeaves() {
     boolean cyclesBroken = false;
+    Map<DexEncodedMethod, Set<DexEncodedMethod>> brokenCalls = Collections.emptyMap();
     if (isEmpty()) {
       return null;
     }
     List<DexEncodedMethod> leaves = removeLeaves();
     if (leaves.size() == 0) {
       // The graph had no more leaves, so break cycles to construct leaves.
-      breakCycles();
+      brokenCalls = breakCycles();
       cyclesBroken = true;
       leaves = removeLeaves();
     }
@@ -298,7 +312,7 @@
     for (DexEncodedMethod leaf : leaves) {
       assert !leaf.isProcessed();
     }
-    return new Leaves(leaves, cyclesBroken);
+    return new Leaves(leaves, cyclesBroken, brokenCalls);
   }
 
   /**
@@ -309,35 +323,41 @@
    * (outgoing) degree.
    * <p>
    * It will avoid removing edges from bridge-methods if possible.
+   * <p>
+   * Returns the calls that were broken.
    */
-  private void breakCycles() {
+  private Map<DexEncodedMethod, Set<DexEncodedMethod>> breakCycles() {
+    Map<DexEncodedMethod, Set<DexEncodedMethod>> brokenCalls = new IdentityHashMap<>();
     // Break non bridges with degree 1.
     int minDegree = nodes.size();
     for (Node node : nodes.values()) {
       // Break cycles and add all leaves created in the process.
       if (!node.isBridge() && node.callDegree() <= 1) {
         assert node.callDegree() == 1;
-        removeAllCalls(node);
+        Set<DexEncodedMethod> calls = removeAllCalls(node);
         leaves.add(node);
+        brokenCalls.put(node.method, calls);
       } else {
         minDegree = Integer.min(minDegree, node.callDegree());
       }
     }
 
-    // Return if new leaves where created.
+    // Return if new leaves were created.
     if (leaves.size() > 0) {
-      return;
+      return brokenCalls;
     }
 
     // Break methods with the found minimum degree and add all leaves created in the process.
     for (Node node : nodes.values()) {
       if (node.callDegree() <= minDegree) {
         assert node.callDegree() == minDegree;
-        removeAllCalls(node);
+        Set<DexEncodedMethod> calls = removeAllCalls(node);
         leaves.add(node);
+        brokenCalls.put(node.method, calls);
       }
     }
     assert leaves.size() > 0;
+    return brokenCalls;
   }
 
   synchronized private Node ensureMethodNode(DexEncodedMethod method) {
@@ -356,11 +376,14 @@
     callee.invokeCount++;
   }
 
-  private void removeAllCalls(Node node) {
+  private Set<DexEncodedMethod> removeAllCalls(Node node) {
+    Set<DexEncodedMethod> calls = Sets.newIdentityHashSet();
     for (Node call : node.calls) {
+      calls.add(call.method);
       call.callees.remove(node);
     }
     node.calls.clear();
+    return calls;
   }
 
   private void remove(Node node, List<Node> leaves) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 3b19b02..8b66c4b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -37,11 +37,10 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
-import java.util.Random;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -66,6 +65,7 @@
   private final LensCodeRewriter lensCodeRewriter;
   private final Inliner inliner;
   private CallGraph callGraph;
+  private OptimizationFeedback ignoreOptimizationFeedback = new OptimizationFeedbackIgnore();
 
   private DexString highestSortingString;
 
@@ -204,7 +204,7 @@
         boolean matchesMethodFilter = options.methodMatchesFilter(method);
         if (matchesMethodFilter) {
           if (method.getCode().isJarCode()) {
-            rewriteCode(method, Outliner::noProcessing);
+            rewriteCode(method, ignoreOptimizationFeedback, Outliner::noProcessing);
           }
           updateHighestSortingStrings(method);
         }
@@ -236,34 +236,41 @@
 
     // Process the application identifying outlining candidates.
     timing.begin("IR conversion phase 1");
+    int count = 0;
+    OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
+    OptimizationFeedbackDelayed delayedFeedback = new OptimizationFeedbackDelayed();
     while (!callGraph.isEmpty()) {
+      count++;
       CallGraph.Leaves leaves = callGraph.pickLeaves();
-      List<DexEncodedMethod> leafMethods = leaves.getLeaves();
-      assert leafMethods.size() > 0;
-      // If cycles where broken to produce leaves, don't do parallel processing to keep
-      // deterministic output.
-      // TODO(37133161): Most likely the failing:
-      //  java.com.android.tools.r8.internal.R8GMSCoreDeterministicTest
-      // is caused by processing in multiple threads.
-      if (true || leaves.brokeCycles()) {
-        for (DexEncodedMethod method : leafMethods) {
-          processMethod(method,
-              outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
-        }
-      } else {
-        List<Future<?>> futures = new ArrayList<>();
-        // For testing we have the option to randomize the processing order for the
-        // deterministic tests.
-        if (options.testing.randomizeCallGraphLeaves) {
-          Collections.shuffle(leafMethods, new Random(System.nanoTime()));
-        }
-        for (DexEncodedMethod method : leafMethods) {
+      List<DexEncodedMethod> methods = leaves.getLeaves();
+      assert methods.size() > 0;
+      List<Future<?>> futures = new ArrayList<>();
+
+      // For testing we have the option to determine the processing order of the methods.
+      if (options.testing.irOrdering != null) {
+        methods = options.testing.irOrdering.apply(methods, leaves);
+      }
+
+      while (methods.size() > 0) {
+        // Process the methods on multiple threads. If cycles where broken collect the
+        // optimization feedback to reprocess methods affected by it. This is required to get
+        // deterministic behaviour, as the processing order within each set of leaves is
+        // non-deterministic.
+        for (DexEncodedMethod method : methods) {
           futures.add(executorService.submit(() -> {
             processMethod(method,
+                leaves.brokeCycles() ? delayedFeedback : directFeedback,
                 outliner == null ? Outliner::noProcessing : outliner::identifyCandidates);
           }));
         }
         ThreadUtils.awaitFutures(futures);
+        if (leaves.brokeCycles()) {
+          // If cycles in the call graph were broken, then re-process all methods which are
+          // affected by the optimization feedback of other methods in this group.
+          methods = delayedFeedback.applyAndClear(methods, leaves);
+        } else {
+          methods = ImmutableList.of();
+        }
       }
     }
     timing.end();
@@ -276,8 +283,7 @@
     if ((inliner != null) && (inliner.doubleInlineCallers.size() > 0)) {
       inliner.applyDoubleInlining = true;
       for (DexEncodedMethod method : inliner.doubleInlineCallers) {
-        method.markNotProcessed();
-        processMethod(method, Outliner::noProcessing);
+        processMethod(method, ignoreOptimizationFeedback, Outliner::noProcessing);
         assert method.isProcessed();
       }
     }
@@ -295,8 +301,7 @@
         for (DexEncodedMethod method : outliner.getMethodsSelectedForOutlining()) {
           // This is the second time we compile this method first mark it not processed.
           assert !method.getCode().isOutlineCode();
-          method.markNotProcessed();
-          processMethod(method, outliner::applyOutliningCandidate);
+          processMethod(method, ignoreOptimizationFeedback, outliner::applyOutliningCandidate);
           assert method.isProcessed();
         }
         builder.addSynthesizedClass(outlineClass, true);
@@ -377,28 +382,28 @@
 
   public void optimizeSynthesizedMethod(DexEncodedMethod method) {
     // Process the generated method, but don't apply any outlining.
-    processMethod(method, Outliner::noProcessing);
+    processMethod(method, ignoreOptimizationFeedback, Outliner::noProcessing);
   }
 
   private String logCode(InternalOptions options, DexEncodedMethod method) {
     return options.useSmaliSyntax ? method.toSmaliString(null) : method.codeToString();
   }
 
-  private void processMethod(
-      DexEncodedMethod method, BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
+  private void processMethod(DexEncodedMethod method,
+      OptimizationFeedback feedback,
+      BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
     Code code = method.getCode();
     boolean matchesMethodFilter = options.methodMatchesFilter(method);
     if (code != null && matchesMethodFilter) {
-      assert !method.isProcessed();
-      Constraint state = rewriteCode(method, outlineHandler);
-      method.markProcessed(state);
+      rewriteCode(method, feedback, outlineHandler);
     } else {
       // Mark abstract methods as processed as well.
       method.markProcessed(Constraint.NEVER);
     }
   }
 
-  private Constraint rewriteCode(DexEncodedMethod method,
+  private void rewriteCode(DexEncodedMethod method,
+      OptimizationFeedback feedback,
       BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
     if (options.verbose) {
       System.out.println("Processing: " + method.toSourceString());
@@ -409,7 +414,8 @@
     }
     IRCode code = method.buildIR(options);
     if (code == null) {
-      return Constraint.NEVER;
+      feedback.markProcessed(method, Constraint.NEVER);
+      return;
     }
     if (Log.ENABLED) {
       Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
@@ -470,7 +476,7 @@
     }
 
     codeRewriter.shortenLiveRanges(code);
-    codeRewriter.identifyReturnsArgument(method, code);
+    codeRewriter.identifyReturnsArgument(method, code, feedback);
 
     // Insert code to log arguments if requested.
     if (options.methodMatchesLogArgumentsFilter(method)) {
@@ -489,10 +495,13 @@
     printMethod(code, "Final IR (non-SSA)");
 
     // After all the optimizations have take place, we compute whether method should be inlined.
+    Constraint state;
     if (!options.inlineAccessors || inliner == null) {
-      return Constraint.NEVER;
+      state = Constraint.NEVER;
+    } else {
+      state = inliner.identifySimpleMethods(code, method);
     }
-    return inliner.identifySimpleMethods(code, method);
+    feedback.markProcessed(method, state);
   }
 
   private void updateHighestSortingStrings(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
new file mode 100644
index 0000000..be5718e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -0,0 +1,15 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+
+public interface OptimizationFeedback {
+  void methodReturnsArgument(DexEncodedMethod method, int argument);
+  void methodReturnsConstant(DexEncodedMethod method, long value);
+  void methodNeverReturnsNull(DexEncodedMethod method);
+  void markProcessed(DexEncodedMethod method, Constraint state);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
new file mode 100644
index 0000000..25d3fe5
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDelayed.java
@@ -0,0 +1,117 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2LongMap;
+import it.unimi.dsi.fastutil.objects.Reference2LongOpenHashMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class OptimizationFeedbackDelayed implements OptimizationFeedback {
+
+  private Reference2IntMap<DexEncodedMethod> returnsArgument = new Reference2IntOpenHashMap<>();
+  private Reference2LongMap<DexEncodedMethod> returnsConstant = new Reference2LongOpenHashMap<>();
+  private Set<DexEncodedMethod> neverReturnsNull = Sets.newIdentityHashSet();
+  private Map<DexEncodedMethod, Constraint> inliningConstraints = Maps.newIdentityHashMap();
+
+  @Override
+  synchronized public void methodReturnsArgument(DexEncodedMethod method, int argument) {
+    if (method.getOptimizationInfo().returnsArgument()) {
+      assert method.getOptimizationInfo().getReturnedArgument() == argument;
+      return;
+    }
+    assert !returnsArgument.containsKey(method);
+    returnsArgument.put(method, argument);
+  }
+
+  @Override
+  synchronized public void methodReturnsConstant(DexEncodedMethod method, long value) {
+    if (method.getOptimizationInfo().returnsConstant()) {
+      assert method.getOptimizationInfo().getReturnedConstant() == value;
+      return;
+    }
+    assert !returnsConstant.containsKey(method);
+    returnsConstant.put(method, value);
+  }
+
+  @Override
+  synchronized public void methodNeverReturnsNull(DexEncodedMethod method) {
+    if (method.getOptimizationInfo().neverReturnsNull()) {
+      return;
+    }
+    assert !neverReturnsNull.contains(method);
+    neverReturnsNull.add(method);
+  }
+
+  @Override
+  public void markProcessed(DexEncodedMethod method, Constraint state) {
+    if (state == Constraint.NEVER) {
+      assert method.cannotInline();
+      method.markProcessed(state);
+    } else {
+      assert method.cannotInline();
+      inliningConstraints.put(method, state);
+    }
+  }
+
+  private <T> boolean setsOverlap(Set<T> set1, Set<T> set2) {
+    for (T element : set1) {
+      if (set2.contains(element)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Apply the optimization feedback.
+   *
+   * Returns the methods from the passed in list that could be affected by applying the
+   * optimization feedback.
+   */
+  public List<DexEncodedMethod> applyAndClear(
+      List<DexEncodedMethod> processed, CallGraph.Leaves leaves) {
+    returnsArgument.forEach(DexEncodedMethod::markReturnsArgument);
+    returnsConstant.forEach(DexEncodedMethod::markReturnsConstant);
+    neverReturnsNull.forEach(DexEncodedMethod::markNeverReturnsNull);
+
+    // Collect all methods affected by the optimization feedback applied.
+    Set<DexEncodedMethod> all = Sets.newIdentityHashSet();
+    all.addAll(returnsArgument.keySet());
+    all.addAll(returnsConstant.keySet());
+    all.addAll(neverReturnsNull);
+    inliningConstraints.forEach((method, constraint) -> {
+      boolean changed = method.markProcessed(constraint);
+      if (changed) {
+        all.add(method);
+      }
+    });
+
+    // Collect the processed methods which could be affected by the applied optimization feedback.
+    List<DexEncodedMethod> result = new ArrayList<>();
+    for (DexEncodedMethod method : processed) {
+      Set<DexEncodedMethod> calls = leaves.getCycleBreakingCalls().get(method);
+      if (setsOverlap(calls, all)) {
+        result.add(method);
+      }
+    }
+
+    // Clear the collected optimization feedback.
+    returnsArgument.clear();
+    returnsConstant.clear();
+    neverReturnsNull.clear();
+    inliningConstraints.clear();
+
+    return result;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
new file mode 100644
index 0000000..dc0a809
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -0,0 +1,31 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+
+public class OptimizationFeedbackDirect implements OptimizationFeedback {
+
+  @Override
+  public void methodReturnsArgument(DexEncodedMethod method, int argument) {
+    method.markReturnsArgument(argument);
+  }
+
+  @Override
+  public void methodReturnsConstant(DexEncodedMethod method, long value) {
+    method.markReturnsConstant(value);
+  }
+
+  @Override
+  public void methodNeverReturnsNull(DexEncodedMethod method) {
+    method.markNeverReturnsNull();
+  }
+
+  @Override
+  public void markProcessed(DexEncodedMethod method, Constraint state) {
+    method.markProcessed(state);
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
new file mode 100644
index 0000000..3c8057f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -0,0 +1,23 @@
+// 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.ir.conversion;
+
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
+
+public class OptimizationFeedbackIgnore implements OptimizationFeedback {
+
+  @Override
+  public void methodReturnsArgument(DexEncodedMethod method, int argument) {}
+
+  @Override
+  public void methodReturnsConstant(DexEncodedMethod method, long value) {}
+
+  @Override
+  public void methodNeverReturnsNull(DexEncodedMethod method) {}
+
+  @Override
+  public void markProcessed(DexEncodedMethod method, Constraint state) {}
+}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index e5b8306..077662e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -48,6 +48,7 @@
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Switch.Type;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.conversion.OptimizationFeedback;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.LongInterval;
 import com.google.common.base.Equivalence;
@@ -607,7 +608,7 @@
   }
 
   public void identifyReturnsArgument(
-      DexEncodedMethod method, IRCode code) {
+      DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     if (code.getNormalExitBlock() != null) {
       Return ret = code.getNormalExitBlock().exit().asReturn();
       if (!ret.isReturnVoid()) {
@@ -616,14 +617,14 @@
           // Find the argument number.
           int index = code.collectArguments().indexOf(returnValue);
           assert index != -1;
-          method.markReturnsArgument(index);
+          feedback.methodReturnsArgument(method, index);
         }
         if (returnValue.isConstant() && returnValue.definition.isConstNumber()) {
           long value = returnValue.definition.asConstNumber().getRawValue();
-          method.markReturnsConstant(value);
+          feedback.methodReturnsConstant(method, value);
         }
         if (returnValue.isNeverNull()) {
-          method.markNeverReturnsNull();
+          feedback.methodNeverReturnsNull(method);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 41759aa..33dc79a 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -62,6 +62,8 @@
  */
 public class LinearScanRegisterAllocator implements RegisterAllocator {
   public static final int NO_REGISTER = Integer.MIN_VALUE;
+  public static final int REGISTER_CANDIDATE_NOT_FOUND = -1;
+  public static final int MIN_CONSTANT_FREE_FOR_POSITIONS = 5;
 
   private enum ArgumentReuseMode {
     ALLOW_ARGUMENT_REUSE,
@@ -916,17 +918,17 @@
 
     if (mode == ArgumentReuseMode.ALLOW_ARGUMENT_REUSE) {
       // The sentinel registers cannot be used and we block them.
-      freePositions.set(0, 0);
+      freePositions.set(0, 0, false);
       int lastSentinelRegister = numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS - 1;
       if (lastSentinelRegister <= registerConstraint) {
-        freePositions.set(lastSentinelRegister, 0);
+        freePositions.set(lastSentinelRegister, 0, false);
       }
     } else {
       // Argument reuse is not allowed and we block all the argument registers so that
       // arguments are never free.
       for (int i = 0; i < numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS; i++) {
         if (i <= registerConstraint) {
-          freePositions.set(i, 0);
+          freePositions.set(i, 0, false);
         }
       }
     }
@@ -938,7 +940,7 @@
     if (hasDedicatedMoveExceptionRegister) {
       int moveExceptionRegister = numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS;
       if (moveExceptionRegister <= registerConstraint) {
-        freePositions.set(moveExceptionRegister, 0);
+        freePositions.set(moveExceptionRegister, 0, false);
       }
     }
 
@@ -948,7 +950,7 @@
       if (activeRegister <= registerConstraint) {
         for (int i = 0; i < intervals.requiredRegisters(); i++) {
           if (activeRegister + i <= registerConstraint) {
-            freePositions.set(activeRegister + i, 0);
+            freePositions.set(activeRegister + i, 0, intervals.isConstantNumberInterval());
           }
         }
       }
@@ -961,17 +963,18 @@
       if (inactiveRegister <= registerConstraint && unhandledInterval.overlaps(intervals)) {
         int nextOverlap = unhandledInterval.nextOverlap(intervals);
         for (int i = 0; i < intervals.requiredRegisters(); i++) {
-          if (inactiveRegister + i <= registerConstraint) {
+          int register = inactiveRegister + i;
+          if (register <= registerConstraint) {
             int unhandledStart = toInstructionPosition(unhandledInterval.getStart());
             if (nextOverlap == unhandledStart) {
               // Don't use the register for an inactive interval that is only free until the next
               // instruction. We can get into this situation when unhandledInterval starts at a
               // gap position.
-              freePositions.set(inactiveRegister + i, 0);
+              freePositions.set(register, 0, freePositions.holdsConstant(register));
             } else {
-              freePositions.set(
-                  inactiveRegister + i,
-                  Math.min(freePositions.get(inactiveRegister + i), nextOverlap));
+              if (nextOverlap < freePositions.get(register)) {
+                freePositions.set(register, nextOverlap, intervals.isConstantNumberInterval());
+              }
             }
           }
         }
@@ -986,7 +989,8 @@
     // Get the register (pair) that is free the longest. That is the register with the largest
     // free position.
     int candidate = getLargestValidCandidate(
-        unhandledInterval, registerConstraint, needsRegisterPair, freePositions);
+        unhandledInterval, registerConstraint, needsRegisterPair, freePositions, false);
+    assert candidate != REGISTER_CANDIDATE_NOT_FOUND;
     int largestFreePosition = freePositions.get(candidate);
     if (needsRegisterPair) {
       largestFreePosition = Math.min(largestFreePosition, freePositions.get(candidate + 1));
@@ -1138,14 +1142,15 @@
     active.add(unhandledInterval);
   }
 
-  private int getLargestCandidate(
-      int registerConstraint, RegisterPositions freePositions, boolean needsRegisterPair) {
-    int candidate = 0;
-    int largest = freePositions.get(0);
-    if (needsRegisterPair) {
-      largest = Math.min(largest, freePositions.get(1));
-    }
-    for (int i = 1; i <= registerConstraint; i++) {
+  private int getLargestCandidate(int registerConstraint, RegisterPositions freePositions,
+      boolean needsRegisterPair, boolean restrictToConstant) {
+    int candidate = REGISTER_CANDIDATE_NOT_FOUND;
+    int largest = -1;
+
+    for (int i = 0; i <= registerConstraint; i++) {
+      if (restrictToConstant && !freePositions.holdsConstant(i)) {
+        continue;
+      }
       int freePosition = freePositions.get(i);
       if (needsRegisterPair) {
         if (i >= registerConstraint) {
@@ -1161,17 +1166,23 @@
         }
       }
     }
+
     return candidate;
   }
 
   private int getLargestValidCandidate(LiveIntervals unhandledInterval, int registerConstraint,
-      boolean needsRegisterPair, RegisterPositions freePositions) {
-    int candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair);
+      boolean needsRegisterPair, RegisterPositions freePositions, boolean restrictToConstant) {
+    int candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair,
+        restrictToConstant);
+    if (candidate == REGISTER_CANDIDATE_NOT_FOUND) {
+      return candidate;
+    }
     if (needsOverlappingLongRegisterWorkaround(unhandledInterval)) {
       while (hasOverlappingLongRegisters(unhandledInterval, candidate)) {
         // Make the overlapping register unavailable for allocation and try again.
-        freePositions.set(candidate, 0);
-        candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair);
+        freePositions.set(candidate, 0, freePositions.holdsConstant(candidate));
+        candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair,
+            restrictToConstant);
       }
     }
     return candidate;
@@ -1198,7 +1209,8 @@
         for (int i = 0; i < intervals.requiredRegisters(); i++) {
           if (activeRegister + i <= registerConstraint) {
             int unhandledStart = unhandledInterval.getStart();
-            usePositions.set(activeRegister + i, intervals.firstUseAfter(unhandledStart));
+            usePositions.set(activeRegister + i, intervals.firstUseAfter(unhandledStart),
+                intervals.isConstantNumberInterval());
           }
         }
       }
@@ -1210,9 +1222,11 @@
       if (inactiveRegister <= registerConstraint && intervals.overlaps(unhandledInterval)) {
         for (int i = 0; i < intervals.requiredRegisters(); i++) {
           if (inactiveRegister + i <= registerConstraint) {
-            int unhandledStart = unhandledInterval.getStart();
-            usePositions.set(inactiveRegister + i, Math.min(
-                usePositions.get(inactiveRegister + i), intervals.firstUseAfter(unhandledStart)));
+            int firstUse = intervals.firstUseAfter(unhandledInterval.getStart());
+            if (firstUse < usePositions.get(inactiveRegister + i)) {
+              usePositions.set(inactiveRegister + i, firstUse,
+                  intervals.isConstantNumberInterval());
+            }
           }
         }
       }
@@ -1221,12 +1235,12 @@
     // Disallow the reuse of argument registers by always treating them as being used
     // at instruction number 0.
     for (int i = 0; i < numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS; i++) {
-      usePositions.set(i, 0);
+      usePositions.set(i, 0, false);
     }
 
     // Disallow reuse of the move exception register if we have reserved one.
     if (hasDedicatedMoveExceptionRegister) {
-      usePositions.set(numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS, 0);
+      usePositions.set(numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS, 0, false);
     }
 
     // Treat active linked argument intervals as pinned. They cannot be given another register
@@ -1241,18 +1255,26 @@
 
     // Get the register (pair) that has the highest use position.
     boolean needsRegisterPair = unhandledInterval.requiredRegisters() == 2;
-    int candidate = getLargestValidCandidate(
-        unhandledInterval, registerConstraint, needsRegisterPair, usePositions);
-    int largestUsePosition = usePositions.get(candidate);
-    int blockedPosition = blockedPositions.get(candidate);
-    if (needsRegisterPair) {
-      blockedPosition = Math.min(blockedPosition, blockedPositions.get(candidate + 1));
-      largestUsePosition = Math.min(largestUsePosition, usePositions.get(candidate + 1));
+
+    int candidate = getLargestValidCandidate(unhandledInterval, registerConstraint,
+        needsRegisterPair, usePositions, true);
+    if (candidate != Integer.MAX_VALUE) {
+      int nonConstCandidate = getLargestValidCandidate(unhandledInterval, registerConstraint,
+          needsRegisterPair, usePositions, false);
+      if (nonConstCandidate == Integer.MAX_VALUE || candidate == REGISTER_CANDIDATE_NOT_FOUND) {
+        candidate = nonConstCandidate;
+      } else {
+        int largestConstUsePosition = getLargestPosition(usePositions, candidate, needsRegisterPair);
+        if (largestConstUsePosition - MIN_CONSTANT_FREE_FOR_POSITIONS < unhandledInterval
+            .getStart()) {
+          // The candidate that can be rematerialized has a live range too short to use it.
+          candidate = nonConstCandidate;
+        }
+      }
     }
 
-    // TODO(ager): When selecting a spill candidate also take rematerialization into account.
-    // Prefer spilling a constant that can be rematerialized instead of spilling something that
-    // cannot be rematerialized.
+    int largestUsePosition = getLargestPosition(usePositions, candidate, needsRegisterPair);
+    int blockedPosition = getBlockedPosition(blockedPositions, candidate, needsRegisterPair);
 
     if (largestUsePosition < unhandledInterval.getFirstUse()) {
       // All active and inactive intervals are used before current. Therefore, it is best to spill
@@ -1279,6 +1301,28 @@
     }
   }
 
+  private int getLargestPosition(RegisterPositions usePositions, int register,
+      boolean needsRegisterPair) {
+    int largestUsePosition = usePositions.get(register);
+
+    if (needsRegisterPair) {
+      largestUsePosition = Math.min(largestUsePosition, usePositions.get(register + 1));
+    }
+
+    return largestUsePosition;
+  }
+
+  private int getBlockedPosition(RegisterPositions blockedPositions, int register,
+      boolean needsRegisterPair) {
+    int blockedPosition = blockedPositions.get(register);
+
+    if (needsRegisterPair) {
+      blockedPosition = Math.min(blockedPosition, blockedPositions.get(register + 1));
+    }
+
+    return blockedPosition;
+  }
+
   private void assignRegisterAndSpill(
       LiveIntervals unhandledInterval, boolean needsRegisterPair, int candidate) {
     assignRegister(unhandledInterval, candidate);
@@ -1456,12 +1500,13 @@
         if (register <= registerConstraint && other.overlaps(interval)) {
           for (int i = 0; i < other.requiredRegisters(); i++) {
             if (register + i <= registerConstraint) {
-              blockedPositions.set(register + i,
-                  Math.min(blockedPositions.get(register + i),
-                      other.firstUseAfter(interval.getStart())));
-              // If we start blocking registers other than linked arguments, we might need to
-              // explicitly update the use positions as well as blocked positions.
-              assert usePositions.get(register + i) <= blockedPositions.get(register + i);
+              int firstUse = other.firstUseAfter(interval.getStart());
+              if (firstUse < blockedPositions.get(register + i)) {
+                blockedPositions.set(register + i, firstUse, other.isConstantNumberInterval());
+                // If we start blocking registers other than linked arguments, we might need to
+                // explicitly update the use positions as well as blocked positions.
+                assert usePositions.get(register + i) <= blockedPositions.get(register + i);
+              }
             }
           }
         }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index c966813..cf44137 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -468,4 +468,8 @@
       splitChild.print(printer, number + delta, number);
     }
   }
+
+  public boolean isConstantNumberInterval() {
+    return value.definition != null && value.definition.isConstNumber();
+  }
 }
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositions.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositions.java
index 87eedca..35b87fc 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositions.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterPositions.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.regalloc;
 
 import java.util.Arrays;
+import java.util.BitSet;
 
 /**
  * Simple mapping from a register to an int value.
@@ -16,6 +17,7 @@
   private static final int INITIAL_SIZE = 16;
   private int limit;
   private int[] backing;
+  private BitSet registerHoldsConstant;
 
   public RegisterPositions(int limit) {
     this.limit = limit;
@@ -23,13 +25,19 @@
     for (int i = 0; i < INITIAL_SIZE; i++) {
       backing[i] = Integer.MAX_VALUE;
     }
+    registerHoldsConstant = new BitSet(limit);
   }
 
-  public void set(int index, int value) {
+  public boolean holdsConstant(int index) {
+    return registerHoldsConstant.get(index);
+  }
+
+  public void set(int index, int value, boolean holdsConstant) {
     if (index >= backing.length) {
       grow(index + 1);
     }
     backing[index] = value;
+    registerHoldsConstant.set(index, holdsConstant);
   }
 
   public int get(int index) {
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index ad5485a..9d2379a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -7,12 +7,14 @@
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.ir.conversion.CallGraph;
 import com.android.tools.r8.shaking.ProguardConfigurationRule;
 import com.android.tools.r8.shaking.ProguardTypeMatcher;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.function.BiFunction;
 
 public class InternalOptions {
 
@@ -43,8 +45,6 @@
   public boolean verbose = false;
   // Silencing output.
   public boolean quiet = false;
-  // Eagerly fill dex files as much as possible.
-  public boolean fillDexFiles = false;
 
   public List<String> methodsFilter = ImmutableList.of();
   public int minApiLevel = Constants.DEFAULT_ANDROID_API;
@@ -91,6 +91,8 @@
 
   public String warningInvalidParameterAnnotations = null;
 
+  public boolean overwriteOutputs; // default value is set in D/R8Command
+
   public boolean printWarnings() {
     boolean printed = false;
     if (warningInvalidParameterAnnotations != null) {
@@ -136,7 +138,7 @@
 
   public static class TestingOptions {
 
-    public boolean randomizeCallGraphLeaves = false;
+    public BiFunction<List<DexEncodedMethod>, CallGraph.Leaves, List<DexEncodedMethod>> irOrdering;
   }
 
   public static class AttributeRemovalOptions {
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 66844af..13a4c24 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -989,7 +989,7 @@
             builder.setMinApiLevel(minSdkVersion);
           }
           D8Output output = D8.run(builder.build());
-          output.write(Paths.get(resultPath));
+          output.write(Paths.get(resultPath), true);
           break;
         }
       case R8:
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 14d5e5e..e74eb2c 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -381,6 +381,10 @@
     return getPlatform().startsWith("Mac");
   }
 
+  public static boolean isWindows() {
+    return getPlatform().startsWith("Windows");
+  }
+
   public static boolean artSupported() {
     if (!isLinux() && !isMac()) {
       System.err.println("Testing on your platform is not fully supported. " +
diff --git a/src/test/java/com/android/tools/r8/d8/DexVersionTests.java b/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
index 0d72a52..2cdb4d7 100644
--- a/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
+++ b/src/test/java/com/android/tools/r8/d8/DexVersionTests.java
@@ -38,17 +38,17 @@
     D8Command.Builder arithmeticBuilder = D8Command.builder().addProgramFiles(ARITHMETIC_JAR);
     D8Command.Builder arrayAccessBuilder = D8Command.builder().addProgramFiles(ARRAYACCESS_JAR);
     D8Output output = D8.run(arrayAccessBuilder.build());
-    output.write(defaultApiFolder1.getRoot().toPath());
+    output.write(defaultApiFolder1.getRoot().toPath(), true);
     output = D8.run(arrayAccessBuilder.setMinApiLevel(Constants.ANDROID_O_API).build());
-    output.write(androidOApiFolder1.getRoot().toPath());
+    output.write(androidOApiFolder1.getRoot().toPath(), true);
     output = D8.run(arrayAccessBuilder.setMinApiLevel(Constants.ANDROID_N_API).build());
-    output.write(androidNApiFolder1.getRoot().toPath());
+    output.write(androidNApiFolder1.getRoot().toPath(), true);
     output = D8.run(arithmeticBuilder.build());
-    output.write(defaultApiFolder2.getRoot().toPath());
+    output.write(defaultApiFolder2.getRoot().toPath(), true);
     output = D8.run(arithmeticBuilder.setMinApiLevel(Constants.ANDROID_O_API).build());
-    output.write(androidOApiFolder2.getRoot().toPath());
+    output.write(androidOApiFolder2.getRoot().toPath(), true);
     output = D8.run(arithmeticBuilder.setMinApiLevel(Constants.ANDROID_N_API).build());
-    output.write(androidNApiFolder2.getRoot().toPath());
+    output.write(androidNApiFolder2.getRoot().toPath(), true);
   }
 
   private Path default1() {
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
index 2bc8dba..755fcab 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreDeterministicTest.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.Resource;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.conversion.CallGraph;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.OutputMode;
@@ -19,17 +21,29 @@
 import java.io.InputStream;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class R8GMSCoreDeterministicTest extends GMSCoreCompilationTestBase {
 
+  public List<DexEncodedMethod> shuffle(List<DexEncodedMethod> methods, CallGraph.Leaves leaves) {
+    Collections.shuffle(methods);
+    return methods;
+  }
+
   private AndroidApp doRun()
       throws IOException, ProguardRuleParserException, CompilationException, ExecutionException {
     R8Command command =
         R8Command.builder().addProgramFiles(Paths.get(GMSCORE_V7_DIR, GMSCORE_APK)).build();
-    return ToolHelper.runR8(command, options -> options.testing.randomizeCallGraphLeaves = true);
+    return ToolHelper.runR8(
+        command, options -> {
+          // For this test just do random shuffle.
+          options.testing.irOrdering = this::shuffle;
+          // Only use one thread to process to process in the order decided by the callback.
+          options.numberOfThreads = 1;
+        });
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/DeterministicProcessingTest.java b/src/test/java/com/android/tools/r8/ir/deterministic/DeterministicProcessingTest.java
index eaf9fe3..6acacda 100644
--- a/src/test/java/com/android/tools/r8/ir/deterministic/DeterministicProcessingTest.java
+++ b/src/test/java/com/android/tools/r8/ir/deterministic/DeterministicProcessingTest.java
@@ -10,38 +10,121 @@
 import com.android.tools.r8.CompilationException;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.conversion.CallGraph;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.smali.SmaliTestBase;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
 import java.io.IOException;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import org.junit.Test;
 
 public class DeterministicProcessingTest extends SmaliTestBase {
+  public List<DexEncodedMethod> shuffle(List<DexEncodedMethod> methods, CallGraph.Leaves leaves) {
+    Collections.shuffle(methods);
+    return methods;
+  }
 
+  // This test will process the code a number of times each time shuffling the order in which
+  // the methods are processed. It does not do a exhaustive probing of all permutations, so if
+  // this fails it might not fail consistently, but under all circumstances a failure does reveal
+  // non-determinism in the IR-processing.
   @Test
-  public void test()
+  public void shuffleOrderTest()
       throws IOException, ExecutionException, ProguardRuleParserException, CompilationException {
-    final int ITERATIONS = 10;
+    final int ITERATIONS = 25;
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(ToolHelper.getClassFileForTestClass(TestClass.class))
             .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()));
-    List<byte[]> results = new ArrayList<>();
+    byte[] expectedDex = null;
     for (int i = 0; i < ITERATIONS; i++) {
       AndroidApp result =
           ToolHelper.runR8(
-              builder.build(), options -> options.testing.randomizeCallGraphLeaves = true);
+              builder.build(), options -> {
+                // For this test just do random shuffle.
+                options.testing.irOrdering = this::shuffle;
+                // Only use one thread to process to process in the order decided by the callback.
+                options.numberOfThreads = 1;
+              });
       List<byte[]> dex = result.writeToMemory();
       assertEquals(1, dex.size());
+      if (i == 0) {
+        assert expectedDex == null;
+        expectedDex = dex.get(0);
+      } else {
+        assertArrayEquals(expectedDex, dex.get(0));
+      }
+    }
+  }
+
+  // Global variables used by the shuffler callback.
+  int iteration = 0;
+  Class testClass = null;
+
+  public List<DexEncodedMethod> permutationsOfTwo(
+      List<DexEncodedMethod> methods, CallGraph.Leaves leaves) {
+    if (!leaves.brokeCycles()) {
+      return methods;
+    }
+    methods.sort(Comparator.comparing(DexEncodedMethod::qualifiedName));
+    assertEquals(2, methods.size());
+    String className = testClass.getTypeName();
+    // Check that we are permutating the expected methods.
+    assertEquals(className + ".a", methods.get(0).qualifiedName());
+    assertEquals(className + ".b", methods.get(1).qualifiedName());
+    if (iteration == 1) {
+      Collections.swap(methods, 0, 1);
+    }
+    return methods;
+  }
+
+  public void runTest(Class clazz, boolean inline) throws Exception {
+    final int ITERATIONS = 2;
+    testClass = clazz;
+    R8Command.Builder builder =
+        R8Command.builder()
+            .addProgramFiles(ToolHelper.getClassFileForTestClass(clazz))
+            .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()));
+    List<byte[]> results = new ArrayList<>();
+    for (iteration = 0; iteration < ITERATIONS; iteration++) {
+      AndroidApp result =
+          ToolHelper.runR8(
+              builder.build(), options -> {
+                options.inlineAccessors = inline;
+                // Callback to determine IR processing order.
+                options.testing.irOrdering = this::permutationsOfTwo;
+                // Only use one thread to process to process in the order decided by the callback.
+                options.numberOfThreads = 1;
+              });
+      List<byte[]> dex = result.writeToMemory();
+      DexInspector x = new DexInspector(result);
+      assertEquals(1, dex.size());
       results.add(dex.get(0));
-      System.out.println(dex.get(0).length);
     }
     for (int i = 0; i < ITERATIONS - 1; i++) {
       assertArrayEquals(results.get(i), results.get(i + 1));
     }
   }
+
+  @Test
+  public void testReturnArguments() throws Exception {
+    runTest(TestClassReturnsArgument.class, false);
+  }
+
+  @Test
+  public void testReturnConstant() throws Exception {
+    runTest(TestClassReturnsConstant.class, false);
+  }
+
+  @Test
+  public void testInline() throws Exception {
+    runTest(TestClassInline.class, true);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassInline.java b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassInline.java
new file mode 100644
index 0000000..aa50f02
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassInline.java
@@ -0,0 +1,24 @@
+// 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.ir.deterministic;
+
+public class TestClassInline {
+
+  public static boolean alwaysFalse() {
+    return false;
+  }
+
+  public static int a() {
+    return b();
+  }
+
+  public static int b() {
+    if (alwaysFalse()) {
+      a();
+      return 0;
+    }
+    return 1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsArgument.java b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsArgument.java
new file mode 100644
index 0000000..ac70401
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsArgument.java
@@ -0,0 +1,19 @@
+// 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.ir.deterministic;
+
+import java.util.Random;
+
+public class TestClassReturnsArgument {
+
+  public static int a(int x) {
+    return b(new Random().nextInt());
+  }
+
+  public static int b(int x) {
+    a(1);
+    return x;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsConstant.java b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsConstant.java
new file mode 100644
index 0000000..7d63f60
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/deterministic/TestClassReturnsConstant.java
@@ -0,0 +1,19 @@
+// 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.ir.deterministic;
+
+import java.util.Random;
+
+public class TestClassReturnsConstant {
+
+  public static int a(int x) {
+    return b(new Random().nextInt());
+  }
+
+  public static int b(int x) {
+    a(x);
+    return 1;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
index 548d9d0..2db35c4 100644
--- a/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
+++ b/src/test/java/com/android/tools/r8/jasmin/BooleanByteConfusion.java
@@ -10,13 +10,12 @@
 
 public class BooleanByteConfusion extends JasminTestBase {
 
-  private void runTest(JasminBuilder builder, String main, String expected) throws Exception {
+  private void runTest(JasminBuilder builder, String main) throws Exception {
     String javaResult = runOnJava(builder, main);
-    assertEquals(expected, javaResult);
     String artResult = runOnArt(builder, main);
-    assertEquals(expected, artResult);
+    assertEquals(javaResult, artResult);
     String dxArtResult = runOnArtDx(builder, main);
-    assertEquals(expected, dxArtResult);
+    assertEquals(javaResult, dxArtResult);
   }
 
   @Test
@@ -108,6 +107,6 @@
         "  invokestatic Test/foo(Z)V",
         "  return");
 
-    runTest(builder, clazz.name, "true\nfalse\nnull array\n");
+    runTest(builder, clazz.name);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
index 4f55d6a..1812fe1 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidClassNames.java
@@ -6,6 +6,7 @@
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.fail;
 
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.CompilationError;
 import java.util.Arrays;
 import java.util.Collection;
@@ -46,19 +47,19 @@
   public static Collection<Object[]> data() {
     return Arrays.asList(new Object[][] {
         { new String(new int[] { 0x00a0 }, 0, 1), true },
-        { new String(new int[] { 0x2000 }, 0, 1), true },
-        { new String(new int[] { 0x200f }, 0, 1), true },
-        { new String(new int[] { 0x2028 }, 0, 1), true },
-        { new String(new int[] { 0x202f }, 0, 1), true },
+        { new String(new int[] { 0x2000 }, 0, 1), !ToolHelper.isWindows() },
+        { new String(new int[] { 0x200f }, 0, 1), !ToolHelper.isWindows() },
+        { new String(new int[] { 0x2028 }, 0, 1), !ToolHelper.isWindows() },
+        { new String(new int[] { 0x202f }, 0, 1), !ToolHelper.isWindows() },
         { new String(new int[] { 0xd800 }, 0, 1), false },
         { new String(new int[] { 0xdfff }, 0, 1), false },
-        { new String(new int[] { 0xfff0 }, 0, 1), true },
-        { new String(new int[] { 0xffff }, 0, 1), true },
+        { new String(new int[] { 0xfff0 }, 0, 1), !ToolHelper.isWindows() },
+        { new String(new int[] { 0xffff }, 0, 1), !ToolHelper.isWindows() },
         { "a/b/c/a/D/", true },
-        { "a<b", true },
-        { "a>b", true },
-        { "<a>b", true },
-        { "<a>", true }
+        { "a<b", !ToolHelper.isWindows() },
+        { "a>b", !ToolHelper.isWindows() },
+        { "<a>b", !ToolHelper.isWindows() },
+        { "<a>", !ToolHelper.isWindows() }
     });
   }
 
@@ -71,9 +72,9 @@
         ".limit locals 1",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  ldc \"MAIN\"",
-        "  invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V",
+        "  invokevirtual java/io/PrintStream.print(Ljava/lang/String;)V",
         "  return");
 
-    runTest(builder, clazz.name, "MAIN\n");
+    runTest(builder, clazz.name, "MAIN");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
index 3b4a75f..791ecca 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidFieldNames.java
@@ -70,9 +70,9 @@
         ".limit locals 1",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  getstatic Test/" + name + " I",
-        "  invokevirtual java/io/PrintStream.println(I)V",
+        "  invokevirtual java/io/PrintStream.print(I)V",
         "  return");
 
-    runTest(builder, clazz.name, "42\n");
+    runTest(builder, clazz.name, "42");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
index 7d92e60..f8b7615 100644
--- a/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
+++ b/src/test/java/com/android/tools/r8/jasmin/InvalidMethodNames.java
@@ -72,7 +72,7 @@
         ".limit locals 0",
         "  getstatic java/lang/System/out Ljava/io/PrintStream;",
         "  ldc \"CALLED\"",
-        "  invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V",
+        "  invokevirtual java/io/PrintStream.print(Ljava/lang/String;)V",
         "  return");
 
     clazz.addMainMethod(
@@ -81,6 +81,6 @@
         "  invokestatic Test/" + name + "()V",
         "  return");
 
-    runTest(builder, clazz.name, "CALLED\n");
+    runTest(builder, clazz.name, "CALLED");
   }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index ca242b7..2549ff1 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -142,9 +142,9 @@
           .collect(Collectors.toList());
       Collections.sort(resultMainDexList);
       String[] refList = new String(Files.readAllBytes(
-          expectedMainDexList), StandardCharsets.UTF_8).split(ToolHelper.LINE_SEPARATOR);
+          expectedMainDexList), StandardCharsets.UTF_8).split("\n");
       for (int i = 0; i < refList.length; i++) {
-        String reference = refList[i];
+        String reference = refList[i].trim();
         String computed = resultMainDexList.get(i);
         if (reference.contains("-$$Lambda$")) {
           // For lambda classes we check that there is a lambda class for the right containing
diff --git a/third_party/proguard/proguard5.3.3.tar.gz.sha1 b/third_party/proguard/proguard5.3.3.tar.gz.sha1
deleted file mode 100644
index 7a37d5c..0000000
--- a/third_party/proguard/proguard5.3.3.tar.gz.sha1
+++ /dev/null
@@ -1 +0,0 @@
-b197e2783baf8ade021d5feeaf609551735cd7b0
\ No newline at end of file
diff --git a/third_party/proguard/proguard_internal_159423826.tar.gz.sha1 b/third_party/proguard/proguard_internal_159423826.tar.gz.sha1
new file mode 100644
index 0000000..8fdbf19
--- /dev/null
+++ b/third_party/proguard/proguard_internal_159423826.tar.gz.sha1
@@ -0,0 +1 @@
+933e970c611a306ef5d755234820188bc50c4c60
\ No newline at end of file
diff --git a/third_party/youtube/youtube.android_12.22.tar.gz.sha1 b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
index e2544d2..8f6813c 100644
--- a/third_party/youtube/youtube.android_12.22.tar.gz.sha1
+++ b/third_party/youtube/youtube.android_12.22.tar.gz.sha1
@@ -1 +1 @@
-284258a4cb50e21f972de603b57b5e9be1e78308
\ No newline at end of file
+73c4880898d734064815d0426d8fe84ee6d075b4
\ No newline at end of file
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 133f852..86833b2 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -133,6 +133,9 @@
         args.extend(['--pg-conf', pgconf])
     if options.k:
       args.extend(['--pg-conf', options.k])
+    if 'multidexrules' in values:
+      for rules in values['multidexrules']:
+        args.extend(['--multidex-rules', rules])
 
   if not options.no_libraries and 'libraries' in values:
     for lib in values['libraries']:
diff --git a/tools/youtube_data.py b/tools/youtube_data.py
index e519571..b32a372 100644
--- a/tools/youtube_data.py
+++ b/tools/youtube_data.py
@@ -66,8 +66,13 @@
     },
     'deploy' : {
       'inputs': ['%s_deploy.jar' % V12_22_PREFIX],
-      'pgconf': ['%s_proguard.config' % V12_22_PREFIX,
-                 '%s/proguardsettings/YouTubeRelease_proguard.config' % THIRD_PARTY],
+      'pgconf': [
+          '%s_proguard.config' % V12_22_PREFIX,
+          '%s/proguardsettings/YouTubeRelease_proguard.config' % THIRD_PARTY],
+      'multidexrules' : [
+          os.path.join(V12_22_BASE, 'mainDexClasses.rules'),
+          os.path.join(V12_22_BASE, 'main-dex-classes-release.cfg'),
+          os.path.join(V12_22_BASE, 'main_dex_YouTubeRelease_proguard.cfg')],
     },
     'proguarded' : {
       'inputs': ['%s_proguard.jar' % V12_22_PREFIX],