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():