Merge "Run string optimizations after class inlining."
diff --git a/build.gradle b/build.gradle
index ec00f11..759e475 100644
--- a/build.gradle
+++ b/build.gradle
@@ -602,45 +602,31 @@
         "--pg-conf", pgconf]
 }
 
-task R8R8(type: Exec) {
+task R8Lib(type: Exec) {
     def pgconf = "src/main/keep.txt"
-    def output = "build/libs/r8-r8.jar"
-    inputs.files files(pgconf, "build/libs/r8.jar")
+    def output = "build/libs/r8lib.jar"
+    inputs.files files(pgconf, R8.outputs)
     outputs.file output
-    dependsOn R8
     dependsOn downloadOpenJDKrt
     commandLine r8CfCommandLine(R8.outputs.files[0], output, pgconf)
     workingDir = projectDir
 }
 
-task D8R8(type: Exec) {
-    def pgconf = "src/main/keep.txt"
-    def output = "build/libs/d8-r8.jar"
-    inputs.files files(pgconf, "build/libs/d8.jar")
-    outputs.file output
-    dependsOn downloadOpenJDKrt
-    dependsOn D8
-    commandLine r8CfCommandLine(R8.outputs.files[0], output, pgconf)
-    workingDir = projectDir
-}
-
-task CompatDxR8(type: Exec) {
+task CompatDxLib(type: Exec) {
     def pgconf = "src/main/keep-compatdx.txt"
-    def output = "build/libs/compatdx-r8.jar"
-    inputs.files files(pgconf, "build/libs/compatdx.jar")
+    def output = "build/libs/compatdxlib.jar"
+    inputs.files files(pgconf, CompatDx.outputs, R8.outputs)
     outputs.file output
-    dependsOn CompatDx
     dependsOn downloadOpenJDKrt
     commandLine r8CfCommandLine(R8.outputs.files[0], output, pgconf)
     workingDir = projectDir
 }
 
-task CompatProguardR8(type: Exec) {
+task CompatProguardLib(type: Exec) {
     def pgconf = "src/main/keep-compatproguard.txt"
-    def output = "build/libs/compatproguard-r8.jar"
-    inputs.files files(pgconf, "build/libs/compatproguard.jar")
+    def output = "build/libs/compatproguardlib.jar"
+    inputs.files files(pgconf, CompatProguard.outputs, R8.outputs)
     outputs.file output
-    dependsOn CompatProguard
     dependsOn downloadOpenJDKrt
     commandLine r8CfCommandLine(R8.outputs.files[0], output, pgconf)
     workingDir = projectDir
@@ -1343,6 +1329,9 @@
             println "Start executing test ${desc.name} [${desc.className}]"
         }
         afterTest { desc, result ->
+            if (project.hasProperty('update_test_timestamp')) {
+                file(project.getProperty('update_test_timestamp')).text = new Date().getTime()
+            }
             println "Done executing test ${desc.name} [${desc.className}] with result: ${result.resultType}"
         }
     }
diff --git a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
index 118df2e..e235e67 100644
--- a/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
+++ b/src/main/java/com/android/tools/r8/DexIndexedConsumer.java
@@ -160,7 +160,7 @@
     public void accept(
         int fileIndex, ByteDataView data, Set<String> descriptors, DiagnosticsHandler handler) {
       super.accept(fileIndex, data, descriptors, handler);
-      outputBuilder.addFile(getDexFileName(fileIndex), data, handler);
+      outputBuilder.addIndexedClassFile(fileIndex, getDexFileName(fileIndex), data, handler);
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7f77c6b..494a62b 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -431,6 +431,7 @@
       new SourceFileRewriter(appView.appInfo(), options).run();
       timing.end();
 
+      MainDexClasses mainDexClasses = MainDexClasses.NONE;
       if (!options.mainDexKeepRules.isEmpty()) {
         appView.setAppInfo(new AppInfoWithSubtyping(application));
         Enqueuer enqueuer = new Enqueuer(appView, options, true);
@@ -444,8 +445,7 @@
         // LiveTypes is the tracing result.
         Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
         // Calculate the automatic main dex list according to legacy multidex constraints.
-        MainDexClasses mainDexClasses =
-            new MainDexListBuilder(mainDexBaseClasses, application).run();
+        mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
         if (!mainDexRootSet.checkDiscarded.isEmpty()) {
           new DiscardedChecker(
                   mainDexRootSet, mainDexClasses.getClasses(), appView.appInfo(), options)
@@ -459,8 +459,6 @@
           ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(mainDexRootSet.reasonAsked);
           reasonPrinter.run(mainDexApplication);
         }
-        // Add automatic main dex classes to an eventual manual list of classes.
-        application = application.builder().addToMainDexList(mainDexClasses.getClasses()).build();
       }
 
       appView.setAppInfo(new AppInfoWithSubtyping(application));
@@ -484,12 +482,21 @@
             reasonPrinter.run(application);
             // Remove annotations that refer to types that no longer exist.
             new AnnotationRemover(appView.appInfo().withLiveness(), options).run();
+            if (!mainDexClasses.isEmpty()) {
+              // Remove types that no longer exists from the computed main dex list.
+              mainDexClasses = mainDexClasses.prunedCopy(appView.appInfo().withLiveness());
+            }
           }
         } finally {
           timing.end();
         }
       }
 
+      // Add automatic main dex classes to an eventual manual list of classes.
+      if (!options.mainDexKeepRules.isEmpty()) {
+        application = application.builder().addToMainDexList(mainDexClasses.getClasses()).build();
+      }
+
       // Only perform discard-checking if tree-shaking is turned on.
       if (options.enableTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
         new DiscardedChecker(rootSet, application, options).run();
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index bdea79c..d23f3d8 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.13-dev";
+  public static final String LABEL = "1.4.14-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 6914bc1..724faf1 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -381,6 +381,37 @@
     updateFirstStackByJoiningTheSecond(typesOfKept.stack, typesOfRemoved.stack);
   }
 
+  @Override
+  public boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second) {
+    if (!java.util.Objects.equals(first.getLocalsAtEntry(), second.getLocalsAtEntry())) {
+      return false;
+    }
+    // Check that stack at entry is same.
+    List<TypeInfo> firstStack = getTypesAtBlockEntry(first).stack;
+    List<TypeInfo> secondStack = getTypesAtBlockEntry(second).stack;
+    if (firstStack.size() != secondStack.size()) {
+      return false;
+    }
+    for (int i = 0; i < firstStack.size(); i++) {
+      if (firstStack.get(i).getDexType() != secondStack.get(i).getDexType()) {
+        return false;
+      }
+    }
+    // Check if the blocks are catch-handlers, which implies that the exception is transferred
+    // through the stack.
+    if (first.entry().isMoveException() != second.entry().isMoveException()) {
+      return false;
+    }
+    // If both blocks are catch handlers, we require that the exceptions are the same type.
+    if (first.entry().isMoveException()
+        && typeHelper.getTypeInfo(first.entry().outValue()).getDexType()
+            != typeHelper.getTypeInfo(second.entry().outValue()).getDexType()) {
+      return false;
+    }
+
+    return true;
+  }
+
   // Return false if this is not an instruction with the type of outvalue dependent on types of
   // invalues.
   private boolean tryApplyInstructionWithDependentOutType(
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 e9966f5..1c04f9d 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -993,13 +993,14 @@
 
   public ReferenceTypeLatticeElement createReferenceTypeLatticeElement(
       DexType type, boolean isNullable, AppInfo appInfo) {
+    ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
+    if (typeLattice != null) {
+      return isNullable == typeLattice.isNullable() ? typeLattice
+          : typeLattice.getOrCreateDualLattice();
+    }
     synchronized (type) {
-      ReferenceTypeLatticeElement typeLattice = referenceTypeLatticeElements.get(type);
-      if (typeLattice != null) {
-        typeLattice = isNullable == typeLattice.isNullable() ? typeLattice
-            : typeLattice.getOrCreateDualLattice();
-        assert typeLattice.isNullable() == isNullable;
-      } else {
+      typeLattice = referenceTypeLatticeElements.get(type);
+      if (typeLattice == null) {
         if (type.isClassType()) {
           if (!type.isUnknown() && type.isInterface()) {
             typeLattice = new ClassTypeLatticeElement(
@@ -1020,9 +1021,11 @@
         }
         referenceTypeLatticeElements.put(type, typeLattice);
       }
-      assert typeLattice.isNullable() == isNullable;
-      return typeLattice;
     }
+    // The call to getOrCreateDualLattice can't be under the DexType synchronized block, since that
+    // can create deadlocks with ClassTypeLatticeElement::getInterfaces (both lock on the lattice).
+    return isNullable == typeLattice.isNullable() ? typeLattice
+        : typeLattice.getOrCreateDualLattice();
   }
 
   private static <S extends PresortedComparable<S>> void assignSortedIndices(Collection<S> items,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
index 73cf07f..17c8d1a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/BasicBlockInstructionsEquivalence.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.google.common.base.Equivalence;
-import com.google.common.base.Objects;
 import java.util.Arrays;
 import java.util.List;
 
@@ -40,7 +39,7 @@
       }
     }
 
-    if (!Objects.equal(first.getLocalsAtEntry(), second.getLocalsAtEntry())) {
+    if (!allocator.hasEqualTypesAtEntry(first, second)) {
       return false;
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
index 8b43bdd..b495507 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/ClassInliner.java
@@ -130,9 +130,10 @@
       Supplier<InliningOracle> defaultOracle) {
 
     // Collect all the new-instance and static-get instructions in the code before inlining.
-    List<Instruction> roots = Streams.stream(code.instructionIterator())
-        .filter(insn -> insn.isNewInstance() || insn.isStaticGet())
-        .collect(Collectors.toList());
+    List<Instruction> roots =
+        Streams.stream(code.instructionIterator())
+            .filter(insn -> insn.isNewInstance() || insn.isStaticGet())
+            .collect(Collectors.toList());
 
     // We loop inlining iterations until there was no inlining, but still use same set
     // of roots to avoid infinite inlining. Looping makes possible for some roots to
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index ade48f3..d952a6c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -104,8 +104,8 @@
       return false;
     }
 
-    eligibleClass = isNewInstance() ?
-        root.asNewInstance().clazz : root.asStaticGet().getField().type;
+    eligibleClass =
+        root.isNewInstance() ? root.asNewInstance().clazz : root.asStaticGet().getField().type;
     eligibleClassDefinition = appInfo.definitionFor(eligibleClass);
     if (eligibleClassDefinition == null && lambdaRewriter != null) {
       // Check if the class is synthesized for a desugared lambda
@@ -131,7 +131,7 @@
       return false;
     }
 
-    if (isNewInstance()) {
+    if (root.isNewInstance()) {
       // NOTE: if the eligible class does not directly extend java.lang.Object,
       // we also have to guarantee that it is initialized with initializer classified as
       // TrivialInstanceInitializer. This will be checked in areInstanceUsersEligible(...).
@@ -197,8 +197,11 @@
     //      of class inlining
     //
 
-    if (eligibleClassDefinition.instanceFields().length > 0 ||
-        !eligibleClassDefinition.accessFlags.isFinal()) {
+    if (eligibleClassDefinition.instanceFields().length > 0) {
+      return false;
+    }
+    if (eligibleClassDefinition.type.hasSubtypes()) {
+      assert !eligibleClassDefinition.accessFlags.isFinal();
       return false;
     }
 
@@ -796,10 +799,6 @@
     }
   }
 
-  private boolean isNewInstance() {
-    return root.isNewInstance();
-  }
-
   private DexEncodedMethod findSingleTarget(DexMethod callee, boolean isDirect) {
     // We don't use computeSingleTarget(...) on invoke since it sometimes fails to
     // find the single target, while this code may be more successful since we exactly
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index c971a14..40d31d2 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -3172,6 +3172,11 @@
   }
 
   @Override
+  public boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second) {
+    return java.util.Objects.equals(first.getLocalsAtEntry(), second.getLocalsAtEntry());
+  }
+
+  @Override
   public void addNewBlockToShareIdenticalSuffix(
       BasicBlock block, int suffixSize, List<BasicBlock> predsBeforeSplit) {
     // Intentionally empty, we don't need to track suffix sharing in this allocator.
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index 1314b3f..43b41e5 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -17,6 +17,8 @@
 
   void mergeBlocks(BasicBlock kept, BasicBlock removed);
 
+  boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second);
+
   // Call before removing the suffix from the preds. Block is used only as a key.
   void addNewBlockToShareIdenticalSuffix(
       BasicBlock block, int suffixSize, List<BasicBlock> predsBeforeSplit);
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
index 3869db6..565a5df 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexClasses.java
@@ -7,13 +7,19 @@
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 public class MainDexClasses {
 
+  public static MainDexClasses NONE = new MainDexClasses(ImmutableSet.of(), ImmutableSet.of());
+
   public static class Builder {
     public final AppInfo appInfo;
     public final Set<DexType> roots = Sets.newIdentityHashSet();
@@ -23,6 +29,12 @@
       this.appInfo = appInfo;
     }
 
+    public Builder addRoot(DexType type) {
+      assert isProgramClass(type) : type.toSourceString();
+      roots.add(type);
+      return this;
+    }
+
     public Builder addRoots(Collection<DexType> rootSet) {
       assert rootSet.stream().allMatch(this::isProgramClass);
       this.roots.addAll(rootSet);
@@ -50,21 +62,26 @@
   }
 
   // The classes in the root set.
-  private final Set<DexType> rootSet;
-  // Additional dependencies (direct dependencise and runtime annotations with enums).
+  private final Set<DexType> roots;
+  // Additional dependencies (direct dependencies and runtime annotations with enums).
   private final Set<DexType> dependencies;
   // All main dex classes.
   private final Set<DexType> classes;
 
-  private MainDexClasses(Set<DexType> rootSet, Set<DexType> dependencies) {
-    assert Sets.intersection(rootSet, dependencies).isEmpty();
-    this.rootSet = Collections.unmodifiableSet(rootSet);
+  private MainDexClasses(Set<DexType> roots, Set<DexType> dependencies) {
+    assert Sets.intersection(roots, dependencies).isEmpty();
+    this.roots = Collections.unmodifiableSet(roots);
     this.dependencies = Collections.unmodifiableSet(dependencies);
-    this.classes = Sets.union(rootSet, dependencies);
+    this.classes = Sets.union(roots, dependencies);
+  }
+
+  public boolean isEmpty() {
+    assert !roots.isEmpty() || dependencies.isEmpty();
+    return roots.isEmpty();
   }
 
   public Set<DexType> getRoots() {
-    return rootSet;
+    return roots;
   }
 
   public Set<DexType> getDependencies() {
@@ -75,6 +92,24 @@
     return classes;
   }
 
+  private void collectTypesMatching(
+      Set<DexType> types, Predicate<DexType> predicate, Consumer<DexType> consumer) {
+    types.forEach(
+        type -> {
+          if (predicate.test(type)) {
+            consumer.accept(type);
+          }
+        });
+  }
+
+  public MainDexClasses prunedCopy(AppInfoWithLiveness appInfo) {
+    Builder builder = builder(appInfo);
+    Predicate<DexType> wasPruned = appInfo::wasPruned;
+    collectTypesMatching(roots, wasPruned.negate(), builder::addRoot);
+    collectTypesMatching(dependencies, wasPruned.negate(), builder::addDependency);
+    return builder.build();
+  }
+
   public static Builder builder(AppInfo appInfo) {
     return new Builder(appInfo);
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index 747dea9..dc8925a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -112,6 +112,10 @@
     return null;
   }
 
+  public final boolean matchesSpecificType() {
+    return getSpecificType() != null;
+  }
+
   private static class MatchAllTypes extends ProguardTypeMatcher {
 
     private static final ProguardTypeMatcher MATCH_ALL_TYPES = new MatchAllTypes();
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 de0537f..7485f31 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -593,19 +593,23 @@
       implementsExpected = satisfyImplementsRule(clazz, rule);
     }
     if (extendsExpected || implementsExpected) {
-      // Warn if users got it wrong, but only warn once.
-      if (rule.getInheritanceIsExtends()) {
-        if (implementsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
+      // Warn if users got it wrong, but only warn once. Also, only warn if rule is actually
+      // specific (there is no correct way to write "keep class X that extends or implements from
+      // a class or interface in package Y").
+      if (rule.getInheritanceClassName().matchesSpecificType()) {
+        if (rule.getInheritanceIsExtends()) {
+          if (implementsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
+            assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
+            options.reporter.warning(
+                new StringDiagnostic(
+                    "The rule `" + rule + "` uses extends but actually matches implements."));
+          }
+        } else if (extendsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
           assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
           options.reporter.warning(
               new StringDiagnostic(
-                  "The rule `" + rule + "` uses extends but actually matches implements."));
+                  "The rule `" + rule + "` uses implements but actually matches extends."));
         }
-      } else if (extendsExpected && rulesThatUseExtendsOrImplementsWrong.add(rule)) {
-        assert options.testing.allowProguardRulesThatUseExtendsOrImplementsWrong;
-        options.reporter.warning(
-            new StringDiagnostic(
-                "The rule `" + rule + "` uses implements but actually matches extends."));
       }
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
index 4111bed..69d65ea 100644
--- a/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/ArchiveBuilder.java
@@ -17,6 +17,10 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardOpenOption;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipException;
 import java.util.zip.ZipOutputStream;
@@ -27,6 +31,9 @@
   private ZipOutputStream stream = null;
   private boolean closed = false;
   private int openCount = 0;
+  private int classesFileIndex = 0;
+  private Map<Integer, DelayedData> delayedClassesDexFiles = new HashMap<>();
+  private SortedSet<DelayedData> delayedWrites = new TreeSet<>();
 
   public ArchiveBuilder(Path archive) {
     this.archive = archive;
@@ -44,6 +51,7 @@
     assert !closed;
     openCount--;
     if (openCount == 0) {
+      writeDelayed(handler);
       closed = true;
       try {
         getStreamRaw().close();
@@ -54,6 +62,20 @@
     }
   }
 
+  private void writeDelayed(DiagnosticsHandler handler) {
+    // We should never have any indexed files at this point
+    assert delayedClassesDexFiles.isEmpty();
+    for (DelayedData data : delayedWrites) {
+      if (data.isDirectory) {
+        assert data.content == null;
+        writeDirectoryNow(data.name, handler);
+      } else {
+        assert data.content != null;
+        writeFileNow(data.name, data.content, handler);
+      }
+    }
+  }
+
   private ZipOutputStream getStreamRaw() throws IOException {
     if (stream != null) {
       return stream;
@@ -85,7 +107,11 @@
   }
 
   @Override
-  public void addDirectory(String name, DiagnosticsHandler handler) {
+  public synchronized void addDirectory(String name, DiagnosticsHandler handler) {
+    delayedWrites.add(DelayedData.createDirectory(name));
+  }
+
+  private void writeDirectoryNow(String name, DiagnosticsHandler handler) {
     if (name.charAt(name.length() - 1) != DataResource.SEPARATOR) {
       name += DataResource.SEPARATOR;
     }
@@ -105,7 +131,10 @@
   @Override
   public void addFile(String name, DataEntryResource content, DiagnosticsHandler handler) {
     try (InputStream in = content.getByteStream()) {
-      addFile(name, ByteDataView.of(ByteStreams.toByteArray(in)), handler);
+      ByteDataView view = ByteDataView.of(ByteStreams.toByteArray(in));
+      synchronized (this) {
+        delayedWrites.add(DelayedData.createFile(name, view));
+      }
     } catch (IOException e) {
       handleIOException(e, handler);
     } catch (ResourceException e) {
@@ -116,6 +145,10 @@
 
   @Override
   public synchronized void addFile(String name, ByteDataView content, DiagnosticsHandler handler) {
+    delayedWrites.add(DelayedData.createFile(name,  ByteDataView.of(content.copyByteData())));
+  }
+
+  private void writeFileNow(String name, ByteDataView content, DiagnosticsHandler handler) {
     try {
       ZipUtils.writeToZipStream(getStream(handler), name, content, ZipEntry.STORED);
     } catch (IOException e) {
@@ -123,6 +156,30 @@
     }
   }
 
+  private void writeNextIfAvailable(DiagnosticsHandler handler) {
+    DelayedData data = delayedClassesDexFiles.remove(classesFileIndex);
+    while (data != null) {
+      writeFileNow(data.name, data.content, handler);
+      classesFileIndex++;
+      data = delayedClassesDexFiles.remove(classesFileIndex);
+    }
+  }
+
+  @Override
+  public synchronized void addIndexedClassFile(
+      int index, String name, ByteDataView content, DiagnosticsHandler handler) {
+    if (index == classesFileIndex) {
+      // Fast case, we got the file in order (or we only had one).
+      writeFileNow(name, content, handler);
+      classesFileIndex++;
+      writeNextIfAvailable(handler);
+    } else {
+      // Data is released in the application writer, take a copy.
+      delayedClassesDexFiles.put(index,
+          new DelayedData(name, ByteDataView.of(content.copyByteData()), false));
+    }
+  }
+
   @Override
   public Origin getOrigin() {
     return origin;
@@ -132,4 +189,32 @@
   public Path getPath() {
     return archive;
   }
+
+  private static class DelayedData implements Comparable<DelayedData> {
+    public final String name;
+    public final ByteDataView content;
+    public final boolean isDirectory;
+
+    public static DelayedData createFile(String name, ByteDataView content) {
+      return new DelayedData(name, content, false);
+    }
+
+    public static DelayedData createDirectory(String name) {
+      return new DelayedData(name, null, true);
+    }
+
+    private DelayedData(String name, ByteDataView content, boolean isDirectory) {
+      this.name = name;
+      this.content = content;
+      this.isDirectory = isDirectory;
+    }
+
+    @Override
+    public int compareTo(DelayedData other) {
+      if (other == null) {
+        return name.compareTo(null);
+      }
+      return name.compareTo(other.name);
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
index 8e0b054..f78983f 100644
--- a/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/DirectoryBuilder.java
@@ -68,6 +68,12 @@
   }
 
   @Override
+  public void addIndexedClassFile(
+      int index, String name, ByteDataView content, DiagnosticsHandler handler) {
+    addFile(name, content, handler);
+  }
+
+  @Override
   public Origin getOrigin() {
     return origin;
   }
diff --git a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
index 9077f7b..be739e3 100644
--- a/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
+++ b/src/main/java/com/android/tools/r8/utils/OutputBuilder.java
@@ -23,6 +23,9 @@
 
   void addFile(String name, ByteDataView content, DiagnosticsHandler handler);
 
+  void addIndexedClassFile(
+      int index, String name, ByteDataView content, DiagnosticsHandler handler);
+
   Path getPath();
 
   Origin getOrigin();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index ee0e8af..4404bae 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -10,10 +10,12 @@
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 public class R8TestBuilder
     extends TestShrinkerBuilder<
@@ -64,6 +66,28 @@
     return self();
   }
 
+  public R8TestBuilder addMainDexRules(Collection<String> rules) {
+    builder.addMainDexRules(new ArrayList<>(rules), Origin.unknown());
+    return self();
+  }
+
+  public R8TestBuilder addMainDexRules(String... rules) {
+    return addMainDexRules(Arrays.asList(rules));
+  }
+
+  public R8TestBuilder addMainDexClassRules(Class<?>... classes) {
+    for (Class<?> clazz : classes) {
+      addMainDexRules("-keep class " + clazz.getTypeName());
+    }
+    return self();
+  }
+
+  public R8TestBuilder addMainDexListClasses(Class<?>... classes) {
+    builder.addMainDexClasses(
+        Arrays.stream(classes).map(Class::getTypeName).collect(Collectors.toList()));
+    return self();
+  }
+
   public R8TestBuilder enableInliningAnnotations() {
     if (!enableInliningAnnotations) {
       enableInliningAnnotations = true;
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index 3042cbe..4821c25 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -47,6 +47,7 @@
 import java.util.zip.ZipException;
 import java.util.zip.ZipFile;
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -581,7 +582,7 @@
 
   void execute(String testName,
       String qualifiedMainClass, Path[] jars, Path[] dexes, List<String> args) throws IOException {
-
+    Assume.assumeTrue(ToolHelper.artSupported() || ToolHelper.compareAgaintsGoldenFiles());
     boolean expectedToFail = expectedToFail(testName);
     try {
       String output = ToolHelper.runArtNoVerificationErrors(
@@ -592,7 +593,9 @@
               builder.appendProgramArgument(arg);
             }
           });
-      if (!skipRunningOnJvm(testName) && !ToolHelper.compareAgaintsGoldenFiles()) {
+      if (!expectedToFail
+          && !skipRunningOnJvm(testName)
+          && !ToolHelper.compareAgaintsGoldenFiles()) {
         ArrayList<String> javaArgs = Lists.newArrayList(args);
         javaArgs.add(0, qualifiedMainClass);
         ToolHelper.ProcessResult javaResult =
@@ -606,7 +609,7 @@
             output.replace("\r", "").equals(javaResult.stdout.replace("\r", "")));
       }
     } catch (Throwable t) {
-      assert expectedToFail && !ToolHelper.compareAgaintsGoldenFiles();
+      assert expectedToFail;
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 34ffcaa..c750ded 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -96,7 +96,30 @@
         return this;
       }
     }
-    fail("No warning matches " + matcher.toString());
+    StringBuilder builder = new StringBuilder("No warning matches " + matcher.toString());
+    builder.append(System.lineSeparator());
+    if (getDiagnosticMessages().getWarnings().size() == 0) {
+      builder.append("There where no warnings.");
+    } else {
+      builder.append("There where " + getDiagnosticMessages().getWarnings().size() + " warnings:");
+      builder.append(System.lineSeparator());
+      for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
+        builder.append(getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage());
+        builder.append(System.lineSeparator());
+      }
+    }
+    fail(builder.toString());
+    return this;
+  }
+
+  public TestCompileResult<RR> assertNoWarningMessageThatMatches(Matcher<String> matcher) {
+    assertNotEquals(0, getDiagnosticMessages().getWarnings().size());
+    for (int i = 0; i < getDiagnosticMessages().getWarnings().size(); i++) {
+      String message = getDiagnosticMessages().getWarnings().get(i).getDiagnosticMessage();
+      if (matcher.matches(message)) {
+        fail("The warning: \"" + message + "\" + matches " + matcher + ".");
+      }
+    }
     return this;
   }
 
diff --git a/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
new file mode 100644
index 0000000..b778c41
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/NonidenticalCatchHandlerTest.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2018, 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.cf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.code.CfLabel;
+import com.android.tools.r8.cf.code.CfTryCatch;
+import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexCode.Try;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import java.util.Set;
+import org.junit.Test;
+
+public class NonidenticalCatchHandlerTest extends TestBase {
+
+  private static class TestClass {
+    public void foo(Object a) {
+      int x = 0;
+      try {
+        x = ((String)a).length();
+      } catch (ClassCastException e) {
+        x = -1;
+      } catch (NullPointerException e) {
+        x = -1;
+      }
+      System.out.println(x);
+    }
+  }
+
+  @Test
+  public void test() throws Exception {
+    byte[] inputBytes = ToolHelper.getClassAsBytes(TestClass.class);
+    AndroidApp inputApp =
+        AndroidApp.builder()
+            .addClassProgramData(inputBytes, Origin.unknown())
+            .addLibraryFiles(ToolHelper.getJava8RuntimeJar())
+            .build();
+    assertEquals(2, countCatchHandlers(inputApp));
+    AndroidApp outputDexApp = ToolHelper.runR8(inputApp);
+    assertEquals(1, countCatchHandlers(outputDexApp));
+    AndroidApp outputCfApp =
+        ToolHelper.runR8WithProgramConsumer(inputApp, ClassFileConsumer.emptyConsumer());
+    // ClassCastException and NullPointerException will not have same type on stack.
+    assertEquals(2, countCatchHandlers(outputCfApp));
+  }
+
+  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();
+    if (code.isCfCode()) {
+      CfCode cfCode = code.asCfCode();
+      Set<CfLabel> targets = Sets.newIdentityHashSet();
+      for (CfTryCatch tryCatch : cfCode.getTryCatchRanges()) {
+        targets.addAll(tryCatch.targets);
+      }
+      return targets.size();
+    } else if (code.isDexCode()) {
+      DexCode dexCode = code.asDexCode();
+      IntSet targets = new IntOpenHashSet();
+      for (Try aTry : dexCode.tries) {
+        targets.add(aTry.handlerOffset);
+      }
+      return targets.size();
+    } else {
+      throw new Unimplemented(code.getClass().getName());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/configuration/WrongUseOfImplementsOrExtendsWarningTest.java b/src/test/java/com/android/tools/r8/configuration/WrongUseOfImplementsOrExtendsWarningTest.java
new file mode 100644
index 0000000..cc07ce3
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/configuration/WrongUseOfImplementsOrExtendsWarningTest.java
@@ -0,0 +1,105 @@
+// Copyright (c) 2018, 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.configuration;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+
+public class WrongUseOfImplementsOrExtendsWarningTest extends TestBase {
+
+  @Test
+  public void testCorrectUseOfExtends() throws Exception {
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(WrongUseOfImplementsOrExtendsWarningTest.class)
+            .addKeepRules(
+                "-keep class " + B.class.getTypeName() + " extends " + A.class.getTypeName())
+            .compile()
+            .assertNoMessages()
+            .inspector();
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void testWrongUseOfExtends() throws Exception {
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(WrongUseOfImplementsOrExtendsWarningTest.class)
+            .addKeepRules(
+                "-keep class " + B.class.getTypeName() + " extends " + I.class.getTypeName())
+            .compile()
+            .assertWarningMessageThatMatches(
+                containsString("uses extends but actually matches implements"))
+            .inspector();
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void testUseOfExtendsWithWildcards() throws Exception {
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(WrongUseOfImplementsOrExtendsWarningTest.class)
+            .addKeepRules(
+                "-keep class " + B.class.getTypeName() + " extends **$" + I.class.getSimpleName())
+            .compile()
+            .assertNoMessages()
+            .inspector();
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void testCorrectUseOfImplements() throws Exception {
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(WrongUseOfImplementsOrExtendsWarningTest.class)
+            .addKeepRules(
+                "-keep class " + B.class.getTypeName() + " implements " + I.class.getTypeName())
+            .compile()
+            .assertNoMessages()
+            .inspector();
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void testWrongUseOfImplements() throws Exception {
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(WrongUseOfImplementsOrExtendsWarningTest.class)
+            .addKeepRules(
+                "-keep class " + B.class.getTypeName() + " implements " + A.class.getTypeName())
+            .compile()
+            .assertWarningMessageThatMatches(
+                containsString("uses implements but actually matches extends"))
+            .inspector();
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  @Test
+  public void testUseOfImplementsWithWildcards() throws Exception {
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(WrongUseOfImplementsOrExtendsWarningTest.class)
+            .addKeepRules(
+                "-keep class "
+                    + B.class.getTypeName()
+                    + " implements **$"
+                    + A.class.getSimpleName())
+            .compile()
+            .assertNoMessages()
+            .inspector();
+    assertThat(inspector.clazz(B.class), isPresent());
+  }
+
+  static class A {}
+
+  static class B extends A implements I {}
+
+  interface I {}
+}
diff --git a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
index e26ec3d..be14412 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.ResourceException;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationWriter;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -43,13 +44,18 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
+import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ComparisonFailure;
 import org.junit.Rule;
@@ -89,6 +95,22 @@
     return result;
   }
 
+  public void assertIdenticalZipFiles(File file1, File file2) throws IOException {
+    try (ZipFile zipFile1 = new ZipFile(file1); ZipFile zipFile2 = new ZipFile(file2)) {
+      final Enumeration<? extends ZipEntry> entries1 = zipFile1.entries();
+      final Enumeration<? extends ZipEntry> entries2 = zipFile2.entries();
+
+      while (entries1.hasMoreElements()) {
+        Assert.assertTrue(entries2.hasMoreElements());
+        ZipEntry entry1 = entries1.nextElement();
+        ZipEntry entry2 = entries2.nextElement();
+        Assert.assertEquals(entry1.getName(), entry2.getName());
+        Assert.assertEquals(entry1.getCrc(), entry2.getCrc());
+        Assert.assertEquals(entry1.getSize(), entry2.getSize());
+      }
+    }
+  }
+
   public AndroidApp runAndCheckVerification(
       CompilerUnderTest compiler,
       CompilationMode mode,
@@ -97,6 +119,19 @@
       Consumer<InternalOptions> optionsConsumer,
       List<String> inputs)
       throws ExecutionException, IOException, CompilationFailedException {
+    return runAndCheckVerification(compiler, mode, referenceApk, pgConfs, optionsConsumer,
+        DexIndexedConsumer::emptyConsumer, inputs);
+  }
+
+  public AndroidApp runAndCheckVerification(
+      CompilerUnderTest compiler,
+      CompilationMode mode,
+      String referenceApk,
+      List<String> pgConfs,
+      Consumer<InternalOptions> optionsConsumer,
+      Supplier<DexIndexedConsumer> dexIndexedConsumerSupplier,
+      List<String> inputs)
+      throws ExecutionException, IOException, CompilationFailedException {
     assertTrue(referenceApk == null || new File(referenceApk).exists());
     AndroidAppConsumers outputApp;
     if (compiler == CompilerUnderTest.R8) {
@@ -107,7 +142,7 @@
             pgConfs.stream().map(Paths::get).collect(Collectors.toList()));
       }
       builder.setMode(mode);
-      builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+      builder.setProgramConsumer(dexIndexedConsumerSupplier.get());
       builder.setMinApiLevel(AndroidApiLevel.L.getLevel());
       ToolHelper.allowPartiallyImplementedProguardOptions(builder);
       ToolHelper.addProguardConfigurationConsumer(
@@ -131,6 +166,7 @@
     return checkVerification(outputApp.build(), referenceApk);
   }
 
+
   public AndroidApp checkVerification(AndroidApp outputApp, String referenceApk)
       throws IOException, ExecutionException {
     Path out = temp.getRoot().toPath().resolve("all.zip");
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
index 09ce657..634ff6f 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
@@ -13,6 +14,7 @@
 import java.util.Collections;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public class GMSCoreDeployJarVerificationTest extends GMSCoreCompilationTestBase {
 
@@ -38,4 +40,20 @@
         optionsConsumer,
         Collections.singletonList(base + DEPLOY_JAR));
   }
+
+  public AndroidApp buildFromDeployJar(
+      CompilerUnderTest compiler, CompilationMode mode, String base, boolean hasReference,
+      Consumer<InternalOptions> optionsConsumer, Supplier<DexIndexedConsumer> consumerSupplier)
+      throws ExecutionException, IOException, ProguardRuleParserException,
+      CompilationFailedException {
+    return runAndCheckVerification(
+        compiler,
+        mode,
+        hasReference ? base + REFERENCE_APK : null,
+        null,
+        optionsConsumer,
+        consumerSupplier,
+        Collections.singletonList(base + DEPLOY_JAR));
+  }
+
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
index d881185..5803355 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -7,8 +7,12 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.utils.AndroidApp;
+import java.io.File;
+import java.util.function.Supplier;
 import org.junit.Test;
 
 public class R8GMSCoreV10DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
@@ -19,6 +23,9 @@
   @Test
   public void buildFromDeployJar() throws Exception {
     // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
+    File tempFolder = temp.newFolder();
+    File app1Zip = new File(tempFolder, "app1.zip");
+    File app2Zip = new File(tempFolder, "app2.zip");
     AndroidApp app1 =
         buildFromDeployJar(
             CompilerUnderTest.R8,
@@ -27,7 +34,8 @@
             false,
             options ->
                 options.proguardMapConsumer =
-                    (proguardMap, handler) -> this.proguardMap1 = proguardMap);
+                    (proguardMap, handler) -> this.proguardMap1 = proguardMap,
+            ()-> new ArchiveConsumer(app1Zip.toPath(), true));
     AndroidApp app2 =
         buildFromDeployJar(
             CompilerUnderTest.R8,
@@ -36,10 +44,14 @@
             false,
             options ->
                 options.proguardMapConsumer =
-                    (proguardMap, handler) -> this.proguardMap2 = proguardMap);
+                    (proguardMap, handler) -> this.proguardMap2 = proguardMap,
+            ()-> new ArchiveConsumer(app2Zip.toPath(), true));
+
+
 
     // Verify that the result of the two compilations was the same.
     assertIdenticalApplications(app1, app2);
+    assertIdenticalZipFiles(app1Zip, app2Zip);
     assertEquals(proguardMap1, proguardMap2);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithSelfReferenceTest.java b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithSelfReferenceTest.java
new file mode 100644
index 0000000..06659a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/classinliner/BuilderWithSelfReferenceTest.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2018, 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.classinliner;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/** Regression test for b/120182628. */
+public class BuilderWithSelfReferenceTest extends TestBase {
+
+  @Test
+  @Ignore("b/120182628")
+  public void test() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(BuilderWithSelfReferenceTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .compile();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Builder().build();
+    }
+  }
+
+  static class Builder {
+
+    public Builder f = this;
+
+    public Object build() {
+      invoke(f);
+      return new Object();
+    }
+
+    @NeverInline
+    public static void invoke(Object o) {}
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 5ed3cc5..12d0a78 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -51,6 +51,11 @@
     }
 
     @Override
+    public boolean hasEqualTypesAtEntry(BasicBlock first, BasicBlock second) {
+      return false;
+    }
+
+    @Override
     public void addNewBlockToShareIdenticalSuffix(
         BasicBlock block, int suffixSize, List<BasicBlock> predsBeforeSplit) {
       // Intentionally empty, we don't need to track suffix sharing in this allocator.
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index ddb0e7d..e013b02 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -20,15 +20,21 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.PrintStream;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 import org.junit.Assert;
 import org.junit.Test;
 
@@ -301,6 +307,34 @@
         nonLambdaOffset++;
       }
     }
+    testZipfileOrder(out);
+  }
+
+  private void testZipfileOrder(Path out) throws IOException {
+    // Sanity check that the classes.dex files are in the correct order
+    ZipFile outZip = new ZipFile(out.toFile());
+    Enumeration<? extends ZipEntry> entries = outZip.entries();
+    int index = 0;
+    LinkedList<String> entryNames = new LinkedList<>();
+    // We expect classes*.dex files first, in order, then the rest of the files, in order.
+    while(entries.hasMoreElements()) {
+      ZipEntry entry = entries.nextElement();
+      if (!entry.getName().startsWith("classes") || !entry.getName().endsWith(".dex")) {
+        entryNames.add(entry.getName());
+        continue;
+      }
+      if (index == 0) {
+        Assert.assertEquals("classes.dex", entry.getName());
+      } else {
+        Assert.assertEquals("classes" + (index + 1) + ".dex", entry.getName());
+      }
+      index++;
+    }
+    // Everything else should be sorted according to name.
+    String[] entriesUnsorted = entryNames.toArray(new String[0]);
+    String[] entriesSorted = entryNames.toArray(new String[0]);
+    Arrays.sort(entriesSorted);
+    Assert.assertArrayEquals(entriesUnsorted, entriesSorted);
   }
 
   private boolean isLambda(String mainDexEntry) {
diff --git a/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
new file mode 100644
index 0000000..e6ca563
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/warnings/MainDexWarningsTest.java
@@ -0,0 +1,101 @@
+// Copyright (c) 2018, 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.maindexlist.warnings;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+public class MainDexWarningsTest extends TestBase {
+
+  private List<Class<?>> testClasses = ImmutableList.of(Main.class, Static.class, Static2.class);
+  private Class<?> mainClass = Main.class;
+
+  private void classStaticGone(CodeInspector inspector) {
+    assertThat(inspector.clazz(Static.class), CoreMatchers.not(isPresent()));
+    assertThat(inspector.clazz(Static2.class), CoreMatchers.not(isPresent()));
+  }
+
+  @Test
+  public void testNoWarningFromMainDexRules() throws Exception {
+    testForR8(Backend.DEX)
+        .setMinApi(AndroidApiLevel.K)
+        .enableInliningAnnotations()
+        .addProgramClasses(testClasses)
+        .addKeepMainRule(mainClass)
+        // Include main dex rule for class Static.
+        .addMainDexClassRules(Main.class, Static.class)
+        .compile()
+        .inspect(this::classStaticGone)
+        .assertNoMessages();
+  }
+
+  @Test
+  public void testWarningFromManualMainDexList() throws Exception {
+    testForR8(Backend.DEX)
+        .setMinApi(AndroidApiLevel.K)
+        .enableInliningAnnotations()
+        .addProgramClasses(testClasses)
+        .addKeepMainRule(mainClass)
+        // Include explicit main dex entry for class Static.
+        .addMainDexListClasses(Static.class)
+        .compile()
+        .inspect(this::classStaticGone)
+        .assertOnlyWarnings()
+        .assertWarningMessageThatMatches(containsString("Application does not contain"))
+        .assertWarningMessageThatMatches(containsString(Static.class.getTypeName()))
+        .assertWarningMessageThatMatches(containsString("as referenced in main-dex-list"));
+  }
+
+  @Test
+  public void testWarningFromManualMainDexListWithRuleAsWell() throws Exception {
+    testForR8(Backend.DEX)
+        .setMinApi(AndroidApiLevel.K)
+        .enableInliningAnnotations()
+        .addProgramClasses(testClasses)
+        .addKeepMainRule(mainClass)
+        // Include explicit main dex entry for class Static.
+        .addMainDexListClasses(Main.class, Static.class)
+        // Include main dex rule for class Static2.
+        .addMainDexClassRules(Static2.class)
+        .compile()
+        .inspect(this::classStaticGone)
+        .assertOnlyWarnings()
+        .assertWarningMessageThatMatches(containsString("Application does not contain"))
+        .assertWarningMessageThatMatches(containsString(Static.class.getTypeName()))
+        .assertWarningMessageThatMatches(containsString("as referenced in main-dex-list"))
+        .assertNoWarningMessageThatMatches(containsString(Static2.class.getTypeName()));
+  }
+}
+
+class Main {
+  public static void main(String[] args) {
+    System.out.println(Static.m());
+    System.out.println(Static2.m());
+  }
+}
+
+class Static {
+  @ForceInline
+  public static int m() {
+    return 1;
+  }
+}
+
+class Static2 {
+  @ForceInline
+  public static int m() {
+    return 1;
+  }
+}
diff --git a/tools/archive.py b/tools/archive.py
index ad58bca..302b0ff 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -79,12 +79,13 @@
   return GetVersionDestination('http://storage.googleapis.com/', '', is_master)
 
 def Main():
-  if not 'BUILDBOT_BUILDERNAME' in os.environ:
+  if not utils.is_bot():
     raise Exception('You are not a bot, don\'t archive builds')
 
   # Generate an r8-ed build without dependencies.
   # Note: build_r8lib does a gradle-clean, this must be the first command.
-  build_r8lib('r8', True, True, utils.R8LIB_KEEP_RULES, utils.R8_EXCLUDE_DEPS_JAR)
+  build_r8lib('r8', True, True, utils.R8LIB_KEEP_RULES,
+    utils.R8LIB_EXCLUDE_DEPS_JAR)
 
   # Create maven release which uses a build that exclude dependencies.
   create_maven_release.main(["--out", utils.LIBS])
@@ -94,10 +95,17 @@
   shutil.copyfile(utils.R8_JAR, utils.R8_FULL_EXCLUDE_DEPS_JAR)
 
   # Ensure all archived artifacts has been built before archiving.
-  # The target tasks postfixed by 'r8' depend on the actual target task so
+  # The target tasks postfixed by 'lib' depend on the actual target task so
   # building it invokes the original task first.
-  gradle.RunGradle(map((lambda t: t + 'r8'),
-    [utils.D8, utils.R8, utils.COMPATDX, utils.COMPATPROGUARD]))
+  gradle.RunGradle([
+    utils.R8,
+    utils.D8,
+    utils.COMPATDX,
+    utils.COMPATPROGUARD,
+    utils.R8LIB,
+    utils.COMPATDXLIB,
+    utils.COMPATPROGUARDLIB,
+  ])
   version = GetVersion()
   is_master = IsMaster(version)
   if is_master:
@@ -116,15 +124,20 @@
           'releaser=go/r8bot (' + os.environ.get('BUILDBOT_SLAVENAME') + ')\n')
       version_writer.write('version-file.version.code=1\n')
 
-    for file in [utils.D8_JAR, utils.D8R8_JAR,
-                 utils.R8_JAR, utils.R8R8_JAR,
-                 utils.R8_SRC_JAR,
-                 utils.R8_FULL_EXCLUDE_DEPS_JAR,
-                 utils.R8_EXCLUDE_DEPS_JAR,
-                 utils.COMPATDX_JAR, utils.COMPATDXR8_JAR,
-                 utils.COMPATPROGUARD_JAR, utils.COMPATPROGUARDR8_JAR,
-                 utils.MAVEN_ZIP,
-                 utils.GENERATED_LICENSE]:
+    for file in [
+      utils.D8_JAR,
+      utils.R8_JAR,
+      utils.R8LIB_JAR,
+      utils.R8_SRC_JAR,
+      utils.R8_FULL_EXCLUDE_DEPS_JAR,
+      utils.R8LIB_EXCLUDE_DEPS_JAR,
+      utils.COMPATDX_JAR,
+      utils.COMPATDXLIB_JAR,
+      utils.COMPATPROGUARD_JAR,
+      utils.COMPATPROGUARDLIB_JAR,
+      utils.MAVEN_ZIP,
+      utils.GENERATED_LICENSE,
+    ]:
       file_name = os.path.basename(file)
       tagged_jar = os.path.join(temp, file_name)
       shutil.copyfile(file, tagged_jar)
diff --git a/tools/test.py b/tools/test.py
index e4433b3..e451699 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -12,6 +12,8 @@
 import optparse
 import subprocess
 import sys
+import thread
+import time
 import utils
 import notify
 
@@ -25,6 +27,14 @@
     "4.4.4",
     "4.0.4"]
 
+# How often do we check for progress on the bots:
+# Should be long enough that a normal run would always have med progress
+# Should be short enough that we ensure that two calls are close enough
+# to happen before bot times out.
+# A false positiv, i.e., printing the stacks of non hanging processes
+# is not a problem, no harm done except some logging in the stdout.
+TIMEOUT_HANDLER_PERIOD = 60 * 18
+
 def ParseOptions():
   result = optparse.OptionParser()
   result.add_option('--no-internal', '--no_internal',
@@ -95,7 +105,7 @@
 
 def Main():
   (options, args) = ParseOptions()
-  if 'BUILDBOT_BUILDERNAME' in os.environ:
+  if utils.is_bot():
     gradle.RunGradle(['clean'])
 
   # Build R8lib with dependencies for bootstrapping tests before adding test sources
@@ -171,6 +181,12 @@
                                     '%s.tar.gz' % sha1)
       utils.unpack_archive('%s.tar.gz' % sha1)
 
+  if utils.is_bot() and not utils.IsWindows():
+    timestamp_file = os.path.join(utils.BUILD, 'last_test_time')
+    if os.path.exists(timestamp_file):
+      os.remove(timestamp_file)
+    gradle_args.append('-Pupdate_test_timestamp=' + timestamp_file)
+    thread.start_new_thread(timeout_handler, (timestamp_file,))
 
   # Now run tests on selected runtime(s).
   vms_to_test = [options.dex_vm] if options.dex_vm != "all" else ALL_ART_VMS
@@ -193,6 +209,39 @@
 
   return 0
 
+
+def print_jstacks():
+  processes = subprocess.check_output(['ps', 'aux'])
+  for l in processes.splitlines():
+    if 'java' in l and 'openjdk' in l:
+      # Example line:
+      # ricow    184313  2.6  0.0 36839068 31808 ?      Sl   09:53   0:00 /us..
+      columns = l.split()
+      pid = columns[1]
+      return_value = subprocess.call(['jstack', pid])
+      if return_value:
+        print('Could not jstack %s' % l)
+
+def get_time_from_file(timestamp_file):
+  if os.path.exists(timestamp_file):
+    timestamp = os.stat(timestamp_file).st_mtime
+    print('TIMEOUT HANDLER timestamp: %s' % (timestamp))
+    sys.stdout.flush()
+    return timestamp
+  else:
+    print('TIMEOUT HANDLER no timestamp file yet')
+    sys.stdout.flush()
+    return None
+
+def timeout_handler(timestamp_file):
+  last_timestamp = None
+  while True:
+    time.sleep(TIMEOUT_HANDLER_PERIOD)
+    new_timestamp = get_time_from_file(timestamp_file)
+    if last_timestamp and new_timestamp == last_timestamp:
+      print_jstacks()
+    last_timestamp = new_timestamp
+
 if __name__ == '__main__':
   return_code = Main()
   if return_code != 0:
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index 395a4b4..b19ecb0 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -15,7 +15,24 @@
 BUILD_ROOT = "http://storage.googleapis.com/r8-releases/raw/"
 MASTER_BUILD_ROOT = "%smaster/" % BUILD_ROOT
 
-JAR_TARGETS = [utils.D8, utils.R8, utils.COMPATDX, utils.COMPATPROGUARD]
+JAR_TARGETS = [
+  utils.D8,
+  utils.R8,
+  utils.COMPATDX,
+  utils.COMPATPROGUARD,
+  utils.R8LIB,
+  utils.COMPATDXLIB,
+  utils.COMPATPROGUARDLIB,
+]
+JAR_FILES = [
+  utils.D8_JAR,
+  utils.R8_JAR,
+  utils.COMPATDX_JAR,
+  utils.COMPATPROGUARD_JAR,
+  utils.R8LIB_JAR,
+  utils.COMPATDXLIB_JAR,
+  utils.COMPATPROGUARDLIB_JAR,
+]
 OTHER_TARGETS = ["LICENSE"]
 
 def parse_arguments():
@@ -28,6 +45,7 @@
   return parser.parse_args()
 
 def copy_targets(root, target_root, srcs, dests):
+  assert len(srcs) == len(dests)
   for i in range(len(srcs)):
     src = os.path.join(root, srcs[i])
     dest = os.path.join(target_root, 'prebuilts', 'r8', dests[i])
@@ -35,9 +53,16 @@
     copyfile(src, dest)
 
 def copy_jar_targets(root, target_root):
-  # With the '-r8' postfix we're using the R8-processed jars.
-  srcs = map((lambda t: t + '-r8.jar'), JAR_TARGETS)
-  dests = map((lambda t: t + '-master.jar'), JAR_TARGETS)
+  srcs = JAR_FILES
+  dests = [
+    'd8-master.jar',
+    'r8-master.jar',
+    'compatdx-master.jar',
+    'compatproguard-master.jar',
+    'r8lib-master.jar',
+    'compatdxlib-master.jar',
+    'compatproguardlib-master.jar',
+  ]
   copy_targets(root, target_root, srcs, dests)
 
 def copy_other_targets(root, target_root):
@@ -62,13 +87,12 @@
   args = parse_arguments()
   target_root = args.android_root[0]
   if args.commit_hash == None and args.version == None:
-    gradle.RunGradle(map((lambda t: t + 'r8'), JAR_TARGETS))
+    gradle.RunGradle(JAR_TARGETS)
     copy_jar_targets(utils.LIBS, target_root)
     copy_other_targets(utils.GENERATED_LICENSE_DIR, target_root)
   else:
     assert args.commit_hash == None or args.version == None
-    # With the '-r8' postfix we're using the R8-processed jars.
-    targets = map((lambda t: t + '-r8.jar'), JAR_TARGETS) + OTHER_TARGETS
+    targets = JAR_FILES + OTHER_TARGETS
     with utils.TempDir() as root:
       for target in targets:
         if args.commit_hash:
diff --git a/tools/utils.py b/tools/utils.py
index d2c4935..83a6478 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -30,21 +30,23 @@
 
 D8 = 'd8'
 R8 = 'r8'
+R8LIB = 'r8lib'
 R8_SRC = 'sourceJar'
 COMPATDX = 'compatdx'
+COMPATDXLIB = 'compatdxlib'
 COMPATPROGUARD = 'compatproguard'
+COMPATPROGUARDLIB = 'compatproguardlib'
 
 D8_JAR = os.path.join(LIBS, 'd8.jar')
-D8R8_JAR = os.path.join(LIBS, 'd8-r8.jar')
 R8_JAR = os.path.join(LIBS, 'r8.jar')
-R8R8_JAR = os.path.join(LIBS, 'r8-r8.jar')
+R8LIB_JAR = os.path.join(LIBS, 'r8lib.jar')
 R8_SRC_JAR = os.path.join(LIBS, 'r8-src.jar')
-R8_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-exclude-deps.jar')
+R8LIB_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8lib-exclude-deps.jar')
 R8_FULL_EXCLUDE_DEPS_JAR = os.path.join(LIBS, 'r8-full-exclude-deps.jar')
 COMPATDX_JAR = os.path.join(LIBS, 'compatdx.jar')
-COMPATDXR8_JAR = os.path.join(LIBS, 'compatdx-r8.jar')
+COMPATDXLIB_JAR = os.path.join(LIBS, 'compatdxlib.jar')
 COMPATPROGUARD_JAR = os.path.join(LIBS, 'compatproguard.jar')
-COMPATPROGUARDR8_JAR = os.path.join(LIBS, 'compatproguard-r8.jar')
+COMPATPROGUARDLIB_JAR = os.path.join(LIBS, 'compatproguardlib.jar')
 MAVEN_ZIP = os.path.join(LIBS, 'r8.zip')
 GENERATED_LICENSE = os.path.join(GENERATED_LICENSE_DIR, 'LICENSE')
 RT_JAR = os.path.join(REPO_ROOT, 'third_party/openjdk/openjdk-rt-1.8/rt.jar')
@@ -313,3 +315,6 @@
 
 def get_android_jar(api):
   return os.path.join(REPO_ROOT, ANDROID_JAR.format(api=api))
+
+def is_bot():
+  return 'BUILDBOT_BUILDERNAME' in os.environ