Merge commit 'e97aa141cfd10dd7fbb19a679d3267c482a769d6' into dev-release
diff --git a/infra/config/global/generated/luci-scheduler.cfg b/infra/config/global/generated/luci-scheduler.cfg
index 58167f9..2c77bc7 100644
--- a/infra/config/global/generated/luci-scheduler.cfg
+++ b/infra/config/global/generated/luci-scheduler.cfg
@@ -403,7 +403,7 @@
triggering_policy {
kind: GREEDY_BATCHING
max_concurrent_invocations: 1
- max_batch_size: 1
+ max_batch_size: 20
}
buildbucket {
server: "cr-buildbucket.appspot.com"
diff --git a/infra/config/global/main.star b/infra/config/global/main.star
index 303dffe..4949fc2 100755
--- a/infra/config/global/main.star
+++ b/infra/config/global/main.star
@@ -306,7 +306,7 @@
triggering_policy = scheduler.policy(
kind = scheduler.GREEDY_BATCHING_KIND,
max_concurrent_invocations = 1,
- max_batch_size = 1
+ max_batch_size = 1 if "release" in name else 20
),
priority = 25,
properties = {
diff --git a/src/main/java/com/android/tools/r8/FeatureSplit.java b/src/main/java/com/android/tools/r8/FeatureSplit.java
index 9b55bde..43e553c 100644
--- a/src/main/java/com/android/tools/r8/FeatureSplit.java
+++ b/src/main/java/com/android/tools/r8/FeatureSplit.java
@@ -44,6 +44,11 @@
public boolean isBase() {
return true;
}
+
+ @Override
+ public boolean isStartupBase() {
+ return true;
+ }
};
private final ProgramConsumer programConsumer;
@@ -59,6 +64,10 @@
return false;
}
+ public boolean isStartupBase() {
+ return false;
+ }
+
public List<ProgramResourceProvider> getProgramResourceProviders() {
return programResourceProviders;
}
diff --git a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
index 977a836..2477240 100644
--- a/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
+++ b/src/main/java/com/android/tools/r8/androidapi/AndroidApiClass.java
@@ -30,28 +30,28 @@
public abstract int getMemberCount();
- public TraversalContinuation<?> visitFields(
- BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation<?>> visitor) {
+ public TraversalContinuation<?, ?> visitFields(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation<?, ?>> visitor) {
return visitFields(visitor, classReference, 1);
}
- public TraversalContinuation<?> visitMethods(
- BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation<?>> visitor) {
+ public TraversalContinuation<?, ?> visitMethods(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation<?, ?>> visitor) {
return visitMethods(visitor, classReference, 1);
}
- protected abstract TraversalContinuation<?> visitFields(
- BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation<?>> visitor,
+ protected abstract TraversalContinuation<?, ?> visitFields(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation<?, ?>> visitor,
ClassReference holder,
int minApiClass);
- protected abstract TraversalContinuation<?> visitMethods(
- BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation<?>> visitor,
+ protected abstract TraversalContinuation<?, ?> visitMethods(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation<?, ?>> visitor,
ClassReference holder,
int minApiClass);
- protected TraversalContinuation<?> visitField(
- BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation<?>> visitor,
+ protected TraversalContinuation<?, ?> visitField(
+ BiFunction<FieldReference, AndroidApiLevel, TraversalContinuation<?, ?>> visitor,
ClassReference holder,
int minApiClass,
int minApiField,
@@ -62,8 +62,8 @@
getAndroidApiLevel(Integer.max(minApiClass, minApiField)));
}
- protected TraversalContinuation<?> visitMethod(
- BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation<?>> visitor,
+ protected TraversalContinuation<?, ?> visitMethod(
+ BiFunction<MethodReference, AndroidApiLevel, TraversalContinuation<?, ?>> visitor,
ClassReference holder,
int minApiClass,
int minApiMethod,
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 827f43bd..7f39bfc 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.dex;
import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+import static com.google.common.base.Predicates.alwaysFalse;
import com.android.tools.r8.FeatureSplit;
import com.android.tools.r8.debuginfo.DebugRepresentation;
@@ -29,6 +30,7 @@
import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.shaking.MainDexInfo;
+import com.android.tools.r8.synthesis.SyntheticItems;
import com.android.tools.r8.synthesis.SyntheticNaming;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.FileUtils;
@@ -39,20 +41,20 @@
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Object2IntMap;
+import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
-import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TreeSet;
-import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Function;
@@ -62,12 +64,6 @@
public static final int MAX_ENTRIES = Constants.U16BIT_MAX + 1;
- /**
- * When distributing classes across files we aim to leave some space. The amount of space left is
- * driven by this constant.
- */
- private static final int MAX_PREFILL_ENTRIES = MAX_ENTRIES - 5000;
-
private final int id;
private final VirtualFileIndexedItemCollection indexedItems;
private final IndexedItemTransaction transaction;
@@ -187,22 +183,6 @@
return originalNames;
}
- private static String extractPrefixToken(int prefixLength, String className, boolean addStar) {
- int index = 0;
- int lastIndex = 0;
- int segmentCount = 0;
- while (lastIndex != -1 && segmentCount++ < prefixLength) {
- index = lastIndex;
- lastIndex = className.indexOf('.', index + 1);
- }
- String prefix = className.substring(0, index);
- if (addStar && segmentCount >= prefixLength) {
- // Full match, add a * to also match sub-packages.
- prefix += ".*";
- }
- return prefix;
- }
-
private ObjectToOffsetMapping objectMapping = null;
public ObjectToOffsetMapping getObjectMapping() {
@@ -427,38 +407,6 @@
mainDexFile.throwIfFull(true, options.reporter);
}
- 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;
- }
-
protected Map<FeatureSplit, Set<DexProgramClass>> removeFeatureSplitClassesGetMapping() {
assert appView.appInfo().hasClassHierarchy() == appView.enableWholeProgramOptimizations();
if (!appView.appInfo().hasClassHierarchy()) {
@@ -482,8 +430,8 @@
return featureSplitClasses;
}
- protected void addFeatureSplitFiles(Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses)
- throws IOException {
+ protected void addFeatureSplitFiles(
+ Map<FeatureSplit, Set<DexProgramClass>> featureSplitClasses) {
if (featureSplitClasses.isEmpty()) {
return;
}
@@ -499,19 +447,15 @@
featureSplitSetEntry.getKey());
virtualFiles.add(featureFile);
addMarkers(featureFile);
- Set<DexProgramClass> featureClasses =
- sortClassesByPackage(featureSplitSetEntry.getValue(), originalNames);
filesForDistribution = virtualFiles.subList(virtualFiles.size() - 1, virtualFiles.size());
-
new PackageSplitPopulator(
filesForDistribution,
appView,
- featureClasses,
+ featureSplitSetEntry.getValue(),
originalNames,
0,
- writer.namingLens,
- options)
- .call();
+ writer.namingLens)
+ .run();
}
}
}
@@ -564,18 +508,14 @@
executorService)
.distribute();
} else {
- // Sort the remaining classes based on the original names.
- // This with make classes from the same package be adjacent.
- classes = sortClassesByPackage(classes, originalNames);
new PackageSplitPopulator(
filesForDistribution,
appView,
classes,
originalNames,
fileIndexOffset,
- writer.namingLens,
- options)
- .call();
+ writer.namingLens)
+ .run();
}
addFeatureSplitFiles(featureSplitClasses);
@@ -974,7 +914,100 @@
* <p>The populator cycles through the files until all classes have been successfully placed and
* adds new files to the passed in map if it can't fit in the existing files.
*/
- private static class PackageSplitPopulator implements Callable<Map<String, Integer>> {
+ private static class PackageSplitPopulator {
+
+ static class PackageSplitClassPartioning {
+
+ // The set of startup classes, sorted by original names so that classes in the same package
+ // are adjacent. This is empty if no startup configuration is given.
+ private final List<DexProgramClass> startupClasses;
+
+ // The remaining set of classes that must be written, sorted by original names so that classes
+ // in the same package are adjacent.
+ private final List<DexProgramClass> nonStartupClasses;
+
+ private PackageSplitClassPartioning(
+ List<DexProgramClass> startupClasses, List<DexProgramClass> nonStartupClasses) {
+ this.startupClasses = startupClasses;
+ this.nonStartupClasses = nonStartupClasses;
+ }
+
+ public static PackageSplitClassPartioning create(
+ Collection<DexProgramClass> classes,
+ AppView<?> appView,
+ Map<DexProgramClass, String> originalNames) {
+ return create(
+ classes,
+ getClassesByPackageComparator(originalNames),
+ getStartupClassPredicate(appView));
+ }
+
+ private static PackageSplitClassPartioning create(
+ Collection<DexProgramClass> classes,
+ Comparator<DexProgramClass> comparator,
+ Predicate<DexProgramClass> startupClassPredicate) {
+ List<DexProgramClass> startupClasses = new ArrayList<>();
+ List<DexProgramClass> nonStartupClasses = new ArrayList<>(classes.size());
+ for (DexProgramClass clazz : classes) {
+ if (startupClassPredicate.test(clazz)) {
+ startupClasses.add(clazz);
+ } else {
+ nonStartupClasses.add(clazz);
+ }
+ }
+ startupClasses.sort(comparator);
+ nonStartupClasses.sort(comparator);
+ return new PackageSplitClassPartioning(startupClasses, nonStartupClasses);
+ }
+
+ private static Comparator<DexProgramClass> getClassesByPackageComparator(
+ Map<DexProgramClass, String> originalNames) {
+ return (a, 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);
+ };
+ }
+
+ private static Predicate<DexProgramClass> getStartupClassPredicate(AppView<?> appView) {
+ if (!appView.hasClassHierarchy()) {
+ return alwaysFalse();
+ }
+ ClassToFeatureSplitMap classToFeatureSplitMap =
+ appView.appInfo().withClassHierarchy().getClassToFeatureSplitMap();
+ SyntheticItems syntheticItems = appView.getSyntheticItems();
+ return clazz ->
+ classToFeatureSplitMap.getFeatureSplit(clazz, syntheticItems).isStartupBase();
+ }
+
+ public List<DexProgramClass> getStartupClasses() {
+ return startupClasses;
+ }
+
+ public List<DexProgramClass> getNonStartupClasses() {
+ return nonStartupClasses;
+ }
+ }
/**
* Android suggests com.company.product for package names, so the components will be at level 4
@@ -988,7 +1021,7 @@
*/
private static final int MIN_FILL_FACTOR = 5;
- private final List<DexProgramClass> classes;
+ private final PackageSplitClassPartioning classPartioning;
private final Map<DexProgramClass, String> originalNames;
private final DexItemFactory dexItemFactory;
private final InternalOptions options;
@@ -997,15 +1030,14 @@
PackageSplitPopulator(
List<VirtualFile> files,
AppView<?> appView,
- Set<DexProgramClass> classes,
+ Collection<DexProgramClass> classes,
Map<DexProgramClass, String> originalNames,
int fileIndexOffset,
- NamingLens namingLens,
- InternalOptions options) {
- this.classes = new ArrayList<>(classes);
+ NamingLens namingLens) {
+ this.classPartioning = PackageSplitClassPartioning.create(classes, appView, originalNames);
this.originalNames = originalNames;
this.dexItemFactory = appView.dexItemFactory();
- this.options = options;
+ this.options = appView.options();
this.cycler = new VirtualFileCycler(files, appView, namingLens, fileIndexOffset);
}
@@ -1022,17 +1054,53 @@
}
private String getOriginalName(DexProgramClass clazz) {
- return originalNames != null ? originalNames.get(clazz) : clazz.toString();
+ return originalNames.get(clazz);
}
- @Override
- public Map<String, Integer> call() throws IOException {
+ public void run() {
+ addStartupClasses();
+ List<DexProgramClass> nonPackageClasses = addNonStartupClasses();
+ addNonPackageClasses(cycler, nonPackageClasses);
+ }
+
+ private void addStartupClasses() {
+ // In practice, all startup classes should fit in a single dex file, so optimistically try to
+ // commit the startup classes using a single transaction.
+ VirtualFile virtualFile = cycler.next();
+ for (DexProgramClass startupClass : classPartioning.getStartupClasses()) {
+ virtualFile.addClass(startupClass);
+ }
+
+ if (hasSpaceForTransaction(virtualFile, options)) {
+ virtualFile.commitTransaction();
+ } 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();
+ }
+ }
+ }
+
+ cycler.restart();
+ }
+
+ private List<DexProgramClass> addNonStartupClasses() {
int prefixLength = MINIMUM_PREFIX_LENGTH;
int transactionStartIndex = 0;
- int fileStartIndex = 0;
String currentPrefix = null;
- Map<String, Integer> newPackageAssignments = new LinkedHashMap<>();
+ Object2IntMap<String> packageAssignments = new Object2IntOpenHashMap<>();
VirtualFile current = cycler.next();
+ List<DexProgramClass> classes = classPartioning.getNonStartupClasses();
List<DexProgramClass> nonPackageClasses = new ArrayList<>();
for (int classIndex = 0; classIndex < classes.size(); classIndex++) {
DexProgramClass clazz = classes.get(classIndex);
@@ -1040,10 +1108,9 @@
if (!coveredByPrefix(originalName, currentPrefix)) {
if (currentPrefix != null) {
current.commitTransaction();
+ assert verifyPackageToVirtualFileAssignment(packageAssignments, currentPrefix, current);
// Reset the cycler to again iterate over all files, starting with the current one.
cycler.restart();
- assert !newPackageAssignments.containsKey(currentPrefix);
- newPackageAssignments.put(currentPrefix, current.id);
// Try to reduce the prefix length if possible. Only do this on a successful commit.
prefixLength = MINIMUM_PREFIX_LENGTH - 1;
}
@@ -1067,58 +1134,93 @@
}
transactionStartIndex = classIndex;
}
- if (currentPrefix != null) {
- assert clazz.superType != null || clazz.type == dexItemFactory.objectType;
- current.addClass(clazz);
- } else {
+
+ if (currentPrefix == null) {
assert clazz.superType != null;
// We don't have a package, add this to a list of classes that we will add last.
- assert current.transaction.classes.isEmpty();
+ assert current.transaction.isEmpty();
nonPackageClasses.add(clazz);
continue;
}
- if (isFullEnough(current, options)) {
- 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.
- if (classIndex - transactionStartIndex > (classIndex - fileStartIndex) / MIN_FILL_FACTOR
- && prefixLength < MAXIMUM_PREFIX_LENGTH) {
- prefixLength++;
- } else {
- // Reset the state to after the last commit and cycle through files.
- // The idea is that we do not increase the number of files, so it has to fit
- // somewhere.
- fileStartIndex = transactionStartIndex;
- if (!cycler.hasNext()) {
- // Special case where we simply will never be able to fit the current package into
- // one dex file. This is currently the case for Strings in jumbo tests, see:
- // b/33227518
- if (current.transaction.getNumberOfClasses() == 0) {
- for (int j = transactionStartIndex; j <= classIndex; j++) {
- nonPackageClasses.add(classes.get(j));
- }
- transactionStartIndex = classIndex + 1;
- }
- // All files are filled up to the 20% mark.
- cycler.addFile();
- }
- current = cycler.next();
- }
- currentPrefix = null;
+
+ assert clazz.superType != null || clazz.type == dexItemFactory.objectType;
+ current.addClass(clazz);
+
+ if (hasSpaceForTransaction(current, options)) {
+ continue;
+ }
+
+ int numberOfClassesInTransaction = classIndex - transactionStartIndex + 1;
+ int numberOfClassesInVirtualFileWithTransaction = current.getNumberOfClasses();
+
+ 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.
+ if (numberOfClassesInTransaction
+ > numberOfClassesInVirtualFileWithTransaction / MIN_FILL_FACTOR
+ && prefixLength < MAXIMUM_PREFIX_LENGTH) {
// Go back to previous start index.
classIndex = transactionStartIndex - 1;
- assert current != null;
+ currentPrefix = null;
+ prefixLength++;
+ continue;
}
+
+ // Reset the state to after the last commit and cycle through files.
+ // The idea is that we do not increase the number of files, so it has to fit somewhere.
+ if (!cycler.hasNext()) {
+ // Special case where we simply will never be able to fit the current package into
+ // one dex file. This is currently the case for Strings in jumbo tests, see b/33227518.
+ if (current.isEmpty()) {
+ for (int j = transactionStartIndex; j <= classIndex; j++) {
+ nonPackageClasses.add(classes.get(j));
+ }
+ transactionStartIndex = classIndex + 1;
+ }
+ // All files are filled up to the 20% mark.
+ cycler.addFile();
+ }
+
+ // Go back to previous start index.
+ classIndex = transactionStartIndex - 1;
+ current = cycler.next();
+ currentPrefix = null;
+ prefixLength = MINIMUM_PREFIX_LENGTH;
}
+
current.commitTransaction();
- assert !newPackageAssignments.containsKey(currentPrefix);
- if (currentPrefix != null) {
- newPackageAssignments.put(currentPrefix, current.id);
+ assert currentPrefix == null
+ || verifyPackageToVirtualFileAssignment(packageAssignments, currentPrefix, current);
+
+ return nonPackageClasses;
+ }
+
+ private static String extractPrefixToken(int prefixLength, String className, boolean addStar) {
+ int index = 0;
+ int lastIndex = 0;
+ int segmentCount = 0;
+ while (lastIndex != -1 && segmentCount++ < prefixLength) {
+ index = lastIndex;
+ lastIndex = className.indexOf('.', index + 1);
}
- if (nonPackageClasses.size() > 0) {
- addNonPackageClasses(cycler, nonPackageClasses);
+ String prefix = className.substring(0, index);
+ if (addStar && segmentCount >= prefixLength) {
+ // Full match, add a * to also match sub-packages.
+ prefix += ".*";
}
- return newPackageAssignments;
+ return prefix;
+ }
+
+ private boolean verifyPackageToVirtualFileAssignment(
+ Object2IntMap<String> packageAssignments, String packageName, VirtualFile virtualFile) {
+ assert !packageAssignments.containsKey(packageName);
+ packageAssignments.put(packageName, virtualFile.getId());
+ return true;
+ }
+
+ private boolean hasSpaceForTransaction(VirtualFile current, InternalOptions options) {
+ return !isFullEnough(current, options);
}
private boolean isFullEnough(VirtualFile current, InternalOptions options) {
@@ -1131,6 +1233,9 @@
private void addNonPackageClasses(
VirtualFileCycler cycler, List<DexProgramClass> nonPackageClasses) {
+ if (nonPackageClasses.isEmpty()) {
+ return;
+ }
cycler.restart();
VirtualFile current;
current = cycler.next();
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
index f5cb1a3..4c28ff0 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithClassHierarchy.java
@@ -148,12 +148,12 @@
}
/** Primitive traversal over all (non-interface) superclasses of a given type. */
- public <T> TraversalContinuation<T> traverseSuperClasses(
- DexClass clazz, TriFunction<DexType, DexClass, DexClass, TraversalContinuation<T>> fn) {
+ public <B> TraversalContinuation<B, ?> traverseSuperClasses(
+ DexClass clazz, TriFunction<DexType, DexClass, DexClass, TraversalContinuation<B, ?>> fn) {
DexClass currentClass = clazz;
while (currentClass != null && currentClass.getSuperType() != null) {
DexClass superclass = definitionFor(currentClass.getSuperType());
- TraversalContinuation<T> stepResult =
+ TraversalContinuation<B, ?> stepResult =
fn.apply(currentClass.getSuperType(), superclass, currentClass);
if (stepResult.shouldBreak()) {
return stepResult;
@@ -170,8 +170,9 @@
* given type is *not* visited. The function indicates if traversal should continue or break. The
* result of the traversal is BREAK iff the function returned BREAK.
*/
- public TraversalContinuation<?> traverseSuperTypes(
- final DexClass clazz, TriFunction<DexType, DexClass, Boolean, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseSuperTypes(
+ final DexClass clazz,
+ TriFunction<DexType, DexClass, Boolean, TraversalContinuation<?, ?>> fn) {
// We do an initial zero-allocation pass over the class super chain as it does not require a
// worklist/seen-set. Only if the traversal is not aborted and there actually are interfaces,
// do we continue traversal over the interface types. This is assuming that the second pass
@@ -184,7 +185,8 @@
if (currentClass.superType == null) {
break;
}
- TraversalContinuation<?> stepResult = fn.apply(currentClass.superType, currentClass, false);
+ TraversalContinuation<?, ?> stepResult =
+ fn.apply(currentClass.superType, currentClass, false);
if (stepResult.shouldBreak()) {
return stepResult;
}
@@ -203,7 +205,7 @@
while (currentClass != null) {
for (DexType iface : currentClass.interfaces.values) {
if (seen.add(iface)) {
- TraversalContinuation<?> stepResult = fn.apply(iface, currentClass, true);
+ TraversalContinuation<?, ?> stepResult = fn.apply(iface, currentClass, true);
if (stepResult.shouldBreak()) {
return stepResult;
}
@@ -223,7 +225,7 @@
if (definition != null) {
for (DexType iface : definition.interfaces.values) {
if (seen.add(iface)) {
- TraversalContinuation<?> stepResult = fn.apply(iface, definition, true);
+ TraversalContinuation<?, ?> stepResult = fn.apply(iface, definition, true);
if (stepResult.shouldBreak()) {
return stepResult;
}
@@ -317,7 +319,7 @@
if (superclass.getType() == dexItemFactory().objectType) {
return true;
}
- TraversalContinuation<Boolean> result =
+ TraversalContinuation<Boolean, ?> result =
traverseSuperClasses(
subclass,
(currentType, currentClass, immediateSubclass) -> {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index e320709..4fac278 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -356,8 +356,8 @@
staticFields(predicate).forEach(consumer);
}
- public TraversalContinuation<?> traverseFields(
- Function<DexEncodedField, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseFields(
+ Function<DexEncodedField, TraversalContinuation<?, ?>> fn) {
for (DexEncodedField field : fields()) {
if (fn.apply(field).shouldBreak()) {
return TraversalContinuation.doBreak();
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index 354cc52..5e30d52 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -353,32 +353,33 @@
return null;
}
- public TraversalContinuation<?> traverseProgramMembers(
- Function<ProgramMember<?, ?>, TraversalContinuation<?>> fn) {
- TraversalContinuation<?> continuation = traverseProgramFields(fn);
+ public TraversalContinuation<?, ?> traverseProgramMembers(
+ Function<ProgramMember<?, ?>, TraversalContinuation<?, ?>> fn) {
+ TraversalContinuation<?, ?> continuation = traverseProgramFields(fn);
if (continuation.shouldContinue()) {
return traverseProgramMethods(fn);
}
return TraversalContinuation.doBreak();
}
- public TraversalContinuation<?> traverseProgramFields(
- Function<? super ProgramField, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseProgramFields(
+ Function<? super ProgramField, TraversalContinuation<?, ?>> fn) {
return traverseFields(field -> fn.apply(new ProgramField(this, field)));
}
- public TraversalContinuation<?> traverseProgramMethods(
- Function<? super ProgramMethod, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseProgramMethods(
+ Function<? super ProgramMethod, TraversalContinuation<?, ?>> fn) {
return getMethodCollection().traverse(method -> fn.apply(new ProgramMethod(this, method)));
}
- public TraversalContinuation<?> traverseProgramInstanceInitializers(
- Function<ProgramMethod, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseProgramInstanceInitializers(
+ Function<ProgramMethod, TraversalContinuation<?, ?>> fn) {
return traverseProgramMethods(fn, DexEncodedMethod::isInstanceInitializer);
}
- public TraversalContinuation<?> traverseProgramMethods(
- Function<ProgramMethod, TraversalContinuation<?>> fn, Predicate<DexEncodedMethod> predicate) {
+ public TraversalContinuation<?, ?> traverseProgramMethods(
+ Function<ProgramMethod, TraversalContinuation<?, ?>> fn,
+ Predicate<DexEncodedMethod> predicate) {
return getMethodCollection()
.traverse(
method ->
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 4354f06..bb94a8e 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -130,9 +130,7 @@
}
reader.accept(
new CreateDexClassVisitor<>(origin, classKind, reader.b, application, classConsumer),
- new Attribute[] {
- SyntheticMarker.getMarkerAttributePrototype(application.getFactory().getSyntheticNaming())
- },
+ getAttributePrototypes(),
parsingOptions);
// Read marker.
@@ -152,6 +150,15 @@
}
}
+ private Attribute[] getAttributePrototypes() {
+ if (classKind == ClassKind.PROGRAM) {
+ return new Attribute[] {
+ SyntheticMarker.getMarkerAttributePrototype(application.getFactory().getSyntheticNaming())
+ };
+ }
+ return new Attribute[0];
+ }
+
private static int cleanAccessFlags(int access) {
// Clear the "synthetic attribute" and "deprecated" attribute-flags if present.
return access & ~ACC_SYNTHETIC_ATTRIBUTE & ~ACC_DEPRECATED;
diff --git a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
index d00dca3..24c8b0a 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodArrayBacking.java
@@ -71,15 +71,15 @@
}
@Override
- TraversalContinuation<?> traverse(Function<DexEncodedMethod, TraversalContinuation<?>> fn) {
+ TraversalContinuation<?, ?> traverse(Function<DexEncodedMethod, TraversalContinuation<?, ?>> fn) {
for (DexEncodedMethod method : directMethods) {
- TraversalContinuation<?> stepResult = fn.apply(method);
+ TraversalContinuation<?, ?> stepResult = fn.apply(method);
if (stepResult.shouldBreak()) {
return stepResult;
}
}
for (DexEncodedMethod method : virtualMethods) {
- TraversalContinuation<?> stepResult = fn.apply(method);
+ TraversalContinuation<?, ?> stepResult = fn.apply(method);
if (stepResult.shouldBreak()) {
return stepResult;
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollection.java b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
index 1cb7d71..a31e669 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollection.java
@@ -117,8 +117,8 @@
return backing.size();
}
- public TraversalContinuation<?> traverse(
- Function<DexEncodedMethod, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverse(
+ Function<DexEncodedMethod, TraversalContinuation<?, ?>> fn) {
return backing.traverse(fn);
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
index 8eacebe..b270516 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionBacking.java
@@ -38,8 +38,8 @@
// Traversal methods.
- abstract TraversalContinuation<?> traverse(
- Function<DexEncodedMethod, TraversalContinuation<?>> fn);
+ abstract TraversalContinuation<?, ?> traverse(
+ Function<DexEncodedMethod, TraversalContinuation<?, ?>> fn);
/**
* Return a new backing mapped by the given function.
diff --git a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
index 17bbae3..0889c63 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodCollectionConcurrencyChecked.java
@@ -84,10 +84,10 @@
}
@Override
- public TraversalContinuation<?> traverse(
- Function<DexEncodedMethod, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverse(
+ Function<DexEncodedMethod, TraversalContinuation<?, ?>> fn) {
assert assertReadEntry();
- TraversalContinuation<?> result = super.traverse(fn);
+ TraversalContinuation<?, ?> result = super.traverse(fn);
assert assertReadExit();
return result;
}
diff --git a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
index eb5b9be..0e838b9 100644
--- a/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
+++ b/src/main/java/com/android/tools/r8/graph/MethodMapBacking.java
@@ -100,9 +100,9 @@
}
@Override
- TraversalContinuation<?> traverse(Function<DexEncodedMethod, TraversalContinuation<?>> fn) {
+ TraversalContinuation<?, ?> traverse(Function<DexEncodedMethod, TraversalContinuation<?, ?>> fn) {
for (Entry<DexMethodSignature, DexEncodedMethod> entry : methodMap.entrySet()) {
- TraversalContinuation<?> result = fn.apply(entry.getValue());
+ TraversalContinuation<?, ?> result = fn.apply(entry.getValue());
if (result.shouldBreak()) {
return result;
}
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
index ef1de89..300bca7 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollection.java
@@ -34,10 +34,10 @@
void forEachInstantiatedLambdaInterfaces(Consumer<DexType> consumer);
- TraversalContinuation<?> traverseInstantiatedSubtypes(
+ TraversalContinuation<?, ?> traverseInstantiatedSubtypes(
DexType type,
- Function<DexProgramClass, TraversalContinuation<?>> onClass,
- Function<LambdaDescriptor, TraversalContinuation<?>> onLambda,
+ Function<DexProgramClass, TraversalContinuation<?, ?>> onClass,
+ Function<LambdaDescriptor, TraversalContinuation<?, ?>> onLambda,
AppInfo appInfo);
ObjectAllocationInfoCollection rewrittenWithLens(
diff --git a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
index 6b31147..1aa8117 100644
--- a/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
+++ b/src/main/java/com/android/tools/r8/graph/ObjectAllocationInfoCollectionImpl.java
@@ -184,10 +184,10 @@
}
@Override
- public TraversalContinuation<?> traverseInstantiatedSubtypes(
+ public TraversalContinuation<?, ?> traverseInstantiatedSubtypes(
DexType type,
- Function<DexProgramClass, TraversalContinuation<?>> onClass,
- Function<LambdaDescriptor, TraversalContinuation<?>> onLambda,
+ Function<DexProgramClass, TraversalContinuation<?, ?>> onClass,
+ Function<LambdaDescriptor, TraversalContinuation<?, ?>> onLambda,
AppInfo appInfo) {
WorkList<DexClass> worklist = WorkList.newIdentityWorkList();
if (type == appInfo.dexItemFactory().objectType) {
diff --git a/src/main/java/com/android/tools/r8/graph/UseRegistry.java b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
index 919d940..6e08e6c 100644
--- a/src/main/java/com/android/tools/r8/graph/UseRegistry.java
+++ b/src/main/java/com/android/tools/r8/graph/UseRegistry.java
@@ -15,7 +15,7 @@
private final AppView<?> appView;
private final T context;
- private TraversalContinuation<?> continuation = TraversalContinuation.doContinue();
+ private TraversalContinuation<?, ?> continuation = TraversalContinuation.doContinue();
public enum MethodHandleUse {
ARGUMENT_TO_LAMBDA_METAFACTORY,
@@ -54,7 +54,7 @@
return context.asMethod();
}
- public TraversalContinuation<?> getTraversalContinuation() {
+ public TraversalContinuation<?, ?> getTraversalContinuation() {
return continuation;
}
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
index 1573dde..5bc0f58 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/policies/RespectPackageBoundaries.java
@@ -65,7 +65,7 @@
// Check that all accesses from [clazz] to classes or members from the current package of
// [clazz] will continue to work. This is guaranteed if the methods of [clazz] do not access
// any private or protected classes or members from the current package of [clazz].
- TraversalContinuation<?> result =
+ TraversalContinuation<?, ?> result =
clazz.traverseProgramMethods(
method -> {
boolean foundIllegalAccess =
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
index af7a836..a3013e3 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/AbstractTransferFunction.java
@@ -4,19 +4,17 @@
package com.android.tools.r8.ir.analysis.framework.intraprocedural;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.Instruction;
-
/**
* A transfer function that defines the abstract semantics of the instructions in the program
* according to some abstract state {@link StateType}.
*/
-public interface AbstractTransferFunction<StateType extends AbstractState<StateType>> {
+public interface AbstractTransferFunction<
+ Block, Instruction, StateType extends AbstractState<StateType>> {
TransferFunctionResult<StateType> apply(Instruction instruction, StateType state);
default StateType computeBlockEntryState(
- BasicBlock block, BasicBlock predecessor, StateType predecessorExitState) {
+ Block block, Block predecessor, StateType predecessorExitState) {
return predecessorExitState;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
new file mode 100644
index 0000000..5710f6e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/ControlFlowGraph.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2022, 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.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.google.common.collect.Iterables;
+import java.util.Collection;
+import java.util.function.BiFunction;
+
+public interface ControlFlowGraph<Block, Instruction> {
+
+ Collection<Block> getPredecessors(Block block);
+
+ Collection<Block> getSuccessors(Block block);
+
+ default boolean hasUniquePredecessor(Block block) {
+ return getPredecessors(block).size() == 1;
+ }
+
+ default Block getUniquePredecessor(Block block) {
+ assert hasUniquePredecessor(block);
+ return Iterables.getOnlyElement(getPredecessors(block));
+ }
+
+ default boolean hasUniqueSuccessor(Block block) {
+ return getSuccessors(block).size() == 1;
+ }
+
+ default boolean hasUniqueSuccessorWithUniquePredecessor(Block block) {
+ return hasUniqueSuccessor(block) && getPredecessors(getUniqueSuccessor(block)).size() == 1;
+ }
+
+ default Block getUniqueSuccessor(Block block) {
+ assert hasUniqueSuccessor(block);
+ return Iterables.getOnlyElement(getSuccessors(block));
+ }
+
+ <BT, CT> TraversalContinuation<BT, CT> traverseInstructions(
+ Block block, BiFunction<Instruction, CT, TraversalContinuation<BT, CT>> fn, CT initialValue);
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
index a8a1832..8974bbf 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/DataflowAnalysisResult.java
@@ -24,8 +24,8 @@
return false;
}
- public <StateType extends AbstractState<StateType>>
- SuccessfulDataflowAnalysisResult<StateType> asSuccessfulAnalysisResult() {
+ public <Block, StateType extends AbstractState<StateType>>
+ SuccessfulDataflowAnalysisResult<Block, StateType> asSuccessfulAnalysisResult() {
return null;
}
@@ -33,12 +33,13 @@
return false;
}
- public static class SuccessfulDataflowAnalysisResult<StateType extends AbstractState<StateType>>
+ public static class SuccessfulDataflowAnalysisResult<
+ Block, StateType extends AbstractState<StateType>>
extends DataflowAnalysisResult {
- private final Map<BasicBlock, StateType> blockExitStates;
+ private final Map<Block, StateType> blockExitStates;
- public SuccessfulDataflowAnalysisResult(Map<BasicBlock, StateType> blockExitStates) {
+ public SuccessfulDataflowAnalysisResult(Map<Block, StateType> blockExitStates) {
this.blockExitStates = blockExitStates;
}
@@ -57,7 +58,7 @@
@SuppressWarnings("unchecked")
@Override
- public SuccessfulDataflowAnalysisResult<StateType> asSuccessfulAnalysisResult() {
+ public SuccessfulDataflowAnalysisResult<Block, StateType> asSuccessfulAnalysisResult() {
return this;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
new file mode 100644
index 0000000..969197f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraProceduralDataflowAnalysisBase.java
@@ -0,0 +1,147 @@
+// Copyright (c) 2022, 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.analysis.framework.intraprocedural;
+
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.FailedDataflowAnalysisResult;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
+import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
+import com.android.tools.r8.utils.WorkList;
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * This defines a simple fixpoint solver for running an intraprocedural dataflow analysis.
+ *
+ * <p>The solver computes an {@link AbstractState} for each {@link Block} using the {@link
+ * AbstractTransferFunction} which defines the abstract semantics for each instruction.
+ *
+ * <p>Once the fixpoint is reached the analysis returns a {@link SuccessfulDataflowAnalysisResult}.
+ * If the supplied {@link AbstractTransferFunction} returns a {@link FailedTransferFunctionResult}
+ * for a given instruction and abstract state, then the analysis return a {@link
+ * FailedDataflowAnalysisResult}.
+ */
+public class IntraProceduralDataflowAnalysisBase<
+ Block, Instruction, StateType extends AbstractState<StateType>> {
+
+ final StateType bottom;
+
+ final ControlFlowGraph<Block, Instruction> cfg;
+
+ // The transfer function that defines the abstract semantics for each instruction.
+ final AbstractTransferFunction<Block, Instruction, StateType> transfer;
+
+ // The state of the analysis.
+ final Map<Block, StateType> blockExitStates = new IdentityHashMap<>();
+
+ // The entry states for each block that satisfies the predicate
+ // shouldCacheBlockEntryStateFor(block). These entry states can be computed from the exit states
+ // of the predecessors, but doing so can be expensive when a block has many predecessors.
+ final Map<Block, StateType> blockEntryStatesCache = new IdentityHashMap<>();
+
+ public IntraProceduralDataflowAnalysisBase(
+ StateType bottom,
+ ControlFlowGraph<Block, Instruction> cfg,
+ AbstractTransferFunction<Block, Instruction, StateType> transfer) {
+ this.bottom = bottom;
+ this.cfg = cfg;
+ this.transfer = transfer;
+ }
+
+ public DataflowAnalysisResult run(Block root) {
+ return run(root, Timing.empty());
+ }
+
+ public DataflowAnalysisResult run(Block root, Timing timing) {
+ return run(WorkList.newIdentityWorkList(root), timing);
+ }
+
+ private DataflowAnalysisResult run(WorkList<Block> worklist, Timing timing) {
+ while (worklist.hasNext()) {
+ Block initialBlock = worklist.next();
+ Block block = initialBlock;
+ Block end = null;
+ // Compute the abstract state upon entry to the basic block, by joining all the predecessor
+ // exit states.
+ StateType state =
+ timing.time("Compute block entry state", () -> computeBlockEntryState(initialBlock));
+
+ timing.begin("Compute transfers");
+ do {
+ TraversalContinuation<FailedDataflowAnalysisResult, StateType> traversalContinuation =
+ cfg.traverseInstructions(
+ block,
+ (instruction, previousState) -> {
+ TransferFunctionResult<StateType> transferResult =
+ transfer.apply(instruction, previousState);
+ if (transferResult.isFailedTransferResult()) {
+ timing.end();
+ return TraversalContinuation.doBreak(new FailedDataflowAnalysisResult());
+ }
+ assert transferResult.isAbstractState();
+ return TraversalContinuation.doContinue(transferResult.asAbstractState());
+ },
+ state);
+ if (traversalContinuation.isBreak()) {
+ return traversalContinuation.asBreak().getValue();
+ }
+ state = traversalContinuation.asContinue().getValue();
+ if (cfg.hasUniqueSuccessorWithUniquePredecessor(block)) {
+ block = cfg.getUniqueSuccessor(block);
+ } else {
+ end = block;
+ block = null;
+ }
+ } while (block != null);
+ timing.end();
+
+ // Update the block exit state, and re-enqueue all successor blocks if the abstract state
+ // changed.
+ if (setBlockExitState(end, state)) {
+ worklist.addAllIgnoringSeenSet(cfg.getSuccessors(end));
+ }
+
+ // Add the computed exit state to the entry state of each successor that satisfies the
+ // predicate shouldCacheBlockEntryStateFor(successor).
+ updateBlockEntryStateCacheForSuccessors(end, state);
+ }
+ return new SuccessfulDataflowAnalysisResult<>(blockExitStates);
+ }
+
+ StateType computeBlockEntryState(Block block) {
+ if (shouldCacheBlockEntryStateFor(block)) {
+ return blockEntryStatesCache.getOrDefault(block, bottom);
+ }
+ StateType result = bottom;
+ for (Block predecessor : cfg.getPredecessors(block)) {
+ StateType edgeState =
+ transfer.computeBlockEntryState(
+ block, predecessor, blockExitStates.getOrDefault(predecessor, bottom));
+ result = result.join(edgeState);
+ }
+ return result;
+ }
+
+ boolean setBlockExitState(Block block, StateType state) {
+ assert !cfg.hasUniqueSuccessorWithUniquePredecessor(block);
+ StateType previous = blockExitStates.put(block, state);
+ assert previous == null || state.isGreaterThanOrEquals(previous);
+ return !state.equals(previous);
+ }
+
+ void updateBlockEntryStateCacheForSuccessors(Block block, StateType state) {
+ for (Block successor : cfg.getSuccessors(block)) {
+ if (shouldCacheBlockEntryStateFor(successor)) {
+ StateType edgeState = transfer.computeBlockEntryState(successor, block, state);
+ StateType previous = blockEntryStatesCache.getOrDefault(successor, bottom);
+ blockEntryStatesCache.put(successor, previous.join(edgeState));
+ }
+ }
+ }
+
+ boolean shouldCacheBlockEntryStateFor(Block block) {
+ return cfg.getPredecessors(block).size() > 2;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
index 4f00ef0..13a9c42 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/framework/intraprocedural/IntraproceduralDataflowAnalysis.java
@@ -4,130 +4,17 @@
package com.android.tools.r8.ir.analysis.framework.intraprocedural;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.FailedDataflowAnalysisResult;
-import com.android.tools.r8.ir.analysis.framework.intraprocedural.DataflowAnalysisResult.SuccessfulDataflowAnalysisResult;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.utils.Timing;
-import com.android.tools.r8.utils.WorkList;
-import java.util.IdentityHashMap;
-import java.util.Map;
-/**
- * This defines a simple fixpoint solver for running an intraprocedural dataflow analysis.
- *
- * <p>The solver computes an {@link AbstractState} for each {@link BasicBlock} using the {@link
- * AbstractTransferFunction} which defines the abstract semantics for each instruction.
- *
- * <p>Once the fixpoint is reached the analysis returns a {@link SuccessfulDataflowAnalysisResult}.
- * If the supplied {@link AbstractTransferFunction} returns a {@link FailedTransferFunctionResult}
- * for a given instruction and abstract state, then the analysis return a {@link
- * FailedDataflowAnalysisResult}.
- */
-public class IntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>> {
-
- private final StateType bottom;
-
- // The transfer function that defines the abstract semantics for each instruction.
- private final AbstractTransferFunction<StateType> transfer;
-
- // The state of the analysis.
- private final Map<BasicBlock, StateType> blockExitStates = new IdentityHashMap<>();
-
- // The entry states for each block that satisfies the predicate
- // shouldCacheBlockEntryStateFor(block). These entry states can be computed from the exit states
- // of the predecessors, but doing so can be expensive when a block has many predecessors.
- private final Map<BasicBlock, StateType> blockEntryStatesCache = new IdentityHashMap<>();
+public class IntraproceduralDataflowAnalysis<StateType extends AbstractState<StateType>>
+ extends IntraProceduralDataflowAnalysisBase<BasicBlock, Instruction, StateType> {
public IntraproceduralDataflowAnalysis(
- StateType bottom, AbstractTransferFunction<StateType> transfer) {
- this.bottom = bottom;
- this.transfer = transfer;
- }
-
- public DataflowAnalysisResult run(BasicBlock root) {
- return run(root, Timing.empty());
- }
-
- public DataflowAnalysisResult run(BasicBlock root, Timing timing) {
- return run(WorkList.newIdentityWorkList(root), timing);
- }
-
- private DataflowAnalysisResult run(WorkList<BasicBlock> worklist, Timing timing) {
- while (worklist.hasNext()) {
- BasicBlock initialBlock = worklist.next();
- BasicBlock block = initialBlock;
- BasicBlock end = null;
- // Compute the abstract state upon entry to the basic block, by joining all the predecessor
- // exit states.
- StateType state =
- timing.time("Compute block entry state", () -> computeBlockEntryState(initialBlock));
-
- timing.begin("Compute transfers");
- do {
- for (Instruction instruction : block.getInstructions()) {
- TransferFunctionResult<StateType> transferResult = transfer.apply(instruction, state);
- if (transferResult.isFailedTransferResult()) {
- timing.end();
- return new FailedDataflowAnalysisResult();
- }
- assert transferResult.isAbstractState();
- state = transferResult.asAbstractState();
- }
- if (block.hasUniqueSuccessorWithUniquePredecessor()) {
- block = block.getUniqueSuccessor();
- } else {
- end = block;
- block = null;
- }
- } while (block != null);
- timing.end();
-
- // Update the block exit state, and re-enqueue all successor blocks if the abstract state
- // changed.
- if (setBlockExitState(end, state)) {
- worklist.addAllIgnoringSeenSet(end.getSuccessors());
- }
-
- // Add the computed exit state to the entry state of each successor that satisfies the
- // predicate shouldCacheBlockEntryStateFor(successor).
- updateBlockEntryStateCacheForSuccessors(end, state);
- }
- return new SuccessfulDataflowAnalysisResult<>(blockExitStates);
- }
-
- private StateType computeBlockEntryState(BasicBlock block) {
- if (shouldCacheBlockEntryStateFor(block)) {
- return blockEntryStatesCache.getOrDefault(block, bottom);
- }
- StateType result = bottom;
- for (BasicBlock predecessor : block.getPredecessors()) {
- StateType edgeState =
- transfer.computeBlockEntryState(
- block, predecessor, blockExitStates.getOrDefault(predecessor, bottom));
- result = result.join(edgeState);
- }
- return result;
- }
-
- private boolean setBlockExitState(BasicBlock block, StateType state) {
- assert !block.hasUniqueSuccessorWithUniquePredecessor();
- StateType previous = blockExitStates.put(block, state);
- assert previous == null || state.isGreaterThanOrEquals(previous);
- return !state.equals(previous);
- }
-
- private void updateBlockEntryStateCacheForSuccessors(BasicBlock block, StateType state) {
- for (BasicBlock successor : block.getSuccessors()) {
- if (shouldCacheBlockEntryStateFor(successor)) {
- StateType edgeState = transfer.computeBlockEntryState(successor, block, state);
- StateType previous = blockEntryStatesCache.getOrDefault(successor, bottom);
- blockEntryStatesCache.put(successor, previous.join(edgeState));
- }
- }
- }
-
- private boolean shouldCacheBlockEntryStateFor(BasicBlock block) {
- return block.getPredecessors().size() > 2;
+ StateType bottom,
+ IRCode code,
+ AbstractTransferFunction<BasicBlock, Instruction, StateType> transfer) {
+ super(bottom, code, transfer);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 01b08a8..1544bdd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -16,6 +16,7 @@
import com.android.tools.r8.graph.classmerging.MergedClassesCollection;
import com.android.tools.r8.ir.analysis.TypeChecker;
import com.android.tools.r8.ir.analysis.VerifyTypesHelper;
+import com.android.tools.r8.ir.analysis.framework.intraprocedural.ControlFlowGraph;
import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
import com.android.tools.r8.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -33,6 +34,7 @@
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.SetUtils;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.TraversalContinuation;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -55,13 +57,14 @@
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Set;
+import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-public class IRCode implements ValueFactory {
+public class IRCode implements ControlFlowGraph<BasicBlock, Instruction>, ValueFactory {
private static final int MAX_MARKING_COLOR = 0x40000000;
@@ -1363,6 +1366,32 @@
return blocks;
}
+ @Override
+ public Collection<BasicBlock> getPredecessors(BasicBlock block) {
+ return block.getPredecessors();
+ }
+
+ @Override
+ public Collection<BasicBlock> getSuccessors(BasicBlock block) {
+ return block.getSuccessors();
+ }
+
+ @Override
+ public <BT, CT> TraversalContinuation<BT, CT> traverseInstructions(
+ BasicBlock block,
+ BiFunction<Instruction, CT, TraversalContinuation<BT, CT>> fn,
+ CT initialValue) {
+ TraversalContinuation<BT, CT> traversalContinuation =
+ TraversalContinuation.doContinue(initialValue);
+ for (Instruction instruction : block.getInstructions()) {
+ traversalContinuation = fn.apply(instruction, traversalContinuation.asContinue().getValue());
+ if (traversalContinuation.shouldBreak()) {
+ return traversalContinuation;
+ }
+ }
+ return traversalContinuation;
+ }
+
/**
* Returns the set of blocks that are reachable from the given source. The source itself is only
* included if there is a path from the given block to itself.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
index 06fadd9..0ef825c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/apimodel/ApiInvokeOutlinerDesugaring.java
@@ -127,7 +127,7 @@
if (result != null) {
return result;
}
- TraversalContinuation<DexEncodedMethod> traversalResult =
+ TraversalContinuation<DexEncodedMethod, ?> traversalResult =
appView
.appInfoForDesugaring()
.traverseSuperClasses(
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 e011479..86578a2 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
@@ -132,6 +132,7 @@
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
@@ -1231,7 +1232,6 @@
AssumeRemover assumeRemover = new AssumeRemover(appView, code);
boolean changed = false;
boolean mayHaveRemovedTrivialPhi = false;
- Set<Value> affectedValues = Sets.newIdentityHashSet();
Set<BasicBlock> blocksToBeRemoved = Sets.newIdentityHashSet();
ListIterator<BasicBlock> blockIterator = code.listIterator();
while (blockIterator.hasNext()) {
@@ -1254,29 +1254,45 @@
}
MethodOptimizationInfo optimizationInfo = target.getDefinition().getOptimizationInfo();
- if (optimizationInfo.returnsArgument()) {
- int argumentIndex = optimizationInfo.getReturnedArgument();
- // Replace the out value of the invoke with the argument and ignore the out value.
- if (argumentIndex >= 0 && checkArgumentType(invoke, argumentIndex)) {
- Value argument = invoke.arguments().get(argumentIndex);
- Value outValue = invoke.outValue();
- assert outValue.verifyCompatible(argument.outType());
- // Make sure that we are only narrowing information here. Note, in cases where
- // we cannot find the definition of types, computing lessThanOrEqual will
- // return false unless it is object.
- if (argument.getType().lessThanOrEqual(outValue.getType(), appView)) {
- affectedValues.addAll(outValue.affectedValues());
- assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
- mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
- outValue.replaceUsers(argument);
- invoke.setOutValue(null);
- changed = true;
- }
- }
+ if (!optimizationInfo.returnsArgument()) {
+ continue;
+ }
+
+ int argumentIndex = optimizationInfo.getReturnedArgument();
+ // Replace the out value of the invoke with the argument and ignore the out value.
+ if (argumentIndex < 0 || !checkArgumentType(invoke, argumentIndex)) {
+ continue;
+ }
+
+ Value argument = invoke.arguments().get(argumentIndex);
+ Value outValue = invoke.outValue();
+ assert outValue.verifyCompatible(argument.outType());
+
+ // Make sure that we are only narrowing information here. Note, in cases where we cannot
+ // find the definition of types, computing lessThanOrEqual will return false unless it is
+ // object.
+ if (!argument.getType().lessThanOrEqual(outValue.getType(), appView)) {
+ continue;
+ }
+
+ Set<Value> affectedValues =
+ argument.getType().equals(outValue.getType())
+ ? Collections.emptySet()
+ : outValue.affectedValues();
+
+ assumeRemover.markAssumeDynamicTypeUsersForRemoval(outValue);
+ mayHaveRemovedTrivialPhi |= outValue.numberOfPhiUsers() > 0;
+ outValue.replaceUsers(argument);
+ invoke.setOutValue(null);
+ changed = true;
+
+ if (!affectedValues.isEmpty()) {
+ new TypeAnalysis(appView).narrowing(affectedValues);
}
}
}
assumeRemover.removeMarkedInstructions(blocksToBeRemoved).finish();
+ Set<Value> affectedValues = Sets.newIdentityHashSet();
if (!blocksToBeRemoved.isEmpty()) {
code.removeBlocks(blocksToBeRemoved);
code.removeAllDeadAndTrivialPhis(affectedValues);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
index 572b153..75e94fb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SimpleDominatingEffectAnalysis.java
@@ -249,7 +249,7 @@
new StatefulDepthFirstSearchWorkList<BasicBlock, ResultStateWithPartialBlocks>() {
@Override
- protected TraversalContinuation<?> process(
+ protected TraversalContinuation<?, ?> process(
DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
Function<BasicBlock, DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>>
childNodeConsumer) {
@@ -285,7 +285,7 @@
}
@Override
- protected TraversalContinuation<?> joiner(
+ protected TraversalContinuation<?, ?> joiner(
DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks> node,
List<DFSNodeWithState<BasicBlock, ResultStateWithPartialBlocks>> childNodes) {
ResultStateWithPartialBlocks resultState = node.getState();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
index 854188e..f45344f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/ClassInlinerMethodConstraintAnalysis.java
@@ -26,8 +26,8 @@
// Analyze code.
IntraproceduralDataflowAnalysis<ParameterUsages> analysis =
new IntraproceduralDataflowAnalysis<>(
- ParameterUsages.bottom(), new TransferFunction(appView, method, code));
- SuccessfulDataflowAnalysisResult<ParameterUsages> result =
+ ParameterUsages.bottom(), code, new TransferFunction(appView, method, code));
+ SuccessfulDataflowAnalysisResult<?, ParameterUsages> result =
timing.time(
"Data flow analysis",
() -> analysis.run(code.entryBlock(), timing).asSuccessfulAnalysisResult());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
index 65c05e6..0e4a8dd 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/analysis/TransferFunction.java
@@ -52,7 +52,8 @@
import com.google.common.collect.Sets;
import java.util.Set;
-class TransferFunction implements AbstractTransferFunction<ParameterUsages> {
+class TransferFunction
+ implements AbstractTransferFunction<BasicBlock, Instruction, ParameterUsages> {
private static final AliasedValueConfiguration aliasedValueConfiguration =
AssumeAndCheckCastAliasedValueConfiguration.getInstance();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
index 80066f3..1179c6d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderAppendFlowAnalysis.java
@@ -12,6 +12,7 @@
import com.android.tools.r8.ir.analysis.framework.intraprocedural.IntraproceduralDataflowAnalysis;
import com.android.tools.r8.ir.analysis.framework.intraprocedural.TransferFunctionResult;
import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeVirtual;
@@ -39,10 +40,10 @@
* loop.
*/
static boolean hasAppendInstructionInLoop(
- Value builder, StringBuilderOptimizationConfiguration configuration) {
+ IRCode code, Value builder, StringBuilderOptimizationConfiguration configuration) {
IntraproceduralDataflowAnalysis<AbstractStateImpl> analysis =
new IntraproceduralDataflowAnalysis<>(
- AbstractStateImpl.bottom(), new TransferFunction(builder, configuration));
+ AbstractStateImpl.bottom(), code, new TransferFunction(builder, configuration));
DataflowAnalysisResult result = analysis.run(builder.definition.getBlock());
return result.isFailedAnalysisResult();
}
@@ -121,7 +122,8 @@
* <p>If a call to {@code toString()} on the builder i seen, then the abstract state is reset to
* bottom.
*/
- private static class TransferFunction implements AbstractTransferFunction<AbstractStateImpl> {
+ private static class TransferFunction
+ implements AbstractTransferFunction<BasicBlock, Instruction, AbstractStateImpl> {
private final Value builder;
private final StringBuilderOptimizationConfiguration configuration;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
index b851f1e..7e0b884 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringBuilderOptimizer.java
@@ -722,7 +722,7 @@
return null;
}
if (StringBuilderAppendFlowAnalysis.hasAppendInstructionInLoop(
- builder, optimizationConfiguration)) {
+ code, builder, optimizationConfiguration)) {
return null;
}
return StringUtils.join("", contents);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 15a3b02..d67d177 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -47,9 +47,15 @@
private final Map<String, ClassNamingForNameMapper.Builder> mapping = new HashMap<>();
private LinkedHashSet<MapVersionMappingInformation> mapVersions = new LinkedHashSet<>();
+ private final Set<String> buildForClass;
- private Builder() {
+ private Builder(Set<String> buildForClass) {
+ this.buildForClass = buildForClass;
+ }
+ @Override
+ public boolean buildForClass(String typeName) {
+ return buildForClass == null || buildForClass.contains(typeName);
}
@Override
@@ -82,7 +88,11 @@
}
public static Builder builder() {
- return new Builder();
+ return new Builder(null);
+ }
+
+ public static Builder builder(Set<String> buildForClass) {
+ return new Builder(buildForClass);
}
public static ClassNameMapper mapperFromFile(Path path) throws IOException {
@@ -146,6 +156,25 @@
}
}
+ public static ClassNameMapper mapperFromBufferedReaderWithFiltering(
+ BufferedReader reader,
+ DiagnosticsHandler diagnosticsHandler,
+ boolean allowEmptyMappedRanges,
+ boolean allowExperimentalMapping,
+ Set<String> buildForClass)
+ throws IOException {
+ try (ProguardMapReader proguardReader =
+ new ProguardMapReader(
+ reader,
+ diagnosticsHandler != null ? diagnosticsHandler : new Reporter(),
+ allowEmptyMappedRanges,
+ allowExperimentalMapping)) {
+ ClassNameMapper.Builder builder = ClassNameMapper.builder(buildForClass);
+ proguardReader.parse(builder);
+ return builder.build();
+ }
+ }
+
private final ImmutableMap<String, ClassNamingForNameMapper> classNameMappings;
private BiMapContainer<String, String> nameMapping;
private final Map<Signature, Signature> signatureMap = new HashMap<>();
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMap.java b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
index 6b95444..d1e7463 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMap.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMap.java
@@ -16,6 +16,10 @@
abstract Builder setCurrentMapVersion(MapVersionMappingInformation mapVersion);
abstract ProguardMap build();
+
+ public boolean buildForClass(String typeName) {
+ return true;
+ }
}
boolean hasMapping(DexType type);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index b2cb740..3e6816b 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -162,6 +162,10 @@
return false;
}
+ private boolean isClassMapping() {
+ return !isEmptyOrCommentLine(line) && line.endsWith(":");
+ }
+
private static boolean hasFirstCharJsonBrace(String line, int commentCharIndex) {
for (int i = commentCharIndex + 1; i < line.length(); i++) {
char c = line.charAt(i);
@@ -258,11 +262,18 @@
String after = parseType(false);
skipWhitespace();
expect(':');
- ClassNaming.Builder currentClassBuilder =
- mapBuilder.classNamingBuilder(after, before, getPosition());
- skipWhitespace();
- if (nextLine()) {
- parseMemberMappings(currentClassBuilder);
+ if (mapBuilder.buildForClass(after)) {
+ ClassNaming.Builder currentClassBuilder =
+ mapBuilder.classNamingBuilder(after, before, getPosition());
+ skipWhitespace();
+ if (nextLine()) {
+ parseMemberMappings(currentClassBuilder);
+ }
+ } else {
+ do {
+ lineOffset = line.length();
+ nextLine();
+ } while (hasLine() && !isClassMapping());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/IllegalClassNameLookupException.java b/src/main/java/com/android/tools/r8/retrace/IllegalClassNameLookupException.java
new file mode 100644
index 0000000..702ed59
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/IllegalClassNameLookupException.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, 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.retrace;
+
+public class IllegalClassNameLookupException extends RuntimeException {
+
+ private final String typeName;
+
+ public IllegalClassNameLookupException(String typeName) {
+ this.typeName = typeName;
+ }
+
+ @Override
+ public String getMessage() {
+ return "Illegal lookup of " + typeName + ".";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java b/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java
index 6e8d4fc..e192003 100644
--- a/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java
+++ b/src/main/java/com/android/tools/r8/retrace/ProguardMappingProvider.java
@@ -5,6 +5,7 @@
package com.android.tools.r8.retrace;
import com.android.tools.r8.Keep;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.retrace.internal.ProguardMappingProviderBuilderImpl;
@Keep
@@ -18,6 +19,10 @@
public abstract static class Builder
extends MappingProviderBuilder<ProguardMappingProvider, Builder> {
+ public abstract Builder registerUse(ClassReference classReference);
+
+ public abstract Builder allowLookupAllClasses();
+
public abstract Builder setProguardMapProducer(ProguardMapProducer proguardMapProducer);
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 61ba441..01dc051 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -12,9 +12,12 @@
import com.android.tools.r8.Version;
import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker;
import com.android.tools.r8.naming.ProguardMapSupplier.ProguardMapChecker.VerifyMappingFileHashResult;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.retrace.RetraceCommand.Builder;
import com.android.tools.r8.retrace.internal.RetraceAbortException;
import com.android.tools.r8.retrace.internal.RetracerImpl;
+import com.android.tools.r8.retrace.internal.StackTraceElementStringProxy;
import com.android.tools.r8.retrace.internal.StackTraceRegularExpressionParser;
import com.android.tools.r8.utils.Box;
import com.android.tools.r8.utils.ExceptionDiagnostic;
@@ -185,6 +188,17 @@
throw new RetraceAbortException();
}
});
+ List<ST> parsed = ListUtils.map(stackTrace, stackTraceLineParser::parse);
+ return retraceStackTraceParsed(parsed);
+ }
+
+ /**
+ * Retraces a complete stack frame and returns a list of retraced stack traces.
+ *
+ * @param stackTrace the stack trace to be retrace
+ * @return list of potentially ambiguous stack traces.
+ */
+ public List<List<List<T>>> retraceStackTraceParsed(List<ST> stackTrace) {
RetraceStackTraceElementProxyEquivalence<T, ST> equivalence =
new RetraceStackTraceElementProxyEquivalence<>(isVerbose);
List<List<List<T>>> finalResult = new ArrayList<>();
@@ -192,14 +206,13 @@
stackTrace,
RetraceStackTraceContext.empty(),
(context, stackTraceLine) -> {
- ST parsedLine = stackTraceLineParser.parse(stackTraceLine);
List<Pair<RetraceStackTraceElementProxy<T, ST>, List<T>>> resultsForLine =
new ArrayList<>();
Box<List<T>> currentList = new Box<>();
Set<Wrapper<RetraceStackTraceElementProxy<T, ST>>> seen = new HashSet<>();
List<RetraceStackTraceContext> contexts = new ArrayList<>();
RetraceStackTraceElementProxyResult<T, ST> retraceResult =
- proxyRetracer.retrace(parsedLine, context);
+ proxyRetracer.retrace(stackTraceLine, context);
retraceResult.stream()
.forEach(
retracedElement -> {
@@ -213,7 +226,9 @@
}
}
if (currentList.isSet()) {
- currentList.get().add(parsedLine.toRetracedItem(retracedElement, isVerbose));
+ currentList
+ .get()
+ .add(stackTraceLine.toRetracedItem(retracedElement, isVerbose));
}
});
resultsForLine.sort(Comparator.comparing(Pair::getFirst));
@@ -309,17 +324,44 @@
}
return;
}
- timing.begin("Read proguard map");
DiagnosticsHandler diagnosticsHandler = options.getDiagnosticsHandler();
- // The setup of a retracer should likely also follow a builder pattern instead of having
- // static create methods. That would avoid the need to method overload the construction here
- // and the default create would become the default build of a retracer.
- MappingProvider mappingProvider =
+ timing.begin("Parsing");
+ StackTraceRegularExpressionParser stackTraceLineParser =
+ new StackTraceRegularExpressionParser(options.getRegularExpression());
+ List<StackTraceElementStringProxy> parsedStackTrace = new ArrayList<>();
+ ListUtils.forEachWithIndex(
+ command.getStackTrace(),
+ (line, lineNumber) -> {
+ if (line == null) {
+ diagnosticsHandler.error(
+ RetraceInvalidStackTraceLineDiagnostics.createNull(lineNumber));
+ throw new RetraceAbortException();
+ }
+ parsedStackTrace.add(stackTraceLineParser.parse(line));
+ });
+ timing.end();
+ timing.begin("Read proguard map");
+ ProguardMappingProvider.Builder mappingBuilder =
ProguardMappingProvider.builder()
.setProguardMapProducer(options.getProguardMapProducer())
.setDiagnosticsHandler(diagnosticsHandler)
- .setAllowExperimental(allowExperimentalMapping)
- .build();
+ .setAllowExperimental(allowExperimentalMapping);
+ parsedStackTrace.forEach(
+ proxy -> {
+ if (proxy.hasClassName()) {
+ mappingBuilder.registerUse(proxy.getClassReference());
+ }
+ if (proxy.hasMethodArguments()) {
+ Arrays.stream(proxy.getMethodArguments().split(","))
+ .forEach(typeName -> registerUseFromTypeReference(mappingBuilder, typeName));
+ }
+ if (proxy.hasFieldOrReturnType() && !proxy.getFieldOrReturnType().equals("void")) {
+ registerUseFromTypeReference(mappingBuilder, proxy.getFieldOrReturnType());
+ }
+ });
+ MappingProvider mappingProvider = mappingBuilder.build();
+ timing.end();
+ timing.begin("Retracing");
RetracerImpl retracer =
RetracerImpl.builder()
.setMappingProvider(mappingProvider)
@@ -334,17 +376,16 @@
RetraceUnknownMapVersionDiagnostic.create(mapVersionInfo.getValue()));
}
});
- timing.end();
- timing.begin("Report result");
StringRetrace stringRetrace =
new StringRetrace(
- new StackTraceRegularExpressionParser(options.getRegularExpression()),
+ stackTraceLineParser,
StackTraceElementProxyRetracer.createDefault(retracer),
diagnosticsHandler,
options.isVerbose());
- command
- .getRetracedStackTraceConsumer()
- .accept(stringRetrace.retrace(command.getStackTrace()));
+ List<String> retraced = stringRetrace.retraceParsed(parsedStackTrace);
+ timing.end();
+ timing.begin("Report result");
+ command.getRetracedStackTraceConsumer().accept(retraced);
timing.end();
if (command.printTimes()) {
timing.report();
@@ -355,6 +396,17 @@
}
}
+ private static void registerUseFromTypeReference(
+ ProguardMappingProvider.Builder builder, String typeName) {
+ TypeReference typeReference = Reference.typeFromTypeName(typeName);
+ if (typeReference.isArray()) {
+ typeReference = typeReference.asArray().getBaseType();
+ }
+ if (typeReference.isClass()) {
+ builder.registerUse(typeReference.asClass());
+ }
+ }
+
public static void run(String[] args) throws RetraceFailedException {
// To be compatible with standard retrace and remapper, we translate -arg into --arg.
String[] mappedArgs = new String[args.length];
diff --git a/src/main/java/com/android/tools/r8/retrace/Retracer.java b/src/main/java/com/android/tools/r8/retrace/Retracer.java
index 9a4eadf..15d3b48 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retracer.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retracer.java
@@ -77,6 +77,7 @@
ProguardMappingProvider.builder()
.setProguardMapProducer(proguardMapProducer)
.setDiagnosticsHandler(diagnosticsHandler)
+ .allowLookupAllClasses()
.build();
return Retracer.builder()
.setMappingProvider(mappingProvider)
diff --git a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
index 29ee4d2..b5ed6ae 100644
--- a/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/StringRetrace.java
@@ -106,6 +106,43 @@
}
/**
+ * Retraces a list of parsed stack trace lines and returns a list. Ambiguous and inline frames
+ * will be appended automatically to the retraced string.
+ *
+ * @param stackTrace the incoming parsed stack trace
+ * @return the retraced stack trace
+ */
+ public List<String> retraceParsed(List<StackTraceElementStringProxy> stackTrace) {
+ List<String> retracedStrings = new ArrayList<>();
+ List<List<List<String>>> lists = retraceStackTraceParsed(stackTrace);
+ for (List<List<String>> newLines : lists) {
+ ListUtils.forEachWithIndex(
+ newLines,
+ (inlineFrames, ambiguousIndex) -> {
+ for (int i = 0; i < inlineFrames.size(); i++) {
+ String stackTraceLine = inlineFrames.get(i);
+ if (i == 0 && ambiguousIndex > 0) {
+ // We are reporting an ambiguous frame. To support retracing tools that retrace line
+ // by line we have to emit <OR> at the point of the first 'at ' if we can find it.
+ int indexToInsertOr = stackTraceLine.indexOf("at ");
+ if (indexToInsertOr < 0) {
+ indexToInsertOr =
+ Math.max(StringUtils.firstNonWhitespaceCharacter(stackTraceLine), 0);
+ }
+ retracedStrings.add(
+ stackTraceLine.substring(0, indexToInsertOr)
+ + "<OR> "
+ + stackTraceLine.substring(indexToInsertOr));
+ } else {
+ retracedStrings.add(stackTraceLine);
+ }
+ }
+ });
+ }
+ return retracedStrings;
+ }
+
+ /**
* Retraces a single stack trace line and returns the potential list of original frames
*
* @param stackTraceLine the stack trace line to retrace
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
index c0739c0..1ff65a4 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/MappingProviderInternal.java
@@ -4,9 +4,13 @@
package com.android.tools.r8.retrace.internal;
-import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
+import java.util.Set;
public abstract class MappingProviderInternal {
- abstract ClassNameMapper getClassNameMapper();
+ abstract ClassNamingForNameMapper getClassNaming(String typeName);
+
+ abstract Set<MapVersionMappingInformation> getMapVersions();
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
index 8162f5c..e18adca 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderBuilderImpl.java
@@ -6,15 +6,20 @@
import com.android.tools.r8.DiagnosticsHandler;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.retrace.InvalidMappingFileException;
import com.android.tools.r8.retrace.ProguardMapProducer;
import com.android.tools.r8.retrace.ProguardMappingProvider;
import java.io.BufferedReader;
+import java.util.HashSet;
+import java.util.Set;
public class ProguardMappingProviderBuilderImpl extends ProguardMappingProvider.Builder {
private ProguardMapProducer proguardMapProducer;
private boolean allowExperimental = false;
+ private Set<String> allowedLookup = new HashSet<>();
+ private boolean allowLookupAllClasses = false;
private DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
@Override
@@ -36,6 +41,18 @@
}
@Override
+ public ProguardMappingProvider.Builder registerUse(ClassReference classReference) {
+ allowedLookup.add(classReference.getTypeName());
+ return self();
+ }
+
+ @Override
+ public ProguardMappingProvider.Builder allowLookupAllClasses() {
+ allowLookupAllClasses = true;
+ return self();
+ }
+
+ @Override
public ProguardMappingProvider.Builder setProguardMapProducer(
ProguardMapProducer proguardMapProducer) {
this.proguardMapProducer = proguardMapProducer;
@@ -45,12 +62,23 @@
@Override
public ProguardMappingProvider build() {
try {
- return new ProguardMappingProviderImpl(
- ClassNameMapper.mapperFromBufferedReader(
- new BufferedReader(proguardMapProducer.get()),
- diagnosticsHandler,
- true,
- allowExperimental));
+ if (allowLookupAllClasses) {
+ return new ProguardMappingProviderImpl(
+ ClassNameMapper.mapperFromBufferedReader(
+ new BufferedReader(proguardMapProducer.get()),
+ diagnosticsHandler,
+ true,
+ allowExperimental));
+ } else {
+ return new ProguardMappingProviderImpl(
+ ClassNameMapper.mapperFromBufferedReaderWithFiltering(
+ new BufferedReader(proguardMapProducer.get()),
+ diagnosticsHandler,
+ true,
+ allowExperimental,
+ allowedLookup),
+ allowedLookup);
+ }
} catch (Exception e) {
throw new InvalidMappingFileException(e);
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
index 8ea72fa..dcfe2cb 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/ProguardMappingProviderImpl.java
@@ -5,7 +5,11 @@
package com.android.tools.r8.retrace.internal;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
+import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
+import com.android.tools.r8.retrace.IllegalClassNameLookupException;
import com.android.tools.r8.retrace.ProguardMappingProvider;
+import java.util.Set;
/**
* IntelliJ highlights the class as being invalid because it cannot see getClassNameMapper is
@@ -14,13 +18,29 @@
public class ProguardMappingProviderImpl extends ProguardMappingProvider {
private final ClassNameMapper classNameMapper;
+ private final Set<String> allowedLookupTypeNames;
public ProguardMappingProviderImpl(ClassNameMapper classNameMapper) {
+ this(classNameMapper, null);
+ }
+
+ public ProguardMappingProviderImpl(
+ ClassNameMapper classNameMapper, Set<String> allowedLookupTypeNames) {
this.classNameMapper = classNameMapper;
+ this.allowedLookupTypeNames = allowedLookupTypeNames;
}
@Override
- ClassNameMapper getClassNameMapper() {
- return classNameMapper;
+ Set<MapVersionMappingInformation> getMapVersions() {
+ return classNameMapper.getMapVersions();
+ }
+
+ @Override
+ ClassNamingForNameMapper getClassNaming(String typeName) {
+ // TODO(b/226885646): Enable lookup check when there are no additional lookups.
+ if (false && !allowedLookupTypeNames.contains(typeName)) {
+ throw new IllegalClassNameLookupException(typeName);
+ }
+ return classNameMapper.getClassNaming(typeName);
}
}
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
index 920393b..c4ed860 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetraceUtils.java
@@ -75,6 +75,7 @@
return clazz.substring(lastIndexOfPeriod + 1, endIndex);
}
+ // TODO(b/226885646): Retracing of a source file should not be dependent on retraced information.
public static RetracedSourceFile getSourceFileOrLookup(
RetracedClassReference holder, RetraceClassElement context, Retracer retracer) {
if (holder.equals(context.getRetracedClass())) {
diff --git a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
index b01aa7f..92e42f1 100644
--- a/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/internal/RetracerImpl.java
@@ -5,7 +5,6 @@
package com.android.tools.r8.retrace.internal;
import com.android.tools.r8.DiagnosticsHandler;
-import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.mappinginformation.MapVersionMappingInformation;
import com.android.tools.r8.references.ClassReference;
import com.android.tools.r8.references.FieldReference;
@@ -22,13 +21,14 @@
/** A default implementation for the retrace api using the ClassNameMapper defined in R8. */
public class RetracerImpl implements Retracer {
- private final ClassNameMapper classNameMapper;
+ private final MappingProviderInternal classNameMapperProvider;
private final DiagnosticsHandler diagnosticsHandler;
- private RetracerImpl(ClassNameMapper classNameMapper, DiagnosticsHandler diagnosticsHandler) {
- this.classNameMapper = classNameMapper;
+ private RetracerImpl(
+ MappingProviderInternal classNameMapperProvider, DiagnosticsHandler diagnosticsHandler) {
+ this.classNameMapperProvider = classNameMapperProvider;
this.diagnosticsHandler = diagnosticsHandler;
- assert classNameMapper != null;
+ assert classNameMapperProvider != null;
}
public DiagnosticsHandler getDiagnosticsHandler() {
@@ -70,7 +70,7 @@
@Override
public RetraceClassResultImpl retraceClass(ClassReference classReference) {
return RetraceClassResultImpl.create(
- classReference, classNameMapper.getClassNaming(classReference.getTypeName()), this);
+ classReference, classNameMapperProvider.getClassNaming(classReference.getTypeName()), this);
}
@Override
@@ -84,7 +84,7 @@
}
public Set<MapVersionMappingInformation> getMapVersions() {
- return classNameMapper.getMapVersions();
+ return classNameMapperProvider.getMapVersions();
}
public static Builder builder() {
@@ -112,8 +112,7 @@
@Override
public RetracerImpl build() {
- return new RetracerImpl(
- ((MappingProviderInternal) mappingProvider).getClassNameMapper(), diagnosticsHandler);
+ return new RetracerImpl(mappingProvider, diagnosticsHandler);
}
}
}
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
index ffc2149..cc5d00b 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassNameList.java
@@ -114,11 +114,11 @@
});
}
- public abstract TraversalContinuation<?> traverseTypeMatchers(
- Function<ProguardTypeMatcher, TraversalContinuation<?>> fn);
+ public abstract TraversalContinuation<?, ?> traverseTypeMatchers(
+ Function<ProguardTypeMatcher, TraversalContinuation<?, ?>> fn);
- public final TraversalContinuation<?> traverseTypeMatchers(
- Function<ProguardTypeMatcher, TraversalContinuation<?>> fn,
+ public final TraversalContinuation<?, ?> traverseTypeMatchers(
+ Function<ProguardTypeMatcher, TraversalContinuation<?, ?>> fn,
Predicate<ProguardTypeMatcher> predicate) {
return traverseTypeMatchers(
matcher -> {
@@ -168,8 +168,8 @@
}
@Override
- public TraversalContinuation<?> traverseTypeMatchers(
- Function<ProguardTypeMatcher, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseTypeMatchers(
+ Function<ProguardTypeMatcher, TraversalContinuation<?, ?>> fn) {
return TraversalContinuation.doContinue();
}
}
@@ -236,8 +236,8 @@
}
@Override
- public TraversalContinuation<?> traverseTypeMatchers(
- Function<ProguardTypeMatcher, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseTypeMatchers(
+ Function<ProguardTypeMatcher, TraversalContinuation<?, ?>> fn) {
return fn.apply(className);
}
}
@@ -320,8 +320,8 @@
}
@Override
- public TraversalContinuation<?> traverseTypeMatchers(
- Function<ProguardTypeMatcher, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseTypeMatchers(
+ Function<ProguardTypeMatcher, TraversalContinuation<?, ?>> fn) {
for (ProguardTypeMatcher matcher : classNames) {
if (fn.apply(matcher).shouldBreak()) {
return TraversalContinuation.doBreak();
@@ -416,8 +416,8 @@
}
@Override
- public TraversalContinuation<?> traverseTypeMatchers(
- Function<ProguardTypeMatcher, TraversalContinuation<?>> fn) {
+ public TraversalContinuation<?, ?> traverseTypeMatchers(
+ Function<ProguardTypeMatcher, TraversalContinuation<?, ?>> fn) {
for (ProguardTypeMatcher matcher : classNames.keySet()) {
if (fn.apply(matcher).shouldBreak()) {
return TraversalContinuation.doBreak();
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
index b10e649..484cd3a 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetUtils.java
@@ -1403,7 +1403,7 @@
dependentMinimumKeepInfo.getUnconditionalMinimumKeepInfoOrDefault(
MinimumKeepInfoCollection.empty());
for (DexProgramClass clazz : classesWithCheckDiscardedMembers) {
- TraversalContinuation<?> continueIfAllMembersMarkedAsCheckDiscarded =
+ TraversalContinuation<?, ?> continueIfAllMembersMarkedAsCheckDiscarded =
clazz.traverseProgramMembers(
member ->
TraversalContinuation.continueIf(
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index 6cc5f84..9797f72 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -417,7 +417,7 @@
// If there is a constructor in the target, make sure that all source constructors can be
// inlined.
if (!Iterables.isEmpty(targetClass.programInstanceInitializers())) {
- TraversalContinuation<?> result =
+ TraversalContinuation<?, ?> result =
sourceClass.traverseProgramInstanceInitializers(
method -> {
AbortReason reason = disallowInlining(method, targetClass);
@@ -454,7 +454,7 @@
// If there is an invoke-special to a default interface method and we are not merging into an
// interface, then abort, since invoke-special to a virtual class method requires desugaring.
if (sourceClass.isInterface() && !targetClass.isInterface()) {
- TraversalContinuation<?> result =
+ TraversalContinuation<?, ?> result =
sourceClass.traverseProgramMethods(
method -> {
boolean foundInvokeSpecialToDefaultLibraryMethod =
@@ -586,7 +586,7 @@
// Check that all accesses from [source] to classes or members from the current package of
// [source] will continue to work. This is guaranteed if the methods of [source] do not access
// any private or protected classes or members from the current package of [source].
- TraversalContinuation<?> result =
+ TraversalContinuation<?, ?> result =
source.traverseProgramMethods(
method -> {
boolean foundIllegalAccess =
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 3559fe3..05ce2e9 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -84,18 +84,28 @@
public final SyntheticKind ARRAY_CONVERSION = generator.forSingleMethod("$ArrayConversion");
public final SyntheticKind API_MODEL_OUTLINE = generator.forSingleMethod("ApiModelOutline");
- private final String versionHash;
private final List<SyntheticKind> ALL_KINDS;
+ private String lazyVersionHash = null;
public SyntheticNaming() {
- generator.hasher.putString(Version.getVersionString(), StandardCharsets.UTF_8);
- versionHash = generator.hasher.hash().toString();
ALL_KINDS = generator.getAllKinds();
generator = null;
}
public String getVersionHash() {
- return versionHash;
+ if (lazyVersionHash == null) {
+ computeVersionHash();
+ }
+ return lazyVersionHash;
+ }
+
+ private void computeVersionHash() {
+ Hasher hasher = Hashing.sha256().newHasher();
+ hasher.putString(Version.getVersionString(), StandardCharsets.UTF_8);
+ for (SyntheticKind kind : ALL_KINDS) {
+ kind.hash(hasher);
+ }
+ lazyVersionHash = hasher.hash().toString();
}
public Collection<SyntheticKind> kinds() {
@@ -112,10 +122,8 @@
private static class KindGenerator {
private int nextId = 1;
private List<SyntheticKind> kinds = new ArrayList<>();
- private Hasher hasher = Hashing.sha256().newHasher();
private SyntheticKind register(SyntheticKind kind) {
- kind.hash(hasher);
kinds.add(kind);
if (kinds.size() != kind.getId()) {
throw new Unreachable("Invalid synthetic kind id: " + kind.getId());
diff --git a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
index 6b5df9b..c9a56da 100644
--- a/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
+++ b/src/main/java/com/android/tools/r8/utils/DepthFirstSearchWorkListBase.java
@@ -107,10 +107,10 @@
abstract T createDfsNode(N node);
/** The initial processing of a node during forward search */
- abstract TraversalContinuation<?> internalOnVisit(T node);
+ abstract TraversalContinuation<?, ?> internalOnVisit(T node);
/** The joining of state during backtracking of the algorithm. */
- abstract TraversalContinuation<?> internalOnJoin(T node);
+ abstract TraversalContinuation<?, ?> internalOnJoin(T node);
final T internalEnqueueNode(N value) {
T dfsNode = createDfsNode(value);
@@ -121,13 +121,13 @@
}
@SafeVarargs
- public final TraversalContinuation<?> run(N... roots) {
+ public final TraversalContinuation<?, ?> run(N... roots) {
return run(Arrays.asList(roots));
}
- public final TraversalContinuation<?> run(Collection<N> roots) {
+ public final TraversalContinuation<?, ?> run(Collection<N> roots) {
roots.forEach(this::internalEnqueueNode);
- TraversalContinuation<?> continuation = TraversalContinuation.doContinue();
+ TraversalContinuation<?, ?> continuation = TraversalContinuation.doContinue();
while (!workList.isEmpty()) {
T node = workList.removeLast();
if (node.isFinished()) {
@@ -161,7 +161,7 @@
* before but not finished there is a cycle.
* @return A value describing if the DFS algorithm should continue to run.
*/
- protected abstract TraversalContinuation<?> process(
+ protected abstract TraversalContinuation<?, ?> process(
DFSNode<N> node, Function<N, DFSNode<N>> childNodeConsumer);
@Override
@@ -170,12 +170,12 @@
}
@Override
- TraversalContinuation<?> internalOnVisit(DFSNodeImpl<N> node) {
+ TraversalContinuation<?, ?> internalOnVisit(DFSNodeImpl<N> node) {
return process(node, this::internalEnqueueNode);
}
@Override
- protected TraversalContinuation<?> internalOnJoin(DFSNodeImpl<N> node) {
+ protected TraversalContinuation<?, ?> internalOnJoin(DFSNodeImpl<N> node) {
return TraversalContinuation.doContinue();
}
}
@@ -195,7 +195,7 @@
* before but not finished there is a cycle.
* @return A value describing if the DFS algorithm should continue to run.
*/
- protected abstract TraversalContinuation<?> process(
+ protected abstract TraversalContinuation<?, ?> process(
DFSNodeWithState<N, S> node, Function<N, DFSNodeWithState<N, S>> childNodeConsumer);
/**
@@ -205,7 +205,7 @@
* @param childStates The already computed child states.
* @return A value describing if the DFS algorithm should continue to run.
*/
- protected abstract TraversalContinuation<?> joiner(
+ protected abstract TraversalContinuation<?, ?> joiner(
DFSNodeWithState<N, S> node, List<DFSNodeWithState<N, S>> childStates);
@Override
@@ -214,7 +214,7 @@
}
@Override
- TraversalContinuation<?> internalOnVisit(DFSNodeWithStateImpl<N, S> node) {
+ TraversalContinuation<?, ?> internalOnVisit(DFSNodeWithStateImpl<N, S> node) {
List<DFSNodeWithState<N, S>> childStates = new ArrayList<>();
List<DFSNodeWithState<N, S>> removedChildStates = childStateMap.put(node, childStates);
assert removedChildStates == null;
@@ -228,7 +228,7 @@
}
@Override
- protected TraversalContinuation<?> internalOnJoin(DFSNodeWithStateImpl<N, S> node) {
+ protected TraversalContinuation<?, ?> internalOnJoin(DFSNodeWithStateImpl<N, S> node) {
return joiner(
node,
childStateMap.computeIfAbsent(
diff --git a/src/main/java/com/android/tools/r8/utils/DisjointSets.java b/src/main/java/com/android/tools/r8/utils/DisjointSets.java
index a39b482..d8e42b9 100644
--- a/src/main/java/com/android/tools/r8/utils/DisjointSets.java
+++ b/src/main/java/com/android/tools/r8/utils/DisjointSets.java
@@ -125,9 +125,6 @@
* <p>Returns the representative for the union set.
*/
public T unionWithMakeSet(T element1, T element2) {
- if (element1.toString().contains("Enum") || element2.toString().contains("Enum")) {
- System.out.println();
- }
if (element1 == element2) {
return findOrMakeSet(element1);
}
diff --git a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
index 5bd242f..19f89d8 100644
--- a/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
+++ b/src/main/java/com/android/tools/r8/utils/TraversalContinuation.java
@@ -7,34 +7,58 @@
/** Two value continuation value to indicate the continuation of a loop/traversal. */
/* This class is used for building up api class member traversals. */
-public abstract class TraversalContinuation<T> {
+public abstract class TraversalContinuation<TB, TC> {
public boolean isBreak() {
- return !isContinue();
+ return false;
+ }
+
+ public Break<TB, TC> asBreak() {
+ return null;
}
public boolean isContinue() {
return false;
}
- public Break<T> asBreak() {
+ public Continue<TB, TC> asContinue() {
return null;
}
- public static final class Continue<T> extends TraversalContinuation<T> {
- private static final TraversalContinuation<?> CONTINUE = new Continue<Object>();
+ public static class Continue<TB, TC> extends TraversalContinuation<TB, TC> {
+ private static final TraversalContinuation<?, ?> CONTINUE_NO_VALUE =
+ new Continue<Object, Object>(null) {
+ @Override
+ public Object getValue() {
+ return new Unreachable(
+ "Invalid attempt at getting a value from a no-value continue state.");
+ }
+ };
- private Continue() {}
+ private final TC value;
+
+ private Continue(TC value) {
+ this.value = value;
+ }
+
+ public TC getValue() {
+ return value;
+ }
@Override
public boolean isContinue() {
return true;
}
+
+ @Override
+ public Continue<TB, TC> asContinue() {
+ return this;
+ }
}
- public static class Break<T> extends TraversalContinuation<T> {
- private static final TraversalContinuation<?> BREAK_NO_VALUE =
- new Break<Object>(null) {
+ public static class Break<TB, TC> extends TraversalContinuation<TB, TC> {
+ private static final TraversalContinuation<?, ?> BREAK_NO_VALUE =
+ new Break<Object, Object>(null) {
@Override
public Object getValue() {
return new Unreachable(
@@ -42,40 +66,50 @@
}
};
- private final T value;
+ private final TB value;
- private Break(T value) {
+ private Break(TB value) {
this.value = value;
}
- public T getValue() {
+ public TB getValue() {
return value;
}
@Override
- public Break<T> asBreak() {
+ public boolean isBreak() {
+ return true;
+ }
+
+ @Override
+ public Break<TB, TC> asBreak() {
return this;
}
}
- public static TraversalContinuation<?> breakIf(boolean condition) {
+ public static TraversalContinuation<?, ?> breakIf(boolean condition) {
return continueIf(!condition);
}
- public static TraversalContinuation<?> continueIf(boolean condition) {
+ public static TraversalContinuation<?, ?> continueIf(boolean condition) {
return condition ? doContinue() : doBreak();
}
@SuppressWarnings("unchecked")
- public static <T> TraversalContinuation<T> doContinue() {
- return (TraversalContinuation<T>) Continue.CONTINUE;
+ public static <TB, TC> TraversalContinuation<TB, TC> doContinue() {
+ return (TraversalContinuation<TB, TC>) Continue.CONTINUE_NO_VALUE;
}
- public static TraversalContinuation<?> doBreak() {
- return Break.BREAK_NO_VALUE;
+ public static <TB, TC> TraversalContinuation<TB, TC> doContinue(TC value) {
+ return new Continue<>(value);
}
- public static <T> TraversalContinuation<T> doBreak(T value) {
+ @SuppressWarnings("unchecked")
+ public static <TB, TC> TraversalContinuation<TB, TC> doBreak() {
+ return (TraversalContinuation<TB, TC>) Break.BREAK_NO_VALUE;
+ }
+
+ public static <TB, TC> TraversalContinuation<TB, TC> doBreak(TB value) {
return new Break<>(value);
}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollectionPrinter.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollectionPrinter.java
index c7336f7..1a26aa7 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollectionPrinter.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkCollectionPrinter.java
@@ -5,6 +5,7 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.CollectionUtils;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
@@ -19,9 +20,11 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
public class BenchmarkCollectionPrinter {
@@ -100,29 +103,77 @@
for (String benchmarkName : sortedNames) {
List<BenchmarkConfig> benchmarkTargets = nameToTargets.get(benchmarkName);
assert !benchmarkTargets.isEmpty();
- scopeBraces(() -> printBenchmarkBlock(benchmarkName, benchmarkTargets));
+ if (benchmarkTargets.get(0).isSingleBenchmark()) {
+ scopeBraces(() -> printStandardBenchmarkBlock(benchmarkName, benchmarkTargets));
+ } else {
+ scopeBraces(() -> printGroupBenchmarkBlock(benchmarkName, benchmarkTargets));
+ }
}
});
print("}");
}
- private void printBenchmarkBlock(String benchmarkName, List<BenchmarkConfig> benchmarkVariants)
- throws IOException {
- // Common properties that must be consistent among all the benchmark variants.
+ private String prettyMetrics(Collection<BenchmarkMetric> metrics) {
+ List<String> metricsStrings =
+ new ArrayList<>(ListUtils.map(metrics, BenchmarkMetric::getDartType));
+ metricsStrings.sort(String::compareTo);
+ return StringUtils.join(", ", metricsStrings, BraceType.SQUARE);
+ }
+
+ private void printStandardBenchmarkBlock(
+ String benchmarkName, List<BenchmarkConfig> benchmarkVariants) throws IOException {
String suite = BenchmarkConfig.getCommonSuite(benchmarkVariants).getDartName();
- List<String> metrics =
- new ArrayList<>(
- ListUtils.map(
- BenchmarkConfig.getCommonMetrics(benchmarkVariants), BenchmarkMetric::getDartType));
- metrics.sort(String::compareTo);
+ List<BenchmarkMetric> metrics =
+ new ArrayList<>(BenchmarkConfig.getCommonMetrics(benchmarkVariants));
+ if (BenchmarkConfig.getCommonHasTimeWarmup(benchmarkVariants)) {
+ metrics.add(BenchmarkMetric.StartupTime);
+ }
printSemi("final name = " + quote(benchmarkName));
- printSemi("final metrics = " + StringUtils.join(", ", metrics, BraceType.SQUARE));
+ printSemi("final metrics = " + prettyMetrics(metrics));
printSemi("final benchmark = StandardBenchmark(name, metrics)");
BenchmarkTimeout timeout = BenchmarkConfig.getCommonTimeout(benchmarkVariants);
if (timeout != null) {
printSemi("final timeout = const Duration(seconds: " + timeout.asSeconds() + ")");
printSemi("ExecutionManagement.addTimeoutConstraint(timeout, benchmark: benchmark)");
}
+ printBenchmarkVariantOptionBlock(benchmarkVariants);
+ printSemi(suite + ".addBenchmark(name)");
+ }
+
+ private void printGroupBenchmarkBlock(
+ String benchmarkName, List<BenchmarkConfig> benchmarkVariants) throws IOException {
+ String suite = BenchmarkConfig.getCommonSuite(benchmarkVariants).getDartName();
+ printSemi("final name = " + quote(benchmarkName));
+ printSemi("final benchmark = GroupBenchmark(name, [])");
+ BenchmarkTimeout timeout = BenchmarkConfig.getCommonTimeout(benchmarkVariants);
+ if (timeout != null) {
+ printSemi("final timeout = const Duration(seconds: " + timeout.asSeconds() + ")");
+ printSemi("ExecutionManagement.addTimeoutConstraint(timeout, benchmark: benchmark)");
+ }
+ printBenchmarkVariantOptionBlock(benchmarkVariants);
+ Map<String, Set<BenchmarkMetric>> subBenchmarks =
+ BenchmarkConfig.getCommonSubBenchmarks(benchmarkVariants);
+ Collection<String> subNames = CollectionUtils.sort(subBenchmarks.keySet(), String::compareTo);
+ for (String subName : subNames) {
+ scopeBraces(
+ () -> {
+ Set<BenchmarkMetric> subMetrics = subBenchmarks.get(subName);
+ printSemi("final subName = " + quote(subName));
+ printSemi("benchmark.addBenchmark(subName, " + prettyMetrics(subMetrics) + ")");
+ printSemi(suite + ".addBenchmark(subName)");
+ });
+ }
+ if (BenchmarkConfig.getCommonHasTimeWarmup(benchmarkVariants)) {
+ printSemi(
+ "benchmark.addBenchmark(name, "
+ + prettyMetrics(Collections.singletonList(BenchmarkMetric.StartupTime))
+ + ")");
+ }
+ printSemi(suite + ".addBenchmark(name)");
+ }
+
+ private void printBenchmarkVariantOptionBlock(List<BenchmarkConfig> benchmarkVariants)
+ throws IOException {
for (BenchmarkConfig benchmark : benchmarkVariants) {
scopeBraces(
() -> {
@@ -153,7 +204,6 @@
}
});
}
- printSemi(suite + ".addBenchmark(name)");
}
private void addGolemResource(String name, Path tarball) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java
index 56ea759..b04a407 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkConfig.java
@@ -3,12 +3,16 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.benchmarks;
-import com.android.tools.r8.errors.Unreachable;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -18,9 +22,23 @@
if (benchmark.getTarget().equals(other.getTarget())) {
throw new BenchmarkConfigError("Duplicate benchmark name and target: " + benchmark);
}
- if (!benchmark.getMetrics().equals(other.getMetrics())) {
+ if (benchmark.isSingleBenchmark() != other.isSingleBenchmark()) {
throw new BenchmarkConfigError(
- "Inconsistent metrics for benchmarks: " + benchmark + " and " + other);
+ "Inconsistent single/group benchmark setup: " + benchmark + " and " + other);
+ }
+ Set<String> subNames =
+ Sets.union(benchmark.getSubBenchmarks().keySet(), other.getSubBenchmarks().keySet());
+ for (String subName : subNames) {
+ if (!Objects.equals(
+ benchmark.getSubBenchmarks().get(subName), other.getSubBenchmarks().get(subName))) {
+ throw new BenchmarkConfigError(
+ "Inconsistent metrics for sub-benchmark "
+ + subName
+ + " in benchmarks: "
+ + benchmark
+ + " and "
+ + other);
+ }
}
if (!benchmark.getSuite().equals(other.getSuite())) {
throw new BenchmarkConfigError(
@@ -40,6 +58,15 @@
return getConsistentRepresentative(variants).getSuite();
}
+ public static boolean getCommonHasTimeWarmup(List<BenchmarkConfig> variants) {
+ return getConsistentRepresentative(variants).hasTimeWarmupRuns();
+ }
+
+ public static Map<String, Set<BenchmarkMetric>> getCommonSubBenchmarks(
+ List<BenchmarkConfig> variants) {
+ return getConsistentRepresentative(variants).getSubBenchmarks();
+ }
+
// Use the largest configured timeout as the timeout for the full group.
public static BenchmarkTimeout getCommonTimeout(List<BenchmarkConfig> variants) {
BenchmarkTimeout timeout = null;
@@ -71,41 +98,63 @@
private BenchmarkMethod method = null;
private BenchmarkTarget target = null;
private Set<BenchmarkMetric> metrics = new HashSet<>();
+ private Map<String, Set<BenchmarkMetric>> subBenchmarks = new HashMap<>();
private BenchmarkSuite suite = BenchmarkSuite.getDefault();
private Collection<BenchmarkDependency> dependencies = new ArrayList<>();
private int fromRevision = -1;
private BenchmarkTimeout timeout = null;
+ private boolean measureWarmup = false;
private Builder() {}
public BenchmarkConfig build() {
if (name == null) {
- throw new Unreachable("Benchmark name must be set");
+ throw new BenchmarkConfigError("Benchmark name must be set");
}
if (method == null) {
- throw new Unreachable("Benchmark method must be set");
+ throw new BenchmarkConfigError("Benchmark method must be set");
}
if (target == null) {
- throw new Unreachable("Benchmark target must be set");
- }
- if (metrics.isEmpty()) {
- throw new Unreachable("Benchmark must have at least one metric to measure");
+ throw new BenchmarkConfigError("Benchmark target must be set");
}
if (suite == null) {
- throw new Unreachable("Benchmark must have a suite");
+ throw new BenchmarkConfigError("Benchmark must have a suite");
}
if (fromRevision < 0) {
- throw new Unreachable("Benchmark must specify from which golem revision it is valid");
+ throw new BenchmarkConfigError(
+ "Benchmark must specify from which golem revision it is valid");
+ }
+ if (!metrics.isEmpty()) {
+ if (subBenchmarks.containsKey(name)) {
+ throw new BenchmarkConfigError(
+ "Benchmark must not specify both direct metrics and a sub-benchmark of the same"
+ + " name");
+ }
+ subBenchmarks.put(name, ImmutableSet.copyOf(metrics));
+ }
+ if (subBenchmarks.isEmpty()) {
+ throw new BenchmarkConfigError(
+ "Benchmark must have at least one metric / sub-benchmark to measure");
+ }
+ if (measureWarmup) {
+ for (Set<BenchmarkMetric> subMetrics : subBenchmarks.values()) {
+ if (subMetrics.contains(BenchmarkMetric.StartupTime)) {
+ throw new BenchmarkConfigError(
+ "Benchmark cannot both measure warmup and set metric: "
+ + BenchmarkMetric.StartupTime);
+ }
+ }
}
return new BenchmarkConfig(
name,
method,
target,
- ImmutableSet.copyOf(metrics),
+ subBenchmarks,
suite,
fromRevision,
dependencies,
- timeout);
+ timeout,
+ measureWarmup);
}
public Builder setName(String name) {
@@ -134,7 +183,16 @@
}
public Builder measureWarmup() {
- metrics.add(BenchmarkMetric.StartupTime);
+ measureWarmup = true;
+ return this;
+ }
+
+ public Builder addSubBenchmark(String name, BenchmarkMetric... metrics) {
+ return addSubBenchmark(name, new HashSet<>(Arrays.asList(metrics)));
+ }
+
+ public Builder addSubBenchmark(String name, Set<BenchmarkMetric> metrics) {
+ subBenchmarks.put(name, metrics);
return this;
}
@@ -165,28 +223,31 @@
private final BenchmarkIdentifier id;
private final BenchmarkMethod method;
- private final ImmutableSet<BenchmarkMetric> metrics;
+ private final Map<String, Set<BenchmarkMetric>> benchmarks;
private final BenchmarkSuite suite;
private final Collection<BenchmarkDependency> dependencies;
private final int fromRevision;
private final BenchmarkTimeout timeout;
+ private final boolean measureWarmup;
private BenchmarkConfig(
String name,
BenchmarkMethod benchmarkMethod,
BenchmarkTarget target,
- ImmutableSet<BenchmarkMetric> metrics,
+ Map<String, Set<BenchmarkMetric>> benchmarks,
BenchmarkSuite suite,
int fromRevision,
Collection<BenchmarkDependency> dependencies,
- BenchmarkTimeout timeout) {
+ BenchmarkTimeout timeout,
+ boolean measureWarmup) {
this.id = new BenchmarkIdentifier(name, target);
this.method = benchmarkMethod;
- this.metrics = metrics;
+ this.benchmarks = benchmarks;
this.suite = suite;
this.fromRevision = fromRevision;
this.dependencies = dependencies;
this.timeout = timeout;
+ this.measureWarmup = measureWarmup;
}
public BenchmarkIdentifier getIdentifier() {
@@ -205,12 +266,19 @@
return id.getTarget();
}
- public Set<BenchmarkMetric> getMetrics() {
- return metrics;
+ public boolean isSingleBenchmark() {
+ return benchmarks.size() == 1 && benchmarks.containsKey(getName());
}
- public boolean hasMetric(BenchmarkMetric metric) {
- return metrics.contains(metric);
+ public Map<String, Set<BenchmarkMetric>> getSubBenchmarks() {
+ return benchmarks;
+ }
+
+ public Set<BenchmarkMetric> getMetrics() {
+ if (!isSingleBenchmark()) {
+ throw new BenchmarkConfigError("Attempt to get single metrics set from group benchmark");
+ }
+ return benchmarks.get(getName());
}
public BenchmarkSuite getSuite() {
@@ -222,7 +290,7 @@
}
public boolean hasTimeWarmupRuns() {
- return hasMetric(BenchmarkMetric.StartupTime);
+ return measureWarmup;
}
public Collection<BenchmarkDependency> getDependencies() {
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResults.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
index 51f9f3a..4fa9f24 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResults.java
@@ -4,98 +4,25 @@
package com.android.tools.r8.benchmarks;
import com.android.tools.r8.benchmarks.BenchmarkRunner.ResultMode;
-import it.unimi.dsi.fastutil.longs.LongArrayList;
-import it.unimi.dsi.fastutil.longs.LongList;
-public class BenchmarkResults {
+public interface BenchmarkResults {
+ // Append a runtime result. This may be summed or averaged depending on the benchmark set up.
+ void addRuntimeResult(long result);
- private final BenchmarkMetric runtimeMetric;
- private final LongList runtimeResults = new LongArrayList();
- private final LongList codeSizeResults = new LongArrayList();
+ // Append a code size result. This is always assumed to be identical if called multiple times.
+ void addCodeSizeResult(long result);
- public static BenchmarkResults create() {
- return new BenchmarkResults(false);
- }
+ // Get the results collection for a "sub-benchmark" when defining a group of benchmarks.
+ // This will throw if called on a benchmark without sub-benchmarks.
+ BenchmarkResults getSubResults(String name);
- public static BenchmarkResults createForWarmup() {
- return new BenchmarkResults(true);
- }
+ void printResults(ResultMode resultMode);
- private BenchmarkResults(boolean isWarmupResults) {
- this.runtimeMetric = isWarmupResults ? BenchmarkMetric.StartupTime : BenchmarkMetric.RunTimeRaw;
- }
-
- private boolean isWarmupResults() {
- return runtimeMetric == BenchmarkMetric.StartupTime;
- }
-
- private String getName(BenchmarkConfig config) {
- return config.getName();
- }
-
- public void addRuntimeResult(long result) {
- runtimeResults.add(result);
- }
-
- public void addCodeSizeResult(long result) {
- codeSizeResults.add(result);
- }
-
- private static void verifyMetric(BenchmarkMetric metric, boolean expected, boolean actual) {
- if (expected != actual) {
- throw new BenchmarkConfigError(
- "Mismatched config and result for "
- + metric.name()
- + ". Expected by config: "
- + expected
- + ", but has result: "
- + actual);
- }
- }
-
- private void verifyConfigAndResults(BenchmarkConfig config) {
- verifyMetric(
- BenchmarkMetric.RunTimeRaw,
- config.getMetrics().contains(BenchmarkMetric.RunTimeRaw),
- !runtimeResults.isEmpty());
- verifyMetric(
- BenchmarkMetric.CodeSize,
- config.getMetrics().contains(BenchmarkMetric.CodeSize),
- !codeSizeResults.isEmpty());
- }
-
- public static String prettyTime(long nanoTime) {
+ static String prettyTime(long nanoTime) {
return "" + (nanoTime / 1000000) + " ms";
}
- private void printRunTime(BenchmarkConfig config, long duration) {
- String metric = runtimeMetric.name();
- System.out.println(getName(config) + "(" + metric + "): " + prettyTime(duration));
- }
-
- private void printCodeSize(BenchmarkConfig config, long bytes) {
- System.out.println(getName(config) + "(CodeSize): " + bytes);
- }
-
- public void printResults(ResultMode mode, BenchmarkConfig config) {
- verifyConfigAndResults(config);
- if (config.hasMetric(runtimeMetric)) {
- long sum = runtimeResults.stream().mapToLong(l -> l).sum();
- if (mode == ResultMode.SUM) {
- printRunTime(config, sum);
- } else if (mode == ResultMode.AVERAGE) {
- printRunTime(config, sum / runtimeResults.size());
- }
- }
- if (!isWarmupResults() && config.hasMetric(BenchmarkMetric.CodeSize)) {
- long size = codeSizeResults.getLong(0);
- for (int i = 1; i < codeSizeResults.size(); i++) {
- if (size != codeSizeResults.getLong(i)) {
- throw new RuntimeException(
- "Unexpected code size difference: " + size + " and " + codeSizeResults.getLong(i));
- }
- }
- printCodeSize(config, size);
- }
+ static String prettyMetric(String name, BenchmarkMetric metric, String value) {
+ return name + "(" + metric.name() + "): " + value;
}
}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
new file mode 100644
index 0000000..e9b205d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsCollection.java
@@ -0,0 +1,49 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import com.android.tools.r8.benchmarks.BenchmarkRunner.ResultMode;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class BenchmarkResultsCollection implements BenchmarkResults {
+
+ private final Map<String, BenchmarkResultsSingle> results;
+
+ public BenchmarkResultsCollection(Map<String, Set<BenchmarkMetric>> benchmarks) {
+ results = new HashMap<>(benchmarks.size());
+ benchmarks.forEach(
+ (name, metrics) -> results.put(name, new BenchmarkResultsSingle(name, metrics)));
+ }
+
+ public void addRuntimeResult(long result) {
+ throw new BenchmarkConfigError(
+ "Unexpected attempt to add a runtime result to a the root of a benchmark with"
+ + " sub-benchmarks");
+ }
+
+ public void addCodeSizeResult(long result) {
+ throw new BenchmarkConfigError(
+ "Unexpected attempt to add a runtime result to a the root of a benchmark with"
+ + " sub-benchmarks");
+ }
+
+ @Override
+ public BenchmarkResults getSubResults(String name) {
+ return results.get(name);
+ }
+
+ @Override
+ public void printResults(ResultMode mode) {
+ List<String> sorted = new ArrayList<>(results.keySet());
+ sorted.sort(String::compareTo);
+ for (String name : sorted) {
+ BenchmarkResultsSingle singleResults = results.get(name);
+ singleResults.printResults(mode);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
new file mode 100644
index 0000000..b3c19d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsSingle.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import com.android.tools.r8.benchmarks.BenchmarkRunner.ResultMode;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongList;
+import java.util.Set;
+
+public class BenchmarkResultsSingle implements BenchmarkResults {
+
+ private String name;
+ private final Set<BenchmarkMetric> metrics;
+ private final LongList runtimeResults = new LongArrayList();
+ private final LongList codeSizeResults = new LongArrayList();
+
+ public BenchmarkResultsSingle(String name, Set<BenchmarkMetric> metrics) {
+ this.name = name;
+ this.metrics = metrics;
+ }
+
+ @Override
+ public void addRuntimeResult(long result) {
+ verifyMetric(BenchmarkMetric.RunTimeRaw, metrics.contains(BenchmarkMetric.RunTimeRaw), true);
+ runtimeResults.add(result);
+ }
+
+ @Override
+ public void addCodeSizeResult(long result) {
+ verifyMetric(BenchmarkMetric.CodeSize, metrics.contains(BenchmarkMetric.CodeSize), true);
+ codeSizeResults.add(result);
+ }
+
+ @Override
+ public BenchmarkResults getSubResults(String name) {
+ throw new BenchmarkConfigError(
+ "Unexpected attempt to get sub-results for benchmark without sub-benchmarks");
+ }
+
+ private static void verifyMetric(BenchmarkMetric metric, boolean expected, boolean actual) {
+ if (expected != actual) {
+ throw new BenchmarkConfigError(
+ "Mismatched config and result for "
+ + metric.name()
+ + ". Expected by config: "
+ + expected
+ + ", but has result: "
+ + actual);
+ }
+ }
+
+ private void verifyConfigAndResults() {
+ verifyMetric(
+ BenchmarkMetric.RunTimeRaw,
+ metrics.contains(BenchmarkMetric.RunTimeRaw),
+ !runtimeResults.isEmpty());
+ verifyMetric(
+ BenchmarkMetric.CodeSize,
+ metrics.contains(BenchmarkMetric.CodeSize),
+ !codeSizeResults.isEmpty());
+ }
+
+ private void printRunTime(long duration) {
+ String value = BenchmarkResults.prettyTime(duration);
+ System.out.println(BenchmarkResults.prettyMetric(name, BenchmarkMetric.RunTimeRaw, value));
+ }
+
+ private void printCodeSize(long bytes) {
+ System.out.println(BenchmarkResults.prettyMetric(name, BenchmarkMetric.CodeSize, "" + bytes));
+ }
+
+ @Override
+ public void printResults(ResultMode mode) {
+ verifyConfigAndResults();
+ if (!runtimeResults.isEmpty()) {
+ long sum = runtimeResults.stream().mapToLong(l -> l).sum();
+ long result = mode == ResultMode.SUM ? sum : sum / runtimeResults.size();
+ printRunTime(result);
+ }
+ if (!codeSizeResults.isEmpty()) {
+ long size = codeSizeResults.getLong(0);
+ for (int i = 1; i < codeSizeResults.size(); i++) {
+ if (size != codeSizeResults.getLong(i)) {
+ throw new RuntimeException(
+ "Unexpected code size difference: " + size + " and " + codeSizeResults.getLong(i));
+ }
+ }
+ printCodeSize(size);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
new file mode 100644
index 0000000..d866577
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkResultsWarmup.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2022, 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.benchmarks;
+
+import com.android.tools.r8.benchmarks.BenchmarkRunner.ResultMode;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongList;
+
+public class BenchmarkResultsWarmup implements BenchmarkResults {
+
+ private final String name;
+ private final LongList runtimeResults = new LongArrayList();
+ private long codeSizeResult = -1;
+
+ public BenchmarkResultsWarmup(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public void addRuntimeResult(long result) {
+ runtimeResults.add(result);
+ }
+
+ @Override
+ public void addCodeSizeResult(long result) {
+ if (codeSizeResult == -1) {
+ codeSizeResult = result;
+ }
+ if (codeSizeResult != result) {
+ throw new RuntimeException(
+ "Unexpected code size difference: " + result + " and " + codeSizeResult);
+ }
+ }
+
+ @Override
+ public BenchmarkResults getSubResults(String name) {
+ // When running warmups all results are amended to the single warmup result.
+ return this;
+ }
+
+ @Override
+ public void printResults(ResultMode mode) {
+ if (runtimeResults.isEmpty()) {
+ throw new BenchmarkConfigError("Expected runtime results for warmup run");
+ }
+ long sum = runtimeResults.stream().mapToLong(l -> l).sum();
+ long result = mode == ResultMode.SUM ? sum : sum / runtimeResults.size();
+ System.out.println(
+ BenchmarkResults.prettyMetric(
+ name, BenchmarkMetric.StartupTime, BenchmarkResults.prettyTime(result)));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
index f0cb0b8..0f1384c 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/BenchmarkRunner.java
@@ -54,7 +54,7 @@
public void run(BenchmarkRunnerFunction fn) throws Exception {
long warmupTotalTime = 0;
- BenchmarkResults warmupResults = BenchmarkResults.createForWarmup();
+ BenchmarkResults warmupResults = new BenchmarkResultsWarmup(config.getName());
if (warmups > 0) {
long start = System.nanoTime();
for (int i = 0; i < warmups; i++) {
@@ -62,7 +62,10 @@
}
warmupTotalTime = System.nanoTime() - start;
}
- BenchmarkResults results = BenchmarkResults.create();
+ BenchmarkResults results =
+ config.isSingleBenchmark()
+ ? new BenchmarkResultsSingle(config.getName(), config.getMetrics())
+ : new BenchmarkResultsCollection(config.getSubBenchmarks());
long start = System.nanoTime();
for (int i = 0; i < iterations; i++) {
fn.run(results);
@@ -76,11 +79,11 @@
if (warmups > 0) {
printMetaInfo("warmup", warmups, warmupTotalTime);
if (config.hasTimeWarmupRuns()) {
- warmupResults.printResults(resultMode, config);
+ warmupResults.printResults(resultMode);
}
}
printMetaInfo("benchmark", iterations, benchmarkTotalTime);
- results.printResults(resultMode, config);
+ results.printResults(resultMode);
System.out.println();
}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
index 683d4f5..f8b16df 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/AppDumpBenchmarkBuilder.java
@@ -11,12 +11,17 @@
import com.android.tools.r8.benchmarks.BenchmarkDependency;
import com.android.tools.r8.benchmarks.BenchmarkEnvironment;
import com.android.tools.r8.benchmarks.BenchmarkMethod;
+import com.android.tools.r8.benchmarks.BenchmarkMetric;
import com.android.tools.r8.benchmarks.BenchmarkSuite;
import com.android.tools.r8.benchmarks.BenchmarkTarget;
import com.android.tools.r8.dump.CompilerDump;
import com.android.tools.r8.dump.DumpOptions;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
import java.util.concurrent.TimeUnit;
public class AppDumpBenchmarkBuilder {
@@ -28,6 +33,7 @@
private String name;
private BenchmarkDependency dumpDependency;
private int fromRevision = -1;
+ private List<String> programPackages = new ArrayList<>();
public void verify() {
if (name == null) {
@@ -64,13 +70,22 @@
return this;
}
- public BenchmarkConfig build() {
+ public AppDumpBenchmarkBuilder addProgramPackages(String... pkgs) {
+ return addProgramPackages(Arrays.asList(pkgs));
+ }
+
+ public AppDumpBenchmarkBuilder addProgramPackages(Collection<String> pkgs) {
+ programPackages.addAll(pkgs);
+ return this;
+ }
+
+ public BenchmarkConfig buildR8() {
verify();
return BenchmarkConfig.builder()
.setName(name)
.setTarget(BenchmarkTarget.R8_NON_COMPAT)
.setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
- .setMethod(run(this))
+ .setMethod(runR8(this))
.setFromRevision(fromRevision)
.addDependency(dumpDependency)
.measureRunTime()
@@ -79,12 +94,59 @@
.build();
}
+ public BenchmarkConfig buildIncrementalD8() {
+ verify();
+ if (programPackages.isEmpty()) {
+ throw new BenchmarkConfigError(
+ "Incremental benchmark should specifiy at least one program package");
+ }
+ return BenchmarkConfig.builder()
+ .setName(name)
+ .setTarget(BenchmarkTarget.D8)
+ .setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
+ .setMethod(runIncrementalD8(this))
+ .setFromRevision(fromRevision)
+ .addDependency(dumpDependency)
+ .addSubBenchmark(nameForLibraryPart(), BenchmarkMetric.RunTimeRaw)
+ .addSubBenchmark(nameForProgramPart(), BenchmarkMetric.RunTimeRaw)
+ .addSubBenchmark(nameForMergePart(), BenchmarkMetric.RunTimeRaw)
+ .setTimeout(10, TimeUnit.MINUTES)
+ .build();
+ }
+
+ public BenchmarkConfig buildBatchD8() {
+ verify();
+ return BenchmarkConfig.builder()
+ .setName(name)
+ .setTarget(BenchmarkTarget.D8)
+ .setSuite(BenchmarkSuite.OPENSOURCE_BENCHMARKS)
+ .setMethod(runBatchD8(this))
+ .setFromRevision(fromRevision)
+ .addDependency(dumpDependency)
+ .measureRunTime()
+ .measureCodeSize()
+ .setTimeout(10, TimeUnit.MINUTES)
+ .build();
+ }
+
+ private String nameForLibraryPart() {
+ return name + "Library";
+ }
+
+ private String nameForProgramPart() {
+ return name + "Program";
+ }
+
+ private String nameForMergePart() {
+ return name + "Merge";
+ }
+
private CompilerDump getExtractedDump(BenchmarkEnvironment environment) throws IOException {
Path dump = dumpDependency.getRoot(environment).resolve("dump_app.zip");
return CompilerDump.fromArchive(dump, environment.getTemp().newFolder().toPath());
}
- private static BenchmarkMethod run(AppDumpBenchmarkBuilder builder) {
+ private static BenchmarkMethod runR8(AppDumpBenchmarkBuilder builder) {
return environment ->
BenchmarkBase.runner(environment.getConfig())
.setWarmupIterations(1)
@@ -93,7 +155,6 @@
CompilerDump dump = builder.getExtractedDump(environment);
DumpOptions dumpProperties = dump.getBuildProperties();
TestBase.testForR8(environment.getTemp(), Backend.DEX)
- // TODO(b/221811893): mock a typical setup of program providers from agp.
.addProgramFiles(dump.getProgramArchive())
.addLibraryFiles(dump.getLibraryArchive())
.addKeepRuleFiles(dump.getProguardConfigFile())
@@ -109,4 +170,62 @@
.benchmarkCodeSize(results);
});
}
+
+ private static BenchmarkMethod runBatchD8(AppDumpBenchmarkBuilder builder) {
+ return environment ->
+ BenchmarkBase.runner(environment.getConfig())
+ .setWarmupIterations(1)
+ .run(
+ results -> {
+ CompilerDump dump = builder.getExtractedDump(environment);
+ DumpOptions dumpProperties = dump.getBuildProperties();
+ TestBase.testForD8(environment.getTemp(), Backend.DEX)
+ .addProgramFiles(dump.getProgramArchive())
+ .addLibraryFiles(dump.getLibraryArchive())
+ .setMinApi(dumpProperties.getMinApi())
+ .benchmarkCompile(results)
+ .benchmarkCodeSize(results);
+ });
+ }
+
+ private static BenchmarkMethod runIncrementalD8(AppDumpBenchmarkBuilder builder) {
+ return environment ->
+ BenchmarkBase.runner(environment.getConfig())
+ .setWarmupIterations(1)
+ .reportResultSum()
+ .run(
+ results -> {
+ CompilerDump dump = builder.getExtractedDump(environment);
+ DumpOptions dumpProperties = dump.getBuildProperties();
+ PackageSplitResources resources =
+ PackageSplitResources.create(
+ environment.getTemp(), dump.getProgramArchive(), builder.programPackages);
+ if (resources.getPackageFiles().isEmpty()) {
+ throw new RuntimeException("Unexpected empty set of program package files");
+ }
+
+ TestBase.testForD8(environment.getTemp(), Backend.DEX)
+ .addProgramFiles(resources.getOtherFiles())
+ .addLibraryFiles(dump.getLibraryArchive())
+ .setMinApi(dumpProperties.getMinApi())
+ .benchmarkCompile(results.getSubResults(builder.nameForLibraryPart()));
+
+ List<Path> programOutputs = new ArrayList<>();
+ for (Path programFile : resources.getPackageFiles()) {
+ programOutputs.add(
+ TestBase.testForD8(environment.getTemp(), Backend.DEX)
+ .addProgramFiles(programFile)
+ .addLibraryFiles(dump.getLibraryArchive())
+ .setMinApi(dumpProperties.getMinApi())
+ .setIntermediate(true)
+ .benchmarkCompile(results.getSubResults(builder.nameForProgramPart()))
+ .writeToZip());
+ }
+
+ TestBase.testForD8(environment.getTemp(), Backend.DEX)
+ .addProgramFiles(programOutputs)
+ .setMinApi(dumpProperties.getMinApi())
+ .benchmarkCompile(results.getSubResults(builder.nameForMergePart()));
+ });
+ }
}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/PackageSplitResources.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/PackageSplitResources.java
new file mode 100644
index 0000000..b0566ac
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/PackageSplitResources.java
@@ -0,0 +1,68 @@
+// Copyright (c) 2022, 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.benchmarks.appdumps;
+
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.rules.TemporaryFolder;
+
+public class PackageSplitResources {
+
+ private final List<Path> packageFiles;
+ private final List<Path> otherFiles;
+
+ public PackageSplitResources(List<Path> packageFiles, List<Path> otherFiles) {
+ this.packageFiles = packageFiles;
+ this.otherFiles = otherFiles;
+ }
+
+ public static PackageSplitResources create(
+ TemporaryFolder temp, Path archive, List<String> packagePrefixes) throws IOException {
+ Path unzipDir = temp.newFolder().toPath();
+ ZipUtils.unzip(archive, unzipDir);
+ List<Path> packageFiles = new ArrayList<>();
+ List<Path> otherFiles = new ArrayList<>();
+ Files.walk(unzipDir)
+ .forEachOrdered(
+ file -> {
+ if (FileUtils.isClassFile(file)) {
+ Path relative = unzipDir.relativize(file);
+ if (isInPackagePrefixes(relative, packagePrefixes)) {
+ packageFiles.add(file);
+ } else {
+ otherFiles.add(file);
+ }
+ }
+ });
+
+ return new PackageSplitResources(packageFiles, otherFiles);
+ }
+
+ private static boolean isInPackagePrefixes(Path file, List<String> programPackages) {
+ String str = file.toString();
+ if (File.separatorChar != '/') {
+ str = str.replace(File.separatorChar, '/');
+ }
+ for (String packagePrefix : programPackages) {
+ if (str.startsWith(packagePrefix)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public List<Path> getPackageFiles() {
+ return packageFiles;
+ }
+
+ public List<Path> getOtherFiles() {
+ return otherFiles;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java b/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java
index 56b28d4..ca31ef6 100644
--- a/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java
+++ b/src/test/java/com/android/tools/r8/benchmarks/appdumps/TiviBenchmarks.java
@@ -34,6 +34,17 @@
.setName("TiviApp")
.setDumpDependencyPath(dump)
.setFromRevision(12215)
- .build());
+ .buildR8(),
+ AppDumpBenchmarkBuilder.builder()
+ .setName("TiviApp")
+ .setDumpDependencyPath(dump)
+ .setFromRevision(12370)
+ .buildBatchD8(),
+ AppDumpBenchmarkBuilder.builder()
+ .setName("TiviIncremental")
+ .setDumpDependencyPath(dump)
+ .setFromRevision(12370)
+ .addProgramPackages("app/tivi")
+ .buildIncrementalD8());
}
}
diff --git a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
index 7d2ec89..06899fd 100644
--- a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
@@ -44,7 +44,7 @@
+ " com.android.tools.r8.retrace.a:",
"# {'id':'sourceFile','fileName':'foobarbaz.java'}",
"# {'id':'sourceFile','fileName':'foobarbaz2.java'}")))
- .setStackTrace(ImmutableList.of())
+ .setStackTrace(ImmutableList.of(" at com.android.tools.r8.retrace.a.foo(Unknown)"))
.setRetracedStackTraceConsumer(
strings -> {
// No need to do anything, we are just checking for diagnostics.
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
index 4e8d81e..9ca6e87 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiInlineInOutlineTest.java
@@ -66,6 +66,7 @@
MappingProvider mappingProvider =
ProguardMappingProvider.builder()
.setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+ .allowLookupAllClasses()
.build();
Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
List<RetraceFrameElement> outlineRetraced =
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
index ca3bb00..650953c 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInOutlineStackTrace.java
@@ -74,6 +74,7 @@
MappingProvider mappingProvider =
ProguardMappingProvider.builder()
.setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+ .allowLookupAllClasses()
.build();
Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
// Retrace the first outline.
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
index 4401dfa..1c67ac5 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineInlineTest.java
@@ -65,6 +65,7 @@
MappingProvider mappingProvider =
ProguardMappingProvider.builder()
.setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+ .allowLookupAllClasses()
.build();
Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
List<RetraceFrameElement> outlineRetraced =
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
index 5ed27fae..d77819d 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiOutlineNoInlineTest.java
@@ -65,6 +65,7 @@
MappingProvider mappingProvider =
ProguardMappingProvider.builder()
.setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+ .allowLookupAllClasses()
.build();
Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
List<RetraceFrameElement> outlineRetraced =
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
index 9cd18f5..6251b54 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeResidualTest.java
@@ -83,6 +83,7 @@
MappingProvider mappingProvider =
ProguardMappingProvider.builder()
.setProguardMapProducer(ProguardMapProducer.fromString(mapping))
+ .allowLookupAllClasses()
.build();
Retracer retracer = Retracer.builder().setMappingProvider(mappingProvider).build();
List<RetraceThrownExceptionElement> npeRetraced =
diff --git a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
index 61ddca6..6d3bd5b 100644
--- a/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
+++ b/src/test/java/com/android/tools/r8/retrace/api/RetraceApiRewriteFrameInlineNpeTest.java
@@ -64,6 +64,7 @@
ProguardMappingProvider.builder()
.setProguardMapProducer(ProguardMapProducer.fromString(mapping))
.setDiagnosticsHandler(testDiagnosticsHandler)
+ .allowLookupAllClasses()
.build();
Retracer retracer =
Retracer.builder()
diff --git a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
index f5943b5..eb1d9f3 100644
--- a/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
+++ b/src/test/java/com/android/tools/r8/retrace/stacktraces/InlineSourceFileContextStackTrace.java
@@ -40,9 +40,11 @@
public List<String> retracedStackTrace() {
return Arrays.asList(
" at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
- + ".throwsException(KotlinJavaSourceFileTestLibrary.kt:22)",
+ // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
+ + ".throwsException(KotlinJavaSourceFileTestLibrary.java:22)",
" at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
- + ".callsThrowsException(KotlinJavaSourceFileTestLibrary.kt:19)",
+ // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
+ + ".callsThrowsException(KotlinJavaSourceFileTestLibrary.java:19)",
" at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
+ ".main(KotlinJavaSourceFileTestObject.java:32)");
}
@@ -51,9 +53,11 @@
public List<String> retraceVerboseStackTrace() {
return Arrays.asList(
" at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
- + ".void throwsException()(KotlinJavaSourceFileTestLibrary.kt:22)",
+ // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
+ + ".void throwsException()(KotlinJavaSourceFileTestLibrary.java:22)",
" at com.google.appreduce.remapper.KotlinJavaSourceFileTestLibrary"
- + ".void callsThrowsException()(KotlinJavaSourceFileTestLibrary.kt:19)",
+ // TODO(b/226885646): Should be KotlinJavaSourceFileTestLibrary.kt
+ + ".void callsThrowsException()(KotlinJavaSourceFileTestLibrary.java:19)",
" at com.google.appreduce.remapper.KotlinJavaSourceFileTestObject"
+ ".void main(java.lang.String[])(KotlinJavaSourceFileTestObject.java:32)");
}
diff --git a/third_party/retrace/binary_compatibility.tar.gz.sha1 b/third_party/retrace/binary_compatibility.tar.gz.sha1
index 431fdbe..30ac55d 100644
--- a/third_party/retrace/binary_compatibility.tar.gz.sha1
+++ b/third_party/retrace/binary_compatibility.tar.gz.sha1
@@ -1 +1 @@
-2ebfdd6c7d270471cb38baa355d5d137af2f7dd1
\ No newline at end of file
+c7b9bd1e5be6caa0e8a8cb6634b7fb73fd48ae8e
\ No newline at end of file
diff --git a/tools/compiledump.py b/tools/compiledump.py
index a7809ac..9f3d1b0 100755
--- a/tools/compiledump.py
+++ b/tools/compiledump.py
@@ -287,12 +287,12 @@
args.append('-D' + name + '=' + value)
return args
-def download_distribution(args, version, temp):
+def download_distribution(version, nolib, temp):
if version == 'main':
- return utils.R8_JAR if args.nolib else utils.R8LIB_JAR
+ return utils.R8_JAR if nolib else utils.R8LIB_JAR
if version == 'source':
return '%s:%s' % (utils.BUILD_JAVA_MAIN_DIR, utils.ALL_DEPS_JAR)
- name = 'r8.jar' if args.nolib else 'r8lib.jar'
+ name = 'r8.jar' if nolib else 'r8lib.jar'
source = archive.GetUploadDestination(version, name, is_hash(version))
dest = os.path.join(temp, 'r8.jar')
utils.download_file_from_cloud_storage(source, dest)
@@ -367,7 +367,7 @@
out = determine_output(args, temp)
min_api = determine_min_api(args, build_properties)
classfile = determine_class_file(args, build_properties)
- jar = args.r8_jar if args.r8_jar else download_distribution(args, version, temp)
+ jar = args.r8_jar if args.r8_jar else download_distribution(version, args.nolib, temp)
if ':' not in jar and not os.path.exists(jar):
error("Distribution does not exist: " + jar)
wrapper_dir = prepare_wrapper(jar, temp, jdkhome)
diff --git a/tools/run_benchmark.py b/tools/run_benchmark.py
index 545ac5a..904b509 100755
--- a/tools/run_benchmark.py
+++ b/tools/run_benchmark.py
@@ -11,6 +11,7 @@
import gradle
import jdk
import utils
+import compiledump
NONLIB_BUILD_TARGET = 'R8WithRelocatedDeps'
NONLIB_TEST_BUILD_TARGETS = [utils.R8_TESTS_TARGET, utils.R8_TESTS_DEPS_TARGET]
@@ -59,14 +60,20 @@
help='Print timing information from r8',
default=False,
action='store_true')
+ result.add_argument('--version', '-v',
+ help='Use R8 version/hash for the run (default local build)',
+ default=None)
result.add_argument('--temp',
help='A directory to use for temporaries and outputs.',
default=None)
return result.parse_known_args(argv)
-def main(argv):
+def main(argv, temp):
(options, args) = parse_options(argv)
+ if options.temp:
+ temp = options.temp
+
if options.golem:
options.no_build = True
if options.nolib:
@@ -74,14 +81,21 @@
return 1
if options.nolib:
+ testBuildTargets = NONLIB_TEST_BUILD_TARGETS
buildTargets = [NONLIB_BUILD_TARGET] + NONLIB_TEST_BUILD_TARGETS
r8jar = utils.R8_WITH_RELOCATED_DEPS_JAR
testjars = [utils.R8_TESTS_DEPS_JAR, utils.R8_TESTS_JAR]
else:
+ testBuildTargets = R8LIB_TEST_BUILD_TARGETS
buildTargets = GOLEM_BUILD_TARGETS
r8jar = utils.R8LIB_JAR
testjars = [utils.R8LIB_TESTS_DEPS_JAR, utils.R8LIB_TESTS_JAR]
+ if options.version:
+ # r8 is downloaded so only test jar needs to be built.
+ buildTargets = testBuildTargets
+ r8jar = compiledump.download_distribution(options.version, options.nolib, temp)
+
if not options.no_build:
gradle.RunGradle(buildTargets + ['-Pno_internal'])
@@ -104,4 +118,5 @@
return subprocess.check_call(cmd)
if __name__ == '__main__':
- sys.exit(main(sys.argv[1:]))
+ with utils.TempDir() as temp:
+ sys.exit(main(sys.argv[1:], temp))