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],