Towards different distribution strategies for multi startup dex files
Bug: b/298617875
Change-Id: I54b929cbe1b9d08037bd06194b03009a48a3c6b0
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 fc0c691..65c0f2e 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -29,6 +29,7 @@
import com.android.tools.r8.graph.lens.GraphLens;
import com.android.tools.r8.ir.conversion.LensCodeRewriterUtils;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.profile.startup.distribution.MultiStartupDexDistributor;
import com.android.tools.r8.profile.startup.profile.StartupProfile;
import com.android.tools.r8.shaking.MainDexInfo;
import com.android.tools.r8.synthesis.SyntheticNaming;
@@ -249,7 +250,7 @@
timing);
}
- void addClass(DexProgramClass clazz) {
+ public void addClass(DexProgramClass clazz) {
transaction.addClassAndDependencies(clazz);
}
@@ -258,7 +259,7 @@
|| (transaction.getNumberOfFields() > maxEntries);
}
- boolean isFull() {
+ public boolean isFull() {
return isFull(MAX_ENTRIES);
}
@@ -1172,15 +1173,15 @@
/**
* Helper class to cycle through the set of virtual files.
*
- * Iteration starts at the first file and iterates through all files.
+ * <p>Iteration starts at the first file and iterates through all files.
*
- * When {@link VirtualFileCycler#restart()} is called iteration of all files is restarted at the
- * current file.
+ * <p>When {@link VirtualFileCycler#restart()} is called iteration of all files is restarted at
+ * the current file.
*
- * If the fill strategy indicate that the main dex file should be minimal, then the main dex file
- * will not be part of the iteration.
+ * <p>If the fill strategy indicate that the main dex file should be minimal, then the main dex
+ * file will not be part of the iteration.
*/
- static class VirtualFileCycler {
+ public static class VirtualFileCycler {
private final List<VirtualFile> files;
private final List<VirtualFile> filesForDistribution;
@@ -1262,7 +1263,7 @@
activeFiles = Iterators.limit(allFilesCyclic, filesForDistribution.size());
}
- VirtualFile addFile() {
+ public VirtualFile addFile() {
VirtualFile newFile = internalAddFile();
reset();
return newFile;
@@ -1466,19 +1467,9 @@
} else {
virtualFile.abortTransaction();
- // If the above failed, then add the startup classes one by one.
- for (DexProgramClass startupClass : classPartioning.getStartupClasses()) {
- virtualFile.addClass(startupClass);
- if (hasSpaceForTransaction(virtualFile, options)) {
- virtualFile.commitTransaction();
- } else {
- virtualFile.abortTransaction();
- virtualFile = cycler.addFile();
- virtualFile.addClass(startupClass);
- assert hasSpaceForTransaction(virtualFile, options);
- virtualFile.commitTransaction();
- }
- }
+ // If the above failed, then apply the selected multi startup dex distribution strategy.
+ MultiStartupDexDistributor distributor = MultiStartupDexDistributor.get(options);
+ distributor.distribute(classPartioning.getStartupClasses(), cycler, virtualFile);
options.reporter.warning(
createStartupClassesOverflowDiagnostic(cycler.filesForDistribution.size()));
diff --git a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
index 4180266..632fd74 100644
--- a/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
+++ b/src/main/java/com/android/tools/r8/profile/startup/StartupOptions.java
@@ -4,7 +4,8 @@
package com.android.tools.r8.profile.startup;
-import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyForDevelopmentOrDefault;
+import static com.android.tools.r8.utils.SystemPropertyUtils.getSystemPropertyOrDefault;
+import static com.android.tools.r8.utils.SystemPropertyUtils.parseSystemPropertyOrDefault;
import com.android.tools.r8.startup.StartupProfileProvider;
import com.android.tools.r8.utils.SystemPropertyUtils;
@@ -20,8 +21,7 @@
* (non-startup) classes will be placed in classes2.dex, ..., classesN.dex.
*/
private boolean enableMinimalStartupDex =
- parseSystemPropertyForDevelopmentOrDefault(
- "com.android.tools.r8.startup.minimalstartupdex", true);
+ parseSystemPropertyOrDefault("com.android.tools.r8.startup.minimalstartupdex", true);
/**
* When enabled, optimizations crossing the startup/non-startup boundary will be allowed.
@@ -30,8 +30,7 @@
* result of optimizations such as inlining and class merging.
*/
private boolean enableStartupBoundaryOptimizations =
- parseSystemPropertyForDevelopmentOrDefault(
- "com.android.tools.r8.startup.boundaryoptimizations", false);
+ parseSystemPropertyOrDefault("com.android.tools.r8.startup.boundaryoptimizations", false);
/**
* When enabled, each method that is not classified as a startup method at the end of compilation
@@ -41,15 +40,17 @@
* rewrites the startup list in presence of optimizations).
*/
private boolean enableStartupCompletenessCheckForTesting =
- parseSystemPropertyForDevelopmentOrDefault(
- "com.android.tools.r8.startup.completenesscheck", false);
+ parseSystemPropertyOrDefault("com.android.tools.r8.startup.completenesscheck", false);
/**
* When enabled, the layout of the primary dex file will be generated using the startup list,
* using {@link com.android.tools.r8.dex.StartupMixedSectionLayoutStrategy}.
*/
private boolean enableStartupLayoutOptimizations =
- parseSystemPropertyForDevelopmentOrDefault("com.android.tools.r8.startup.layout", true);
+ parseSystemPropertyOrDefault("com.android.tools.r8.startup.layout", true);
+
+ private String multiStartupDexDistributionStrategyName =
+ getSystemPropertyOrDefault("com.android.tools.r8.startup.multistartupdexdistribution", null);
private Collection<StartupProfileProvider> startupProfileProviders;
@@ -101,6 +102,10 @@
return this;
}
+ public String getMultiStartupDexDistributionStrategyName() {
+ return multiStartupDexDistributionStrategyName;
+ }
+
public boolean hasStartupProfileProviders() {
return startupProfileProviders != null && !startupProfileProviders.isEmpty();
}
diff --git a/src/main/java/com/android/tools/r8/profile/startup/distribution/MultiStartupDexDistributor.java b/src/main/java/com/android/tools/r8/profile/startup/distribution/MultiStartupDexDistributor.java
new file mode 100644
index 0000000..471ea08
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/profile/startup/distribution/MultiStartupDexDistributor.java
@@ -0,0 +1,69 @@
+// Copyright (c) 2023, 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.profile.startup.distribution;
+
+import com.android.tools.r8.dex.VirtualFile;
+import com.android.tools.r8.dex.VirtualFile.VirtualFileCycler;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.utils.InternalOptions;
+import java.util.List;
+
+public abstract class MultiStartupDexDistributor {
+
+ public abstract void distribute(
+ List<DexProgramClass> classes, VirtualFileCycler cycler, VirtualFile virtualFile);
+
+ boolean hasSpaceForTransaction(VirtualFile virtualFile) {
+ return !virtualFile.isFull();
+ }
+
+ public static MultiStartupDexDistributor getDefault() {
+ return new ClassByNameMultiStartupDexDistributor();
+ }
+
+ public static MultiStartupDexDistributor get(InternalOptions options) {
+ String strategyName = options.getStartupOptions().getMultiStartupDexDistributionStrategyName();
+ if (strategyName == null) {
+ return getDefault();
+ }
+ switch (strategyName) {
+ case "classByName":
+ return getDefault();
+ case "classByNumberOfStartupMethods":
+ throw new Unimplemented();
+ case "classByNumberOfStartupMethodsMinusNumberOfNonStartupMethods":
+ throw new Unimplemented();
+ case "packageByName":
+ throw new Unimplemented();
+ case "packageByNumberOfStartupMethods":
+ throw new Unimplemented();
+ default:
+ throw new IllegalArgumentException(
+ "Unexpected multi startup dex distribution strategy: " + strategyName);
+ }
+ }
+
+ private static class ClassByNameMultiStartupDexDistributor extends MultiStartupDexDistributor {
+
+ @Override
+ public void distribute(
+ List<DexProgramClass> classes, VirtualFileCycler cycler, VirtualFile virtualFile) {
+ // Add the (already sorted) startup classes one by one.
+ for (DexProgramClass startupClass : classes) {
+ virtualFile.addClass(startupClass);
+ if (hasSpaceForTransaction(virtualFile)) {
+ virtualFile.commitTransaction();
+ } else {
+ virtualFile.abortTransaction();
+ virtualFile = cycler.addFile();
+ virtualFile.addClass(startupClass);
+ assert hasSpaceForTransaction(virtualFile);
+ virtualFile.commitTransaction();
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java b/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java
index a984560..57cb56d 100644
--- a/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/SystemPropertyUtils.java
@@ -22,6 +22,10 @@
}
}
+ public static String getSystemPropertyOrDefault(String propertyName, String defaultValue) {
+ return System.getProperty(propertyName, defaultValue);
+ }
+
public static String getSystemPropertyForDevelopment(String propertyName) {
return Version.isDevelopmentVersion() ? System.getProperty(propertyName) : null;
}