Merge "Add API to get R8 version"
diff --git a/.gitignore b/.gitignore
index 14e24ec..c04d9dd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,6 +63,8 @@
 third_party/kotlin
 third_party/nest/*
 !third_party/nest/*.sha1
+third_party/opensource_apps
+third_party/opensource_apps.tar.gz
 third_party/photos/*
 !third_party/photos/*.sha1
 third_party/proguard/*
diff --git a/build.gradle b/build.gradle
index f129fb5..4ce2e18 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,6 @@
 ext {
     androidSupportVersion = '25.4.0'
     asmVersion = '6.2.1'
-    autoValueVersion = '1.5'
     espressoVersion = '3.0.0'
     fastutilVersion = '7.2.0'
     guavaVersion = '23.0'
@@ -66,7 +65,6 @@
     dependencies {
         classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.4'
         classpath "net.ltgt.gradle:gradle-errorprone-plugin:0.0.13"
-        classpath "net.ltgt.gradle:gradle-apt-plugin:0.12"
         classpath "com.gradle:build-scan-plugin:1.14"
     }
 }
@@ -81,7 +79,6 @@
 apply plugin: 'java'
 apply plugin: 'idea'
 apply plugin: 'net.ltgt.errorprone-base'
-apply plugin: "net.ltgt.apt"
 
 if (project.hasProperty('with_code_coverage')) {
     apply plugin: 'jacoco'
@@ -257,7 +254,6 @@
     examplesCompile "com.google.guava:guava:$guavaVersion"
     examplesCompile "junit:junit:$junitVersion"
     examplesCompile "org.mockito:mockito-core:$mockitoVersion"
-    examplesCompileOnly "com.google.auto.value:auto-value:$autoValueVersion"
     supportLibs "com.android.support:support-v4:$androidSupportVersion"
     supportLibs "junit:junit:$junitVersion"
     supportLibs "com.android.support.test.espresso:espresso-core:$espressoVersion"
@@ -266,7 +262,6 @@
     debugTestResourcesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     examplesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
     kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
-    apt "com.google.auto.value:auto-value:$autoValueVersion"
 }
 
 def r8LibPath = "$buildDir/libs/r8lib.jar"
diff --git a/src/cf_segments/java/com/android/tools/r8/cf_segments/Metrics.java b/src/cf_segments/java/com/android/tools/r8/cf_segments/Metrics.java
index 57786e1..8a08fde 100644
--- a/src/cf_segments/java/com/android/tools/r8/cf_segments/Metrics.java
+++ b/src/cf_segments/java/com/android/tools/r8/cf_segments/Metrics.java
@@ -63,7 +63,7 @@
   public final SegmentInfo maxLocals = new SegmentInfo("MaxLocal", false);
   public final SegmentInfo maxStacks = new SegmentInfo("MaxStack", false);
   public final SegmentInfo methodInfo = new SegmentInfo("Method");
-  public final SegmentInfo size = new SegmentInfo("Size").increment(1, 0);
+  public final SegmentInfo size = new SegmentInfo("Total").increment(1, 0);
   public final SegmentInfo stores = new SegmentInfo("Stores", false);
   public final SegmentInfo stackMapTable = new SegmentInfo("StackMapTable");
   public final SegmentInfo stackmapTableOtherEntries = new SegmentInfo("StackMapTableOtherEntries");
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index f78912f..27c564a 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -58,6 +58,7 @@
     InternalOptions options = command.getInternalOptions();
     options.enableDesugaring = false;
     options.enableMainDexListCheck = false;
+    options.ignoreMainDexMissingClasses = true;
     options.minimalMainDex = false;
     options.enableMinification = false;
     options.enableInlining = false;
diff --git a/src/main/java/com/android/tools/r8/ResourceShrinker.java b/src/main/java/com/android/tools/r8/ResourceShrinker.java
index 47ff5ee..6267db1 100644
--- a/src/main/java/com/android/tools/r8/ResourceShrinker.java
+++ b/src/main/java/com/android/tools/r8/ResourceShrinker.java
@@ -241,11 +241,11 @@
               .filter(DexEncodedField::hasAnnotation)
               .flatMap(f -> Arrays.stream(f.annotations.annotations));
       Stream<DexAnnotation> virtualMethodAnnotations =
-          Arrays.stream(classDef.virtualMethods())
+          classDef.virtualMethods().stream()
               .filter(DexEncodedMethod::hasAnnotation)
               .flatMap(m -> Arrays.stream(m.annotations.annotations));
       Stream<DexAnnotation> directMethodAnnotations =
-          Arrays.stream(classDef.directMethods())
+          classDef.directMethods().stream()
               .filter(DexEncodedMethod::hasAnnotation)
               .flatMap(m -> Arrays.stream(m.annotations.annotations));
       Stream<DexAnnotation> classAnnotations = Arrays.stream(classDef.annotations.annotations);
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index 3eda1b7..a84b916 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -45,6 +45,10 @@
     return opcode;
   }
 
+  public boolean isInterface() {
+    return itf;
+  }
+
   @Override
   public void write(MethodVisitor visitor, NamingLens lens) {
     String owner = lens.lookupInternalName(method.getHolder());
diff --git a/src/main/java/com/android/tools/r8/dex/FileWriter.java b/src/main/java/com/android/tools/r8/dex/FileWriter.java
index 5e71072..1af80fa 100644
--- a/src/main/java/com/android/tools/r8/dex/FileWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/FileWriter.java
@@ -563,7 +563,7 @@
   }
 
   private void writeEncodedFields(DexEncodedField[] fields) {
-    assert PresortedComparable.isSorted(fields);
+    assert PresortedComparable.isSorted(Arrays.asList(fields));
     int currentOffset = 0;
     for (DexEncodedField field : fields) {
       int nextOffset = mapping.getOffsetFor(field.field);
@@ -574,7 +574,7 @@
     }
   }
 
-  private void writeEncodedMethods(DexEncodedMethod[] methods, boolean clearBodies) {
+  private void writeEncodedMethods(List<DexEncodedMethod> methods, boolean clearBodies) {
     assert PresortedComparable.isSorted(methods);
     int currentOffset = 0;
     for (DexEncodedMethod method : methods) {
@@ -602,8 +602,8 @@
     mixedSectionOffsets.setOffsetFor(clazz, dest.position());
     dest.putUleb128(clazz.staticFields().length);
     dest.putUleb128(clazz.instanceFields().length);
-    dest.putUleb128(clazz.directMethods().length);
-    dest.putUleb128(clazz.virtualMethods().length);
+    dest.putUleb128(clazz.directMethods().size());
+    dest.putUleb128(clazz.virtualMethods().size());
     writeEncodedFields(clazz.staticFields());
     writeEncodedFields(clazz.instanceFields());
 
diff --git a/src/main/java/com/android/tools/r8/dex/VirtualFile.java b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
index 0e62775..0d12915 100644
--- a/src/main/java/com/android/tools/r8/dex/VirtualFile.java
+++ b/src/main/java/com/android/tools/r8/dex/VirtualFile.java
@@ -307,11 +307,13 @@
             mainDexFile.addClass(programClass);
             classes.remove(programClass);
           } else {
-            options.reporter.warning(
-                new StringDiagnostic(
-                    "Application does not contain `"
-                        + type.toSourceString()
-                        + "` as referenced in main-dex-list."));
+            if (!options.ignoreMainDexMissingClasses) {
+              options.reporter.warning(
+                  new StringDiagnostic(
+                      "Application does not contain `"
+                          + type.toSourceString()
+                          + "` as referenced in main-dex-list."));
+            }
           }
           mainDexFile.commitTransaction();
         }
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index 9062ae7..4b89d9e 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -86,7 +86,7 @@
 
   @Keep
   public static final class Options {
-    private final DiagnosticsHandler diagnosticsHandler = new DiagnosticsHandler() {};
+    private final DiagnosticsHandler diagnosticsHandler;
     private List<String> inputArchives = new ArrayList<>();
     private List<FeatureJar> featureJars = new ArrayList<>();
     private List<String> baseJars = new ArrayList<>();
@@ -97,6 +97,14 @@
     private String mainDexList;
     private boolean splitNonClassResources = false;
 
+    public Options() {
+      this(new DiagnosticsHandler() {});
+    }
+
+    public Options(DiagnosticsHandler diagnosticsHandler) {
+      this.diagnosticsHandler = diagnosticsHandler;
+    }
+
     public DiagnosticsHandler getDiagnosticsHandler() {
       return diagnosticsHandler;
     }
@@ -293,7 +301,9 @@
       throw new AbortException();
     }
 
-    D8Command.Builder builder = D8Command.builder();
+    D8Command.Builder builder = D8Command.builder(options.diagnosticsHandler);
+
+
     for (String s : options.inputArchives) {
       builder.addProgramFiles(Paths.get(s));
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
index fa5d8a8..1ef2f68 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAnnotationDirectory.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.OrderedMergingIterator;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Function;
 
@@ -37,10 +38,11 @@
         parameterAnnotations.add(method);
       }
     }
-    assert isSorted(clazz.staticFields());
-    assert isSorted(clazz.instanceFields());
+    assert isSorted(Arrays.asList(clazz.staticFields()));
+    assert isSorted(Arrays.asList(clazz.instanceFields()));
     OrderedMergingIterator<DexEncodedField, DexField> fields =
-        new OrderedMergingIterator<>(clazz.staticFields(), clazz.instanceFields());
+        new OrderedMergingIterator<>(
+            Arrays.asList(clazz.staticFields()), Arrays.asList(clazz.instanceFields()));
     fieldAnnotations = new ArrayList<>();
     while (fields.hasNext()) {
       DexEncodedField field = fields.next();
@@ -108,11 +110,13 @@
     throw new Unreachable();
   }
 
-  private static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
+  private static <T extends PresortedComparable<T>> boolean isSorted(
+      List<? extends KeyedDexItem<T>> items) {
     return isSorted(items, KeyedDexItem::getKey);
   }
 
-  private static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
+  private static <S, T extends Comparable<T>> boolean isSorted(
+      List<S> items, Function<S, T> getter) {
     T current = null;
     for (S item : items) {
       T next = getter.apply(item);
diff --git a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
index 21900a01..60893c7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
+++ b/src/main/java/com/android/tools/r8/graph/DexByteCodeWriter.java
@@ -11,7 +11,6 @@
 import java.io.PrintStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.Arrays;
 import java.util.function.Consumer;
 
 public abstract class DexByteCodeWriter {
@@ -62,7 +61,8 @@
   private void write(ThrowingFunction<DexClass, PrintStream, IOException> outputStreamProvider,
       Consumer<PrintStream> closer)
       throws IOException {
-    for (DexProgramClass clazz : application.classes()) {
+    Iterable<DexProgramClass> classes = application.classesWithDeterministicOrder();
+    for (DexProgramClass clazz : classes) {
       if (anyMethodMatches(clazz)) {
         PrintStream ps = outputStreamProvider.apply(clazz);
         try {
@@ -76,8 +76,8 @@
 
   private boolean anyMethodMatches(DexClass clazz) {
     return !options.hasMethodsFilter()
-        || Arrays.stream(clazz.virtualMethods()).anyMatch(options::methodMatchesFilter)
-        || Arrays.stream(clazz.directMethods()).anyMatch(options::methodMatchesFilter);
+        || clazz.virtualMethods().stream().anyMatch(options::methodMatchesFilter)
+        || clazz.directMethods().stream().anyMatch(options::methodMatchesFilter);
   }
 
   private void writeClass(DexProgramClass clazz, PrintStream ps) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 0ca5602..28aabea 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -8,10 +8,14 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.kotlin.KotlinInfo;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Predicates;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -19,6 +23,11 @@
 
 public abstract class DexClass extends DexDefinition {
 
+  public interface MethodSetter {
+
+    void setMethod(int index, DexEncodedMethod method);
+  }
+
   private static final DexEncodedMethod[] NO_METHODS = {};
   private static final DexEncodedField[] NO_FIELDS = {};
 
@@ -124,20 +133,106 @@
     throw new Unreachable();
   }
 
-  public DexEncodedMethod[] directMethods() {
-    return directMethods;
+  public List<DexEncodedMethod> directMethods() {
+    assert directMethods != null;
+    if (InternalOptions.assertionsEnabled()) {
+      return Collections.unmodifiableList(Arrays.asList(directMethods));
+    }
+    return Arrays.asList(directMethods);
+  }
+
+  public void appendDirectMethod(DexEncodedMethod method) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + 1];
+    System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
+    newMethods[directMethods.length] = method;
+    directMethods = newMethods;
+    assert verifyNoDuplicateMethods(directMethods);
+  }
+
+  public void appendDirectMethods(Collection<DexEncodedMethod> methods) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length + methods.size()];
+    System.arraycopy(directMethods, 0, newMethods, 0, directMethods.length);
+    int i = directMethods.length;
+    for (DexEncodedMethod method : methods) {
+      newMethods[i] = method;
+      i++;
+    }
+    directMethods = newMethods;
+    assert verifyNoDuplicateMethods(directMethods);
+  }
+
+  public void removeDirectMethod(int index) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[directMethods.length - 1];
+    System.arraycopy(directMethods, 0, newMethods, 0, index);
+    System.arraycopy(directMethods, index + 1, newMethods, index, directMethods.length - index - 1);
+    directMethods = newMethods;
+  }
+
+  public void setDirectMethod(int index, DexEncodedMethod method) {
+    directMethods[index] = method;
+    assert verifyNoDuplicateMethods(directMethods);
   }
 
   public void setDirectMethods(DexEncodedMethod[] values) {
     directMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+    assert verifyNoDuplicateMethods(directMethods);
   }
 
-  public DexEncodedMethod[] virtualMethods() {
-    return virtualMethods;
+  public List<DexEncodedMethod> virtualMethods() {
+    assert virtualMethods != null;
+    if (InternalOptions.assertionsEnabled()) {
+      return Collections.unmodifiableList(Arrays.asList(virtualMethods));
+    }
+    return Arrays.asList(virtualMethods);
+  }
+
+  public void appendVirtualMethod(DexEncodedMethod method) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + 1];
+    System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
+    newMethods[virtualMethods.length] = method;
+    virtualMethods = newMethods;
+    assert verifyNoDuplicateMethods(virtualMethods);
+  }
+
+  public void appendVirtualMethods(Collection<DexEncodedMethod> methods) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length + methods.size()];
+    System.arraycopy(virtualMethods, 0, newMethods, 0, virtualMethods.length);
+    int i = virtualMethods.length;
+    for (DexEncodedMethod method : methods) {
+      newMethods[i] = method;
+      i++;
+    }
+    virtualMethods = newMethods;
+    assert verifyNoDuplicateMethods(virtualMethods);
+  }
+
+  public void removeVirtualMethod(int index) {
+    DexEncodedMethod[] newMethods = new DexEncodedMethod[virtualMethods.length - 1];
+    System.arraycopy(virtualMethods, 0, newMethods, 0, index);
+    System.arraycopy(
+        virtualMethods, index + 1, newMethods, index, virtualMethods.length - index - 1);
+    virtualMethods = newMethods;
+  }
+
+  public void setVirtualMethod(int index, DexEncodedMethod method) {
+    virtualMethods[index] = method;
+    assert verifyNoDuplicateMethods(virtualMethods);
   }
 
   public void setVirtualMethods(DexEncodedMethod[] values) {
     virtualMethods = MoreObjects.firstNonNull(values, NO_METHODS);
+    assert verifyNoDuplicateMethods(virtualMethods);
+  }
+
+  private boolean verifyNoDuplicateMethods(DexEncodedMethod[] methods) {
+    Set<DexMethod> unique = Sets.newIdentityHashSet();
+    Arrays.stream(methods)
+        .forEach(
+            method -> {
+              boolean changed = unique.add(method.method);
+              assert changed : "Duplicate method `" + method.method.toSourceString() + "`";
+            });
+    return true;
   }
 
   public void forEachMethod(Consumer<DexEncodedMethod> consumer) {
@@ -289,14 +384,14 @@
    * Find direct method in this class matching method.
    */
   public DexEncodedMethod lookupDirectMethod(DexMethod method) {
-    return lookupTarget(directMethods(), method);
+    return lookupTarget(directMethods, method);
   }
 
   /**
    * Find virtual method in this class matching method.
    */
   public DexEncodedMethod lookupVirtualMethod(DexMethod method) {
-    return lookupTarget(virtualMethods(), method);
+    return lookupTarget(virtualMethods, method);
   }
 
   /**
@@ -377,7 +472,9 @@
   }
 
   public DexEncodedMethod getClassInitializer() {
-    return Arrays.stream(directMethods()).filter(DexEncodedMethod::isClassInitializer).findAny()
+    return Arrays.stream(directMethods)
+        .filter(DexEncodedMethod::isClassInitializer)
+        .findAny()
         .orElse(null);
   }
 
@@ -452,7 +549,8 @@
   }
 
   public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
-    if (ignore.test(type)) {
+    if (ignore.test(type)
+        || appInfo.dexItemFactory.libraryTypesWithoutStaticInitialization.contains(type)) {
       return false;
     }
     if (hasNonTrivialClassInitializer()) {
@@ -483,7 +581,7 @@
 
   public boolean defaultValuesForStaticFieldsMayTriggerAllocation() {
     return Arrays.stream(staticFields())
-        .anyMatch(field -> !field.getStaticValue().mayTriggerAllocation());
+        .anyMatch(field -> field.getStaticValue().mayTriggerAllocation());
   }
 
   public List<InnerClassAttribute> getInnerClasses() {
@@ -552,7 +650,7 @@
 
   public boolean isValid() {
     assert !isInterface()
-        || Arrays.stream(virtualMethods()).noneMatch(method -> method.accessFlags.isFinal());
+        || Arrays.stream(virtualMethods).noneMatch(method -> method.accessFlags.isFinal());
     return true;
   }
 }
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 32d3c37..f956b53 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -638,15 +638,15 @@
     return builder.build();
   }
 
-  public DexEncodedMethod toForwardingMethod(DexClass holder, DexItemFactory itemFactory) {
+  public DexEncodedMethod toForwardingMethod(DexClass holder, AppInfo appInfo) {
     checkIfObsolete();
-    assert accessFlags.isPublic();
     // Clear the final flag, as this method is now overwritten. Do this before creating the builder
     // for the forwarding method, as the forwarding method will copy the access flags from this,
     // and if different forwarding methods are created in different subclasses the first could be
     // final.
     accessFlags.demoteFromFinal();
-    DexMethod newMethod = itemFactory.createMethod(holder.type, method.proto, method.name);
+    DexMethod newMethod =
+        appInfo.dexItemFactory.createMethod(holder.type, method.proto, method.name);
     Invoke.Type type = accessFlags.isStatic() ? Invoke.Type.STATIC : Invoke.Type.SUPER;
     Builder builder = builder(this);
     builder.setMethod(newMethod);
@@ -656,6 +656,7 @@
       builder.accessFlags.setAbstract();
     } else {
       // Create code that forwards the call to the target.
+      DexClass target = appInfo.definitionFor(method.holder);
       builder.setCode(
           new SynthesizedCode(
               callerPosition ->
@@ -666,7 +667,8 @@
                       accessFlags.isStatic() ? null : method.holder,
                       method,
                       type,
-                      callerPosition),
+                      callerPosition,
+                      target.isInterface()),
               registry -> {
                 if (accessFlags.isStatic()) {
                   registry.registerInvokeStatic(method);
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index 54895ac..ebaad34 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -300,6 +300,7 @@
   public final DexType metafactoryType = createType("Ljava/lang/invoke/LambdaMetafactory;");
   public final DexType callSiteType = createType("Ljava/lang/invoke/CallSite;");
   public final DexType lookupType = createType("Ljava/lang/invoke/MethodHandles$Lookup;");
+  public final DexType iteratorType = createType("Ljava/util/Iterator;");
   public final DexType serializableType = createType("Ljava/io/Serializable;");
   public final DexType externalizableType = createType("Ljava/io/Externalizable;");
   public final DexType comparableType = createType("Ljava/lang/Comparable;");
@@ -350,6 +351,9 @@
           createString("makeConcat")
       );
 
+  public final Set<DexType> libraryTypesWithoutStaticInitialization =
+      ImmutableSet.of(iteratorType, serializableType);
+
   private boolean skipNameValidationForTesting = false;
 
   public void setSkipNameValidationForTesting(boolean skipNameValidationForTesting) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
index dafb07c..57b08fb 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -226,7 +226,7 @@
 
   public boolean hasMethodsOrFields() {
     int numberOfFields = staticFields().length + instanceFields().length;
-    int numberOfMethods = directMethods().length + virtualMethods().length;
+    int numberOfMethods = directMethods().size() + virtualMethods().size();
     return numberOfFields + numberOfMethods > 0;
   }
 
@@ -269,7 +269,7 @@
     // It does not actually hurt to compute this multiple times. So racing on staticValues is OK.
     if (staticValues == SENTINEL_NOT_YET_COMPUTED) {
       synchronized (staticFields) {
-        assert PresortedComparable.isSorted(staticFields);
+        assert PresortedComparable.isSorted(Arrays.asList(staticFields));
         DexEncodedField[] fields = staticFields;
         int length = 0;
         List<DexValue> values = new ArrayList<>(fields.length);
diff --git a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
index a4ee2ee..563b40a 100644
--- a/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
+++ b/src/main/java/com/android/tools/r8/graph/PresortedComparable.java
@@ -4,15 +4,23 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.naming.NamingLens;
+import java.util.Arrays;
+import java.util.List;
 import java.util.function.Function;
 
 public interface PresortedComparable<T> extends Presorted, Comparable<T> {
 
-  static <T extends PresortedComparable<T>> boolean isSorted(KeyedDexItem<T>[] items) {
+  static <T extends PresortedComparable<T>> boolean isSorted(
+      List<? extends KeyedDexItem<T>> items) {
     return isSorted(items, KeyedDexItem::getKey);
   }
 
   static <S, T extends Comparable<T>> boolean isSorted(S[] items, Function<S, T> getter) {
+    return isSorted(Arrays.asList(items), getter);
+  }
+
+  static <S, T extends Comparable<T>> boolean isSorted(
+      List<? extends S> items, Function<S, T> getter) {
     T current = null;
     for (S item : items) {
       T next = getter.apply(item);
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
index 2aa7f47..c48740d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodHandle.java
@@ -14,6 +14,8 @@
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
 
 public class ConstMethodHandle extends ConstInstruction {
 
@@ -69,6 +71,12 @@
   }
 
   @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forConstMethodHandle();
+  }
+
+  @Override
   public int maxOutValueRegister() {
     return Constants.U8BIT_MAX;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index 8a46e1e..743f2a5 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.cf.code.CfFrame;
 import com.android.tools.r8.cf.code.CfFrame.FrameType;
 import com.android.tools.r8.cf.code.CfInstruction;
+import com.android.tools.r8.cf.code.CfInvoke;
 import com.android.tools.r8.cf.code.CfLabel;
 import com.android.tools.r8.cf.code.CfPosition;
 import com.android.tools.r8.cf.code.CfTryCatch;
@@ -21,6 +22,7 @@
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.CfCode.LocalVariableInfo;
 import com.android.tools.r8.graph.DebugLocalInfo;
+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.DexField;
@@ -179,9 +181,23 @@
     CodeRewriter.collapseTrivialGotos(method, code);
     DexBuilder.removeRedundantDebugPositions(code);
     CfCode code = buildCfCode();
+    assert verifyInvokeInterface(code, appInfo);
     return code;
   }
 
+  private static boolean verifyInvokeInterface(CfCode code, AppInfo appInfo) {
+    for (CfInstruction instruction : code.instructions) {
+      if (instruction instanceof CfInvoke) {
+        CfInvoke invoke = (CfInvoke) instruction;
+        if (invoke.getMethod().holder.isClassType()) {
+          DexClass holder = appInfo.definitionFor(invoke.getMethod().holder);
+          assert holder == null || holder.isInterface() == invoke.isInterface();
+        }
+      }
+    }
+    return true;
+  }
+
   public DexField resolveField(DexField field) {
     DexEncodedField resolvedField = appInfo.resolveFieldOn(field.clazz, field);
     return resolvedField == null ? field : resolvedField.field;
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 31b0ec6..3f355c1 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
@@ -1405,19 +1405,6 @@
     add(Invoke.create(type, item, callSiteProto, null, arguments, itf));
   }
 
-  public void addInvoke(Type type, DexItem item, DexProto callSiteProto, List<Value> arguments) {
-    addInvoke(type, item, callSiteProto, arguments, false);
-  }
-
-  public void addInvoke(
-      Type type,
-      DexItem item,
-      DexProto callSiteProto,
-      List<ValueType> types,
-      List<Integer> registers) {
-    addInvoke(type, item, callSiteProto, types, registers, false);
-  }
-
   public void addInvoke(
       Type type,
       DexItem item,
@@ -1518,7 +1505,9 @@
       registerIndex += constraint.requiredRegisters();
     }
     checkInvokeArgumentRegisters(registerIndex, argumentRegisterCount);
-    addInvoke(type, method, callSiteProto, arguments);
+    // Note: We only call this register variant from DEX inputs where isInterface does not matter.
+    assert !isGeneratingClassFiles();
+    addInvoke(type, method, callSiteProto, arguments, false /* isInterface */);
   }
 
   public void addInvokeNewArray(DexType type, int argumentCount, int[] argumentRegisters) {
@@ -1538,7 +1527,7 @@
       registerIndex += constraint.requiredRegisters();
     }
     checkInvokeArgumentRegisters(registerIndex, argumentCount);
-    addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments);
+    addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments, false /* isInterface */);
   }
 
   public void addMultiNewArray(DexType type, int dest, int[] dimensions) {
@@ -1547,7 +1536,7 @@
     for (int dimension : dimensions) {
       arguments.add(readRegister(dimension, ValueTypeConstraint.INT));
     }
-    addInvoke(Invoke.Type.MULTI_NEW_ARRAY, type, null, arguments);
+    addInvoke(Invoke.Type.MULTI_NEW_ARRAY, type, null, arguments, false /* isInterface */);
     addMoveResult(dest);
   }
 
@@ -1581,7 +1570,9 @@
       register += valueTypeConstraint.requiredRegisters();
     }
     checkInvokeArgumentRegisters(register, firstArgumentRegister + argumentCount);
-    addInvoke(type, method, callSiteProto, arguments);
+    // Note: We only call this register variant from DEX inputs where isInterface does not matter.
+    assert !isGeneratingClassFiles();
+    addInvoke(type, method, callSiteProto, arguments, false /* isInterface */);
   }
 
   public void addInvokeRangeNewArray(DexType type, int argumentCount, int firstArgumentRegister) {
@@ -1597,7 +1588,9 @@
       register += constraint.requiredRegisters();
     }
     checkInvokeArgumentRegisters(register, firstArgumentRegister + argumentCount);
-    addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments);
+    // Note: We only call this register variant from DEX inputs where isInterface does not matter.
+    assert !isGeneratingClassFiles();
+    addInvoke(Invoke.Type.NEW_ARRAY, type, null, arguments, false /* isInterface */);
   }
 
   private void checkInvokeArgumentRegisters(int expected, int actual) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 1387f75..93e5355 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -2864,7 +2864,13 @@
     List<ValueType> argumentTypes = Arrays.asList(valueType(CLASS_TYPE), valueType(INT_ARRAY_TYPE));
     List<Integer> argumentRegisters = Arrays.asList(classDestTemp, dimensionsDestTemp);
     builder.ensureBlockForThrowingInstruction();
-    builder.addInvoke(Invoke.Type.STATIC, newInstance, null, argumentTypes, argumentRegisters);
+    builder.addInvoke(
+        Invoke.Type.STATIC,
+        newInstance,
+        null,
+        argumentTypes,
+        argumentRegisters,
+        false /* isInterface */);
     // Pop the temporaries and push the final result.
     state.pop(); // classDestTemp.
     state.pop(); // dimensionsDestTemp.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index 3554806..81f93a7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -109,8 +109,17 @@
     }
 
     // Method call to the original impl-method.
-    add(builder -> builder.addInvoke(inferInvokeType(),
-        implMethod, implMethod.proto, argValueTypes, argRegisters));
+    // Mirroring assert in constructor, we never need accessors to interfaces.
+    assert !descriptor().implHandle.type.isInvokeInterface();
+    add(
+        builder ->
+            builder.addInvoke(
+                inferInvokeType(),
+                implMethod,
+                implMethod.proto,
+                argValueTypes,
+                argRegisters,
+                false /* isInterface */));
 
     // Does the method have return value?
     if (proto.returnType == factory().voidType) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index d2a233b..ec788a4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -85,25 +84,22 @@
     }
 
     // Add the methods.
-    DexEncodedMethod[] existing = clazz.virtualMethods();
-    clazz.setVirtualMethods(new DexEncodedMethod[existing.length + methodsToImplement.size()]);
-    System.arraycopy(existing, 0, clazz.virtualMethods(), 0, existing.length);
-
-    for (int i = 0; i < methodsToImplement.size(); i++) {
-      DexEncodedMethod method = methodsToImplement.get(i);
+    List<DexEncodedMethod> newForwardingMethods = new ArrayList<>(methodsToImplement.size());
+    for (DexEncodedMethod method : methodsToImplement) {
       assert method.accessFlags.isPublic() && !method.accessFlags.isAbstract();
       DexEncodedMethod newMethod = addForwardingMethod(method, clazz);
-      clazz.virtualMethods()[existing.length + i] = newMethod;
+      newForwardingMethods.add(newMethod);
       createdMethods.put(newMethod, method);
     }
+    clazz.appendVirtualMethods(newForwardingMethods);
   }
 
   private DexEncodedMethod addForwardingMethod(DexEncodedMethod defaultMethod, DexClass clazz) {
     DexMethod method = defaultMethod.method;
+    DexClass target = rewriter.findDefinitionFor(method.holder);
     // NOTE: Never add a forwarding method to methods of classes unknown or coming from android.jar
     // even if this results in invalid code, these classes are never desugared.
-    assert rewriter.findDefinitionFor(method.holder) != null
-        && !rewriter.findDefinitionFor(method.holder).isLibraryClass();
+    assert target != null && !target.isLibraryClass();
     // New method will have the same name, proto, and also all the flags of the
     // default method, including bridge flag.
     DexMethod newMethod = rewriter.factory.createMethod(clazz.type, method.proto, method.name);
@@ -124,7 +120,8 @@
                     null /* static method */,
                     rewriter.defaultAsMethodOfCompanionClass(method),
                     Invoke.Type.STATIC,
-                    callerPosition)));
+                    callerPosition,
+                    target.isInterface())));
   }
 
   // For a given class `clazz` inspects all interfaces it implements directly or
@@ -148,7 +145,7 @@
         helper.merge(rewriter.getOrCreateInterfaceInfo(clazz, current, type));
       }
 
-      accumulatedVirtualMethods.addAll(Arrays.asList(clazz.virtualMethods()));
+      accumulatedVirtualMethods.addAll(clazz.virtualMethods());
 
       List<DexEncodedMethod> defaultMethodsInDirectInterface = helper.createFullList();
 
@@ -201,7 +198,7 @@
     current = clazz;
     while (true) {
       // Hide candidates by virtual method of the class.
-      hideCandidates(Arrays.asList(current.virtualMethods()), candidates, toBeImplemented);
+      hideCandidates(current.virtualMethods(), candidates, toBeImplemented);
       if (candidates.isEmpty()) {
         return toBeImplemented;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
index 955abf0..0c76382 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/CovariantReturnTypeAnnotationTransformer.java
@@ -96,16 +96,7 @@
           method.annotations.keepIf(x -> !isCovariantReturnTypeAnnotation(x.annotation));
     }
     // Add the newly constructed methods to the class.
-    DexEncodedMethod[] oldVirtualMethods = clazz.virtualMethods();
-    DexEncodedMethod[] newVirtualMethods =
-        new DexEncodedMethod[oldVirtualMethods.length + covariantReturnTypeMethods.size()];
-    System.arraycopy(oldVirtualMethods, 0, newVirtualMethods, 0, oldVirtualMethods.length);
-    int i = oldVirtualMethods.length;
-    for (DexEncodedMethod syntheticMethod : covariantReturnTypeMethods) {
-      newVirtualMethods[i] = syntheticMethod;
-      i++;
-    }
-    clazz.setVirtualMethods(newVirtualMethods);
+    clazz.appendVirtualMethods(covariantReturnTypeMethods);
   }
 
   // Processes all the dalvik.annotation.codegen.CovariantReturnType and dalvik.annotation.codegen.
@@ -175,7 +166,8 @@
                         method.method,
                         Invoke.Type.VIRTUAL,
                         callerPosition,
-                        true)));
+                        false /* isInterface */,
+                        true /* castResult */)));
     // Optimize to generate DexCode instead of SynthesizedCode.
     converter.optimizeSynthesizedMethod(newVirtualMethod);
     return newVirtualMethod;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 01b647b..9e18c94 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -114,7 +114,7 @@
     }
 
     // If at least one bridge method was removed then update the table.
-    if (remainingMethods.size() < iface.virtualMethods().length) {
+    if (remainingMethods.size() < iface.virtualMethods().size()) {
       iface.setVirtualMethods(remainingMethods.toArray(
           new DexEncodedMethod[remainingMethods.size()]));
     }
@@ -169,7 +169,7 @@
         }
       }
     }
-    if (remainingMethods.size() < iface.directMethods().length) {
+    if (remainingMethods.size() < iface.directMethods().size()) {
       iface.setDirectMethods(remainingMethods.toArray(
           new DexEncodedMethod[remainingMethods.size()]));
     }
@@ -252,7 +252,8 @@
                           null,
                           origMethod,
                           Type.STATIC,
-                          callerPosition)));
+                          callerPosition,
+                          true /* isInterface */)));
       newEncodedMethod.getMutableOptimizationInfo().markNeverInline();
       dispatchMethods.add(newEncodedMethod);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
index 7e5cd81..9507471 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaBridgeMethodSourceCode.java
@@ -46,8 +46,15 @@
     }
 
     // Method call to the main functional interface method.
-    add(builder -> builder.addInvoke(Invoke.Type.VIRTUAL,
-        this.mainMethod, this.mainMethod.proto, argValueTypes, argRegisters));
+    add(
+        builder ->
+            builder.addInvoke(
+                Invoke.Type.VIRTUAL,
+                this.mainMethod,
+                this.mainMethod.proto,
+                argValueTypes,
+                argRegisters,
+                false /* isInterface */));
 
     // Does the method have return value?
     if (proto.returnType == factory().voidType) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index b78f6df..a0bc184 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -513,9 +513,9 @@
       DexMethod implMethod = descriptor.implHandle.asMethod();
       DexClass implMethodHolder = definitionFor(implMethod.holder);
 
-      DexEncodedMethod[] directMethods = implMethodHolder.directMethods();
-      for (int i = 0; i < directMethods.length; i++) {
-        DexEncodedMethod encodedMethod = directMethods[i];
+      List<DexEncodedMethod> directMethods = implMethodHolder.directMethods();
+      for (int i = 0; i < directMethods.size(); i++) {
+        DexEncodedMethod encodedMethod = directMethods.get(i);
         if (implMethod.match(encodedMethod)) {
           // We need to create a new static method with the same code to be able to safely
           // relax its accessibility without making it virtual.
@@ -539,7 +539,7 @@
           dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
           assert (dexCode.getDebugInfo() == null)
               || (callTarget.getArity() == dexCode.getDebugInfo().parameters.length);
-          directMethods[i] = newMethod;
+          implMethodHolder.setDirectMethod(i, newMethod);
           return true;
         }
       }
@@ -579,20 +579,11 @@
 
       // We may arrive here concurrently so we need must update the methods of the class atomically.
       synchronized (accessorClass) {
-        accessorClass.setDirectMethods(
-            appendMethod(accessorClass.directMethods(), accessorEncodedMethod));
+        accessorClass.appendDirectMethod(accessorEncodedMethod);
       }
 
       rewriter.converter.optimizeSynthesizedMethod(accessorEncodedMethod);
       return true;
     }
-
-    private DexEncodedMethod[] appendMethod(DexEncodedMethod[] methods, DexEncodedMethod method) {
-      int size = methods.length;
-      DexEncodedMethod[] newMethods = new DexEncodedMethod[size + 1];
-      System.arraycopy(methods, 0, newMethods, 0, size);
-      newMethods[size] = method;
-      return newMethods;
-    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
index 2aa4630..cec70bb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClassConstructorSourceCode.java
@@ -24,12 +24,15 @@
     // Create and initialize an instance.
     int instance = nextRegister(ValueType.OBJECT);
     add(builder -> builder.addNewInstance(instance, lambda.type));
-    add(builder -> builder.addInvoke(
-        Invoke.Type.DIRECT,
-        lambda.constructor,
-        lambda.constructor.proto,
-        ImmutableList.of(ValueType.OBJECT),
-        ImmutableList.of(instance)));
+    add(
+        builder ->
+            builder.addInvoke(
+                Invoke.Type.DIRECT,
+                lambda.constructor,
+                lambda.constructor.proto,
+                ImmutableList.of(ValueType.OBJECT),
+                ImmutableList.of(instance),
+                false /* isInterface */));
 
     // Assign to a field.
     add(builder -> builder.addStaticPut(instance, lambda.instanceField));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
index 7f2acc3..b2b9486 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaConstructorSourceCode.java
@@ -23,8 +23,14 @@
   protected void prepareInstructions() {
     // Super constructor call (always java.lang.Object.<init>()).
     DexMethod objectInitMethod = lambda.rewriter.objectInitMethod;
-    add(builder -> builder.addInvoke(Invoke.Type.DIRECT, objectInitMethod,
-        objectInitMethod.proto, Collections.singletonList(getReceiverValue())));
+    add(
+        builder ->
+            builder.addInvoke(
+                Invoke.Type.DIRECT,
+                objectInitMethod,
+                objectInitMethod.proto,
+                Collections.singletonList(getReceiverValue()),
+                false /* isInterface */));
 
     // Assign capture fields.
     DexType[] capturedTypes = captures();
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
index fb06f31..99112d7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaMainMethodSourceCode.java
@@ -224,8 +224,15 @@
     }
 
     // Method call to the method implementing lambda or method-ref.
-    add(builder -> builder.addInvoke(target.invokeType,
-        methodToCall, methodToCall.proto, argValueTypes, argRegisters));
+    add(
+        builder ->
+            builder.addInvoke(
+                target.invokeType,
+                methodToCall,
+                methodToCall.proto,
+                argValueTypes,
+                argRegisters,
+                false /* isInterface */));
 
     // Does the method have return value?
     if (enforcedReturnType.isVoidType()) {
@@ -446,8 +453,15 @@
 
     List<ValueType> argValueTypes = ImmutableList.of(ValueType.OBJECT);
     List<Integer> argRegisters = Collections.singletonList(register);
-    add(builder -> builder.addInvoke(Invoke.Type.VIRTUAL,
-        method, method.proto, argValueTypes, argRegisters));
+    add(
+        builder ->
+            builder.addInvoke(
+                Invoke.Type.VIRTUAL,
+                method,
+                method.proto,
+                argValueTypes,
+                argRegisters,
+                false /* isInterface */));
 
     ValueType valueType = ValueType.fromDexType(primitiveType);
     int result = nextRegister(valueType);
@@ -469,8 +483,15 @@
     ValueType valueType = ValueType.fromDexType(primitiveType);
     List<ValueType> argValueTypes = ImmutableList.of(valueType);
     List<Integer> argRegisters = Collections.singletonList(register);
-    add(builder -> builder.addInvoke(Invoke.Type.STATIC,
-        method, method.proto, argValueTypes, argRegisters));
+    add(
+        builder ->
+            builder.addInvoke(
+                Invoke.Type.STATIC,
+                method,
+                method.proto,
+                argValueTypes,
+                argRegisters,
+                false /* isInterface */));
 
     int result = nextRegister(ValueType.OBJECT);
     add(builder -> builder.addMoveResult(result));
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index df52ef1..4e29e6f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -129,33 +129,26 @@
 
   /** Remove lambda deserialization methods. */
   public boolean removeLambdaDeserializationMethods(Iterable<DexProgramClass> classes) {
-    boolean anyRemoved = false;
     for (DexProgramClass clazz : classes) {
       // Search for a lambda deserialization method and remove it if found.
-      DexEncodedMethod[] directMethods = clazz.directMethods();
+      List<DexEncodedMethod> directMethods = clazz.directMethods();
       if (directMethods != null) {
-        int methodCount = directMethods.length;
+        int methodCount = directMethods.size();
         for (int i = 0; i < methodCount; i++) {
-          DexEncodedMethod encoded = directMethods[i];
+          DexEncodedMethod encoded = directMethods.get(i);
           DexMethod method = encoded.method;
           if (method.isLambdaDeserializeMethod(appInfo.dexItemFactory)) {
             assert encoded.accessFlags.isStatic();
             assert encoded.accessFlags.isSynthetic();
-
-            DexEncodedMethod[] newMethods = new DexEncodedMethod[methodCount - 1];
-            System.arraycopy(directMethods, 0, newMethods, 0, i);
-            System.arraycopy(directMethods, i + 1, newMethods, i, methodCount - i - 1);
-            clazz.setDirectMethods(newMethods);
-
-            anyRemoved = true;
+            clazz.removeDirectMethod(i);
 
             // We assume there is only one such method in the class.
-            break;
+            return true;
           }
         }
       }
     }
-    return anyRemoved;
+    return false;
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 359223d..30712e6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1055,8 +1055,9 @@
     //      that it is not the instance being initialized.
     //
     boolean instanceInitializer = method.isInstanceInitializer();
-    if (method.accessFlags.isNative() ||
-        (!method.isNonAbstractVirtualMethod() && !instanceInitializer)) {
+    if (method.accessFlags.isNative()
+        || (!method.isNonAbstractVirtualMethod() && !instanceInitializer)
+        || method.accessFlags.isSynchronized()) {
       return;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index 3292704..873eb20 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -259,6 +259,10 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
+  public ConstraintWithTarget forConstMethodHandle() {
+    return ConstraintWithTarget.NEVER;
+  }
+
   private ConstraintWithTarget forFieldInstruction(
       DexField field, DexEncodedField target, DexType invocationContext) {
     // Resolve the field if possible and decide whether the instruction can inlined.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 4cc3207..ff330e1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -179,9 +179,9 @@
           }
 
           // Change the return type of direct methods that return an uninstantiated type to void.
-          DexEncodedMethod[] directMethods = clazz.directMethods();
-          for (int i = 0; i < directMethods.length; ++i) {
-            DexEncodedMethod encodedMethod = directMethods[i];
+          List<DexEncodedMethod> directMethods = clazz.directMethods();
+          for (int i = 0; i < directMethods.size(); ++i) {
+            DexEncodedMethod encodedMethod = directMethods.get(i);
             DexMethod method = encodedMethod.method;
             RewrittenPrototypeDescription prototypeChanges =
                 prototypeChangesPerMethod.getOrDefault(
@@ -194,7 +194,7 @@
               // TODO(b/110806787): Can be extended to handle collisions by renaming the given
               // method.
               if (usedSignatures.add(wrapper)) {
-                directMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+                clazz.setDirectMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
                 methodMapping.put(method, newMethod);
                 if (removedArgumentsInfo.hasRemovedArguments()) {
                   removedArgumentsInfoPerMethod.put(newMethod, removedArgumentsInfo);
@@ -209,9 +209,9 @@
           // all supertypes of the current class are always visited prior to the current class.
           // This is important to ensure that a method that used to override a method in its super
           // class will continue to do so after this optimization.
-          DexEncodedMethod[] virtualMethods = clazz.virtualMethods();
-          for (int i = 0; i < virtualMethods.length; ++i) {
-            DexEncodedMethod encodedMethod = virtualMethods[i];
+          List<DexEncodedMethod> virtualMethods = clazz.virtualMethods();
+          for (int i = 0; i < virtualMethods.size(); ++i) {
+            DexEncodedMethod encodedMethod = virtualMethods.get(i);
             DexMethod method = encodedMethod.method;
             RewrittenPrototypeDescription prototypeChanges =
                 getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
@@ -229,13 +229,13 @@
                 boolean signatureIsAvailable = usedSignatures.add(wrapper);
                 assert signatureIsAvailable;
 
-                virtualMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+                clazz.setVirtualMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
                 methodMapping.put(method, newMethod);
               }
             }
           }
-          for (int i = 0; i < virtualMethods.length; ++i) {
-            DexEncodedMethod encodedMethod = virtualMethods[i];
+          for (int i = 0; i < virtualMethods.size(); ++i) {
+            DexEncodedMethod encodedMethod = virtualMethods.get(i);
             DexMethod method = encodedMethod.method;
             RewrittenPrototypeDescription prototypeChanges =
                 getPrototypeChanges(encodedMethod, DISALLOW_ARGUMENT_REMOVAL);
@@ -249,7 +249,7 @@
               if (!methodPool.hasSeen(wrapper) && usedSignatures.add(wrapper)) {
                 methodPool.seen(wrapper);
 
-                virtualMethods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+                clazz.setVirtualMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
                 methodMapping.put(method, newMethod);
 
                 boolean added =
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index aae8429..6211270 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -184,12 +184,13 @@
     for (DexEncodedMethod method : clazz.methods()) {
       signatures.markSignatureAsUsed(method.method);
     }
-    for (int i = 0; i < clazz.directMethods().length; i++) {
-      DexEncodedMethod method = clazz.directMethods()[i];
+    List<DexEncodedMethod> directMethods = clazz.directMethods();
+    for (int i = 0; i < directMethods.size(); i++) {
+      DexEncodedMethod method = directMethods.get(i);
       RemovedArgumentsInfo unused = collectUnusedArguments(method);
       DexEncodedMethod newMethod = signatures.removeArguments(method, unused);
       if (newMethod != null) {
-        clazz.directMethods()[i] = newMethod;
+        clazz.setDirectMethod(i, newMethod);
         synchronized (this) {
           methodMapping.put(method.method, newMethod.method);
           removedArguments.put(newMethod.method, unused);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
index 5177e4a..c6aecbb 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/ClassInitializerSourceCode.java
@@ -41,18 +41,27 @@
     List<ValueType> argTypes = Lists.newArrayList(ValueType.OBJECT, ValueType.INT);
     List<Integer> argRegisters = Lists.newArrayList(instance, lambdaId);
 
-    group.forEachLambda(info -> {
-      DexType lambda = info.clazz.type;
-      if (group.isSingletonLambda(lambda)) {
-        int id = group.lambdaId(lambda);
-        add(builder -> builder.addNewInstance(instance, groupClassType));
-        add(builder -> builder.addConst(TypeLatticeElement.INT, lambdaId, id));
-        add(builder -> builder.addInvoke(Type.DIRECT,
-            lambdaConstructorMethod, lambdaConstructorMethod.proto, argTypes, argRegisters));
-        add(builder -> builder.addStaticPut(
-            instance, group.getSingletonInstanceField(factory, id)));
-      }
-    });
+    group.forEachLambda(
+        info -> {
+          DexType lambda = info.clazz.type;
+          if (group.isSingletonLambda(lambda)) {
+            int id = group.lambdaId(lambda);
+            add(builder -> builder.addNewInstance(instance, groupClassType));
+            add(builder -> builder.addConst(TypeLatticeElement.INT, lambdaId, id));
+            add(
+                builder ->
+                    builder.addInvoke(
+                        Type.DIRECT,
+                        lambdaConstructorMethod,
+                        lambdaConstructorMethod.proto,
+                        argTypes,
+                        argRegisters,
+                        false /* isInterface*/));
+            add(
+                builder ->
+                    builder.addStaticPut(instance, group.getSingletonInstanceField(factory, id)));
+          }
+        });
 
     assert this.nextInstructionIndex() > 0 : "no single field initialized";
     add(IRBuilder::addReturn);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
index e8fcd40..2bdc441 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/JStyleLambdaGroup.java
@@ -215,8 +215,15 @@
 
     @Override
     void prepareSuperConstructorCall(int receiverRegister) {
-      add(builder -> builder.addInvoke(Type.DIRECT, objectInitializer, objectInitializer.proto,
-          Lists.newArrayList(ValueType.OBJECT), Lists.newArrayList(receiverRegister)));
+      add(
+          builder ->
+              builder.addInvoke(
+                  Type.DIRECT,
+                  objectInitializer,
+                  objectInitializer.proto,
+                  Lists.newArrayList(ValueType.OBJECT),
+                  Lists.newArrayList(receiverRegister),
+                  false /* isInterface */));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
index f677b46..ee0b800 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KStyleLambdaGroup.java
@@ -233,9 +233,15 @@
     void prepareSuperConstructorCall(int receiverRegister) {
       int arityRegister = nextRegister(ValueType.INT);
       add(builder -> builder.addConst(TypeLatticeElement.INT, arityRegister, arity));
-      add(builder -> builder.addInvoke(Type.DIRECT, lambdaInitializer, lambdaInitializer.proto,
-          Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
-          Lists.newArrayList(receiverRegister, arityRegister)));
+      add(
+          builder ->
+              builder.addInvoke(
+                  Type.DIRECT,
+                  lambdaInitializer,
+                  lambdaInitializer.proto,
+                  Lists.newArrayList(ValueType.OBJECT, ValueType.INT),
+                  Lists.newArrayList(receiverRegister, arityRegister),
+                  false /* isInterface */));
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
index 8469004..234bcc6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupIdFactory.java
@@ -144,8 +144,7 @@
   }
 
   void validateDirectMethods(DexClass lambda) throws LambdaStructureError {
-    DexEncodedMethod[] directMethods = lambda.directMethods();
-    for (DexEncodedMethod method : directMethods) {
+    for (DexEncodedMethod method : lambda.directMethods()) {
       if (method.isClassInitializer()) {
         // We expect to see class initializer only if there is a singleton field.
         if (lambda.staticFields().length != 1) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
index 82acf49..bcd0a75 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaVirtualMethodSourceCode.java
@@ -85,16 +85,18 @@
       offsets[i] = nextInstructionIndex();
 
       // Emit fake call on `this` receiver.
-      add(builder -> {
-        if (arguments.isEmpty()) {
-          // Late initialization of argument list.
-          arguments.add(getReceiverValue());
-          for (int index = 0; index < paramCount; index++) {
-            arguments.add(getParamValue(index));
-          }
-        }
-        builder.addInvoke(Type.VIRTUAL, impl.method, impl.method.proto, arguments);
-      });
+      add(
+          builder -> {
+            if (arguments.isEmpty()) {
+              // Late initialization of argument list.
+              arguments.add(getReceiverValue());
+              for (int index = 0; index < paramCount; index++) {
+                arguments.add(getParamValue(index));
+              }
+            }
+            builder.addInvoke(
+                Type.VIRTUAL, impl.method, impl.method.proto, arguments, false /* isInterface */);
+          });
 
       // Handle return value if needed.
       if (returnsValue) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index 6284338..0fd8cc4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -548,16 +548,13 @@
     }
 
     // Process static methods.
-    if (candidateClass.directMethods().length > 0) {
-      DexEncodedMethod[] oldMethods = hostClass.directMethods();
-      DexEncodedMethod[] extraMethods = candidateClass.directMethods();
-      DexEncodedMethod[] newMethods = new DexEncodedMethod[oldMethods.length + extraMethods.length];
-      System.arraycopy(oldMethods, 0, newMethods, 0, oldMethods.length);
-      for (int i = 0; i < extraMethods.length; i++) {
-        DexEncodedMethod method = extraMethods[i];
+    List<DexEncodedMethod> extraMethods = candidateClass.directMethods();
+    if (!extraMethods.isEmpty()) {
+      List<DexEncodedMethod> newMethods = new ArrayList<>(extraMethods.size());
+      for (DexEncodedMethod method : extraMethods) {
         DexEncodedMethod newMethod = method.toTypeSubstitutedMethod(
             factory().createMethod(hostType, method.method.proto, method.method.name));
-        newMethods[oldMethods.length + i] = newMethod;
+        newMethods.add(newMethod);
         staticizedMethods.add(newMethod);
         staticizedMethods.remove(method);
         DexMethod originalMethod = methodMapping.inverse().get(method.method);
@@ -567,7 +564,7 @@
           methodMapping.put(originalMethod, newMethod.method);
         }
       }
-      hostClass.setDirectMethods(newMethods);
+      hostClass.appendDirectMethods(newMethods);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
index 37341fb..76dba16 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/ForwardMethodSourceCode.java
@@ -23,6 +23,7 @@
   private final DexMethod target;
   private final Invoke.Type invokeType;
   private final boolean castResult;
+  private final boolean isInterface;
 
   public ForwardMethodSourceCode(
       DexType receiver,
@@ -31,7 +32,8 @@
       DexType targetReceiver,
       DexMethod target,
       Type invokeType,
-      Position callerPosition) {
+      Position callerPosition,
+      boolean isInterface) {
     this(
         receiver,
         method,
@@ -40,6 +42,7 @@
         target,
         invokeType,
         callerPosition,
+        isInterface,
         false);
   }
 
@@ -51,6 +54,7 @@
       DexMethod target,
       Type invokeType,
       Position callerPosition,
+      boolean isInterface,
       boolean castResult) {
     super(receiver, method, callerPosition, originalMethod);
     assert (targetReceiver == null) == (invokeType == Invoke.Type.STATIC);
@@ -58,6 +62,7 @@
     this.target = target;
     this.targetReceiver = targetReceiver;
     this.invokeType = invokeType;
+    this.isInterface = isInterface;
     this.castResult = castResult;
     assert checkSignatures();
 
@@ -119,8 +124,15 @@
     }
 
     // Method call to the target method.
-    add(builder -> builder.addInvoke(this.invokeType,
-        this.target, this.target.proto, argValueTypes, argRegisters));
+    add(
+        builder ->
+            builder.addInvoke(
+                this.invokeType,
+                this.target,
+                this.target.proto,
+                argValueTypes,
+                argRegisters,
+                this.isInterface));
 
     // Does the method return value?
     if (proto.returnType.isVoidType()) {
diff --git a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
index 18998c8..4bfb5ce 100644
--- a/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
+++ b/src/main/java/com/android/tools/r8/jar/InliningConstraintVisitor.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
 import com.android.tools.r8.ir.optimize.InliningConstraints;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import org.objectweb.asm.Handle;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -116,6 +117,8 @@
     if (cst instanceof Type && ((Type) cst).getSort() != Type.METHOD) {
       DexType type = application.getType((Type) cst);
       updateConstraint(inliningConstraints.forConstClass(type, invocationContext));
+    } else if (cst instanceof Handle) {
+      updateConstraint(inliningConstraints.forConstMethodHandle());
     } else {
       updateConstraint(inliningConstraints.forConstInstruction());
     }
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
index 3ae9d06..e3d09b6 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
@@ -16,6 +16,7 @@
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
@@ -38,6 +39,12 @@
     return copy;
   }
 
+  private <T> List<T> sortedCopy(List<T> source, Comparator<? super T> comparator) {
+    List<T> copy = new ArrayList<>(source);
+    Collections.sort(copy, comparator);
+    return copy;
+  }
+
   private void writeClass(DexProgramClass clazz, StringBuilder out) {
     seenTypes.add(clazz.type);
     DexString descriptor = namingLens.lookupDescriptor(clazz.type);
@@ -87,7 +94,7 @@
     out.append(renamed).append(NEW_LINE);
   }
 
-  private void writeMethods(DexEncodedMethod[] methods, StringBuilder out) {
+  private void writeMethods(List<DexEncodedMethod> methods, StringBuilder out) {
     for (DexEncodedMethod encodedMethod : methods) {
       DexMethod method = encodedMethod.method;
       DexString renamed = namingLens.lookupName(method);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index d5add45..b0e2f4c 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClass.MethodSetter;
 import com.android.tools.r8.graph.DexEncodedAnnotation;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -25,6 +26,7 @@
 import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
@@ -317,18 +319,18 @@
       clazz.superType = substituteType(clazz.superType, null);
       clazz.interfaces = substituteTypesIn(clazz.interfaces);
       clazz.annotations = substituteTypesIn(clazz.annotations);
-      clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
-      clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+      fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
+      fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
       clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
       clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
     }
 
-    private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
+    private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
       if (methods == null) {
-        return null;
+        return;
       }
-      for (int i = 0; i < methods.length; i++) {
-        DexEncodedMethod encodedMethod = methods[i];
+      for (int i = 0; i < methods.size(); i++) {
+        DexEncodedMethod encodedMethod = methods.get(i);
         DexMethod appliedMethod = appliedLense.lookupMethod(encodedMethod.method);
         DexType newHolderType = substituteType(appliedMethod.holder, encodedMethod);
         DexProto newProto = substituteTypesIn(appliedMethod.proto, encodedMethod);
@@ -341,9 +343,8 @@
           newMethod = appliedMethod;
         }
         // Explicitly fix members.
-        methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+        setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
       }
-      return methods;
     }
 
     private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) {
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index 3c300b6..3ff88b9 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -120,8 +120,10 @@
   }
 
   private void computeMethodRebinding(
-      Set<DexMethod> methods, Function<DexMethod, DexEncodedMethod> lookupTarget, Type invokeType) {
-    for (DexMethod method : methods) {
+      Map<DexMethod, Set<DexEncodedMethod>> methodsWithContexts,
+      Function<DexMethod, DexEncodedMethod> lookupTarget,
+      Type invokeType) {
+    for (DexMethod method : methodsWithContexts.keySet()) {
       // We can safely ignore array types, as the corresponding methods are defined in a library.
       if (!method.getHolder().isClassType()) {
         continue;
@@ -149,7 +151,10 @@
 
         // If the target class is not public but the targeted method is, we might run into
         // visibility problems when rebinding.
-        if (mayNeedBridgeForVisibility(target, targetClass)) {
+        final DexEncodedMethod finalTarget = target;
+        Set<DexEncodedMethod> contexts = methodsWithContexts.get(method);
+        if (contexts.stream().anyMatch(context ->
+            mayNeedBridgeForVisibility(context.method.getHolder(), finalTarget))) {
           target =
               insertBridgeForVisibilityIfNeeded(
                   method, target, originalClass, targetClass, lookupTarget);
@@ -186,7 +191,7 @@
         findHolderForInterfaceMethodBridge(originalClass, targetClass.type);
     assert bridgeHolder != null;
     assert bridgeHolder != targetClass;
-    DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+    DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo);
     bridgeHolder.addMethod(bridgeMethod);
     assert lookupTarget.apply(method) == bridgeMethod;
     return bridgeMethod;
@@ -205,8 +210,20 @@
     return findHolderForInterfaceMethodBridge(superClass.asProgramClass(), iface);
   }
 
-  private boolean mayNeedBridgeForVisibility(DexEncodedMethod target, DexClass targetClass) {
-    return !targetClass.accessFlags.isPublic() && target.accessFlags.isPublic();
+  private boolean mayNeedBridgeForVisibility(DexType context, DexEncodedMethod method) {
+    DexType holderType = method.method.getHolder();
+    DexClass holder = appInfo.definitionFor(holderType);
+    if (holder == null) {
+      return false;
+    }
+    ConstraintWithTarget classVisibility =
+        ConstraintWithTarget.deriveConstraint(context, holderType, holder.accessFlags, appInfo);
+    ConstraintWithTarget methodVisibility =
+        ConstraintWithTarget.deriveConstraint(context, holderType, method.accessFlags, appInfo);
+    // We may need bridge for visibility if the target class is not visible while the target method
+    // is visible from the calling context.
+    return classVisibility == ConstraintWithTarget.NEVER
+        && methodVisibility != ConstraintWithTarget.NEVER;
   }
 
   private DexEncodedMethod insertBridgeForVisibilityIfNeeded(
@@ -225,8 +242,7 @@
       DexProgramClass bridgeHolder =
           findHolderForVisibilityBridge(originalClass, targetClass, packageDescriptor);
       assert bridgeHolder != null;
-      DexEncodedMethod bridgeMethod =
-          target.toForwardingMethod(bridgeHolder, appInfo.dexItemFactory);
+      DexEncodedMethod bridgeMethod = target.toForwardingMethod(bridgeHolder, appInfo);
       bridgeHolder.addMethod(bridgeMethod);
       assert lookupTarget.apply(method) == bridgeMethod;
       return bridgeMethod;
@@ -263,15 +279,15 @@
     return null;
   }
 
-  private void computeFieldRebinding(Map<DexField, Set<DexEncodedMethod>> fields,
+  private void computeFieldRebinding(
+      Map<DexField, Set<DexEncodedMethod>> fieldsWithContexts,
       BiFunction<DexType, DexField, DexEncodedField> lookup,
       BiFunction<DexClass, DexField, DexEncodedField> lookupTargetOnClass) {
-    for (Map.Entry<DexField, Set<DexEncodedMethod>> entry : fields.entrySet()) {
-      DexField field = entry.getKey();
+    for (DexField field : fieldsWithContexts.keySet()) {
       DexEncodedField target = lookup.apply(field.getHolder(), field);
       // Rebind to the lowest library class or program class. Do not rebind accesses to fields that
       // are not visible from the access context.
-      Set<DexEncodedMethod> contexts = entry.getValue();
+      Set<DexEncodedMethod> contexts = fieldsWithContexts.get(field);
       if (target != null && target.field != field
           && contexts.stream().allMatch(context ->
               isVisibleFromOriginalContext(context.method.getHolder(), target))) {
diff --git a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
index 59caf25..f2e6c6d 100644
--- a/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
+++ b/src/main/java/com/android/tools/r8/optimize/VisibilityBridgeRemover.java
@@ -12,7 +12,7 @@
 import com.android.tools.r8.optimize.InvokeSingleTargetExtractor.InvokeKind;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.google.common.collect.Sets;
-import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 
 public class VisibilityBridgeRemover {
@@ -24,11 +24,17 @@
   }
 
   private void removeUnneededVisibilityBridgesFromClass(DexProgramClass clazz) {
-    clazz.setDirectMethods(removeUnneededVisibilityBridges(clazz.directMethods()));
-    clazz.setVirtualMethods(removeUnneededVisibilityBridges(clazz.virtualMethods()));
+    DexEncodedMethod[] newDirectMethods = removeUnneededVisibilityBridges(clazz.directMethods());
+    if (newDirectMethods != null) {
+      clazz.setDirectMethods(newDirectMethods);
+    }
+    DexEncodedMethod[] newVirtualMethods = removeUnneededVisibilityBridges(clazz.virtualMethods());
+    if (newVirtualMethods != null) {
+      clazz.setVirtualMethods(newVirtualMethods);
+    }
   }
 
-  private DexEncodedMethod[] removeUnneededVisibilityBridges(DexEncodedMethod[] methods) {
+  private DexEncodedMethod[] removeUnneededVisibilityBridges(List<DexEncodedMethod> methods) {
     Set<DexEncodedMethod> methodsToBeRemoved = null;
     for (DexEncodedMethod method : methods) {
       if (isUnneededVisibilityBridge(method)) {
@@ -40,11 +46,11 @@
     }
     if (methodsToBeRemoved != null) {
       Set<DexEncodedMethod> finalMethodsToBeRemoved = methodsToBeRemoved;
-      return Arrays.stream(methods)
+      return methods.stream()
           .filter(method -> !finalMethodsToBeRemoved.contains(method))
           .toArray(DexEncodedMethod[]::new);
     }
-    return methods;
+    return null;
   }
 
   private boolean isUnneededVisibilityBridge(DexEncodedMethod method) {
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index ae88ba7..0bdbcee 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -38,20 +38,23 @@
     DexClass holder = appInfo.definitionFor(type);
     scope = scope.newNestedScope();
     if (holder != null && !holder.isLibraryClass()) {
-      holder.setVirtualMethods(processMethods(holder.virtualMethods()));
+      DexEncodedMethod[] newVirtualMethods = processMethods(holder.virtualMethods());
+      if (newVirtualMethods != null) {
+        holder.setVirtualMethods(newVirtualMethods);
+      }
     }
     type.forAllExtendsSubtypes(this::processClass);
     scope = scope.getParent();
   }
 
-  private DexEncodedMethod[] processMethods(DexEncodedMethod[] virtualMethods) {
+  private DexEncodedMethod[] processMethods(List<DexEncodedMethod> virtualMethods) {
     if (virtualMethods == null) {
       return null;
     }
     // Removal of abstract methods is rare, so avoid copying the array until we find one.
     List<DexEncodedMethod> methods = null;
-    for (int i = 0; i < virtualMethods.length; i++) {
-      DexEncodedMethod method = virtualMethods[i];
+    for (int i = 0; i < virtualMethods.size(); i++) {
+      DexEncodedMethod method = virtualMethods.get(i);
       if (scope.addMethodIfMoreVisible(method)
           || !method.accessFlags.isAbstract()
           || appInfo.isPinned(method.method)) {
@@ -60,9 +63,9 @@
         }
       } else {
         if (methods == null) {
-          methods = new ArrayList<>(virtualMethods.length - 1);
+          methods = new ArrayList<>(virtualMethods.size() - 1);
           for (int j = 0; j < i; j++) {
-            methods.add(virtualMethods[j]);
+            methods.add(virtualMethods.get(j));
           }
         }
         if (Log.ENABLED) {
@@ -70,7 +73,10 @@
         }
       }
     }
-    return methods == null ? virtualMethods : methods.toArray(new DexEncodedMethod[methods.size()]);
+    if (methods != null) {
+      return methods.toArray(new DexEncodedMethod[methods.size()]);
+    }
+    return null;
   }
 
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index 8ce5cb0..3fa9db5 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -18,7 +18,6 @@
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
 import java.util.List;
 
 public class AnnotationRemover {
@@ -184,8 +183,9 @@
       return original;
     }
     assert definition.isInterface();
-    boolean liveGetter = Arrays.stream(definition.virtualMethods())
-        .anyMatch(method -> method.method.name == original.name);
+    boolean liveGetter =
+        definition.virtualMethods().stream()
+            .anyMatch(method -> method.method.name == original.name);
     return liveGetter ? original : null;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 55ceee8..a51ca83 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -88,7 +88,9 @@
 import java.util.Objects;
 import java.util.Queue;
 import java.util.Set;
+import java.util.SortedMap;
 import java.util.SortedSet;
+import java.util.TreeMap;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
@@ -116,12 +118,16 @@
   private RootSet rootSet;
   private ProguardClassFilter dontWarnPatterns;
 
-  private final Map<DexType, Set<DexMethod>> virtualInvokes = Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexMethod>> interfaceInvokes = Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexMethod>>> virtualInvokes =
+      Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexMethod>>> interfaceInvokes =
+      Maps.newIdentityHashMap();
   private final Map<DexType, Set<TargetWithContext<DexMethod>>> superInvokes =
       Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexMethod>> directInvokes = Maps.newIdentityHashMap();
-  private final Map<DexType, Set<DexMethod>> staticInvokes = Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexMethod>>> directInvokes =
+      Maps.newIdentityHashMap();
+  private final Map<DexType, Set<TargetWithContext<DexMethod>>> staticInvokes =
+      Maps.newIdentityHashMap();
   private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsWritten =
       Maps.newIdentityHashMap();
   private final Map<DexType, Set<TargetWithContext<DexField>>> instanceFieldsRead =
@@ -439,7 +445,7 @@
         // Revisit the current method to implicitly add -keep rule for items with reflective access.
         pendingReflectiveUses.add(currentMethod);
       }
-      if (!registerItemWithTarget(virtualInvokes, method)) {
+      if (!registerItemWithTargetAndContext(virtualInvokes, method, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -455,7 +461,7 @@
     }
 
     boolean registerInvokeDirect(DexMethod method, KeepReason keepReason) {
-      if (!registerItemWithTarget(directInvokes, method)) {
+      if (!registerItemWithTargetAndContext(directInvokes, method, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -482,7 +488,7 @@
       if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
         pendingReflectiveUses.add(currentMethod);
       }
-      if (!registerItemWithTarget(staticInvokes, method)) {
+      if (!registerItemWithTargetAndContext(staticInvokes, method, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -498,7 +504,7 @@
     }
 
     boolean registerInvokeInterface(DexMethod method, KeepReason keepReason) {
-      if (!registerItemWithTarget(interfaceInvokes, method)) {
+      if (!registerItemWithTargetAndContext(interfaceInvokes, method, currentMethod)) {
         return false;
       }
       if (Log.ENABLED) {
@@ -1629,34 +1635,18 @@
     }
   }
 
-  private Map<DexField, Set<DexEncodedMethod>> collectFields(
-      Map<DexType, Set<TargetWithContext<DexField>>> map) {
-    Map<DexField, Set<DexEncodedMethod>> result = new IdentityHashMap<>();
-    for (Entry<DexType, Set<TargetWithContext<DexField>>> entry : map.entrySet()) {
-      for (TargetWithContext<DexField> fieldWithContext : entry.getValue()) {
-        DexField field = fieldWithContext.getTarget();
-        DexEncodedMethod context = fieldWithContext.getContext();
-        result.computeIfAbsent(field, k -> Sets.newIdentityHashSet())
+  <T extends Descriptor<?, T>> SortedMap<T, Set<DexEncodedMethod>> collectDescriptors(
+      Map<DexType, Set<TargetWithContext<T>>> map) {
+    SortedMap<T, Set<DexEncodedMethod>> result = new TreeMap<>(PresortedComparable::slowCompare);
+    for (Entry<DexType, Set<TargetWithContext<T>>> entry : map.entrySet()) {
+      for (TargetWithContext<T> descriptorWithContext : entry.getValue()) {
+        T descriptor = descriptorWithContext.getTarget();
+        DexEncodedMethod context = descriptorWithContext.getContext();
+        result.computeIfAbsent(descriptor, k -> Sets.newIdentityHashSet())
             .add(context);
       }
     }
-    return result;
-  }
-
-  Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsRead() {
-    return Collections.unmodifiableMap(collectFields(instanceFieldsRead));
-  }
-
-  Map<DexField, Set<DexEncodedMethod>> collectInstanceFieldsWritten() {
-    return Collections.unmodifiableMap(collectFields(instanceFieldsWritten));
-  }
-
-  Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsRead() {
-    return Collections.unmodifiableMap(collectFields(staticFieldsRead));
-  }
-
-  Map<DexField, Set<DexEncodedMethod>> collectStaticFieldsWritten() {
-    return Collections.unmodifiableMap(collectFields(staticFieldsWritten));
+    return Collections.unmodifiableSortedMap(result);
   }
 
   private Set<DexField> collectReachedFields(
@@ -1915,39 +1905,39 @@
     /**
      * Set of all field ids used in instance field reads, along with access context.
      */
-    public final Map<DexField, Set<DexEncodedMethod>> instanceFieldReads;
+    public final SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldReads;
     /**
      * Set of all field ids used in instance field writes, along with access context.
      */
-    public final Map<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
+    public final SortedMap<DexField, Set<DexEncodedMethod>> instanceFieldWrites;
     /**
      * Set of all field ids used in static field reads, along with access context.
      */
-    public final Map<DexField, Set<DexEncodedMethod>> staticFieldReads;
+    public final SortedMap<DexField, Set<DexEncodedMethod>> staticFieldReads;
     /**
      * Set of all field ids used in static field writes, along with access context.
      */
-    public final Map<DexField, Set<DexEncodedMethod>> staticFieldWrites;
+    public final SortedMap<DexField, Set<DexEncodedMethod>> staticFieldWrites;
     /**
-     * Set of all methods referenced in virtual invokes;
+     * Set of all methods referenced in virtual invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> virtualInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> virtualInvokes;
     /**
-     * Set of all methods referenced in interface invokes;
+     * Set of all methods referenced in interface invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> interfaceInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> interfaceInvokes;
     /**
-     * Set of all methods referenced in super invokes;
+     * Set of all methods referenced in super invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> superInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> superInvokes;
     /**
-     * Set of all methods referenced in direct invokes;
+     * Set of all methods referenced in direct invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> directInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> directInvokes;
     /**
-     * Set of all methods referenced in static invokes;
+     * Set of all methods referenced in static invokes, along with calling context.
      */
-    public final SortedSet<DexMethod> staticInvokes;
+    public final SortedMap<DexMethod, Set<DexEncodedMethod>> staticInvokes;
     /**
      * Set of live call sites in the code. Note that if desugaring has taken place call site objects
      * will have been removed from the code.
@@ -2041,20 +2031,20 @@
               DexMethod::slowCompareTo, enqueuer.virtualMethodsTargetedByInvokeDirect);
       this.liveMethods = toSortedDescriptorSet(enqueuer.liveMethods.getItems());
       this.liveFields = toSortedDescriptorSet(enqueuer.liveFields.getItems());
-      this.instanceFieldReads = enqueuer.collectInstanceFieldsRead();
-      this.instanceFieldWrites = enqueuer.collectInstanceFieldsWritten();
-      this.staticFieldReads = enqueuer.collectStaticFieldsRead();
-      this.staticFieldWrites = enqueuer.collectStaticFieldsWritten();
+      this.instanceFieldReads = enqueuer.collectDescriptors(enqueuer.instanceFieldsRead);
+      this.instanceFieldWrites = enqueuer.collectDescriptors(enqueuer.instanceFieldsWritten);
+      this.staticFieldReads = enqueuer.collectDescriptors(enqueuer.staticFieldsRead);
+      this.staticFieldWrites = enqueuer.collectDescriptors(enqueuer.staticFieldsWritten);
       this.fieldsRead = enqueuer.mergeFieldAccesses(
           instanceFieldReads.keySet(), staticFieldReads.keySet());
       this.fieldsWritten = enqueuer.mergeFieldAccesses(
           instanceFieldWrites.keySet(), staticFieldWrites.keySet());
       this.pinnedItems = enqueuer.pinnedItems;
-      this.virtualInvokes = joinInvokedMethods(enqueuer.virtualInvokes);
-      this.interfaceInvokes = joinInvokedMethods(enqueuer.interfaceInvokes);
-      this.superInvokes = joinInvokedMethods(enqueuer.superInvokes, TargetWithContext::getTarget);
-      this.directInvokes = joinInvokedMethods(enqueuer.directInvokes);
-      this.staticInvokes = joinInvokedMethods(enqueuer.staticInvokes);
+      this.virtualInvokes = enqueuer.collectDescriptors(enqueuer.virtualInvokes);
+      this.interfaceInvokes = enqueuer.collectDescriptors(enqueuer.interfaceInvokes);
+      this.superInvokes = enqueuer.collectDescriptors(enqueuer.superInvokes);
+      this.directInvokes = enqueuer.collectDescriptors(enqueuer.directInvokes);
+      this.staticInvokes = enqueuer.collectDescriptors(enqueuer.staticInvokes);
       this.callSites = enqueuer.callSites;
       this.brokenSuperInvokes =
           ImmutableSortedSet.copyOf(DexMethod::slowCompareTo, enqueuer.brokenSuperInvokes);
@@ -2153,11 +2143,16 @@
       this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
       this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
       this.pinnedItems = lense.rewriteReferencesConservatively(previous.pinnedItems);
-      this.virtualInvokes = lense.rewriteMethodsConservatively(previous.virtualInvokes);
-      this.interfaceInvokes = lense.rewriteMethodsConservatively(previous.interfaceInvokes);
-      this.superInvokes = lense.rewriteMethodsConservatively(previous.superInvokes);
-      this.directInvokes = lense.rewriteMethodsConservatively(previous.directInvokes);
-      this.staticInvokes = lense.rewriteMethodsConservatively(previous.staticInvokes);
+      this.virtualInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.virtualInvokes, lense::lookupMethodInAllContexts);
+      this.interfaceInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.interfaceInvokes, lense::lookupMethodInAllContexts);
+      this.superInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.superInvokes, lense::lookupMethodInAllContexts);
+      this.directInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.directInvokes, lense::lookupMethodInAllContexts);
+      this.staticInvokes = rewriteKeysConservativelyWhileMergingValues(
+          previous.staticInvokes, lense::lookupMethodInAllContexts);
       // TODO(sgjesse): Rewrite call sites as well? Right now they are only used by minification
       // after second tree shaking.
       this.callSites = previous.callSites;
@@ -2294,16 +2289,6 @@
       return isInstantiatedDirectly(type) || isInstantiatedIndirectly(type);
     }
 
-    private SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
-      return joinInvokedMethods(invokes, Function.identity());
-    }
-
-    private <T> SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<T>> invokes,
-        Function<T, DexMethod> getter) {
-      return invokes.values().stream().flatMap(Set::stream).map(getter)
-          .collect(ImmutableSortedSet.toImmutableSortedSet(PresortedComparable::slowCompare));
-    }
-
     private Object2BooleanMap<DexReference> joinIdentifierNameStrings(
         Set<DexReference> explicit, Set<DexReference> implicit) {
       Object2BooleanMap<DexReference> result = new Object2BooleanArrayMap<>();
@@ -2336,17 +2321,30 @@
       return builder.build();
     }
 
-
     private static <T extends PresortedComparable<T>, S>
-        Map<T, Set<S>> rewriteKeysWhileMergingValues(
+        SortedMap<T, Set<S>> rewriteKeysWhileMergingValues(
             Map<T, Set<S>> original, Function<T, T> rewrite) {
-      Map<T, Set<S>> result = new IdentityHashMap<>();
+      SortedMap<T, Set<S>> result = new TreeMap<>(PresortedComparable::slowCompare);
       for (T item : original.keySet()) {
         T rewrittenKey = rewrite.apply(item);
         result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
             .addAll(original.get(item));
       }
-      return Collections.unmodifiableMap(result);
+      return Collections.unmodifiableSortedMap(result);
+    }
+
+    private static <T extends PresortedComparable<T>, S>
+        SortedMap<T, Set<S>> rewriteKeysConservativelyWhileMergingValues(
+            Map<T, Set<S>> original, Function<T, Set<T>> rewrite) {
+      SortedMap<T, Set<S>> result = new TreeMap<>(PresortedComparable::slowCompare);
+      for (T item : original.keySet()) {
+        Set<T> rewrittenKeys = rewrite.apply(item);
+        for (T rewrittenKey : rewrittenKeys) {
+          result.computeIfAbsent(rewrittenKey, k -> Sets.newIdentityHashSet())
+              .addAll(original.get(item));
+        }
+      }
+      return Collections.unmodifiableSortedMap(result);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index f94954b..a0c9735 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -495,14 +495,16 @@
       }
       // In compat mode traverse all direct methods in the hierarchy.
       if (clazz == startingClass || options.forceProguardCompatibility) {
-        Arrays.stream(clazz.directMethods())
+        clazz
+            .directMethods()
             .forEach(
                 method -> {
                   DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
                   markMethod(method, memberKeepRules, methodsMarked, rule, precondition);
                 });
       }
-      Arrays.stream(clazz.virtualMethods())
+      clazz
+          .virtualMethods()
           .forEach(
               method -> {
                 DexDefinition precondition = testAndGetPrecondition(method, preconditionSupplier);
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index a51116b..42b7e21 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -30,8 +30,10 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Multiset.Entry;
 import com.google.common.collect.Streams;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -257,7 +259,7 @@
     if (appView.appInfo().neverMerge.contains(clazz.type)) {
       return MergeGroup.DONT_MERGE;
     }
-    if (clazz.staticFields().length + clazz.directMethods().length + clazz.virtualMethods().length
+    if (clazz.staticFields().length + clazz.directMethods().size() + clazz.virtualMethods().size()
         == 0) {
       return MergeGroup.DONT_MERGE;
     }
@@ -268,10 +270,10 @@
         .anyMatch(field -> appView.appInfo().isPinned(field.field))) {
       return MergeGroup.DONT_MERGE;
     }
-    if (Arrays.stream(clazz.directMethods()).anyMatch(DexEncodedMethod::isInitializer)) {
+    if (clazz.directMethods().stream().anyMatch(DexEncodedMethod::isInitializer)) {
       return MergeGroup.DONT_MERGE;
     }
-    if (!Arrays.stream(clazz.virtualMethods()).allMatch(DexEncodedMethod::isPrivateMethod)) {
+    if (!clazz.virtualMethods().stream().allMatch(DexEncodedMethod::isPrivateMethod)) {
       return MergeGroup.DONT_MERGE;
     }
     if (Streams.stream(clazz.methods())
@@ -445,7 +447,7 @@
       return false;
     }
     // Check that all of the members are private or public.
-    if (!Arrays.stream(clazz.directMethods())
+    if (!clazz.directMethods().stream()
         .allMatch(method -> method.accessFlags.isPrivate() || method.accessFlags.isPublic())) {
       return false;
     }
@@ -458,7 +460,7 @@
     // virtual methods are private. Therefore, we don't need to consider check if there are any
     // package-private or protected instance fields or virtual methods here.
     assert Arrays.stream(clazz.instanceFields()).count() == 0;
-    assert Arrays.stream(clazz.virtualMethods()).allMatch(method -> method.accessFlags.isPrivate());
+    assert clazz.virtualMethods().stream().allMatch(method -> method.accessFlags.isPrivate());
 
     // Check that no methods access package-private or protected members.
     IllegalAccessDetector registry = new IllegalAccessDetector(appView, clazz);
@@ -489,9 +491,9 @@
     numberOfMergedClasses++;
 
     // Move members from source to target.
-    targetClass.setDirectMethods(
+    targetClass.appendDirectMethods(
         mergeMethods(sourceClass.directMethods(), targetClass.directMethods(), targetClass));
-    targetClass.setVirtualMethods(
+    targetClass.appendVirtualMethods(
         mergeMethods(sourceClass.virtualMethods(), targetClass.virtualMethods(), targetClass));
     targetClass.setStaticFields(
         mergeFields(sourceClass.staticFields(), targetClass.staticFields(), targetClass));
@@ -502,30 +504,25 @@
     sourceClass.setStaticFields(DexEncodedField.EMPTY_ARRAY);
   }
 
-  private DexEncodedMethod[] mergeMethods(
-      DexEncodedMethod[] sourceMethods,
-      DexEncodedMethod[] targetMethods,
+  private List<DexEncodedMethod> mergeMethods(
+      List<DexEncodedMethod> sourceMethods,
+      List<DexEncodedMethod> targetMethods,
       DexProgramClass targetClass) {
-    DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.length + targetMethods.length];
-
-    // Move all target methods to result.
-    System.arraycopy(targetMethods, 0, result, 0, targetMethods.length);
-
     // Move source methods to result one by one, renaming them if needed.
     MethodSignatureEquivalence equivalence = MethodSignatureEquivalence.get();
     Set<Wrapper<DexMethod>> existingMethods =
-        Arrays.stream(targetMethods)
+        targetMethods.stream()
             .map(targetMethod -> equivalence.wrap(targetMethod.method))
             .collect(Collectors.toSet());
 
     Predicate<DexMethod> availableMethodSignatures =
         method -> !existingMethods.contains(equivalence.wrap(method));
 
-    int i = targetMethods.length;
+    List<DexEncodedMethod> newMethods = new ArrayList<>(sourceMethods.size());
     for (DexEncodedMethod sourceMethod : sourceMethods) {
       DexEncodedMethod sourceMethodAfterMove =
           renameMethodIfNeeded(sourceMethod, targetClass, availableMethodSignatures);
-      result[i++] = sourceMethodAfterMove;
+      newMethods.add(sourceMethodAfterMove);
 
       DexMethod originalMethod =
           methodMapping.inverse().getOrDefault(sourceMethod.method, sourceMethod.method);
@@ -533,8 +530,7 @@
 
       existingMethods.add(equivalence.wrap(sourceMethodAfterMove.method));
     }
-
-    return result;
+    return newMethods;
   }
 
   private DexEncodedField[] mergeFields(
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 f502692..dd8bcd6 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -99,8 +100,15 @@
         // The class is used and must be kept. Remove the unused fields and methods from
         // the class.
         usagePrinter.visiting(clazz);
-        clazz.setDirectMethods(reachableMethods(clazz.directMethods(), clazz));
-        clazz.setVirtualMethods(reachableMethods(clazz.virtualMethods(), clazz));
+        DexEncodedMethod[] reachableDirectMethods = reachableMethods(clazz.directMethods(), clazz);
+        if (reachableDirectMethods != null) {
+          clazz.setDirectMethods(reachableDirectMethods);
+        }
+        DexEncodedMethod[] reachableVirtualMethods =
+            reachableMethods(clazz.virtualMethods(), clazz);
+        if (reachableVirtualMethods != null) {
+          clazz.setVirtualMethods(reachableVirtualMethods);
+        }
         clazz.setInstanceFields(reachableFields(clazz.instanceFields()));
         clazz.setStaticFields(reachableFields(clazz.staticFields()));
         clazz.removeInnerClasses(this::isAttributeReferencingPrunedType);
@@ -145,9 +153,9 @@
   }
 
   private <S extends PresortedComparable<S>, T extends KeyedDexItem<S>> int firstUnreachableIndex(
-      T[] items, Predicate<S> live) {
-    for (int i = 0; i < items.length; i++) {
-      if (!live.test(items[i].getKey())) {
+      List<T> items, Predicate<S> live) {
+    for (int i = 0; i < items.size(); i++) {
+      if (!live.test(items.get(i).getKey())) {
         return i;
       }
     }
@@ -159,18 +167,18 @@
         && method.method.proto.parameters.isEmpty();
   }
 
-  private DexEncodedMethod[] reachableMethods(DexEncodedMethod[] methods, DexClass clazz) {
+  private DexEncodedMethod[] reachableMethods(List<DexEncodedMethod> methods, DexClass clazz) {
     int firstUnreachable = firstUnreachableIndex(methods, appInfo.liveMethods::contains);
     // Return the original array if all methods are used.
     if (firstUnreachable == -1) {
-      return methods;
+      return null;
     }
-    ArrayList<DexEncodedMethod> reachableMethods = new ArrayList<>(methods.length);
+    ArrayList<DexEncodedMethod> reachableMethods = new ArrayList<>(methods.size());
     for (int i = 0; i < firstUnreachable; i++) {
-      reachableMethods.add(methods[i]);
+      reachableMethods.add(methods.get(i));
     }
-    for (int i = firstUnreachable; i < methods.length; i++) {
-      DexEncodedMethod method = methods[i];
+    for (int i = firstUnreachable; i < methods.size(); i++) {
+      DexEncodedMethod method = methods.get(i);
       if (appInfo.liveMethods.contains(method.getKey())) {
         reachableMethods.add(method);
       } else if (options.debugKeepRules && isDefaultConstructor(method)) {
@@ -222,7 +230,8 @@
             appInfo.liveFields.contains(field)
                 || appInfo.fieldsRead.contains(field)
                 || appInfo.fieldsWritten.contains(field);
-    int firstUnreachable = firstUnreachableIndex(fields, isReachableOrReferencedField);
+    int firstUnreachable =
+        firstUnreachableIndex(Arrays.asList(fields), isReachableOrReferencedField);
     // Return the original array if all fields are used.
     if (firstUnreachable == -1) {
       return fields;
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index fe10045..fe401a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexClass.MethodSetter;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -993,11 +994,6 @@
         return false;
       }
 
-      DexEncodedMethod[] mergedDirectMethods =
-          mergeMethods(directMethods.values(), target.directMethods());
-      DexEncodedMethod[] mergedVirtualMethods =
-          mergeMethods(virtualMethods.values(), target.virtualMethods());
-
       // Step 2: Merge fields
       Set<DexString> existingFieldNames = new HashSet<>();
       for (DexEncodedField field : target.fields()) {
@@ -1044,8 +1040,8 @@
               ? DexTypeList.empty()
               : new DexTypeList(interfaces.toArray(new DexType[0]));
       // Step 2: replace fields and methods.
-      target.setDirectMethods(mergedDirectMethods);
-      target.setVirtualMethods(mergedVirtualMethods);
+      target.appendDirectMethods(directMethods.values());
+      target.appendVirtualMethods(virtualMethods.values());
       target.setInstanceFields(mergedInstanceFields);
       target.setStaticFields(mergedStaticFields);
       // Step 3: Unlink old class to ease tree shaking.
@@ -1191,7 +1187,8 @@
               newMethod,
               graphLense.getOriginalMethodSignature(method.method),
               invocationTarget.method,
-              invocationTarget.isPrivateMethod() ? DIRECT : STATIC);
+              invocationTarget.isPrivateMethod() ? DIRECT : STATIC,
+              target.isInterface());
 
       // Add the bridge to the list of synthesized bridges such that the method signatures will
       // be updated by the end of vertical class merging.
@@ -1226,8 +1223,19 @@
         assert actual.isVirtualMethod() == method.isVirtualMethod();
         return actual;
       }
-      // We will keep the method, so the class better be abstract if there is no implementation.
-      assert !method.accessFlags.isAbstract() || target.accessFlags.isAbstract();
+      // The method is not actually overridden. This means that we will move `method` to the
+      // subtype. If `method` is abstract, then so should the subtype be.
+      if (Log.ENABLED) {
+        if (method.accessFlags.isAbstract() && !target.accessFlags.isAbstract()) {
+          Log.warn(
+              VerticalClassMerger.class,
+              "The non-abstract type `"
+                  + target.type.toSourceString()
+                  + "` does not implement the method `"
+                  + method.method.toSourceString()
+                  + "`.");
+        }
+      }
       return null;
     }
 
@@ -1271,8 +1279,8 @@
     }
 
     private DexEncodedMethod[] mergeMethods(
-        Collection<DexEncodedMethod> sourceMethods, DexEncodedMethod[] targetMethods) {
-      DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.size() + targetMethods.length];
+        Collection<DexEncodedMethod> sourceMethods, List<DexEncodedMethod> targetMethods) {
+      DexEncodedMethod[] result = new DexEncodedMethod[sourceMethods.size() + targetMethods.size()];
       // Add methods from source.
       int i = 0;
       for (DexEncodedMethod method : sourceMethods) {
@@ -1280,7 +1288,7 @@
         i++;
       }
       // Add methods from target.
-      System.arraycopy(targetMethods, 0, result, i, targetMethods.length);
+      System.arraycopy(targetMethods, 0, result, i, targetMethods.size());
       return result;
     }
 
@@ -1423,8 +1431,8 @@
     private GraphLense fixupTypeReferences(GraphLense graphLense) {
       // Globally substitute merged class types in protos and holders.
       for (DexProgramClass clazz : appInfo.classes()) {
-        clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
-        clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
+        fixupMethods(clazz.directMethods(), clazz::setDirectMethod);
+        fixupMethods(clazz.virtualMethods(), clazz::setVirtualMethod);
         clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
         clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
       }
@@ -1438,20 +1446,19 @@
       return lense.build(application.dexItemFactory, graphLense);
     }
 
-    private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
+    private void fixupMethods(List<DexEncodedMethod> methods, MethodSetter setter) {
       if (methods == null) {
-        return null;
+        return;
       }
-      for (int i = 0; i < methods.length; i++) {
-        DexEncodedMethod encodedMethod = methods[i];
+      for (int i = 0; i < methods.size(); i++) {
+        DexEncodedMethod encodedMethod = methods.get(i);
         DexMethod method = encodedMethod.method;
         DexMethod newMethod = fixupMethod(method);
         if (newMethod != method) {
           lense.move(method, newMethod);
-          methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+          setter.setMethod(i, encodedMethod.toTypeSubstitutedMethod(newMethod));
         }
       }
-      return methods;
     }
 
     private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) {
@@ -1873,13 +1880,19 @@
     private DexMethod originalMethod;
     private DexMethod invocationTarget;
     private Type type;
+    private final boolean isInterface;
 
     public SynthesizedBridgeCode(
-        DexMethod method, DexMethod originalMethod, DexMethod invocationTarget, Type type) {
+        DexMethod method,
+        DexMethod originalMethod,
+        DexMethod invocationTarget,
+        Type type,
+        boolean isInterface) {
       this.method = method;
       this.originalMethod = originalMethod;
       this.invocationTarget = invocationTarget;
       this.type = type;
+      this.isInterface = isInterface;
     }
 
     // By the time the synthesized code object is created, vertical class merging still has not
@@ -1908,7 +1921,8 @@
               type == DIRECT ? method.holder : null,
               invocationTarget,
               type,
-              callerPosition);
+              callerPosition,
+              isInterface);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index a35100e..a5854b6 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -147,6 +147,10 @@
   // Throw exception if there is a warning about invalid debug info.
   public boolean invalidDebugInfoFatal = false;
 
+  // When dexsplitting we ignore main dex classes missing in the application. These will be
+  // fused together by play store when shipped for pre-L devices.
+  public boolean ignoreMainDexMissingClasses = false;
+
   // Hidden marker for classes.dex
   private boolean hasMarker = false;
   private Marker marker;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index c9be1b8..f5a4b81 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -341,7 +341,7 @@
   private static IdentityHashMap<DexString, List<DexEncodedMethod>> groupMethodsByRenamedName(
       NamingLens namingLens, DexProgramClass clazz) {
     IdentityHashMap<DexString, List<DexEncodedMethod>> methodsByRenamedName =
-        new IdentityHashMap<>(clazz.directMethods().length + clazz.virtualMethods().length);
+        new IdentityHashMap<>(clazz.directMethods().size() + clazz.virtualMethods().size());
     for (DexEncodedMethod method : clazz.methods()) {
       // Add method only if renamed or contains positions.
       DexString renamedName = namingLens.lookupName(method.method);
diff --git a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
index dd4c73a..33f9d7c 100644
--- a/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
+++ b/src/main/java/com/android/tools/r8/utils/OrderedMergingIterator.java
@@ -7,48 +7,49 @@
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
 import java.util.Iterator;
+import java.util.List;
 import java.util.NoSuchElementException;
 
 public class OrderedMergingIterator<T extends KeyedDexItem<S>, S extends PresortedComparable<S>>
     implements Iterator<T> {
 
-  private final T[] one;
-  private final T[] other;
+  private final List<T> one;
+  private final List<T> other;
   private int oneIndex = 0;
   private int otherIndex = 0;
 
-  public OrderedMergingIterator(T[] one, T[] other) {
+  public OrderedMergingIterator(List<T> one, List<T> other) {
     this.one = one;
     this.other = other;
   }
 
-  private static <T> T getNextChecked(T[] array, int position) {
-    if (position >= array.length) {
+  private static <T> T getNextChecked(List<T> list, int position) {
+    if (position >= list.size()) {
       throw new NoSuchElementException();
     }
-    return array[position];
+    return list.get(position);
   }
 
   @Override
   public boolean hasNext() {
-    return oneIndex < one.length || otherIndex < other.length;
+    return oneIndex < one.size() || otherIndex < other.size();
   }
 
   @Override
   public T next() {
-    if (oneIndex >= one.length) {
+    if (oneIndex >= one.size()) {
       return getNextChecked(other, otherIndex++);
     }
-    if (otherIndex >= other.length) {
+    if (otherIndex >= other.size()) {
       return getNextChecked(one, oneIndex++);
     }
-    int comparison = one[oneIndex].getKey().compareTo(other[otherIndex].getKey());
+    int comparison = one.get(oneIndex).getKey().compareTo(other.get(otherIndex).getKey());
     if (comparison < 0) {
-      return one[oneIndex++];
+      return one.get(oneIndex++);
     }
     if (comparison == 0) {
       throw new InternalCompilerError("Source arrays are not disjoint.");
     }
-    return other[otherIndex++];
+    return other.get(otherIndex++);
   }
 }
diff --git a/src/test/examples/autovalue/SimpleAutoValue.java b/src/test/examples/autovalue/SimpleAutoValue.java
deleted file mode 100644
index 2c0b2c6..0000000
--- a/src/test/examples/autovalue/SimpleAutoValue.java
+++ /dev/null
@@ -1,54 +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 autovalue;
-
-import com.google.auto.value.AutoValue;
-import javax.annotation.Nullable;
-
-public class SimpleAutoValue {
-
-  @AutoValue
-  static abstract class Pair {
-
-    Pair() {
-      // Intentionally left empty.
-    }
-
-    abstract int getOne();
-
-    @Nullable
-    abstract String getOther();
-
-    abstract String getRequiredOther();
-
-    static Builder builder() {
-      return new AutoValue_SimpleAutoValue_Pair.Builder();
-    }
-
-    @AutoValue.Builder
-    abstract static class Builder {
-
-      abstract Builder setOne(int value);
-
-      abstract Builder setOther(String value);
-
-      abstract Builder setRequiredOther(String value);
-
-      abstract Pair build();
-    }
-  }
-
-  public static void main(String... args) {
-    Pair.Builder builder = Pair.builder();
-    builder.setOne(42);
-    builder.setRequiredOther("123");
-    System.out.println(builder.build());
-    builder = Pair.builder();
-    try {
-      builder.build();
-    } catch (Exception e) {
-      System.out.println(e.getMessage());
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 37e4dec..c807a5d 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -87,8 +87,8 @@
     return self();
   }
 
-  public D8TestBuilder setIntermediate(boolean b) {
-    builder.setIntermediate(true);
+  public D8TestBuilder setIntermediate(boolean intermediate) {
+    builder.setIntermediate(intermediate);
     return self();
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
index f0e758d..c710ed2 100644
--- a/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8CFRunExamplesJava9Test.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
@@ -31,11 +32,24 @@
     }
 
     @Override
+    R8CFTestRunner withKeepAll() {
+      return withBuilderTransformation(
+          builder ->
+              builder
+                  .setMode(CompilationMode.DEBUG)
+                  .setDisableTreeShaking(true)
+                  .setDisableMinification(true)
+                  .addProguardConfiguration(
+                      ImmutableList.of("-keepattributes *"), Origin.unknown()));
+    }
+
+    @Override
     void build(Path inputFile, Path out) throws Throwable {
       R8Command.Builder builder = R8Command.builder();
       for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
         builder = transformation.apply(builder);
       }
+      // TODO(b/124041175): We should not be linking against the Java 8 runtime for Java 9 inputs.
       builder.addLibraryFiles(ToolHelper.getJava8RuntimeJar());
       R8Command command =
           builder.addProgramFiles(inputFile).setOutput(out, OutputMode.ClassFile).build();
@@ -67,7 +81,7 @@
         }
       }
 
-      execute(testName, qualifiedMainClass, new Path[] {inputFile}, new Path[] {out});
+      execute(testName, qualifiedMainClass, new Path[] {inputFile}, new Path[] {out}, args);
 
       if (expectedToThrow) {
         System.out.println("Did not throw ApiLevelException as expected");
@@ -85,15 +99,23 @@
     return new R8CFTestRunner(testName, packageName, mainClass);
   }
 
-  void execute(String testName, String qualifiedMainClass, Path[] inputJars, Path[] outputJars)
+  @Override
+  void execute(
+      String testName,
+      String qualifiedMainClass,
+      Path[] inputJars,
+      Path[] outputJars,
+      List<String> args)
       throws IOException {
     boolean expectedToFail = expectedToFailCf(testName);
     if (expectedToFail) {
       thrown.expect(Throwable.class);
     }
-    ProcessResult outputResult = ToolHelper.runJava(Arrays.asList(outputJars), qualifiedMainClass);
+    String[] mainAndArgs =
+        ImmutableList.builder().add(qualifiedMainClass).addAll(args).build().toArray(new String[0]);
+    ProcessResult outputResult = ToolHelper.runJava(Arrays.asList(outputJars), mainAndArgs);
     ToolHelper.ProcessResult inputResult =
-        ToolHelper.runJava(ImmutableList.copyOf(inputJars), qualifiedMainClass);
+        ToolHelper.runJava(ImmutableList.copyOf(inputJars), mainAndArgs);
     assertEquals(inputResult.toString(), outputResult.toString());
     if (inputResult.exitCode != 0) {
       System.out.println(inputResult);
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 36a4f00..bfd929e 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -45,7 +45,7 @@
     IRConverter converter = new IRConverter(new AppInfoWithSubtyping(application), options);
     converter.optimize(application);
     DexProgramClass clazz = application.classes().iterator().next();
-    assertEquals(4, clazz.directMethods().length);
+    assertEquals(4, clazz.directMethods().size());
     for (DexEncodedMethod method : clazz.directMethods()) {
       if (!method.method.name.toString().equals("main")) {
         assertEquals(2, method.getCode().asDexCode().instructions.length);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
index c95d8a6..f3f5339 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesJava9Test.java
@@ -10,6 +10,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
@@ -274,6 +275,7 @@
 
   @Test
   public void desugaredPrivateInterfaceMethods() throws Throwable {
+    assumeFalse("CF backend does not desugar", this instanceof R8CFRunExamplesJava9Test);
     final String iName = "privateinterfacemethods.I";
     test("desugared-private-interface-methods",
         "privateinterfacemethods", "PrivateInterfaceMethods")
@@ -297,6 +299,7 @@
   public void varHandle() throws Throwable {
     test("varhandle", "varhandle", "VarHandleTests")
         .withMinApiLevel(AndroidApiLevel.P.getLevel())
+        .withKeepAll()
         .run();
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
index 15c03dc..7ad544b 100644
--- a/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
+++ b/src/test/java/com/android/tools/r8/TestDiagnosticMessagesImpl.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.fail;
 
+import com.android.tools.r8.utils.ListUtils;
 import java.util.ArrayList;
 import java.util.List;
 import org.hamcrest.Matcher;
@@ -44,25 +45,34 @@
     return errors;
   }
 
+  private void assertEmpty(String type, List<Diagnostic> messages) {
+    assertEquals(
+        "Expected no "
+            + type
+            + " messages, got:\n"
+            + String.join("\n", ListUtils.map(messages, m -> m.getDiagnosticMessage())),
+        0,
+        messages.size());
+  }
 
   public TestDiagnosticMessages assertNoMessages() {
-    assertEquals(0, getInfos().size());
-    assertEquals(0, getWarnings().size());
-    assertEquals(0, getErrors().size());
+    assertEmpty("info", getInfos());
+    assertEmpty("warning", getWarnings());
+    assertEmpty("error", getErrors());
     return this;
   }
 
   public TestDiagnosticMessages assertOnlyInfos() {
     assertNotEquals(0, getInfos().size());
-    assertEquals(0, getWarnings().size());
-    assertEquals(0, getErrors().size());
+    assertEmpty("warning", getWarnings());
+    assertEmpty("error", getErrors());
     return this;
   }
 
   public TestDiagnosticMessages assertOnlyWarnings() {
-    assertEquals(0, getInfos().size());
+    assertEmpty("info", getInfos());
     assertNotEquals(0, getWarnings().size());
-    assertEquals(0, getErrors().size());
+    assertEmpty("error", getErrors());
     return this;
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
index 3d75846..a958c3a 100644
--- a/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/IdenticalCatchHandlerTest.java
@@ -72,7 +72,7 @@
   private int countCatchHandlers(AndroidApp inputApp) throws Exception {
     CodeInspector inspector = new CodeInspector(inputApp);
     DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
-    Code code = dexClass.virtualMethods()[0].getCode();
+    Code code = dexClass.virtualMethods().get(0).getCode();
     if (code.isCfCode()) {
       CfCode cfCode = code.asCfCode();
       Set<CfLabel> targets = Sets.newIdentityHashSet();
diff --git a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
index 46ed96b..da70125 100644
--- a/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
+++ b/src/test/java/com/android/tools/r8/cf/MethodHandleTestRunner.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.cf;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ByteDataView;
 import com.android.tools.r8.ClassFileConsumer;
@@ -23,10 +24,14 @@
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.ExecutionException;
 import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -133,6 +138,16 @@
     ProcessResult runCf = ToolHelper.runJava(outCf, CLASS.getCanonicalName(), expected);
     assertEquals(runCf.stderr, 0, runCf.exitCode);
     assertEquals(runInput.toString(), runCf.toString());
+    // Ensure that we did not inline the const method handle
+    ensureConstHandleNotInlined(outCf);
+  }
+
+  private void ensureConstHandleNotInlined(Path file) throws IOException, ExecutionException {
+    CodeInspector inspector = new CodeInspector(file);
+    MethodSubject subject = inspector.clazz(MethodHandleTest.D.class).method(
+        "java.lang.MethodHandle", "vcviSpecialMethod");
+    assertTrue(inspector.clazz(MethodHandleTest.D.class)
+        .method("java.lang.invoke.MethodHandle", "vcviSpecialMethod").isPresent());
   }
 
   private void runDex() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
index 2664ef4..5d536cf 100644
--- a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
@@ -71,7 +71,7 @@
   private int countCatchHandlers(AndroidApp inputApp) throws Exception {
     CodeInspector inspector = new CodeInspector(inputApp);
     DexClass dexClass = inspector.clazz(TestClass.class).getDexClass();
-    Code code = dexClass.virtualMethods()[0].getCode();
+    Code code = dexClass.virtualMethods().get(0).getCode();
     if (code.isCfCode()) {
       CfCode cfCode = code.asCfCode();
       Set<CfLabel> targets = Sets.newIdentityHashSet();
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
new file mode 100644
index 0000000..e9b1b8c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTest.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+public class DefaultLambdaWithInvokeInterfaceTest {
+
+  public interface I {
+    int run();
+  }
+
+  public interface J {
+    default String stateless() {
+      return "hest";
+    }
+
+    default I stateful() {
+      return () -> stateless().length();
+    }
+  }
+
+  public static void main(String[] args) {
+    System.out.println(new J() {}.stateful().run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
new file mode 100644
index 0000000..732612d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithInvokeInterfaceTestRunner.java
@@ -0,0 +1,35 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+public class DefaultLambdaWithInvokeInterfaceTestRunner extends TestBase {
+
+  final Class<?> CLASS = DefaultLambdaWithInvokeInterfaceTest.class;
+  final String EXPECTED = StringUtils.lines("4");
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm().addTestClasspath().run(CLASS).assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForD8()
+        .addProgramClassesAndInnerClasses(CLASS)
+        .setMinApi(AndroidApiLevel.K)
+        .compile()
+        // TODO(b/123506120): Add .assertNoMessages()
+        .run(CLASS)
+        .assertSuccessWithOutput(EXPECTED)
+        .inspect(inspector -> assertThat(inspector.clazz(CLASS), isPresent()));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java
new file mode 100644
index 0000000..eb4e75b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTest.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+public class DefaultLambdaWithSelfReferenceTest {
+
+  interface I {
+    String foo();
+
+    default I stateless() {
+      return () -> "stateless";
+    }
+
+    default I stateful() {
+      return () -> "stateful(" + stateless().foo() + ")";
+    }
+  }
+
+  public static void main(String[] args) {
+    System.out.println(((I) () -> "foo").stateful().foo());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
new file mode 100644
index 0000000..bc193c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DefaultLambdaWithSelfReferenceTestRunner.java
@@ -0,0 +1,94 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.Disassemble;
+import com.android.tools.r8.Disassemble.DisassembleCommand;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Test;
+
+public class DefaultLambdaWithSelfReferenceTestRunner extends TestBase {
+
+  final Class<?> CLASS = DefaultLambdaWithSelfReferenceTest.class;
+  final String EXPECTED = StringUtils.lines("stateful(stateless)");
+
+  @Test
+  public void testJvm() throws Exception {
+    testForJvm().addTestClasspath().run(CLASS).assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void test() throws Exception {
+    Path out1 = temp.newFolder().toPath().resolve("out1.zip");
+    testForD8()
+        .addProgramClassesAndInnerClasses(CLASS)
+        .setMinApi(AndroidApiLevel.K)
+        .compile()
+        // TODO(b/123506120): Add .assertNoMessages()
+        .writeToZip(out1)
+        .run(CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+
+    Path outPerClassDir = temp.newFolder().toPath();
+    Collection<Path> innerClasses =
+        ToolHelper.getClassFilesForInnerClasses(Collections.singleton(CLASS));
+
+    int i = 0;
+    List<Path> outs = new ArrayList<>();
+    {
+      Path mainOut = outPerClassDir.resolve("class" + i++ + ".zip");
+      outs.add(mainOut);
+      testForD8()
+          .addProgramClasses(CLASS)
+          .addClasspathFiles(ToolHelper.getClassPathForTests())
+          .setIntermediate(true)
+          .setMinApi(AndroidApiLevel.K)
+          .compile()
+          // TODO(b/123506120): Add .assertNoMessages()
+          .writeToZip(mainOut);
+    }
+    for (Path innerClass : innerClasses) {
+      Path out = outPerClassDir.resolve("class" + i++ + ".zip");
+      outs.add(out);
+      testForD8()
+          .addProgramFiles(innerClass)
+          .addClasspathFiles(ToolHelper.getClassPathForTests())
+          .setIntermediate(true)
+          .setMinApi(AndroidApiLevel.K)
+          .compile()
+          // TODO(b/123506120): Add .assertNoMessages()
+          .writeToZip(out);
+    }
+
+    Path out2 = temp.newFolder().toPath().resolve("out2.zip");
+    testForD8()
+        .addProgramFiles(outs)
+        .compile()
+        // TODO(b/123506120): Add .assertNoMessages()
+        .writeToZip(out2)
+        .run(CLASS)
+        .assertSuccessWithOutput(EXPECTED);
+
+    Path dissasemble1 = temp.newFolder().toPath().resolve("disassemble1.txt");
+    Path dissasemble2 = temp.newFolder().toPath().resolve("disassemble2.txt");
+    Disassemble.disassemble(
+        DisassembleCommand.builder().addProgramFiles(out1).setOutputPath(dissasemble1).build());
+    Disassemble.disassemble(
+        DisassembleCommand.builder().addProgramFiles(out2).setOutputPath(dissasemble2).build());
+    String content1 = StringUtils.join(Files.readAllLines(dissasemble1), "\n");
+    String content2 = StringUtils.join(Files.readAllLines(dissasemble2), "\n");
+    assertEquals(content1, content2);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
index 68daeb8..9c9e785 100644
--- a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
@@ -59,11 +59,11 @@
     assertThat(clazz, isPresent());
 
     // There are two direct methods, but only because one is <init>.
-    assertEquals(2, clazz.getDexClass().directMethods().length);
+    assertEquals(2, clazz.getDexClass().directMethods().size());
     assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
 
     // There is only one virtual method.
-    assertEquals(1, clazz.getDexClass().virtualMethods().length);
+    assertEquals(1, clazz.getDexClass().virtualMethods().size());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
new file mode 100644
index 0000000..519c273
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.InvokeInstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InlineSynchronizedTest extends TestBase {
+
+  private static final List<String> METHOD_NAMES =
+      ImmutableList.of(
+          "println",
+          "normalInlinedSynchronized",
+          "classInlinedSynchronized",
+          "normalInlinedControl",
+          "classInlinedControl");
+
+  @Parameterized.Parameters(name = "Backend: {0}, ClassInlining: {1}")
+  public static Collection data() {
+    return buildParameters(Backend.values(), BooleanUtils.values());
+  }
+
+  private final Backend backend;
+  private final boolean classInlining;
+
+  public InlineSynchronizedTest(Backend backend, boolean classInlining) {
+    this.backend = backend;
+    this.classInlining = classInlining;
+  }
+
+  private void configure(InternalOptions options) {
+    options.enableClassInlining = classInlining;
+  }
+
+  @Test
+  public void test() throws Exception {
+    CodeInspector codeInspector =
+        testForR8(backend)
+            .addProgramClasses(InlineSynchronizedTestClass.class)
+            .addKeepMainRule("com.android.tools.r8.ir.optimize.inliner.InlineSynchronizedTestClass")
+            .addKeepRules("-dontobfuscate")
+            .addOptionsModification(o -> o.enableClassInlining = classInlining)
+            .compile()
+            .inspector();
+
+    ClassSubject classSubject = codeInspector.clazz(InlineSynchronizedTestClass.class);
+    assertThat(classSubject, isPresent());
+    MethodSubject methodSubject = classSubject.mainMethod();
+    Iterator<InstructionSubject> it = methodSubject.iterateInstructions();
+    int[] counts = new int[METHOD_NAMES.size()];
+    while (it.hasNext()) {
+      InstructionSubject instruction = it.next();
+      if (!instruction.isInvoke()) {
+        continue;
+      }
+      DexString invokedName = ((InvokeInstructionSubject) instruction).invokedMethod().name;
+      int idx = METHOD_NAMES.indexOf(invokedName.toString());
+      if (idx >= 0) {
+        ++counts[idx];
+      }
+    }
+    // Synchronized methods can never be inlined.
+    assertCount(counts, "normalInlinedSynchronized", 1);
+    assertCount(counts, "classInlinedSynchronized", 1);
+    // Control methods must be inlined, only the normal one or both, depending on classInlining.
+    assertCount(counts, "normalInlinedControl", 0);
+    assertCount(counts, "classInlinedControl", classInlining ? 0 : 1);
+    // Double check the total.
+    int total = 0;
+    for (int i = 0; i < counts.length; ++i) {
+      total += counts[i];
+    }
+    assertEquals(4, total);
+  }
+
+  private static void assertCount(int counts[], String methodName, int expectedCount) {
+    int idx = METHOD_NAMES.indexOf(methodName);
+    assert idx >= 0;
+    assertEquals(expectedCount, counts[idx]);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
new file mode 100644
index 0000000..4e2cae1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineSynchronizedTestClass.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+class InlineSynchronizedTestClass {
+  private synchronized void normalInlinedSynchronized() {
+    System.out.println("InlineSynchronizedTestClass::normalInlinedSynchronized");
+  }
+
+  public synchronized void classInlinedSynchronized() {
+    System.out.println("InlineSynchronizedTestClass::classInlinedSynchronized");
+  }
+
+  private void normalInlinedControl() {
+    System.out.println("InlineSynchronizedTestClass::normalInlinedControl");
+  }
+
+  public void classInlinedControl() {
+    System.out.println("InlineSynchronizedTestClass::classInlinedControl");
+  }
+
+  public static void main(String[] args) {
+    // Test normal inlining.
+    InlineSynchronizedTestClass testClass = new InlineSynchronizedTestClass();
+    testClass.normalInlinedSynchronized();
+    testClass.normalInlinedControl();
+
+    // Test class-inlining.
+    new InlineSynchronizedTestClass().classInlinedSynchronized();
+    new InlineSynchronizedTestClass().classInlinedControl();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineWithSimpleFieldNoValue.java b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineWithSimpleFieldNoValue.java
new file mode 100644
index 0000000..eac5881
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/inliner/InlineWithSimpleFieldNoValue.java
@@ -0,0 +1,43 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.inliner;
+
+import static junit.framework.TestCase.assertTrue;
+
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+class TestClass {
+  public static void main(String[] args) {
+    System.out.println(InlineFrom.getValue());
+    InlineFrom.value = 43;
+    System.out.println(InlineFrom.value);
+  }
+}
+
+// Simple class, with no clinit and no static field initialization.
+// We should always inline getValue()
+// We ensure that we use value.
+class InlineFrom {
+  public static int value;
+
+  public static int getValue() {
+    return 42;
+  }
+}
+
+public class InlineWithSimpleFieldNoValue extends TestBase {
+  @Test
+  public void test() throws Exception {
+    R8TestRunResult result = testForR8(Backend.DEX)
+        .addKeepMainRule(TestClass.class)
+        .addProgramClasses(TestClass.class, InlineFrom.class)
+        .run(TestClass.class)
+        .assertSuccessWithOutput(StringUtils.lines("42", "43"));
+    assertTrue(result.inspector().clazz(InlineFrom.class).allMethods().isEmpty());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index b8c4b47..7e9269d 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -183,7 +183,7 @@
           if (check.match(clazz)) {
             // Validate static initializer.
             if (check instanceof Group) {
-              assertEquals(clazz.directMethods().length, ((Group) check).singletons == 0 ? 1 : 2);
+              assertEquals(clazz.directMethods().size(), ((Group) check).singletons == 0 ? 1 : 2);
             }
 
             list.remove(clazz);
@@ -228,8 +228,8 @@
     } else {
       assertTrue(isJStyleLambdaOrGroup(clazz));
       // Taking the number of any virtual method parameters seems to be good enough.
-      assertTrue(clazz.virtualMethods().length > 0);
-      return clazz.virtualMethods()[0].method.proto.parameters.size();
+      assertTrue(clazz.virtualMethods().size() > 0);
+      return clazz.virtualMethods().get(0).method.proto.parameters.size();
     }
     fail("Failed to get arity for " + clazz.type.descriptor.toString());
     throw new AssertionError();
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
index ac75230..bc23757 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinPropertiesTest.java
@@ -910,11 +910,10 @@
       MemberNaming.MethodSignature getter = testedClass.getGetterForProperty(propertyName);
       MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
 
-      // Field is public and getter is small so we expect to always inline it.
+      // Field is public and getter/setter is only called from one place so we expect to always
+      // inline it.
       checkMethodIsRemoved(objectClass, getter);
-
-      // Setter has null check of new value, thus may not be inlined.
-      checkMethodIsKept(objectClass, setter);
+      checkMethodIsRemoved(objectClass, setter);
     });
   }
 
@@ -934,7 +933,7 @@
       MemberNaming.MethodSignature setter = testedClass.getSetterForProperty(propertyName);
 
       checkMethodIsRemoved(objectClass, getter);
-      checkMethodIsKept(objectClass, setter);
+      checkMethodIsRemoved(objectClass, setter);
       assertTrue(fieldSubject.getField().accessFlags.isPublic());
     });
   }
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 ff948aa..5103951 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -226,14 +226,15 @@
   @Test
   public void everyThirdClassInMainWithDexSplitter() throws Throwable {
     List<String> featureMappings = new ArrayList<>();
+    List<String> inFeatureMapping = new ArrayList<>();
 
     ImmutableList.Builder<String> mainDexBuilder = ImmutableList.builder();
     for (int i = 0; i < MANY_CLASSES.size(); i++) {
       String clazz = MANY_CLASSES.get(i);
       // Write the first 2 classes into the split.
-      if (i < 2) {
+      if (i < 10) {
         featureMappings.add(clazz + ":feature1");
-        continue;
+        inFeatureMapping.add(clazz);
       }
       if (i % 3 == 0) {
         mainDexBuilder.add(clazz);
@@ -246,19 +247,20 @@
     FileUtils.writeTextFile(mainDexFile, ListUtils.map(mainDexList, MainDexListTests::typeToEntry));
     Path output = temp.getRoot().toPath().resolve("split_output");
     Files.createDirectories(output);
-
-    Options options = new Options();
+    TestDiagnosticsHandler diagnosticsHandler = new TestDiagnosticsHandler();
+    Options options = new Options(diagnosticsHandler);
     options.addInputArchive(getManyClassesMultiDexAppPath().toString());
     options.setFeatureSplitMapping(featureSplitMapping.toString());
     options.setOutput(output.toString());
     options.setMainDexList(mainDexFile.toString());
     DexSplitter.run(options);
+    assertEquals(0, diagnosticsHandler.numberOfErrorsAndWarnings());
     Path baseDir = output.resolve("base");
     CodeInspector inspector =
         new CodeInspector(
             AndroidApp.builder().addProgramFiles(baseDir.resolve("classes.dex")).build());
     for (String clazz : mainDexList) {
-      if (!inspector.clazz(clazz).isPresent()) {
+      if (!inspector.clazz(clazz).isPresent() && !inFeatureMapping.contains(clazz)) {
         failedToFindClassInExpectedFile(baseDir, clazz);
       }
     }
@@ -923,10 +925,20 @@
   private class TestDiagnosticsHandler implements DiagnosticsHandler {
 
     public List<Diagnostic> errors = new ArrayList<>();
+    public List<Diagnostic> warnings = new ArrayList<>();
+
+    public int numberOfErrorsAndWarnings() {
+      return errors.size() + warnings.size();
+    }
 
     @Override
     public void error(Diagnostic error) {
       errors.add(error);
     }
+
+    @Override
+    public void warning(Diagnostic warning) {
+      warnings.add(warning);
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java b/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
index e298c9d..462d514 100644
--- a/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
+++ b/src/test/java/com/android/tools/r8/resolution/b123730538/B123730538.java
@@ -22,7 +22,6 @@
 import java.nio.file.Path;
 import java.util.List;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -59,17 +58,18 @@
     testForProguard()
         .addProgramFiles(inJar)
         .addKeepMainRule(MAIN)
+        .addKeepRules("-dontoptimize")
         .run(MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT)
         .inspect(this::inspect);
   }
 
-  @Ignore("b/123730538")
   @Test
   public void testR8() throws Exception {
     testForR8(backend)
         .addProgramFiles(CLASSES)
         .addKeepMainRule(MAIN)
+        .addKeepRules("-dontoptimize")
         .run(MAIN)
         .assertSuccessWithOutput(EXPECTED_OUTPUT)
         .inspect(this::inspect);
diff --git a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
index d28770a..5b02222 100644
--- a/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/AsterisksTest.java
@@ -100,7 +100,7 @@
     assertThat(classSubject, isPresent());
     assertThat(classSubject, not(isRenamed()));
     DexClass clazz = classSubject.getDexClass();
-    assertEquals(3, clazz.virtualMethods().length);
+    assertEquals(3, clazz.virtualMethods().size());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
       assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
       MethodSubject methodSubject =
@@ -141,7 +141,7 @@
     assertThat(classSubject, isPresent());
     assertThat(classSubject, not(isRenamed()));
     DexClass clazz = classSubject.getDexClass();
-    assertEquals(3, clazz.virtualMethods().length);
+    assertEquals(3, clazz.virtualMethods().size());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
       assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
       MethodSubject methodSubject =
@@ -163,7 +163,7 @@
     assertThat(classSubject, isPresent());
     assertThat(classSubject, not(isRenamed()));
     DexClass clazz = classSubject.getDexClass();
-    assertEquals(3, clazz.virtualMethods().length);
+    assertEquals(3, clazz.virtualMethods().size());
     for (DexEncodedMethod encodedMethod : clazz.virtualMethods()) {
       assertTrue(encodedMethod.method.name.toString().startsWith("foo"));
       MethodSubject methodSubject =
diff --git a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
index 5038729..e191921 100644
--- a/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/desugar/interfacemethods/BridgeInliningTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.shaking.desugar.interfacemethods;
 
 import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -60,11 +59,9 @@
     MethodSubject m = c.uniqueMethodWithName("m");
     assertThat(m, isPresent());
     assertTrue(m.getMethod().hasCode());
-    // TODO(b/123778921): why are I and C not merged without merge annotations?
+    // TODO(b/124017330): Verify that I$-CC.m() has been inlined into C.m().
     //assertTrue(
     //    m.iterateInstructions(i -> i.isConstString("I::m", JumboStringMode.ALLOW)).hasNext());
-
-    // TODO(b/123778921): No companion class is in the output.
     //codeInspector.forAllClasses(classSubject -> {
     //  assertFalse(classSubject.getOriginalDescriptor().contains("$-CC"));
     //});
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
index d300a4e..da38a47 100644
--- a/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/IfOnAccessModifierTest.java
@@ -12,7 +12,6 @@
 
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase;
-import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
@@ -45,11 +44,6 @@
     return ImmutableList.of(Shrinker.R8_CF, Shrinker.PROGUARD6, Shrinker.R8);
   }
 
-  private void configure(InternalOptions options) {
-    // Disable inlining, otherwise classes can be pruned away if all their methods are inlined.
-    options.enableInlining = false;
-  }
-
   @Test
   public void ifOnPublic_noPublicClassForIfRule() throws Exception {
     List<String> config = ImmutableList.of(
@@ -64,7 +58,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -93,7 +87,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -126,7 +120,7 @@
         "  !public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -160,7 +154,7 @@
         "  public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
@@ -192,7 +186,7 @@
         "  !public <methods>;",
         "}"
     );
-    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config, this::configure);
+    CodeInspector codeInspector = inspectAfterShrinking(shrinker, CLASSES, config);
     ClassSubject classSubject = codeInspector.clazz(ClassForIf.class);
     assertThat(classSubject, isPresent());
     MethodSubject methodSubject = classSubject.method(publicMethod);
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/C.java b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
index 79370a4..7a30a65 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/C.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/C.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.shaking.testrules;
 
+import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.NeverMerge;
 
 @NeverMerge
@@ -11,6 +12,7 @@
 
   private static int i;
 
+  @NeverInline
   public static int x() {
     return i;
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
index 432711f..4bf8c92 100644
--- a/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/testrules/ForceInlineTest.java
@@ -69,6 +69,7 @@
             ImmutableList.of(
                 "-keep class **.Main { *; }",
                 "-nevermerge @com.android.tools.r8.NeverMerge class *",
+                "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
                 "-dontobfuscate"));
 
     ClassSubject classA = inspector.clazz(A.class);
@@ -99,6 +100,7 @@
                 "-neverinline class **.B { method(); }",
                 "-keep class **.Main { *; }",
                 "-nevermerge @com.android.tools.r8.NeverMerge class *",
+                "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
                 "-dontobfuscate"));
 
     ClassSubject classA = inspector.clazz(A.class);
@@ -126,6 +128,7 @@
                 "-forceinline class **.B { int m(int, int); }",
                 "-keep class **.Main { *; }",
                 "-nevermerge @com.android.tools.r8.NeverMerge class *",
+                "-neverinline class *{ @com.android.tools.r8.NeverInline <methods>;}",
                 "-dontobfuscate"));
 
     ClassSubject classA = inspector.clazz(A.class);
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index eacc338..bfeac7a 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -848,13 +848,13 @@
     CodeInspector inspector = new CodeInspector(processedApplication);
     ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
     assertTrue(clazz.isPresent());
-    assertEquals(3, clazz.getDexClass().directMethods().length);
+    assertEquals(3, clazz.getDexClass().directMethods().size());
     // Collect the return types of the putlines for the body of method1 and method2.
     List<DexType> r = new ArrayList<>();
-    for (int i = 0; i < clazz.getDexClass().directMethods().length; i++) {
-      if (clazz.getDexClass().directMethods()[i].getCode().asDexCode().instructions[0]
+    for (int i = 0; i < clazz.getDexClass().directMethods().size(); i++) {
+      if (clazz.getDexClass().directMethods().get(i).getCode().asDexCode().instructions[0]
           instanceof InvokeVirtual) {
-        r.add(clazz.getDexClass().directMethods()[i].method.proto.returnType);
+        r.add(clazz.getDexClass().directMethods().get(i).method.proto.returnType);
       }
     }
     assert r.size() == 2;
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index a9058ff..aa083da 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -185,7 +185,7 @@
   }
 
   static <S, T extends Subject> void forAll(
-      S[] items,
+      List<? extends S> items,
       BiFunction<S, FoundClassSubject, ? extends T> constructor,
       FoundClassSubject clazz,
       Consumer<T> consumer) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 11ee3af..c7b3781 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
 import com.android.tools.r8.utils.DescriptorUtils;
+import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -89,7 +90,7 @@
         : new FoundMethodSubject(codeInspector, encoded, this);
   }
 
-  private DexEncodedMethod findMethod(DexEncodedMethod[] methods, DexMethod dexMethod) {
+  private DexEncodedMethod findMethod(List<DexEncodedMethod> methods, DexMethod dexMethod) {
     for (DexEncodedMethod method : methods) {
       if (method.method.equals(dexMethod)) {
         return method;
@@ -113,12 +114,12 @@
   @Override
   public void forAllFields(Consumer<FoundFieldSubject> inspection) {
     CodeInspector.forAll(
-        dexClass.staticFields(),
+        Arrays.asList(dexClass.staticFields()),
         (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
         this,
         inspection);
     CodeInspector.forAll(
-        dexClass.instanceFields(),
+        Arrays.asList(dexClass.instanceFields()),
         (dexField, clazz) -> new FoundFieldSubject(codeInspector, dexField, clazz),
         this,
         inspection);
diff --git a/third_party/cf_segments.tar.gz.sha1 b/third_party/cf_segments.tar.gz.sha1
index e4d4bec..7d32955 100644
--- a/third_party/cf_segments.tar.gz.sha1
+++ b/third_party/cf_segments.tar.gz.sha1
@@ -1 +1 @@
-2172e0b42550dd144958f9ead6d0310a68b43d65
\ No newline at end of file
+cd6068354ebd74e098011517576f09a375b324a3
\ No newline at end of file
diff --git a/third_party/opensource_apps.tar.gz.sha1 b/third_party/opensource_apps.tar.gz.sha1
new file mode 100644
index 0000000..9fb2bb2
--- /dev/null
+++ b/third_party/opensource_apps.tar.gz.sha1
@@ -0,0 +1 @@
+64b0689bce8f6789320b34da4e91c6dbcc1296e9
\ No newline at end of file
diff --git a/tools/apk_utils.py b/tools/apk_utils.py
index 5a6aa94..af24ec1 100644
--- a/tools/apk_utils.py
+++ b/tools/apk_utils.py
@@ -23,7 +23,8 @@
   ]
   utils.RunCmd(cmd, quiet=quiet)
 
-def sign_with_apksigner(build_tools_dir, unsigned_apk, signed_apk, keystore, password):
+def sign_with_apksigner(
+    build_tools_dir, unsigned_apk, signed_apk, keystore, password, quiet=False):
   cmd = [
     os.path.join(build_tools_dir, 'apksigner'),
     'sign',
@@ -34,5 +35,4 @@
     '--out', signed_apk,
     unsigned_apk
   ]
-  utils.PrintCmd(cmd)
-  subprocess.check_call(cmd)
+  utils.RunCmd(cmd, quiet=quiet)
diff --git a/tools/as_utils.py b/tools/as_utils.py
index 5f224a6..7a6528a 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -127,7 +127,7 @@
       if '-printconfiguration' not in line:
         f.write(line)
     # Check that there is a line-break at the end of the file or insert one.
-    if lines[-1].strip():
+    if len(lines) and lines[-1].strip():
       f.write('\n')
     f.write('-printconfiguration {}\n'.format(destination))
 
@@ -159,7 +159,7 @@
   Move(src, dst, quiet=quiet)
 
 def MoveFile(src, dst, quiet=False):
-  assert os.path.isfile(src)
+  assert os.path.isfile(src), "Expected a file to be present at " + src
   Move(src, dst, quiet=quiet)
 
 def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
diff --git a/tools/download_all_benchmark_dependencies.py b/tools/download_all_benchmark_dependencies.py
new file mode 100755
index 0000000..43ec0ab
--- /dev/null
+++ b/tools/download_all_benchmark_dependencies.py
@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Utility script to make it easier to update what golem builds.
+
+import gradle
+import sys
+import utils
+
+BUILD_TARGETS = ['downloadDeps', 'downloadAndroidCts', 'downloadDx']
+
+def Main():
+  gradle.RunGradle(BUILD_TARGETS)
+  # Download opensource_apps and place in build.
+  utils.DownloadFromX20(utils.OPENSOURCE_APPS_SHA_FILE)
+
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/historic_memory_usage.py b/tools/historic_memory_usage.py
index f23faaa..376426f 100755
--- a/tools/historic_memory_usage.py
+++ b/tools/historic_memory_usage.py
@@ -38,6 +38,10 @@
   result.add_option('--output',
                     default='build',
                     help='Directory where to output results')
+  result.add_option('--timeout',
+                    type=int,
+                    default=0,
+                    help='Set timeout instead of waiting for OOM.')
   return result.parse_args(argv)
 
 
@@ -122,7 +126,10 @@
 def run_on_app(options, commit):
   app = options.app
   compiler = options.compiler
-  cmd = ['tools/run_on_app.py', '--app', app, '--compiler', compiler,
+  cmd = ['tools/run_on_app.py',
+         '--app', app,
+         '--compiler', compiler,
+         '--timeout', str(options.timeout),
          '--no-build', '--find-min-xmx']
   stdout = subprocess.check_output(cmd)
   output_path = options.output or 'build'
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 41d4578..b404dd3 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -27,6 +27,9 @@
 
 # We use this magic exit code to signal that the program OOM'ed
 OOM_EXIT_CODE = 42
+# According to Popen.returncode doc:
+# A negative value -N indicates that the child was terminated by signal N.
+TIMEOUT_KILL_CODE = -9
 
 def ParseOptions(argv):
   result = optparse.OptionParser()
@@ -58,6 +61,10 @@
                     help='Find the minimum amount of memory we can run in',
                     default=False,
                     action='store_true')
+  result.add_option('--timeout',
+                    type=int,
+                    default=0,
+                    help='Set timeout instead of waiting for OOM.')
   result.add_option('--golem',
                     help='Running on golem, do not build or download',
                     default=False,
@@ -178,7 +185,7 @@
   assert len(args) == 0
   # If we can run in 128 MB then we are good (which we can for small examples
   # or D8 on medium sized examples)
-  not_working = 128
+  not_working = 128 if options.compiler == 'd8' else 1024
   working = 1024 * 8
   exit_code = 0
   while working - not_working > 32:
@@ -191,11 +198,15 @@
     exit_code = run_with_options(options, [], extra_args)
     t1 = time.time()
     print('Running took: %s ms' % (1000.0 * (t1 - t0)))
-    if exit_code != 0 and exit_code != OOM_EXIT_CODE:
-      print('Non OOM error executing, exiting')
-      return 2
+    if exit_code != 0:
+      if exit_code not in [OOM_EXIT_CODE, TIMEOUT_KILL_CODE]:
+        print('Non OOM/Timeout error executing, exiting')
+        return 2
     if exit_code == 0:
       working = next_candidate
+    elif exit_code == TIMEOUT_KILL_CODE:
+      print('Timeout. Continue to the next candidate.')
+      not_working = next_candidate
     else:
       assert exit_code == OOM_EXIT_CODE
       not_working = next_candidate
@@ -356,7 +367,9 @@
             debug=not options.no_debug,
             profile=options.profile,
             track_memory_file=options.track_memory_to_file,
-            extra_args=extra_args, stderr=stderr)
+            extra_args=extra_args,
+            stderr=stderr,
+            timeout=options.timeout)
       if exit_code != 0:
         with open(stderr_path) as stderr:
           stderr_text = stderr.read()
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 3a9bf8c..2889d74 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -5,6 +5,7 @@
 
 import apk_masseur
 import apk_utils
+import golem
 import gradle
 import os
 import optparse
@@ -17,15 +18,20 @@
 
 import as_utils
 
-SHRINKERS = ['r8', 'r8-minified', 'r8full', 'r8full-minified', 'proguard']
-WORKING_DIR = utils.BUILD
+SHRINKERS = ['r8', 'r8-full', 'r8-nolib', 'r8-nolib-full', 'pg']
+WORKING_DIR = os.path.join(utils.BUILD, 'opensource_apps')
 
-if 'R8_BENCHMARK_DIR' in os.environ and os.path.isdir(os.environ['R8_BENCHMARK_DIR']):
+if ('R8_BENCHMARK_DIR' in os.environ
+    and os.path.isdir(os.environ['R8_BENCHMARK_DIR'])):
   WORKING_DIR = os.environ['R8_BENCHMARK_DIR']
 
+# For running on Golem all APPS are bundled as an x20-dependency and then copied
+# to WORKING_DIR. To make it easier to update the app-bundle, remove the folder
+# WORKING_DIR and then run run_on_as_app.py --download-only.
 APPS = {
   # 'app-name': {
   #     'git_repo': ...
+  #     'revision': ...,
   #     'app_module': ... (default app)
   #     'archives_base_name': ... (default same as app_module)
   #     'flavor': ... (default no flavor)
@@ -34,6 +40,7 @@
   'AnExplorer': {
       'app_id': 'dev.dworks.apps.anexplorer.pro',
       'git_repo': 'https://github.com/christofferqa/AnExplorer',
+      'revision': '365927477b8eab4052a1882d5e358057ae3dee4d',
       'flavor': 'googleMobilePro',
       'signed-apk-name': 'AnExplorer-googleMobileProRelease-4.0.3.apk',
       'min_sdk': 17
@@ -41,6 +48,7 @@
   'AntennaPod': {
       'app_id': 'de.danoeh.antennapod',
       'git_repo': 'https://github.com/christofferqa/AntennaPod.git',
+      'revision': '77e94f4783a16abe9cc5b78dc2d2b2b1867d8c06',
       'flavor': 'play',
       'min_sdk': 14,
       'compile_sdk': 26
@@ -48,78 +56,105 @@
   'apps-android-wikipedia': {
       'app_id': 'org.wikipedia',
       'git_repo': 'https://github.com/christofferqa/apps-android-wikipedia',
+      'revision': '686e8aa5682af8e6a905054b935dd2daa57e63ee',
       'flavor': 'prod',
-      'signed-apk-name': 'app-prod-universal-release.apk'
+      'signed-apk-name': 'app-prod-universal-release.apk',
   },
   'chanu': {
-    'app_id': 'com.chanapps.four.activity',
-    'git_repo': 'https://github.com/mkj-gram/chanu.git',
+      'app_id': 'com.chanapps.four.activity',
+      'git_repo': 'https://github.com/mkj-gram/chanu.git',
+      'revision': '04ade1e9c33d707f0850d5eb9d6fa5e8af814a26',
   },
   'friendlyeats-android': {
       'app_id': 'com.google.firebase.example.fireeats',
-      'git_repo': 'https://github.com/christofferqa/friendlyeats-android.git'
+      'git_repo': 'https://github.com/christofferqa/friendlyeats-android.git',
+      'revision': '10091fa0ec37da12e66286559ad1b6098976b07b',
+  },
+  'Instabug-Android': {
+      'app_id': 'com.example.instabug',
+      'git_repo': 'https://github.com/christofferqa/Instabug-Android.git',
+      'revision': 'b8df78c96630a6537fbc07787b4990afc030cc0f'
   },
   'KISS': {
       'app_id': 'fr.neamar.kiss',
       'git_repo': 'https://github.com/christofferqa/KISS',
+      'revision': '093da9ee0512e67192f62951c45a07a616fc3224',
   },
   'materialistic': {
       'app_id': 'io.github.hidroh.materialistic',
       'git_repo': 'https://github.com/christofferqa/materialistic',
+      'revision': '2b2b2ee25ce9e672d5aab1dc90a354af1522b1d9',
   },
   'Minimal-Todo': {
       'app_id': 'com.avjindersinghsekhon.minimaltodo',
       'git_repo': 'https://github.com/christofferqa/Minimal-Todo',
+      'revision': '9d8c73746762cd376b718858ec1e8783ca07ba7c',
   },
   'NewPipe': {
       'app_id': 'org.schabi.newpipe',
       'git_repo': 'https://github.com/christofferqa/NewPipe',
+      'revision': 'ed543099c7823be00f15d9340f94bdb7cb37d1e6',
   },
   'rover-android': {
-    'app_id': 'io.rover.app.debug',
-    'app_module': 'debug-app',
-    'git_repo': 'https://github.com/mkj-gram/rover-android.git',
+      'app_id': 'io.rover.app.debug',
+      'app_module': 'debug-app',
+      'git_repo': 'https://github.com/mkj-gram/rover-android.git',
+      'revision': 'd2e876e597b3af7eab406e38a0e08327a38bd942',
   },
   'Signal-Android': {
-    'app_id': 'org.thoughtcrime.securesms',
-    'app_module': '',
-    'flavor': 'play',
-    'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
-    'releaseTarget': 'assemblePlayRelease',
-    'signed-apk-name': 'Signal-play-release-4.32.7.apk',
+      'app_id': 'org.thoughtcrime.securesms',
+      'app_module': '',
+      'flavor': 'play',
+      'git_repo': 'https://github.com/mkj-gram/Signal-Android.git',
+      'revision': '85e1a10993e5e9ffe923f0798b26cbc44068ba31',
+      'releaseTarget': 'assemblePlayRelease',
+      'signed-apk-name': 'Signal-play-release-4.32.7.apk',
   },
   'Simple-Calendar': {
       'app_id': 'com.simplemobiletools.calendar.pro',
       'git_repo': 'https://github.com/christofferqa/Simple-Calendar',
+      'revision': '82dad8c203eea5a0f0ddb513506d8f1de986ef2b',
       'signed-apk-name': 'calendar-release.apk'
   },
+  'sqldelight': {
+      'app_id': 'com.example.sqldelight.hockey',
+      'git_repo': 'https://github.com/christofferqa/sqldelight.git',
+      'revision': '2e67a1126b6df05e4119d1e3a432fde51d76cdc8',
+      'app_module': 'sample/android',
+      'archives_base_name': 'android',
+      'min_sdk': 14,
+      'compile_sdk': 28,
+  },
   'tachiyomi': {
       'app_id': 'eu.kanade.tachiyomi',
       'git_repo': 'https://github.com/sgjesse/tachiyomi.git',
+      'revision': 'b15d2fe16864645055af6a745a62cc5566629798',
       'flavor': 'standard',
       'releaseTarget': 'app:assembleRelease',
       'min_sdk': 16
   },
   'tivi': {
       'app_id': 'app.tivi',
-      # Forked from https://github.com/chrisbanes/tivi.git removing
-      # signingConfigs.
       'git_repo': 'https://github.com/sgjesse/tivi.git',
-      # TODO(123047413): Fails with R8.
-      'skip': True,
+      'revision': '7d7f591d6f39d7caeb88dd13bf476c0c06accdfb',
+      'min_sdk': 23,
+      'compile_sdk': 28,
   },
   'Tusky': {
-    'app_id': 'com.keylesspalace.tusky',
-    'git_repo': 'https://github.com/mkj-gram/Tusky.git',
-    'flavor': 'blue'
+      'app_id': 'com.keylesspalace.tusky',
+      'git_repo': 'https://github.com/mkj-gram/Tusky.git',
+      'revision': 'b794f3ab90388add98461ffe70edb65c39351c33',
+      'flavor': 'blue'
   },
   'Vungle-Android-SDK': {
-    'app_id': 'com.publisher.vungle.sample',
-    'git_repo': 'https://github.com/mkj-gram/Vungle-Android-SDK.git',
+      'app_id': 'com.publisher.vungle.sample',
+      'git_repo': 'https://github.com/mkj-gram/Vungle-Android-SDK.git',
+      'revision': '3e231396ea7ce97b2655e03607497c75730e45f6',
   },
   # This does not build yet.
   'muzei': {
       'git_repo': 'https://github.com/sgjesse/muzei.git',
+      'revision': 'bed2a5f79c6e08b0a21e3e3f9242232d0848ef74',
       'app_module': 'main',
       'archives_base_name': 'muzei',
       'skip': True,
@@ -151,17 +186,21 @@
   return '~~R8' in subprocess.check_output(cmd).strip()
 
 def IsMinifiedR8(shrinker):
-  return shrinker == 'r8-minified' or shrinker == 'r8full-minified'
+  return 'nolib' not in shrinker
 
 def IsTrackedByGit(file):
   return subprocess.check_output(['git', 'ls-files', file]).strip() != ''
 
-def GitClone(git_url):
-  return subprocess.check_output(['git', 'clone', git_url]).strip()
-
-def GitPull():
-  # Use --no-edit to accept the auto-generated merge message, if any.
-  return subprocess.call(['git', 'pull', '--no-edit']) == 0
+def GitClone(git_url, revision, checkout_dir, options):
+  result = subprocess.check_output(
+      ['git', 'clone', git_url, checkout_dir]).strip()
+  head_rev = utils.get_HEAD_sha1_for_checkout(checkout_dir)
+  if revision == head_rev:
+    return result
+  warn('Target revision is not head in {}.'.format(checkout_dir))
+  with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
+    subprocess.check_output(['git', 'reset', '--hard', revision])
+  return result
 
 def GitCheckout(file):
   return subprocess.check_output(['git', 'checkout', file]).strip()
@@ -183,7 +222,7 @@
 def UninstallApkOnEmulator(app, config, options):
   app_id = config.get('app_id')
   process = subprocess.Popen(
-      ['adb', 'uninstall', app_id],
+      ['adb', '-s', emulator_id, 'uninstall', app_id],
       stdout=subprocess.PIPE, stderr=subprocess.PIPE)
   stdout, stderr = process.communicate()
 
@@ -220,26 +259,23 @@
       return True
 
 def GetResultsForApp(app, config, options, temp_dir):
-  git_repo = config['git_repo']
-
   # Checkout and build in the build directory.
   checkout_dir = os.path.join(WORKING_DIR, app)
 
   result = {}
 
-  if not os.path.exists(checkout_dir):
+  if not os.path.exists(checkout_dir) and not options.golem:
     with utils.ChangedWorkingDirectory(WORKING_DIR, quiet=options.quiet):
-      GitClone(git_repo)
-  elif options.pull:
-    with utils.ChangedWorkingDirectory(checkout_dir, quiet=options.quiet):
-      # Checkout build.gradle to avoid merge conflicts.
-      if IsTrackedByGit('build.gradle'):
-        GitCheckout('build.gradle')
+      GitClone(config['git_repo'], config['revision'], checkout_dir, options)
 
-      if not GitPull():
-        result['status'] = 'failed'
-        result['error_message'] = 'Unable to pull from remote'
-        return result
+  checkout_rev = utils.get_HEAD_sha1_for_checkout(checkout_dir)
+  if config['revision'] != checkout_rev:
+    msg = 'Checkout is not target revision for {} in {}.'.format(
+        app, checkout_dir)
+    if options.ignore_versions:
+      warn(msg)
+    else:
+      raise Exception(msg)
 
   result['status'] = 'success'
 
@@ -267,7 +303,7 @@
             BuildAppWithShrinker(app, config, shrinker, checkout_dir, out_dir,
                 temp_dir, options)
         dex_size = ComputeSizeOfDexFilesInApk(apk_dest)
-        result['apk_dest'] = apk_dest,
+        result['apk_dest'] = apk_dest
         result['build_status'] = 'success'
         result['dex_size'] = dex_size
         result['profile_dest_dir'] = profile_dest_dir
@@ -357,11 +393,8 @@
       shrinker,
       ' for recompilation' if keepRuleSynthesisForRecompilation else ''))
 
-  # Add/remove 'r8.jar' from top-level build.gradle.
-  if options.disable_tot:
-    as_utils.remove_r8_dependency(checkout_dir)
-  else:
-    as_utils.add_r8_dependency(checkout_dir, temp_dir, IsMinifiedR8(shrinker))
+  # Add 'r8.jar' from top-level build.gradle.
+  as_utils.add_r8_dependency(checkout_dir, temp_dir, IsMinifiedR8(shrinker))
 
   app_module = config.get('app_module', 'app')
   archives_base_name = config.get('archives_base_name', app_module)
@@ -382,13 +415,13 @@
 
   releaseTarget = config.get('releaseTarget')
   if not releaseTarget:
-    releaseTarget = app_module + ':' + 'assemble' + (
+    releaseTarget = app_module.replace('/', ':') + ':' + 'assemble' + (
         flavor.capitalize() if flavor else '') + 'Release'
 
   # Value for property android.enableR8.
   enableR8 = 'r8' in shrinker
   # Value for property android.enableR8.fullMode.
-  enableR8FullMode = shrinker == 'r8full' or shrinker == 'r8full-minified'
+  enableR8FullMode = shrinker == 'r8-full' or shrinker == 'r8-nolib-full'
   # Build gradlew command line.
   cmd = ['./gradlew', '--no-daemon', 'clean', releaseTarget,
          '--profile', '--stacktrace',
@@ -419,14 +452,13 @@
   if options.sign_apks and not os.path.isfile(signed_apk):
     assert os.path.isfile(unsigned_apk)
     if options.sign_apks:
-      keystore = 'app.keystore'
-      keystore_password = 'android'
       apk_utils.sign_with_apksigner(
           utils.ANDROID_BUILD_TOOLS,
           unsigned_apk,
           signed_apk,
-          keystore,
-          keystore_password)
+          options.keystore,
+          options.keystore_password,
+          quiet=options.quiet)
 
   if os.path.isfile(signed_apk):
     apk_dest = os.path.join(out_dir, signed_apk_name)
@@ -507,10 +539,28 @@
 def LogResultsForApps(result_per_shrinker_per_app, options):
   print('')
   for app, result_per_shrinker in sorted(
-      result_per_shrinker_per_app.iteritems()):
+      result_per_shrinker_per_app.iteritems(), key=lambda s: s[0].lower()):
     LogResultsForApp(app, result_per_shrinker, options)
 
 def LogResultsForApp(app, result_per_shrinker, options):
+  if options.print_dexsegments:
+    LogSegmentsForApp(app, result_per_shrinker, options)
+  else:
+    LogComparisonResultsForApp(app, result_per_shrinker, options)
+
+def LogSegmentsForApp(app, result_per_shrinker, options):
+  for shrinker in SHRINKERS:
+    if shrinker not in result_per_shrinker:
+      continue
+    result = result_per_shrinker[shrinker];
+    benchmark_name = '{}-{}'.format(options.print_dexsegments, app)
+    utils.print_dexsegments(benchmark_name, [result.get('apk_dest')])
+    duration = sum(result.get('profile').values())
+    print('%s-Total(RunTimeRaw): %s ms' % (benchmark_name, duration * 1000))
+    print('%s-Total(CodeSize): %s' % (benchmark_name, result.get('dex_size')))
+
+
+def LogComparisonResultsForApp(app, result_per_shrinker, options):
   print(app + ':')
 
   if result_per_shrinker.get('status', 'success') != 'success':
@@ -518,7 +568,7 @@
     print('  skipped ({})'.format(error_message))
     return
 
-  proguard_result = result_per_shrinker.get('proguard', {})
+  proguard_result = result_per_shrinker.get('pg', {})
   proguard_dex_size = float(proguard_result.get('dex_size', -1))
   proguard_duration = sum(proguard_result.get('profile', {}).values())
 
@@ -587,6 +637,27 @@
   result.add_option('--app',
                     help='What app to run on',
                     choices=APPS.keys())
+  result.add_option('--download-only', '--download_only',
+                    help='Whether to download apps without any compilation',
+                    default=False,
+                    action='store_true')
+  result.add_option('--golem',
+                    help='Running on golem, do not download',
+                    default=False,
+                    action='store_true')
+  result.add_option('--gradle-flags', '--gradle_flags',
+                    help='Flags to pass in to gradle')
+  result.add_option('--ignore-versions', '--ignore_versions',
+                    help='Allow checked-out app to differ in revision from '
+                         'pinned',
+                    default=False,
+                    action='store_true')
+  result.add_option('--keystore',
+                    help='Path to app.keystore',
+                    default='app.keystore')
+  result.add_option('--keystore-password', '--keystore_password',
+                    help='Password for app.keystore',
+                    default='android')
   result.add_option('--monkey',
                     help='Whether to install and run app(s) with monkey',
                     default=False,
@@ -595,10 +666,23 @@
                     help='Number of events that the monkey should trigger',
                     default=250,
                     type=int)
-  result.add_option('--pull',
-                    help='Whether to pull the latest version of each app',
+  result.add_option('--no-build', '--no_build',
+                    help='Run without building ToT first (only when using ToT)',
                     default=False,
                     action='store_true')
+  result.add_option('--quiet',
+                    help='Disable verbose logging',
+                    default=False,
+                    action='store_true')
+  result.add_option('--print-dexsegments',
+                    metavar='BENCHMARKNAME',
+                    help='Print the sizes of individual dex segments as ' +
+                         '\'<BENCHMARKNAME>-<APP>-<segment>(CodeSize): '
+                         '<bytes>\'')
+  result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
+                    help='Number of times R8 should be run on each app',
+                    default=2,
+                    type=int)
   result.add_option('--sign-apks', '--sign_apks',
                     help='Whether the APKs should be signed',
                     default=False,
@@ -606,57 +690,50 @@
   result.add_option('--shrinker',
                     help='The shrinkers to use (by default, all are run)',
                     action='append')
-  result.add_option('--r8-compilation-steps', '--r8_compilation_steps',
-                    help='Number of times R8 should be run on each app',
-                    default=2,
-                    type=int)
-  result.add_option('--disable-tot', '--disable_tot',
-                    help='Whether to disable the use of the ToT version of R8',
-                    default=False,
-                    action='store_true')
-  result.add_option('--no-build', '--no_build',
-                    help='Run without building ToT first (only when using ToT)',
-                    default=False,
-                    action='store_true')
-  result.add_option('--gradle-flags', '--gradle_flags',
-                    help='Flags to pass in to gradle')
-  result.add_option('--quiet',
-                    help='Disable verbose logging',
-                    default=False,
-                    action='store_true')
   (options, args) = result.parse_args(argv)
-  if options.disable_tot:
-    # r8.jar is required for recompiling the generated APK
-    options.r8_compilation_steps = 1
   if options.shrinker:
     for shrinker in options.shrinker:
       assert shrinker in SHRINKERS
   return (options, args)
 
+def download_apps(options):
+  # Download apps and place in build
+  with utils.ChangedWorkingDirectory(WORKING_DIR):
+    for app, config in APPS.iteritems():
+      app_dir = os.path.join(WORKING_DIR, app)
+      if not os.path.exists(app_dir):
+        GitClone(config['git_repo'], config['revision'], app_dir, options)
+
+
 def main(argv):
   global SHRINKERS
 
   (options, args) = ParseOptions(argv)
 
+  if options.golem:
+    golem.link_third_party()
+    if os.path.exists(WORKING_DIR):
+      shutil.rmtree(WORKING_DIR)
+    shutil.copytree(utils.OPENSOURCE_APPS_FOLDER, WORKING_DIR)
+
+  if not os.path.exists(WORKING_DIR):
+    os.makedirs(WORKING_DIR)
+
+  if options.download_only:
+    download_apps(options)
+    return
+
   with utils.TempDir() as temp_dir:
-    if options.disable_tot:
-      # Cannot run r8 lib without adding r8lib.jar as an dependency
-      SHRINKERS = [
-          shrinker for shrinker in SHRINKERS
-          if 'minified' not in shrinker]
-    else:
-      if not options.no_build:
-        gradle.RunGradle(['r8', 'r8lib'])
+    if not options.no_build or options.golem:
+      gradle.RunGradle(['r8', 'r8lib'])
 
-      assert os.path.isfile(utils.R8_JAR), (
-          'Cannot build from ToT without r8.jar')
-      assert os.path.isfile(utils.R8LIB_JAR), (
-          'Cannot build from ToT without r8lib.jar')
+    assert os.path.isfile(utils.R8_JAR), 'Cannot build without r8.jar'
+    assert os.path.isfile(utils.R8LIB_JAR), 'Cannot build without r8lib.jar'
 
-      # Make a copy of r8.jar and r8lib.jar such that they stay the same for
-      # the entire execution of this script.
-      shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
-      shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
+    # Make a copy of r8.jar and r8lib.jar such that they stay the same for
+    # the entire execution of this script.
+    shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
+    shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))
 
     result_per_shrinker_per_app = {}
 
@@ -664,7 +741,7 @@
       result_per_shrinker_per_app[options.app] = GetResultsForApp(
           options.app, APPS.get(options.app), options, temp_dir)
     else:
-      for app, config in sorted(APPS.iteritems()):
+      for app, config in sorted(APPS.iteritems(), key=lambda s: s[0].lower()):
         if not config.get('skip', False):
           result_per_shrinker_per_app[app] = GetResultsForApp(
               app, config, options, temp_dir)
diff --git a/tools/toolhelper.py b/tools/toolhelper.py
index d7cf222..9371fb9 100644
--- a/tools/toolhelper.py
+++ b/tools/toolhelper.py
@@ -6,11 +6,12 @@
 import gradle
 import os
 import subprocess
+from threading import Timer
 import utils
 
 def run(tool, args, build=None, debug=True,
         profile=False, track_memory_file=None, extra_args=None,
-        stderr=None, stdout=None, return_stdout=False):
+        stderr=None, stdout=None, return_stdout=False, timeout=0):
   if build is None:
     build, args = extract_build_from_args(args)
   if build:
@@ -36,9 +37,20 @@
     cmd.extend(["--lib", lib])
   cmd.extend(args)
   utils.PrintCmd(cmd)
-  if return_stdout:
-    return subprocess.check_output(cmd)
-  return subprocess.call(cmd, stdout=stdout, stderr=stderr)
+  if timeout > 0:
+    kill = lambda process: process.kill()
+    proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    timer = Timer(timeout, kill, [proc])
+    try:
+      timer.start()
+      stdout, stderr = proc.communicate()
+    finally:
+      timer.cancel()
+    return stdout if return_stdout else proc.returncode
+  else:
+    if return_stdout:
+      return subprocess.check_output(cmd)
+    return subprocess.call(cmd, stdout=stdout, stderr=stderr)
 
 def run_in_tests(tool, args, build=None, debug=True, extra_args=None):
   if build is None:
diff --git a/tools/utils.py b/tools/utils.py
index aff96a3..08faec6 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -62,6 +62,10 @@
 CF_SEGMENTS_TOOL = os.path.join(THIRD_PARTY, 'cf_segments')
 PINNED_R8_JAR = os.path.join(REPO_ROOT, 'third_party/r8/r8.jar')
 PINNED_PGR8_JAR = os.path.join(REPO_ROOT, 'third_party/r8/r8-pg6.0.1.jar')
+OPENSOURCE_APPS_SHA_FILE = os.path.join(
+    REPO_ROOT,
+    'third_party/opensource_apps.tar.gz.sha1')
+OPENSOURCE_APPS_FOLDER = os.path.join(REPO_ROOT, 'third_party/opensource_apps/')
 
 
 # Common environment setup.
@@ -184,9 +188,12 @@
   return 'origin/master' in remotes
 
 def get_HEAD_sha1():
+  return get_HEAD_sha1_for_checkout(REPO_ROOT)
+
+def get_HEAD_sha1_for_checkout(checkout):
   cmd = ['git', 'rev-parse', 'HEAD']
   PrintCmd(cmd)
-  with ChangedWorkingDirectory(REPO_ROOT):
+  with ChangedWorkingDirectory(checkout):
     return subprocess.check_output(cmd).strip()
 
 def makedirs_if_needed(path):