Merge "Decrease peak memory usage for the common case."
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 6988b06..bca7a9f 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.MainDexError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.conversion.IRConverter;
@@ -151,7 +152,7 @@
     }
   }
 
-  static CompilationResult runForTesting(
+  private static CompilationResult runForTesting(
       AndroidApp inputApp, InternalOptions options, ExecutorService executor) throws IOException {
     try {
       assert !inputApp.hasPackageDistribution();
@@ -184,12 +185,13 @@
 
       options.printWarnings();
       return output;
+    } catch (MainDexError mainDexError) {
+      throw new CompilationError(mainDexError.getMessageForD8());
     } catch (ExecutionException e) {
       if (e.getCause() instanceof CompilationError) {
         throw (CompilationError) e.getCause();
-      } else {
-        throw new RuntimeException(e.getMessage(), e.getCause());
       }
+      throw new RuntimeException(e.getMessage(), e.getCause());
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 19fc358..fd5c29f 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.MainDexError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.ClassAndMemberPublicizer;
@@ -363,12 +364,13 @@
 
       options.printWarnings();
       return new CompilationResult(androidApp, application, appInfo);
+    } catch (MainDexError mainDexError) {
+      throw new CompilationError(mainDexError.getMessageForR8());
     } catch (ExecutionException e) {
       if (e.getCause() instanceof CompilationError) {
         throw (CompilationError) e.getCause();
-      } else {
-        throw new RuntimeException(e.getMessage(), e.getCause());
       }
+      throw new RuntimeException(e.getMessage(), e.getCause());
     } finally {
       // Dump timings.
       if (options.printTimes) {
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 6586a57..5613169 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -3,9 +3,6 @@
 // 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.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -134,7 +131,7 @@
       if (options.outputMode == OutputMode.FilePerClass) {
         assert packageDistribution == null :
             "Cannot combine package distribution definition with file-per-class option.";
-        distributor = new FilePerClassDistributor(this);
+        distributor = new VirtualFile.FilePerClassDistributor(this);
       } else if (!options.canUseMultidex()
           && options.mainDexKeepRules.isEmpty()
           && application.mainDexList.isEmpty()) {
@@ -148,9 +145,10 @@
       } else if (packageDistribution != null) {
         assert !options.minimalMainDex :
             "Cannot combine package distribution definition with minimal-main-dex option.";
-        distributor = new PackageMapDistributor(this, packageDistribution, executorService);
+        distributor =
+            new VirtualFile.PackageMapDistributor(this, packageDistribution, executorService);
       } else {
-        distributor = new FillFilesDistributor(this, options.minimalMainDex);
+        distributor = new VirtualFile.FillFilesDistributor(this, options.minimalMainDex);
       }
       Map<Integer, VirtualFile> newFiles = distributor.run();
 
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 f309670..3432b62 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
+import com.android.tools.r8.errors.MainDexError;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -174,33 +175,15 @@
     return isFull(transaction.getNumberOfMethods(), transaction.getNumberOfFields(), MAX_ENTRIES);
   }
 
-  void throwIfFull(boolean multiDexEnabled) {
+  void throwIfFull(boolean hasMainDexList) {
     if (!isFull()) {
       return;
     }
-    StringBuilder messageBuilder = new StringBuilder();
-    // General message: Cannot fit.
-    messageBuilder.append("Cannot fit requested classes in ");
-    messageBuilder.append(multiDexEnabled ? "the main-" : "a single ");
-    messageBuilder.append("dex file.\n");
-    // Suggest supplying the main-dex list or explicitly mention that main-dex list is too large.
-    if (multiDexEnabled) {
-      messageBuilder.append("The list of classes for the main-dex list is too large.\n");
-    } else {
-      messageBuilder.append("Try supplying a main-dex list.\n");
-    }
-    // Show the numbers of methods and/or fields that exceed the limit.
-    if (transaction.getNumberOfMethods() > MAX_ENTRIES) {
-      messageBuilder.append("# methods: ");
-      messageBuilder.append(transaction.getNumberOfMethods());
-      messageBuilder.append(" > ").append(MAX_ENTRIES).append('\n');
-    }
-    if (transaction.getNumberOfFields() > MAX_ENTRIES) {
-      messageBuilder.append("# fields: ");
-      messageBuilder.append(transaction.getNumberOfFields());
-      messageBuilder.append(" > ").append(MAX_ENTRIES).append('\n');
-    }
-    throw new CompilationError(messageBuilder.toString());
+    throw new MainDexError(
+        hasMainDexList,
+        transaction.getNumberOfMethods(),
+        transaction.getNumberOfFields(),
+        MAX_ENTRIES);
   }
 
   private boolean isFilledEnough(FillStrategy fillStrategy) {
@@ -276,7 +259,6 @@
           if (clazz != null && clazz.isProgramClass()) {
             DexProgramClass programClass = (DexProgramClass) clazz;
             mainDexFile.addClass(programClass);
-            mainDexFile.throwIfFull(true);
             classes.remove(programClass);
           } else {
             System.out.println(
@@ -286,6 +268,7 @@
           }
           mainDexFile.commitTransaction();
         }
+        mainDexFile.throwIfFull(true);
       }
     }
 
@@ -364,9 +347,9 @@
 
       for (DexProgramClass programClass : classes) {
         mainDexFile.addClass(programClass);
-        mainDexFile.throwIfFull(false);
       }
       mainDexFile.commitTransaction();
+      mainDexFile.throwIfFull(false);
       return nameToFileMap;
     }
   }
diff --git a/src/main/java/com/android/tools/r8/errors/MainDexError.java b/src/main/java/com/android/tools/r8/errors/MainDexError.java
new file mode 100644
index 0000000..4c23aca
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/errors/MainDexError.java
@@ -0,0 +1,89 @@
+// 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.errors;
+
+/**
+ * Exception regarding main-dex list and main dex rules.
+ *
+ * Depending on tool kind, this exception should be massaged, e.g., adding appropriate suggestions,
+ * and re-thrown as {@link CompilationError}, which will be in turn informed to the user as an
+ * expected compilation error.
+ */
+public class MainDexError extends RuntimeException {
+
+  private final boolean hasMainDexList;
+  private final long numOfMethods;
+  private final long numOfFields;
+  private final long maxNumOfEntries;
+
+  public MainDexError(
+      boolean hasMainDexList, long numOfMethods, long numOfFields, long maxNumOfEntries) {
+    this.hasMainDexList = hasMainDexList;
+    this.numOfMethods = numOfMethods;
+    this.numOfFields = numOfFields;
+    this.maxNumOfEntries = maxNumOfEntries;
+  }
+
+  private String getGeneralMessage() {
+    StringBuilder messageBuilder = new StringBuilder();
+    // General message: Cannot fit.
+    messageBuilder.append("Cannot fit requested classes in ");
+    messageBuilder.append(hasMainDexList ? "the main-" : "a single ");
+    messageBuilder.append("dex file.\n");
+
+    return messageBuilder.toString();
+  }
+
+  private String getNumberRelatedMessage() {
+    StringBuilder messageBuilder = new StringBuilder();
+    // Show the numbers of methods and/or fields that exceed the limit.
+    if (numOfMethods > maxNumOfEntries) {
+      messageBuilder.append("# methods: ");
+      messageBuilder.append(numOfMethods);
+      messageBuilder.append(" > ").append(maxNumOfEntries).append('\n');
+    }
+    if (numOfFields > maxNumOfEntries) {
+      messageBuilder.append("# fields: ");
+      messageBuilder.append(numOfFields);
+      messageBuilder.append(" > ").append(maxNumOfEntries).append('\n');
+    }
+
+    return messageBuilder.toString();
+  }
+
+  @Override
+  public String getMessage() {
+    // Placeholder to generate a general error message for other (minor) utilities:
+    //   Bisect, disassembler, dexsegments.
+    // Implement tool-specific error message generator, like D8 and R8 below, if necessary.
+    return getGeneralMessage() + getNumberRelatedMessage();
+  }
+
+  public String getMessageForD8() {
+    StringBuilder messageBuilder = new StringBuilder();
+    messageBuilder.append(getGeneralMessage());
+    if (hasMainDexList) {
+      messageBuilder.append("Classes required by the main-dex list ");
+      messageBuilder.append("do not fit in one dex.\n");
+    } else {
+      messageBuilder.append("Try supplying a main-dex list.\n");
+    }
+    messageBuilder.append(getNumberRelatedMessage());
+    return messageBuilder.toString();
+  }
+
+  public String getMessageForR8() {
+    StringBuilder messageBuilder = new StringBuilder();
+    messageBuilder.append(getGeneralMessage());
+    if (hasMainDexList) {
+      messageBuilder.append("Classes required by main dex rules and the main-dex list ");
+      messageBuilder.append("do not fit in one dex.\n");
+    } else {
+      messageBuilder.append("Try supplying a main-dex list or main dex rules.\n");
+    }
+    messageBuilder.append(getNumberRelatedMessage());
+    return messageBuilder.toString();
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index b83b193..0b3395b 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -752,7 +752,10 @@
       "122-npe",
 
       // Calls some internal art methods that cannot tolerate inlining.
-      "466-get-live-vreg"
+      "466-get-live-vreg",
+
+      // Requires a certain call pattern to surface an Art bug.
+      "534-checker-bce-deoptimization"
   );
 
   private static List<String> failuresToTriage = ImmutableList.of(
@@ -905,9 +908,9 @@
   }
 
   private static Map<SpecificationKey, TestSpecification> getTestsMap(
-      CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm version) {
+      CompilerUnderTest compilerUnderTest, CompilationMode compilationMode, DexVm dexVm) {
     File artTestDir = new File(ART_TESTS_DIR);
-    if (version != DexVm.ART_DEFAULT) {
+    if (dexVm != DexVm.ART_DEFAULT) {
       artTestDir = new File(ART_LEGACY_TESTS_DIR);
     }
     if (!artTestDir.exists()) {
@@ -927,7 +930,6 @@
     // Collect the tests requiring the native library.
     Set<String> useNativeLibrary = Sets.newHashSet(useJNI);
 
-    DexVm dexVm = ToolHelper.getDexVm();
     for (DexTool dexTool : DexTool.values()) {
       // Collect the tests failing code generation.
       Set<String> failsWithCompiler =
@@ -959,11 +961,11 @@
         failsWithArt.addAll(tmpSet);
       }
 
-      if (!ToolHelper.isDefaultDexVm()) {
+      if (!ToolHelper.isDefaultDexVm(dexVm)) {
         // Generally failing when not TOT art.
         failsWithArt.addAll(expectedToFailRunWithArtNonDefault);
         // Version specific failures
-        failsWithArt.addAll(expectedToFailRunWithArtVersion.get(ToolHelper.getDexVm()));
+        failsWithArt.addAll(expectedToFailRunWithArtVersion.get(dexVm));
       }
 
       // Collect the tests failing with output differences in Art.
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index d0032cd..1683557 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -341,8 +341,8 @@
   }
 
   // Returns if the passed in vm to use is the default.
-  public static boolean isDefaultDexVm() {
-    return getDexVm() == DexVm.ART_DEFAULT;
+  public static boolean isDefaultDexVm(DexVm dexVm) {
+    return dexVm == DexVm.ART_DEFAULT;
   }
 
   public static DexVm getDexVm() {
diff --git a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
index 5af5c0c..dbe776d 100644
--- a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
+++ b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.Constants;
@@ -35,6 +36,8 @@
 import org.junit.rules.TemporaryFolder;
 
 public class CompatDxTests {
+  private static final int MAX_METHOD_COUNT = Constants.U16BIT_MAX;
+
   private static final String EXAMPLE_JAR_FILE1 = "build/test/examples/arithmetic.jar";
   private static final String EXAMPLE_JAR_FILE2 = "build/test/examples/barray.jar";
 
@@ -44,9 +47,6 @@
   private static final String NUM_THREADS_5 = "--num-threads=5";
 
   @Rule
-  public ExpectedException thrown = ExpectedException.none();
-
-  @Rule
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   @Test
@@ -104,7 +104,7 @@
     // Generate an application that fills the whole dex file.
     AndroidApp generated =
         MainDexListTests.generateApplication(
-            ImmutableList.of("A"), Constants.ANDROID_L_API, Constants.U16BIT_MAX + 1);
+            ImmutableList.of("A"), Constants.ANDROID_L_API, MAX_METHOD_COUNT + 1);
     Path applicationJar = temp.newFile("application.jar").toPath();
     generated.write(applicationJar, OutputMode.Indexed);
     runDexer(applicationJar.toString());
@@ -114,11 +114,19 @@
   public void singleDexProgramIsTooLarge() throws IOException, ExecutionException {
     // Generate an application that will not fit into a single dex file.
     AndroidApp generated = MainDexListTests.generateApplication(
-        ImmutableList.of("A", "B"), Constants.ANDROID_L_API, Constants.U16BIT_MAX / 2 + 2);
+        ImmutableList.of("A", "B"), Constants.ANDROID_L_API, MAX_METHOD_COUNT / 2 + 2);
     Path applicationJar = temp.newFile("application.jar").toPath();
     generated.write(applicationJar, OutputMode.Indexed);
-    thrown.expect(CompilationError.class);
-    runDexer(applicationJar.toString());
+    try {
+      runDexer(applicationJar.toString());
+      fail("Expect to fail, for there are many classes while multidex is not enabled.");
+    } catch (CompilationError e) {
+      // Make sure {@link MonoDexDistributor} was used.
+      assertTrue(e.getMessage().contains("single dex file"));
+      // Make sure what exceeds the limit is the number of methods.
+      assertTrue(e.getMessage().contains("# methods: "
+          + String.valueOf((MAX_METHOD_COUNT / 2 + 2) * 2)));
+    }
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 8befa70..714792d 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -16,6 +16,7 @@
 import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.errors.MainDexError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.Code;
@@ -157,7 +158,8 @@
       // Make sure {@link MonoDexDistributor} was _not_ used.
       assertFalse(e.getMessage().contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
-      assertTrue(e.getMessage().contains("# methods"));
+      assertTrue(e.getMessage().contains("# methods: "
+          + String.valueOf(TWO_LARGE_CLASSES.size() * MAX_METHOD_COUNT)));
     }
   }
 
@@ -197,7 +199,8 @@
       // Make sure {@link MonoDexDistributor} was _not_ used.
       assertFalse(e.getMessage().contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
-      assertTrue(e.getMessage().contains("# methods"));
+      assertTrue(e.getMessage().contains("# methods: "
+          + String.valueOf(MANY_CLASSES_COUNT * MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS)));
     }
   }
 
@@ -283,7 +286,7 @@
     }
     addMainListFile(mainLists, mainList);
 
-    // Same in reverese order
+    // Same in reverse order
     addMainListFile(mainLists, Lists.reverse(mainList));
 
     // Mixed partial list.
@@ -347,11 +350,12 @@
       generateApplication(
           MANY_CLASSES, Constants.ANDROID_K_API, false, MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS);
       fail("Expect to fail, for there are many classes while multidex is not enabled.");
-    } catch (CompilationError e) {
+    } catch (MainDexError e) {
       // Make sure {@link MonoDexDistributor} was used.
       assertTrue(e.getMessage().contains("single dex file"));
       // Make sure what exceeds the limit is the number of methods.
-      assertTrue(e.getMessage().contains("# methods"));
+      assertTrue(e.getMessage().contains("# methods: "
+          + String.valueOf(MANY_CLASSES_COUNT * MANY_CLASSES_MULTI_DEX_METHODS_PER_CLASS)));
     }
   }
 
diff --git a/tools/test.py b/tools/test.py
index 53a7b1e..ba459e7 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -66,7 +66,7 @@
   u_dir = uuid.uuid4()
   destination = 'gs://%s/%s' % (BUCKET, u_dir)
   utils.upload_html_to_cloud_storage(upload_dir, destination)
-  url = 'http://storage.googleapis.com/%s/%s/index.html' % (BUCKET, u_dir)
+  url = 'http://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, u_dir)
   print 'Test results available at: %s' % url
 
 def Main():