Version 1.7.2-dev Merge commit 'e727ab7eecb212a954e249f3ef138eb6d3581e65' into 1.7
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg index 9b6281b..0c95e98 100644 --- a/infra/config/global/luci-scheduler.cfg +++ b/infra/config/global/luci-scheduler.cfg
@@ -56,9 +56,8 @@ acl_sets: "default" gitiles: { repo: "https://r8.googlesource.com/r8" - refs: "regexp:refs/heads/d8.*" - refs: "regexp:refs/heads/1.*" - refs: "regexp:refs/heads/2.*" + # Version branches are named d8-x.y (up until d8-1.5) or just x.y (from 1.6) + refs: "regexp:refs/heads/(?:d8-)?[0-9]+\\.[0-9]+" } triggers: "archive_release" triggers: "linux-android-4.0.4_release"
diff --git a/src/main/java/com/android/tools/r8/DesugaredLibraryConfigurationForTesting.java b/src/main/java/com/android/tools/r8/DesugaredLibraryConfigurationForTesting.java index 4a07bf4..2f03241 100644 --- a/src/main/java/com/android/tools/r8/DesugaredLibraryConfigurationForTesting.java +++ b/src/main/java/com/android/tools/r8/DesugaredLibraryConfigurationForTesting.java
@@ -126,6 +126,7 @@ private static Map<String, String> buildPrefixRewritingForCoreLibCompilationAndroidNPlus() { return ImmutableMap.<String, String>builder() + .put("j$.time.", "java.time.") .put("java.time.", "j$.time.") .put("java.util.Desugar", "j$.util.Desugar") .build();
diff --git a/src/main/java/com/android/tools/r8/GenerateLintFiles.java b/src/main/java/com/android/tools/r8/GenerateLintFiles.java new file mode 100644 index 0000000..2c63a69 --- /dev/null +++ b/src/main/java/com/android/tools/r8/GenerateLintFiles.java
@@ -0,0 +1,335 @@ +// Copyright (c) 2019, 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; + +import com.android.tools.r8.cf.code.CfConstNull; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfThrow; +import com.android.tools.r8.dex.ApplicationReader; +import com.android.tools.r8.dex.Marker.Tool; +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexAnnotationSet; +import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexLibraryClass; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProgramClass; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.graph.DirectMappedDexApplication; +import com.android.tools.r8.graph.GraphLense; +import com.android.tools.r8.graph.ParameterAnnotationsList; +import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; +import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser; +import com.android.tools.r8.jar.CfApplicationWriter; +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.origin.Origin; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.FileUtils; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.ThreadUtils; +import com.android.tools.r8.utils.Timing; +import com.google.common.collect.Sets; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.function.BiPredicate; +import java.util.function.Predicate; + +public class GenerateLintFiles { + + private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar"; + + private final DexItemFactory factory = new DexItemFactory(); + private final Reporter reporter = new Reporter(); + private final InternalOptions options = new InternalOptions(factory, reporter); + + private final DesugaredLibraryConfiguration desugaredLibraryConfiguration; + private final String outputDirectory; + + private final Set<DexMethod> parallelMethods = Sets.newIdentityHashSet(); + + private GenerateLintFiles(String desugarConfigurationPath, String outputDirectory) { + this.desugaredLibraryConfiguration = + readDesugaredLibraryConfiguration(desugarConfigurationPath); + this.outputDirectory = + outputDirectory.endsWith("/") ? outputDirectory : outputDirectory + File.separator; + + DexType streamType = factory.createType(factory.createString("Ljava/util/stream/Stream;")); + DexMethod parallelMethod = + factory.createMethod( + factory.collectionType, + factory.createProto(streamType), + factory.createString("parallelStream")); + parallelMethods.add(parallelMethod); + for (String typePrefix : new String[] {"Base", "Double", "Int", "Long"}) { + streamType = + factory.createType(factory.createString("Ljava/util/stream/" + typePrefix + "Stream;")); + parallelMethod = + factory.createMethod( + streamType, factory.createProto(streamType), factory.createString("parallel")); + parallelMethods.add(parallelMethod); + } + } + + private static Path getAndroidJarPath(AndroidApiLevel apiLevel) { + String jar = String.format(ANDROID_JAR_PATTERN, apiLevel.getLevel()); + return Paths.get(jar); + } + + private DesugaredLibraryConfiguration readDesugaredLibraryConfiguration( + String desugarConfigurationPath) { + return new DesugaredLibraryConfigurationParser( + factory, reporter, false, AndroidApiLevel.B.getLevel()) + .parse(StringResource.fromFile(Paths.get(desugarConfigurationPath))); + } + + private CfCode buildEmptyThrowingCfCode(DexMethod method) { + CfInstruction insn[] = {new CfConstNull(), new CfThrow()}; + return new CfCode( + method.holder, + 1, + method.proto.parameters.size() + 1, + Arrays.asList(insn), + Collections.emptyList(), + Collections.emptyList()); + } + + private void addMethodsToHeaderJar( + DexApplication.Builder builder, DexClass clazz, List<DexEncodedMethod> methods) { + if (methods.size() == 0) { + return; + } + + List<DexEncodedMethod> directMethods = new ArrayList<>(); + List<DexEncodedMethod> virtualMethods = new ArrayList<>(); + for (DexEncodedMethod method : methods) { + assert method.method.holder == clazz.type; + CfCode code = null; + if (!method.accessFlags.isAbstract() /*&& !method.accessFlags.isNative()*/) { + code = buildEmptyThrowingCfCode(method.method); + } + DexEncodedMethod throwingMethod = + new DexEncodedMethod( + method.method, + method.accessFlags, + DexAnnotationSet.empty(), + ParameterAnnotationsList.empty(), + code, + 50); + if (method.accessFlags.isStatic()) { + directMethods.add(throwingMethod); + } else { + virtualMethods.add(throwingMethod); + } + } + + DexEncodedMethod[] directMethodsArray = new DexEncodedMethod[directMethods.size()]; + DexEncodedMethod[] virtualMethodsArray = new DexEncodedMethod[virtualMethods.size()]; + directMethods.toArray(directMethodsArray); + virtualMethods.toArray(virtualMethodsArray); + builder.addProgramClass( + new DexProgramClass( + clazz.type, + null, + Origin.unknown(), + clazz.accessFlags, + clazz.superType, + clazz.interfaces, + null, + null, + Collections.emptyList(), + null, + Collections.emptyList(), + DexAnnotationSet.empty(), + DexEncodedField.EMPTY_ARRAY, + DexEncodedField.EMPTY_ARRAY, + directMethodsArray, + virtualMethodsArray, + false)); + } + + private Map<DexClass, List<DexEncodedMethod>> collectSupportedMethods( + AndroidApiLevel compilationApiLevel, Predicate<DexEncodedMethod> supported) + throws IOException, ExecutionException { + + // Read the android.jar for the compilation API level. + AndroidApp library = + AndroidApp.builder().addLibraryFiles(getAndroidJarPath(compilationApiLevel)).build(); + DirectMappedDexApplication dexApplication = + new ApplicationReader(library, options, new Timing()).read().toDirect(); + + // collect all the methods that the library desugar configuration adds support for. + Map<DexClass, List<DexEncodedMethod>> supportedMethods = new LinkedHashMap<>(); + for (DexLibraryClass clazz : dexApplication.libraryClasses()) { + String className = clazz.toSourceString(); + // All the methods with the rewritten prefix are supported. + for (String prefix : desugaredLibraryConfiguration.getRewritePrefix().keySet()) { + if (clazz.accessFlags.isPublic() && className.startsWith(prefix)) { + for (DexEncodedMethod method : clazz.methods()) { + if (supported.test(method)) { + supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method); + } + } + } + } + + // All retargeted methods are supported. + for (DexEncodedMethod method : clazz.methods()) { + if (desugaredLibraryConfiguration + .getRetargetCoreLibMember() + .keySet() + .contains(method.method.name)) { + if (desugaredLibraryConfiguration + .getRetargetCoreLibMember() + .get(method.method.name) + .containsKey(clazz.type)) { + if (supported.test(method)) { + supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method); + } + } + } + } + // All emulated interfaces methods are supported. + if (desugaredLibraryConfiguration.getEmulateLibraryInterface().containsKey(clazz.type)) { + for (DexEncodedMethod method : clazz.methods()) { + if (supported.test(method)) { + supportedMethods.computeIfAbsent(clazz, k -> new ArrayList<>()).add(method); + } + } + } + } + + return supportedMethods; + } + + private String lintBaseFileName( + AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel) { + return "desugared_apis_" + compilationApiLevel.getLevel() + "_" + minApiLevel.getLevel(); + } + + private Path lintFile( + AndroidApiLevel compilationApiLevel, AndroidApiLevel minApiLevel, String extension) + throws Exception { + Path directory = + Paths.get(outputDirectory + "compile_api_level_" + compilationApiLevel.getLevel()); + Files.createDirectories(directory); + return Paths.get( + directory + + File.separator + + lintBaseFileName(compilationApiLevel, minApiLevel) + + extension); + } + + private void writeLintFiles( + AndroidApiLevel compilationApiLevel, + AndroidApiLevel minApiLevel, + Map<DexClass, List<DexEncodedMethod>> supportedMethods) + throws Exception { + // Build a plain text file with the desugared APIs. + List<String> desugaredApisSignatures = new ArrayList<>(); + + DexApplication.Builder builder = DexApplication.builder(options, new Timing()); + supportedMethods.forEach( + (clazz, methods) -> { + String classBinaryName = + DescriptorUtils.getClassBinaryNameFromDescriptor(clazz.type.descriptor.toString()); + for (DexEncodedMethod method : methods) { + desugaredApisSignatures.add( + classBinaryName + + '/' + + method.method.name + + method.method.proto.toDescriptorString()); + } + + addMethodsToHeaderJar(builder, clazz, methods); + }); + DexApplication app = builder.build(); + + // Write a plain text file with the desugared APIs. + FileUtils.writeTextFile( + lintFile(compilationApiLevel, minApiLevel, ".txt"), desugaredApisSignatures); + + // Write a header jar with the desugared APIs. + AppInfo appInfo = new AppInfo(app); + AppView<?> appView = AppView.createForD8(appInfo, options); + CfApplicationWriter writer = + new CfApplicationWriter( + builder.build(), + appView, + options, + options.getMarker(Tool.L8), + GraphLense.getIdentityLense(), + NamingLens.getIdentityLens(), + null); + ClassFileConsumer consumer = + new ClassFileConsumer.ArchiveConsumer( + lintFile(compilationApiLevel, minApiLevel, FileUtils.JAR_EXTENSION)); + writer.write(consumer, ThreadUtils.getExecutorService(options)); + consumer.finished(options.reporter); + } + + private void generateLintFiles( + AndroidApiLevel compilationApiLevel, + Predicate<AndroidApiLevel> generateForThisMinApiLevel, + BiPredicate<AndroidApiLevel, DexEncodedMethod> supportedForMinApiLevel) + throws Exception { + for (AndroidApiLevel value : AndroidApiLevel.values()) { + if (!generateForThisMinApiLevel.test(value)) { + continue; + } + + Map<DexClass, List<DexEncodedMethod>> supportedMethods = + collectSupportedMethods( + compilationApiLevel, (method -> supportedForMinApiLevel.test(value, method))); + writeLintFiles(compilationApiLevel, value, supportedMethods); + } + } + + private void run() throws Exception { + // Run over all the API levels that the desugared library can be compiled with. + for (int apiLevel = AndroidApiLevel.Q.getLevel(); + apiLevel >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel(); + apiLevel--) { + System.out.println("Generating lint files for compile API " + apiLevel); + generateLintFiles( + AndroidApiLevel.getAndroidApiLevel(apiLevel), + minApiLevel -> minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B, + (minApiLevel, method) -> { + assert minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B; + if (minApiLevel == AndroidApiLevel.L) { + return true; + } + assert minApiLevel == AndroidApiLevel.B; + return !parallelMethods.contains(method.method); + }); + } + } + + public static void main(String[] args) throws Exception { + if (args.length != 2) { + System.out.println("Usage: GenerateLineFiles <desuage configuration> <output directory>"); + System.exit(1); + } + new GenerateLintFiles(args[0], args[1]).run(); + } +}
diff --git a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java index 946ac89..94633a0 100644 --- a/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java +++ b/src/main/java/com/android/tools/r8/cf/CfCodePrinter.java
@@ -113,7 +113,7 @@ .append(r8Type("InternalOptions", "utils")) .append(" options, ") .append(r8Type("DexMethod", "graph")) - .append(" method, String name) {"); + .append(" method) {"); for (CfInstruction instruction : code.getInstructions()) { if (instruction instanceof CfLabel) { @@ -166,7 +166,7 @@ } private String longValue(long value) { - return (value < Integer.MIN_VALUE || Integer.MAX_VALUE < value) ? (value + "l") : ("" + value); + return (value < Integer.MIN_VALUE || Integer.MAX_VALUE < value) ? (value + "L") : ("" + value); } // Ensure a type import for a given type.
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 7b18fd8..02a6129 100644 --- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java +++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -309,17 +309,21 @@ mainDexFile = new VirtualFile(0, writer.namingLens); assert virtualFiles.isEmpty(); virtualFiles.add(mainDexFile); - if (writer.markerStrings != null && !writer.markerStrings.isEmpty()) { - for (DexString markerString : writer.markerStrings) { - mainDexFile.transaction.addString(markerString); - } - mainDexFile.commitTransaction(); - } + addMarkers(mainDexFile); classes = Sets.newHashSet(application.classes()); originalNames = computeOriginalNameMapping(classes, application.getProguardMap()); } + private void addMarkers(VirtualFile virtualFile) { + if (writer.markerStrings != null && !writer.markerStrings.isEmpty()) { + for (DexString markerString : writer.markerStrings) { + virtualFile.transaction.addString(markerString); + } + virtualFile.commitTransaction(); + } + } + protected void fillForMainDexList(Set<DexProgramClass> classes) { if (!application.mainDexList.isEmpty()) { VirtualFile mainDexFile = virtualFiles.get(0); @@ -418,7 +422,10 @@ for (Map.Entry<FeatureSplit, Set<DexProgramClass>> featureSplitSetEntry : featureSplitClasses.entrySet()) { // Add a new virtual file, start from index 0 again - virtualFiles.add(new VirtualFile(0, writer.namingLens, featureSplitSetEntry.getKey())); + VirtualFile featureFile = + new VirtualFile(0, writer.namingLens, featureSplitSetEntry.getKey()); + virtualFiles.add(featureFile); + addMarkers(featureFile); Set<DexProgramClass> featureClasses = sortClassesByPackage(featureSplitSetEntry.getValue(), originalNames); filesForDistribution = virtualFiles.subList(virtualFiles.size() - 1, virtualFiles.size());
diff --git a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java index b7f6158..22e819e 100644 --- a/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java +++ b/src/main/java/com/android/tools/r8/features/FeatureSplitConfiguration.java
@@ -59,7 +59,12 @@ return result; } - public boolean inSameFeatureOrBase(DexMethod a, DexMethod b) { + public boolean isInFeature(DexProgramClass clazz) { + return javaTypeToFeatureSplitMapping.containsKey( + DescriptorUtils.descriptorToJavaType(clazz.type.toDescriptorString())); + } + + public boolean inSameFeatureOrBase(DexMethod a, DexMethod b){ return inSameFeatureOrBase(a.holder, b.holder); }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java index 3e3ed2b..bc4f6f0 100644 --- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java +++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -1098,6 +1098,10 @@ return false; } + public TrivialClassInitializer asTrivialClassInitializer() { + return null; + } + // Defines instance trivial initialized, see details in comments // to CodeRewriter::computeInstanceInitializerInfo(...) public static final class TrivialInstanceInitializer extends TrivialInitializer { @@ -1122,6 +1126,11 @@ public TrivialClassInitializer(DexField field) { this.field = field; } + + @Override + public TrivialClassInitializer asTrivialClassInitializer() { + return this; + } } }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java index 36e4cc8..87d7ceb 100644 --- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java +++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -161,6 +161,8 @@ public final DexString toStringMethodName = createString("toString"); public final DexString internMethodName = createString("intern"); + public final DexString convertMethodName = createString("convert"); + public final DexString getClassMethodName = createString("getClass"); public final DexString finalizeMethodName = createString("finalize"); public final DexString ordinalMethodName = createString("ordinal"); @@ -214,6 +216,10 @@ public final DexString proxyDescriptor = createString("Ljava/lang/reflect/Proxy;"); public final DexString serviceLoaderDescriptor = createString("Ljava/util/ServiceLoader;"); public final DexString listDescriptor = createString("Ljava/util/List;"); + public final DexString setDescriptor = createString("Ljava/util/Set;"); + public final DexString mapDescriptor = createString("Ljava/util/Map;"); + public final DexString mapEntryDescriptor = createString("Ljava/util/Map$Entry;"); + public final DexString collectionDescriptor = createString("Ljava/util/Collection;"); public final DexString comparatorDescriptor = createString("Ljava/util/Comparator;"); public final DexString callableDescriptor = createString("Ljava/util/concurrent/Callable;"); public final DexString supplierDescriptor = createString("Ljava/util/function/Supplier;"); @@ -302,6 +308,10 @@ public final DexType proxyType = createType(proxyDescriptor); public final DexType serviceLoaderType = createType(serviceLoaderDescriptor); public final DexType listType = createType(listDescriptor); + public final DexType setType = createType(setDescriptor); + public final DexType mapType = createType(mapDescriptor); + public final DexType mapEntryType = createType(mapEntryDescriptor); + public final DexType collectionType = createType(collectionDescriptor); public final DexType comparatorType = createType(comparatorDescriptor); public final DexType callableType = createType(callableDescriptor); public final DexType supplierType = createType(supplierDescriptor);
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java new file mode 100644 index 0000000..90320b7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/AbstractFieldSet.java
@@ -0,0 +1,68 @@ +// Copyright (c) 2019, 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.fieldvalueanalysis; + +import com.android.tools.r8.graph.DexEncodedField; + +/** + * Implements a lifted subset lattice for fields. + * + * <p>An element can either be: + * + * <ol> + * <li>bottom, represented by {@link EmptyFieldSet}, + * <li>a (possibly empty) set of fields, represented by {@link ConcreteMutableFieldSet}, or + * <li>top, represented by {@link UnknownFieldSet}. + * </ol> + * + * <p>Note that this class currently does not contain a {@code join()} method. Instead, the {@link + * ConcreteMutableFieldSet} has an {@link ConcreteMutableFieldSet#add(DexEncodedField)} and {@link + * ConcreteMutableFieldSet#addAll(ConcreteMutableFieldSet)} method. This is intentional, since the + * use of {@code join()} could lead to an excessive amount of set copying under the hood. + */ +public abstract class AbstractFieldSet { + + public boolean isConcreteFieldSet() { + return false; + } + + public ConcreteMutableFieldSet asConcreteFieldSet() { + return null; + } + + public boolean isKnownFieldSet() { + return false; + } + + public KnownFieldSet asKnownFieldSet() { + return null; + } + + public abstract boolean contains(DexEncodedField field); + + public boolean isBottom() { + return false; + } + + public boolean isTop() { + return false; + } + + public final boolean lessThanOrEqual(AbstractFieldSet other) { + if (isBottom() || other.isTop()) { + return true; + } + if (isTop() || other.isBottom()) { + return false; + } + assert isConcreteFieldSet(); + assert other.isConcreteFieldSet(); + return other.asConcreteFieldSet().getFields().containsAll(asConcreteFieldSet().getFields()); + } + + public final boolean strictlyLessThan(AbstractFieldSet other) { + return lessThanOrEqual(other) && !equals(other); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java new file mode 100644 index 0000000..0ac6de7 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/ConcreteMutableFieldSet.java
@@ -0,0 +1,87 @@ +// Copyright (c) 2019, 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.fieldvalueanalysis; + +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.SetUtils; +import com.google.common.collect.Sets; +import java.util.Collections; +import java.util.Set; + +public class ConcreteMutableFieldSet extends AbstractFieldSet implements KnownFieldSet { + + private final Set<DexEncodedField> fields; + + public ConcreteMutableFieldSet() { + fields = Sets.newIdentityHashSet(); + } + + public ConcreteMutableFieldSet(DexEncodedField field) { + fields = SetUtils.newIdentityHashSet(field); + } + + public void add(DexEncodedField field) { + fields.add(field); + } + + public void addAll(ConcreteMutableFieldSet other) { + fields.addAll(other.fields); + } + + Set<DexEncodedField> getFields() { + if (InternalOptions.assertionsEnabled()) { + return Collections.unmodifiableSet(fields); + } + return fields; + } + + @Override + public boolean isConcreteFieldSet() { + return true; + } + + @Override + public ConcreteMutableFieldSet asConcreteFieldSet() { + return this; + } + + @Override + public boolean isKnownFieldSet() { + return true; + } + + @Override + public ConcreteMutableFieldSet asKnownFieldSet() { + return this; + } + + @Override + public boolean contains(DexEncodedField field) { + return fields.contains(field); + } + + @Override + public int size() { + return fields.size(); + } + + @Override + public int hashCode() { + return fields.hashCode(); + } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + if (other.getClass() != getClass()) { + return false; + } + ConcreteMutableFieldSet concreteFieldSet = (ConcreteMutableFieldSet) other; + return fields.equals(concreteFieldSet.fields); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java new file mode 100644 index 0000000..c50ded8 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/EmptyFieldSet.java
@@ -0,0 +1,43 @@ +// Copyright (c) 2019, 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.fieldvalueanalysis; + +import com.android.tools.r8.graph.DexEncodedField; + +public class EmptyFieldSet extends AbstractFieldSet implements KnownFieldSet { + + private static final EmptyFieldSet INSTANCE = new EmptyFieldSet(); + + private EmptyFieldSet() {} + + public static EmptyFieldSet getInstance() { + return INSTANCE; + } + + @Override + public boolean isKnownFieldSet() { + return true; + } + + @Override + public EmptyFieldSet asKnownFieldSet() { + return this; + } + + @Override + public boolean contains(DexEncodedField field) { + return false; + } + + @Override + public boolean isBottom() { + return true; + } + + @Override + public int size() { + return 0; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java new file mode 100644 index 0000000..2d67908 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/FieldValueAnalysis.java
@@ -0,0 +1,253 @@ +// Copyright (c) 2019, 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.fieldvalueanalysis; + +import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexEncodedField; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexField; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.DominatorTree; +import com.android.tools.r8.ir.code.DominatorTree.Assumption; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionIterator; +import com.android.tools.r8.ir.code.StaticPut; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.DequeUtils; +import java.util.Deque; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public class FieldValueAnalysis { + + private final AppView<AppInfoWithLiveness> appView; + private final IRCode code; + private final OptimizationFeedback feedback; + private final DexEncodedMethod method; + + private Map<BasicBlock, AbstractFieldSet> fieldsMaybeReadBeforeBlockInclusiveCache; + + private FieldValueAnalysis( + AppView<AppInfoWithLiveness> appView, + IRCode code, + OptimizationFeedback feedback, + DexEncodedMethod method) { + this.appView = appView; + this.code = code; + this.feedback = feedback; + this.method = method; + } + + public static void run( + AppView<?> appView, IRCode code, OptimizationFeedback feedback, DexEncodedMethod method) { + if (appView.enableWholeProgramOptimizations() && method.isClassInitializer()) { + assert appView.appInfo().hasLiveness(); + new FieldValueAnalysis(appView.withLiveness(), code, feedback, method) + .computeFieldOptimizationInfo(); + } + } + + private Map<BasicBlock, AbstractFieldSet> getOrCreateFieldsMaybeReadBeforeBlockInclusive() { + if (fieldsMaybeReadBeforeBlockInclusiveCache == null) { + fieldsMaybeReadBeforeBlockInclusiveCache = createFieldsMaybeReadBeforeBlockInclusive(); + } + return fieldsMaybeReadBeforeBlockInclusiveCache; + } + + /** This method analyzes initializers with the purpose of computing field optimization info. */ + private void computeFieldOptimizationInfo() { + AppInfoWithLiveness appInfo = appView.appInfo(); + DominatorTree dominatorTree = null; + + if (method.isClassInitializer()) { + DexType context = method.method.holder; + + // Find all the static-put instructions that assign a field in the enclosing class which is + // guaranteed to be assigned only in the current initializer. + boolean isStraightLineCode = true; + Map<DexEncodedField, LinkedList<StaticPut>> staticPutsPerField = new IdentityHashMap<>(); + for (Instruction instruction : code.instructions()) { + if (instruction.isStaticPut()) { + StaticPut staticPut = instruction.asStaticPut(); + DexField field = staticPut.getField(); + DexEncodedField encodedField = appInfo.resolveField(field); + if (encodedField != null + && encodedField.field.holder == context + && appInfo.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(encodedField)) { + staticPutsPerField + .computeIfAbsent(encodedField, ignore -> new LinkedList<>()) + .add(staticPut); + } + } + if (instruction.isJumpInstruction()) { + if (!instruction.isGoto() && !instruction.isReturn()) { + isStraightLineCode = false; + } + } + } + + List<BasicBlock> normalExitBlocks = code.computeNormalExitBlocks(); + for (Entry<DexEncodedField, LinkedList<StaticPut>> entry : staticPutsPerField.entrySet()) { + DexEncodedField encodedField = entry.getKey(); + LinkedList<StaticPut> staticPuts = entry.getValue(); + if (staticPuts.size() > 1) { + continue; + } + StaticPut staticPut = staticPuts.getFirst(); + if (!isStraightLineCode) { + if (dominatorTree == null) { + dominatorTree = new DominatorTree(code, Assumption.NO_UNREACHABLE_BLOCKS); + } + if (!dominatorTree.dominatesAllOf(staticPut.getBlock(), normalExitBlocks)) { + continue; + } + } + if (fieldMaybeReadBeforeInstruction(encodedField, staticPut)) { + continue; + } + updateFieldOptimizationInfo(encodedField, staticPut.value()); + } + } + } + + private boolean fieldMaybeReadBeforeInstruction( + DexEncodedField encodedField, Instruction instruction) { + BasicBlock block = instruction.getBlock(); + + // First check if the field may be read in any of the (transitive) predecessor blocks. + if (fieldMaybeReadBeforeBlock(encodedField, block)) { + return true; + } + + // Then check if any of the instructions that precede the given instruction in the current block + // may read the field. + DexType context = method.method.holder; + InstructionIterator instructionIterator = block.iterator(); + while (instructionIterator.hasNext()) { + Instruction current = instructionIterator.next(); + if (current == instruction) { + break; + } + if (current.readSet(appView, context).contains(encodedField)) { + return true; + } + } + + // Otherwise, the field is not read prior to the given instruction. + return false; + } + + private boolean fieldMaybeReadBeforeBlock(DexEncodedField encodedField, BasicBlock block) { + for (BasicBlock predecessor : block.getPredecessors()) { + if (fieldMaybeReadBeforeBlockInclusive(encodedField, predecessor)) { + return true; + } + } + return false; + } + + private boolean fieldMaybeReadBeforeBlockInclusive( + DexEncodedField encodedField, BasicBlock block) { + return getOrCreateFieldsMaybeReadBeforeBlockInclusive().get(block).contains(encodedField); + } + + /** + * Eagerly creates a mapping from each block to the set of fields that may be read in that block + * and its transitive predecessors. + */ + private Map<BasicBlock, AbstractFieldSet> createFieldsMaybeReadBeforeBlockInclusive() { + DexType context = method.method.holder; + Map<BasicBlock, AbstractFieldSet> result = new IdentityHashMap<>(); + Deque<BasicBlock> worklist = DequeUtils.newArrayDeque(code.entryBlock()); + while (!worklist.isEmpty()) { + BasicBlock block = worklist.removeFirst(); + boolean seenBefore = result.containsKey(block); + AbstractFieldSet readSet = + result.computeIfAbsent(block, ignore -> EmptyFieldSet.getInstance()); + if (readSet.isTop()) { + // We already have unknown information for this block. + continue; + } + + assert readSet.isKnownFieldSet(); + KnownFieldSet knownReadSet = readSet.asKnownFieldSet(); + int oldSize = seenBefore ? knownReadSet.size() : -1; + + // Everything that is read in the predecessor blocks should also be included in the read set + // for the current block, so here we join the information from the predecessor blocks into the + // current read set. + boolean blockOrPredecessorMaybeReadAnyField = false; + for (BasicBlock predecessor : block.getPredecessors()) { + AbstractFieldSet predecessorReadSet = + result.getOrDefault(predecessor, EmptyFieldSet.getInstance()); + if (predecessorReadSet.isBottom()) { + continue; + } + if (predecessorReadSet.isTop()) { + blockOrPredecessorMaybeReadAnyField = true; + break; + } + assert predecessorReadSet.isConcreteFieldSet(); + if (!knownReadSet.isConcreteFieldSet()) { + knownReadSet = new ConcreteMutableFieldSet(); + } + knownReadSet.asConcreteFieldSet().addAll(predecessorReadSet.asConcreteFieldSet()); + } + + if (!blockOrPredecessorMaybeReadAnyField) { + // Finally, we update the read set with the fields that are read by the instructions in the + // current block. + for (Instruction instruction : block.getInstructions()) { + AbstractFieldSet instructionReadSet = instruction.readSet(appView, context); + if (instructionReadSet.isBottom()) { + continue; + } + if (instructionReadSet.isTop()) { + blockOrPredecessorMaybeReadAnyField = true; + break; + } + if (!knownReadSet.isConcreteFieldSet()) { + knownReadSet = new ConcreteMutableFieldSet(); + } + knownReadSet.asConcreteFieldSet().addAll(instructionReadSet.asConcreteFieldSet()); + } + } + + boolean changed = false; + if (blockOrPredecessorMaybeReadAnyField) { + // Record that this block reads all fields. + result.put(block, UnknownFieldSet.getInstance()); + changed = true; + } else if (knownReadSet.size() != oldSize) { + assert knownReadSet.size() > oldSize; + changed = true; + } + + if (changed) { + // Rerun the analysis for all successors because the state of the current block changed. + worklist.addAll(block.getSuccessors()); + } + } + return result; + } + + private void updateFieldOptimizationInfo(DexEncodedField field, Value value) { + TypeLatticeElement fieldType = + TypeLatticeElement.fromDexType(field.field.type, Nullability.maybeNull(), appView); + TypeLatticeElement valueType = value.getTypeLattice(); + if (valueType.strictlyLessThan(fieldType, appView)) { + feedback.markFieldHasDynamicType(field, valueType); + } + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java new file mode 100644 index 0000000..b96918e --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/KnownFieldSet.java
@@ -0,0 +1,18 @@ +// Copyright (c) 2019, 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.fieldvalueanalysis; + +public interface KnownFieldSet { + + default boolean isConcreteFieldSet() { + return false; + } + + default ConcreteMutableFieldSet asConcreteFieldSet() { + return null; + } + + int size(); +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java new file mode 100644 index 0000000..9df87cc --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldvalueanalysis/UnknownFieldSet.java
@@ -0,0 +1,28 @@ +// Copyright (c) 2019, 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.fieldvalueanalysis; + +import com.android.tools.r8.graph.DexEncodedField; + +public class UnknownFieldSet extends AbstractFieldSet { + + private static final UnknownFieldSet INSTANCE = new UnknownFieldSet(); + + private UnknownFieldSet() {} + + public static UnknownFieldSet getInstance() { + return INSTANCE; + } + + @Override + public boolean contains(DexEncodedField field) { + return true; + } + + @Override + public boolean isTop() { + return true; + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java new file mode 100644 index 0000000..57f8d2e --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/analysis/modeling/LibraryMethodReadSetModeling.java
@@ -0,0 +1,45 @@ +// Copyright (c) 2019, 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.modeling; + +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet; +import com.android.tools.r8.ir.code.InvokeMethod; +import java.util.function.Predicate; + +/** Models if a given library method may cause a program field to be read. */ +public class LibraryMethodReadSetModeling { + + public static AbstractFieldSet getModeledReadSetOrUnknown( + InvokeMethod invoke, DexItemFactory dexItemFactory) { + DexMethod invokedMethod = invoke.getInvokedMethod(); + + // Check if it is a library method that does not have side effects. In that case it is safe to + // assume that the method does not read any fields, since even if it did, it would not be able + // to do anything with the values it read (since we will remove such invocations without side + // effects). + Predicate<InvokeMethod> noSideEffectsPredicate = + dexItemFactory.libraryMethodsWithoutSideEffects.get(invokedMethod); + if (noSideEffectsPredicate != null && noSideEffectsPredicate.test(invoke)) { + return EmptyFieldSet.getInstance(); + } + + // Already handled above. + assert !dexItemFactory.classMethods.isReflectiveNameLookup(invokedMethod); + + // Modeling of other library methods. + DexType holder = invokedMethod.holder; + if (holder == dexItemFactory.objectType) { + if (invokedMethod == dexItemFactory.objectMethods.constructor) { + return EmptyFieldSet.getInstance(); + } + } + return UnknownFieldSet.getInstance(); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Assume.java b/src/main/java/com/android/tools/r8/ir/code/Assume.java index c4dfbc4..18ba962 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Assume.java +++ b/src/main/java/com/android/tools/r8/ir/code/Assume.java
@@ -153,6 +153,10 @@ return self; } + public boolean mayAffectStaticType() { + return isAssumeNonNull(); + } + @Override public boolean couldIntroduceAnAlias(AppView<?> appView, Value root) { assert root != null && root.getTypeLattice().isReference();
diff --git a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java index 72c64a0..120a2d4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java +++ b/src/main/java/com/android/tools/r8/ir/code/DominatorTree.java
@@ -95,9 +95,9 @@ /** * Check if one basic block is dominated by another basic block. * - * @param subject subject to check for domination by {@code dominator} + * @param subject subject to check for domination by {@param dominator} * @param dominator dominator to check against - * @return wether {@code subject} is dominated by {@code dominator} + * @return whether {@param subject} is dominated by {@param dominator} */ public boolean dominatedBy(BasicBlock subject, BasicBlock dominator) { assert !obsolete; @@ -108,11 +108,27 @@ } /** + * Checks if one basic block dominates a collection of other basic blocks. + * + * @param dominator dominator to check against + * @param subjects subjects to check for domination by {@param dominator} + * @return whether {@param subjects} are all dominated by {@param dominator} + */ + public boolean dominatesAllOf(BasicBlock dominator, Iterable<BasicBlock> subjects) { + for (BasicBlock subject : subjects) { + if (!dominatedBy(subject, dominator)) { + return false; + } + } + return true; + } + + /** * Check if one basic block is strictly dominated by another basic block. * - * @param subject subject to check for domination by {@code dominator} + * @param subject subject to check for domination by {@param dominator} * @param dominator dominator to check against - * @return wether {@code subject} is strictly dominated by {@code dominator} + * @return whether {@param subject} is strictly dominated by {@param dominator} */ public boolean strictlyDominatedBy(BasicBlock subject, BasicBlock dominator) { assert !obsolete;
diff --git a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java index e041d5a..b95376e 100644 --- a/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/FieldInstruction.java
@@ -11,6 +11,10 @@ import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.AbstractError; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.ConcreteMutableFieldSet; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet; import com.android.tools.r8.shaking.AppInfoWithLiveness; import java.util.Collections; import java.util.List; @@ -131,4 +135,32 @@ // TODO(jsjeon): what if the target field is known to be non-null? return true; } + + @Override + public AbstractFieldSet readSet(AppView<?> appView, DexType context) { + if (instructionMayTriggerMethodInvocation(appView, context)) { + // This may trigger class initialization, which could potentially read any field. + return UnknownFieldSet.getInstance(); + } + + if (isFieldGet()) { + DexField field = getField(); + DexEncodedField encodedField = null; + if (appView.enableWholeProgramOptimizations()) { + encodedField = appView.appInfo().resolveField(field); + } else { + DexClass clazz = appView.definitionFor(field.holder); + if (clazz != null) { + encodedField = clazz.lookupField(field); + } + } + if (encodedField != null) { + return new ConcreteMutableFieldSet(encodedField); + } + return UnknownFieldSet.getInstance(); + } + + assert isFieldPut(); + return EmptyFieldSet.getInstance(); + } }
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 b924c4a..cb32015 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
@@ -262,7 +262,6 @@ } public boolean controlFlowMayDependOnEnvironment(AppView<?> appView) { - DexType context = method.method.holder; for (BasicBlock block : blocks) { if (block.hasCatchHandlers()) { // Whether an instruction throws may generally depend on the environment. @@ -1009,14 +1008,22 @@ } public void removeAllTrivialPhis() { - removeAllTrivialPhis(null); + removeAllTrivialPhis(null, null); } public void removeAllTrivialPhis(IRBuilder builder) { + removeAllTrivialPhis(builder, null); + } + + public void removeAllTrivialPhis(Set<Value> affectedValues) { + removeAllTrivialPhis(null, affectedValues); + } + + public void removeAllTrivialPhis(IRBuilder builder, Set<Value> affectedValues) { for (BasicBlock block : blocks) { List<Phi> phis = new ArrayList<>(block.getPhis()); for (Phi phi : phis) { - phi.removeTrivialPhi(builder); + phi.removeTrivialPhi(builder, affectedValues); } } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java index 03421cc..1a17a8b 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java +++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -17,6 +17,9 @@ import com.android.tools.r8.ir.analysis.constant.Bottom; import com.android.tools.r8.ir.analysis.constant.ConstRangeLatticeElement; import com.android.tools.r8.ir.analysis.constant.LatticeElement; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.UnknownFieldSet; import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; import com.android.tools.r8.ir.code.Assume.DynamicTypeAssumption; import com.android.tools.r8.ir.code.Assume.NoAssumption; @@ -540,6 +543,18 @@ } /** + * Returns an abstraction of the set of fields that may possibly be read as a result of executing + * this instruction. + */ + public AbstractFieldSet readSet(AppView<?> appView, DexType context) { + if (instructionMayTriggerMethodInvocation(appView, context) + && instructionMayHaveSideEffects(appView, context)) { + return UnknownFieldSet.getInstance(); + } + return EmptyFieldSet.getInstance(); + } + + /** * Returns true if this instruction need this value in a register. */ public boolean needsValueInRegister(Value value) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java index 9381fd1..48baef4 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -12,11 +12,15 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.AnalysisAssumption; import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis.Query; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.EmptyFieldSet; +import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling; import com.android.tools.r8.ir.conversion.CfBuilder; import com.android.tools.r8.ir.conversion.DexBuilder; import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget; @@ -277,4 +281,22 @@ return true; } + + @Override + public AbstractFieldSet readSet(AppView<?> appView, DexType context) { + DexMethod invokedMethod = getInvokedMethod(); + + // Trivial instance initializers do not read any fields. + if (appView.dexItemFactory().isConstructor(invokedMethod)) { + DexEncodedMethod singleTarget = lookupSingleTarget(appView, context); + if (singleTarget != null) { + TrivialInitializer info = singleTarget.getOptimizationInfo().getTrivialInitializerInfo(); + if (info != null && info.isTrivialInstanceInitializer()) { + return EmptyFieldSet.getInstance(); + } + } + } + + return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(this, appView.dexItemFactory()); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java index c3db7ea..178da3f 100644 --- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java +++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -11,6 +11,8 @@ import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.AbstractFieldSet; +import com.android.tools.r8.ir.analysis.modeling.LibraryMethodReadSetModeling; import com.android.tools.r8.ir.optimize.Inliner.InlineAction; import com.android.tools.r8.ir.optimize.InliningOracle; import com.android.tools.r8.ir.regalloc.RegisterAllocator; @@ -119,4 +121,9 @@ public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, DexType context) { return true; } + + @Override + public AbstractFieldSet readSet(AppView<?> appView, DexType context) { + return LibraryMethodReadSetModeling.getModeledReadSetOrUnknown(this, appView.dexItemFactory()); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java index e2f9cf3..7cd8ca2 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Phi.java +++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -123,7 +123,7 @@ builder.constrainType(operand, readConstraint); appendOperand(operand); } - removeTrivialPhi(builder); + removeTrivialPhi(builder, null); } public void addOperands(List<Value> operands) { @@ -224,10 +224,10 @@ } public void removeTrivialPhi() { - removeTrivialPhi(null); + removeTrivialPhi(null, null); } - public void removeTrivialPhi(IRBuilder builder) { + void removeTrivialPhi(IRBuilder builder, Set<Value> affectedValues) { Value same = null; for (Value op : operands) { if (op == same || op == this) { @@ -252,6 +252,9 @@ if (builder != null && typeLattice.isPreciseType() && !typeLattice.isBottom()) { builder.constrainType(same, ValueTypeConstraint.fromTypeLattice(typeLattice)); } + if (affectedValues != null) { + affectedValues.addAll(this.affectedValues()); + } // Removing this phi, so get rid of it as a phi user from all of the operands to avoid // recursively getting back here with the same phi. If the phi has itself as an operand // that also removes the self-reference. @@ -277,7 +280,7 @@ replaceUsers(same); // Try to simplify phi users that might now have become trivial. for (Phi user : phiUsersToSimplify) { - user.removeTrivialPhi(builder); + user.removeTrivialPhi(builder, affectedValues); } } // Get rid of the phi itself.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java index 2881de7..6648777 100644 --- a/src/main/java/com/android/tools/r8/ir/code/Value.java +++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -19,6 +19,7 @@ import com.android.tools.r8.position.MethodPosition; import com.android.tools.r8.utils.LongInterval; import com.android.tools.r8.utils.Reporter; +import com.android.tools.r8.utils.SetUtils; import com.android.tools.r8.utils.StringDiagnostic; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableSet; @@ -424,6 +425,22 @@ return users.getFirst(); } + public Set<Instruction> aliasedUsers() { + Set<Instruction> users = SetUtils.newIdentityHashSet(uniqueUsers()); + collectAliasedUsersViaAssume(uniqueUsers(), users); + return users; + } + + private static void collectAliasedUsersViaAssume( + Set<Instruction> usersToTest, Set<Instruction> collectedUsers) { + for (Instruction user : usersToTest) { + if (user.isAssume()) { + collectedUsers.addAll(user.outValue().uniqueUsers()); + collectAliasedUsersViaAssume(user.outValue().uniqueUsers(), collectedUsers); + } + } + } + public Phi firstPhiUser() { assert !phiUsers.isEmpty(); return phiUsers.getFirst();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index e370192..a75ce9e 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -32,6 +32,7 @@ import com.android.tools.r8.ir.analysis.TypeChecker; import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation; import com.android.tools.r8.ir.analysis.fieldaccess.FieldBitAccessAnalysis; +import com.android.tools.r8.ir.analysis.fieldvalueanalysis.FieldValueAnalysis; import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis; import com.android.tools.r8.ir.analysis.sideeffect.ClassInitializerSideEffectAnalysis.ClassInitializerSideEffect; import com.android.tools.r8.ir.analysis.type.ClassTypeLatticeElement; @@ -1110,8 +1111,7 @@ } if (memberValuePropagation != null) { - memberValuePropagation.rewriteWithConstantValues( - code, method.method.holder, isProcessedConcurrently); + memberValuePropagation.rewriteWithConstantValues(code, method.method.holder); } if (options.enableEnumValueOptimization) { assert appView.enableWholeProgramOptimizations(); @@ -1217,7 +1217,7 @@ codeRewriter.rewriteThrowNullPointerException(code); if (classInitializerDefaultsOptimization != null && !isDebugMode) { - classInitializerDefaultsOptimization.optimize(method, code, feedback); + classInitializerDefaultsOptimization.optimize(method, code); } if (Log.ENABLED) { @@ -1249,26 +1249,6 @@ assert code.verifyTypes(appView); - if (nonNullTracker != null) { - // TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args, - // this may not be the right place to collect call site optimization info. - // Collecting call-site optimization info depends on the existence of non-null IRs. - // Arguments can be changed during the debug mode. - if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) { - appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code); - } - // Computation of non-null parameters on normal exits rely on the existence of non-null IRs. - nonNullTracker.computeNonNullParamOnNormalExits(feedback, code); - } - if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) { - codeRewriter.removeAssumeInstructions(code); - assert code.isConsistentSSA(); - } - // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL. - assert code.verifyNoNullabilityBottomTypes(); - - assert code.verifyTypes(appView); - previous = printMethod(code, "IR before class inlining (SSA)", previous); if (classInliner != null) { @@ -1337,6 +1317,26 @@ assert code.isConsistentSSA(); } + if (nonNullTracker != null) { + // TODO(b/139246447): Once we extend this optimization to, e.g., constants of primitive args, + // this may not be the right place to collect call site optimization info. + // Collecting call-site optimization info depends on the existence of non-null IRs. + // Arguments can be changed during the debug mode. + if (!isDebugMode && appView.callSiteOptimizationInfoPropagator() != null) { + appView.callSiteOptimizationInfoPropagator().collectCallSiteOptimizationInfo(code); + } + // Computation of non-null parameters on normal exits rely on the existence of non-null IRs. + nonNullTracker.computeNonNullParamOnNormalExits(feedback, code); + } + if (aliasIntroducer != null || nonNullTracker != null || dynamicTypeOptimization != null) { + codeRewriter.removeAssumeInstructions(code); + assert code.isConsistentSSA(); + } + // Assert that we do not have unremoved non-sense code in the output, e.g., v <- non-null NULL. + assert code.verifyNoNullabilityBottomTypes(); + + assert code.verifyTypes(appView); + previous = printMethod(code, "IR after outline handler (SSA)", previous); // TODO(mkroghj) Test if shorten live ranges is worth it. @@ -1384,6 +1384,7 @@ } computeDynamicReturnType(feedback, method, code); + FieldValueAnalysis.run(appView, code, feedback, method); computeInitializedClassesOnNormalExit(feedback, method, code); computeMayHaveSideEffects(feedback, method, code); computeReturnValueOnlyDependsOnArguments(feedback, method, code);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java index 0e13782..b796600 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -31,9 +31,9 @@ import com.android.tools.r8.ir.conversion.IRConverter; import com.android.tools.r8.ir.desugar.backports.BackportedMethods; import com.android.tools.r8.ir.desugar.backports.BooleanMethodRewrites; +import com.android.tools.r8.ir.desugar.backports.CollectionMethodGenerators; +import com.android.tools.r8.ir.desugar.backports.CollectionMethodRewrites; import com.android.tools.r8.ir.desugar.backports.FloatMethodRewrites; -import com.android.tools.r8.ir.desugar.backports.ListMethodGenerators; -import com.android.tools.r8.ir.desugar.backports.ListMethodRewrites; import com.android.tools.r8.ir.desugar.backports.LongMethodRewrites; import com.android.tools.r8.ir.desugar.backports.NumericMethodRewrites; import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites; @@ -999,19 +999,63 @@ name = factory.createString("of"); for (int i = 0; i <= 10; i++) { final int formalCount = i; - proto = factory.createProto(factory.listType, Collections.nCopies(i, factory.objectType)); + proto = factory.createProto(type, Collections.nCopies(i, factory.objectType)); method = factory.createMethod(type, proto, name); addProvider( i == 0 - ? new InvokeRewriter(method, ListMethodRewrites::rewriteEmptyOf) + ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteListOfEmpty) : new MethodGenerator( method, - (options, methodArg, ignored) -> - ListMethodGenerators.generateListOf(options, methodArg, formalCount))); + (options, methodArg) -> + CollectionMethodGenerators.generateListOf(options, methodArg, formalCount))); } - proto = factory.createProto(factory.listType, factory.objectArrayType); + proto = factory.createProto(type, factory.objectArrayType); method = factory.createMethod(type, proto, name); - addProvider(new MethodGenerator(method, BackportedMethods::ListMethods_ofArray, "ofArray")); + addProvider( + new MethodGenerator( + method, BackportedMethods::CollectionMethods_listOfArray, "ofArray")); + + // Set<E> Set.of(<args>) for 0 to 10 arguments and Set.of(E[]) + type = factory.setType; + name = factory.createString("of"); + for (int i = 0; i <= 10; i++) { + final int formalCount = i; + proto = factory.createProto(type, Collections.nCopies(i, factory.objectType)); + method = factory.createMethod(type, proto, name); + addProvider( + i == 0 + ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteSetOfEmpty) + : new MethodGenerator( + method, + (options, methodArg) -> + CollectionMethodGenerators.generateSetOf(options, methodArg, formalCount))); + } + proto = factory.createProto(type, factory.objectArrayType); + method = factory.createMethod(type, proto, name); + addProvider( + new MethodGenerator( + method, BackportedMethods::CollectionMethods_setOfArray, "ofArray")); + + // Map<K, V> Map.of(<K, V args>) for 0 to 10 pairs and Map.ofEntries(Map.Entry<K, V>[]) + type = factory.mapType; + name = factory.createString("of"); + for (int i = 0; i <= 10; i++) { + final int formalCount = i; + proto = factory.createProto(type, Collections.nCopies(i * 2, factory.objectType)); + method = factory.createMethod(type, proto, name); + addProvider( + i == 0 + ? new InvokeRewriter(method, CollectionMethodRewrites::rewriteMapOfEmpty) + : new MethodGenerator( + method, + (options, methodArg) -> + CollectionMethodGenerators.generateMapOf(options, methodArg, formalCount))); + } + proto = factory.createProto(type, factory.createArrayType(1, factory.mapEntryType)); + method = factory.createMethod(type, proto, "ofEntries"); + addProvider( + new MethodGenerator( + method, BackportedMethods::CollectionMethods_mapOfEntries, "ofEntries")); } private void initializeJava11MethodProviders(DexItemFactory factory) { @@ -1273,7 +1317,7 @@ @Override public Code generateTemplateMethod(InternalOptions options, DexMethod method) { - return factory.create(options, method, methodName); + return factory.create(options, method); } @Override @@ -1311,7 +1355,7 @@ private interface TemplateMethodFactory { - Code create(InternalOptions options, DexMethod method, String name); + Code create(InternalOptions options, DexMethod method); } private interface MethodInvokeRewriter {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java index 8f0e3f6..0922cd2 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryAPIConverter.java
@@ -5,23 +5,56 @@ package com.android.tools.r8.ir.desugar; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; +import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexProto; import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.analysis.type.Nullability; +import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.Invoke; import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.InvokeStatic; +import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.StringDiagnostic; +import java.util.Collections; +// TODO(b/134732760): In progress. +// I convert library calls with desugared parameters/return values so they can work normally. +// In the JSON of the desugared library, one can specify conversions between desugared and +// non-desugared types. If no conversion is specified, D8/R8 simply generate wrapper classes around +// the types. Wrappers induce both memory and runtime performance overhead. Wrappers overload +// all potential called APIs. +// Since many types are going to be rewritten, I also need to change the signature of the method +// called so that they are still called with the original types. Hence the vivified types. +// Given a type from the library, the prefix rewriter rewrites (->) as follow: +// vivifiedType -> type; +// type -> desugarType; +// No vivified types can be present in the compiled program (will necessarily be rewritten). +// DesugarType is only a rewritten type (generated through rewriting of type). +// The type, from the library, may either be rewritten to the desugarType, +// or be a rewritten type (generated through rewriting of vivifiedType). public class DesugaredLibraryAPIConverter { - AppView<?> appView; + private static final String VIVIFIED_PREFIX = "$-vivified-$."; + + private final AppView<?> appView; + private final DexItemFactory factory; public DesugaredLibraryAPIConverter(AppView<?> appView) { this.appView = appView; + this.factory = appView.dexItemFactory(); } public void desugar(IRCode code) { + // TODO(b/134732760): The current code does not catch library calls into a program override + // which gets rewritten. If method signature has rewritten types and method overrides library, + // I should convert back. + InstructionListIterator iterator = code.instructionListIterator(); while (iterator.hasNext()) { Instruction instruction = iterator.next(); @@ -30,23 +63,29 @@ } InvokeMethod invokeMethod = instruction.asInvokeMethod(); DexMethod invokedMethod = invokeMethod.getInvokedMethod(); - if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder)) { + // Rewritting is required only on calls to library methods which are not desugared. + if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder) + || invokedMethod.holder.isArrayType()) { continue; } - // In this case, the method has not been rewritten and is not on a rewritten class. - // This invoke will (likely) not work at runtime if a desugared type is present. - if (appView.rewritePrefix.hasRewrittenType(invokedMethod.proto.returnType)) { - warnInvalidInvoke(invokedMethod.proto.returnType, invokedMethod, "return"); + DexClass dexClass = appView.definitionFor(invokedMethod.holder); + if (dexClass == null || !dexClass.isLibraryClass()) { + continue; } - for (DexType argType : invokedMethod.proto.parameters.values) { + if (appView.rewritePrefix.hasRewrittenType(invokedMethod.proto.returnType)) { + addReturnConversion(code, invokeMethod, iterator); + } + for (int i = 0; i < invokedMethod.proto.parameters.values.length; i++) { + DexType argType = invokedMethod.proto.parameters.values[i]; if (appView.rewritePrefix.hasRewrittenType(argType)) { - warnInvalidInvoke(argType, invokedMethod, "parameter"); + addParameterConversion(code, invokeMethod, iterator, argType, i); } } } } private void warnInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) { + DexType desugaredType = appView.rewritePrefix.rewrittenType(type); appView .options() .reporter @@ -59,7 +98,128 @@ + " may not work correctly at runtime (" + debugString + " type " - + appView.rewritePrefix.rewrittenType(type) + + desugaredType + " is a desugared type).")); } + + private DexType vivifiedTypeFor(DexType type) { + DexType vivifiedType = + factory.createType(DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString())); + appView.rewritePrefix.rewriteType(vivifiedType, type); + return vivifiedType; + } + + private void addParameterConversion( + IRCode code, + InvokeMethod invokeMethod, + InstructionListIterator iterator, + DexType argType, + int parameter) { + if (!appView + .options() + .desugaredLibraryConfiguration + .getCustomConversions() + .containsKey(argType)) { + // TODO(b/134732760): Add Wrapper Conversions. + warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter"); + return; + } + + Value inValue = invokeMethod.inValues().get(parameter); + DexType argVivifiedType = vivifiedTypeFor(argType); + DexType conversionHolder = + appView.options().desugaredLibraryConfiguration.getCustomConversions().get(argType); + + // ConversionType has static method "type convert(rewrittenType)". + // But everything is going to be rewritten, so we need to call "vivifiedType convert(type)". + DexMethod conversionMethod = + factory.createMethod( + conversionHolder, + factory.createProto(argVivifiedType, argType), + factory.convertMethodName); + Value convertedValue = + code.createValue( + TypeLatticeElement.fromDexType( + argVivifiedType, inValue.getTypeLattice().nullability(), appView)); + InvokeStatic conversionInstruction = + new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue)); + conversionInstruction.setPosition(invokeMethod.getPosition()); + iterator.previous(); + iterator.add(conversionInstruction); + iterator.next(); + + // Rewrite invoke (signature and inValue to rewrite). + DexMethod newDexMethod = + dexMethodWithDifferentParameter( + invokeMethod.getInvokedMethod(), argVivifiedType, parameter); + Invoke newInvokeMethod = + Invoke.create( + invokeMethod.getType(), + newDexMethod, + newDexMethod.proto, + invokeMethod.outValue(), + invokeMethod.inValues()); + newInvokeMethod.replaceValue(parameter, conversionInstruction.outValue()); + iterator.replaceCurrentInstruction(newInvokeMethod); + } + + private void addReturnConversion( + IRCode code, InvokeMethod invokeMethod, InstructionListIterator iterator) { + DexType returnType = invokeMethod.getReturnType(); + if (!appView + .options() + .desugaredLibraryConfiguration + .getCustomConversions() + .containsKey(returnType)) { + // TODO(b/134732760): Add Wrapper Conversions. + warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return"); + return; + } + + DexType returnVivifiedType = vivifiedTypeFor(returnType); + DexType conversionHolder = + appView.options().desugaredLibraryConfiguration.getCustomConversions().get(returnType); + + // ConversionType has static method "rewrittenType convert(type)". + // But everything is going to be rewritten, so we need to call "type convert(vivifiedType)". + DexMethod conversionMethod = + factory.createMethod( + conversionHolder, + factory.createProto(returnType, returnVivifiedType), + factory.convertMethodName); + Value convertedValue = + code.createValue( + TypeLatticeElement.fromDexType(returnType, Nullability.maybeNull(), appView)); + invokeMethod.outValue().replaceUsers(convertedValue); + InvokeStatic conversionInstruction = + new InvokeStatic( + conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue())); + conversionInstruction.setPosition(invokeMethod.getPosition()); + + // Rewrite invoke (signature to rewrite). + DexMethod newDexMethod = + dexMethodWithDifferentReturn(invokeMethod.getInvokedMethod(), returnVivifiedType); + Invoke newInvokeMethod = + Invoke.create( + invokeMethod.getType(), + newDexMethod, + newDexMethod.proto, + invokeMethod.outValue(), + invokeMethod.inValues()); + iterator.replaceCurrentInstruction(newInvokeMethod); + iterator.add(conversionInstruction); + } + + private DexMethod dexMethodWithDifferentParameter( + DexMethod method, DexType newParameterType, int parameter) { + DexType[] newParameters = method.proto.parameters.values.clone(); + newParameters[parameter] = newParameterType; + DexProto newProto = factory.createProto(method.proto.returnType, newParameters); + return factory.createMethod(method.holder, newProto, method.name); + } + + private DexMethod dexMethodWithDifferentReturn(DexMethod method, DexType newReturnType) { + DexProto newProto = factory.createProto(newReturnType, method.proto.parameters.values); + return factory.createMethod(method.holder, newProto, method.name); + } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java index 5bf1c8e..e1f2181 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -9,6 +9,7 @@ import com.android.tools.r8.graph.DexString; import com.android.tools.r8.graph.DexType; import com.android.tools.r8.ir.desugar.PrefixRewritingMapper.DesugarPrefixRewritingMapper; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.Pair; import com.google.common.collect.ImmutableList; @@ -22,11 +23,13 @@ public class DesugaredLibraryConfiguration { // TODO(b/134732760): should use DexString, DexType, DexMethod or so on when possible. + private final AndroidApiLevel requiredCompilationAPILevel; private final boolean libraryCompilation; private final Map<String, String> rewritePrefix; private final Map<DexType, DexType> emulateLibraryInterface; private final Map<DexString, Map<DexType, DexType>> retargetCoreLibMember; private final Map<DexType, DexType> backportCoreLibraryMember; + private final Map<DexType, DexType> customConversions; private final List<Pair<DexType, DexString>> dontRewriteInvocation; public static Builder builder(DexItemFactory dexItemFactory) { @@ -35,26 +38,32 @@ public static DesugaredLibraryConfiguration empty() { return new DesugaredLibraryConfiguration( + AndroidApiLevel.B, false, ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), + ImmutableMap.of(), ImmutableList.of()); } public DesugaredLibraryConfiguration( + AndroidApiLevel requiredCompilationAPILevel, boolean libraryCompilation, Map<String, String> rewritePrefix, Map<DexType, DexType> emulateLibraryInterface, Map<DexString, Map<DexType, DexType>> retargetCoreLibMember, Map<DexType, DexType> backportCoreLibraryMember, + Map<DexType, DexType> customConversions, List<Pair<DexType, DexString>> dontRewriteInvocation) { + this.requiredCompilationAPILevel = requiredCompilationAPILevel; this.libraryCompilation = libraryCompilation; this.rewritePrefix = rewritePrefix; this.emulateLibraryInterface = emulateLibraryInterface; this.retargetCoreLibMember = retargetCoreLibMember; this.backportCoreLibraryMember = backportCoreLibraryMember; + this.customConversions = customConversions; this.dontRewriteInvocation = dontRewriteInvocation; } @@ -64,6 +73,10 @@ : new DesugarPrefixRewritingMapper(rewritePrefix, factory); } + public AndroidApiLevel getRequiredCompilationApiLevel() { + return requiredCompilationAPILevel; + } + public boolean isLibraryCompilation() { return libraryCompilation; } @@ -84,6 +97,10 @@ return backportCoreLibraryMember; } + public Map<DexType, DexType> getCustomConversions() { + return customConversions; + } + public List<Pair<DexType, DexString>> getDontRewriteInvocation() { return dontRewriteInvocation; } @@ -92,17 +109,24 @@ private final DexItemFactory factory; + private AndroidApiLevel requiredCompilationAPILevel; private boolean libraryCompilation = false; private Map<String, String> rewritePrefix = new HashMap<>(); private Map<DexType, DexType> emulateLibraryInterface = new HashMap<>(); private Map<DexString, Map<DexType, DexType>> retargetCoreLibMember = new IdentityHashMap<>(); private Map<DexType, DexType> backportCoreLibraryMember = new HashMap<>(); + private Map<DexType, DexType> customConversions = new HashMap<>(); private List<Pair<DexType, DexString>> dontRewriteInvocation = new ArrayList<>(); public Builder(DexItemFactory dexItemFactory) { this.factory = dexItemFactory; } + public Builder setRequiredCompilationAPILevel(AndroidApiLevel requiredCompilationAPILevel) { + this.requiredCompilationAPILevel = requiredCompilationAPILevel; + return this; + } + public Builder setProgramCompilation() { libraryCompilation = false; return this; @@ -126,6 +150,13 @@ return this; } + public Builder putCustomConversion(String type, String conversionHolder) { + DexType dexType = stringClassToDexType(type); + DexType conversionType = stringClassToDexType(conversionHolder); + customConversions.put(dexType, conversionType); + return this; + } + public Builder putRetargetCoreLibMember(String retarget, String rewrittenRetarget) { int index = sharpIndex(retarget, "retarget core library member"); DexString methodName = factory.createString(retarget.substring(index + 1)); @@ -169,11 +200,13 @@ public DesugaredLibraryConfiguration build() { return new DesugaredLibraryConfiguration( + requiredCompilationAPILevel, libraryCompilation, ImmutableMap.copyOf(rewritePrefix), ImmutableMap.copyOf(emulateLibraryInterface), ImmutableMap.copyOf(retargetCoreLibMember), ImmutableMap.copyOf(backportCoreLibraryMember), + ImmutableMap.copyOf(customConversions), ImmutableList.copyOf(dontRewriteInvocation)); } }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java index f5664d3..fbf3d1f 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfigurationParser.java
@@ -6,6 +6,7 @@ import com.android.tools.r8.StringResource; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.Reporter; import com.android.tools.r8.utils.StringDiagnostic; import com.google.gson.JsonArray; @@ -57,6 +58,10 @@ "Unsupported desugared library configuration version, please upgrade the D8/R8" + " compiler.")); } + int required_compilation_api_level = + jsonConfig.get("required_compilation_api_level").getAsInt(); + configurationBuilder.setRequiredCompilationAPILevel( + AndroidApiLevel.getAndroidApiLevel(required_compilation_api_level)); JsonArray jsonFlags = libraryCompilation ? jsonConfig.getAsJsonArray("library_flags") @@ -100,6 +105,13 @@ configurationBuilder.putEmulateLibraryInterface(itf.getKey(), itf.getValue().getAsString()); } } + if (jsonFlagSet.has("custom_conversion")) { + for (Map.Entry<String, JsonElement> conversion : + jsonFlagSet.get("custom_conversion").getAsJsonObject().entrySet()) { + configurationBuilder.putCustomConversion( + conversion.getKey(), conversion.getValue().getAsString()); + } + } if (jsonFlagSet.has("dont_rewrite")) { JsonArray dontRewrite = jsonFlagSet.get("dont_rewrite").getAsJsonArray(); for (JsonElement rewrite : dontRewrite) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java index 320935f..db8f582 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -156,12 +156,15 @@ } private void addRewritePrefix(DexType interfaceType, String rewrittenType) { - appView.rewritePrefix.addPrefix( - getCompanionClassType(interfaceType).toString(), - rewrittenType + COMPANION_CLASS_NAME_SUFFIX); - appView.rewritePrefix.addPrefix( - getEmulateLibraryInterfaceClassType(interfaceType, factory).toString(), - rewrittenType + EMULATE_LIBRARY_CLASS_NAME_SUFFIX); + appView.rewritePrefix.rewriteType( + getCompanionClassType(interfaceType), + factory.createType( + DescriptorUtils.javaTypeToDescriptor(rewrittenType + COMPANION_CLASS_NAME_SUFFIX))); + appView.rewritePrefix.rewriteType( + getEmulateLibraryInterfaceClassType(interfaceType, factory), + factory.createType( + DescriptorUtils.javaTypeToDescriptor( + rewrittenType + EMULATE_LIBRARY_CLASS_NAME_SUFFIX))); } boolean isEmulatedInterface(DexType itf) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java index 65eb486..454085b 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -30,6 +30,7 @@ import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.IRConverter; +import com.android.tools.r8.utils.DescriptorUtils; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; import com.google.common.collect.ImmutableSet; @@ -298,8 +299,11 @@ String rewrittenString = rewritten.toString(); String actualRewrittenPrefix = rewrittenString.substring(0, rewrittenString.lastIndexOf('.')); assert javaName.startsWith(actualPrefix); - appView.rewritePrefix.addPrefix( - javaName, actualRewrittenPrefix + javaName.substring(actualPrefix.length())); + appView.rewritePrefix.rewriteType( + lambdaClassType, + factory.createType( + DescriptorUtils.javaTypeToDescriptor( + actualRewrittenPrefix + javaName.substring(actualPrefix.length())))); } private static <K, V> V getKnown(Map<K, V> map, K key) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java index 2b1b45b..244dfaf 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/PrefixRewritingMapper.java
@@ -23,7 +23,7 @@ public abstract DexType rewrittenType(DexType type); - public abstract void addPrefix(String prefix, String rewrittenPrefix); + public abstract void rewriteType(DexType type, DexType rewrittenType); public boolean hasRewrittenType(DexType type) { return rewrittenType(type) != null; @@ -35,9 +35,7 @@ private final Set<DexType> notRewritten = Sets.newConcurrentHashSet(); private final Map<DexType, DexType> rewritten = new ConcurrentHashMap<>(); - // Prefix is IdentityHashMap, additionalPrefixes requires however concurrent read and writes. private final Map<DexString, DexString> initialPrefixes; - private final Map<DexString, DexString> additionalPrefixes = new ConcurrentHashMap<>(); private final DexItemFactory factory; public DesugarPrefixRewritingMapper(Map<String, String> prefixes, DexItemFactory factory) { @@ -97,19 +95,25 @@ } @Override - public void addPrefix(String prefix, String rewrittenPrefix) { - additionalPrefixes.put(toDescriptorPrefix(prefix), toDescriptorPrefix(rewrittenPrefix)); + public void rewriteType(DexType type, DexType rewrittenType) { + assert !notRewritten.contains(type) + : "New rewriting rule for " + + type + + " but the compiler has already made decisions based on the fact that this type was" + + " not rewritten"; + assert !rewritten.containsKey(type) || rewritten.get(type) == rewrittenType + : "New rewriting rule for " + + type + + " but the compiler has already made decisions based on a different rewriting rule" + + " for this type"; + rewritten.put(type, rewrittenType); } private DexType computePrefix(DexType type) { DexString prefixToMatch = type.descriptor.withoutArray(factory); - DexType result1 = lookup(type, prefixToMatch, initialPrefixes); - if (result1 != null) { - return result1; - } - DexType result2 = lookup(type, prefixToMatch, additionalPrefixes); - if (result2 != null) { - return result2; + DexType result = lookup(type, prefixToMatch, initialPrefixes); + if (result != null) { + return result; } notRewritten.add(type); return null; @@ -122,7 +126,7 @@ DexString rewrittenTypeDescriptor = type.descriptor.withNewPrefix(prefix, map.get(prefix), factory); DexType rewrittenType = factory.createType(rewrittenTypeDescriptor); - rewritten.put(type, rewrittenType); + rewriteType(type, rewrittenType); return rewrittenType; } } @@ -143,7 +147,7 @@ } @Override - public void addPrefix(String prefix, String rewrittenPrefix) {} + public void rewriteType(DexType type, DexType rewrittenType) {} @Override public boolean isRewriting() {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java index 12d43cb..d746bf1 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -126,7 +126,7 @@ // The only encoded method. CfCode code = BackportedMethods.CloseResourceMethod_closeResourceImpl( - options, twrCloseResourceMethod, null); + options, twrCloseResourceMethod); MethodAccessFlags flags = MethodAccessFlags.fromSharedAccessFlags( Constants.ACC_PUBLIC | Constants.ACC_STATIC | Constants.ACC_SYNTHETIC, false); DexEncodedMethod method = new DexEncodedMethod(twrCloseResourceMethod,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java index fb025c9..aadf9e5 100644 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -47,8 +47,7 @@ public final class BackportedMethods { - public static CfCode BooleanMethods_compare( - InternalOptions options, DexMethod method, String name) { + public static CfCode BooleanMethods_compare(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -79,8 +78,7 @@ ImmutableList.of()); } - public static CfCode BooleanMethods_hashCode( - InternalOptions options, DexMethod method, String name) { + public static CfCode BooleanMethods_hashCode(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -104,7 +102,7 @@ ImmutableList.of()); } - public static CfCode ByteMethods_compare(InternalOptions options, DexMethod method, String name) { + public static CfCode ByteMethods_compare(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -122,8 +120,7 @@ ImmutableList.of()); } - public static CfCode ByteMethods_compareUnsigned( - InternalOptions options, DexMethod method, String name) { + public static CfCode ByteMethods_compareUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -145,8 +142,7 @@ ImmutableList.of()); } - public static CfCode ByteMethods_toUnsignedInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode ByteMethods_toUnsignedInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -164,8 +160,7 @@ ImmutableList.of()); } - public static CfCode ByteMethods_toUnsignedLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode ByteMethods_toUnsignedLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -184,8 +179,7 @@ ImmutableList.of()); } - public static CfCode CharacterMethods_compare( - InternalOptions options, DexMethod method, String name) { + public static CfCode CharacterMethods_compare(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -204,7 +198,7 @@ } public static CfCode CharacterMethods_toStringCodepoint( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -239,7 +233,7 @@ } public static CfCode CloseResourceMethod_closeResourceImpl( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -518,8 +512,400 @@ ImmutableList.of()); } + public static CfCode CollectionMethods_listOfArray(InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + return new CfCode( + method.holder, + 3, + 6, + ImmutableList.of( + label0, + new CfNew(options.itemFactory.createType("Ljava/util/ArrayList;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/ArrayList;"), + options.itemFactory.createProto( + options.itemFactory.createType("V"), options.itemFactory.createType("I")), + options.itemFactory.createString("<init>")), + false), + new CfStore(ValueType.OBJECT, 1), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfStore(ValueType.OBJECT, 2), + new CfLoad(ValueType.OBJECT, 2), + new CfArrayLength(), + new CfStore(ValueType.INT, 3), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 4), + label2, + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 3), + new CfIfCmp(If.Type.GE, ValueType.INT, label5), + new CfLoad(ValueType.OBJECT, 2), + new CfLoad(ValueType.INT, 4), + new CfArrayLoad(MemberType.OBJECT), + new CfStore(ValueType.OBJECT, 5), + label3, + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 5), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Objects;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/Object;"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("requireNonNull")), + false), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/ArrayList;"), + options.itemFactory.createProto( + options.itemFactory.createType("Z"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("add")), + false), + new CfStackInstruction(CfStackInstruction.Opcode.Pop), + label4, + new CfIinc(4, 1), + new CfGoto(label2), + label5, + new CfLoad(ValueType.OBJECT, 1), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Collections;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/util/List;"), + options.itemFactory.createType("Ljava/util/List;")), + options.itemFactory.createString("unmodifiableList")), + false), + new CfReturn(ValueType.OBJECT), + label6), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode CollectionMethods_mapOfEntries(InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + CfLabel label8 = new CfLabel(); + CfLabel label9 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 8, + ImmutableList.of( + label0, + new CfNew(options.itemFactory.createType("Ljava/util/HashMap;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/HashMap;"), + options.itemFactory.createProto( + options.itemFactory.createType("V"), options.itemFactory.createType("I")), + options.itemFactory.createString("<init>")), + false), + new CfStore(ValueType.OBJECT, 1), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfStore(ValueType.OBJECT, 2), + new CfLoad(ValueType.OBJECT, 2), + new CfArrayLength(), + new CfStore(ValueType.INT, 3), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 4), + label2, + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 3), + new CfIfCmp(If.Type.GE, ValueType.INT, label8), + new CfLoad(ValueType.OBJECT, 2), + new CfLoad(ValueType.INT, 4), + new CfArrayLoad(MemberType.OBJECT), + new CfStore(ValueType.OBJECT, 5), + label3, + new CfLoad(ValueType.OBJECT, 5), + new CfInvoke( + 185, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Map$Entry;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("getKey")), + true), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Objects;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/Object;"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("requireNonNull")), + false), + new CfStore(ValueType.OBJECT, 6), + label4, + new CfLoad(ValueType.OBJECT, 5), + new CfInvoke( + 185, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Map$Entry;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("getValue")), + true), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Objects;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/Object;"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("requireNonNull")), + false), + new CfStore(ValueType.OBJECT, 7), + label5, + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 6), + new CfLoad(ValueType.OBJECT, 7), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/HashMap;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/Object;"), + options.itemFactory.createType("Ljava/lang/Object;"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("put")), + false), + new CfIf(If.Type.EQ, ValueType.OBJECT, label7), + label6, + new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfNew(options.itemFactory.createType("Ljava/lang/StringBuilder;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto(options.itemFactory.createType("V")), + options.itemFactory.createString("<init>")), + false), + new CfConstString(options.itemFactory.createString("duplicate key: ")), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("append")), + false), + new CfLoad(ValueType.OBJECT, 6), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("append")), + false), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("toString")), + false), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"), + options.itemFactory.createProto( + options.itemFactory.createType("V"), + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("<init>")), + false), + new CfThrow(), + label7, + new CfIinc(4, 1), + new CfGoto(label2), + label8, + new CfLoad(ValueType.OBJECT, 1), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Collections;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/util/Map;"), + options.itemFactory.createType("Ljava/util/Map;")), + options.itemFactory.createString("unmodifiableMap")), + false), + new CfReturn(ValueType.OBJECT), + label9), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode CollectionMethods_setOfArray(InternalOptions options, DexMethod method) { + CfLabel label0 = new CfLabel(); + CfLabel label1 = new CfLabel(); + CfLabel label2 = new CfLabel(); + CfLabel label3 = new CfLabel(); + CfLabel label4 = new CfLabel(); + CfLabel label5 = new CfLabel(); + CfLabel label6 = new CfLabel(); + CfLabel label7 = new CfLabel(); + return new CfCode( + method.holder, + 4, + 6, + ImmutableList.of( + label0, + new CfNew(options.itemFactory.createType("Ljava/util/HashSet;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfLoad(ValueType.OBJECT, 0), + new CfArrayLength(), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/HashSet;"), + options.itemFactory.createProto( + options.itemFactory.createType("V"), options.itemFactory.createType("I")), + options.itemFactory.createString("<init>")), + false), + new CfStore(ValueType.OBJECT, 1), + label1, + new CfLoad(ValueType.OBJECT, 0), + new CfStore(ValueType.OBJECT, 2), + new CfLoad(ValueType.OBJECT, 2), + new CfArrayLength(), + new CfStore(ValueType.INT, 3), + new CfConstNumber(0, ValueType.INT), + new CfStore(ValueType.INT, 4), + label2, + new CfLoad(ValueType.INT, 4), + new CfLoad(ValueType.INT, 3), + new CfIfCmp(If.Type.GE, ValueType.INT, label6), + new CfLoad(ValueType.OBJECT, 2), + new CfLoad(ValueType.INT, 4), + new CfArrayLoad(MemberType.OBJECT), + new CfStore(ValueType.OBJECT, 5), + label3, + new CfLoad(ValueType.OBJECT, 1), + new CfLoad(ValueType.OBJECT, 5), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Objects;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/Object;"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("requireNonNull")), + false), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/HashSet;"), + options.itemFactory.createProto( + options.itemFactory.createType("Z"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("add")), + false), + new CfIf(If.Type.NE, ValueType.INT, label5), + label4, + new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfNew(options.itemFactory.createType("Ljava/lang/StringBuilder;")), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto(options.itemFactory.createType("V")), + options.itemFactory.createString("<init>")), + false), + new CfConstString(options.itemFactory.createString("duplicate element: ")), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("append")), + false), + new CfLoad(ValueType.OBJECT, 5), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createType("Ljava/lang/Object;")), + options.itemFactory.createString("append")), + false), + new CfInvoke( + 182, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/StringBuilder;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("toString")), + false), + new CfInvoke( + 183, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"), + options.itemFactory.createProto( + options.itemFactory.createType("V"), + options.itemFactory.createType("Ljava/lang/String;")), + options.itemFactory.createString("<init>")), + false), + new CfThrow(), + label5, + new CfIinc(4, 1), + new CfGoto(label2), + label6, + new CfLoad(ValueType.OBJECT, 1), + new CfInvoke( + 184, + options.itemFactory.createMethod( + options.itemFactory.createType("Ljava/util/Collections;"), + options.itemFactory.createProto( + options.itemFactory.createType("Ljava/util/Set;"), + options.itemFactory.createType("Ljava/util/Set;")), + options.itemFactory.createString("unmodifiableSet")), + false), + new CfReturn(ValueType.OBJECT), + label7), + ImmutableList.of(), + ImmutableList.of()); + } + public static CfCode CollectionsMethods_emptyEnumeration( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); return new CfCode( method.holder, @@ -549,8 +935,7 @@ ImmutableList.of()); } - public static CfCode CollectionsMethods_emptyIterator( - InternalOptions options, DexMethod method, String name) { + public static CfCode CollectionsMethods_emptyIterator(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); return new CfCode( method.holder, @@ -580,7 +965,7 @@ } public static CfCode CollectionsMethods_emptyListIterator( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); return new CfCode( method.holder, @@ -609,8 +994,7 @@ ImmutableList.of()); } - public static CfCode DoubleMethods_hashCode( - InternalOptions options, DexMethod method, String name) { + public static CfCode DoubleMethods_hashCode(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -643,8 +1027,7 @@ ImmutableList.of()); } - public static CfCode DoubleMethods_isFinite( - InternalOptions options, DexMethod method, String name) { + public static CfCode DoubleMethods_isFinite(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -686,8 +1069,7 @@ ImmutableList.of()); } - public static CfCode FloatMethods_isFinite( - InternalOptions options, DexMethod method, String name) { + public static CfCode FloatMethods_isFinite(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -729,8 +1111,7 @@ ImmutableList.of()); } - public static CfCode IntegerMethods_compare( - InternalOptions options, DexMethod method, String name) { + public static CfCode IntegerMethods_compare(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -762,8 +1143,7 @@ ImmutableList.of()); } - public static CfCode IntegerMethods_compareUnsigned( - InternalOptions options, DexMethod method, String name) { + public static CfCode IntegerMethods_compareUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -802,8 +1182,7 @@ ImmutableList.of()); } - public static CfCode IntegerMethods_divideUnsigned( - InternalOptions options, DexMethod method, String name) { + public static CfCode IntegerMethods_divideUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -816,13 +1195,13 @@ label0, new CfLoad(ValueType.INT, 0), new CfNumberConversion(NumericType.INT, NumericType.LONG), - new CfConstNumber(4294967295l, ValueType.LONG), + new CfConstNumber(4294967295L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.And, NumericType.LONG), new CfStore(ValueType.LONG, 2), label1, new CfLoad(ValueType.INT, 1), new CfNumberConversion(NumericType.INT, NumericType.LONG), - new CfConstNumber(4294967295l, ValueType.LONG), + new CfConstNumber(4294967295L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.And, NumericType.LONG), new CfStore(ValueType.LONG, 4), label2, @@ -836,8 +1215,7 @@ ImmutableList.of()); } - public static CfCode IntegerMethods_parseUnsignedInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode IntegerMethods_parseUnsignedInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -865,7 +1243,7 @@ } public static CfCode IntegerMethods_parseUnsignedIntWithRadix( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -930,7 +1308,7 @@ new CfStore(ValueType.LONG, 2), label3, new CfLoad(ValueType.LONG, 2), - new CfConstNumber(4294967295l, ValueType.LONG), + new CfConstNumber(4294967295L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.And, NumericType.LONG), new CfLoad(ValueType.LONG, 2), new CfCmp(Cmp.Bias.NONE, NumericType.LONG), @@ -1025,8 +1403,7 @@ ImmutableList.of()); } - public static CfCode IntegerMethods_remainderUnsigned( - InternalOptions options, DexMethod method, String name) { + public static CfCode IntegerMethods_remainderUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -1039,13 +1416,13 @@ label0, new CfLoad(ValueType.INT, 0), new CfNumberConversion(NumericType.INT, NumericType.LONG), - new CfConstNumber(4294967295l, ValueType.LONG), + new CfConstNumber(4294967295L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.And, NumericType.LONG), new CfStore(ValueType.LONG, 2), label1, new CfLoad(ValueType.INT, 1), new CfNumberConversion(NumericType.INT, NumericType.LONG), - new CfConstNumber(4294967295l, ValueType.LONG), + new CfConstNumber(4294967295L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.And, NumericType.LONG), new CfStore(ValueType.LONG, 4), label2, @@ -1059,8 +1436,7 @@ ImmutableList.of()); } - public static CfCode IntegerMethods_toUnsignedLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode IntegerMethods_toUnsignedLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -1071,7 +1447,7 @@ label0, new CfLoad(ValueType.INT, 0), new CfNumberConversion(NumericType.INT, NumericType.LONG), - new CfConstNumber(4294967295l, ValueType.LONG), + new CfConstNumber(4294967295L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.And, NumericType.LONG), new CfReturn(ValueType.LONG), label1), @@ -1079,8 +1455,7 @@ ImmutableList.of()); } - public static CfCode IntegerMethods_toUnsignedString( - InternalOptions options, DexMethod method, String name) { + public static CfCode IntegerMethods_toUnsignedString(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -1108,7 +1483,7 @@ } public static CfCode IntegerMethods_toUnsignedStringWithRadix( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -1120,7 +1495,7 @@ label0, new CfLoad(ValueType.INT, 0), new CfNumberConversion(NumericType.INT, NumericType.LONG), - new CfConstNumber(4294967295l, ValueType.LONG), + new CfConstNumber(4294967295L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.And, NumericType.LONG), new CfStore(ValueType.LONG, 2), label1, @@ -1142,93 +1517,7 @@ ImmutableList.of()); } - public static CfCode ListMethods_ofArray(InternalOptions options, DexMethod method, String name) { - CfLabel label0 = new CfLabel(); - CfLabel label1 = new CfLabel(); - CfLabel label2 = new CfLabel(); - CfLabel label3 = new CfLabel(); - CfLabel label4 = new CfLabel(); - CfLabel label5 = new CfLabel(); - CfLabel label6 = new CfLabel(); - return new CfCode( - method.holder, - 3, - 6, - ImmutableList.of( - label0, - new CfNew(options.itemFactory.createType("Ljava/util/ArrayList;")), - new CfStackInstruction(CfStackInstruction.Opcode.Dup), - new CfLoad(ValueType.OBJECT, 0), - new CfArrayLength(), - new CfInvoke( - 183, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/util/ArrayList;"), - options.itemFactory.createProto( - options.itemFactory.createType("V"), options.itemFactory.createType("I")), - options.itemFactory.createString("<init>")), - false), - new CfStore(ValueType.OBJECT, 1), - label1, - new CfLoad(ValueType.OBJECT, 0), - new CfStore(ValueType.OBJECT, 2), - new CfLoad(ValueType.OBJECT, 2), - new CfArrayLength(), - new CfStore(ValueType.INT, 3), - new CfConstNumber(0, ValueType.INT), - new CfStore(ValueType.INT, 4), - label2, - new CfLoad(ValueType.INT, 4), - new CfLoad(ValueType.INT, 3), - new CfIfCmp(If.Type.GE, ValueType.INT, label5), - new CfLoad(ValueType.OBJECT, 2), - new CfLoad(ValueType.INT, 4), - new CfArrayLoad(MemberType.OBJECT), - new CfStore(ValueType.OBJECT, 5), - label3, - new CfLoad(ValueType.OBJECT, 1), - new CfLoad(ValueType.OBJECT, 5), - new CfInvoke( - 184, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/util/Objects;"), - options.itemFactory.createProto( - options.itemFactory.createType("Ljava/lang/Object;"), - options.itemFactory.createType("Ljava/lang/Object;")), - options.itemFactory.createString("requireNonNull")), - false), - new CfInvoke( - 182, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/util/ArrayList;"), - options.itemFactory.createProto( - options.itemFactory.createType("Z"), - options.itemFactory.createType("Ljava/lang/Object;")), - options.itemFactory.createString("add")), - false), - new CfStackInstruction(CfStackInstruction.Opcode.Pop), - label4, - new CfIinc(4, 1), - new CfGoto(label2), - label5, - new CfLoad(ValueType.OBJECT, 1), - new CfInvoke( - 184, - options.itemFactory.createMethod( - options.itemFactory.createType("Ljava/util/Collections;"), - options.itemFactory.createProto( - options.itemFactory.createType("Ljava/util/List;"), - options.itemFactory.createType("Ljava/util/List;")), - options.itemFactory.createString("unmodifiableList")), - false), - new CfReturn(ValueType.OBJECT), - label6), - ImmutableList.of(), - ImmutableList.of()); - } - - public static CfCode LongMethods_compareUnsigned( - InternalOptions options, DexMethod method, String name) { + public static CfCode LongMethods_compareUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -1240,12 +1529,12 @@ ImmutableList.of( label0, new CfLoad(ValueType.LONG, 0), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 4), label1, new CfLoad(ValueType.LONG, 2), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 6), label2, @@ -1267,8 +1556,7 @@ ImmutableList.of()); } - public static CfCode LongMethods_divideUnsigned( - InternalOptions options, DexMethod method, String name) { + public static CfCode LongMethods_divideUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -1297,12 +1585,12 @@ new CfIf(If.Type.GE, ValueType.INT, label6), label1, new CfLoad(ValueType.LONG, 0), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 4), label2, new CfLoad(ValueType.LONG, 2), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 6), label3, @@ -1344,12 +1632,12 @@ new CfStore(ValueType.LONG, 6), label10, new CfLoad(ValueType.LONG, 6), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 8), label11, new CfLoad(ValueType.LONG, 2), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 10), label12, @@ -1371,8 +1659,7 @@ ImmutableList.of()); } - public static CfCode LongMethods_hashCode( - InternalOptions options, DexMethod method, String name) { + public static CfCode LongMethods_hashCode(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -1393,8 +1680,7 @@ ImmutableList.of()); } - public static CfCode LongMethods_parseUnsignedLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode LongMethods_parseUnsignedLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -1422,7 +1708,7 @@ } public static CfCode LongMethods_parseUnsignedLongWithRadix( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -1679,8 +1965,7 @@ ImmutableList.of()); } - public static CfCode LongMethods_remainderUnsigned( - InternalOptions options, DexMethod method, String name) { + public static CfCode LongMethods_remainderUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -1709,12 +1994,12 @@ new CfIf(If.Type.GE, ValueType.INT, label6), label1, new CfLoad(ValueType.LONG, 0), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 4), label2, new CfLoad(ValueType.LONG, 2), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 6), label3, @@ -1758,12 +2043,12 @@ new CfStore(ValueType.LONG, 6), label10, new CfLoad(ValueType.LONG, 6), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 8), label11, new CfLoad(ValueType.LONG, 2), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfLogicalBinop(CfLogicalBinop.Opcode.Xor, NumericType.LONG), new CfStore(ValueType.LONG, 10), label12, @@ -1784,8 +2069,7 @@ ImmutableList.of()); } - public static CfCode LongMethods_toUnsignedString( - InternalOptions options, DexMethod method, String name) { + public static CfCode LongMethods_toUnsignedString(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -1813,7 +2097,7 @@ } public static CfCode LongMethods_toUnsignedStringWithRadix( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2066,8 +2350,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_addExactInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_addExactInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2115,8 +2398,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_addExactLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_addExactLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2180,8 +2462,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_decrementExactInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_decrementExactInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2216,8 +2497,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_decrementExactLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_decrementExactLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2229,7 +2509,7 @@ ImmutableList.of( label0, new CfLoad(ValueType.LONG, 0), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfCmp(Cmp.Bias.NONE, NumericType.LONG), new CfIf(If.Type.NE, ValueType.INT, label2), label1, @@ -2253,8 +2533,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_floorDivInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_floorDivInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2312,8 +2591,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_floorDivLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_floorDivLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2375,8 +2653,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_floorDivLongInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_floorDivLongInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -2404,8 +2681,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_floorModInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_floorModInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2455,8 +2731,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_floorModLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_floorModLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2510,8 +2785,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_floorModLongInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_floorModLongInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -2540,8 +2814,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_incrementExactInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_incrementExactInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2576,8 +2849,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_incrementExactLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_incrementExactLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2589,7 +2861,7 @@ ImmutableList.of( label0, new CfLoad(ValueType.LONG, 0), - new CfConstNumber(9223372036854775807l, ValueType.LONG), + new CfConstNumber(9223372036854775807L, ValueType.LONG), new CfCmp(Cmp.Bias.NONE, NumericType.LONG), new CfIf(If.Type.NE, ValueType.INT, label2), label1, @@ -2613,8 +2885,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_multiplyExactInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_multiplyExactInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2662,8 +2933,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_multiplyExactLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_multiplyExactLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2758,7 +3028,7 @@ new CfConstNumber(0, ValueType.INT), label9, new CfLoad(ValueType.LONG, 2), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfCmp(Cmp.Bias.NONE, NumericType.LONG), new CfIf(If.Type.EQ, ValueType.INT, label10), new CfConstNumber(1, ValueType.INT), @@ -2803,8 +3073,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_multiplyExactLongInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_multiplyExactLongInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -2832,8 +3101,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_negateExactInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_negateExactInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2867,8 +3135,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_negateExactLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_negateExactLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -2880,7 +3147,7 @@ ImmutableList.of( label0, new CfLoad(ValueType.LONG, 0), - new CfConstNumber(-9223372036854775808l, ValueType.LONG), + new CfConstNumber(-9223372036854775808L, ValueType.LONG), new CfCmp(Cmp.Bias.NONE, NumericType.LONG), new CfIf(If.Type.NE, ValueType.INT, label2), label1, @@ -2903,8 +3170,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_nextDownDouble( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_nextDownDouble(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -2930,8 +3196,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_nextDownFloat( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_nextDownFloat(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -2957,8 +3222,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_subtractExactInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_subtractExactInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3006,8 +3270,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_subtractExactLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_subtractExactLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3071,8 +3334,7 @@ ImmutableList.of()); } - public static CfCode MathMethods_toIntExact( - InternalOptions options, DexMethod method, String name) { + public static CfCode MathMethods_toIntExact(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3113,7 +3375,7 @@ } public static CfCode ObjectsMethods_checkFromIndexSize( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3253,8 +3515,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_checkFromToIndex( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_checkFromToIndex(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3371,8 +3632,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_checkIndex( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_checkIndex(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3466,8 +3726,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_compare( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_compare(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3504,8 +3763,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_deepEquals( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_deepEquals(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3841,8 +4099,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_equals( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_equals(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3883,8 +4140,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_hashCode( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_hashCode(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3915,8 +4171,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_isNull( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_isNull(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3940,8 +4195,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_nonNull( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_nonNull(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -3966,7 +4220,7 @@ } public static CfCode ObjectsMethods_requireNonNullElse( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4000,7 +4254,7 @@ } public static CfCode ObjectsMethods_requireNonNullElseGet( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4058,7 +4312,7 @@ } public static CfCode ObjectsMethods_requireNonNullMessage( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4093,8 +4347,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_toString( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_toString(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -4121,8 +4374,7 @@ ImmutableList.of()); } - public static CfCode ObjectsMethods_toStringDefault( - InternalOptions options, DexMethod method, String name) { + public static CfCode ObjectsMethods_toStringDefault(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4154,8 +4406,7 @@ ImmutableList.of()); } - public static CfCode OptionalMethods_ifPresentOrElse( - InternalOptions options, DexMethod method, String name) { + public static CfCode OptionalMethods_ifPresentOrElse(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4214,7 +4465,7 @@ } public static CfCode OptionalMethods_ifPresentOrElseDouble( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4271,7 +4522,7 @@ } public static CfCode OptionalMethods_ifPresentOrElseInt( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4328,7 +4579,7 @@ } public static CfCode OptionalMethods_ifPresentOrElseLong( - InternalOptions options, DexMethod method, String name) { + InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4384,7 +4635,7 @@ ImmutableList.of()); } - public static CfCode OptionalMethods_or(InternalOptions options, DexMethod method, String name) { + public static CfCode OptionalMethods_or(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4451,8 +4702,7 @@ ImmutableList.of()); } - public static CfCode OptionalMethods_stream( - InternalOptions options, DexMethod method, String name) { + public static CfCode OptionalMethods_stream(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4507,8 +4757,7 @@ ImmutableList.of()); } - public static CfCode ShortMethods_compare( - InternalOptions options, DexMethod method, String name) { + public static CfCode ShortMethods_compare(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -4526,8 +4775,7 @@ ImmutableList.of()); } - public static CfCode ShortMethods_compareUnsigned( - InternalOptions options, DexMethod method, String name) { + public static CfCode ShortMethods_compareUnsigned(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -4549,8 +4797,7 @@ ImmutableList.of()); } - public static CfCode ShortMethods_toUnsignedInt( - InternalOptions options, DexMethod method, String name) { + public static CfCode ShortMethods_toUnsignedInt(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -4568,8 +4815,7 @@ ImmutableList.of()); } - public static CfCode ShortMethods_toUnsignedLong( - InternalOptions options, DexMethod method, String name) { + public static CfCode ShortMethods_toUnsignedLong(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); return new CfCode( @@ -4588,8 +4834,7 @@ ImmutableList.of()); } - public static CfCode StringMethods_joinArray( - InternalOptions options, DexMethod method, String name) { + public static CfCode StringMethods_joinArray(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel(); @@ -4707,8 +4952,7 @@ ImmutableList.of()); } - public static CfCode StringMethods_joinIterable( - InternalOptions options, DexMethod method, String name) { + public static CfCode StringMethods_joinIterable(InternalOptions options, DexMethod method) { CfLabel label0 = new CfLabel(); CfLabel label1 = new CfLabel(); CfLabel label2 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java new file mode 100644 index 0000000..9e78a3b --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodGenerators.java
@@ -0,0 +1,122 @@ +// Copyright (c) 2019, 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.desugar.backports; + +import com.android.tools.r8.cf.code.CfArrayStore; +import com.android.tools.r8.cf.code.CfConstNumber; +import com.android.tools.r8.cf.code.CfInstruction; +import com.android.tools.r8.cf.code.CfInvoke; +import com.android.tools.r8.cf.code.CfLoad; +import com.android.tools.r8.cf.code.CfNew; +import com.android.tools.r8.cf.code.CfNewArray; +import com.android.tools.r8.cf.code.CfReturn; +import com.android.tools.r8.cf.code.CfStackInstruction; +import com.android.tools.r8.dex.Constants; +import com.android.tools.r8.graph.CfCode; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.graph.DexType; +import com.android.tools.r8.ir.code.MemberType; +import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.utils.InternalOptions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableList.Builder; +import org.objectweb.asm.Opcodes; + +public final class CollectionMethodGenerators { + + private CollectionMethodGenerators() {} + + public static CfCode generateListOf(InternalOptions options, DexMethod method, int formalCount) { + return generateFixedMethods(options, method, formalCount, options.itemFactory.listType); + } + + public static CfCode generateSetOf(InternalOptions options, DexMethod method, int formalCount) { + return generateFixedMethods(options, method, formalCount, options.itemFactory.setType); + } + + private static CfCode generateFixedMethods( + InternalOptions options, DexMethod method, int formalCount, DexType returnType) { + Builder<CfInstruction> builder = ImmutableList.builder(); + builder.add( + new CfConstNumber(formalCount, ValueType.INT), + new CfNewArray(options.itemFactory.objectArrayType)); + + for (int i = 0; i < formalCount; i++) { + builder.add( + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfConstNumber(i, ValueType.INT), + new CfLoad(ValueType.OBJECT, i), + new CfArrayStore(MemberType.OBJECT)); + } + + builder.add( + new CfInvoke( + Opcodes.INVOKESTATIC, + options.itemFactory.createMethod( + returnType, + options.itemFactory.createProto(returnType, options.itemFactory.objectArrayType), + options.itemFactory.createString("of")), + false), + new CfReturn(ValueType.OBJECT)); + + return new CfCode( + method.holder, + 4, + formalCount, + builder.build(), + ImmutableList.of(), + ImmutableList.of()); + } + + public static CfCode generateMapOf( + InternalOptions options, DexMethod method, int formalCount) { + DexType mapEntryArray = + options.itemFactory.createArrayType(1, options.itemFactory.mapEntryType); + DexType simpleEntry = options.itemFactory.createType("Ljava/util/AbstractMap$SimpleEntry;"); + DexMethod simpleEntryConstructor = options.itemFactory.createMethod( + simpleEntry, + options.itemFactory.createProto( + options.itemFactory.voidType, + options.itemFactory.objectType, + options.itemFactory.objectType), + Constants.INSTANCE_INITIALIZER_NAME); + + Builder<CfInstruction> builder = ImmutableList.builder(); + builder.add( + new CfConstNumber(formalCount, ValueType.INT), + new CfNewArray(mapEntryArray)); + + for (int i = 0; i < formalCount; i++) { + builder.add( + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfConstNumber(i, ValueType.INT), + new CfNew(simpleEntry), + new CfStackInstruction(CfStackInstruction.Opcode.Dup), + new CfLoad(ValueType.OBJECT, i * 2), + new CfLoad(ValueType.OBJECT, i * 2 + 1), + new CfInvoke(Opcodes.INVOKESPECIAL, simpleEntryConstructor, false), + new CfArrayStore(MemberType.OBJECT)); + } + + builder.add( + new CfInvoke( + Opcodes.INVOKESTATIC, + options.itemFactory.createMethod( + options.itemFactory.mapType, + options.itemFactory.createProto( + options.itemFactory.mapType, + mapEntryArray), + options.itemFactory.createString("ofEntries")), + false), + new CfReturn(ValueType.OBJECT)); + + return new CfCode( + method.holder, + 7, + formalCount * 2, + builder.build(), + ImmutableList.of(), + ImmutableList.of()); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java new file mode 100644 index 0000000..5f54256 --- /dev/null +++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/CollectionMethodRewrites.java
@@ -0,0 +1,43 @@ +// Copyright (c) 2019, 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.desugar.backports; + +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.graph.DexMethod; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.InvokeMethod; +import com.android.tools.r8.ir.code.InvokeStatic; +import java.util.Collections; + +public final class CollectionMethodRewrites { + + private CollectionMethodRewrites() {} + + public static void rewriteListOfEmpty( + InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) { + rewriteToCollectionMethod(invoke, iterator, factory, "emptyList"); + } + + public static void rewriteSetOfEmpty( + InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) { + rewriteToCollectionMethod(invoke, iterator, factory, "emptySet"); + } + + public static void rewriteMapOfEmpty( + InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) { + rewriteToCollectionMethod(invoke, iterator, factory, "emptyMap"); + } + + private static void rewriteToCollectionMethod(InvokeMethod invoke, + InstructionListIterator iterator, DexItemFactory factory, String methodName) { + assert invoke.inValues().isEmpty(); + + DexMethod collectionsEmptyList = + factory.createMethod(factory.collectionsType, invoke.getInvokedMethod().proto, methodName); + InvokeStatic newInvoke = + new InvokeStatic(collectionsEmptyList, invoke.outValue(), Collections.emptyList()); + iterator.replaceCurrentInstruction(newInvoke); + } +}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ListMethodGenerators.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ListMethodGenerators.java deleted file mode 100644 index 1319752..0000000 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ListMethodGenerators.java +++ /dev/null
@@ -1,60 +0,0 @@ -// Copyright (c) 2019, 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.desugar.backports; - -import com.android.tools.r8.cf.code.CfArrayStore; -import com.android.tools.r8.cf.code.CfConstNumber; -import com.android.tools.r8.cf.code.CfInstruction; -import com.android.tools.r8.cf.code.CfInvoke; -import com.android.tools.r8.cf.code.CfLoad; -import com.android.tools.r8.cf.code.CfNewArray; -import com.android.tools.r8.cf.code.CfReturn; -import com.android.tools.r8.cf.code.CfStackInstruction; -import com.android.tools.r8.graph.CfCode; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.ir.code.MemberType; -import com.android.tools.r8.ir.code.ValueType; -import com.android.tools.r8.utils.InternalOptions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; -import org.objectweb.asm.Opcodes; - -public final class ListMethodGenerators { - - private ListMethodGenerators() {} - - public static CfCode generateListOf(InternalOptions options, DexMethod method, int formalCount) { - Builder<CfInstruction> builder = ImmutableList.builder(); - builder.add( - new CfConstNumber(formalCount, ValueType.INT), - new CfNewArray(options.itemFactory.objectArrayType)); - - for (int i = 0; i < formalCount; i++) { - builder.add( - new CfStackInstruction(CfStackInstruction.Opcode.Dup), - new CfConstNumber(i, ValueType.INT), - new CfLoad(ValueType.OBJECT, i), - new CfArrayStore(MemberType.OBJECT)); - } - - builder.add( - new CfInvoke( - Opcodes.INVOKESTATIC, - options.itemFactory.createMethod( - options.itemFactory.listType, - options.itemFactory.createProto( - options.itemFactory.listType, options.itemFactory.objectArrayType), - options.itemFactory.createString("of")), - false), - new CfReturn(ValueType.OBJECT)); - - return new CfCode( - method.holder, - formalCount + 2, - formalCount, - builder.build(), - ImmutableList.of(), - ImmutableList.of()); - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/ListMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/ListMethodRewrites.java deleted file mode 100644 index c3245d8..0000000 --- a/src/main/java/com/android/tools/r8/ir/desugar/backports/ListMethodRewrites.java +++ /dev/null
@@ -1,28 +0,0 @@ -// Copyright (c) 2019, 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.desugar.backports; - -import com.android.tools.r8.graph.DexItemFactory; -import com.android.tools.r8.graph.DexMethod; -import com.android.tools.r8.ir.code.InstructionListIterator; -import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.InvokeStatic; -import java.util.Collections; - -public final class ListMethodRewrites { - - private ListMethodRewrites() {} - - public static void rewriteEmptyOf( - InvokeMethod invoke, InstructionListIterator iterator, DexItemFactory factory) { - assert invoke.inValues().isEmpty(); - - DexMethod collectionsEmptyList = - factory.createMethod(factory.collectionsType, invoke.getInvokedMethod().proto, "emptyList"); - InvokeStatic newInvoke = - new InvokeStatic(collectionsEmptyList, invoke.outValue(), Collections.emptyList()); - iterator.replaceCurrentInstruction(newInvoke); - } -}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java index 72009f4..6c2110b 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/ClassInitializerDefaultsOptimization.java
@@ -29,7 +29,6 @@ import com.android.tools.r8.graph.DexValue.DexValueNull; import com.android.tools.r8.graph.DexValue.DexValueShort; import com.android.tools.r8.graph.DexValue.DexValueString; -import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; import com.android.tools.r8.ir.code.ArrayPut; import com.android.tools.r8.ir.code.BasicBlock; @@ -45,7 +44,6 @@ import com.android.tools.r8.ir.code.StaticPut; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.conversion.IRConverter; -import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo; import com.android.tools.r8.naming.dexitembasedstring.ClassNameComputationInfo.ClassNameMapping; import com.android.tools.r8.shaking.AppInfoWithLiveness; @@ -104,7 +102,7 @@ this.dexItemFactory = appView.dexItemFactory(); } - public void optimize(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) { + public void optimize(DexEncodedMethod method, IRCode code) { if (!method.isClassInitializer()) { return; } @@ -165,14 +163,6 @@ throw new Unreachable("Unexpected field type " + fieldType + "."); } } - } else if (appView.options().enableFieldTypePropagation && appView.appInfo().hasLiveness()) { - AppInfoWithLiveness appInfoWithLiveness = appView.withLiveness().appInfo(); - if (appInfoWithLiveness.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(field)) { - TypeLatticeElement valueType = value.getTypeLattice(); - assert valueType.strictlyLessThan( - TypeLatticeElement.fromDexType(fieldType, Nullability.maybeNull(), appView), appView); - feedback.markFieldHasDynamicType(field, valueType); - } } }
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 fba5ae7..1f36d25 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
@@ -218,7 +218,7 @@ // Therefore, Assume elimination may result in a trivial phi: // z <- phi(x, x) if (needToCheckTrivialPhis) { - code.removeAllTrivialPhis(); + code.removeAllTrivialPhis(valuesThatRequireWidening); } if (!valuesThatRequireWidening.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java index 6644b26..787cf33 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -5,12 +5,9 @@ import com.android.tools.r8.graph.AppView; import com.android.tools.r8.graph.DebugLocalInfo; -import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexDefinition; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer; -import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer.TrivialClassInitializer; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexMethod; import com.android.tools.r8.graph.DexReference; @@ -19,7 +16,6 @@ import com.android.tools.r8.ir.analysis.type.Nullability; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.analysis.type.TypeLatticeElement; -import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo; import com.android.tools.r8.ir.code.ConstInstruction; @@ -31,7 +27,6 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.InvokeMethod; -import com.android.tools.r8.ir.code.Position; import com.android.tools.r8.ir.code.StaticGet; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; @@ -44,7 +39,6 @@ import com.google.common.collect.Sets; import java.util.ListIterator; import java.util.Set; -import java.util.function.Predicate; public class MemberValuePropagation { @@ -335,18 +329,11 @@ iterator.add(replacement); } target.getMutableOptimizationInfo().markAsPropagated(); - return; - } - if (target.getOptimizationInfo().neverReturnsNull() - && current.outValue().getTypeLattice().isReference() - && current.outValue().canBeNull()) { - insertAssumeNotNull(code, affectedValues, blocks, iterator, current); } } private void rewriteStaticGetWithConstantValues( IRCode code, - Predicate<DexEncodedMethod> isProcessedConcurrently, Set<Value> affectedValues, ListIterator<BasicBlock> blocks, InstructionListIterator iterator, @@ -390,49 +377,6 @@ code.method.getMutableOptimizationInfo().markUseIdentifierNameString(); } feedback.markFieldAsPropagated(target); - return; - } - - if (current.hasOutValue()) { - Value outValue = current.outValue(); - TypeLatticeElement outType = outValue.getTypeLattice(); - if (outType.isReference() && outType.isNullable()) { - TypeLatticeElement dynamicType = target.getOptimizationInfo().getDynamicType(); - if (dynamicType != null && dynamicType.isDefinitelyNotNull()) { - insertAssumeNotNull(code, affectedValues, blocks, iterator, current); - return; - } - - // In case the class holder of this static field satisfying following criteria: - // -- cannot trigger other static initializer except for its own - // -- is final - // -- has a class initializer which is classified as trivial - // (see CodeRewriter::computeClassInitializerInfo) and - // initializes the field being accessed - // - // ... and the field itself is not pinned by keep rules (in which case it might - // be updated outside the class constructor, e.g. via reflections), it is safe - // to assume that the static-get instruction reads the value it initialized value - // in class initializer and is never null. - // TODO(b/141143236): This should be subsumed entirely by the non-null propagation for - // fields, and thus should be removed. - DexClass holderDefinition = appView.definitionFor(field.holder); - if (holderDefinition != null - && holderDefinition.accessFlags.isFinal() - && !field.holder.initializationOfParentTypesMayHaveSideEffects(appView)) { - DexEncodedMethod classInitializer = holderDefinition.getClassInitializer(); - if (classInitializer != null && !isProcessedConcurrently.test(classInitializer)) { - TrivialInitializer info = - classInitializer.getOptimizationInfo().getTrivialInitializerInfo(); - if (info != null - && ((TrivialClassInitializer) info).field == field - && outValue.getTypeLattice().isReference() - && outValue.canBeNull()) { - insertAssumeNotNull(code, affectedValues, blocks, iterator, current); - } - } - } - } } } @@ -465,39 +409,12 @@ } } - private void insertAssumeNotNull( - IRCode code, - Set<Value> affectedValues, - ListIterator<BasicBlock> blocks, - InstructionListIterator iterator, - Instruction current) { - Value knownToBeNonNullValue = current.outValue(); - Set<Value> affectedUsers = knownToBeNonNullValue.affectedValues(); - TypeLatticeElement typeLattice = knownToBeNonNullValue.getTypeLattice(); - Value nonNullValue = - code.createValue( - typeLattice.asReferenceTypeLatticeElement().asNotNull(), - knownToBeNonNullValue.getLocalInfo()); - knownToBeNonNullValue.replaceUsers(nonNullValue); - Assume nonNull = - Assume.createAssumeNonNullInstruction( - nonNullValue, knownToBeNonNullValue, current, appView); - nonNull.setPosition(appView.options().debug ? current.getPosition() : Position.none()); - if (current.getBlock().hasCatchHandlers()) { - iterator.split(code, blocks).listIterator(code).add(nonNull); - } else { - iterator.add(nonNull); - } - affectedValues.addAll(affectedUsers); - } - /** * Replace invoke targets and field accesses with constant values where possible. * * <p>Also assigns value ranges to values where possible. */ - public void rewriteWithConstantValues( - IRCode code, DexType callingContext, Predicate<DexEncodedMethod> isProcessedConcurrently) { + public void rewriteWithConstantValues(IRCode code, DexType callingContext) { IRMetadata metadata = code.metadata(); if (!metadata.mayHaveFieldGet() && !metadata.mayHaveInvokeMethod()) { return; @@ -516,7 +433,6 @@ } else if (current.isStaticGet()) { rewriteStaticGetWithConstantValues( code, - isProcessedConcurrently, affectedValues, blocks, iterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java index 3b06027..02cfe3a 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -6,8 +6,10 @@ import static com.android.tools.r8.ir.code.DominatorTree.Assumption.MAY_HAVE_UNREACHABLE_BLOCKS; import com.android.tools.r8.graph.AppView; +import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedField; import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.graph.DexEncodedMethod.TrivialInitializer; import com.android.tools.r8.graph.DexField; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.graph.DexMethod; @@ -30,6 +32,7 @@ import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo; import com.android.tools.r8.ir.optimize.info.MethodOptimizationInfo; import com.android.tools.r8.ir.optimize.info.OptimizationFeedback; +import com.android.tools.r8.shaking.AppInfoWithLiveness; import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; @@ -139,6 +142,9 @@ && optimizationInfo.getDynamicType().isDefinitelyNotNull()) { knownToBeNonNullValues.add(outValue); } + + assert verifyCompanionClassInstanceIsKnownToBeNonNull( + fieldInstruction, encodedField, knownToBeNonNullValues); } } } @@ -244,6 +250,35 @@ } } + private boolean verifyCompanionClassInstanceIsKnownToBeNonNull( + FieldInstruction instruction, + DexEncodedField encodedField, + Set<Value> knownToBeNonNullValues) { + if (!appView.appInfo().hasLiveness()) { + return true; + } + if (instruction.isStaticGet()) { + AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness(); + DexField field = encodedField.field; + DexClass clazz = appViewWithLiveness.definitionFor(field.holder); + assert clazz != null; + if (clazz.accessFlags.isFinal() + && !clazz.initializationOfParentTypesMayHaveSideEffects(appViewWithLiveness)) { + DexEncodedMethod classInitializer = clazz.getClassInitializer(); + if (classInitializer != null) { + TrivialInitializer info = + classInitializer.getOptimizationInfo().getTrivialInitializerInfo(); + boolean expectedToBeNonNull = + info != null + && info.asTrivialClassInitializer().field == field + && !appViewWithLiveness.appInfo().isPinned(field); + assert !expectedToBeNonNull || knownToBeNonNullValues.contains(instruction.outValue()); + } + } + } + return true; + } + private void addNonNullForValues( IRCode code, ListIterator<BasicBlock> blockIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java index 4837545..1845186 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -556,8 +556,10 @@ templateInstructions.add(OutlineInstruction.fromInstruction(current)); } else if (current.isConstInstruction()) { // Don't include const instructions in the template. + } else if (current.isAssume()) { + // Don't include assume instructions in the template. } else { - assert false : "Unexpected type of instruction in outlining template."; + assert false : "Unexpected type of instruction in outlining template:" + current; } } } @@ -788,6 +790,12 @@ include = true; instructionIncrement = 0; } + } else if (instruction.isAssume()) { + // Technically, assume instructions will be removed, and thus it should not be included. + // However, we should keep searching, so here we pretend to include it with 0 increment. + // When adding instruction into the outline candidate, we filter out assume instructions. + include = true; + instructionIncrement = 0; } else { include = canIncludeInstruction(instruction); } @@ -986,12 +994,16 @@ // Add the current instruction to the outline. private void includeInstruction(Instruction instruction) { + if (instruction.isAssume()) { + return; + } + List<Value> inValues = orderedInValues(instruction, returnValue); Value prevReturnValue = returnValue; if (returnValue != null) { for (Value value : inValues) { - if (value == returnValue) { + if (value.getAliasedValue() == returnValue) { assert returnValueUsersLeft > 0; returnValueUsersLeft--; } @@ -1013,7 +1025,7 @@ || instruction.isArithmeticBinop(); if (inValues.size() > 0) { for (int i = 0; i < inValues.size(); i++) { - Value value = inValues.get(i); + Value value = inValues.get(i).getAliasedValue(); if (value == prevReturnValue) { argumentsMap.add(OutlineInstruction.OUTLINE_TEMP); continue; @@ -1067,7 +1079,6 @@ } } - protected abstract void handle(int start, int end, Outline outline); private void candidate(int start, int index) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java index b656678..7df3542 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -8,9 +8,11 @@ import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.ir.analysis.type.TypeAnalysis; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionOrPhi; +import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.desugar.LambdaRewriter; import com.android.tools.r8.ir.optimize.CodeRewriter; import com.android.tools.r8.ir.optimize.Inliner; @@ -18,9 +20,11 @@ import com.android.tools.r8.ir.optimize.string.StringOptimizer; import com.android.tools.r8.logging.Log; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.google.common.collect.Sets; import com.google.common.collect.Streams; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import java.util.function.Supplier; @@ -34,8 +38,14 @@ NON_CLASS_TYPE, UNKNOWN_TYPE, + // Used by isClassEligible + NON_PROGRAM_CLASS, + ABSTRACT_OR_INTERFACE, + NEVER_CLASS_INLINE, + HAS_FINALIZER, + TRIGGER_CLINIT, + // Used by InlineCandidateProcessor#isClassAndUsageEligible - INELIGIBLE_CLASS, HAS_CLINIT, HAS_INSTANCE_FIELDS, NON_FINAL_TYPE, @@ -46,7 +56,8 @@ } private final LambdaRewriter lambdaRewriter; - private final ConcurrentHashMap<DexClass, Boolean> knownClasses = new ConcurrentHashMap<>(); + private final ConcurrentHashMap<DexClass, EligibilityStatus> knownClasses = + new ConcurrentHashMap<>(); public ClassInliner(LambdaRewriter lambdaRewriter) { this.lambdaRewriter = lambdaRewriter; @@ -56,7 +67,15 @@ DexEncodedMethod context, Instruction root, EligibilityStatus status) { if (Log.ENABLED && Log.isLoggingEnabledFor(ClassInliner.class)) { Log.info(getClass(), "At %s,", context.toSourceString()); - Log.info(getClass(), "ClassInlining eligibility of %s: %s,", root, status); + Log.info(getClass(), "ClassInlining eligibility of `%s`: %s.", root, status); + } + } + + private void logIneligibleUser( + DexEncodedMethod context, Instruction root, InstructionOrPhi ineligibleUser) { + if (Log.ENABLED && Log.isLoggingEnabledFor(ClassInliner.class)) { + Log.info(getClass(), "At %s,", context.toSourceString()); + Log.info(getClass(), "Ineligible user of `%s`: `%s`.", root, ineligibleUser); } } @@ -133,7 +152,7 @@ // return 1; // } // static int method3() { - // return "F::getX"; + // return 123; // } // } // @@ -195,6 +214,7 @@ InstructionOrPhi ineligibleUser = processor.areInstanceUsersEligible(defaultOracle); if (ineligibleUser != null) { // This root may succeed if users change in future. + logIneligibleUser(code.method, root, ineligibleUser); continue; } @@ -208,7 +228,11 @@ anyInlinedMethods |= processor.processInlining(code, defaultOracle); // Restore normality. - code.removeAllTrivialPhis(); + Set<Value> affectedValues = Sets.newIdentityHashSet(); + code.removeAllTrivialPhis(affectedValues); + if (!affectedValues.isEmpty()) { + new TypeAnalysis(appView).narrowing(affectedValues); + } assert code.isConsistentSSA(); rootsIterator.remove(); repeat = true; @@ -233,11 +257,11 @@ } } - private boolean isClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) { - Boolean eligible = knownClasses.get(clazz); + private EligibilityStatus isClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) { + EligibilityStatus eligible = knownClasses.get(clazz); if (eligible == null) { - boolean computed = computeClassEligible(appView, clazz); - Boolean existing = knownClasses.putIfAbsent(clazz, computed); + EligibilityStatus computed = computeClassEligible(appView, clazz); + EligibilityStatus existing = knownClasses.putIfAbsent(clazz, computed); assert existing == null || existing == computed; eligible = existing == null ? computed : existing; } @@ -248,13 +272,19 @@ // - is not an abstract class or interface // - does not declare finalizer // - does not trigger any static initializers except for its own - private boolean computeClassEligible(AppView<AppInfoWithLiveness> appView, DexClass clazz) { - if (clazz == null - || clazz.isNotProgramClass() - || clazz.accessFlags.isAbstract() - || clazz.accessFlags.isInterface() - || appView.appInfo().neverClassInline.contains(clazz.type)) { - return false; + private EligibilityStatus computeClassEligible( + AppView<AppInfoWithLiveness> appView, DexClass clazz) { + if (clazz == null) { + return EligibilityStatus.UNKNOWN_TYPE; + } + if (clazz.isNotProgramClass()) { + return EligibilityStatus.NON_PROGRAM_CLASS; + } + if (clazz.isAbstract() || clazz.isInterface()) { + return EligibilityStatus.ABSTRACT_OR_INTERFACE; + } + if (appView.appInfo().neverClassInline.contains(clazz.type)) { + return EligibilityStatus.NEVER_CLASS_INLINE; } // Class must not define finalizer. @@ -262,11 +292,14 @@ for (DexEncodedMethod method : clazz.virtualMethods()) { if (method.method.name == dexItemFactory.finalizeMethodName && method.method.proto == dexItemFactory.objectMethods.finalize.proto) { - return false; + return EligibilityStatus.HAS_FINALIZER; } } // Check for static initializers in this class or any of interfaces it implements. - return !clazz.initializationOfParentTypesMayHaveSideEffects(appView); + if (clazz.initializationOfParentTypesMayHaveSideEffects(appView)) { + return EligibilityStatus.TRIGGER_CLINIT; + } + return EligibilityStatus.ELIGIBLE; } }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java index a16e9a3..d06b3f9 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/FieldValueHelper.java
@@ -4,6 +4,7 @@ package com.android.tools.r8.ir.optimize.classinliner; +import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull; import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull; import com.android.tools.r8.graph.AppView; @@ -14,6 +15,7 @@ import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionIterator; +import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Phi.RegisterReadType; import com.android.tools.r8.ir.code.Value; @@ -39,6 +41,9 @@ this.code = code; this.root = root; this.appView = appView; + // Verify that `root` is not aliased. + assert root.hasOutValue(); + assert root.outValue() == root.outValue().getAliasedValue(); } void replaceValue(Value oldValue, Value newValue) { @@ -122,10 +127,10 @@ Instruction instruction = iterator.previous(); assert instruction != null; - if (instruction == root || - (instruction.isInstancePut() && - instruction.asInstancePut().getField() == field && - instruction.asInstancePut().object() == root.outValue())) { + if (instruction == root + || (instruction.isInstancePut() + && instruction.asInstancePut().getField() == field + && instruction.asInstancePut().object().getAliasedValue() == root.outValue())) { valueProducingInsn = instruction; break; } @@ -140,12 +145,17 @@ assert root == valueProducingInsn; if (defaultValue == null) { + InstructionListIterator it = block.listIterator(code, root); // If we met newInstance it means that default value is supposed to be used. - defaultValue = - code.createValue(TypeLatticeElement.fromDexType(field.type, maybeNull(), appView)); - ConstNumber defaultValueInsn = new ConstNumber(defaultValue, 0); - defaultValueInsn.setPosition(root.getPosition()); - block.listIterator(code, root).add(defaultValueInsn); + if (field.type.isPrimitiveType()) { + defaultValue = code.createValue( + TypeLatticeElement.fromDexType(field.type, definitelyNotNull(), appView)); + ConstNumber defaultValueInsn = new ConstNumber(defaultValue, 0); + defaultValueInsn.setPosition(root.getPosition()); + it.add(defaultValueInsn); + } else { + defaultValue = it.insertConstNullInstruction(code, appView.options()); + } } return defaultValue; }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java index a2e9358..1318135 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -17,6 +17,7 @@ import com.android.tools.r8.graph.ResolutionResult; import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis; import com.android.tools.r8.ir.analysis.type.TypeAnalysis; +import com.android.tools.r8.ir.code.Assume; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.ConstNumber; import com.android.tools.r8.ir.code.IRCode; @@ -40,16 +41,18 @@ import com.android.tools.r8.ir.optimize.info.ParameterUsagesInfo.ParameterUsage; import com.android.tools.r8.kotlin.KotlinInfo; import com.android.tools.r8.shaking.AppInfoWithLiveness; +import com.android.tools.r8.utils.ListUtils; import com.android.tools.r8.utils.Pair; +import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import java.util.ArrayList; -import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; @@ -60,7 +63,7 @@ private final AppView<AppInfoWithLiveness> appView; private final LambdaRewriter lambdaRewriter; private final Inliner inliner; - private final Predicate<DexClass> isClassEligible; + private final Function<DexClass, EligibilityStatus> isClassEligible; private final Predicate<DexEncodedMethod> isProcessedConcurrently; private final DexEncodedMethod method; private final Instruction root; @@ -83,7 +86,7 @@ AppView<AppInfoWithLiveness> appView, LambdaRewriter lambdaRewriter, Inliner inliner, - Predicate<DexClass> isClassEligible, + Function<DexClass, EligibilityStatus> isClassEligible, Predicate<DexEncodedMethod> isProcessedConcurrently, DexEncodedMethod method, Instruction root) { @@ -140,8 +143,9 @@ // * class has class initializer marked as TrivialClassInitializer, and // class initializer initializes the field we are reading here. EligibilityStatus isClassAndUsageEligible() { - if (!isClassEligible.test(eligibleClassDefinition)) { - return EligibilityStatus.INELIGIBLE_CLASS; + EligibilityStatus status = isClassEligible.apply(eligibleClassDefinition); + if (status != EligibilityStatus.ELIGIBLE) { + return status; } if (root.isNewInstance()) { @@ -251,7 +255,7 @@ * * @return null if all users are eligible, or the first ineligible user. */ - protected InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) { + InstructionOrPhi areInstanceUsersEligible(Supplier<InliningOracle> defaultOracle) { // No Phi users. if (eligibleInstance.numberOfPhiUsers() > 0) { return eligibleInstance.firstPhiUser(); // Not eligible. @@ -259,11 +263,19 @@ Set<Instruction> currentUsers = eligibleInstance.uniqueUsers(); while (!currentUsers.isEmpty()) { - Set<Instruction> indirectUsers = new HashSet<>(); + Set<Instruction> indirectUsers = Sets.newIdentityHashSet(); for (Instruction user : currentUsers) { + if (user.isAssume()) { + if (user.outValue().numberOfPhiUsers() > 0) { + return user.outValue().firstPhiUser(); // Not eligible. + } + indirectUsers.addAll(user.outValue().uniqueUsers()); + continue; + } // Field read/write. if (user.isInstanceGet() - || (user.isInstancePut() && user.asInstancePut().value() != eligibleInstance)) { + || (user.isInstancePut() + && user.asInstancePut().value().getAliasedValue() != eligibleInstance)) { DexField field = user.asFieldInstruction().getField(); if (field.holder == eligibleClass && eligibleClassDefinition.lookupInstanceField(field) != null) { @@ -288,7 +300,7 @@ boolean isCorrespondingConstructorCall = root.isNewInstance() && !invoke.inValues().isEmpty() - && root.outValue() == invoke.inValues().get(0); + && root.outValue() == invoke.getReceiver(); if (isCorrespondingConstructorCall) { InliningInfo inliningInfo = isEligibleConstructorCall(user.asInvokeDirect(), singleTarget, defaultOracle); @@ -343,6 +355,7 @@ // Process inlining, includes the following steps: // + // * remove linked assume instructions if any so that users of the eligible field are up-to-date. // * replace unused instance usages as arguments which are never used // * inline extra methods if any, collect new direct method calls // * inline direct methods if any @@ -352,6 +365,9 @@ // // Returns `true` if at least one method was inlined. boolean processInlining(IRCode code, Supplier<InliningOracle> defaultOracle) { + // Verify that `eligibleInstance` is not aliased. + assert eligibleInstance == eligibleInstance.getAliasedValue(); + replaceUsagesAsUnusedArgument(code); boolean anyInlinedMethods = forceInlineExtraMethodInvocations(code); @@ -374,11 +390,14 @@ // methods that need to be inlined anyway. return true; } - assert extraMethodCalls.isEmpty(); - assert unusedArguments.isEmpty(); + assert extraMethodCalls.isEmpty() + : "Remaining extra method calls: " + StringUtils.join(extraMethodCalls.entrySet(), ", "); + assert unusedArguments.isEmpty() + : "Remaining unused arg: " + StringUtils.join(unusedArguments, ", "); } anyInlinedMethods |= forceInlineDirectMethodInvocations(code); + removeAssumeInstructionsLinkedToEligibleInstance(); removeMiscUsages(code); removeFieldReads(code); removeFieldWrites(); @@ -419,6 +438,22 @@ return true; } + private void removeAssumeInstructionsLinkedToEligibleInstance() { + for (Instruction user : eligibleInstance.aliasedUsers()) { + if (!user.isAssume()) { + continue; + } + Assume<?> assumeInstruction = user.asAssume(); + Value src = assumeInstruction.src(); + Value dest = assumeInstruction.outValue(); + assert dest.numberOfPhiUsers() == 0; + dest.replaceUsers(src); + removeInstruction(user); + } + // Verify that no more assume instructions are left as users. + assert eligibleInstance.aliasedUsers().stream().noneMatch(Instruction::isAssume); + } + // Remove miscellaneous users before handling field reads. private void removeMiscUsages(IRCode code) { boolean needToRemoveUnreachableBlocks = false; @@ -495,8 +530,8 @@ } } - private void replaceFieldRead(IRCode code, - InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) { + private void replaceFieldRead( + IRCode code, InstanceGet fieldRead, Map<DexField, FieldValueHelper> fieldHelpers) { Value value = fieldRead.outValue(); if (value != null) { FieldValueHelper helper = @@ -508,7 +543,10 @@ fieldValueHelper.replaceValue(value, newValue); } assert value.numberOfAllUsers() == 0; - new TypeAnalysis(appView).narrowing(newValue.affectedValues()); + // `newValue` could be a phi introduced by FieldValueHelper. Its initial type is set as the + // type of read field, but it could be more precise than that due to (multiple) inlining. + // Instead of values affected by `newValue`, it's necessary to begin with `newValue` itself. + new TypeAnalysis(appView).narrowing(ImmutableSet.of(newValue)); } removeInstruction(fieldRead); } @@ -539,7 +577,8 @@ assert isEligibleSingleTarget(singleTarget); // Must be a constructor called on the receiver. - if (invoke.inValues().lastIndexOf(eligibleInstance) != 0) { + if (ListUtils.lastIndexMatching( + invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) != 0) { return null; } @@ -594,7 +633,7 @@ : null; } - // An invoke is eligible for inlinining in the following cases: + // An invoke is eligible for inlining in the following cases: // // - if it does not return the receiver // - if there are no uses of the out value @@ -645,7 +684,8 @@ DexEncodedMethod singleTarget, Set<Instruction> indirectUsers) { assert isEligibleSingleTarget(singleTarget); - if (invoke.inValues().lastIndexOf(eligibleInstance) > 0) { + if (ListUtils.lastIndexMatching( + invoke.inValues(), v -> v.getAliasedValue() == eligibleInstance) > 0) { return null; // Instance passed as an argument. } return isEligibleVirtualMethodCall( @@ -714,7 +754,8 @@ return false; } if (invoke.isInvokeMethodWithReceiver() - && invoke.asInvokeMethodWithReceiver().getReceiver() == eligibleInstance) { + && invoke.asInvokeMethodWithReceiver().getReceiver().getAliasedValue() + == eligibleInstance) { return false; } if (invoke.isInvokeSuper()) { @@ -755,7 +796,7 @@ // If we got here with invocation on receiver the user is ineligible. if (invoke.isInvokeMethodWithReceiver()) { - if (arguments.get(0) == eligibleInstance) { + if (arguments.get(0).getAliasedValue() == eligibleInstance) { return false; } @@ -775,7 +816,7 @@ } for (int argIndex = 0; argIndex < arguments.size(); argIndex++) { - Value argument = arguments.get(argIndex); + Value argument = arguments.get(argIndex).getAliasedValue(); if (argument == eligibleInstance && optimizationInfo.getParameterUsages(argIndex).notUsed()) { // Reference can be removed since it's not used. unusedArguments.add(new Pair<>(invoke, argIndex)); @@ -796,7 +837,7 @@ Supplier<InliningOracle> defaultOracle) { // Go through all arguments, see if all usages of eligibleInstance are good. for (int argIndex = 0; argIndex < arguments.size(); argIndex++) { - Value argument = arguments.get(argIndex); + Value argument = arguments.get(argIndex).getAliasedValue(); if (argument != eligibleInstance) { continue; // Nothing to worry about. }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java index e77f932..1eb64de 100644 --- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java +++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -284,10 +284,9 @@ */ private final Set<DexEncodedMethod> pendingReflectiveUses = Sets.newLinkedHashSet(); - /** - * A cache for DexMethod that have been marked reachable. - */ - private final Set<DexMethod> virtualTargetsMarkedAsReachable = Sets.newIdentityHashSet(); + /** A cache for DexMethod that have been marked reachable. */ + private final Map<DexMethod, DexEncodedMethod> virtualTargetsMarkedAsReachable = + Maps.newIdentityHashMap(); /** * A set of references we have reported missing to dedupe warnings. @@ -596,9 +595,14 @@ private final DexEncodedMethod currentMethod; - private UseRegistry(DexItemFactory factory, DexEncodedMethod currentMethod) { + private UseRegistry(DexItemFactory factory, DexProgramClass holder, DexEncodedMethod method) { super(factory); - this.currentMethod = currentMethod; + assert holder.type == method.method.holder; + this.currentMethod = method; + } + + private KeepReasonWitness reportClassReferenced(DexProgramClass referencedClass) { + return graphReporter.reportClassReferencedFrom(referencedClass, currentMethod); } @Override @@ -744,10 +748,8 @@ // the field as live, if the holder is an interface. if (appView.options().enableUnusedInterfaceRemoval) { if (encodedField.field != field) { - markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, currentMethod)); - markTypeAsLive( - encodedField.field.type, - type -> graphReporter.reportClassReferencedFrom(type, currentMethod)); + markTypeAsLive(clazz, reportClassReferenced(clazz)); + markTypeAsLive(encodedField.field.type, this::reportClassReferenced); } } @@ -784,10 +786,8 @@ // the field as live, if the holder is an interface. if (appView.options().enableUnusedInterfaceRemoval) { if (encodedField.field != field) { - markTypeAsLive(clazz, graphReporter.reportClassReferencedFrom(clazz, currentMethod)); - markTypeAsLive( - encodedField.field.type, - type -> graphReporter.reportClassReferencedFrom(type, currentMethod)); + markTypeAsLive(clazz, reportClassReferenced(clazz)); + markTypeAsLive(encodedField.field.type, this::reportClassReferenced); } } @@ -923,7 +923,7 @@ @Override public boolean registerTypeReference(DexType type) { - markTypeAsLive(type, clazz -> graphReporter.reportClassReferencedFrom(clazz, currentMethod)); + markTypeAsLive(type, this::reportClassReferenced); return true; } @@ -1056,8 +1056,7 @@ markClassAsInstantiatedWithCompatRule(baseClass); } else { // This also handles reporting of missing classes. - markTypeAsLive( - baseType, clazz -> graphReporter.reportClassReferencedFrom(clazz, currentMethod)); + markTypeAsLive(baseType, this::reportClassReferenced); } return true; } @@ -1847,9 +1846,6 @@ boolean interfaceInvoke, KeepReason reason, BiPredicate<DexProgramClass, DexEncodedMethod> possibleTargetsFilter) { - if (!virtualTargetsMarkedAsReachable.add(method)) { - return; - } if (Log.ENABLED) { Log.verbose(getClass(), "Marking virtual method `%s` as reachable.", method); } @@ -1867,8 +1863,13 @@ return; } - DexEncodedMethod resolutionTarget = - findAndMarkResolutionTarget(method, interfaceInvoke, reason); + DexEncodedMethod resolutionTarget = virtualTargetsMarkedAsReachable.get(method); + if (resolutionTarget != null) { + registerMethod(resolutionTarget, reason); + return; + } + resolutionTarget = findAndMarkResolutionTarget(method, interfaceInvoke, reason); + virtualTargetsMarkedAsReachable.put(method, resolutionTarget); if (resolutionTarget == null || !resolutionTarget.isValidVirtualTarget(options)) { // There is no valid resolution, so any call will lead to a runtime exception. return; @@ -2514,7 +2515,7 @@ method.parameterAnnotationsList.forEachAnnotation( annotation -> processAnnotation(method, annotation)); } - method.registerCodeReferences(new UseRegistry(options.itemFactory, method)); + method.registerCodeReferences(new UseRegistry(options.itemFactory, clazz, method)); // Add all dependent members to the workqueue. enqueueRootItems(rootSet.getDependentItems(method));
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java index 7f16e07..487afa2 100644 --- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -256,6 +256,11 @@ if (appView.appInfo().neverMerge.contains(clazz.type)) { return MergeGroup.DONT_MERGE; } + if (appView.options().featureSplitConfiguration != null && + appView.options().featureSplitConfiguration.isInFeature(clazz)) { + // TODO(b/141452765): Allow class merging between classes in features. + return MergeGroup.DONT_MERGE; + } if (clazz.staticFields().size() + clazz.directMethods().size() + clazz.virtualMethods().size() == 0) { return MergeGroup.DONT_MERGE;
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 cd77544..b8b8482 100644 --- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java +++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -374,6 +374,12 @@ || appInfo.neverMerge.contains(clazz.type)) { return false; } + if (appView.options().featureSplitConfiguration != null && + appView.options().featureSplitConfiguration.isInFeature(clazz)) { + // TODO(b/141452765): Allow class merging between classes in features. + return false; + } + // Note that the property "singleSubtype == null" cannot change during merging, since we visit // classes in a top-down order. DexType singleSubtype = appInfo.getSingleSubtype(clazz.type);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index 2e45d5f..9c135c8 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -239,7 +239,6 @@ public boolean enableInitializedClassesInInstanceMethodsAnalysis = true; public boolean enableRedundantFieldLoadElimination = true; public boolean enableValuePropagation = true; - public boolean enableFieldTypePropagation = true; public boolean enableUninstantiatedTypeOptimization = true; // TODO(b/138917494): Disable until we have numbers on potential performance penalties. public boolean enableRedundantConstNumberOptimization = false;
diff --git a/src/main/java/com/android/tools/r8/utils/ListUtils.java b/src/main/java/com/android/tools/r8/utils/ListUtils.java index a9c3f98..370d5e2 100644 --- a/src/main/java/com/android/tools/r8/utils/ListUtils.java +++ b/src/main/java/com/android/tools/r8/utils/ListUtils.java
@@ -8,9 +8,19 @@ import java.util.Collection; import java.util.List; import java.util.function.Function; +import java.util.function.Predicate; public class ListUtils { + public static <T> int lastIndexMatching(List<T> list, Predicate<T> tester) { + for (int i = list.size() - 1; i >= 0; i--) { + if (tester.test(list.get(i))) { + return i; + } + } + return -1; + } + public static <S, T> List<T> map(Collection<S> list, Function<S, T> fn) { List<T> result = new ArrayList<>(list.size()); for (S element : list) {
diff --git a/src/test/desugaredLibraryConversions/conversions/TimeConversions.java b/src/test/desugaredLibraryConversions/conversions/TimeConversions.java new file mode 100644 index 0000000..b5adf3c --- /dev/null +++ b/src/test/desugaredLibraryConversions/conversions/TimeConversions.java
@@ -0,0 +1,114 @@ +// Copyright (c) 2019, 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 java.time; + +public class TimeConversions { + + public static j$.time.ZonedDateTime convert(java.time.ZonedDateTime dateTime) { + if (dateTime == null) { + return null; + } + return j$.time.ZonedDateTime.of( + dateTime.getYear(), + dateTime.getMonthValue(), + dateTime.getDayOfMonth(), + dateTime.getHour(), + dateTime.getMinute(), + dateTime.getSecond(), + dateTime.getNano(), + convert(dateTime.getZone())); + } + + public static java.time.ZonedDateTime convert(j$.time.ZonedDateTime dateTime) { + if (dateTime == null) { + return null; + } + return java.time.ZonedDateTime.of( + dateTime.getYear(), + dateTime.getMonthValue(), + dateTime.getDayOfMonth(), + dateTime.getHour(), + dateTime.getMinute(), + dateTime.getSecond(), + dateTime.getNano(), + convert(dateTime.getZone())); + } + + // ZoneId conversion works because in practice only two final classes are used. + // ZoneId is responsible for using one of the other final classes. + // This does not support custom implementations of ZoneId. + + public static j$.time.ZoneId convert(java.time.ZoneId zoneId) { + if (zoneId == null) { + return null; + } + return j$.time.ZoneId.of(zoneId.getId()); + } + + public static java.time.ZoneId convert(j$.time.ZoneId zoneId) { + if (zoneId == null) { + return null; + } + return java.time.ZoneId.of(zoneId.getId()); + } + + public static j$.time.MonthDay convert(java.time.MonthDay monthDay) { + if (monthDay == null) { + return null; + } + return j$.time.MonthDay.of(monthDay.getMonthValue(), monthDay.getDayOfMonth()); + } + + public static java.time.MonthDay convert(j$.time.MonthDay monthDay) { + if (monthDay == null) { + return null; + } + return java.time.MonthDay.of(monthDay.getMonthValue(), monthDay.getDayOfMonth()); + } + + public static j$.time.Instant convert(java.time.Instant instant) { + if (instant == null) { + return null; + } + return j$.time.Instant.ofEpochSecond(instant.getEpochSecond(), (long) instant.getNano()); + } + + public static java.time.Instant convert(j$.time.Instant instant) { + if (instant == null) { + return null; + } + return java.time.Instant.ofEpochSecond(instant.getEpochSecond(), (long) instant.getNano()); + } + + // Following conversions are hidden (Used by tests APIs only). + + public static j$.time.LocalDate convert(java.time.LocalDate date) { + if (date == null) { + return null; + } + return j$.time.LocalDate.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth()); + } + + public static java.time.LocalDate convert(j$.time.LocalDate date) { + if (date == null) { + return null; + } + return java.time.LocalDate.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth()); + } + + public static j$.time.Duration convert(java.time.Duration duration) { + if (duration == null) { + return null; + } + return j$.time.Duration.ofSeconds(duration.getSeconds(), duration.getNano()); + } + + public static java.time.Duration convert(j$.time.Duration duration) { + if (duration == null) { + return null; + } + return java.time.Duration.ofSeconds(duration.getSeconds(), duration.getNano()); + } +}
diff --git a/src/test/desugaredLibraryConversions/stubs/Clock.java b/src/test/desugaredLibraryConversions/stubs/Clock.java new file mode 100644 index 0000000..51f6e52 --- /dev/null +++ b/src/test/desugaredLibraryConversions/stubs/Clock.java
@@ -0,0 +1,7 @@ +// Copyright (c) 2019, 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 j$.time; + +public class Clock {}
diff --git a/src/test/desugaredLibraryConversions/stubs/Duration.java b/src/test/desugaredLibraryConversions/stubs/Duration.java new file mode 100644 index 0000000..e559a8f --- /dev/null +++ b/src/test/desugaredLibraryConversions/stubs/Duration.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2019, 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 j$.time; + +public class Duration { + public static Duration ofSeconds(long seconds, long nanoAdjustment) { + return null; + } + + public int getSeconds() { + return 0; + } + + public int getNano() { + return 0; + } +}
diff --git a/src/test/desugaredLibraryConversions/stubs/Instant.java b/src/test/desugaredLibraryConversions/stubs/Instant.java new file mode 100644 index 0000000..bc399ac --- /dev/null +++ b/src/test/desugaredLibraryConversions/stubs/Instant.java
@@ -0,0 +1,20 @@ +// Copyright (c) 2019, 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 j$.time; + +public class Instant { + + public static Instant ofEpochSecond(long seconds, long nanos) { + return null; + } + + public long getEpochSecond() { + return 0L; + } + + public int getNano() { + return 0; + } +}
diff --git a/src/test/desugaredLibraryConversions/stubs/LocalDate.java b/src/test/desugaredLibraryConversions/stubs/LocalDate.java new file mode 100644 index 0000000..ddbff3b --- /dev/null +++ b/src/test/desugaredLibraryConversions/stubs/LocalDate.java
@@ -0,0 +1,23 @@ +// Copyright (c) 2019, 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 j$.time; + +public class LocalDate { + public static LocalDate of(int year, int month, int dayOfMonth) { + return null; + } + + public int getYear() { + return 0; + } + + public int getMonthValue() { + return 0; + } + + public int getDayOfMonth() { + return 0; + } +}
diff --git a/src/test/desugaredLibraryConversions/stubs/MonthDay.java b/src/test/desugaredLibraryConversions/stubs/MonthDay.java new file mode 100644 index 0000000..25a0392 --- /dev/null +++ b/src/test/desugaredLibraryConversions/stubs/MonthDay.java
@@ -0,0 +1,19 @@ +// Copyright (c) 2019, 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 j$.time; + +public final class MonthDay { + public static MonthDay of(int monthValue, int dayValue) { + return null; + } + + public int getMonthValue() { + return 0; + } + + public int getDayOfMonth() { + return 0; + } +}
diff --git a/src/test/desugaredLibraryConversions/stubs/ZoneId.java b/src/test/desugaredLibraryConversions/stubs/ZoneId.java new file mode 100644 index 0000000..27f7230 --- /dev/null +++ b/src/test/desugaredLibraryConversions/stubs/ZoneId.java
@@ -0,0 +1,16 @@ +// Copyright (c) 2019, 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 j$.time; + +public final class ZoneId { + + public static ZoneId of(String id) { + return null; + } + + public String getId() { + return null; + } +}
diff --git a/src/test/desugaredLibraryConversions/stubs/ZonedDateTime.java b/src/test/desugaredLibraryConversions/stubs/ZonedDateTime.java new file mode 100644 index 0000000..ff02570 --- /dev/null +++ b/src/test/desugaredLibraryConversions/stubs/ZonedDateTime.java
@@ -0,0 +1,52 @@ +// Copyright (c) 2019, 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 j$.time; + +public final class ZonedDateTime { + + public static ZonedDateTime of( + int year, + int month, + int dayOfMonth, + int hour, + int minute, + int second, + int nanoOfSecond, + ZoneId zone) { + return null; + } + + public int getYear() { + return 0; + } + + public int getMonthValue() { + return 0; + } + + public int getDayOfMonth() { + return 0; + } + + public int getHour() { + return 0; + } + + public int getMinute() { + return 0; + } + + public int getSecond() { + return 0; + } + + public int getNano() { + return 0; + } + + public j$.time.ZoneId getZone() { + return null; + } +}
diff --git a/src/test/examplesJava9/backport/MapBackportJava9Main.java b/src/test/examplesJava9/backport/MapBackportJava9Main.java new file mode 100644 index 0000000..2aa5cfd --- /dev/null +++ b/src/test/examplesJava9/backport/MapBackportJava9Main.java
@@ -0,0 +1,247 @@ +// Copyright (c) 2019, 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 backport; + +import java.util.AbstractMap; +import java.util.Map; + +public class MapBackportJava9Main { + + public static void main(String[] args) { + testOf0(); + testOf1(); + testOf2(); + testOf10(); + testOfEntries(); + } + + private static void testOf0() { + Map<Object, Object> ofObject = Map.of(); + assertEquals(0, ofObject.size()); + assertEquals(null, ofObject.get(new Object())); + assertMutationNotAllowed(ofObject); + + Map<Integer, Integer> ofInteger = Map.of(); + assertEquals(0, ofInteger.size()); + assertEquals(null, ofInteger.get(0)); + } + + private static void testOf1() { + Object objectKey0 = new Object(); + Object objectValue0 = new Object(); + Map<Object, Object> ofObject = Map.of(objectKey0, objectValue0); + assertEquals(1, ofObject.size()); + assertSame(objectValue0, ofObject.get(objectKey0)); + assertEquals(null, ofObject.get(new Object())); + assertMutationNotAllowed(ofObject); + + Map<Integer, Integer> ofInteger = Map.of(0, 0); + assertEquals(1, ofInteger.size()); + assertEquals(0, ofInteger.get(0)); + assertEquals(null, ofInteger.get(1)); + + try { + Map.of((Object) null, 1); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + try { + Map.of(1, (Object) null); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + } + + private static void testOf2() { + Object objectKey0 = new Object(); + Object objectValue0 = new Object(); + Object objectKey1 = new Object(); + Object objectValue1 = new Object(); + Map<Object, Object> ofObject = Map.of(objectKey0, objectValue0, objectKey1, objectValue1); + assertEquals(2, ofObject.size()); + assertSame(objectValue0, ofObject.get(objectKey0)); + assertSame(objectValue1, ofObject.get(objectKey1)); + assertEquals(null, ofObject.get(new Object())); + assertMutationNotAllowed(ofObject); + + Map<Integer, Integer> ofInteger = Map.of(0, 0, 1, 1); + assertEquals(2, ofInteger.size()); + assertEquals(0, ofInteger.get(0)); + assertEquals(1, ofInteger.get(1)); + assertEquals(null, ofInteger.get(3)); + + Map<Object, Object> ofMixed = Map.of(objectKey0, 0, objectKey1, 1); + assertEquals(2, ofMixed.size()); + assertEquals(0, ofMixed.get(objectKey0)); + assertEquals(1, ofMixed.get(objectKey1)); + assertEquals(null, ofMixed.get(new Object())); + + try { + Map.of(1, 1, null, 2); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + try { + Map.of(1, 1, 2, null); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + + try { + Map.of(1, 1, 1, 2); + throw new AssertionError(); + } catch (IllegalArgumentException expected) { + assertEquals("duplicate key: 1", expected.getMessage()); + } + } + + private static void testOf10() { + Object objectKey0 = new Object(); + Object objectValue0 = new Object(); + Object objectKey6 = new Object(); + Object objectValue6 = new Object(); + Object objectKey9 = new Object(); + Object objectValue9 = new Object(); + Map<Object, Object> ofObject = + Map.of(objectKey0, objectValue0, new Object(), new Object(), new Object(), new Object(), + new Object(), new Object(), new Object(), new Object(), new Object(), new Object(), + objectKey6, objectValue6, new Object(), new Object(), new Object(), new Object(), + objectKey9, objectValue9); + assertEquals(10, ofObject.size()); + assertSame(objectValue0, ofObject.get(objectKey0)); + assertSame(objectValue6, ofObject.get(objectKey6)); + assertSame(objectValue9, ofObject.get(objectKey9)); + assertEquals(null, ofObject.get(new Object())); + assertMutationNotAllowed(ofObject); + + Map<Integer, Integer> ofInteger = + Map.of(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9); + assertEquals(10, ofInteger.size()); + assertEquals(0, ofInteger.get(0)); + assertEquals(6, ofInteger.get(6)); + assertEquals(9, ofInteger.get(9)); + assertEquals(null, ofInteger.get(10)); + + Map<Object, Object> ofMixed = + Map.of(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, objectKey9, objectValue9); + assertEquals(10, ofMixed.size()); + assertEquals(0, ofMixed.get(0)); + assertEquals(6, ofMixed.get(6)); + assertSame(objectValue9, ofMixed.get(objectKey9)); + assertEquals(null, ofMixed.get(9)); + + try { + Map.of(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, null, objectValue9); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + try { + Map.of(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, objectKey9, null); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + + try { + Map.of(0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 0, 9); + throw new AssertionError(); + } catch (IllegalArgumentException expected) { + assertEquals("duplicate key: 0", expected.getMessage()); + } + } + + private static void testOfEntries() { + Object objectKey0 = new Object(); + Object objectValue0 = new Object(); + Object objectKey1 = new Object(); + Object objectValue1 = new Object(); + Map<Object, Object> ofObject = Map.ofEntries( + new AbstractMap.SimpleEntry<>(objectKey0, objectValue0), + new AbstractMap.SimpleEntry<>(objectKey1, objectValue1)); + assertEquals(2, ofObject.size()); + assertSame(objectValue0, ofObject.get(objectKey0)); + assertSame(objectValue1, ofObject.get(objectKey1)); + assertEquals(null, ofObject.get(new Object())); + assertMutationNotAllowed(ofObject); + + Map<Integer, Integer> ofInteger = Map.ofEntries( + new AbstractMap.SimpleEntry<>(0, 0), + new AbstractMap.SimpleEntry<>(1, 1)); + assertEquals(2, ofInteger.size()); + assertEquals(0, ofInteger.get(0)); + assertEquals(1, ofInteger.get(1)); + assertEquals(null, ofInteger.get(2)); + + Map<Object, Object> ofMixed = Map.ofEntries( + new AbstractMap.SimpleEntry<>(0, objectValue0), + new AbstractMap.SimpleEntry<>(objectKey1, 1)); + assertEquals(2, ofMixed.size()); + assertSame(objectValue0, ofMixed.get(0)); + assertEquals(1, ofMixed.get(objectKey1)); + assertEquals(null, ofMixed.get(1)); + + // Ensure the supplied entry objects are not used directly since they are mutable. + Map.Entry<Object, Object> mutableEntry = + new AbstractMap.SimpleEntry<>(objectKey0, objectValue0); + Map<Object, Object> ofMutableEntry = Map.ofEntries(mutableEntry); + mutableEntry.setValue(objectValue1); + assertSame(objectValue0, ofMutableEntry.get(objectKey0)); + + // Ensure the supplied mutable array is not used directly since it is mutable. + @SuppressWarnings("unchecked") + Map.Entry<Object, Object>[] mutableArray = + new Map.Entry[] { new AbstractMap.SimpleEntry<>(objectKey0, objectValue0) }; + Map<Object, Object> ofArray = Map.ofEntries(mutableArray); + mutableArray[0] = new AbstractMap.SimpleEntry<>(objectKey1, objectValue1); + assertSame(objectValue0, ofArray.get(objectKey0)); + assertEquals(null, ofArray.get(objectKey1)); + + try { + Map.ofEntries(new AbstractMap.SimpleEntry<Object, Integer>(null, 1)); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + try { + Map.ofEntries(new AbstractMap.SimpleEntry<Object, Integer>(1, null)); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + + try { + Map.ofEntries( + new AbstractMap.SimpleEntry<>(0, objectValue0), + new AbstractMap.SimpleEntry<>(0, objectValue1)); + throw new AssertionError(); + } catch (IllegalArgumentException expected) { + assertEquals("duplicate key: 0", expected.getMessage()); + } + } + + private static void assertMutationNotAllowed(Map<Object, Object> ofObject) { + try { + ofObject.put(new Object(), new Object()); + throw new AssertionError(); + } catch (UnsupportedOperationException expected) { + } + for (Map.Entry<Object, Object> entry : ofObject.entrySet()) { + try { + entry.setValue(new Object()); + throw new AssertionError(); + } catch (UnsupportedOperationException expected) { + } + } + } + + private static void assertSame(Object expected, Object actual) { + if (expected != actual) { + throw new AssertionError("Expected <" + expected + "> but was <" + actual + ">"); + } + } + + private static void assertEquals(Object expected, Object actual) { + if (expected != actual && !expected.equals(actual)) { + throw new AssertionError("Expected <" + expected + "> but was <" + actual + ">"); + } + } +}
diff --git a/src/test/examplesJava9/backport/SetBackportJava9Main.java b/src/test/examplesJava9/backport/SetBackportJava9Main.java new file mode 100644 index 0000000..d824bd0 --- /dev/null +++ b/src/test/examplesJava9/backport/SetBackportJava9Main.java
@@ -0,0 +1,207 @@ +// Copyright (c) 2019, 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 backport; + +import java.util.Set; + +public class SetBackportJava9Main { + + public static void main(String[] args) { + testOf0(); + testOf1(); + testOf2(); + testOf10(); + testOfVarargs(); + } + + private static void testOf0() { + Set<Object> ofObject = Set.of(); + assertEquals(0, ofObject.size()); + assertFalse(ofObject.contains(new Object())); + assertMutationNotAllowed(ofObject); + + Set<Integer> ofInteger = Set.of(); + assertEquals(0, ofInteger.size()); + assertFalse(ofInteger.contains(0)); + } + + private static void testOf1() { + Object anObject = new Object(); + Set<Object> ofObject = Set.of(anObject); + assertEquals(1, ofObject.size()); + assertTrue(ofObject.contains(anObject)); + assertFalse(ofObject.contains(new Object())); + assertMutationNotAllowed(ofObject); + + Set<Integer> ofInteger = Set.of(1); + assertEquals(1, ofInteger.size()); + assertTrue(ofInteger.contains(1)); + assertFalse(ofInteger.contains(2)); + + try { + Set.of((Object) null); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + } + + private static void testOf2() { + Object anObject0 = new Object(); + Object anObject1 = new Object(); + Set<Object> ofObject = Set.of(anObject0, anObject1); + assertEquals(2, ofObject.size()); + assertTrue(ofObject.contains(anObject0)); + assertTrue(ofObject.contains(anObject1)); + assertFalse(ofObject.contains(new Object())); + assertMutationNotAllowed(ofObject); + + Set<Integer> ofInteger = Set.of(1, 2); + assertEquals(2, ofInteger.size()); + assertTrue(ofInteger.contains(1)); + assertTrue(ofInteger.contains(2)); + assertFalse(ofInteger.contains(3)); + + Set<Object> ofMixed = Set.of(anObject0, 1); + assertEquals(2, ofMixed.size()); + assertTrue(ofMixed.contains(anObject0)); + assertTrue(ofMixed.contains(1)); + assertFalse(ofMixed.contains(2)); + assertFalse(ofMixed.contains(anObject1)); + assertMutationNotAllowed(ofMixed); + + try { + Set.of(1, null); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + + try { + Set.of(1, 1); + throw new AssertionError(); + } catch (IllegalArgumentException expected) { + assertEquals("duplicate element: 1", expected.getMessage()); + } + } + + private static void testOf10() { + Object anObject0 = new Object(); + Object anObject6 = new Object(); + Object anObject9 = new Object(); + Set<Object> ofObject = + Set.of(anObject0, new Object(), new Object(), new Object(), new Object(), new Object(), + anObject6, new Object(), new Object(), anObject9); + assertEquals(10, ofObject.size()); + assertTrue(ofObject.contains(anObject0)); + assertTrue(ofObject.contains(anObject6)); + assertTrue(ofObject.contains(anObject9)); + assertFalse(ofObject.contains(new Object())); + assertMutationNotAllowed(ofObject); + + Set<Integer> ofInteger = Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); + assertEquals(10, ofInteger.size()); + assertTrue(ofInteger.contains(0)); + assertTrue(ofInteger.contains(6)); + assertTrue(ofInteger.contains(9)); + assertFalse(ofInteger.contains(10)); + + Set<Object> ofMixed = Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8, anObject9); + assertEquals(10, ofMixed.size()); + assertTrue(ofMixed.contains(0)); + assertTrue(ofMixed.contains(6)); + assertTrue(ofMixed.contains(anObject9)); + assertFalse(ofMixed.contains(anObject0)); + assertMutationNotAllowed(ofMixed); + + try { + Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8, null); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + + try { + Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 0); + throw new AssertionError(); + } catch (IllegalArgumentException expected) { + assertEquals("duplicate element: 0", expected.getMessage()); + } + } + + private static void testOfVarargs() { + Object anObject0 = new Object(); + Object anObject6 = new Object(); + Object anObject10 = new Object(); + Set<Object> ofObject = + Set.of(anObject0, new Object(), new Object(), new Object(), new Object(), new Object(), + anObject6, new Object(), new Object(), new Object(), anObject10); + assertEquals(11, ofObject.size()); + assertTrue(ofObject.contains(anObject0)); + assertTrue(ofObject.contains(anObject6)); + assertTrue(ofObject.contains(anObject10)); + assertFalse(ofObject.contains(new Object())); + assertMutationNotAllowed(ofObject); + + Set<Integer> ofInteger = Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + assertEquals(11, ofInteger.size()); + assertTrue(ofInteger.contains(0)); + assertTrue(ofInteger.contains(6)); + assertTrue(ofInteger.contains(10)); + assertFalse(ofInteger.contains(11)); + + Set<Object> ofMixed = Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, anObject10); + assertEquals(11, ofMixed.size()); + assertTrue(ofMixed.contains(0)); + assertTrue(ofMixed.contains(6)); + assertTrue(ofMixed.contains(anObject10)); + assertFalse(ofMixed.contains(10)); + assertFalse(ofMixed.contains(anObject0)); + assertMutationNotAllowed(ofMixed); + + // Ensure the supplied mutable array is not used directly since it is mutable. + Object[] mutableArray = { anObject0 }; + Set<Object> ofMutableArray = Set.of(mutableArray); + mutableArray[0] = anObject10; + assertTrue(ofMutableArray.contains(anObject0)); + assertFalse(ofMutableArray.contains(anObject10)); + + try { + Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, null); + throw new AssertionError(); + } catch (NullPointerException expected) { + } + + try { + Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0); + throw new AssertionError(); + } catch (IllegalArgumentException expected) { + assertEquals("duplicate element: 0", expected.getMessage()); + } + } + + private static void assertMutationNotAllowed(Set<Object> ofObject) { + try { + ofObject.add(new Object()); + throw new AssertionError(); + } catch (UnsupportedOperationException expected) { + } + } + + private static void assertTrue(boolean value) { + if (!value) { + throw new AssertionError("Expected <true> but was <false>"); + } + } + + private static void assertFalse(boolean value) { + if (value) { + throw new AssertionError("Expected <false> but was <true>"); + } + } + + private static void assertEquals(Object expected, Object actual) { + if (expected != actual && !expected.equals(actual)) { + throw new AssertionError("Expected <" + expected + "> but was <" + actual + ">"); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java index 6cd2bdf..fee1235 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -93,7 +93,7 @@ .assertSuccess(); } else { testForD8() - .setMinApi(parameters.getRuntime()) + .setMinApi(parameters.getApiLevel()) .apply(this::configureProgram) .compile() .run(parameters.getRuntime(), testClassName) @@ -117,7 +117,7 @@ .filter(is -> !ignoredInvokes.contains(is.getMethod().name.toString())) .collect(toList()); - AndroidApiLevel apiLevel = parameters.getRuntime().asDex().getMinApiLevel(); + AndroidApiLevel apiLevel = parameters.getApiLevel(); long expectedTargetInvokes = invokeStaticCounts.ceilingEntry(apiLevel).getValue(); long actualTargetInvokes = javaInvokeStatics.size(); assertEquals("Expected "
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/BooleanBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/BooleanBackportTest.java index edc9e53..3e4b973 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/BooleanBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/BooleanBackportTest.java
@@ -14,7 +14,9 @@ public final class BooleanBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public BooleanBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportJava9Test.java index fd03159..28eefe3 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportJava9Test.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportJava9Test.java
@@ -22,6 +22,7 @@ return getTestParameters() .withCfRuntimesStartingFromIncluding(CfVm.JDK9) .withDexRuntimes() + .withAllApiLevels() .build(); }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java index 82d143c..13a4564 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/ByteBackportTest.java
@@ -14,7 +14,9 @@ public final class ByteBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public ByteBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/CharacterBackportJava11Test.java b/src/test/java/com/android/tools/r8/desugar/backports/CharacterBackportJava11Test.java index 1374fcc..f1d51fc 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/CharacterBackportJava11Test.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/CharacterBackportJava11Test.java
@@ -21,6 +21,7 @@ public static Iterable<?> data() { return getTestParameters() .withDexRuntimes() + .withAllApiLevels() .withCfRuntimesStartingFromIncluding(CfVm.JDK11) .build(); }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/CharacterBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/CharacterBackportTest.java index 4e1f069..e4eef82 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/CharacterBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/CharacterBackportTest.java
@@ -14,7 +14,9 @@ public final class CharacterBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public CharacterBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/CollectionsBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/CollectionsBackportTest.java index 25c3721..66af3d0 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/CollectionsBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/CollectionsBackportTest.java
@@ -19,7 +19,9 @@ public final class CollectionsBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public CollectionsBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/DoubleBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/DoubleBackportTest.java index 2f6da00..16cdeda 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/DoubleBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/DoubleBackportTest.java
@@ -14,7 +14,9 @@ public final class DoubleBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public DoubleBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/FloatBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/FloatBackportTest.java index d8c1f8d..92db3a6 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/FloatBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/FloatBackportTest.java
@@ -14,7 +14,9 @@ public final class FloatBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public FloatBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportTest.java index 706cf36..ed697a7 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/IntegerBackportTest.java
@@ -15,7 +15,9 @@ public final class IntegerBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public IntegerBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java index b512634..a861d1f 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/ListBackportJava9Test.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.ToolHelper; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; @@ -22,6 +23,7 @@ return getTestParameters() .withCfRuntimesStartingFromIncluding(CfVm.JDK9) .withDexRuntimes() + .withAllApiLevels() .build(); } @@ -29,7 +31,13 @@ Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION); public ListBackportJava9Test(TestParameters parameters) { - super(parameters, Byte.class, TEST_JAR, "backport.ListBackportJava9Main"); - // TODO Once shipped in an actual API level, migrate to ByteBackportTest + super(parameters, List.class, TEST_JAR, "backport.ListBackportJava9Main"); + // TODO Once shipped in an actual API level, migrate to ListBackportTest + + // Available since API 1 and used to test created lists. + ignoreInvokes("add"); + ignoreInvokes("get"); + ignoreInvokes("set"); + ignoreInvokes("size"); } }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportSingleMethodTest.java b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportSingleMethodTest.java index 4ddc18c..7344bd8 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportSingleMethodTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportSingleMethodTest.java
@@ -20,7 +20,9 @@ @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withDexRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public LongBackportSingleMethodTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportTest.java index ba8a638..f8a6ad8 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/LongBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/LongBackportTest.java
@@ -15,7 +15,9 @@ public final class LongBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public LongBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java new file mode 100644 index 0000000..06e41f8 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/backports/MapBackportJava9Test.java
@@ -0,0 +1,43 @@ +// Copyright (c) 2019, 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.desugar.backports; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; + +@RunWith(Parameterized.class) +public class MapBackportJava9Test extends AbstractBackportTest { + @Parameters(name = "{0}") + public static Iterable<?> data() { + return getTestParameters() + .withCfRuntimesStartingFromIncluding(CfVm.JDK9) + .withDexRuntimes() + .withAllApiLevels() + .build(); + } + + private static final Path TEST_JAR = + Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION); + + public MapBackportJava9Test(TestParameters parameters) { + super(parameters, Map.class, TEST_JAR, "backport.MapBackportJava9Main"); + // TODO Once shipped in an actual API level, migrate to MapBackportTest + + // Available since API 1 and used to test created maps. + ignoreInvokes("entrySet"); + ignoreInvokes("get"); + ignoreInvokes("put"); + ignoreInvokes("size"); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava9Test.java index d822d5e..439ef38 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava9Test.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/MathBackportJava9Test.java
@@ -21,6 +21,7 @@ public static Iterable<?> data() { return getTestParameters() .withDexRuntimes() + .withAllApiLevels() .withCfRuntimesStartingFromIncluding(CfVm.JDK9) .build(); }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/MathBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/MathBackportTest.java index cbde5a7..acae4c9 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/MathBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/MathBackportTest.java
@@ -14,7 +14,9 @@ public final class MathBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public MathBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java index da50daf..7544e87 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportJava9Test.java
@@ -21,6 +21,7 @@ public static Iterable<?> data() { return getTestParameters() .withDexRuntimes() + .withAllApiLevels() .withCfRuntimesStartingFromIncluding(CfVm.JDK9) .build(); }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportTest.java index 74aec20..3120a95 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/ObjectsBackportTest.java
@@ -15,9 +15,13 @@ import static java.util.Collections.reverseOrder; -@RunWith(Parameterized.class) public final class ObjectsBackportTest extends AbstractBackportTest { - @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); +@RunWith(Parameterized.class) +public final class ObjectsBackportTest extends AbstractBackportTest { + @Parameters(name = "{0}") + public static Iterable<?> data() { + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public ObjectsBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/OptionalBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/OptionalBackportJava9Test.java index e6105d3..d77052a 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/OptionalBackportJava9Test.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/OptionalBackportJava9Test.java
@@ -10,6 +10,7 @@ import com.android.tools.r8.TestRuntime.CfVm; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.utils.AndroidApiLevel; import java.nio.file.Path; import java.nio.file.Paths; import org.junit.runner.RunWith; @@ -22,6 +23,7 @@ public static Iterable<?> data() { return getTestParameters() .withDexRuntimesStartingFromIncluding(Version.V7_0_0) + .withApiLevelsStartingAtIncluding(AndroidApiLevel.N) .withCfRuntimesStartingFromIncluding(CfVm.JDK9) .build(); }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java new file mode 100644 index 0000000..b248e0b --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/backports/SetBackportJava9Test.java
@@ -0,0 +1,42 @@ +// Copyright (c) 2019, 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.desugar.backports; + +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Set; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION; + +@RunWith(Parameterized.class) +public class SetBackportJava9Test extends AbstractBackportTest { + @Parameters(name = "{0}") + public static Iterable<?> data() { + return getTestParameters() + .withCfRuntimesStartingFromIncluding(CfVm.JDK9) + .withDexRuntimes() + .withAllApiLevels() + .build(); + } + + private static final Path TEST_JAR = + Paths.get(ToolHelper.EXAMPLES_JAVA9_BUILD_DIR).resolve("backport" + JAR_EXTENSION); + + public SetBackportJava9Test(TestParameters parameters) { + super(parameters, Set.class, TEST_JAR, "backport.SetBackportJava9Main"); + // TODO Once shipped in an actual API level, migrate to SetBackportTest + + // Available since API 1 and used to test created sets. + ignoreInvokes("add"); + ignoreInvokes("contains"); + ignoreInvokes("size"); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportJava9Test.java index eb2a078..d56ef5b 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportJava9Test.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportJava9Test.java
@@ -22,6 +22,7 @@ return getTestParameters() .withCfRuntimesStartingFromIncluding(CfVm.JDK9) .withDexRuntimes() + .withAllApiLevels() .build(); }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportTest.java index dd6e702..2d1a61a 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/ShortBackportTest.java
@@ -14,7 +14,9 @@ public final class ShortBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public ShortBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportJava9Test.java b/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportJava9Test.java index e7486b9..43cbf48 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportJava9Test.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportJava9Test.java
@@ -21,6 +21,7 @@ public static Iterable<?> data() { return getTestParameters() .withDexRuntimes() + .withAllApiLevels() .withCfRuntimesStartingFromIncluding(CfVm.JDK9) .build(); }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportTest.java index 915a6fb..1926975 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/StrictMathBackportTest.java
@@ -14,7 +14,9 @@ public final class StrictMathBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public StrictMathBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StringBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/StringBackportTest.java index 52610db..6ac9189 100644 --- a/src/test/java/com/android/tools/r8/desugar/backports/StringBackportTest.java +++ b/src/test/java/com/android/tools/r8/desugar/backports/StringBackportTest.java
@@ -18,7 +18,9 @@ public final class StringBackportTest extends AbstractBackportTest { @Parameters(name = "{0}") public static Iterable<?> data() { - return getTestParameters().withAllRuntimes().build(); + return getTestParameters() + .withAllRuntimesAndApiLevels() + .build(); } public StringBackportTest(TestParameters parameters) {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/CoreLibDesugarTestBase.java b/src/test/java/com/android/tools/r8/desugar/corelib/CoreLibDesugarTestBase.java index 7543fc5..8741c08 100644 --- a/src/test/java/com/android/tools/r8/desugar/corelib/CoreLibDesugarTestBase.java +++ b/src/test/java/com/android/tools/r8/desugar/corelib/CoreLibDesugarTestBase.java
@@ -20,6 +20,8 @@ import com.android.tools.r8.origin.Origin; import com.android.tools.r8.utils.AndroidApiLevel; import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.List; @@ -103,6 +105,13 @@ } } + protected static Path[] getAllFilesWithSuffixInDirectory(Path directory, String suffix) + throws IOException { + return Files.walk(directory) + .filter(path -> path.toString().endsWith(suffix)) + .toArray(Path[]::new); + } + protected KeepRuleConsumer createKeepRuleConsumer(TestParameters parameters) { if (requiresAnyCoreLibDesugaring(parameters)) { return new PresentKeepRuleConsumer();
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/InconsistentPrefixTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/InconsistentPrefixTest.java index 167374a..843551e 100644 --- a/src/test/java/com/android/tools/r8/desugar/corelib/InconsistentPrefixTest.java +++ b/src/test/java/com/android/tools/r8/desugar/corelib/InconsistentPrefixTest.java
@@ -11,6 +11,7 @@ import com.android.tools.r8.TestBase; import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; import com.android.tools.r8.jasmin.JasminBuilder; +import com.android.tools.r8.utils.AndroidApiLevel; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.nio.file.Path; @@ -39,11 +40,13 @@ options -> options.desugaredLibraryConfiguration = new DesugaredLibraryConfiguration( + AndroidApiLevel.B, false, x, ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), + ImmutableMap.of(), ImmutableList.of())) .compile(); fail("Should have raised the compilation error.");
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/KeepRuleShrinkTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/KeepRuleShrinkTest.java index 30b62a0..843e154 100644 --- a/src/test/java/com/android/tools/r8/desugar/corelib/KeepRuleShrinkTest.java +++ b/src/test/java/com/android/tools/r8/desugar/corelib/KeepRuleShrinkTest.java
@@ -57,6 +57,7 @@ static class Executor { + @SuppressWarnings("unchecked") public static void main(String[] args) { Map<String, String>[] maps = new Map[] {
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java new file mode 100644 index 0000000..87a6140 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/corelib/LintFilesTest.java
@@ -0,0 +1,94 @@ +// Copyright (c) 2019, 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.desugar.corelib; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.GenerateLintFiles; +import com.android.tools.r8.StringResource; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.graph.DexItemFactory; +import com.android.tools.r8.ir.desugar.DesugaredLibraryConfiguration; +import com.android.tools.r8.ir.desugar.DesugaredLibraryConfigurationParser; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.FileUtils; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Reporter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.Assume; +import org.junit.Test; + +public class LintFilesTest extends TestBase { + + private void checkFileContent(AndroidApiLevel minApiLevel, Path lintFile) throws Exception { + // Just do some light probing in the generated lint files. + List<String> methods = FileUtils.readAllLines(lintFile); + assertTrue(methods.contains("java/util/List/spliterator()Ljava/util/Spliterator;")); + assertTrue(methods.contains("java/util/Optional/empty()Ljava/util/Optional;")); + assertTrue(methods.contains("java/util/OptionalInt/empty()Ljava/util/OptionalInt;")); + assertEquals( + minApiLevel == AndroidApiLevel.L, + methods.contains("java/util/Collection/parallelStream()Ljava/util/stream/Stream;")); + assertEquals( + minApiLevel == AndroidApiLevel.L, + methods.contains( + "java/util/stream/DoubleStream/parallel()Ljava/util/stream/DoubleStream;")); + assertEquals( + minApiLevel == AndroidApiLevel.L, + methods.contains("java/util/stream/IntStream/parallel()Ljava/util/stream/IntStream;")); + } + + @Test + public void testFileContent() throws Exception { + // Test require r8.jar not r8lib.jar, as the class com.android.tools.r8.GenerateLintFiles in + // not kept. + Assume.assumeTrue(!ToolHelper.isTestingR8Lib()); + + Path directory = temp.newFolder().toPath(); + GenerateLintFiles.main( + new String[] {ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING.toString(), directory.toString()}); + InternalOptions options = new InternalOptions(new DexItemFactory(), new Reporter()); + DesugaredLibraryConfiguration desugaredLibraryConfiguration = + new DesugaredLibraryConfigurationParser( + options.itemFactory, options.reporter, false, AndroidApiLevel.B.getLevel()) + .parse(StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING)); + + for (AndroidApiLevel apiLevel : AndroidApiLevel.values()) { + if (apiLevel == AndroidApiLevel.R) { + // Skip API level 30 for now. + continue; + } + + if (apiLevel.getLevel() + >= desugaredLibraryConfiguration.getRequiredCompilationApiLevel().getLevel()) { + Path compileApiLevelDirectory = + directory.resolve("compile_api_level_" + apiLevel.getLevel()); + assertTrue(Files.exists(compileApiLevelDirectory)); + for (AndroidApiLevel minApiLevel : AndroidApiLevel.values()) { + String desugaredApisBaseName = + "desugared_apis_" + apiLevel.getLevel() + "_" + minApiLevel.getLevel(); + if (minApiLevel == AndroidApiLevel.L || minApiLevel == AndroidApiLevel.B) { + assertTrue( + Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"))); + assertTrue( + Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar"))); + checkFileContent( + minApiLevel, compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt")); + } else { + assertFalse( + Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".txt"))); + assertFalse( + Files.exists(compileApiLevelDirectory.resolve(desugaredApisBaseName + ".jar"))); + } + } + } + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/APIConversionTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java similarity index 95% rename from src/test/java/com/android/tools/r8/desugar/corelib/APIConversionTest.java rename to src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java index 167ecf7..7a68a55 100644 --- a/src/test/java/com/android/tools/r8/desugar/corelib/APIConversionTest.java +++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTest.java
@@ -2,7 +2,7 @@ // 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.desugar.corelib; +package com.android.tools.r8.desugar.corelib.conversionTests; import static org.hamcrest.CoreMatchers.endsWith; import static org.hamcrest.core.StringContains.containsString; @@ -10,6 +10,7 @@ import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.desugar.corelib.CoreLibDesugarTestBase; import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.StringUtils; import java.util.Arrays;
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTestBase.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTestBase.java new file mode 100644 index 0000000..eca2a47 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/APIConversionTestBase.java
@@ -0,0 +1,59 @@ +// Copyright (c) 2019, 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.desugar.corelib.conversionTests; + +import com.android.tools.r8.TestRuntime.CfVm; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.desugar.corelib.CoreLibDesugarTestBase; +import com.android.tools.r8.utils.AndroidApiLevel; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Collections; + +public class APIConversionTestBase extends CoreLibDesugarTestBase { + + private static final Path CONVERSION_FOLDER = Paths.get("src/test/desugaredLibraryConversions"); + + public Path[] getTimeConversionClasses() throws IOException { + File conversionFolder = temp.newFolder("conversions"); + File stubsFolder = temp.newFolder("stubs"); + + // Compile the stubs to be able to compile the conversions. + ToolHelper.runJavac( + CfVm.JDK8, + null, + stubsFolder.toPath(), + getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("stubs/"), "java")); + + // Compile the conversions using the stubs. + ArrayList<Path> classPath = new ArrayList<>(); + classPath.add(stubsFolder.toPath()); + ToolHelper.runJavac( + CfVm.JDK8, + classPath, + conversionFolder.toPath(), + getAllFilesWithSuffixInDirectory(CONVERSION_FOLDER.resolve("conversions"), "java")); + + Path[] classes = getAllFilesWithSuffixInDirectory(conversionFolder.toPath(), "class"); + assert classes.length > 0 + : "Something went wrong during compilation, check the runJavac return value for debugging."; + return classes; + } + + protected Path buildDesugaredLibraryWithConversionExtension(AndroidApiLevel apiLevel) { + Path[] timeConversionClasses = null; + try { + timeConversionClasses = getTimeConversionClasses(); + } catch (IOException e) { + throw new RuntimeException(e); + } + ArrayList<Path> paths = new ArrayList<>(); + Collections.addAll(paths, timeConversionClasses); + return buildDesugaredLibrary(apiLevel, "", false, paths); + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java new file mode 100644 index 0000000..2cdf6e7 --- /dev/null +++ b/src/test/java/com/android/tools/r8/desugar/corelib/conversionTests/TimeConversionCompilationTest.java
@@ -0,0 +1,135 @@ +// Copyright (c) 2019, 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.desugar.corelib.conversionTests; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.L8Command; +import com.android.tools.r8.OutputMode; +import com.android.tools.r8.StringResource; +import com.android.tools.r8.TestDiagnosticMessagesImpl; +import com.android.tools.r8.TestRuntime.DexRuntime; +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.ToolHelper.DexVm; +import com.android.tools.r8.utils.AndroidApiLevel; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import java.nio.file.Path; +import java.time.ZoneId; +import java.util.TimeZone; +import org.junit.Test; + +public class TimeConversionCompilationTest extends APIConversionTestBase { + + @Test + public void testTimeGeneratedDex() throws Exception { + TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl(); + Path desugaredLib = temp.newFolder().toPath().resolve("conversion_dex.zip"); + L8Command.Builder l8Builder = + L8Command.builder(diagnosticsHandler) + .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P)) + .addProgramFiles(getTimeConversionClasses()) + .addDesugaredLibraryConfiguration( + StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING)) + .setMinApiLevel(AndroidApiLevel.B.getLevel()) + .setOutput(desugaredLib, OutputMode.DexIndexed); + ToolHelper.runL8(l8Builder.build(), x -> {}); + this.checkTimeConversionGeneratedDex(new CodeInspector(desugaredLib)); + } + + private void checkTimeConversionGeneratedDex(CodeInspector inspector) { + ClassSubject clazz = inspector.clazz("j$.time.TimeConversions"); + assertThat(clazz, isPresent()); + assertEquals(13, clazz.allMethods().size()); + } + + @Test + public void testRewrittenAPICalls() throws Exception { + testForD8() + .setMinApi(AndroidApiLevel.B) + .addInnerClasses(TimeConversionCompilationTest.class) + .enableCoreLibraryDesugaring(AndroidApiLevel.B) + .compile() + .inspect(this::checkAPIRewritten) + .addDesugaredCoreLibraryRunClassPath( + this::buildDesugaredLibraryWithConversionExtension, AndroidApiLevel.B) + .run(new DexRuntime(DexVm.ART_9_0_0_HOST), Executor.class); + } + + private void checkAPIRewritten(CodeInspector inspector) { + MethodSubject mainMethod = inspector.clazz(Executor.class).uniqueMethodWithName("main"); + // Check the API calls are not using j$ types. + assertTrue( + mainMethod + .streamInstructions() + .anyMatch( + instr -> + instr.isInvokeStatic() + && instr.getMethod().name.toString().equals("getTimeZone") + && instr + .getMethod() + .proto + .parameters + .values[0] + .toString() + .equals("java.time.ZoneId"))); + assertTrue( + mainMethod + .streamInstructions() + .anyMatch( + instr -> + instr.isInvokeVirtual() + && instr.getMethod().name.toString().equals("toZoneId") + && instr + .getMethod() + .proto + .returnType + .toString() + .equals("java.time.ZoneId"))); + // Check the conversion instructions are present. + assertTrue( + mainMethod + .streamInstructions() + .anyMatch( + instr -> + instr.isInvokeStatic() + && instr.getMethod().name.toString().equals("convert") + && instr + .getMethod() + .proto + .parameters + .values[0] + .toString() + .equals("j$.time.ZoneId"))); + assertTrue( + mainMethod + .streamInstructions() + .anyMatch( + instr -> + instr.isInvokeStatic() + && instr.getMethod().name.toString().equals("convert") + && instr + .getMethod() + .proto + .parameters + .values[0] + .toString() + .equals("java.time.ZoneId"))); + } + + static class Executor { + public static void main(String[] args) { + ZoneId zoneId = ZoneId.systemDefault(); + // Following is a call where java.time.ZoneId is a parameter type (getTimeZone()). + TimeZone timeZone = TimeZone.getTimeZone(zoneId); + // Following is a call where java.time.ZoneId is a return type (toZoneId()). + System.out.println(timeZone.toZoneId().getId()); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11CoreLibTestBase.java b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11CoreLibTestBase.java index c106bec..4be9077 100644 --- a/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11CoreLibTestBase.java +++ b/src/test/java/com/android/tools/r8/desugar/corelib/corelibjdktests/Jdk11CoreLibTestBase.java
@@ -53,13 +53,6 @@ .toArray(Path[]::new); } - protected static Path[] getAllFilesWithSuffixInDirectory(Path directory, String suffix) - throws IOException { - return Files.walk(directory) - .filter(path -> path.toString().endsWith(suffix)) - .toArray(Path[]::new); - } - protected static Path getSafeVarArgsFile() { return ANDROID_SAFE_VAR_ARGS_LOCATION; }
diff --git a/src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json b/src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json index 6b8dc10..baa351d 100644 --- a/src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json +++ b/src/test/java/com/android/tools/r8/desugar/corelib/desugar_jdk_libs.json
@@ -1,11 +1,12 @@ { "configuration_format_version": 1, - "version": "0.2.0", + "version": "0.3.0", "required_compilation_api_level": 26, "library_flags": [ { "api_level_below_or_equal": 25, "rewrite_prefix": { + "j$.time.": "java.time.", "java.time.": "j$.time.", "java.util.Desugar": "j$.util.Desugar" }, @@ -71,6 +72,14 @@ "java.util.Date#toInstant": "java.util.DesugarDate", "java.util.GregorianCalendar#from": "java.util.DesugarGregorianCalendar", "java.util.GregorianCalendar#toZonedDateTime": "java.util.DesugarGregorianCalendar" + }, + "custom_conversion": { + "java.time.ZonedDateTime": "j$.time.TimeConversions", + "java.time.LocalDate": "j$.time.TimeConversions", + "java.time.Duration": "j$.time.TimeConversions", + "java.time.ZoneId": "j$.time.TimeConversions", + "java.time.MonthDay": "j$.time.TimeConversions", + "java.time.Instant": "j$.time.TimeConversions" } }, {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java index 9ea52d0..25cbe9a 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterMergeRegression.java
@@ -4,9 +4,13 @@ package com.android.tools.r8.dexsplitter; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; +import com.android.tools.r8.CompilationFailedException; +import com.android.tools.r8.FeatureSplit; import com.android.tools.r8.NeverInline; import com.android.tools.r8.NeverMerge; import com.android.tools.r8.R8FullTestBuilder; @@ -77,6 +81,26 @@ assertTrue(processResult.stderr.contains("NoClassDefFoundError")); } + @Test + public void testOnR8Splitter() throws IOException, CompilationFailedException, + ExecutionException { + assumeTrue(parameters.isDexRuntime()); + Consumer<R8FullTestBuilder> configurator = + r8FullTestBuilder -> r8FullTestBuilder.enableMergeAnnotations().noMinification(); + ProcessResult processResult = + testR8Splitter( + parameters, + ImmutableSet.of(BaseClass.class, BaseWithStatic.class), + ImmutableSet.of(FeatureClass.class, AFeatureWithStatic.class), + FeatureClass.class, + EXPECTED, + a -> true, + configurator); + + assertEquals(processResult.exitCode, 0); + assertTrue(processResult.stdout.equals(StringUtils.lines("42", "foobar"))); + } + @NeverMerge public static class BaseClass implements RunInterface {
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java index 04d3b14..5622177 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/R8FeatureSplitTest.java
@@ -8,15 +8,19 @@ import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.DexIndexedConsumer; +import com.android.tools.r8.ExtractMarker; import com.android.tools.r8.FeatureSplit; +import com.android.tools.r8.ResourceException; import com.android.tools.r8.TestParameters; import com.android.tools.r8.TestParametersCollection; import com.android.tools.r8.ToolHelper.ProcessResult; +import com.android.tools.r8.dex.Marker; import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Path; +import java.util.Collection; import java.util.concurrent.ExecutionException; import org.junit.Test; import org.junit.runner.RunWith; @@ -60,21 +64,10 @@ @Test public void testTwoFeatures() throws CompilationFailedException, IOException, ExecutionException { - Path basePath = temp.newFile("base.zip").toPath(); - Path feature1Path = temp.newFile("feature1.zip").toPath(); - Path feature2Path = temp.newFile("feature2.zip").toPath(); - - testForR8(parameters.getBackend()) - .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class) - .setMinApi(parameters.getRuntime()) - .addFeatureSplit( - builder -> simpleSplitProvider(builder, feature1Path, temp, FeatureClass.class)) - .addFeatureSplit( - builder -> simpleSplitProvider(builder, feature2Path, temp, FeatureClass2.class)) - .addKeepAllClassesRule() - .compile() - .writeToZip(basePath); - + CompiledWithFeature compiledWithFeature = new CompiledWithFeature().invoke(); + Path basePath = compiledWithFeature.getBasePath(); + Path feature1Path = compiledWithFeature.getFeature1Path(); + Path feature2Path = compiledWithFeature.getFeature2Path(); CodeInspector baseInspector = new CodeInspector(basePath); assertTrue(baseInspector.clazz(BaseClass.class).isPresent()); @@ -100,6 +93,24 @@ assertEquals(result.stdout, StringUtils.lines("Testing second")); } + @Test + public void testMarkerInFeatures() + throws IOException, CompilationFailedException, ExecutionException, ResourceException { + CompiledWithFeature compiledWithFeature = new CompiledWithFeature().invoke(); + Path basePath = compiledWithFeature.getBasePath(); + Path feature1Path = compiledWithFeature.getFeature1Path(); + Path feature2Path = compiledWithFeature.getFeature2Path(); + Collection<Marker> markers = ExtractMarker.extractMarkerFromDexFile(basePath); + Collection<Marker> feature1Markers = ExtractMarker.extractMarkerFromDexFile(feature1Path); + Collection<Marker> feature2Markers = ExtractMarker.extractMarkerFromDexFile(feature2Path); + + assertEquals(markers.size(), 1); + assertEquals(feature1Markers.size(), 1); + assertEquals(feature2Markers.size(), 1); + assertEquals(markers.iterator().next(), feature1Markers.iterator().next()); + assertEquals(markers.iterator().next(), feature2Markers.iterator().next()); + } + public static class HelloWorld { public static void main(String[] args) { System.out.println("Hello world"); @@ -139,4 +150,41 @@ test(); } } + + private class CompiledWithFeature { + + private Path basePath; + private Path feature1Path; + private Path feature2Path; + + public Path getBasePath() { + return basePath; + } + + public Path getFeature1Path() { + return feature1Path; + } + + public Path getFeature2Path() { + return feature2Path; + } + + public CompiledWithFeature invoke() throws IOException, CompilationFailedException { + basePath = temp.newFile("base.zip").toPath(); + feature1Path = temp.newFile("feature1.zip").toPath(); + feature2Path = temp.newFile("feature2.zip").toPath(); + + testForR8(parameters.getBackend()) + .addProgramClasses(BaseClass.class, RunInterface.class, SplitRunner.class) + .setMinApi(parameters.getRuntime()) + .addFeatureSplit( + builder -> simpleSplitProvider(builder, feature1Path, temp, FeatureClass.class)) + .addFeatureSplit( + builder -> simpleSplitProvider(builder, feature2Path, temp, FeatureClass2.class)) + .addKeepAllClassesRule() + .compile() + .writeToZip(basePath); + return this; + } + } }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java index 9d8c12b..b8154cc 100644 --- a/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java +++ b/src/test/java/com/android/tools/r8/dexsplitter/SplitterTestBase.java
@@ -1,6 +1,5 @@ package com.android.tools.r8.dexsplitter; -import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; import static org.junit.Assume.assumeTrue; @@ -66,7 +65,6 @@ ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) { - assertEquals(classNames.size(), descriptors.size()); for (String descriptor : descriptors) { assertTrue(classNames.contains(DescriptorUtils.descriptorToJavaType(descriptor))); }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/CollectionMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/CollectionMethods.java new file mode 100644 index 0000000..0dd1a53 --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/CollectionMethods.java
@@ -0,0 +1,47 @@ +// Copyright (c) 2019, 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.desugar.backports; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +public class CollectionMethods { + + public static <E> List<E> listOfArray(E[] elements) { + ArrayList<E> list = new ArrayList<>(elements.length); + for (E element : elements) { + list.add(Objects.requireNonNull(element)); + } + return Collections.unmodifiableList(list); + } + + public static <E> Set<E> setOfArray(E[] elements) { + HashSet<E> set = new HashSet<>(elements.length); + for (E element : elements) { + if (!set.add(Objects.requireNonNull(element))) { + throw new IllegalArgumentException("duplicate element: " + element); + } + } + return Collections.unmodifiableSet(set); + } + + public static <K, V> Map<K, V> mapOfEntries(Map.Entry<K, V>[] elements) { + HashMap<K, V> map = new HashMap<>(elements.length); + for (Map.Entry<K, V> element : elements) { + K key = Objects.requireNonNull(element.getKey()); + V value = Objects.requireNonNull(element.getValue()); + if (map.put(key, value) != null) { + throw new IllegalArgumentException("duplicate key: " + key); + } + } + return Collections.unmodifiableMap(map); + } +}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java index 2bb7dc3..e210196 100644 --- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java +++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -29,6 +29,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -73,11 +74,11 @@ ByteMethods.class, CharacterMethods.class, CloseResourceMethod.class, + CollectionMethods.class, CollectionsMethods.class, DoubleMethods.class, FloatMethods.class, IntegerMethods.class, - ListMethods.class, LongMethods.class, MathMethods.class, ObjectsMethods.class, @@ -99,7 +100,7 @@ @Test public void test() throws Exception { ArrayList<Class<?>> sorted = new ArrayList<>(methodTemplateClasses); - sorted.sort((a, b) -> a.getTypeName().compareTo(b.getTypeName())); + sorted.sort(Comparator.comparing(Class::getTypeName)); assertEquals("Classes should be listed in sorted order", sorted, methodTemplateClasses); assertEquals( FileUtils.readTextFile(backportMethodsFile, StandardCharsets.UTF_8),
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/ListMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/ListMethods.java deleted file mode 100644 index 7d74919..0000000 --- a/src/test/java/com/android/tools/r8/ir/desugar/backports/ListMethods.java +++ /dev/null
@@ -1,23 +0,0 @@ -// Copyright (c) 2019, 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.desugar.backports; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; - -public class ListMethods { - - public static <E> List<E> ofArray(E[] elements) { - // TODO(140709356): The other overloads should call into this method to ensure consistent - // behavior, but we cannot link against List.of(E[]) because it's only available in Java 9. - ArrayList<E> list = new ArrayList<>(elements.length); - for (E element : elements) { - list.add(Objects.requireNonNull(element)); - } - return Collections.unmodifiableList(list); - } -}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java new file mode 100644 index 0000000..eabd3cc --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/optimize/outliner/OutlinesWithNonNullTest.java
@@ -0,0 +1,186 @@ +// Copyright (c) 2019, 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.optimize.outliner; + +import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +import com.android.tools.r8.NeverClassInline; +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.utils.InternalOptions.OutlineOptions; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.codeinspector.ClassSubject; +import com.android.tools.r8.utils.codeinspector.CodeInspector; +import com.android.tools.r8.utils.codeinspector.CodeMatchers; +import com.android.tools.r8.utils.codeinspector.MethodSubject; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class OutlinesWithNonNullTest extends TestBase { + private static final String JVM_OUTPUT = StringUtils.lines( + "42", + "arg", + "42", + "arg" + ); + + private final TestParameters parameters; + + @Parameterized.Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withAllRuntimes().build(); + } + + public OutlinesWithNonNullTest(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void testNonNullOnOneSide() throws Exception { + testForR8(parameters.getBackend()) + .enableClassInliningAnnotations() + .enableInliningAnnotations() + .addProgramClasses(TestArg.class, TestClassWithNonNullOnOneSide.class) + .addKeepMainRule(TestClassWithNonNullOnOneSide.class) + .setMinApi(parameters.getRuntime()) + .allowAccessModification() + .noMinification() + .addOptionsModification( + options -> { + options.outline.threshold = 2; + options.outline.minSize = 2; + }) + .compile() + .inspect(inspector -> validateOutlining(inspector, TestClassWithNonNullOnOneSide.class)) + .run(parameters.getRuntime(), TestClassWithNonNullOnOneSide.class) + .assertSuccessWithOutput(JVM_OUTPUT); + } + + @Test + public void testNonNullOnBothSides() throws Exception { + testForR8(parameters.getBackend()) + .enableClassInliningAnnotations() + .enableInliningAnnotations() + .addProgramClasses(TestArg.class, TestClassWithNonNullOnBothSides.class) + .addKeepMainRule(TestClassWithNonNullOnBothSides.class) + .setMinApi(parameters.getRuntime()) + .allowAccessModification() + .noMinification() + .addOptionsModification( + options -> { + options.outline.threshold = 2; + options.outline.minSize = 2; + }) + .compile() + .inspect(inspector -> validateOutlining(inspector, TestClassWithNonNullOnBothSides.class)) + .run(parameters.getRuntime(), TestClassWithNonNullOnBothSides.class) + .assertSuccessWithOutput(JVM_OUTPUT); + } + + private void validateOutlining(CodeInspector inspector, Class<?> main) { + ClassSubject outlineClass = inspector.clazz(OutlineOptions.CLASS_NAME); + assertThat(outlineClass, isPresent()); + MethodSubject outlineMethod = outlineClass.uniqueMethodWithName("outline0"); + assertThat(outlineMethod, isPresent()); + + ClassSubject argClass = inspector.clazz(TestArg.class); + assertThat(argClass, isPresent()); + MethodSubject printHash = argClass.uniqueMethodWithName("printHash"); + assertThat(printHash, isPresent()); + MethodSubject printArg= argClass.uniqueMethodWithName("printArg"); + assertThat(printArg, isPresent()); + + ClassSubject classSubject = inspector.clazz(main); + assertThat(classSubject, isPresent()); + MethodSubject method1 = classSubject.uniqueMethodWithName("method1"); + assertThat(method1, isPresent()); + assertThat(method1, CodeMatchers.invokesMethod(outlineMethod)); + assertThat(method1, not(CodeMatchers.invokesMethod(printHash))); + assertThat(method1, not(CodeMatchers.invokesMethod(printArg))); + MethodSubject method2 = classSubject.uniqueMethodWithName("method2"); + assertThat(method2, isPresent()); + assertThat(method2, CodeMatchers.invokesMethod(outlineMethod)); + assertThat(method2, not(CodeMatchers.invokesMethod(printHash))); + assertThat(method2, not(CodeMatchers.invokesMethod(printArg))); + } + + @NeverClassInline + public static class TestArg { + @Override + public int hashCode() { + return 42; + } + + @Override + public String toString() { + return "arg"; + } + + @NeverInline + static void printHash(Object arg) { + if (arg == null) { + throw new NullPointerException(); + } + System.out.println(arg.hashCode()); + // This method guarantees that, at the normal exit, argument is not null. + } + + @NeverInline + static void printArg(Object arg) { + System.out.println(arg); + } + } + + static class TestClassWithNonNullOnOneSide { + @NeverInline + static void method1(Object arg) { + TestArg.printHash(arg); + // We will have non-null aliasing here. + TestArg.printArg(arg); + } + + @NeverInline + static void method2(Object arg) { + if (arg != null) { + // We will have non-null aliasing here. + TestArg.printHash(arg); + TestArg.printArg(arg); + } + } + + public static void main(String... args) { + TestArg arg = new TestArg(); + method1(arg); + method2(arg); + } + } + + static class TestClassWithNonNullOnBothSides { + @NeverInline + static void method1(Object arg) { + TestArg.printHash(arg); + // We will have non-null aliasing here. + TestArg.printArg(arg); + } + + @NeverInline + static void method2(Object arg) { + TestArg.printHash(arg); + // We will have non-null aliasing here. + TestArg.printArg(arg); + } + + public static void main(String... args) { + TestArg arg = new TestArg(); + method1(arg); + method2(arg); + } + } +}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java index 6c87416..3353fd2 100644 --- a/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java +++ b/src/test/java/com/android/tools/r8/memberrebinding/b135627418/B135627418.java
@@ -16,6 +16,7 @@ import com.android.tools.r8.memberrebinding.b135627418.library.Drawable; import com.android.tools.r8.memberrebinding.b135627418.library.DrawableWrapper; import com.android.tools.r8.memberrebinding.b135627418.library.InsetDrawable; +import com.android.tools.r8.utils.AndroidApiLevel; import com.android.tools.r8.utils.codeinspector.ClassSubject; import com.android.tools.r8.utils.codeinspector.CodeInspector; import com.android.tools.r8.utils.codeinspector.InstructionSubject; @@ -79,11 +80,13 @@ options -> options.desugaredLibraryConfiguration = new DesugaredLibraryConfiguration( + AndroidApiLevel.B, false, ImmutableMap.of(packageName + ".runtime", packageName + ".library"), ImmutableMap.of(), ImmutableMap.of(), ImmutableMap.of(), + ImmutableMap.of(), ImmutableList.of())) .compile();
diff --git a/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java new file mode 100644 index 0000000..8a67d39 --- /dev/null +++ b/src/test/java/com/android/tools/r8/shaking/keptgraph/KeptByTwoMethods.java
@@ -0,0 +1,95 @@ +// Copyright (c) 2019, 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.shaking.keptgraph; + +import static com.android.tools.r8.references.Reference.methodFromMethod; + +import com.android.tools.r8.NeverInline; +import com.android.tools.r8.NeverMerge; +import com.android.tools.r8.TestBase; +import com.android.tools.r8.TestParameters; +import com.android.tools.r8.TestParametersCollection; +import com.android.tools.r8.references.MethodReference; +import com.android.tools.r8.utils.StringUtils; +import com.android.tools.r8.utils.graphinspector.GraphInspector; +import com.android.tools.r8.utils.graphinspector.GraphInspector.QueryNode; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class KeptByTwoMethods extends TestBase { + + @NeverMerge + public static class A { + + @NeverInline + void foo() { + System.out.println("A.foo!"); + } + } + + @NeverMerge + public static class B extends A { + // Intermediate to A. + } + + public static class TestClass { + + @NeverInline + static void bar(B b) { + b.foo(); + } + + @NeverInline + static void baz(B b) { + b.foo(); + } + + public static void main(String[] args) { + bar(new B()); + baz(new B()); + } + } + + private static final String EXPECTED = StringUtils.lines("A.foo!", "A.foo!"); + + @Parameters(name = "{0}") + public static TestParametersCollection data() { + return getTestParameters().withCfRuntimes().build(); + } + + private final TestParameters parameters; + + public KeptByTwoMethods(TestParameters parameters) { + this.parameters = parameters; + } + + @Test + public void test() throws Exception { + GraphInspector inspector = + testForR8(parameters.getBackend()) + .enableGraphInspector() + .enableMergeAnnotations() + .enableInliningAnnotations() + .addKeepMainRule(TestClass.class) + .addProgramClasses(TestClass.class, A.class, B.class) + .run(parameters.getRuntime(), TestClass.class) + .assertSuccessWithOutput(EXPECTED) + .graphInspector(); + + MethodReference barRef = methodFromMethod(TestClass.class.getDeclaredMethod("bar", B.class)); + inspector.method(barRef).assertPresent(); + + MethodReference bazRef = methodFromMethod(TestClass.class.getDeclaredMethod("baz", B.class)); + inspector.method(bazRef).assertPresent(); + + QueryNode foo = + inspector.method(methodFromMethod(A.class.getDeclaredMethod("foo"))).assertPresent(); + + foo.assertInvokedFrom(barRef); + foo.assertInvokedFrom(bazRef); + } +}
diff --git a/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java b/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java new file mode 100644 index 0000000..3a63555 --- /dev/null +++ b/src/test/java/com/android/tools/r8/utils/ListUtilsTest.java
@@ -0,0 +1,50 @@ +// Copyright (c) 2019, 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.utils; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import org.junit.Test; + +public class ListUtilsTest { + + private List<Integer> createInputData(int size) { + List<Integer> input = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + input.add(i); + } + return input; + } + + @Test + public void lastIndexOf_outOfRange() { + List<Integer> input = createInputData(3); + Predicate<Integer> tester = x -> x * x == -1; + assertEquals(-1, ListUtils.lastIndexMatching(input, tester)); + } + + @Test + public void lastIndexOf_first() { + List<Integer> input = createInputData(3); + Predicate<Integer> tester = x -> x * x == 0; + assertEquals(0, ListUtils.lastIndexMatching(input, tester)); + } + + @Test + public void lastIndexOf_middle() { + List<Integer> input = createInputData(4); + Predicate<Integer> tester = x -> x * x == 4; + assertEquals(2, ListUtils.lastIndexMatching(input, tester)); + } + + @Test + public void lastIndexOf_last() { + List<Integer> input = createInputData(2); + Predicate<Integer> tester = x -> x * x == 1; + assertEquals(1, ListUtils.lastIndexMatching(input, tester)); + } +}
diff --git a/tools/archive.py b/tools/archive.py index 1041b40..62e0e27 100755 --- a/tools/archive.py +++ b/tools/archive.py
@@ -126,9 +126,6 @@ create_maven_release.generate_r8_maven_zip(utils.MAVEN_ZIP) create_maven_release.generate_r8_maven_zip( utils.MAVEN_ZIP_LIB, is_r8lib=True) - # Create maven release of the desuage_jdk_libs configuration. - create_maven_release.generate_desugar_configuration_maven_zip( - utils.DESUGAR_CONFIGURATION_MAVEN_ZIP) # Generate and copy a full build without dependencies. gradle.RunGradleExcludeDeps([utils.R8, utils.R8_SRC]) @@ -149,6 +146,12 @@ utils.COMPATPROGUARDLIB, '-Pno_internal' ]) + + # Create maven release of the desuage_jdk_libs configuration. This require + # an r8.jar with dependencies to have been built. + create_maven_release.generate_desugar_configuration_maven_zip( + utils.DESUGAR_CONFIGURATION_MAVEN_ZIP) + version = GetVersion() is_master = IsMaster(version) if is_master:
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py index b4343f3..30c14c8 100755 --- a/tools/create_maven_release.py +++ b/tools/create_maven_release.py
@@ -6,6 +6,7 @@ import argparse import gradle import hashlib +import jdk import json from os import makedirs from os.path import join @@ -338,6 +339,19 @@ configuration_dir = join(tmp_dir, 'META-INF', 'desugar', 'd8') makedirs(configuration_dir) copyfile(configuration, join(configuration_dir, 'desugar.json')) + + lint_dir = join(configuration_dir, 'lint') + makedirs(lint_dir) + cmd = [ + jdk.GetJavaExecutable(), + '-cp', + utils.R8_JAR, + 'com.android.tools.r8.GenerateLintFiles', + configuration, + lint_dir] + utils.PrintCmd(cmd) + subprocess.check_call(cmd) + make_archive(destination, 'zip', tmp_dir) move(destination + '.zip', destination)
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py index f647f4b..89d0da2 100755 --- a/tools/run_on_as_app.py +++ b/tools/run_on_as_app.py
@@ -64,6 +64,8 @@ # For running on Golem all third-party repositories are bundled as an x20- # dependency and then copied to WORKING_DIR. To update the app-bundle use # 'run_on_as_app_x20_packager.py'. +# For showing benchmark data, also include the app in appSegmentBenchmarks in +# the file <golem_repo>/config/r8/benchmarks.dart. APP_REPOSITORIES = [ # ... # Repo({ @@ -314,7 +316,8 @@ 'module': 'sample/android', 'archives_base_name': 'android', 'min_sdk': 14, - 'compile_sdk': 28 + 'compile_sdk': 28, + 'skip': True }) ] }),