Merge "Add an option for output of the main dex list"
diff --git a/build.gradle b/build.gradle
index 0a512f2..967923c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,7 +1,8 @@
// Copyright (c) 2016, 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.
-import org.gradle.internal.os.OperatingSystem;
+import org.gradle.internal.os.OperatingSystem
+import utils.Utils;
apply plugin: 'java'
apply plugin: 'idea'
@@ -984,7 +985,7 @@
def artTestDir = file("${androidCheckoutDir}/art/test")
def artRunTestScript = file("${artTestDir}/run-test")
def dxExecutable = new File("tools/linux/dx/bin/dx");
- def dexMergerExecutable = new File("tools/linux/dx/bin/dexmerger");
+ def dexMergerExecutable = Utils.dexMergerExecutable()
def dexToolName = dexTool == DexTool.DX ? "dx" : "jack"
def name = dir.getName();
diff --git a/buildSrc/src/main/java/dx/DexMerger.java b/buildSrc/src/main/java/dx/DexMerger.java
index 39a4bfb..d3d8427 100644
--- a/buildSrc/src/main/java/dx/DexMerger.java
+++ b/buildSrc/src/main/java/dx/DexMerger.java
@@ -53,7 +53,7 @@
public void execute(ExecSpec execSpec) {
try {
if (dexMergerExecutable == null) {
- dexMergerExecutable = new File("tools/" + Utils.toolsDir() + "/dx/bin/dexmerger");
+ dexMergerExecutable = Utils.dexMergerExecutable();
}
execSpec.setExecutable(dexMergerExecutable);
execSpec.args(destination.getCanonicalPath());
diff --git a/buildSrc/src/main/java/utils/Utils.java b/buildSrc/src/main/java/utils/Utils.java
index 4cbc90c..7158e80 100644
--- a/buildSrc/src/main/java/utils/Utils.java
+++ b/buildSrc/src/main/java/utils/Utils.java
@@ -3,6 +3,8 @@
// BSD-style license that can be found in the LICENSE file.
package utils;
+import java.io.File;
+
public class Utils {
public static String toolsDir() {
String osName = System.getProperty("os.name");
@@ -14,4 +16,9 @@
return "linux";
}
}
-}
+
+ public static File dexMergerExecutable() {
+ String executableName = Utils.toolsDir().equals("windows") ? "dexmerger.bat" : "dexmerger";
+ return new File("tools/" + Utils.toolsDir() + "/dx/bin/" + executableName);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/r8/ResourceProvider.java b/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
similarity index 73%
rename from src/main/java/com/android/tools/r8/ResourceProvider.java
rename to src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
index bed36e3..7dbb420 100644
--- a/src/main/java/com/android/tools/r8/ResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/ClassFileResourceProvider.java
@@ -3,17 +3,18 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8;
+import java.util.Set;
+
/**
- * Represents a provider for application resources. All resources returned
- * via this provider should be class file resources, other resource kinds
- * are not yet supported.
+ * Represents a provider for application resources of class file kind.
*
* Note that the classes will only be created for resources provided by
* resource providers on-demand when they are needed by the tool. If
* never needed, the resource will never be loaded.
*/
-public interface ResourceProvider {
- // TODO: Consider adding support for DEX resources.
+public interface ClassFileResourceProvider {
+ /** Returns all class descriptors. */
+ Set<String> getClassDescriptors();
/**
* Get the class resource associated with the descriptor, or null if
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index ceefac5..c06438f 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OffOrAuto;
import com.android.tools.r8.utils.OutputMode;
-import com.android.tools.r8.utils.PreloadedResourceProvider;
+import com.android.tools.r8.utils.PreloadedClassFileProvider;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.file.Path;
@@ -53,7 +53,7 @@
public Builder addClasspathFiles(Collection<Path> files) throws IOException {
for (Path file : files) {
if (isArchive(file)) {
- addClasspathResourceProvider(PreloadedResourceProvider.fromArchive(file));
+ addClasspathResourceProvider(PreloadedClassFileProvider.fromArchive(file));
} else {
super.addClasspathFiles(file);
}
@@ -70,7 +70,7 @@
public Builder addLibraryFiles(Collection<Path> files) throws IOException {
for (Path file : files) {
if (isArchive(file)) {
- addLibraryResourceProvider(PreloadedResourceProvider.fromArchive(file));
+ addLibraryResourceProvider(PreloadedClassFileProvider.fromArchive(file));
} else {
super.addLibraryFiles(file);
}
@@ -78,12 +78,12 @@
return this;
}
- public Builder addClasspathResourceProvider(ResourceProvider provider) {
+ public Builder addClasspathResourceProvider(ClassFileResourceProvider provider) {
getAppBuilder().addClasspathResourceProvider(provider);
return this;
}
- public Builder addLibraryResourceProvider(ResourceProvider provider) {
+ public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
getAppBuilder().addLibraryResourceProvider(provider);
return this;
}
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 8b57155..7dd2d33 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -39,7 +39,6 @@
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.AttributeRemovalOptions;
-import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.PackageDistribution;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -375,12 +374,12 @@
*/
public static AndroidApp run(R8Command command)
throws IOException, CompilationException, ProguardRuleParserException {
- InternalOptions options = command.getInternalOptions();
- AndroidApp outputApp =
- runForTesting(command.getInputApp(), options).androidApp;
- writeOutputs(command, options, outputApp);
-
- return outputApp;
+ ExecutorService executorService = ThreadUtils.getExecutorService(command.getInternalOptions());
+ try {
+ return run(command, executorService);
+ } finally {
+ executorService.shutdown();
+ }
}
static void writeOutputs(R8Command command, InternalOptions options, AndroidApp outputApp)
@@ -445,11 +444,10 @@
*/
public static AndroidApp run(R8Command command, ExecutorService executor)
throws IOException, CompilationException, ProguardRuleParserException {
+ InternalOptions options = command.getInternalOptions();
AndroidApp outputApp =
- runForTesting(command.getInputApp(), command.getInternalOptions(), executor).androidApp;
- if (command.getOutputPath() != null) {
- outputApp.write(command.getOutputPath(), OutputMode.Indexed);
- }
+ runForTesting(command.getInputApp(), options, executor).androidApp;
+ writeOutputs(command, options, outputApp);
return outputApp;
}
diff --git a/src/main/java/com/android/tools/r8/bisect/BisectState.java b/src/main/java/com/android/tools/r8/bisect/BisectState.java
index 183e513..148c167 100644
--- a/src/main/java/com/android/tools/r8/bisect/BisectState.java
+++ b/src/main/java/com/android/tools/r8/bisect/BisectState.java
@@ -8,8 +8,6 @@
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplication.Builder;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.naming.NamingLens;
@@ -24,7 +22,6 @@
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -261,23 +258,20 @@
System.out.println("Next bisection range: " + nextRange);
int goodClasses = 0;
int badClasses = 0;
- Map<DexType, DexClass> classes = new HashMap<>();
- for (DexLibraryClass clazz : badApp.libraryClasses()) {
- classes.put(clazz.type, clazz);
- }
+ List<DexProgramClass> programClasses = new ArrayList<>();
for (DexProgramClass clazz : badApp.classes()) {
DexProgramClass goodClass = getGoodClass(clazz);
if (goodClass != null) {
- classes.put(goodClass.type, goodClass);
+ programClasses.add(goodClass);
++goodClasses;
} else {
- classes.put(clazz.type, clazz);
+ programClasses.add(clazz);
assert !nextRange.isEmpty();
++badClasses;
}
}
System.out.println("Class split is good: " + goodClasses + ", bad: " + badClasses);
- return new Builder(badApp, classes).build();
+ return new Builder(badApp).replaceProgramClasses(programClasses).build();
}
private DexProgramClass getGoodClass(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
index e7bf9db..c7a66c2 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationReader.java
@@ -8,20 +8,29 @@
import static com.android.tools.r8.dex.Constants.ANDROID_O_API;
import static com.android.tools.r8.dex.Constants.ANDROID_O_DEX_VERSION;
import static com.android.tools.r8.dex.Constants.DEFAULT_ANDROID_API;
+import static com.android.tools.r8.graph.ClassKind.CLASSPATH;
+import static com.android.tools.r8.graph.ClassKind.LIBRARY;
+import static com.android.tools.r8.graph.ClassKind.PROGRAM;
import static com.android.tools.r8.utils.FileUtils.DEFAULT_DEX_FILENAME;
+import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.Resource;
-import com.android.tools.r8.ResourceProvider;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.ClassKind;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClasspathClass;
import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.graph.JarClassFileReader;
import com.android.tools.r8.naming.ProguardMapReader;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.ClassProvider;
+import com.android.tools.r8.utils.ClasspathClassCollection;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.LazyClassCollection;
+import com.android.tools.r8.utils.LibraryClassCollection;
import com.android.tools.r8.utils.MainDexList;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
@@ -30,6 +39,8 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -64,82 +75,25 @@
final DexApplication.Builder builder = new DexApplication.Builder(itemFactory, timing);
try (Closer closer = Closer.create()) {
List<Future<?>> futures = new ArrayList<>();
+ // Still preload some of the classes, primarily for two reasons:
+ // (a) class lazy loading is not supported for DEX files
+ // now and current implementation of parallel DEX file
+ // loading will be lost with on-demand class loading.
+ // (b) some of the class file resources don't provide information
+ // about class descriptor.
+ // TODO: try and preload less classes.
readProguardMap(builder, executorService, futures, closer);
readMainDexList(builder, executorService, futures, closer);
- readDexSources(builder, executorService, futures, closer);
- readClassSources(builder, closer);
- initializeLazyClassCollection(builder);
+ ClassReader classReader = new ClassReader(executorService, futures, closer);
+ classReader.readSources();
ThreadUtils.awaitFutures(futures);
+ classReader.initializeLazyClassCollection(builder);
} finally {
timing.end();
}
return builder.build();
}
- private void readClassSources(DexApplication.Builder builder, Closer closer)
- throws IOException, ExecutionException {
- JarApplicationReader application = new JarApplicationReader(options);
- JarClassFileReader reader = new JarClassFileReader(
- application, builder::addClassIgnoringLibraryDuplicates);
- for (Resource input : inputApp.getClassProgramResources()) {
- reader.read(DEFAULT_DEX_FILENAME, ClassKind.PROGRAM, input.getStream(closer));
- }
- for (Resource input : inputApp.getClassClasspathResources()) {
- reader.read(DEFAULT_DEX_FILENAME, ClassKind.CLASSPATH, input.getStream(closer));
- }
- for (Resource input : inputApp.getClassLibraryResources()) {
- reader.read(DEFAULT_DEX_FILENAME, ClassKind.LIBRARY, input.getStream(closer));
- }
- }
-
- private void initializeLazyClassCollection(DexApplication.Builder builder) {
- List<ResourceProvider> classpathProviders = inputApp.getClasspathResourceProviders();
- List<ResourceProvider> libraryProviders = inputApp.getLibraryResourceProviders();
- if (!classpathProviders.isEmpty() || !libraryProviders.isEmpty()) {
- builder.setLazyClassCollection(new LazyClassCollection(
- new JarApplicationReader(options), classpathProviders, libraryProviders));
- }
- }
-
- private void readDexSources(DexApplication.Builder builder, ExecutorService executorService,
- List<Future<?>> futures, Closer closer)
- throws IOException, ExecutionException {
- List<Resource> dexProgramSources = inputApp.getDexProgramResources();
- List<Resource> dexClasspathSources = inputApp.getDexClasspathResources();
- List<Resource> dexLibrarySources = inputApp.getDexLibraryResources();
- int numberOfFiles = dexProgramSources.size()
- + dexLibrarySources.size() + dexClasspathSources.size();
- if (numberOfFiles > 0) {
- List<DexFileReader> fileReaders = new ArrayList<>(numberOfFiles);
- int computedMinApiLevel = options.minApiLevel;
- for (Resource input : dexProgramSources) {
- DexFile file = new DexFile(input.getStream(closer));
- computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
- fileReaders.add(new DexFileReader(file, ClassKind.PROGRAM, itemFactory));
- }
- for (Resource input : dexClasspathSources) {
- DexFile file = new DexFile(input.getStream(closer));
- fileReaders.add(new DexFileReader(file, ClassKind.CLASSPATH, itemFactory));
- }
- for (Resource input : dexLibrarySources) {
- DexFile file = new DexFile(input.getStream(closer));
- computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
- fileReaders.add(new DexFileReader(file, ClassKind.LIBRARY, itemFactory));
- }
- options.minApiLevel = computedMinApiLevel;
- for (DexFileReader reader : fileReaders) {
- DexFileReader.populateIndexTables(reader);
- }
- // Read the DexCode items and DexProgramClass items in parallel.
- for (DexFileReader reader : fileReaders) {
- futures.add(executorService.submit(() -> {
- reader.addCodeItemsTo(); // Depends on Everything for parsing.
- reader.addClassDefsTo(builder::addClass); // Depends on Methods, Code items etc.
- }));
- }
- }
- }
-
private int verifyOrComputeMinApiLevel(int computedMinApiLevel, DexFile file) {
int version = file.getDexVersion();
if (options.minApiLevel == DEFAULT_ANDROID_API) {
@@ -203,4 +157,111 @@
}));
}
}
+
+ private final class ClassReader {
+ private final ExecutorService executorService;
+ private final List<Future<?>> futures;
+ private final Closer closer;
+
+ // We use concurrent queues to collect classes
+ // since the classes can be collected concurrently.
+ private final Queue<DexProgramClass> programClasses = new ConcurrentLinkedQueue<>();
+ private final Queue<DexClasspathClass> classpathClasses = new ConcurrentLinkedQueue<>();
+ private final Queue<DexLibraryClass> libraryClasses = new ConcurrentLinkedQueue<>();
+ // Jar application reader to share across all class readers.
+ private final JarApplicationReader application = new JarApplicationReader(options);
+
+ ClassReader(ExecutorService executorService, List<Future<?>> futures, Closer closer) {
+ this.executorService = executorService;
+ this.futures = futures;
+ this.closer = closer;
+ }
+
+ private <T extends DexClass> void readDexSources(List<Resource> dexSources,
+ ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
+ if (dexSources.size() > 0) {
+ List<DexFileReader> fileReaders = new ArrayList<>(dexSources.size());
+ int computedMinApiLevel = options.minApiLevel;
+ for (Resource input : dexSources) {
+ DexFile file = new DexFile(input.getStream(closer));
+ computedMinApiLevel = verifyOrComputeMinApiLevel(computedMinApiLevel, file);
+ fileReaders.add(new DexFileReader(file, classKind, itemFactory));
+ }
+ options.minApiLevel = computedMinApiLevel;
+ for (DexFileReader reader : fileReaders) {
+ DexFileReader.populateIndexTables(reader);
+ }
+ // Read the DexCode items and DexProgramClass items in parallel.
+ for (DexFileReader reader : fileReaders) {
+ futures.add(executorService.submit(() -> {
+ reader.addCodeItemsTo(); // Depends on Everything for parsing.
+ reader.addClassDefsTo(
+ classKind.bridgeConsumer(classes::add)); // Depends on Methods, Code items etc.
+ }));
+ }
+ }
+ }
+
+ private <T extends DexClass> void readClassSources(List<Resource> classSources,
+ ClassKind classKind, Queue<T> classes) throws IOException, ExecutionException {
+ JarClassFileReader reader = new JarClassFileReader(
+ application, classKind.bridgeConsumer(classes::add));
+ for (Resource input : classSources) {
+ reader.read(DEFAULT_DEX_FILENAME, classKind, input.getStream(closer));
+ }
+ }
+
+ void readSources() throws IOException, ExecutionException {
+ readDexSources(inputApp.getDexProgramResources(), PROGRAM, programClasses);
+ readDexSources(inputApp.getDexClasspathResources(), CLASSPATH, classpathClasses);
+ readDexSources(inputApp.getDexLibraryResources(), LIBRARY, libraryClasses);
+ readClassSources(inputApp.getClassProgramResources(), PROGRAM, programClasses);
+ readClassSources(inputApp.getClassClasspathResources(), CLASSPATH, classpathClasses);
+ readClassSources(inputApp.getClassLibraryResources(), LIBRARY, libraryClasses);
+ }
+
+ private <T extends DexClass> ClassProvider<T> buildClassProvider(ClassKind classKind,
+ Queue<T> preloadedClasses, List<ClassFileResourceProvider> resourceProviders,
+ JarApplicationReader reader) {
+ List<ClassProvider<T>> providers = new ArrayList<>();
+
+ // Preloaded classes.
+ if (!preloadedClasses.isEmpty()) {
+ providers.add(ClassProvider.forPreloadedClasses(classKind, preloadedClasses));
+ }
+
+ // Class file resource providers.
+ for (ClassFileResourceProvider provider : resourceProviders) {
+ providers.add(ClassProvider.forClassFileResources(classKind, provider, reader));
+ }
+
+ // Combine if needed.
+ if (providers.isEmpty()) {
+ return null;
+ }
+ return providers.size() == 1 ? providers.get(0)
+ : ClassProvider.combine(classKind, providers);
+ }
+
+ void initializeLazyClassCollection(DexApplication.Builder builder) {
+ // Add all program classes to the builder.
+ for (DexProgramClass clazz : programClasses) {
+ builder.addProgramClass(clazz.asProgramClass());
+ }
+
+ // Create classpath class collection if needed.
+ ClassProvider<DexClasspathClass> classpathClassProvider = buildClassProvider(CLASSPATH,
+ classpathClasses, inputApp.getClasspathResourceProviders(), application);
+ if (classpathClassProvider != null) {
+ builder.setClasspathClassCollection(new ClasspathClassCollection(classpathClassProvider));
+ }
+
+ // Create library class collection if needed.
+ ClassProvider<DexLibraryClass> libraryClassProvider = buildClassProvider(LIBRARY,
+ libraryClasses, inputApp.getLibraryResourceProviders(), application);
+ if (libraryClassProvider != null) {
+ builder.setLibraryClassCollection(new LibraryClassCollection(libraryClassProvider));
+ }
+ }
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 73c0533..9e3e118 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -21,7 +21,7 @@
public AppInfoWithSubtyping(DexApplication application) {
super(application);
- populateSubtypeMap(application.getClassMap(), application.dexItemFactory);
+ populateSubtypeMap(application.getFullClassMap(), application.dexItemFactory);
}
protected AppInfoWithSubtyping(AppInfoWithSubtyping previous) {
@@ -33,7 +33,7 @@
protected AppInfoWithSubtyping(AppInfoWithSubtyping previous, GraphLense lense) {
super(previous, lense);
// Recompute subtype map if we have modified the graph.
- populateSubtypeMap(previous.app.getClassMap(), dexItemFactory);
+ populateSubtypeMap(previous.app.getFullClassMap(), dexItemFactory);
}
public Set<DexType> getMissingClasses() {
diff --git a/src/main/java/com/android/tools/r8/graph/ClassKind.java b/src/main/java/com/android/tools/r8/graph/ClassKind.java
index 6f53a40..9298ba5 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassKind.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassKind.java
@@ -1,12 +1,14 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.Resource;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
/** Kind of the application class. Can be program, classpath or library. */
public enum ClassKind {
- PROGRAM(DexProgramClass::new),
- CLASSPATH(DexClasspathClass::new),
- LIBRARY(DexLibraryClass::new);
+ PROGRAM(DexProgramClass::new, DexClass::isProgramClass),
+ CLASSPATH(DexClasspathClass::new, DexClass::isClasspathClass),
+ LIBRARY(DexLibraryClass::new, DexClass::isLibraryClass);
private interface Factory {
DexClass create(DexType type, Resource.Kind origin, DexAccessFlags accessFlags,
@@ -17,9 +19,11 @@
}
private final Factory factory;
+ private final Predicate<DexClass> check;
- ClassKind(Factory factory) {
+ ClassKind(Factory factory, Predicate<DexClass> check) {
this.factory = factory;
+ this.check = check;
}
public DexClass create(
@@ -30,4 +34,16 @@
return factory.create(type, origin, accessFlags, superType, interfaces, sourceFile,
annotations, staticFields, instanceFields, directMethods, virtualMethods);
}
+
+ public boolean isOfKind(DexClass clazz) {
+ return check.test(clazz);
+ }
+
+ public <T extends DexClass> Consumer<DexClass> bridgeConsumer(Consumer<T> consumer) {
+ return clazz -> {
+ assert isOfKind(clazz);
+ @SuppressWarnings("unchecked") T specialized = (T) clazz;
+ consumer.accept(specialized);
+ };
+ }
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexApplication.java b/src/main/java/com/android/tools/r8/graph/DexApplication.java
index af586db..e7485aa 100644
--- a/src/main/java/com/android/tools/r8/graph/DexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DexApplication.java
@@ -6,16 +6,13 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
-import com.android.tools.r8.Resource;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.ir.desugar.LambdaRewriter;
-import com.android.tools.r8.logging.Log;
import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.utils.ClasspathClassCollection;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.LazyClassCollection;
+import com.android.tools.r8.utils.LibraryClassCollection;
+import com.android.tools.r8.utils.ProgramClassCollection;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.Timing;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.io.ByteArrayOutputStream;
@@ -26,8 +23,10 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -35,16 +34,9 @@
public class DexApplication {
// Maps type into class, may be used concurrently.
- private final ImmutableMap<DexType, DexClass> classMap;
-
- // Lazily loaded classes.
- //
- // Note that this collection is autonomous and may be used in several
- // different applications. Particularly, it is the case when one
- // application is being build based on another one. Among others,
- // it will have an important side-effect: class conflict resolution,
- // generated errors in particular, may be different in lazy scenario.
- private final LazyClassCollection lazyClassCollection;
+ private ProgramClassCollection programClasses;
+ private ClasspathClassCollection classpathClasses;
+ private LibraryClassCollection libraryClasses;
public final ImmutableSet<DexType> mainDexList;
@@ -60,67 +52,97 @@
/** Constructor should only be invoked by the DexApplication.Builder. */
private DexApplication(
ClassNameMapper proguardMap,
- ImmutableMap<DexType, DexClass> classMap,
- LazyClassCollection lazyClassCollection,
+ ProgramClassCollection programClasses,
+ ClasspathClassCollection classpathClasses,
+ LibraryClassCollection libraryClasses,
ImmutableSet<DexType> mainDexList,
DexItemFactory dexItemFactory,
DexString highestSortingString,
Timing timing) {
+ assert programClasses != null;
this.proguardMap = proguardMap;
- this.lazyClassCollection = lazyClassCollection;
+ this.programClasses = programClasses;
+ this.classpathClasses = classpathClasses;
+ this.libraryClasses = libraryClasses;
this.mainDexList = mainDexList;
- this.classMap = classMap;
this.dexItemFactory = dexItemFactory;
this.highestSortingString = highestSortingString;
this.timing = timing;
}
- ImmutableMap<DexType, DexClass> getClassMap() {
- assert lazyClassCollection == null : "Only allowed in non-lazy scenarios.";
- return classMap;
+ /** Force load all classes and return type -> class map containing all the classes */
+ public Map<DexType, DexClass> getFullClassMap() {
+ return forceLoadAllClasses();
}
- public Iterable<DexProgramClass> classes() {
- List<DexProgramClass> result = new ArrayList<>();
- // Note: we ignore lazy class collection because it
- // is never supposed to be used for program classes.
- for (DexClass clazz : classMap.values()) {
- if (clazz.isProgramClass()) {
- result.add(clazz.asProgramClass());
- }
- }
- return result;
+ // Reorder classes randomly. Note that the order of classes in program or library
+ // class collections should not matter for compilation of valid code and when running
+ // with assertions enabled we reorder the classes randomly to catch possible issues.
+ // Also note that the order may add to non-determinism in reporting errors for invalid
+ // code, but this non-determinism exists even with the same order of classes since we
+ // may process classes concurrently and fail-fast on the first error.
+ private <T> boolean reorderClasses(List<T> classes) {
+ Collections.shuffle(classes);
+ return true;
}
- public Iterable<DexLibraryClass> libraryClasses() {
- assert lazyClassCollection == null : "Only allowed in non-lazy scenarios.";
- List<DexLibraryClass> result = new ArrayList<>();
+ public List<DexProgramClass> classes() {
+ List<DexProgramClass> classes = programClasses.collectLoadedClasses();
+ assert reorderClasses(classes);
+ return classes;
+ }
+
+ public List<DexLibraryClass> libraryClasses() {
+ assert classpathClasses == null : "Operation is not supported.";
+ Map<DexType, DexClass> classMap = forceLoadAllClasses();
+ List<DexLibraryClass> classes = new ArrayList<>();
for (DexClass clazz : classMap.values()) {
if (clazz.isLibraryClass()) {
- result.add(clazz.asLibraryClass());
+ classes.add(clazz.asLibraryClass());
}
}
- return result;
+ assert reorderClasses(classes);
+ return classes;
+ }
+
+ private Map<DexType, DexClass> forceLoadAllClasses() {
+ Map<DexType, DexClass> loaded = new IdentityHashMap<>();
+
+ // program classes are supposed to be loaded, but force-loading them is no-op.
+ programClasses.forceLoad(type -> true);
+ programClasses.collectLoadedClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
+
+ if (classpathClasses != null) {
+ classpathClasses.forceLoad(type -> !loaded.containsKey(type));
+ classpathClasses.collectLoadedClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
+ }
+
+ if (libraryClasses != null) {
+ libraryClasses.forceLoad(type -> !loaded.containsKey(type));
+ libraryClasses.collectLoadedClasses().forEach(clazz -> loaded.put(clazz.type, clazz));
+ }
+
+ return loaded;
}
public DexClass definitionFor(DexType type) {
- DexClass clazz = classMap.get(type);
- // In presence of lazy class collection we also reach out to it
- // as well unless the class found is already a program class.
- if (lazyClassCollection != null && (clazz == null || !clazz.isProgramClass())) {
- clazz = lazyClassCollection.get(type, clazz);
+ DexClass clazz = programClasses.get(type);
+ if (clazz == null && classpathClasses != null) {
+ clazz = classpathClasses.get(type);
+ }
+ if (clazz == null && libraryClasses != null) {
+ clazz = libraryClasses.get(type);
}
return clazz;
}
public DexProgramClass programDefinitionFor(DexType type) {
- DexClass clazz = classMap.get(type);
- // Don't bother about lazy class collection, it should never load program classes.
- return (clazz == null || !clazz.isProgramClass()) ? null : clazz.asProgramClass();
+ DexClass clazz = programClasses.get(type);
+ return clazz == null ? null : clazz.asProgramClass();
}
public String toString() {
- return "Application (classes #" + classMap.size() + ")";
+ return "Application (" + programClasses + "; " + classpathClasses + "; " + libraryClasses + ")";
}
public ClassNameMapper getProguardMap() {
@@ -170,7 +192,7 @@
* <p>If no directory is provided everything is written to System.out.
*/
public void disassemble(Path outputDir, InternalOptions options) {
- for (DexClass clazz : classes()) {
+ for (DexProgramClass clazz : programClasses.collectLoadedClasses()) {
for (DexEncodedMethod method : clazz.virtualMethods()) {
if (options.methodMatchesFilter(method)) {
disassemble(method, getProguardMap(), outputDir);
@@ -224,7 +246,7 @@
* Write smali source for the application code on the provided PrintStream.
*/
public void smali(InternalOptions options, PrintStream ps) {
- List<DexProgramClass> classes = (List<DexProgramClass>) classes();
+ List<DexProgramClass> classes = programClasses.collectLoadedClasses();
classes.sort(Comparator.comparing(DexProgramClass::toSourceString));
boolean firstClass = true;
for (DexClass clazz : classes) {
@@ -273,9 +295,16 @@
}
public static class Builder {
+ // We handle program class collection separately from classpath
+ // and library class collections. Since while we assume program
+ // class collection should always be fully loaded and thus fully
+ // represented by the map (making it easy, for example, adding
+ // new or removing existing classes), classpath and library
+ // collections will be considered monolithic collections.
- private final Hashtable<DexType, DexClass> classMap = new Hashtable<>();
- private LazyClassCollection lazyClassCollection;
+ private final List<DexProgramClass> programClasses;
+ private ClasspathClassCollection classpathClasses;
+ private LibraryClassCollection libraryClasses;
public final Hashtable<DexCode, DexCode> codeItems = new Hashtable<>();
@@ -287,18 +316,17 @@
private final Set<DexType> mainDexList = Sets.newIdentityHashSet();
public Builder(DexItemFactory dexItemFactory, Timing timing) {
+ this.programClasses = new ArrayList<>();
this.dexItemFactory = dexItemFactory;
this.timing = timing;
- this.lazyClassCollection = null;
+ this.classpathClasses = null;
+ this.libraryClasses = null;
}
public Builder(DexApplication application) {
- this(application, application.classMap);
- }
-
- public Builder(DexApplication application, Map<DexType, DexClass> classMap) {
- this.classMap.putAll(classMap);
- this.lazyClassCollection = application.lazyClassCollection;
+ programClasses = application.programClasses.collectLoadedClasses();
+ classpathClasses = application.classpathClasses;
+ libraryClasses = application.libraryClasses;
proguardMap = application.proguardMap;
timing = application.timing;
highestSortingString = application.highestSortingString;
@@ -312,58 +340,45 @@
return this;
}
+ public synchronized Builder replaceProgramClasses(List<DexProgramClass> newProgramClasses) {
+ assert newProgramClasses != null;
+ this.programClasses.clear();
+ this.programClasses.addAll(newProgramClasses);
+ return this;
+ }
+
public synchronized Builder setHighestSortingString(DexString value) {
highestSortingString = value;
return this;
}
- public Builder addClass(DexClass clazz) {
- addClass(clazz, false);
+ public synchronized Builder addProgramClass(DexProgramClass clazz) {
+ programClasses.add(clazz);
return this;
}
- public Builder setLazyClassCollection(LazyClassCollection lazyClassMap) {
- this.lazyClassCollection = lazyClassMap;
+ public Builder setClasspathClassCollection(ClasspathClassCollection classes) {
+ this.classpathClasses = classes;
return this;
}
- public Builder addClassIgnoringLibraryDuplicates(DexClass clazz) {
- addClass(clazz, true);
+ public Builder setLibraryClassCollection(LibraryClassCollection classes) {
+ this.libraryClasses = classes;
return this;
}
- public Builder addSynthesizedClass(DexProgramClass synthesizedClass, boolean addToMainDexList) {
+ public synchronized Builder addSynthesizedClass(
+ DexProgramClass synthesizedClass, boolean addToMainDexList) {
assert synthesizedClass.isProgramClass() : "All synthesized classes must be program classes";
- addClass(synthesizedClass);
+ addProgramClass(synthesizedClass);
if (addToMainDexList && !mainDexList.isEmpty()) {
mainDexList.add(synthesizedClass.type);
}
return this;
}
- public List<DexProgramClass> getProgramClasses() {
- List<DexProgramClass> result = new ArrayList<>();
- // Note: we ignore lazy class collection because it
- // is never supposed to be used for program classes.
- for (DexClass clazz : classMap.values()) {
- if (clazz.isProgramClass()) {
- result.add(clazz.asProgramClass());
- }
- }
- return result;
- }
-
- // Callback from FileReader when parsing a DexProgramClass (multi-threaded).
- public synchronized void addClass(DexClass newClass, boolean skipLibDups) {
- assert newClass != null;
- DexType type = newClass.type;
- DexClass oldClass = classMap.get(type);
- if (oldClass != null) {
- newClass = chooseClass(newClass, oldClass, skipLibDups);
- }
- if (oldClass != newClass) {
- classMap.put(type, newClass);
- }
+ public Collection<DexProgramClass> getProgramClasses() {
+ return programClasses;
}
public Builder addToMainDexList(Collection<DexType> mainDexList) {
@@ -374,83 +389,13 @@
public DexApplication build() {
return new DexApplication(
proguardMap,
- ImmutableMap.copyOf(classMap),
- lazyClassCollection,
+ ProgramClassCollection.create(programClasses),
+ classpathClasses,
+ libraryClasses,
ImmutableSet.copyOf(mainDexList),
dexItemFactory,
highestSortingString,
timing);
}
}
-
- public static DexClass chooseClass(DexClass a, DexClass b, boolean skipLibDups) {
- // NOTE: We assume that there should not be any conflicting names in user defined
- // classes and/or linked jars. If we ever want to allow 'keep first'-like policy
- // to resolve this kind of conflict between program and/or classpath classes, we'll
- // need to make sure we choose the class we keep deterministically.
- if (a.isProgramClass() && b.isProgramClass()) {
- if (allowProgramClassConflict(a.asProgramClass(), b.asProgramClass())) {
- return a;
- }
- throw new CompilationError("Program type already present: " + a.type.toSourceString());
- }
- if (a.isProgramClass()) {
- return chooseClass(a.asProgramClass(), b);
- }
- if (b.isProgramClass()) {
- return chooseClass(b.asProgramClass(), a);
- }
-
- if (a.isClasspathClass() && b.isClasspathClass()) {
- throw new CompilationError("Classpath type already present: " + a.type.toSourceString());
- }
- if (a.isClasspathClass()) {
- return chooseClass(a.asClasspathClass(), b.asLibraryClass());
- }
- if (b.isClasspathClass()) {
- return chooseClass(b.asClasspathClass(), a.asLibraryClass());
- }
-
- return chooseClasses(b.asLibraryClass(), a.asLibraryClass(), skipLibDups);
- }
-
- private static boolean allowProgramClassConflict(DexProgramClass a, DexProgramClass b) {
- // Currently only allow collapsing synthetic lambda classes.
- return a.getOrigin() == Resource.Kind.DEX
- && b.getOrigin() == Resource.Kind.DEX
- && a.accessFlags.isSynthetic()
- && b.accessFlags.isSynthetic()
- && LambdaRewriter.hasLambdaClassPrefix(a.type)
- && LambdaRewriter.hasLambdaClassPrefix(b.type);
- }
-
- private static DexClass chooseClass(DexProgramClass selected, DexClass ignored) {
- assert !ignored.isProgramClass();
- if (ignored.isLibraryClass()) {
- logIgnoredClass(ignored, "Class `%s` was specified as library and program type.");
- }
- // We don't log program/classpath class conflict since it is expected case.
- return selected;
- }
-
- private static DexClass chooseClass(DexClasspathClass selected, DexLibraryClass ignored) {
- logIgnoredClass(ignored, "Class `%s` was specified as library and classpath type.");
- return selected;
- }
-
- private static DexClass chooseClasses(
- DexLibraryClass selected, DexLibraryClass ignored, boolean skipDups) {
- if (!skipDups) {
- throw new CompilationError(
- "Library type already present: " + selected.type.toSourceString());
- }
- logIgnoredClass(ignored, "Class `%s` was specified twice as a library type.");
- return selected;
- }
-
- private static void logIgnoredClass(DexClass ignored, String message) {
- if (Log.ENABLED) {
- Log.warn(DexApplication.class, message, ignored.type.toSourceString());
- }
- }
}
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 188440d..965d770 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -22,7 +22,7 @@
import com.android.tools.r8.ir.code.MoveType;
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
import com.android.tools.r8.ir.synthetic.SynthesizedCode;
@@ -102,7 +102,7 @@
return compilationState == PROCESSED_INLINING_CANDIDATE_PUBLIC;
}
- public void markProcessed(InliningConstraint state) {
+ public void markProcessed(Constraint state) {
switch (state) {
case ALWAYS:
compilationState = PROCESSED_INLINING_CANDIDATE_PUBLIC;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index 94c415e..94f6507 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.InternalOptions;
/**
@@ -67,7 +67,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.ALWAYS;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.ALWAYS;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 2bef174..ab2056d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -119,7 +120,7 @@
public List<BasicBlock> getNormalPredecessors() {
ImmutableList.Builder<BasicBlock> normals = ImmutableList.builder();
for (BasicBlock predecessor : predecessors) {
- if (!predecessor.isCatchSuccessor(this)) {
+ if (!predecessor.hasCatchSuccessor(this)) {
normals.add(predecessor);
}
}
@@ -647,10 +648,10 @@
public EdgeType getEdgeType(BasicBlock successor) {
assert successors.indexOf(successor) >= 0;
- return isCatchSuccessor(successor) ? EdgeType.EXCEPTIONAL : EdgeType.NORMAL;
+ return hasCatchSuccessor(successor) ? EdgeType.EXCEPTIONAL : EdgeType.NORMAL;
}
- public boolean isCatchSuccessor(BasicBlock block) {
+ public boolean hasCatchSuccessor(BasicBlock block) {
if (!hasCatchHandlers()) {
return false;
}
@@ -658,7 +659,7 @@
}
public int guardsForCatchSuccessor(BasicBlock block) {
- assert isCatchSuccessor(block);
+ assert hasCatchSuccessor(block);
int index = successors.indexOf(block);
int count = 0;
for (int handler : catchHandlers.getAllTargets()) {
@@ -713,7 +714,7 @@
}
private String predecessorPostfix(BasicBlock block) {
- if (isCatchSuccessor(block)) {
+ if (hasCatchSuccessor(block)) {
return new String(new char[guardsForCatchSuccessor(block)]).replace("\0", "*");
}
return "";
@@ -1053,7 +1054,7 @@
// one new phi to merge the two exception values, and all other phis don't need
// to be changed.
for (BasicBlock catchSuccessor : catchSuccessors) {
- catchSuccessor.splitCriticalExceptioEdges(
+ catchSuccessor.splitCriticalExceptionEdges(
code.valueNumberGenerator,
newBlock -> {
newBlock.setNumber(code.blocks.size());
@@ -1062,15 +1063,6 @@
}
}
- private boolean allPredecessorsHaveCatchEdges() {
- for (BasicBlock predecessor : getPredecessors()) {
- if (!predecessor.isCatchSuccessor(this)) {
- assert false;
- }
- }
- return true;
- }
-
/**
* Assumes that `this` block is a catch handler target (note that it does not have to
* start with MoveException instruction, since the instruction can be removed by
@@ -1086,10 +1078,8 @@
*
* NOTE: onNewBlock must assign block number to the newly created block.
*/
- public void splitCriticalExceptioEdges(
+ public void splitCriticalExceptionEdges(
ValueNumberGenerator valueNumberGenerator, Consumer<BasicBlock> onNewBlock) {
- assert allPredecessorsHaveCatchEdges();
-
List<BasicBlock> predecessors = this.getPredecessors();
boolean hasMoveException = entry().isMoveException();
MoveException move = null;
@@ -1103,6 +1093,10 @@
List<BasicBlock> newPredecessors = new ArrayList<>();
List<Value> values = new ArrayList<>(predecessors.size());
for (BasicBlock predecessor : predecessors) {
+ if (!predecessor.hasCatchSuccessor(this)) {
+ throw new CompilationError(
+ "Invalid block structure: catch block reachable via non-exceptional flow.");
+ }
BasicBlock newBlock = new BasicBlock();
newPredecessors.add(newBlock);
if (hasMoveException) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 83240bb..899516a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -10,7 +10,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public abstract class Binop extends Instruction {
@@ -112,7 +112,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.ALWAYS;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.ALWAYS;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index 451832f..4cca9dd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -15,7 +15,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.NumberUtils;
public class ConstNumber extends ConstInstruction {
@@ -188,7 +188,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.ALWAYS;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.ALWAYS;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Div.java b/src/main/java/com/android/tools/r8/ir/code/Div.java
index c8c1c32..8fd9bf0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Div.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Div.java
@@ -14,9 +14,6 @@
import com.android.tools.r8.code.DivIntLit8;
import com.android.tools.r8.code.DivLong;
import com.android.tools.r8.code.DivLong2Addr;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
public class Div extends ArithmeticBinop {
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 ba1e0e9..0b8d876 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
@@ -9,8 +9,7 @@
import com.android.tools.r8.graph.DexEncodedField;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import java.util.List;
abstract class FieldInstruction extends Instruction {
@@ -43,7 +42,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
// Resolve the field if possible and decide whether the instruction can inlined.
DexType fieldHolder = field.getHolder();
DexEncodedField target = info.lookupInstanceTarget(fieldHolder, field);
@@ -51,15 +50,15 @@
if ((target != null) && (fieldClass != null) && !fieldClass.isLibraryClass()) {
DexAccessFlags flags = target.accessFlags;
if (flags.isPublic()) {
- return Inliner.InliningConstraint.ALWAYS;
+ return Constraint.ALWAYS;
}
if (flags.isPrivate() && (fieldHolder == holder)) {
- return Inliner.InliningConstraint.PRIVATE;
+ return Constraint.PRIVATE;
}
if (flags.isProtected() && (fieldHolder.isSamePackage(holder))) {
- return Inliner.InliningConstraint.PACKAGE;
+ return Constraint.PACKAGE;
}
}
- return Inliner.InliningConstraint.NEVER;
+ return Constraint.NEVER;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index 3fe498d..1126299 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class InstanceOf extends Instruction {
@@ -75,14 +75,14 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
DexClass targetClass = info.definitionFor(type());
if (targetClass == null) {
- return InliningConstraint.NEVER;
+ return Constraint.NEVER;
}
if (targetClass.accessFlags.isPublic()) {
- return InliningConstraint.ALWAYS;
+ return Constraint.ALWAYS;
}
- return InliningConstraint.NEVER;
+ return Constraint.NEVER;
}
}
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 1b18ccc..2e821cf 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
@@ -9,7 +9,7 @@
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.code.Value.DebugInfo;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.regalloc.RegisterAllocator;
import com.android.tools.r8.utils.CfgPrinter;
import com.android.tools.r8.utils.InternalOptions;
@@ -797,7 +797,7 @@
}
// Returns the inlining constraint for this instruction.
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.NEVER;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.NEVER;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index 8d0fa34..87edec8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -91,8 +91,14 @@
}
protected int argumentRegisterValue(int i, DexBuilder builder) {
+ assert requiredArgumentRegisters() > 5;
if (i < arguments().size()) {
- return builder.allocatedRegisterForRangedArgument(arguments().get(i), getNumber());
+ // If argument values flow into ranged invokes, all the ranged invoke arguments
+ // are arguments to this method in order. Therefore, we use the incoming registers
+ // for the ranged invoke arguments. We know that arguments are always available there.
+ // If argument reuse is allowed there is no splitting and if argument reuse is disallowed
+ // the argument registers are never overwritten.
+ return builder.argumentOrAllocateRegister(arguments().get(i), getNumber());
}
return 0;
}
@@ -120,10 +126,10 @@
protected boolean argumentsConsecutive(DexBuilder builder) {
Value value = arguments().get(0);
- int next = builder.allocatedRegisterForRangedArgument(value, getNumber()) + value.requiredRegisters();
+ int next = builder.argumentOrAllocateRegister(value, getNumber()) + value.requiredRegisters();
for (int i = 1; i < arguments().size(); i++) {
value = arguments().get(i);
- assert next == builder.allocatedRegisterForRangedArgument(value, getNumber());
+ assert next == builder.argumentOrAllocateRegister(value, getNumber());
next += value.requiredRegisters();
}
return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
index 2a02218..7b1c007 100644
--- a/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/JumpInstruction.java
@@ -5,7 +5,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.utils.InternalOptions;
import java.util.List;
@@ -47,7 +47,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.ALWAYS;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.ALWAYS;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 869d1d0..b3e155b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -29,7 +29,15 @@
@Override
public void buildDex(DexBuilder builder) {
- int object = builder.allocatedRegister(object(), getNumber());
+ // If the monitor object is an argument, we use the argument register for all the monitor
+ // enters and exits in order to not confuse the Art verifier lock verification code.
+ // This is best effort. If the argument happens to be in a very high register we cannot
+ // do it and the lock verification can hit a case where it gets confused. Not much we
+ // can do about that, but this should avoid it in the most common cases.
+ int object = builder.argumentOrAllocateRegister(object(), getNumber());
+ if (object > maxInValueRegister()) {
+ object = builder.allocatedRegister(object(), getNumber());
+ }
if (type == Type.ENTER) {
builder.add(this, new MonitorEnter(object));
} else {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 6f70c57..91e19f0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -8,7 +8,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class Move extends Instruction {
@@ -81,7 +81,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.ALWAYS;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.ALWAYS;
}
}
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 5912f0f..de44e83 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
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.code;
+import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.ir.code.BasicBlock.EdgeType;
import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -60,6 +61,9 @@
// exactly once by adding the operands.
assert operands.isEmpty();
boolean canBeNull = false;
+ if (block.getPredecessors().size() == 0) {
+ throwUndefinedValueError();
+ }
for (BasicBlock pred : block.getPredecessors()) {
EdgeType edgeType = pred.getEdgeType(block);
// Since this read has been delayed we must provide the local info for the value.
@@ -79,6 +83,9 @@
// exactly once by adding the operands.
assert this.operands.isEmpty();
boolean canBeNull = false;
+ if (operands.size() == 0) {
+ throwUndefinedValueError();
+ }
for (Value operand : operands) {
canBeNull |= operand.canBeNull();
appendOperand(operand);
@@ -89,6 +96,13 @@
removeTrivialPhi();
}
+ private void throwUndefinedValueError() {
+ throw new CompilationError(
+ "Undefined value encountered during compilation. "
+ + "This is typically caused by invalid dex input that uses a register "
+ + "that is not define on all control-flow paths leading to the use.");
+ }
+
private void appendOperand(Value operand) {
operands.add(operand);
operand.addPhiUser(this);
@@ -174,9 +188,6 @@
same = op;
}
assert isTrivialPhi();
- if (same == null) {
- same = Value.UNDEFINED;
- }
// 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.
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index 2819c58..142206c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -11,7 +11,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class Return extends JumpInstruction {
@@ -110,7 +110,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.ALWAYS;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.ALWAYS;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index e0ba866..9bd6a81 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -7,7 +7,7 @@
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
public class Throw extends JumpInstruction {
@@ -63,7 +63,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.ALWAYS;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.ALWAYS;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Unop.java b/src/main/java/com/android/tools/r8/ir/code/Unop.java
index ff1744a..174b7b6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Unop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Unop.java
@@ -6,7 +6,7 @@
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
abstract public class Unop extends Instruction {
@@ -43,7 +43,7 @@
}
@Override
- public InliningConstraint inliningConstraint(AppInfo info, DexType holder) {
- return InliningConstraint.ALWAYS;
+ public Constraint inliningConstraint(AppInfo info, DexType holder) {
+ return Constraint.ALWAYS;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 9cf14d8..50afe5f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -318,8 +318,10 @@
return registerAllocator.getRegisterForValue(value, instructionNumber);
}
- public int allocatedRegisterForRangedArgument(Value value, int instructionNumber) {
- return registerAllocator.getRegisterForRangedArgument(value, instructionNumber);
+ // Get the argument register for a value if it is an argument, otherwise returns the
+ // allocated register at the instruction number.
+ public int argumentOrAllocateRegister(Value value, int instructionNumber) {
+ return registerAllocator.getArgumentOrAllocateRegisterForValue(value, instructionNumber);
}
public boolean argumentValueUsesHighRegister(Value value, int instructionNumber) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index b3d5266..d24c8b4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -81,8 +81,6 @@
import com.android.tools.r8.ir.code.ValueNumberGenerator;
import com.android.tools.r8.ir.code.Xor;
import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.StringUtils;
-import com.android.tools.r8.utils.StringUtils.BraceType;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
@@ -372,9 +370,6 @@
// necessary.
splitCriticalEdges();
- // Consistency check.
- assert phiOperandsAreConsistent();
-
// Package up the IR code.
IRCode ir = new IRCode(method, blocks, normalExitBlock, valueNumberGenerator);
@@ -808,7 +803,7 @@
public void addGoto(int targetOffset) {
addInstruction(new Goto());
BasicBlock targetBlock = getTarget(targetOffset);
- if (currentBlock.isCatchSuccessor(targetBlock)) {
+ if (currentBlock.hasCatchSuccessor(targetBlock)) {
needGotoToCatchBlocks.add(new BasicBlock.Pair(currentBlock, targetBlock));
} else {
currentBlock.link(targetBlock);
@@ -1086,7 +1081,12 @@
Value out = writeRegister(dest, MoveType.OBJECT, ThrowingInfo.NO_THROW);
MoveException instruction = new MoveException(out);
assert !instruction.instructionTypeCanThrow();
- assert currentBlock.getInstructions().isEmpty();
+ if (!currentBlock.getInstructions().isEmpty()) {
+ throw new CompilationError("Invalid MoveException instruction encountered. "
+ + "The MoveException instruction is not the first instruction in the block in "
+ + method.qualifiedName()
+ + ".");
+ }
addInstruction(instruction);
}
@@ -1698,7 +1698,7 @@
assert currentBlock != null;
flushCurrentDebugPosition();
currentBlock.add(new Goto());
- if (currentBlock.isCatchSuccessor(nextBlock)) {
+ if (currentBlock.hasCatchSuccessor(nextBlock)) {
needGotoToCatchBlocks.add(new BasicBlock.Pair(currentBlock, nextBlock));
} else {
currentBlock.link(nextBlock);
@@ -1778,8 +1778,8 @@
target.getPredecessors().add(newBlock);
// Check that the successor indexes are correct.
- assert source.isCatchSuccessor(newBlock);
- assert !source.isCatchSuccessor(target);
+ assert source.hasCatchSuccessor(newBlock);
+ assert !source.hasCatchSuccessor(target);
// Mark the filled predecessors to the blocks.
if (source.isFilled()) {
@@ -1790,22 +1790,6 @@
return blockNumber;
}
- private boolean phiOperandsAreConsistent() {
- for (BasicBlock block : blocks) {
- if (block.hasIncompletePhis()) {
- StringBuilder builder = new StringBuilder("Incomplete phis in ");
- builder.append(method);
- builder.append(". The following registers appear to be uninitialized: ");
- StringUtils.append(builder, block.getIncompletePhiRegisters(), ", ", BraceType.NONE);
- throw new CompilationError(builder.toString());
- }
- for (Phi phi : block.getPhis()) {
- assert phi.getOperands().size() == block.getPredecessors().size();
- }
- }
- return true;
- }
-
/**
* Change to control-flow graph to avoid repeated phi operands when all the same values
* flow in from multiple predecessors.
@@ -1837,6 +1821,15 @@
public void joinPredecessorsWithIdenticalPhis() {
List<BasicBlock> blocksToAdd = new ArrayList<>();
for (BasicBlock block : blocks) {
+ // Consistency check. At this point there should be no incomplete phis.
+ // If there are, the input is typically dex code that uses a register
+ // that is not defined on all control-flow paths.
+ if (block.hasIncompletePhis()) {
+ throw new CompilationError(
+ "Undefined value encountered during compilation. "
+ + "This is typically caused by invalid dex input that uses a register "
+ + "that is not define on all control-flow paths leading to the use.");
+ }
if (block.entry() instanceof MoveException) {
// TODO: Should we support joining in the presence of move-exception instructions?
continue;
@@ -1889,7 +1882,7 @@
// If any of the edges to the block are critical, we need to insert new blocks on each
// containing the move-exception instruction which must remain the first instruction.
if (block.entry() instanceof MoveException) {
- block.splitCriticalExceptioEdges(valueNumberGenerator,
+ block.splitCriticalExceptionEdges(valueNumberGenerator,
newBlock -> {
newBlock.setNumber(blocks.size() + newBlocks.size());
newBlocks.add(newBlock);
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 8370eb8..3b19b02 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
@@ -25,7 +25,7 @@
import com.android.tools.r8.ir.optimize.CodeRewriter;
import com.android.tools.r8.ir.optimize.DeadCodeRemover;
import com.android.tools.r8.ir.optimize.Inliner;
-import com.android.tools.r8.ir.optimize.Inliner.InliningConstraint;
+import com.android.tools.r8.ir.optimize.Inliner.Constraint;
import com.android.tools.r8.ir.optimize.MemberValuePropagation;
import com.android.tools.r8.ir.optimize.Outliner;
import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
@@ -390,15 +390,15 @@
boolean matchesMethodFilter = options.methodMatchesFilter(method);
if (code != null && matchesMethodFilter) {
assert !method.isProcessed();
- InliningConstraint state = rewriteCode(method, outlineHandler);
+ Constraint state = rewriteCode(method, outlineHandler);
method.markProcessed(state);
} else {
// Mark abstract methods as processed as well.
- method.markProcessed(InliningConstraint.NEVER);
+ method.markProcessed(Constraint.NEVER);
}
}
- private InliningConstraint rewriteCode(DexEncodedMethod method,
+ private Constraint rewriteCode(DexEncodedMethod method,
BiConsumer<IRCode, DexEncodedMethod> outlineHandler) {
if (options.verbose) {
System.out.println("Processing: " + method.toSourceString());
@@ -409,7 +409,7 @@
}
IRCode code = method.buildIR(options);
if (code == null) {
- return InliningConstraint.NEVER;
+ return Constraint.NEVER;
}
if (Log.ENABLED) {
Log.debug(getClass(), "Initial (SSA) flow graph for %s:\n%s", method.toSourceString(), code);
@@ -490,7 +490,7 @@
// After all the optimizations have take place, we compute whether method should be inlined.
if (!options.inlineAccessors || inliner == null) {
- return InliningConstraint.NEVER;
+ return Constraint.NEVER;
}
return inliner.identifySimpleMethods(code, method);
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index f677637..f57c022 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -50,23 +50,23 @@
this.options = options;
}
- private InliningConstraint instructionAllowedForInlining(
+ private Constraint instructionAllowedForInlining(
DexEncodedMethod method, Instruction instruction) {
- InliningConstraint result = instruction.inliningConstraint(appInfo, method.method.holder);
- if ((result == InliningConstraint.NEVER) && instruction.isDebugInstruction()) {
- return InliningConstraint.ALWAYS;
+ Constraint result = instruction.inliningConstraint(appInfo, method.method.holder);
+ if ((result == Constraint.NEVER) && instruction.isDebugInstruction()) {
+ return Constraint.ALWAYS;
}
return result;
}
- public InliningConstraint identifySimpleMethods(IRCode code, DexEncodedMethod method) {
+ public Constraint identifySimpleMethods(IRCode code, DexEncodedMethod method) {
DexCode dex = method.getCode().asDexCode();
// We have generated code for a method and we want to figure out whether the method is a
// candidate for inlining. The code is the final IR after optimizations.
if (dex.instructions.length > INLINING_INSTRUCTION_LIMIT) {
- return InliningConstraint.NEVER;
+ return Constraint.NEVER;
}
- InliningConstraint result = InliningConstraint.ALWAYS;
+ Constraint result = Constraint.ALWAYS;
ListIterator<BasicBlock> iterator = code.listIterator();
assert iterator.hasNext();
BasicBlock block = iterator.next();
@@ -76,9 +76,9 @@
InstructionListIterator it = block.listIterator();
while (it.hasNext()) {
Instruction instruction = it.next();
- InliningConstraint state = instructionAllowedForInlining(method, instruction);
- if (state == InliningConstraint.NEVER) {
- return InliningConstraint.NEVER;
+ Constraint state = instructionAllowedForInlining(method, instruction);
+ if (state == Constraint.NEVER) {
+ return Constraint.NEVER;
}
if (state.ordinal() < result.ordinal()) {
result = state;
@@ -105,7 +105,7 @@
return methodHolder.isSamePackage(targetHolder);
}
- public enum InliningConstraint {
+ public enum Constraint {
// The ordinal values are important so please do not reorder.
NEVER, // Never inline this.
PRIVATE, // Only inline this into methods with same holder.
@@ -113,23 +113,27 @@
ALWAYS, // No restrictions for inlining this.
}
+ public enum Reason {
+ FORCE, // Inlinee is marked for forced inlining (bridge method or renamed constructor).
+ SINGLE_CALLER, // Inlinee has precisely one caller.
+ DUAL_CALLER, // Inlinee has precisely two callers.
+ SIMPLE, // Inlinee has simple code suitable for inlining.
+ }
+
static public class InlineAction {
public final DexEncodedMethod target;
public final Invoke invoke;
- public final boolean forceInline;
+ public final Reason reason;
- public InlineAction(
- DexEncodedMethod target, Invoke invoke, boolean forceInline) {
+ public InlineAction(DexEncodedMethod target, Invoke invoke, Reason reason) {
this.target = target;
this.invoke = invoke;
- this.forceInline = forceInline;
+ this.reason = reason;
}
- public InlineAction(DexEncodedMethod target, Invoke invoke) {
- this.target = target;
- this.invoke = invoke;
- this.forceInline = target.getOptimizationInfo().forceInline();
+ public boolean forceInline() {
+ return reason != Reason.SIMPLE;
}
public IRCode buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo,
@@ -275,7 +279,7 @@
// Back up before the invoke instruction.
iterator.previous();
instruction_allowance -= numberOfInstructions(inlinee);
- if (instruction_allowance >= 0 || result.forceInline) {
+ if (instruction_allowance >= 0 || result.forceInline()) {
iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
}
// If we inlined the invoke from a bridge method, it is no longer a bridge method.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index 954f11c..7d83150 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.ir.conversion.CallGraph;
import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
+import com.android.tools.r8.ir.optimize.Inliner.Reason;
import com.android.tools.r8.logging.Log;
/**
@@ -103,6 +104,19 @@
return candidate;
}
+ private Reason computeInliningReason(DexEncodedMethod target) {
+ if (target.getOptimizationInfo().forceInline()) {
+ return Reason.FORCE;
+ }
+ if (callGraph.hasSingleCallSite(target)) {
+ return Reason.SINGLE_CALLER;
+ }
+ if (isDoubleInliningTarget(target)) {
+ return Reason.DUAL_CALLER;
+ }
+ return Reason.SIMPLE;
+ }
+
public InlineAction computeForInvokeWithReceiver(InvokeMethodWithReceiver invoke) {
boolean receiverIsNeverNull = invoke.receiverIsNeverNull();
if (!receiverIsNeverNull) {
@@ -120,11 +134,9 @@
return null;
}
- boolean fromBridgeMethod = target.getOptimizationInfo().forceInline();
-
if (target == method) {
// Bridge methods should never have recursive calls.
- assert !fromBridgeMethod;
+ assert !target.getOptimizationInfo().forceInline();
return null;
}
@@ -154,11 +166,9 @@
return null;
}
- boolean doubleInlineTarget = isDoubleInliningTarget(target);
+ Reason reason = computeInliningReason(target);
// Determine if this should be inlined no matter how big it is.
- boolean forceInline =
- fromBridgeMethod | callGraph.hasSingleCallSite(target) | doubleInlineTarget;
- if (!target.isInliningCandidate(method, forceInline)) {
+ if (!target.isInliningCandidate(method, reason != Reason.SIMPLE)) {
// Abort inlining attempt if the single target is not an inlining candidate.
if (info != null) {
info.exclude(invoke, "target is not identified for inlining");
@@ -175,7 +185,7 @@
}
// Attempt to inline a candidate that is only called twice.
- if (doubleInlineTarget && (doubleInlining(target) == null)) {
+ if ((reason == Reason.DUAL_CALLER) && (doubleInlining(target) == null)) {
if (info != null) {
info.exclude(invoke, "target is not ready for double inlining");
}
@@ -185,7 +195,7 @@
if (info != null) {
info.include(invoke.getType(), target);
}
- return new InlineAction(target, invoke, forceInline);
+ return new InlineAction(target, invoke, reason);
}
public InlineAction computeForInvokeVirtual(InvokeVirtual invoke) {
@@ -224,12 +234,9 @@
if (candidate == null) {
return null;
}
- boolean fromBridgeMethod = candidate.getOptimizationInfo().forceInline();
- boolean doubleInlineTarget = isDoubleInliningTarget(candidate);
+ Reason reason = computeInliningReason(candidate);
// Determine if this should be inlined no matter how big it is.
- boolean forceInline =
- fromBridgeMethod | callGraph.hasSingleCallSite(candidate) | doubleInlineTarget;
- if (!candidate.isInliningCandidate(method, forceInline)) {
+ if (!candidate.isInliningCandidate(method, reason != Reason.SIMPLE)) {
// Abort inlining attempt if the single target is not an inlining candidate.
if (info != null) {
info.exclude(invoke, "target is not identified for inlining");
@@ -246,7 +253,7 @@
}
// Attempt to inline a candidate that is only called twice.
- if (doubleInlineTarget && (doubleInlining(candidate) == null)) {
+ if ((reason == Reason.DUAL_CALLER) && (doubleInlining(candidate) == null)) {
if (info != null) {
info.exclude(invoke, "target is not ready for double inlining");
}
@@ -256,7 +263,7 @@
if (info != null) {
info.include(invoke.getType(), candidate);
}
- return new InlineAction(candidate, invoke);
+ return new InlineAction(candidate, invoke, reason);
}
public InlineAction computeForInvokeSuper(InvokeSuper invoke) {
@@ -270,7 +277,7 @@
if (info != null) {
info.include(invoke.getType(), candidate);
}
- return new InlineAction(candidate, invoke);
+ return new InlineAction(candidate, invoke, Reason.SIMPLE);
}
public InlineAction computeForInvokePolymorpic(InvokePolymorphic invoke) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 4bf5dc6..a317701 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -421,12 +421,7 @@
}
@Override
- public int getRegisterForRangedArgument(Value value, int instructionNumber) {
- // If argument values flow into ranged invokes, all the ranged invoke arguments
- // are arguments to this method in order. Therefore, we use the incoming registers
- // for the ranged invoke arguments. We know that arguments are always available there.
- // If argument reuse is allowed there is no splitting and if argument reuse is disallowed
- // the argument registers are never overwritten.
+ public int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber) {
if (value.isArgument()) {
return getRegisterForIntervals(value.getLiveIntervals());
}
@@ -1414,7 +1409,8 @@
assert spilled.isSpilled();
assert spilled.getValue().isConstant();
assert !spilled.isLinked() || spilled.isArgumentInterval();
- int maxGapSize = 50 * INSTRUCTION_NUMBER_DELTA;
+ // Do not split range if constant is reused by one of the eleven following instruction.
+ int maxGapSize = 11 * INSTRUCTION_NUMBER_DELTA;
if (!spilled.getUses().isEmpty()) {
// Split at first use after the spill position and add to unhandled to get a register
// assigned for rematerialization.
@@ -1520,7 +1516,7 @@
// If we are processing an exception edge, we need to use the throwing instruction
// as the instruction we are coming from.
int fromInstruction = block.exit().getNumber();
- boolean isCatch = block.isCatchSuccessor(successor);
+ boolean isCatch = block.hasCatchSuccessor(successor);
if (isCatch) {
for (Instruction instruction : block.getInstructions()) {
if (instruction.instructionTypeCanThrow()) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index fc7c2d8..79ba8db 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -10,5 +10,5 @@
int registersUsed();
int getRegisterForValue(Value value, int instructionNumber);
boolean argumentValueUsesHighRegister(Value value, int instructionNumber);
- int getRegisterForRangedArgument(Value value, int instructionNumber);
+ int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber);
}
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 21ab0d7..04f6786 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -7,17 +7,14 @@
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.DexLibraryClass;
import com.android.tools.r8.graph.DexProgramClass;
-import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.KeyedDexItem;
import com.android.tools.r8.graph.PresortedComparable;
import com.android.tools.r8.logging.Log;
import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
import com.android.tools.r8.utils.InternalOptions;
import java.util.ArrayList;
-import java.util.IdentityHashMap;
-import java.util.Map;
+import java.util.List;
import java.util.Set;
public class TreePruner {
@@ -51,22 +48,20 @@
}
private DexApplication.Builder removeUnused(DexApplication application) {
- return new DexApplication.Builder(application, removeUnusedClassStructure(application));
+ return new DexApplication.Builder(application)
+ .replaceProgramClasses(getNewProgramClasses(application.classes()));
}
- private Map<DexType, DexClass> removeUnusedClassStructure(DexApplication application) {
- Map<DexType, DexClass> classMap = new IdentityHashMap<>();
- for (DexLibraryClass clazz : application.libraryClasses()) {
- classMap.put(clazz.type, clazz);
- }
- for (DexProgramClass clazz : application.classes()) {
+ private List<DexProgramClass> getNewProgramClasses(List<DexProgramClass> classes) {
+ List<DexProgramClass> newClasses = new ArrayList<>();
+ for (DexProgramClass clazz : classes) {
if (!appInfo.liveTypes.contains(clazz.type) && !options.debugKeepRules) {
// The class is completely unused and we can remove it.
if (Log.ENABLED) {
Log.debug(getClass(), "Removing class: " + clazz);
}
} else {
- classMap.put(clazz.type, clazz);
+ newClasses.add(clazz);
if (!appInfo.instantiatedTypes.contains(clazz.type) && !options.debugKeepRules) {
// The class is only needed as a type but never instantiated. Make it abstract to reflect
// this.
@@ -88,7 +83,7 @@
clazz.staticFields = reachableFields(clazz.staticFields());
}
}
- return classMap;
+ return newClasses;
}
private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index b4fdfe9..389db15 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -7,8 +7,8 @@
import static com.android.tools.r8.utils.FileUtils.isClassFile;
import static com.android.tools.r8.utils.FileUtils.isDexFile;
+import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.Resource;
-import com.android.tools.r8.ResourceProvider;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.ClassKind;
@@ -53,8 +53,8 @@
private final ImmutableList<Resource> programResources;
private final ImmutableList<Resource> classpathResources;
private final ImmutableList<Resource> libraryResources;
- private final ImmutableList<ResourceProvider> classpathResourceProviders;
- private final ImmutableList<ResourceProvider> libraryResourceProviders;
+ private final ImmutableList<ClassFileResourceProvider> classpathResourceProviders;
+ private final ImmutableList<ClassFileResourceProvider> libraryResourceProviders;
private final Resource proguardMap;
private final Resource proguardSeeds;
private final Resource packageDistribution;
@@ -65,8 +65,8 @@
ImmutableList<Resource> programResources,
ImmutableList<Resource> classpathResources,
ImmutableList<Resource> libraryResources,
- ImmutableList<ResourceProvider> classpathResourceProviders,
- ImmutableList<ResourceProvider> libraryResourceProviders,
+ ImmutableList<ClassFileResourceProvider> classpathResourceProviders,
+ ImmutableList<ClassFileResourceProvider> libraryResourceProviders,
Resource proguardMap,
Resource proguardSeeds,
Resource packageDistribution,
@@ -176,12 +176,12 @@
}
/** Get classpath resource providers. */
- public List<ResourceProvider> getClasspathResourceProviders() {
+ public List<ClassFileResourceProvider> getClasspathResourceProviders() {
return classpathResourceProviders;
}
/** Get library resource providers. */
- public List<ResourceProvider> getLibraryResourceProviders() {
+ public List<ClassFileResourceProvider> getLibraryResourceProviders() {
return libraryResourceProviders;
}
@@ -372,8 +372,8 @@
private final List<Resource> programResources = new ArrayList<>();
private final List<Resource> classpathResources = new ArrayList<>();
private final List<Resource> libraryResources = new ArrayList<>();
- private final List<ResourceProvider> classpathResourceProviders = new ArrayList<>();
- private final List<ResourceProvider> libraryResourceProviders = new ArrayList<>();
+ private final List<ClassFileResourceProvider> classpathResourceProviders = new ArrayList<>();
+ private final List<ClassFileResourceProvider> libraryResourceProviders = new ArrayList<>();
private Resource proguardMap;
private Resource proguardSeeds;
private Resource packageDistribution;
@@ -456,7 +456,7 @@
/**
* Add classpath resource provider.
*/
- public Builder addClasspathResourceProvider(ResourceProvider provider) {
+ public Builder addClasspathResourceProvider(ClassFileResourceProvider provider) {
classpathResourceProviders.add(provider);
return this;
}
@@ -481,7 +481,7 @@
/**
* Add library resource provider.
*/
- public Builder addLibraryResourceProvider(ResourceProvider provider) {
+ public Builder addLibraryResourceProvider(ClassFileResourceProvider provider) {
libraryResourceProviders.add(provider);
return this;
}
@@ -641,7 +641,7 @@
Resource.Kind.DEX, ByteStreams.toByteArray(stream)));
} else if (isClassFile(name)) {
containsClassData = true;
- String descriptor = PreloadedResourceProvider.guessTypeDescriptor(name);
+ String descriptor = PreloadedClassFileProvider.guessTypeDescriptor(name);
resources(classKind).add(Resource.fromBytes(Resource.Kind.CLASSFILE,
ByteStreams.toByteArray(stream), Collections.singleton(descriptor)));
}
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
new file mode 100644
index 0000000..d0f2cda
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -0,0 +1,151 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * Represents a collection of classes. Collection can be fully loaded,
+ * lazy loaded or have preloaded classes along with lazy loaded content.
+ */
+public abstract class ClassMap<T extends DexClass> {
+ // For each type which has ever been queried stores one class loaded from
+ // resources provided by different resource providers.
+ //
+ // NOTE: all access must be synchronized on `classes`.
+ private final Map<DexType, Value<T>> classes;
+
+ // Class provider if available. In case it's `null`, all classes of
+ // the collection must be pre-populated in `classes`.
+ private final ClassProvider<T> classProvider;
+
+ ClassMap(Map<DexType, Value<T>> classes, ClassProvider<T> classProvider) {
+ this.classes = classes == null ? new IdentityHashMap<>() : classes;
+ this.classProvider = classProvider;
+ assert this.classProvider == null || this.classProvider.getClassKind() == getClassKind();
+ }
+
+ /** Resolves a class conflict by selecting a class, may generate compilation error. */
+ abstract T resolveClassConflict(T a, T b);
+
+ /** Kind of the classes supported by this collection. */
+ abstract ClassKind getClassKind();
+
+ @Override
+ public String toString() {
+ synchronized (classes) {
+ return classes.size() + " loaded, provider: " +
+ (classProvider == null ? "none" : classProvider.toString());
+ }
+ }
+
+ /** Returns a definition for a class or `null` if there is no such class in the collection. */
+ public T get(DexType type) {
+ final Value<T> value = getOrCreateValue(type);
+
+ if (!value.ready) {
+ // Load the value in a context synchronized on value instance. This way
+ // we avoid locking the whole collection during expensive resource loading
+ // and classes construction operations.
+ synchronized (value) {
+ if (!value.ready && classProvider != null) {
+ classProvider.collectClass(type, clazz -> {
+ assert clazz != null;
+ assert getClassKind().isOfKind(clazz);
+ assert !value.ready;
+
+ if (clazz.type != type) {
+ throw new CompilationError("Class content provided for type descriptor "
+ + type.toSourceString() + " actually defines class " + clazz.type
+ .toSourceString());
+ }
+
+ if (value.clazz == null) {
+ value.clazz = clazz;
+ } else {
+ // The class resolution *may* generate a compilation error as one of
+ // possible resolutions. In this case we leave `value` in (false, null)
+ // state so in rare case of another thread trying to get the same class
+ // before this error is propagated it will get the same conflict.
+ T oldClass = value.clazz;
+ value.clazz = null;
+ value.clazz = resolveClassConflict(oldClass, clazz);
+ }
+ });
+ }
+ value.ready = true;
+ }
+ }
+
+ assert value.ready;
+ return value.clazz;
+ }
+
+ private Value<T> getOrCreateValue(DexType type) {
+ synchronized (classes) {
+ return classes.computeIfAbsent(type, k -> new Value<>());
+ }
+ }
+
+ /** Returns currently loaded classes */
+ public List<T> collectLoadedClasses() {
+ List<T> loadedClasses = new ArrayList<>();
+ synchronized (classes) {
+ for (Value<T> value : classes.values()) {
+ // Method collectLoadedClasses() must always be called when there
+ // is no risk of concurrent loading of the classes, otherwise the
+ // behaviour of this method is undefined. Note that value mutations
+ // are NOT synchronized in `classes`, so the assertion below does
+ // not enforce this requirement, but may help detect wrong behaviour.
+ assert value.ready : "";
+ if (value.clazz != null) {
+ loadedClasses.add(value.clazz);
+ }
+ }
+ }
+ return loadedClasses;
+ }
+
+ /** Forces loading of all the classes satisfying the criteria specified. */
+ public void forceLoad(Predicate<DexType> load) {
+ if (classProvider != null) {
+ Set<DexType> loaded;
+ synchronized (classes) {
+ loaded = classes.keySet();
+ }
+ Collection<DexType> types = classProvider.collectTypes();
+ for (DexType type : types) {
+ if (load.test(type) && !loaded.contains(type)) {
+ get(type); // force-load type.
+ }
+ }
+ }
+ }
+
+ // Represents a value in the class map.
+ final static class Value<T> {
+ volatile boolean ready;
+ T clazz;
+
+ Value() {
+ ready = false;
+ clazz = null;
+ }
+
+ Value(T clazz) {
+ this.clazz = clazz;
+ this.ready = true;
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ClassProvider.java b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
new file mode 100644
index 0000000..25abdce
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ClassProvider.java
@@ -0,0 +1,187 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import static com.android.tools.r8.utils.FileUtils.DEFAULT_DEX_FILENAME;
+
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.JarApplicationReader;
+import com.android.tools.r8.graph.JarClassFileReader;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.Multimap;
+import com.google.common.collect.Sets;
+import com.google.common.io.Closer;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+/** Represents a provider for classes loaded from different sources. */
+public abstract class ClassProvider<T extends DexClass> {
+ private final ClassKind classKind;
+
+ ClassProvider(ClassKind classKind) {
+ this.classKind = classKind;
+ }
+
+ /** The kind of the classes created by the provider. */
+ final ClassKind getClassKind() {
+ return classKind;
+ }
+
+ /**
+ * The provider uses the callback to return all the classes that might
+ * be associated with the descriptor asked for.
+ *
+ * NOTE: the provider is not required to cache created classes and this
+ * method may create a new class instance in case it is called twice for
+ * the same type. For this reason it is recommended that the provider
+ * user only calls this method once per any given type.
+ *
+ * NOTE: thread-safe.
+ */
+ public abstract void collectClass(DexType type, Consumer<T> classConsumer);
+
+ /**
+ * Returns all the types of classes that might be produced by this provider.
+ *
+ * NOTE: thread-safe.
+ */
+ public abstract Collection<DexType> collectTypes();
+
+ /** Create class provider for java class resource provider. */
+ public static <T extends DexClass> ClassProvider<T> forClassFileResources(
+ ClassKind classKind, ClassFileResourceProvider provider, JarApplicationReader reader) {
+ return new ClassFileResourceReader<>(classKind, provider, reader);
+ }
+
+ /** Create class provider for preloaded classes, classes may have conflicting names. */
+ public static <T extends DexClass> ClassProvider<T> forPreloadedClasses(
+ ClassKind classKind, Collection<T> classes) {
+ ImmutableListMultimap.Builder<DexType, T> builder = ImmutableListMultimap.builder();
+ for (T clazz : classes) {
+ builder.put(clazz.type, clazz);
+ }
+ return new PreloadedClassProvider<>(classKind, builder.build());
+ }
+
+ /** Create class provider for preloaded classes. */
+ public static <T extends DexClass> ClassProvider<T> combine(
+ ClassKind classKind, List<ClassProvider<T>> providers) {
+ return new CombinedClassProvider<>(classKind, providers);
+ }
+
+ private static class ClassFileResourceReader<T extends DexClass> extends ClassProvider<T> {
+ private final ClassKind classKind;
+ private final ClassFileResourceProvider provider;
+ private final JarApplicationReader reader;
+
+ private ClassFileResourceReader(
+ ClassKind classKind, ClassFileResourceProvider provider, JarApplicationReader reader) {
+ super(classKind);
+ this.classKind = classKind;
+ this.provider = provider;
+ this.reader = reader;
+ }
+
+ @Override
+ public void collectClass(DexType type, Consumer<T> classConsumer) {
+ String descriptor = type.descriptor.toString();
+ Resource resource = provider.getResource(descriptor);
+ if (resource != null) {
+ try (Closer closer = Closer.create()) {
+ JarClassFileReader classReader =
+ new JarClassFileReader(reader, classKind.bridgeConsumer(classConsumer));
+ classReader.read(DEFAULT_DEX_FILENAME, classKind, resource.getStream(closer));
+ } catch (IOException e) {
+ throw new CompilationError("Failed to load class: " + descriptor, e);
+ }
+ }
+ }
+
+ @Override
+ public Collection<DexType> collectTypes() {
+ List<DexType> types = new ArrayList<>();
+ for (String descriptor : provider.getClassDescriptors()) {
+ types.add(reader.options.itemFactory.createType(descriptor));
+ }
+ return types;
+ }
+
+ @Override
+ public String toString() {
+ return "class-resource-provider(" + provider.toString() + ")";
+ }
+ }
+
+ private static class PreloadedClassProvider<T extends DexClass> extends ClassProvider<T> {
+ private final Multimap<DexType, T> classes;
+
+ private PreloadedClassProvider(ClassKind classKind, Multimap<DexType, T> classes) {
+ super(classKind);
+ this.classes = classes;
+ }
+
+ @Override
+ public void collectClass(DexType type, Consumer<T> classConsumer) {
+ for (T clazz : classes.get(type)) {
+ classConsumer.accept(clazz);
+ }
+ }
+
+ @Override
+ public Collection<DexType> collectTypes() {
+ return classes.keys();
+ }
+
+ @Override
+ public String toString() {
+ return "preloaded(" + classes.size() + ")";
+ }
+ }
+
+ private static class CombinedClassProvider<T extends DexClass> extends ClassProvider<T> {
+ private final List<ClassProvider<T>> providers;
+
+ private CombinedClassProvider(ClassKind classKind, List<ClassProvider<T>> providers) {
+ super(classKind);
+ this.providers = providers;
+ }
+
+ @Override
+ public void collectClass(DexType type, Consumer<T> classConsumer) {
+ for (ClassProvider<T> provider : providers) {
+ provider.collectClass(type, classConsumer);
+ }
+ }
+
+ @Override
+ public Collection<DexType> collectTypes() {
+ Set<DexType> types = Sets.newIdentityHashSet();
+ for (ClassProvider<T> provider : providers) {
+ types.addAll(provider.collectTypes());
+ }
+ return types;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ String prefix = "combined(";
+ for (ClassProvider<T> provider : providers) {
+ builder.append(prefix);
+ prefix = ", ";
+ builder.append(provider);
+ }
+ return builder.append(")").toString();
+ }
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java b/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java
new file mode 100644
index 0000000..ee00c39
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ClasspathClassCollection.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexClasspathClass;
+
+/** Represents a collection of classpath classes. */
+public class ClasspathClassCollection extends ClassMap<DexClasspathClass> {
+ public ClasspathClassCollection(ClassProvider<DexClasspathClass> classProvider) {
+ super(null, classProvider);
+ }
+
+ @Override
+ DexClasspathClass resolveClassConflict(DexClasspathClass a, DexClasspathClass b) {
+ throw new CompilationError("Classpath type already present: " + a.type.toSourceString());
+ }
+
+ @Override
+ ClassKind getClassKind() {
+ return ClassKind.CLASSPATH;
+ }
+
+ @Override
+ public String toString() {
+ return "classpath classes: " + super.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryClassFileProvider.java b/src/main/java/com/android/tools/r8/utils/DirectoryClassFileProvider.java
new file mode 100644
index 0000000..c060ead
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryClassFileProvider.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
+
+import com.android.tools.r8.ClassFileResourceProvider;
+import com.android.tools.r8.Resource;
+import com.google.common.collect.Sets;
+import java.io.File;
+import java.nio.file.Path;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Lazy resource provider returning class file resources based
+ * on filesystem directory content.
+ */
+public final class DirectoryClassFileProvider implements ClassFileResourceProvider {
+ private final Path root;
+
+ private DirectoryClassFileProvider(Path root) {
+ this.root = root;
+ }
+
+ @Override
+ public Set<String> getClassDescriptors() {
+ HashSet<String> result = Sets.newHashSet();
+ collectClassDescriptors(root, result);
+ return result;
+ }
+
+ private void collectClassDescriptors(Path dir, Set<String> result) {
+ File file = dir.toFile();
+ if (file.exists()) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File child : files) {
+ if (child.isDirectory()) {
+ collectClassDescriptors(child.toPath(), result);
+ } else {
+ String relative = root.relativize(child.toPath()).toString();
+ if (relative.endsWith(CLASS_EXTENSION)) {
+ result.add("L" + relative.substring(
+ 0, relative.length() - CLASS_EXTENSION.length()) + ";");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public Resource getResource(String descriptor) {
+ assert DescriptorUtils.isClassDescriptor(descriptor);
+
+ // Build expected file path based on type descriptor.
+ String classBinaryName = DescriptorUtils.getClassBinaryNameFromDescriptor(descriptor);
+ Path filePath = root.resolve(classBinaryName + CLASS_EXTENSION);
+ File file = filePath.toFile();
+
+ return (file.exists() && !file.isDirectory())
+ ? Resource.fromFile(Resource.Kind.CLASSFILE, filePath) : null;
+ }
+
+ /** Create resource provider from directory path. */
+ public static ClassFileResourceProvider fromDirectory(Path dir) {
+ return new DirectoryClassFileProvider(dir.toAbsolutePath());
+ }
+
+ @Override
+ public String toString() {
+ return "directory(" + root + ")";
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryResourceProvider.java b/src/main/java/com/android/tools/r8/utils/DirectoryResourceProvider.java
deleted file mode 100644
index b003d86..0000000
--- a/src/main/java/com/android/tools/r8/utils/DirectoryResourceProvider.java
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION;
-
-import com.android.tools.r8.Resource;
-import com.android.tools.r8.ResourceProvider;
-import java.io.File;
-import java.nio.file.Path;
-
-/**
- * Lazy resource provider based on filesystem directory content.
- *
- * NOTE: only handles classfile resources.
- */
-public final class DirectoryResourceProvider implements ResourceProvider {
- private final Path root;
-
- private DirectoryResourceProvider(Path root) {
- this.root = root;
- }
-
- @Override
- public Resource getResource(String descriptor) {
- assert DescriptorUtils.isClassDescriptor(descriptor);
-
- // Build expected file path based on type descriptor.
- String classBinaryName = DescriptorUtils.getClassBinaryNameFromDescriptor(descriptor);
- Path filePath = root.resolve(classBinaryName + CLASS_EXTENSION);
- File file = filePath.toFile();
-
- return (file.exists() && !file.isDirectory())
- ? Resource.fromFile(Resource.Kind.CLASSFILE, filePath) : null;
- }
-
- /** Create resource provider from directory path. */
- public static ResourceProvider fromDirectory(Path dir) {
- return new DirectoryResourceProvider(dir.toAbsolutePath());
- }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/LazyClassCollection.java b/src/main/java/com/android/tools/r8/utils/LazyClassCollection.java
deleted file mode 100644
index d7379ef..0000000
--- a/src/main/java/com/android/tools/r8/utils/LazyClassCollection.java
+++ /dev/null
@@ -1,170 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import static com.android.tools.r8.utils.FileUtils.DEFAULT_DEX_FILENAME;
-
-import com.android.tools.r8.Resource;
-import com.android.tools.r8.ResourceProvider;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.graph.ClassKind;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexClass;
-import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.JarApplicationReader;
-import com.android.tools.r8.graph.JarClassFileReader;
-import com.google.common.collect.ImmutableList;
-import com.google.common.io.Closer;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.IdentityHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Represents a collection of classes loaded lazily from a set of lazy resource
- * providers. The collection is autonomous, it lazily collects classes but
- * does not choose the classes in cases of conflicts, delaying it until
- * the class is asked for.
- *
- * NOTE: only java class resources are allowed to be lazy loaded.
- */
-public final class LazyClassCollection {
- // For each type which has ever been queried stores one or several classes loaded
- // from resources provided by different resource providers. In majority of the
- // cases there will only be one class per type. We store classes for all the
- // resource providers and resolve the classes at the time it is queried.
- //
- // Must be synchronized on `classes`.
- private final Map<DexType, DexClass[]> classes = new IdentityHashMap<>();
-
- // Available lazy resource providers.
- private final List<ResourceProvider> classpathProviders;
- private final List<ResourceProvider> libraryProviders;
-
- // Application reader to be used. Note that the reader may be reused in
- // many loaders and may be used concurrently, it is considered to be
- // thread-safe since its only state is internal options which we
- // consider immutable after they are initialized (barring dex factory
- // which is thread-safe).
- private final JarApplicationReader reader;
-
- public LazyClassCollection(JarApplicationReader reader,
- List<ResourceProvider> classpathProviders, List<ResourceProvider> libraryProviders) {
- this.classpathProviders = ImmutableList.copyOf(classpathProviders);
- this.libraryProviders = ImmutableList.copyOf(libraryProviders);
- this.reader = reader;
- }
-
- /**
- * Returns a definition for a class or `null` if there is no such class.
- * Parameter `dominator` represents a class that is considered
- * to be already loaded, it may be null but if specified it may affect
- * the conflict resolution. For example non-lazy loaded classpath class
- * provided as `dominator` will conflict with lazy-loaded classpath classes.
- */
- public DexClass get(DexType type, DexClass dominator) {
- DexClass[] candidates;
- synchronized (classes) {
- candidates = classes.get(type);
- }
-
- if (candidates == null) {
- String descriptor = type.descriptor.toString();
-
- // Loading resources and constructing classes may be time consuming, we do it
- // outside the global lock so others don't have to wait.
- List<Resource> classpathResources = collectResources(classpathProviders, descriptor);
- List<Resource> libraryResources = collectResources(libraryProviders, descriptor);
-
- candidates = new DexClass[classpathResources.size() + libraryResources.size()];
-
- // Check if someone else has already added the array for this type.
- synchronized (classes) {
- DexClass[] existing = classes.get(type);
- if (existing != null) {
- assert candidates.length == existing.length;
- candidates = existing;
- } else {
- classes.put(type, candidates);
- }
- }
-
- if (candidates.length > 0) {
- // Load classes in synchronized content unique for the type.
- synchronized (candidates) {
- // Either all or none of the array classes will be loaded, so we use this
- // as a criteria for checking if we need to load classes.
- if (candidates[0] == null) {
- new ClassLoader(type, candidates, reader, classpathResources, libraryResources).load();
- }
- }
- }
- }
-
- // Choose class in case there are conflicts.
- DexClass candidate = dominator;
- for (DexClass clazz : candidates) {
- candidate = (candidate == null) ? clazz
- : DexApplication.chooseClass(candidate, clazz, /* skipLibDups: */ true);
- }
- return candidate;
- }
-
- private List<Resource> collectResources(List<ResourceProvider> providers, String descriptor) {
- List<Resource> resources = new ArrayList<>();
- for (ResourceProvider provider : providers) {
- Resource resource = provider.getResource(descriptor);
- if (resource != null) {
- resources.add(resource);
- }
- }
- return resources;
- }
-
- private static final class ClassLoader {
- int index = 0;
- final DexType type;
- final DexClass[] classes;
- final JarApplicationReader reader;
- final List<Resource> classpathResources;
- final List<Resource> libraryResources;
-
- ClassLoader(DexType type, DexClass[] classes, JarApplicationReader reader,
- List<Resource> classpathResources, List<Resource> libraryResources) {
- this.type = type;
- this.classes = classes;
- this.reader = reader;
- this.classpathResources = classpathResources;
- this.libraryResources = libraryResources;
- }
-
- void addClass(DexClass clazz) {
- assert index < classes.length;
- assert clazz != null;
- if (clazz.type != type) {
- throw new CompilationError("Class content provided for type descriptor "
- + type.toSourceString() + " actually defines class " + clazz.type
- .toSourceString());
- }
- classes[index++] = clazz;
- }
-
- void load() {
- try (Closer closer = Closer.create()) {
- for (Resource resource : classpathResources) {
- JarClassFileReader classReader = new JarClassFileReader(reader, this::addClass);
- classReader.read(DEFAULT_DEX_FILENAME, ClassKind.CLASSPATH, resource.getStream(closer));
- }
- for (Resource resource : libraryResources) {
- JarClassFileReader classReader = new JarClassFileReader(reader, this::addClass);
- classReader.read(DEFAULT_DEX_FILENAME, ClassKind.LIBRARY, resource.getStream(closer));
- }
- } catch (IOException e) {
- throw new CompilationError("Failed to load class: " + type.toSourceString(), e);
- }
- assert index == classes.length;
- }
- }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java b/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java
new file mode 100644
index 0000000..e11e660
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/LibraryClassCollection.java
@@ -0,0 +1,42 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexLibraryClass;
+import com.android.tools.r8.logging.Log;
+
+/** Represents a collection of library classes. */
+public class LibraryClassCollection extends ClassMap<DexLibraryClass> {
+ public LibraryClassCollection(ClassProvider<DexLibraryClass> classProvider) {
+ super(null, classProvider);
+ }
+
+ @Override
+ DexLibraryClass resolveClassConflict(DexLibraryClass a, DexLibraryClass b) {
+ if (a.origin != Resource.Kind.CLASSFILE || b.origin != Resource.Kind.CLASSFILE) {
+ // We only support conflicts for classes both coming from jar files.
+ throw new CompilationError(
+ "Library type already present: " + a.type.toSourceString());
+ }
+ if (Log.ENABLED) {
+ Log.warn(DexApplication.class,
+ "Class `%s` was specified twice as a library type.", a.type.toSourceString());
+ }
+ return a;
+ }
+
+ @Override
+ ClassKind getClassKind() {
+ return ClassKind.LIBRARY;
+ }
+
+ @Override
+ public String toString() {
+ return "library classes: " + super.toString();
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
similarity index 80%
rename from src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java
rename to src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
index 9601ff6..07c136d 100644
--- a/src/main/java/com/android/tools/r8/utils/PreloadedResourceProvider.java
+++ b/src/main/java/com/android/tools/r8/utils/PreloadedClassFileProvider.java
@@ -7,9 +7,10 @@
import static com.android.tools.r8.utils.FileUtils.isArchive;
import static com.android.tools.r8.utils.FileUtils.isClassFile;
+import com.android.tools.r8.ClassFileResourceProvider;
import com.android.tools.r8.Resource;
-import com.android.tools.r8.ResourceProvider;
import com.android.tools.r8.errors.CompilationError;
+import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import java.io.File;
@@ -20,23 +21,25 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipInputStream;
-/**
- * Lazy resource provider based on preloaded/prebuilt context.
- *
- * NOTE: only handles classfile resources.
- */
-public final class PreloadedResourceProvider implements ResourceProvider {
+/** Lazy Java class file resource provider based on preloaded/prebuilt context. */
+public final class PreloadedClassFileProvider implements ClassFileResourceProvider {
private final Map<String, byte[]> content;
- private PreloadedResourceProvider(Map<String, byte[]> content) {
+ private PreloadedClassFileProvider(Map<String, byte[]> content) {
this.content = content;
}
@Override
+ public Set<String> getClassDescriptors() {
+ return Sets.newHashSet(content.keySet());
+ }
+
+ @Override
public Resource getResource(String descriptor) {
byte[] bytes = content.get(descriptor);
if (bytes == null) {
@@ -46,7 +49,7 @@
}
/** Create preloaded content resource provider from archive file. */
- public static ResourceProvider fromArchive(Path archive) throws IOException {
+ public static ClassFileResourceProvider fromArchive(Path archive) throws IOException {
assert isArchive(archive);
Builder builder = builder();
try (ZipInputStream stream = new ZipInputStream(new FileInputStream(archive.toFile()))) {
@@ -85,6 +88,11 @@
return 'L' + descriptor + ';';
}
+ @Override
+ public String toString() {
+ return content.size() + " preloaded resources";
+ }
+
/** Create a new empty builder. */
public static Builder builder() {
return new Builder();
@@ -105,9 +113,9 @@
return this;
}
- public PreloadedResourceProvider build() {
+ public PreloadedClassFileProvider build() {
assert content != null;
- PreloadedResourceProvider provider = new PreloadedResourceProvider(content);
+ PreloadedClassFileProvider provider = new PreloadedClassFileProvider(content);
content = null;
return provider;
}
diff --git a/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
new file mode 100644
index 0000000..a37eb92
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/ProgramClassCollection.java
@@ -0,0 +1,63 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.Resource;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.ClassKind;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import java.util.IdentityHashMap;
+import java.util.List;
+
+/** Represents a collection of library classes. */
+public class ProgramClassCollection extends ClassMap<DexProgramClass> {
+ public static ProgramClassCollection create(List<DexProgramClass> classes) {
+ // We have all classes preloaded, but not necessarily without conflicts.
+ IdentityHashMap<DexType, Value<DexProgramClass>> map = new IdentityHashMap<>();
+ for (DexProgramClass clazz : classes) {
+ Value<DexProgramClass> value = map.get(clazz.type);
+ if (value == null) {
+ value = new Value<>(clazz);
+ map.put(clazz.type, value);
+ } else {
+ value.clazz = resolveClassConflictImpl(value.clazz, clazz);
+ }
+ }
+ return new ProgramClassCollection(map);
+ }
+
+ private ProgramClassCollection(IdentityHashMap<DexType, Value<DexProgramClass>> classes) {
+ super(classes, null);
+ }
+
+ @Override
+ public String toString() {
+ return "program classes: " + super.toString();
+ }
+
+ @Override
+ DexProgramClass resolveClassConflict(DexProgramClass a, DexProgramClass b) {
+ return resolveClassConflictImpl(a, b);
+ }
+
+ @Override
+ ClassKind getClassKind() {
+ return ClassKind.PROGRAM;
+ }
+
+ private static DexProgramClass resolveClassConflictImpl(DexProgramClass a, DexProgramClass b) {
+ // Currently only allow collapsing synthetic lambda classes.
+ if (a.getOrigin() == Resource.Kind.DEX
+ && b.getOrigin() == Resource.Kind.DEX
+ && a.accessFlags.isSynthetic()
+ && b.accessFlags.isSynthetic()
+ && LambdaRewriter.hasLambdaClassPrefix(a.type)
+ && LambdaRewriter.hasLambdaClassPrefix(b.type)) {
+ return a;
+ }
+ throw new CompilationError("Program type already present: " + a.type.toSourceString());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
index 8449bd9..34cd83c 100644
--- a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
@@ -4,8 +4,8 @@
package com.android.tools.r8;
-import com.android.tools.r8.utils.DirectoryResourceProvider;
-import com.android.tools.r8.utils.PreloadedResourceProvider;
+import com.android.tools.r8.utils.DirectoryClassFileProvider;
+import com.android.tools.r8.utils.PreloadedClassFileProvider;
import java.io.IOException;
import java.nio.file.Path;
@@ -25,13 +25,13 @@
private void addClasspathPath(Path location, D8Command.Builder builder) {
builder.addClasspathResourceProvider(
- DirectoryResourceProvider.fromDirectory(location.resolve("..")));
+ DirectoryClassFileProvider.fromDirectory(location.resolve("..")));
}
@Override
void addLibraryReference(D8Command.Builder builder, Path location) throws IOException {
builder.addLibraryResourceProvider(
- PreloadedResourceProvider.fromArchive(location));
+ PreloadedClassFileProvider.fromArchive(location));
}
}
diff --git a/src/test/java/com/android/tools/r8/R8EntryPointTests.java b/src/test/java/com/android/tools/r8/R8EntryPointTests.java
new file mode 100644
index 0000000..c83cea6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8EntryPointTests.java
@@ -0,0 +1,124 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class R8EntryPointTests extends TestBase {
+
+ private static final String MAPPING = "mapping.txt";
+ private static final String SEEDS = "seeds.txt";
+ private static final Path INPUT_JAR =
+ Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "minification" + FileUtils.JAR_EXTENSION);
+ private static final Path PROGUARD_FLAGS =
+ Paths.get(ToolHelper.EXAMPLES_DIR, "minification", "keep-rules.txt");
+
+ private Path testFlags;
+
+ @Before
+ public void setup() throws IOException {
+ testFlags = temp.newFile("local.flags").toPath();
+ FileUtils.writeTextFile(testFlags, ImmutableList.of(
+ "-printseeds " + SEEDS,
+ "-printmapping " + MAPPING));
+ }
+
+ @Test
+ public void testRun1Dir() throws IOException, CompilationException, ProguardRuleParserException {
+ Path out = temp.newFolder("outdex").toPath();
+ R8.run(getCommand(out));
+ Assert.assertTrue(Files.isRegularFile(out.resolve(FileUtils.DEFAULT_DEX_FILENAME)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(MAPPING)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(SEEDS)));
+ }
+
+ @Test
+ public void testRun1Zip() throws IOException, CompilationException, ProguardRuleParserException {
+ Path out = temp.newFolder("outdex").toPath().resolve("dex.zip");
+ R8.run(getCommand(out));
+ Assert.assertTrue(Files.isRegularFile(out));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(MAPPING)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(SEEDS)));
+ }
+
+ @Test
+ public void testRun2Dir() throws IOException, CompilationException, ProguardRuleParserException {
+ Path out = temp.newFolder("outdex").toPath();
+ ExecutorService executor = Executors.newWorkStealingPool(2);
+ try {
+ R8.run(getCommand(out), executor);
+ } finally {
+ executor.shutdown();
+ }
+ Assert.assertTrue(Files.isRegularFile(out.resolve(FileUtils.DEFAULT_DEX_FILENAME)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(MAPPING)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(SEEDS)));
+ }
+
+ @Test
+ public void testRun2Zip() throws IOException, CompilationException, ProguardRuleParserException {
+ Path out = temp.newFolder("outdex").toPath().resolve("dex.zip");
+ ExecutorService executor = Executors.newWorkStealingPool(2);
+ try {
+ R8.run(getCommand(out), executor);
+ } finally {
+ executor.shutdown();
+ }
+ Assert.assertTrue(Files.isRegularFile(out));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(MAPPING)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(SEEDS)));
+ }
+
+ @Test
+ public void testMainDir() throws IOException, InterruptedException {
+ Path out = temp.newFolder("outdex").toPath();
+ ProcessResult r8 = ToolHelper.forkR8(Paths.get("."),
+ "--lib", ToolHelper.getDefaultAndroidJar(),
+ "--output", out.toString(),
+ "--pg-conf", PROGUARD_FLAGS.toString(),
+ "--pg-conf", testFlags.toString(),
+ INPUT_JAR.toString());
+ Assert.assertEquals(0, r8.exitCode);
+ Assert.assertTrue(Files.isRegularFile(out.resolve(FileUtils.DEFAULT_DEX_FILENAME)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(MAPPING)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(SEEDS)));
+ }
+
+ @Test
+ public void testMainZip() throws IOException, InterruptedException {
+ Path out = temp.newFolder("outdex").toPath().resolve("dex.zip");
+ ProcessResult r8 = ToolHelper.forkR8(Paths.get("."),
+ "--lib", ToolHelper.getDefaultAndroidJar(),
+ "--output", out.toString(),
+ "--pg-conf", PROGUARD_FLAGS.toString(),
+ "--pg-conf", testFlags.toString(),
+ INPUT_JAR.toString());
+ Assert.assertEquals(0, r8.exitCode);
+ Assert.assertTrue(Files.isRegularFile(out));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(MAPPING)));
+ Assert.assertTrue(Files.isRegularFile(testFlags.getParent().resolve(SEEDS)));
+ }
+
+ private R8Command getCommand(Path out) throws CompilationException, IOException {
+ return R8Command.builder()
+ .addLibraryFiles(Paths.get(ToolHelper.getDefaultAndroidJar()))
+ .addProgramFiles(INPUT_JAR)
+ .setOutputPath(out)
+ .addProguardConfigurationFiles(PROGUARD_FLAGS, testFlags)
+ .build();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
index 9be6930..5e265aa 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesTest.java
@@ -14,6 +14,7 @@
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.JarBuilder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -40,7 +41,6 @@
public class R8RunExamplesTest {
private static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_BUILD_DIR;
- private static final String DEFAULT_DEX_FILENAME = "classes.dex";
// For local testing on a specific Art version(s) change this set. e.g. to
// ImmutableSet.of(DexVm.ART_DEFAULT) or pass the option -Ddex_vm=<version> to the Java VM.
@@ -161,7 +161,7 @@
}
private Path getOriginalDexFile() {
- return Paths.get(EXAMPLE_DIR, pkg, DEFAULT_DEX_FILENAME);
+ return Paths.get(EXAMPLE_DIR, pkg, FileUtils.DEFAULT_DEX_FILENAME);
}
@Rule
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index ba69bff..08e90c0 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -38,7 +38,7 @@
}
@Override
- public int getRegisterForRangedArgument(Value value, int instructionNumber) {
+ public int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber) {
return value.getNumber();
}
}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index dc49caf..d05c800 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -287,7 +287,7 @@
method.setCode(ir, allocator, factory);
virtualMethods[i] = method;
}
- builder.addClass(
+ builder.addProgramClass(
new DexProgramClass(
type,
null,
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index f5b2b1a..8f46572 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -11,13 +11,16 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.errors.CompilationError;
+import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
+import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
@@ -95,7 +98,11 @@
actualMapping = new String(Files.readAllBytes(outputmapping), StandardCharsets.UTF_8);
String refMapping = new String(Files.readAllBytes(
Paths.get(EXAMPLES_DIR, "shaking1", "print-mapping.ref")), StandardCharsets.UTF_8);
- Assert.assertEquals(refMapping, actualMapping);
+ Assert.assertEquals(sorted(refMapping), sorted(actualMapping));
}
+ private static String sorted(String str) {
+ return new BufferedReader(new StringReader(str))
+ .lines().sorted().collect(Collectors.joining("\n"));
+ }
}
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
index 5e67ad1..ca30f76 100644
--- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
+++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -93,7 +93,7 @@
assertTrue(
defBlock.getInstructions().get(defBlock.getInstructions().size() - 2).isInvoke());
for (BasicBlock returnPredecessor : block.getPredecessors()) {
- if (defBlock.isCatchSuccessor(returnPredecessor)) {
+ if (defBlock.hasCatchSuccessor(returnPredecessor)) {
hasExceptionalPredecessor = true;
} else if (defBlock == returnPredecessor) {
// Normal flow goes to return.
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 966f786..480774f 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -8,6 +8,7 @@
import r8
import d8
import sys
+import utils
import gmscore_data
import youtube_data
@@ -49,8 +50,12 @@
help='')
result.add_option('-k',
help='Override the default ProGuard keep rules')
+ result.add_option('--compiler-flags',
+ help='Additional option(s) for the compiler. ' +
+ 'If passing several options use a quoted string.')
result.add_option('--r8-flags',
- help='Additional option(s) for R8. ' +
+ help='Additional option(s) for the compiler. ' +
+ 'Same as --compiler-flags, keeping it for backward compatibility. ' +
'If passing several options use a quoted string.')
result.add_option('--track-memory-to-file',
help='Track how much memory the jvm is using while ' +
@@ -65,6 +70,17 @@
'the run.')
return result.parse_args()
+# Most apps have the -printmapping and -printseeds in the Proguard
+# configuration. However we don't want to write these files in these
+# locations. Instead generate an auxiliary Proguard configuration
+# placing these two output files together with the dex output.
+def GenerateAdditionalProguardConfiguration(temp, outdir):
+ name = "output.config"
+ with open(os.path.join(temp, name), 'w') as file:
+ file.write('-printmapping ' + os.path.join(outdir, 'proguard.map') + "\n")
+ file.write('-printseeds ' + os.path.join(outdir, 'proguard.seeds') + "\n")
+ return os.path.abspath(file.name)
+
def main():
(options, args) = ParseOptions()
outdir = options.out
@@ -119,8 +135,11 @@
if options.compiler == 'r8':
if 'r8-flags' in values:
args.extend(values['r8-flags'].split(' '))
- if options.r8_flags:
- args.extend(options.r8_flags.split(' '))
+
+ if options.compiler_flags:
+ args.extend(options.compiler_flags.split(' '))
+ if options.r8_flags:
+ args.extend(options.r8_flags.split(' '))
if inputs:
args.extend(inputs)
@@ -133,8 +152,16 @@
d8.run(args, not options.no_build, not options.no_debug, options.profile,
options.track_memory_to_file)
else:
- r8.run(args, not options.no_build, not options.no_debug, options.profile,
- options.track_memory_to_file)
+ with utils.TempDir() as temp:
+ if outdir.endswith('.zip') or outdir.endswith('.jar'):
+ pg_outdir = os.path.dirname(outdir)
+ else:
+ pg_outdir = outdir
+ additional_pg_conf = GenerateAdditionalProguardConfiguration(
+ temp, os.path.abspath(pg_outdir))
+ args.extend(['--pg-conf', additional_pg_conf])
+ r8.run(args, not options.no_build, not options.no_debug, options.profile,
+ options.track_memory_to_file)
if __name__ == '__main__':
sys.exit(main())
diff --git a/tools/windows/README.dx b/tools/windows/README.dx
index 99db256..348dc4a 100644
--- a/tools/windows/README.dx
+++ b/tools/windows/README.dx
@@ -21,3 +21,5 @@
---
> call java %javaOpts% -Djava.ext.dirs="%frameworkdir%" -jar "%jarpath%" %params%
+dexmerger.bat has been copied from dx.bat, and the command line has been updated
+according to the SDK dexmerger bash script to call the right class.
\ No newline at end of file
diff --git a/tools/windows/dx.tar.gz.sha1 b/tools/windows/dx.tar.gz.sha1
index beadad3..0f3e345 100644
--- a/tools/windows/dx.tar.gz.sha1
+++ b/tools/windows/dx.tar.gz.sha1
@@ -1 +1 @@
-1d680c9efc9e17d4fc51e8afd0bbe1b3d8724903
\ No newline at end of file
+9adeae753e17fa0a663e4d458b406a39ded27621
\ No newline at end of file