Merge "Add scripts to create a self-contained dir of logged D8 calls."
diff --git a/.gitignore b/.gitignore
index 8f75e04..2ece82e3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -50,6 +50,8 @@
 third_party/youtube/youtube.android_11.47.tar.gz
 third_party/youtube/youtube.android_12.10
 third_party/youtube/youtube.android_12.10.tar.gz
+third_party/youtube/youtube.android_12.17/
+third_party/youtube/youtube.android_12.17.tar.gz
 third_party/jctf
 third_party/jctf.tar.gz
 third_party/android_cts_baseline
diff --git a/CONTRIBUTING b/CONTRIBUTING.md
similarity index 100%
rename from CONTRIBUTING
rename to CONTRIBUTING.md
diff --git a/README b/README.md
similarity index 100%
rename from README
rename to README.md
diff --git a/build.gradle b/build.gradle
index 59192bd..695e898 100644
--- a/build.gradle
+++ b/build.gradle
@@ -29,6 +29,12 @@
         }
         output.resourcesDir = 'build/classes/debugTestResources'
     }
+    debugTestResourcesJava8 {
+        java {
+            srcDirs = ['src/test/debugTestResourcesJava8']
+        }
+        output.resourcesDir = 'build/classes/debugTestResourcesJava8'
+    }
     examples {
         java {
             srcDirs = ['src/test/examples']
@@ -75,6 +81,7 @@
 dependencies {
     compile 'net.sf.jopt-simple:jopt-simple:4.6'
     compile group: 'com.google.guava', name: 'guava', version: '19.0'
+    compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
     compile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
     compile group: 'org.ow2.asm', name: 'asm', version: '5.1'
     compile group: 'org.ow2.asm', name: 'asm-commons', version: '5.1'
@@ -146,6 +153,7 @@
         "gmscore/gmscore_v10.tar.gz",
         "youtube/youtube.android_11.47.tar.gz",
         "youtube/youtube.android_12.10.tar.gz",
+        "youtube/youtube.android_12.17.tar.gz",
         "proguardsettings.tar.gz",
     ],
 ]
@@ -163,6 +171,16 @@
     }
 }
 
+task downloadProguard {
+    cloudDependencies.each { entry ->
+        entry.value.each { entryFile ->
+            if (entryFile.contains("proguard")) {
+                dependsOn "download_deps_${entry.key}/${entryFile}"
+            }
+        }
+    }
+}
+
 task downloadDeps {
     cloudDependencies.each { entry ->
         entry.value.each { entryFile ->
@@ -297,18 +315,17 @@
 
 task createArtTests(type: Exec) {
     def outputDir = "build/generated/test/java/com/android/tools/r8/art"
-    def createArtTestsScript = "scripts/create-art-tests.sh"
+    def createArtTestsScript = "tools/create_art_tests.py"
     inputs.file "tests/art.tar.gz"
     inputs.file createArtTestsScript
     outputs.dir outputDir
     dependsOn downloadDeps
-    executable "bash"
-    args "${projectDir}/${createArtTestsScript}"
+    commandLine "python", createArtTestsScript
     workingDir = projectDir
 }
 
 task createJctfTests(type: Exec) {
-    def outputDir = "build/generated/test/java/com/android/tools/r8/art"
+    def outputDir = "build/generated/test/java/com/android/tools/r8/jctf"
     def script = "scripts/create-jctf-tests.sh"
     inputs.file script
     outputs.dir outputDir
@@ -354,7 +371,6 @@
 }
 
 task buildDebugTestResourcesJars {
-    dependsOn downloadDeps
     def resourcesDir = file("src/test/debugTestResources")
     def hostJar = "debug_test_resources.jar"
     task "compile_debugTestResources"(type: JavaCompile) {
@@ -371,11 +387,29 @@
         from "build/test/debugTestResources/classes"
         include "**/*.class"
     }
+    def java8ResourcesDir = file("src/test/debugTestResourcesJava8")
+    def java8HostJar = "debug_test_resources_java8.jar"
+    task "compile_debugTestResourcesJava8"(type: JavaCompile) {
+        source = fileTree(dir: java8ResourcesDir, include: '**/*.java')
+        destinationDir = file("build/test/debugTestResourcesJava8/classes")
+        classpath = sourceSets.main.compileClasspath
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+        options.compilerArgs += ["-g", "-Xlint:-options"]
+    }
+    task "jar_debugTestResourcesJava8"(type: Jar, dependsOn: "compile_debugTestResourcesJava8") {
+        archiveName = java8HostJar
+        destinationDir = file("build/test/")
+        from "build/test/debugTestResourcesJava8/classes"
+        include "**/*.class"
+    }
+    dependsOn downloadDeps
     dependsOn jar_debugTestResources
+    dependsOn jar_debugTestResourcesJava8
 }
 
 task buildExampleJars {
-    dependsOn downloadDeps
+    dependsOn downloadProguard
     def examplesDir = file("src/test/examples")
     def proguardScript = "third_party/proguard/proguard5.2.1/bin/proguard.sh"
     task "compile_examples"(type: JavaCompile) {
@@ -662,10 +696,10 @@
     }
     // TODO(tamaskenez) enable jctf on all_tests when consolidated
     if (!project.hasProperty('jctf') && !project.hasProperty('only_jctf')) {
-        exclude "com/android/tools/r8/art/jctf/**"
+        exclude "com/android/tools/r8/jctf/**"
     }
     if (project.hasProperty('only_jctf')) {
-        include "com/android/tools/r8/art/jctf/**"
+        include "com/android/tools/r8/jctf/**"
     }
     if (project.hasProperty('jctf_compile_only')) {
         println "JCTF: compiling only"
diff --git a/scripts/create-art-tests.sh b/scripts/create-art-tests.sh
deleted file mode 100755
index dc6d6d2..0000000
--- a/scripts/create-art-tests.sh
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/bin/bash
-#
-# Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
-function generate_test() {
-  local name=$1
-  local testClassName=$2
-  local testGeneratingToolchain=$3
-  # The bash uppercase substitution ^^ is not supported on the bash version on Mac OS.
-  local testGeneratingToolchainEnum=$(echo $testGeneratingToolchain | tr /a-z/ /A-Z/)
-  local fileName=$4
-  local compilerUnderTest=$5
-  local compilerUnderTestEnum=$(echo ${compilerUnderTest} | tr /a-z/ /A-Z/)
-
-  cat <<EOF > $fileName
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.art.${testGeneratingToolchain}.${compilerUnderTest};
-
-import static com.android.tools.r8.R8RunArtTestsTest.DexTool.${testGeneratingToolchainEnum};
-
-import com.android.tools.r8.R8RunArtTestsTest;
-import org.junit.Test;
-
-/**
- * Auto-generated test for the art ${name} test using the ${testGeneratingToolchain} toolchain.
- *
- * DO NOT EDIT THIS FILE. EDIT THE HERE DOCUMENT TEMPLATE IN scripts/create-art-tests.sh INSTEAD!
- */
-public class ${testClassName} extends R8RunArtTestsTest {
-
-    public ${testClassName}() {
-      super("${name}", ${testGeneratingToolchainEnum});
-    }
-
-    @Test
-    public void run${testClassName}() throws Throwable {
-      // For testing with other Art VMs than the default pass the VM version as a argument to
-      // runArtTest, e.g. runArtTest(ToolHelper.ART_4_4_4).
-      runArtTest(CompilerUnderTest.${compilerUnderTestEnum});
-    }
-}
-EOF
-}
-
-TESTDIR="tests/art"
-TOOLCHAINS=("dx" "jack" "none")
-DESTINATIONDIR="build/generated/test/java/com/android/tools/r8/art"
-
-if [ ! -e $TESTDIR ]; then
-  echo "Missing art tests in $TESTDIR."
-  exit
-fi
-
-for TOOLCHAIN in ${TOOLCHAINS[@]}; do
-  for d in $DESTINATIONDIR/$TOOLCHAIN/r8 $DESTINATIONDIR/$TOOLCHAIN/d8; do
-    rm -rf $d
-    mkdir -p $d
-  done
-  # class files are found in the dx directory.
-  if [ "$TOOLCHAIN" == "none" ]; then
-    SOURCEDIR=${TESTDIR}/dx
-  else
-    SOURCEDIR=${TESTDIR}/${TOOLCHAIN}
-  fi
-  for TEST in ${SOURCEDIR}/*; do
-    TESTNAME=$(basename $TEST)
-    TESTCLASSNAME="Art${TESTNAME//-/_}Test"
-    generate_test $TESTNAME $TESTCLASSNAME ${TOOLCHAIN} $DESTINATIONDIR/$TOOLCHAIN/r8/$TESTCLASSNAME.java r8
-    generate_test $TESTNAME $TESTCLASSNAME ${TOOLCHAIN} $DESTINATIONDIR/$TOOLCHAIN/d8/$TESTCLASSNAME.java d8
-  done
-done
diff --git a/scripts/create-jctf-tests.sh b/scripts/create-jctf-tests.sh
index 538f3c3..2f4e91e 100755
--- a/scripts/create-jctf-tests.sh
+++ b/scripts/create-jctf-tests.sh
@@ -26,7 +26,7 @@
 // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.art.jctf.${compilerUnderTest}.${relativePackage};
+package com.android.tools.r8.jctf.${compilerUnderTest}.${relativePackage};
 
 import org.junit.Test;
 import com.android.tools.r8.R8RunArtTestsTest;
@@ -57,14 +57,14 @@
 }
 
 JCTFROOT="third_party/jctf"
-DESTINATIONDIR="build/generated/test/java/com/android/tools/r8/art"
+DESTINATIONDIR="build/generated/test/java/com/android/tools/r8/jctf"
 
 if [ ! -e $JCTFROOT ]; then
   echo "Missing jctf tests in $JCTFROOT."
   exit
 fi
 
-for d in $DESTINATIONDIR/jctf/r8 $DESTINATIONDIR/jctf/d8; do
+for d in $DESTINATIONDIR/r8 $DESTINATIONDIR/d8; do
   rm -rf $d
   mkdir -p $d
 done
@@ -86,10 +86,10 @@
   RELATIVE_PACKAGE=$(expr "$PACKAGE" : ".*\.java\.\(.*$\)")
 
   generate_test $PACKAGE.$TESTNAME $TESTCLASSNAME \
-    "$DESTINATIONDIR/jctf/r8/$RELATIVE_TEST_PATH" r8 \
+    "$DESTINATIONDIR/r8/$RELATIVE_TEST_PATH" r8 \
     $CLASSFILE $RELATIVE_PACKAGE
   generate_test $PACKAGE.$TESTNAME $TESTCLASSNAME \
-    "$DESTINATIONDIR/jctf/d8/$RELATIVE_TEST_PATH" d8 \
+    "$DESTINATIONDIR/d8/$RELATIVE_TEST_PATH" d8 \
     $CLASSFILE $RELATIVE_PACKAGE
 done
 
diff --git a/src/main/java/com/android/tools/r8/IncrementalDexingBenchmark.java b/src/main/java/com/android/tools/r8/IncrementalDexingBenchmark.java
index 7b9d8aa..e509bf1 100644
--- a/src/main/java/com/android/tools/r8/IncrementalDexingBenchmark.java
+++ b/src/main/java/com/android/tools/r8/IncrementalDexingBenchmark.java
@@ -33,7 +33,7 @@
         compile(executor);
       }
       double elapsedMs = (System.nanoTime() - start) / 1000000.0;
-      System.out.println("Runtime: " + elapsedMs + " ms");
+      System.out.println("IncrementalDexing(Runtime): " + elapsedMs + " ms");
     } finally {
       executor.shutdown();
     }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 44a687c..cd92f2d 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.shaking.ReasonPrinter;
 import com.android.tools.r8.shaking.RootSetBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder.RootSet;
+import com.android.tools.r8.shaking.SimpleClassMerger;
 import com.android.tools.r8.shaking.TreePruner;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.CfgPrinter;
@@ -245,8 +246,16 @@
 
       if (appInfo.withLiveness() != null) {
         // No-op until class merger is added.
-        appInfo = appInfo.withLiveness().rewrittenWithLense(graphLense);
         graphLense = new MemberRebindingAnalysis(appInfo.withLiveness(), graphLense).run();
+        // Class merging requires inlining.
+        if (!options.skipClassMerging && options.inlineAccessors) {
+          timing.begin("ClassMerger");
+          graphLense = new SimpleClassMerger(application, appInfo.withLiveness(), graphLense,
+              timing).run();
+          timing.end();
+        }
+        appInfo = appInfo.withLiveness().prunedCopyFrom(application);
+        appInfo = appInfo.withLiveness().rewrittenWithLense(graphLense);
       }
 
       graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withSubtyping()).run();
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 1e6751c..e820cb3 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.compatdx;
 
 import static com.android.tools.r8.utils.FileUtils.isApkFile;
+import static com.android.tools.r8.utils.FileUtils.isArchive;
 import static com.android.tools.r8.utils.FileUtils.isClassFile;
 import static com.android.tools.r8.utils.FileUtils.isDexFile;
 import static com.android.tools.r8.utils.FileUtils.isJarFile;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.D8Output;
+import com.android.tools.r8.Resource;
 import com.android.tools.r8.compatdx.CompatDx.DxCompatOptions.DxUsageMessage;
 import com.android.tools.r8.compatdx.CompatDx.DxCompatOptions.PositionInfo;
 import com.android.tools.r8.dex.Constants;
@@ -23,6 +25,7 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.File;
 import java.io.IOException;
@@ -35,6 +38,10 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
 import joptsimple.OptionParser;
 import joptsimple.OptionSet;
 import joptsimple.OptionSpec;
@@ -224,8 +231,10 @@
             .accepts("main-dex-list", "File listing classes that must be in the main dex file")
             .withRequiredArg()
             .describedAs(FILE_ARG);
-        multiDex = parser.accepts("multi-dex", "Allow generation of multi-dex")
-            .requiredIf(noStrict, minimalMainDex, mainDexList, maxIndexNumber);
+        multiDex =
+            parser
+                .accepts("multi-dex", "Allow generation of multi-dex")
+                .requiredIf(minimalMainDex, mainDexList, maxIndexNumber);
         minApiLevel = parser
             .accepts("min-sdk-version", "Minimum Android API level compatibility.")
             .withRequiredArg().ofType(Integer.class);
@@ -254,8 +263,8 @@
             positions = PositionInfo.LINES;
             break;
           default:
-            // Should be checked by parser.
-            throw new DxParseError(spec.parser);
+            positions = PositionInfo.IMPORTANT;
+            break;
         }
       } else {
         positions = PositionInfo.LINES;
@@ -274,7 +283,7 @@
       verboseDump = options.has(spec.verboseDump);
       noFiles = options.has(spec.noFiles);
       coreLibrary = options.has(spec.coreLibrary);
-      numThreads = options.valueOf(spec.numThreads);
+      numThreads = lastIntOf(options.valuesOf(spec.numThreads));
       incremental = options.has(spec.incremental);
       forceJumbo = options.has(spec.forceJumbo);
       noWarning = options.has(spec.noWarning);
@@ -293,6 +302,11 @@
       Spec spec = new Spec();
       return new DxCompatOptions(spec.parser.parse(args), spec);
     }
+
+    private static int lastIntOf(List<Integer> values) {
+      assert !values.isEmpty();
+      return values.get(values.size() - 1);
+    }
   }
 
   public static void main(String[] args) throws IOException {
@@ -309,6 +323,7 @@
   }
 
   private static void run(String[] args) throws DxUsageMessage, IOException, CompilationException {
+    System.out.println("CompatDx " + String.join(" ", args));
     DxCompatOptions dexArgs = DxCompatOptions.parse(args);
     if (dexArgs.help) {
       printHelpOn(System.out);
@@ -360,10 +375,6 @@
       throw new Unimplemented("dump-to file not yet supported");
     }
 
-    if (dexArgs.keepClasses) {
-      throw new Unimplemented("keeping classes in jar not yet supported");
-    }
-
     if (dexArgs.positions != PositionInfo.NONE) {
       mode = CompilationMode.DEBUG;
     }
@@ -377,11 +388,15 @@
     }
 
     if (dexArgs.forceJumbo) {
-      throw new Unimplemented("force jumbo not yet supported");
+      System.out.println(
+          "Warning: no support for forcing jumbo-strings.\n"
+              + "Strings will only use jumbo-string indexing if necessary.\n"
+              + "Make sure that any dex merger subsequently used "
+              + "supports correct handling of jumbo-strings (eg, D8/R8 does).");
     }
 
     if (dexArgs.noOptimize) {
-      System.out.println("Warning: no support for not optimizing yet");
+      System.out.println("Warning: no support for not optimizing");
     }
 
     if (dexArgs.optimizeList != null) {
@@ -406,6 +421,8 @@
 
     if (dexArgs.noStrict) {
       System.out.println("Warning: conservative main-dex list not yet supported");
+    } else {
+      System.out.println("Warning: strict name checking not yet supported");
     }
 
     if (dexArgs.minimalMainDex) {
@@ -452,7 +469,15 @@
       }
     }
 
-    result.write(output);
+    if (dexArgs.keepClasses) {
+      if (!isArchive(output)) {
+        throw new DxCompatOptions.DxUsageMessage(
+            "Output must be an archive when --keep-classes is set.");
+      }
+      writeZipWithClasses(inputs, result, output);
+    } else {
+      result.write(output);
+    }
   }
 
   static void printHelpOn(PrintStream sink) throws IOException {
@@ -484,4 +509,47 @@
       processPath(file, files);
     }
   }
+
+  private static void writeZipWithClasses(List<Path> inputs, D8Output output, Path path)
+      throws IOException {
+    try (Closer closer = Closer.create()) {
+      try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(path))) {
+        // For each input archive file, add all class files within.
+        for (Path input : inputs) {
+          if (isArchive(input)) {
+            try (ZipInputStream in = new ZipInputStream(Files.newInputStream(input))) {
+              ZipEntry entry;
+              while ((entry = in.getNextEntry()) != null) {
+                if (isClassFile(Paths.get(entry.getName()))) {
+                  addEntry(entry.getName(), in, out);
+                }
+              }
+            } catch (ZipException e) {
+              throw new CompilationError(
+                  "Zip error while reading '" + input + "': " + e.getMessage(), e);
+            }
+          }
+        }
+        // Add dex files.
+        List<Resource> dexProgramSources = output.getDexResources();
+        for (int i = 0; i < dexProgramSources.size(); i++) {
+          addEntry(getDexFileName(i), dexProgramSources.get(i).getStream(closer), out);
+        }
+      }
+    }
+  }
+
+  private static void addEntry(String name, InputStream in, ZipOutputStream out)
+      throws IOException {
+    ZipEntry zipEntry = new ZipEntry(name);
+    byte[] bytes = ByteStreams.toByteArray(in);
+    zipEntry.setSize(bytes.length);
+    out.putNextEntry(zipEntry);
+    out.write(bytes);
+    out.closeEntry();
+  }
+
+  private static String getDexFileName(int index) {
+    return index == 0 ? "classes.dex" : "classes" + (index + 1) + ".dex";
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/dex/DexFileReader.java b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
index a38d397..627d106 100644
--- a/src/main/java/com/android/tools/r8/dex/DexFileReader.java
+++ b/src/main/java/com/android/tools/r8/dex/DexFileReader.java
@@ -50,8 +50,8 @@
 import com.android.tools.r8.graph.DexValue.DexValueString;
 import com.android.tools.r8.graph.OffsetToObjectMapping;
 import com.android.tools.r8.logging.Log;
-import com.android.tools.r8.utils.IntHashMap;
-import com.android.tools.r8.utils.InternalResource;
+import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
+import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.nio.ShortBuffer;
@@ -91,10 +91,10 @@
   private OffsetToObjectMapping indexedItems = new OffsetToObjectMapping();
 
   // Mapping from offset to code item;
-  private IntHashMap<DexCode> codes = new IntHashMap<>();
+  private Int2ObjectMap<DexCode> codes = new Int2ObjectOpenHashMap<>();
 
   // Mapping from offset to dex item;
-  private IntHashMap<Object> offsetMap = new IntHashMap<>();
+  private Int2ObjectMap<Object> offsetMap = new Int2ObjectOpenHashMap<>();
 
   // Factory to canonicalize certain dexitems.
   private final DexItemFactory dexItemFactory;
@@ -140,7 +140,9 @@
   }
 
   private DexTypeList typeListAt(int offset) {
-    if (offset == 0) return DexTypeList.empty();
+    if (offset == 0) {
+      return DexTypeList.empty();
+    }
     return (DexTypeList) cacheAt(offset, this::parseTypeList);
   }
 
@@ -358,9 +360,13 @@
   }
 
   private <T> Object cacheAt(int offset, Supplier<T> function) {
-    if (offset == 0) return null;  // return null for offset zero.
+    if (offset == 0) {
+      return null;  // return null for offset zero.
+    }
     Object result = offsetMap.get(offset);
-    if (result != null) return result;  // return the cached result.
+    if (result != null) {
+      return result;  // return the cached result.
+    }
     // Cache is empty so parse the structure.
     file.position(offset);
     result = function.get();
@@ -812,7 +818,7 @@
   private static void populateMethodHandles(DexFileReader reader) {
     Segment segment = reader.lookupSegment(Constants.TYPE_METHOD_HANDLE_ITEM);
     reader.indexedItems.initializeMethodHandles(segment.size);
-    for (int i = 0; i < segment.size; i++){
+    for (int i = 0; i < segment.size; i++) {
       reader.indexedItems.setMethodHandle(i, reader.methodHandleAt(i));
     }
   }
@@ -820,7 +826,7 @@
   private static void populateCallSites(DexFileReader reader) {
     Segment segment = reader.lookupSegment(Constants.TYPE_CALL_SITE_ID_ITEM);
     reader.indexedItems.initializeCallSites(segment.size);
-    for (int i = 0; i < segment.size; i++){
+    for (int i = 0; i < segment.size; i++) {
       reader.indexedItems.setCallSites(i, reader.callSiteAt(i));
     }
   }
@@ -828,7 +834,7 @@
   private static void populateTypes(DexFileReader reader) {
     Segment segment = reader.lookupSegment(Constants.TYPE_TYPE_ID_ITEM);
     reader.indexedItems.initializeTypes(segment.size);
-    for (int i = 0; i < segment.size; i++){
+    for (int i = 0; i < segment.size; i++) {
       reader.indexedItems.setType(i, reader.typeAt(i));
     }
   }
@@ -836,7 +842,7 @@
   private static void populateFields(DexFileReader reader) {
     Segment segment = reader.lookupSegment(Constants.TYPE_FIELD_ID_ITEM);
     reader.indexedItems.initializeFields(segment.size);
-    for (int i = 0; i < segment.size; i++){
+    for (int i = 0; i < segment.size; i++) {
       reader.indexedItems.setField(i, reader.fieldAt(i));
     }
   }
@@ -844,7 +850,7 @@
   private static void populateProtos(DexFileReader reader) {
     Segment segment = reader.lookupSegment(Constants.TYPE_PROTO_ID_ITEM);
     reader.indexedItems.initializeProtos(segment.size);
-    for (int i = 0; i < segment.size; i++){
+    for (int i = 0; i < segment.size; i++) {
       reader.indexedItems.setProto(i, reader.protoAt(i));
     }
   }
@@ -852,7 +858,7 @@
   private static void populateMethods(DexFileReader reader) {
     Segment segment = reader.lookupSegment(Constants.TYPE_METHOD_ID_ITEM);
     reader.indexedItems.initializeMethods(segment.size);
-    for (int i = 0; i < segment.size; i++){
+    for (int i = 0; i < segment.size; i++) {
       reader.indexedItems.setMethod(i, reader.methodAt(i));
     }
   }
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index f5f22e7..63185d1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -334,4 +334,17 @@
   public AppInfoWithLiveness withLiveness() {
     return null;
   }
+
+  public List<DexClass> getSuperTypeClasses(DexType type) {
+    List<DexClass> result = new ArrayList<>();
+    do {
+      DexClass clazz = definitionFor(type);
+      if (clazz == null) {
+        break;
+      }
+      result.add(clazz);
+      type = clazz.superType;
+    } while (type != null);
+    return result;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
index e2e972c..3c3c2b9 100644
--- a/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/graph/ClassAndMemberPublicizer.java
@@ -7,7 +7,7 @@
 
   private static void publicizeAllMethods(DexEncodedMethod[] methods) {
     for (DexEncodedMethod method : methods) {
-      method.accessFlags.promoteToPublic();
+      method.accessFlags.promoteNonPrivateToPublic();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexAccessFlags.java b/src/main/java/com/android/tools/r8/graph/DexAccessFlags.java
index e145158..a8f9526 100644
--- a/src/main/java/com/android/tools/r8/graph/DexAccessFlags.java
+++ b/src/main/java/com/android/tools/r8/graph/DexAccessFlags.java
@@ -79,6 +79,10 @@
     set(Constants.ACC_PUBLIC);
   }
 
+  public void unsetPublic() {
+    unset(Constants.ACC_PUBLIC);
+  }
+
   public boolean isPrivate() {
     return isSet(Constants.ACC_PRIVATE);
   }
@@ -99,6 +103,10 @@
     set(Constants.ACC_PROTECTED);
   }
 
+  public void unsetProtected() {
+    unset(Constants.ACC_PROTECTED);
+  }
+
   public boolean isStatic() {
     return isSet(Constants.ACC_STATIC);
   }
@@ -115,6 +123,10 @@
     set(Constants.ACC_FINAL);
   }
 
+  public void unsetFinal() {
+    unset(Constants.ACC_FINAL);
+  }
+
   public boolean isSynchronized() {
     return isSet(Constants.ACC_SYNCHRONIZED);
   }
@@ -239,6 +251,10 @@
     set(Constants.ACC_CONSTRUCTOR);
   }
 
+  public void unsetConstructor() {
+    unset(Constants.ACC_CONSTRUCTOR);
+  }
+
   public boolean isDeclaredSynchronized() {
     return isSet(Constants.ACC_DECLARED_SYNCHRONIZED);
   }
@@ -247,13 +263,18 @@
     set(Constants.ACC_DECLARED_SYNCHRONIZED);
   }
 
-  public void promoteToPublic() {
+  public void promoteNonPrivateToPublic() {
     if (!isPrivate()) {
       flags &= ~Constants.ACC_PROTECTED;
       flags |= Constants.ACC_PUBLIC;
     }
   }
 
+  public void promoteToPublic() {
+    flags &= ~Constants.ACC_PROTECTED & ~Constants.ACC_PRIVATE;
+    flags |= Constants.ACC_PUBLIC;
+  }
+
   private boolean isSet(int flag) {
     return (flags & flag) != 0;
   }
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 232019c..90828b7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -11,7 +11,9 @@
 import com.google.common.base.MoreObjects;
 
 public abstract class DexClass extends DexItem implements DexClassPromise {
+
   public interface Factory {
+
     DexClass create(DexType type, Origin origin, DexAccessFlags accessFlags, DexType superType,
         DexTypeList interfaces, DexString sourceFile, DexAnnotationSet annotations,
         DexEncodedField[] staticFields, DexEncodedField[] instanceFields,
@@ -28,8 +30,8 @@
   public final Origin origin;
   public final DexType type;
   public final DexAccessFlags accessFlags;
-  public final DexType superType;
-  public final DexTypeList interfaces;
+  public DexType superType;
+  public DexTypeList interfaces;
   public final DexString sourceFile;
   public DexEncodedField[] staticFields;
   public DexEncodedField[] instanceFields;
@@ -156,9 +158,9 @@
     return null;
   }
 
-  public DexEncodedMethod getClassInitializer(DexItemFactory factory) {
+  public DexEncodedMethod getClassInitializer() {
     for (DexEncodedMethod method : directMethods()) {
-      if (factory.isClassConstructor(method.method)) {
+      if (method.accessFlags.isConstructor() && method.accessFlags.isStatic()) {
         return method;
       }
     }
@@ -192,4 +194,8 @@
     }
     throw new Unreachable();
   }
+
+  public boolean hasClassInitializer() {
+    return getClassInitializer() != null;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 06add73..c94fc3c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -71,9 +71,20 @@
     if (accessFlags.isStatic() && accessFlags.isPublic() && accessFlags.isFinal()) {
       DexClass clazz = appInfo.definitionFor(field.getHolder());
       assert clazz != null : "Class for the field must be present";
-      return staticValue.asConstInstruction(
-          clazz.getClassInitializer(appInfo.dexItemFactory) != null, dest);
+      return staticValue.asConstInstruction(clazz.hasClassInitializer(), dest);
     }
     return null;
   }
+
+  public DexEncodedField toRenamedField(DexString name, DexItemFactory dexItemFactory) {
+    return new DexEncodedField(dexItemFactory.createField(field.clazz, field.type, name),
+        accessFlags, annotations, staticValue);
+  }
+
+  public DexEncodedField toTypeSubstitutedField(DexField field) {
+    if (this.field == field) {
+      return this;
+    }
+    return new DexEncodedField(field, accessFlags, annotations, staticValue);
+  }
 }
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 9244be5..5ea8edd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -26,7 +26,9 @@
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
 
-  public enum CompilationState {
+  public enum CompilationState
+
+  {
     NOT_PROCESSED,
     PROCESSED_NOT_INLINING_CANDIDATE,
     // Code only contains instructions that access public entities.
@@ -56,6 +58,7 @@
     this.annotations = annotations;
     this.parameterAnnotations = parameterAnnotations;
     this.code = code;
+    assert code == null || !accessFlags.isAbstract();
   }
 
   public boolean isProcessed() {
@@ -67,10 +70,10 @@
       // This will probably never happen but never inline a class initializer.
       return false;
     }
-    if (alwaysInline && (compilationState != CompilationState.NOT_PROCESSED)) {
+    if (alwaysInline) {
       // Only inline constructor iff holder classes are equal.
       if (!accessFlags.isStatic() && accessFlags.isConstructor()) {
-         return container.method.getHolder() == method.getHolder();
+        return container.method.getHolder() == method.getHolder();
       }
       return true;
     }
@@ -78,7 +81,7 @@
       case PROCESSED_INLINING_CANDIDATE_PUBLIC:
         return true;
       case PROCESSED_INLINING_CANDIDATE_PACKAGE_PRIVATE:
-         return container.method.getHolder().isSamePackage(method.getHolder());
+        return container.method.getHolder().isSamePackage(method.getHolder());
       // TODO(bak): Expand check for package private access:
       case PROCESSED_INLINING_CANDIDATE_PRIVATE:
         return container.method.getHolder() == method.getHolder();
@@ -232,12 +235,15 @@
 
   public DexEncodedMethod toEmptyThrowingMethod() {
     Instruction insn[] = {new Const(0, 0), new Throw(0)};
-    code = generateCodeFromTemplate(1, 0, insn);
-    return this;
+    DexCode code = generateCodeFromTemplate(1, 0, insn);
+    assert !accessFlags.isAbstract();
+    Builder builder = builder(this);
+    builder.setCode(code);
+    return builder.build();
   }
 
   public DexEncodedMethod toMethodThatLogsError(DexItemFactory itemFactory) {
-    Signature signature = MethodSignature.fromDexMethod(this.method);
+    Signature signature = MethodSignature.fromDexMethod(method);
     // TODO(herhut): Construct this out of parts to enable reuse, maybe even using descriptors.
     DexString message = itemFactory.createString(
         "Shaking error: Missing method in " + method.holder.toSourceString() + ": "
@@ -263,8 +269,29 @@
         new InvokeStatic(2, initMethod, 0, 1, 0, 0, 0),
         new Throw(0)
     };
-    code = generateCodeFromTemplate(2, 2, insn);
-    return this;
+    DexCode code = generateCodeFromTemplate(2, 2, insn);
+    Builder builder = builder(this);
+    builder.setCode(code);
+    return builder.build();
+  }
+
+  public DexEncodedMethod toTypeSubstitutedMethod(DexMethod method) {
+    if (this.method == method) {
+      return this;
+    }
+    Builder builder = builder(this);
+    builder.setMethod(method);
+    return builder.build();
+  }
+
+  public DexEncodedMethod toRenamedMethod(DexString name, DexItemFactory factory) {
+    if (method.name == name) {
+      return this;
+    }
+    DexMethod newMethod = factory.createMethod(method.holder, method.proto, name);
+    Builder builder = builder(this);
+    builder.setMethod(newMethod);
+    return builder.build();
   }
 
   public String codeToString() {
@@ -290,6 +317,7 @@
   }
 
   public static class OptimizationInfo {
+
     private int returnedArgument = -1;
     private boolean neverReturnsNull = false;
     private boolean returnsConstant = false;
@@ -300,6 +328,14 @@
       // Intentionally left empty.
     }
 
+    private OptimizationInfo(OptimizationInfo template) {
+      returnedArgument = template.returnedArgument;
+      neverReturnsNull = template.neverReturnsNull;
+      returnsConstant = template.returnsConstant;
+      returnedConstant = template.returnedConstant;
+      forceInline = template.forceInline;
+    }
+
     public boolean returnsArgument() {
       return returnedArgument != -1;
     }
@@ -345,12 +381,23 @@
     private void markForceInline() {
       forceInline = true;
     }
+
+    public OptimizationInfo copy() {
+      return new OptimizationInfo(this);
+    }
   }
 
   private static class DefaultOptimizationInfo extends OptimizationInfo {
-    public static final OptimizationInfo DEFAULT = new DefaultOptimizationInfo();
 
-    private DefaultOptimizationInfo() {}
+    static final OptimizationInfo DEFAULT = new DefaultOptimizationInfo();
+
+    private DefaultOptimizationInfo() {
+    }
+
+    @Override
+    public OptimizationInfo copy() {
+      return this;
+    }
   }
 
   synchronized private OptimizationInfo ensureMutableOI() {
@@ -379,4 +426,62 @@
   public OptimizationInfo getOptimizationInfo() {
     return optimizationInfo;
   }
+
+  private static Builder builder(DexEncodedMethod from) {
+    return new Builder(from);
+  }
+
+  private static class Builder {
+
+    private DexMethod method;
+    private DexAccessFlags accessFlags;
+    private DexAnnotationSet annotations;
+    private DexAnnotationSetRefList parameterAnnotations;
+    private Code code;
+    private CompilationState compilationState = CompilationState.NOT_PROCESSED;
+    private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
+
+    private Builder(DexEncodedMethod from) {
+      // Copy all the mutable state of a DexEncodedMethod here.
+      method = from.method;
+      accessFlags = new DexAccessFlags(from.accessFlags.get());
+      annotations = from.annotations;
+      parameterAnnotations = from.parameterAnnotations;
+      code = from.code;
+      compilationState = from.compilationState;
+      optimizationInfo = from.optimizationInfo.copy();
+    }
+
+    public void setMethod(DexMethod method) {
+      this.method = method;
+    }
+
+    public void setAccessFlags(DexAccessFlags accessFlags) {
+      this.accessFlags = accessFlags;
+    }
+
+    public void setAnnotations(DexAnnotationSet annotations) {
+      this.annotations = annotations;
+    }
+
+    public void setParameterAnnotations(DexAnnotationSetRefList parameterAnnotations) {
+      this.parameterAnnotations = parameterAnnotations;
+    }
+
+    public void setCode(Code code) {
+      this.code = code;
+    }
+
+    public DexEncodedMethod build() {
+      assert method != null;
+      assert accessFlags != null;
+      assert annotations != null;
+      assert parameterAnnotations != null;
+      DexEncodedMethod result =
+          new DexEncodedMethod(method, accessFlags, annotations, parameterAnnotations, code);
+      result.compilationState = compilationState;
+      result.optimizationInfo = optimizationInfo;
+      return result;
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
index f0ca9b5..c4ed702 100644
--- a/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexLibraryClass.java
@@ -28,6 +28,11 @@
   }
 
   @Override
+  public String toSourceString() {
+    return type.toSourceString() + "(library class)";
+  }
+
+  @Override
   public void addDependencies(MixedSectionCollection collector) {
     // Should never happen but does not harm.
     assert false;
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 2fc73bc..368cb22 100644
--- a/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexProgramClass.java
@@ -76,6 +76,11 @@
   }
 
   @Override
+  public String toSourceString() {
+    return type.toSourceString();
+  }
+
+  @Override
   public boolean isProgramClass() {
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index 1b70497..234773f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -8,6 +8,7 @@
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
 import java.util.ArrayDeque;
 import java.util.Arrays;
@@ -29,7 +30,6 @@
   public final DexString descriptor;
   private String toStringCache = null;
   private int hierarchyLevel = UNKNOWN_LEVEL;
-
   private Set<DexType> directSubtypes = NO_DIRECT_SUBTYPE;
 
   DexType(DexString descriptor) {
@@ -340,11 +340,16 @@
     }
   }
 
-  public DexType toBaseType(DexItemFactory dexItemFactory) {
+  private int getNumberOfLeadingSquareBrackets() {
     int leadingSquareBrackets = 0;
     while (descriptor.content[leadingSquareBrackets] == '[') {
       leadingSquareBrackets++;
     }
+    return leadingSquareBrackets;
+  }
+
+  public DexType toBaseType(DexItemFactory dexItemFactory) {
+    int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
     if (leadingSquareBrackets == 0) {
       return this;
     }
@@ -353,6 +358,20 @@
     return dexItemFactory.createType(newDesc);
   }
 
+  public DexType replaceBaseType(DexType newBase, DexItemFactory dexItemFactory) {
+    assert this.isArrayType();
+    assert !newBase.isArrayType();
+    int leadingSquareBrackets = getNumberOfLeadingSquareBrackets();
+    byte[] content = new byte[newBase.descriptor.content.length + leadingSquareBrackets];
+    Arrays.fill(content, 0, leadingSquareBrackets, (byte) '[');
+    for (int i = 0; i < newBase.descriptor.content.length; i++) {
+      content[leadingSquareBrackets + i] = newBase.descriptor.content[i];
+    }
+    DexString newDesc = dexItemFactory
+        .createString(newBase.descriptor.size + leadingSquareBrackets, content);
+    return dexItemFactory.createType(newDesc);
+  }
+
   public DexType toArrayElementType(DexItemFactory dexItemFactory) {
     assert this.isArrayType();
     DexString newDesc = dexItemFactory.createString(descriptor.size - 1,
@@ -411,6 +430,15 @@
     }
   }
 
+  public DexType getSingleSubtype() {
+    assert hierarchyLevel != UNKNOWN_LEVEL;
+    if (directSubtypes.size() == 1) {
+      return Iterables.getFirst(directSubtypes, null);
+    } else {
+      return null;
+    }
+  }
+
   public String getPackageDescriptor() {
     return getPackageOrName(true);
   }
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 42f7c6e..e6c2d00 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -10,6 +10,10 @@
 
   public static class Builder {
 
+    private Builder() {
+
+    }
+
     private final Map<DexType, DexType> typeMap = new IdentityHashMap<>();
     private final Map<DexMethod, DexMethod> methodMap = new IdentityHashMap<>();
     private final Map<DexField, DexField> fieldMap = new IdentityHashMap<>();
@@ -26,16 +30,20 @@
       fieldMap.put(from, to);
     }
 
-    public GraphLense build() {
-      return build(new IdentityGraphLense());
+    public GraphLense build(DexItemFactory dexItemFactory) {
+      return build(new IdentityGraphLense(), dexItemFactory);
     }
 
-    public GraphLense build(GraphLense previousLense) {
-      return new NestedGraphLense(typeMap, methodMap, fieldMap, previousLense);
+    public GraphLense build(GraphLense previousLense, DexItemFactory dexItemFactory) {
+      return new NestedGraphLense(typeMap, methodMap, fieldMap, previousLense, dexItemFactory);
     }
 
   }
 
+  public static Builder builder() {
+    return new Builder();
+  }
+
   public abstract DexType lookupType(DexType type, DexEncodedMethod context);
 
   public abstract DexMethod lookupMethod(DexMethod method, DexEncodedMethod context);
@@ -78,21 +86,38 @@
   private static class NestedGraphLense extends GraphLense {
 
     private final GraphLense previousLense;
+    private final DexItemFactory dexItemFactory;
 
     private final Map<DexType, DexType> typeMap;
+    private final Map<DexType, DexType> arrayTypeCache = new IdentityHashMap<>();
     private final Map<DexMethod, DexMethod> methodMap;
     private final Map<DexField, DexField> fieldMap;
 
     private NestedGraphLense(Map<DexType, DexType> typeMap, Map<DexMethod, DexMethod> methodMap,
-        Map<DexField, DexField> fieldMap, GraphLense previousLense) {
+        Map<DexField, DexField> fieldMap, GraphLense previousLense, DexItemFactory dexItemFactory) {
       this.typeMap = typeMap;
       this.methodMap = methodMap;
       this.fieldMap = fieldMap;
       this.previousLense = previousLense;
+      this.dexItemFactory = dexItemFactory;
     }
 
     @Override
     public DexType lookupType(DexType type, DexEncodedMethod context) {
+      if (type.isArrayType()) {
+        DexType result = arrayTypeCache.get(type);
+        if (result == null) {
+          DexType baseType = type.toBaseType(dexItemFactory);
+          DexType newType = lookupType(baseType, context);
+          if (baseType == newType) {
+            result = type;
+          } else {
+            result = type.replaceBaseType(newType, dexItemFactory);
+          }
+          arrayTypeCache.put(type, result);
+        }
+        return result;
+      }
       DexType previous = previousLense.lookupType(type, context);
       return typeMap.getOrDefault(previous, previous);
     }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index b40f6ef..2766684 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.JarSourceCode;
 import com.android.tools.r8.jar.JarRegisterEffectsVisitor;
@@ -79,13 +80,21 @@
 
   @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options) {
-    assert clazz == encodedMethod.method.getHolder();
     triggerDelayedParsingIfNeccessary();
     JarSourceCode source = new JarSourceCode(clazz, node, application);
     IRBuilder builder = new IRBuilder(encodedMethod, source, options);
     return builder.build();
   }
 
+  public IRCode buildIR(DexEncodedMethod encodedMethod, ValueNumberGenerator generator,
+      InternalOptions options) {
+    triggerDelayedParsingIfNeccessary();
+    JarSourceCode source = new JarSourceCode(clazz, node, application);
+    IRBuilder builder = new IRBuilder(encodedMethod, source, generator, options);
+    return builder.build();
+  }
+
+
   @Override
   public void registerReachableDefinitions(UseRegistry registry) {
     triggerDelayedParsingIfNeccessary();
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 7963357..573db67 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -26,6 +26,14 @@
     this.type = type;
   }
 
+  public DexType getType() {
+    return type;
+  }
+
+  public Value object() {
+    return inValues().get(0);
+  }
+
   @Override
   public void buildDex(DexBuilder builder) {
     // The check cast instruction in dex doesn't write a new register. Therefore,
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 1576190..12f0aa2 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
@@ -219,7 +219,6 @@
     this.options = options;
   }
 
-
   private void addToWorklist(BasicBlock block, int firstInstructionIndex) {
     // TODO(ager): Filter out the ones that are already in the worklist, mark bit in block?
     if (!block.isFilled()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index b01fad8..d4b03e3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -114,7 +114,7 @@
     this.codeRewriter = new CodeRewriter(appInfo);
     this.outliner = new Outliner(appInfo, options);
     this.memberValuePropagation = new MemberValuePropagation(appInfo);
-    this.inliner = new Inliner(appInfo, options);
+    this.inliner = new Inliner(appInfo, graphLense, options);
     this.lambdaRewriter = new LambdaRewriter(this);
     this.interfaceMethodRewriter = enableInterfaceMethodDesugaring()
         ? new InterfaceMethodRewriter(this) : null;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 13507ab..1eb8707 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -17,8 +17,10 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.CheckCast;
+import com.android.tools.r8.ir.code.ConstClass;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.InstanceGet;
+import com.android.tools.r8.ir.code.InstanceOf;
 import com.android.tools.r8.ir.code.InstancePut;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -26,6 +28,8 @@
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.code.InvokeCustom;
 import com.android.tools.r8.ir.code.InvokeMethod;
+import com.android.tools.r8.ir.code.InvokeNewArray;
+import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
@@ -43,6 +47,14 @@
     this.appInfo = appInfo;
   }
 
+  private Value makeOutValue(Instruction insn, IRCode code) {
+    if (insn.outValue() == null) {
+      return null;
+    } else {
+      return new Value(code.valueNumberGenerator.next(), -1, insn.outType(), insn.getDebugInfo());
+    }
+  }
+
   /**
    * Replace invoke targets and field accesses with actual definitions.
    */
@@ -84,9 +96,10 @@
             continue;
           }
           DexMethod actualTarget = graphLense.lookupMethod(invokedMethod, method);
-          if (actualTarget != invokedMethod) {
+          Invoke.Type invokeType = getInvokeType(invoke, actualTarget, invokedMethod);
+          if (actualTarget != invokedMethod || invoke.getType() != invokeType) {
             Invoke newInvoke = Invoke
-                .create(getInvokeType(invoke, actualTarget), actualTarget, null,
+                .create(invokeType, actualTarget, null,
                     invoke.outValue(), invoke.inValues());
             iterator.replaceCurrentInstruction(newInvoke);
             // Fix up the return type if needed.
@@ -98,7 +111,7 @@
               CheckCast cast = new CheckCast(
                   newValue,
                   newInvoke.outValue(),
-                  invokedMethod.proto.returnType);
+                  graphLense.lookupType(invokedMethod.proto.returnType, method));
               iterator.add(cast);
               // If the current block has catch handlers split the check cast into its own block.
               if (newInvoke.getBlock().hasCatchHandlers()) {
@@ -144,6 +157,45 @@
                 new StaticPut(staticPut.getType(), staticPut.inValue(), actualField);
             iterator.replaceCurrentInstruction(newStaticPut);
           }
+        } else if (current.isCheckCast()) {
+          CheckCast checkCast = current.asCheckCast();
+          DexType newType = graphLense.lookupType(checkCast.getType(), method);
+          if (newType != checkCast.getType()) {
+            CheckCast newCheckCast =
+                new CheckCast(makeOutValue(checkCast, code), checkCast.object(), newType);
+            iterator.replaceCurrentInstruction(newCheckCast);
+          }
+        } else if (current.isConstClass()) {
+          ConstClass constClass = current.asConstClass();
+          DexType newType = graphLense.lookupType(constClass.getValue(), method);
+          if (newType != constClass.getValue()) {
+            ConstClass newConstClass = new ConstClass(makeOutValue(constClass, code), newType);
+            iterator.replaceCurrentInstruction(newConstClass);
+          }
+        } else if (current.isInstanceOf()) {
+          InstanceOf instanceOf = current.asInstanceOf();
+          DexType newType = graphLense.lookupType(instanceOf.type(), method);
+          if (newType != instanceOf.type()) {
+            InstanceOf newInstanceOf = new InstanceOf(makeOutValue(instanceOf, code),
+                instanceOf.value(), newType);
+            iterator.replaceCurrentInstruction(newInstanceOf);
+          }
+        } else if (current.isInvokeNewArray()) {
+          InvokeNewArray newArray = current.asInvokeNewArray();
+          DexType newType = graphLense.lookupType(newArray.getArrayType(), method);
+          if (newType != newArray.getArrayType()) {
+            InvokeNewArray newNewArray = new InvokeNewArray(newType, makeOutValue(newArray, code),
+                newArray.inValues());
+            iterator.replaceCurrentInstruction(newNewArray);
+          }
+        } else if (current.isNewArrayEmpty()) {
+          NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
+          DexType newType = graphLense.lookupType(newArrayEmpty.type, method);
+          if (newType != newArrayEmpty.type) {
+            NewArrayEmpty newNewArray = new NewArrayEmpty(makeOutValue(newArrayEmpty, code),
+                newArrayEmpty.size(), newType);
+            iterator.replaceCurrentInstruction(newNewArray);
+          }
         }
       }
     }
@@ -176,16 +228,26 @@
     return methodHandle;
   }
 
-  private Type getInvokeType(InvokeMethod invoke, DexMethod actualTarget) {
+  private Type getInvokeType(InvokeMethod invoke, DexMethod actualTarget,
+      DexMethod originalTarget) {
     if (invoke.isInvokeVirtual() || invoke.isInvokeInterface()) {
       // Get the invoke type of the actual definition.
-      DexClass clazz = appInfo.definitionFor(actualTarget.holder);
-      if (clazz == null) {
+      DexClass newTargetClass = appInfo.definitionFor(actualTarget.holder);
+      if (newTargetClass == null) {
         return invoke.getType();
       } else {
-        return clazz.accessFlags.isInterface()
-            ? Type.INTERFACE
-            : Type.VIRTUAL;
+        DexClass originalTargetClass = appInfo.definitionFor(originalTarget.holder);
+        if (originalTargetClass.isInterface() ^ (invoke.getType() == Type.INTERFACE)) {
+          // The invoke was wrong to start with, so we keep it wrong. This is to ensure we get
+          // the IncompatibleClassChangeError the original invoke would have triggered.
+          return newTargetClass.accessFlags.isInterface()
+              ? Type.VIRTUAL
+              : Type.INTERFACE;
+        } else {
+          return newTargetClass.accessFlags.isInterface()
+              ? Type.INTERFACE
+              : Type.VIRTUAL;
+        }
       }
     } else {
       return invoke.getType();
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 ba15c9d..ecda01e 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
@@ -115,7 +115,7 @@
     // by this class as well as its superclasses.
     DexClass current = clazz;
     while (true) {
-      for (DexType type : clazz.interfaces.values) {
+      for (DexType type : current.interfaces.values) {
         helper.merge(getOrCreateInterfaceInfo(type));
       }
 
@@ -139,7 +139,7 @@
     current = clazz;
     while (true) {
       // Hide candidates by virtual method of the class.
-      hideCandidates(clazz.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/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 11484fb..5644096 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
@@ -50,7 +50,7 @@
   private static final String METAFACTORY_METHOD_NAME = "metafactory";
   private static final String METAFACTORY_ALT_METHOD_NAME = "altMetafactory";
 
-  static final String LAMBDA_CLASS_NAME_PREFIX = "-$Lambda$";
+  static final String LAMBDA_CLASS_NAME_PREFIX = "-$$Lambda$";
   static final String EXPECTED_LAMBDA_METHOD_PREFIX = "lambda$";
   static final DexType[] EMPTY_TYPE_ARRAY = new DexType[0];
   static final String LAMBDA_INSTANCE_FIELD_NAME = "INSTANCE";
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index ef7fe72..bc9b95e 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.DominatorTree;
 import com.android.tools.r8.ir.code.IRCode;
@@ -18,6 +19,8 @@
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.CallGraph;
+import com.android.tools.r8.ir.conversion.LensCodeRewriter;
+import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
@@ -32,6 +35,7 @@
   private static final int INLINING_INSTRUCTION_LIMIT = 5;
 
   protected final AppInfoWithSubtyping appInfo;
+  private final GraphLense graphLense;
   private final InternalOptions options;
 
   // State for inlining methods which are known to be called twice.
@@ -40,9 +44,9 @@
   public final Set<DexEncodedMethod> doubleInlineSelectedTargets = Sets.newIdentityHashSet();
   public final Map<DexEncodedMethod, DexEncodedMethod> doubleInlineeCandidates = new HashMap<>();
 
-  public Inliner(
-      AppInfoWithSubtyping appInfo, InternalOptions options) {
+  public Inliner(AppInfoWithSubtyping appInfo, GraphLense graphLense, InternalOptions options) {
     this.appInfo = appInfo;
+    this.graphLense = graphLense;
     this.options = options;
   }
 
@@ -110,6 +114,7 @@
   }
 
   static public class InlineAction {
+
     public final DexEncodedMethod target;
     public final Invoke invoke;
     public final boolean forceInline;
@@ -127,14 +132,26 @@
       this.forceInline = target.getOptimizationInfo().forceInline();
     }
 
-    public IRCode buildIR(ValueNumberGenerator generator, InternalOptions options) {
-      assert target.isProcessed();
-      assert target.getCode().isDexCode();
-      return target.buildIR(generator, options);
+    public IRCode buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo,
+        GraphLense graphLense, InternalOptions options) {
+      if (target.isProcessed()) {
+        assert target.getCode().isDexCode();
+        return target.buildIR(generator, options);
+      } else {
+        // Build the IR for a yet not processed method, and perform minimal IR processing.
+        IRCode code;
+        if (target.getCode().isJarCode()) {
+          code = target.getCode().asJarCode().buildIR(target, generator, options);
+        } else {
+          code = target.getCode().asDexCode().buildIR(target, generator, options);
+        }
+        new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
+        return code;
+      }
     }
   }
 
- private int numberOfInstructions(IRCode code) {
+  private int numberOfInstructions(IRCode code) {
     int numOfInstructions = 0;
     for (BasicBlock block : code.blocks) {
       numOfInstructions += block.getInstructions().size();
@@ -167,7 +184,9 @@
     while (iterator.hasNext()) {
       instruction = iterator.next();
       if (instruction.inValues().contains(unInitializedObject)) {
-        return instruction.isInvokeDirect();
+        return instruction.isInvokeDirect()
+            && appInfo.dexItemFactory
+            .isConstructor(instruction.asInvokeDirect().getInvokedMethod());
       }
     }
     assert false : "Execution should never reach this point";
@@ -208,14 +227,33 @@
           InvokeMethod invoke = current.asInvokeMethod();
           InlineAction result = invoke.computeInlining(oracle);
           if (result != null) {
-            IRCode inlinee = result.buildIR(code.valueNumberGenerator, options);
+            DexEncodedMethod target = appInfo.lookup(invoke.getType(), invoke.getInvokedMethod());
+            if (target == null) {
+              // The declared target cannot be found so skip inlining.
+              continue;
+            }
+            boolean forceInline = target.getOptimizationInfo().forceInline();
+            if (!target.isProcessed() && !forceInline) {
+              // Do not inline code that was not processed unless we have to force inline.
+              continue;
+            }
+            IRCode inlinee = result
+                .buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
             if (inlinee != null) {
               // TODO(sgjesse): Get rid of this additional check by improved inlining.
               if (block.hasCatchHandlers() && inlinee.getNormalExitBlock() == null) {
                 continue;
               }
+              // If this code did not go through the full pipeline, apply inlining to make sure
+              // that force inline targets get processed.
+              if (!target.isProcessed()) {
+                assert forceInline;
+                if (Log.ENABLED) {
+                  Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
+                }
+                performInlining(target, inlinee, callGraph);
+              }
               // Make sure constructor inlining is legal.
-              DexEncodedMethod target = appInfo.lookup(invoke.getType(), invoke.getInvokedMethod());
               if (target.accessFlags.isConstructor() && !legalConstructorInline(method, inlinee)) {
                 continue;
               }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
index fa55f1c..954f11c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningOracle.java
@@ -20,10 +20,11 @@
 import com.android.tools.r8.logging.Log;
 
 /**
- *  The InliningOracle contains information needed for when inlining
- *  other methods into @method.
+ * The InliningOracle contains information needed for when inlining
+ * other methods into @method.
  */
 public class InliningOracle {
+
   final Inliner inliner;
   final DexEncodedMethod method;
   final Value receiver;
@@ -208,7 +209,7 @@
       return true;
     }
     DexClass clazz = inliner.appInfo.definitionFor(targetHolder);
-    return (clazz != null) && (clazz.getClassInitializer(inliner.appInfo.dexItemFactory) == null);
+    return (clazz != null) && (!clazz.hasClassInitializer());
   }
 
   private boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
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 e79abc1..f621c67 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
@@ -429,9 +429,48 @@
   private boolean performAllocation(ArgumentReuseMode mode) {
     boolean result = performAllocationWithoutMoveInsertion(mode);
     insertMoves();
+    if (mode == ArgumentReuseMode.DISALLOW_ARGUMENT_REUSE) {
+      // Now that we know the max register number we can compute whether it is safe to use
+      // argument registers in place. If it is, we redo move insertion to get rid of the moves
+      // caused by splitting of the argument registers.
+      if (unsplitArguments()) {
+        removeSpillAndPhiMoves();
+        insertMoves();
+      }
+    }
     return result;
   }
 
+  // When argument register reuse is disallowed, we split argument values to make sure that
+  // we can get the argument into low enough registers at uses that require low numbers. After
+  // register allocation we can check if it is safe to just use the argument register itself
+  // for all uses and thereby avoid moving argument values around.
+  private boolean unsplitArguments() {
+    boolean argumentRegisterUnsplit = false;
+    Value current = preArgumentSentinelValue;
+    while (current != null) {
+      LiveIntervals intervals = current.getLiveIntervals();
+      assert intervals.getRegisterLimit() == Constants.U16BIT_MAX;
+      boolean canUseArgumentRegister = true;
+      for (LiveIntervals child : intervals.getSplitChildren()) {
+        if (child.getRegisterLimit() < registersUsed()) {
+          canUseArgumentRegister = false;
+          break;
+        }
+      }
+      if (canUseArgumentRegister) {
+        argumentRegisterUnsplit = true;
+        for (LiveIntervals child : intervals.getSplitChildren()) {
+          child.clearRegisterAssignment();
+          child.setRegister(intervals.getRegister());
+          child.setSpilled(false);
+        }
+      }
+      current = current.getNextConsecutive();
+    }
+    return argumentRegisterUnsplit;
+  }
+
   private void removeSpillAndPhiMoves() {
     for (BasicBlock block : code.blocks) {
       InstructionListIterator it = block.listIterator();
@@ -470,7 +509,7 @@
     return realRegisterNumberFromAllocated(intervalsRegister);
   }
 
-  int realRegisterNumberFromAllocated(int allocated) {
+  int unadjustedRealRegisterFromAllocated(int allocated) {
     assert allocated != NO_REGISTER;
     assert allocated >= 0;
     int register;
@@ -484,6 +523,11 @@
       // For everything else use the lower numbers.
       register = allocated - numberOfArgumentRegisters - NUMBER_OF_SENTINEL_REGISTERS;
     }
+    return register;
+  }
+
+  int realRegisterNumberFromAllocated(int allocated) {
+    int register = unadjustedRealRegisterFromAllocated(allocated);
     // Adjust for spill registers that turn out to be unused because the value can be
     // rematerialized instead of spilled.
     if (unusedRegisters != null) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index f1d3b9e..c966813 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -89,8 +89,12 @@
     }
     // If one of the non-spilled splits uses a register that is higher than U8BIT_MAX we cannot
     // rematerialize it using a ConstNumber instruction and we use spill moves instead of
-    // rematerialization.
-    int max = registerAllocator.realRegisterNumberFromAllocated(getMaxNonSpilledRegister());
+    // rematerialization. We use this check both before and after we have computed the set
+    // of unused registers. We therefore have to be careful to use the same max number for
+    // these computations. We use the unadjusted real register number to make sure that
+    // isRematerializable for the same intervals does not change from one phase of
+    // compilation to the next.
+    int max = registerAllocator.unadjustedRealRegisterFromAllocated(getMaxNonSpilledRegister());
     return max < Constants.U8BIT_MAX;
   }
 
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index 18099fb..9e81102 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -42,7 +42,7 @@
     timing.begin("MinifyClasses");
     Map<DexType, DexString> classRenaming =
         new ClassNameMinifier(
-                appInfo, rootSet, options.packagePrefix, options.classObfuscationDictionary)
+            appInfo, rootSet, options.packagePrefix, options.classObfuscationDictionary)
             .computeRenaming();
     timing.end();
     timing.begin("MinifyMethods");
diff --git a/src/main/java/com/android/tools/r8/optimize/DebugStripper.java b/src/main/java/com/android/tools/r8/optimize/DebugStripper.java
index d045f76..998bc8d 100644
--- a/src/main/java/com/android/tools/r8/optimize/DebugStripper.java
+++ b/src/main/java/com/android/tools/r8/optimize/DebugStripper.java
@@ -17,9 +17,10 @@
 import com.android.tools.r8.naming.MemberNaming;
 import com.android.tools.r8.naming.MemberNaming.Range;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.utils.HashMapInt;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.ImmutableMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.List;
 
 public class DebugStripper {
@@ -50,6 +51,7 @@
   }
 
   private static class NumberedDebugInfo {
+
     final int numberOfEntries;
     final DexDebugInfo info;
 
@@ -103,7 +105,7 @@
   }
 
   private void processCode(DexEncodedMethod encodedMethod, MemberNaming naming,
-      HashMapInt<DexString> nameCounts) {
+      Reference2IntMap<DexString> nameCounts) {
     if (encodedMethod.getCode() == null) {
       return;
     }
@@ -118,7 +120,7 @@
     if (options.skipDebugLineNumberOpt) {
       startLine = originalInfo.startLine;
     } else {
-      int nameCount = nameCounts.get(name);
+      int nameCount = nameCounts.getInt(name);
       if (nameCount == USED_ONCE) {
         isUsedOnce = true;
         startLine = 0;
@@ -132,7 +134,7 @@
     DexDebugInfo newInfo = numberedInfo.info;
     if (!options.skipDebugLineNumberOpt) {
       // Fix up the line information.
-      int previousCount = nameCounts.get(name);
+      int previousCount = nameCounts.getInt(name);
       nameCounts.put(name, previousCount + numberedInfo.numberOfEntries);
       // If we don't actually need line information and there are no debug entries, throw it away.
       if (newInfo != null && isUsedOnce && newInfo.events.length == 0) {
@@ -147,7 +149,7 @@
   }
 
   private void processMethod(DexEncodedMethod method, ClassNaming classNaming,
-      HashMapInt<DexString> nameCounts) {
+      Reference2IntMap<DexString> nameCounts) {
     MemberNaming naming = null;
     if (classNaming != null) {
       Signature renamedSignature = classNameMapper.getRenamedMethodSignature(method.method);
@@ -157,7 +159,7 @@
   }
 
   private void processMethods(DexEncodedMethod[] methods, ClassNaming naming,
-      HashMapInt<DexString> nameCounts) {
+      Reference2IntMap<DexString> nameCounts) {
     if (methods == null) {
       return;
     }
@@ -172,14 +174,14 @@
     }
     String name = descriptorToName(clazz.type.toDescriptorString());
     ClassNaming naming = classNameMapper == null ? null : classNameMapper.getClassNaming(name);
-    HashMapInt<DexString> nameCounts = new HashMapInt<>();
+    Reference2IntMap<DexString> nameCounts = new Reference2IntOpenHashMap<>();
     setIntialNameCounts(nameCounts, clazz.directMethods());
     setIntialNameCounts(nameCounts, clazz.virtualMethods());
     processMethods(clazz.directMethods(), naming, nameCounts);
     processMethods(clazz.virtualMethods(), naming, nameCounts);
   }
 
-  private void setIntialNameCounts(HashMapInt<DexString> nameCounts,
+  private void setIntialNameCounts(Reference2IntMap<DexString> nameCounts,
       DexEncodedMethod[] methods) {
     for (DexEncodedMethod method : methods) {
       if (nameCounts.containsKey(method.method.name)) {
diff --git a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
index 61b270d..8dc8a73 100644
--- a/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
+++ b/src/main/java/com/android/tools/r8/optimize/InvokeSingleTargetExtractor.java
@@ -8,11 +8,11 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.UseRegistry;
 
-class InvokeSingleTargetExtractor extends UseRegistry {
+public class InvokeSingleTargetExtractor extends UseRegistry {
   private InvokeKind kind = InvokeKind.NONE;
   private DexMethod target;
 
-  InvokeSingleTargetExtractor() {
+  public InvokeSingleTargetExtractor() {
   }
 
   private boolean setTarget(DexMethod target, InvokeKind kind) {
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 1e4c51b..4e68d0f 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -20,7 +20,7 @@
 public class MemberRebindingAnalysis {
   private final AppInfoWithLiveness appInfo;
   private final GraphLense lense;
-  private final GraphLense.Builder builder = new GraphLense.Builder();
+  private final GraphLense.Builder builder = GraphLense.builder();
 
   public MemberRebindingAnalysis(AppInfoWithLiveness appInfo, GraphLense lense) {
     assert lense.isContextFree();
@@ -150,6 +150,6 @@
         appInfo::lookupStaticTarget, DexClass::findStaticTarget);
     computeFieldRebinding(Sets.union(appInfo.instanceFieldsRead, appInfo.instanceFieldsWritten),
         appInfo::lookupInstanceTarget, DexClass::findInstanceTarget);
-    return builder.build(lense);
+    return builder.build(lense, appInfo.dexItemFactory);
   }
 }
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 07e641a..6dbf79b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -349,7 +349,7 @@
       }
       // We also need to add the corresponding <clinit> to the set of live methods, as otherwise
       // static field initialization (and other class-load-time sideeffects) will not happen.
-      DexEncodedMethod clinit = holder.getClassInitializer(appInfo.dexItemFactory);
+      DexEncodedMethod clinit = holder.getClassInitializer();
       if (clinit != null) {
         markDirectStaticOrConstructorMethodAsLive(clinit, KeepReason.reachableFromLiveType(type));
       }
@@ -682,6 +682,7 @@
     }
     superInvokeDependencies.computeIfAbsent(from, ignore -> Sets.newIdentityHashSet()).add(target);
     if (liveMethods.contains(from)) {
+      markMethodAsTargeted(target, KeepReason.invokedViaSuperFrom(from));
       markVirtualMethodAsLive(target, KeepReason.invokedViaSuperFrom(from));
     }
   }
@@ -822,6 +823,7 @@
             Log.verbose(getClass(), "Found super invoke constraint on `%s`.",
                 superCallTarget.method);
           }
+          markMethodAsTargeted(superCallTarget, KeepReason.invokedViaSuperFrom(method));
           markVirtualMethodAsLive(superCallTarget, KeepReason.invokedViaSuperFrom(method));
         }
       }
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
new file mode 100644
index 0000000..a2864e4
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -0,0 +1,632 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.shaking;
+
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexClass;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexProto;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.Builder;
+import com.android.tools.r8.graph.KeyedDexItem;
+import com.android.tools.r8.graph.PresortedComparable;
+import com.android.tools.r8.logging.Log;
+import com.android.tools.r8.optimize.InvokeSingleTargetExtractor;
+import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.utils.FieldSignatureEquivalence;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.Timing;
+import com.google.common.base.Equivalence;
+import com.google.common.base.Equivalence.Wrapper;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2IntMap;
+import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * Merges Supertypes with a single implementation into their single subtype.
+ * <p>
+ * A common use-case for this is to merge an interface into its single implementation.
+ * <p>
+ * The class merger only fixes the structure of the graph but leaves the actual instructions
+ * untouched. Fixup of instructions is deferred via a {@link GraphLense} to the Ir building phase.
+ */
+public class SimpleClassMerger {
+
+  private final DexApplication application;
+  private final AppInfoWithLiveness appInfo;
+  private final GraphLense graphLense;
+  private final GraphLense.Builder renamedMembersLense = GraphLense.builder();
+  private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
+  private final Timing timing;
+  private Set<DexMethod> invokes;
+  private int numberOfMerges = 0;
+
+  public SimpleClassMerger(DexApplication application, AppInfoWithLiveness appInfo,
+      GraphLense graphLense, Timing timing) {
+    this.application = application;
+    this.appInfo = appInfo;
+    this.graphLense = graphLense;
+    this.timing = timing;
+  }
+
+  private boolean isMergeCandidate(DexProgramClass clazz) {
+    // We can merge program classes if they are not instantiated, have a single subtype
+    // and we do not have to keep them.
+    return !clazz.isLibraryClass()
+        && !appInfo.instantiatedTypes.contains(clazz.type)
+        && !appInfo.pinnedItems.contains(clazz)
+        && clazz.type.getSingleSubtype() != null;
+  }
+
+  private Set<DexMethod> getInvokes() {
+    if (invokes == null) {
+
+      // TODO(herhut): Ignore invokes into the library, as those can only reference library types.
+      invokes = Sets.newIdentityHashSet();
+      invokes.addAll(appInfo.directInvokes);
+      invokes.addAll(appInfo.staticInvokes);
+      invokes.addAll(appInfo.superInvokes);
+      invokes.addAll(appInfo.virtualInvokes);
+      invokes.addAll(appInfo.targetedMethods);
+      invokes.addAll(appInfo.liveMethods);
+      for (DexEncodedMethod method : Iterables
+          .filter(appInfo.pinnedItems, DexEncodedMethod.class)) {
+        invokes.add(method.method);
+      }
+    }
+    return invokes;
+  }
+
+  public GraphLense run() {
+    timing.begin("merge");
+    GraphLense mergingGraphLense = mergeClasses(graphLense);
+    timing.end();
+    timing.begin("fixup");
+    GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
+    timing.end();
+    return result;
+  }
+
+  private GraphLense mergeClasses(GraphLense graphLense) {
+    for (DexProgramClass clazz : application.classes()) {
+      if (isMergeCandidate(clazz)) {
+        DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
+        if (appInfo.pinnedItems.contains(targetClass)) {
+          // We have to keep the target class intact, so we cannot merge it.
+          continue;
+        }
+        if (mergedClasses.containsKey(targetClass.type)) {
+          // TODO(herhut): Traverse top-down.
+          continue;
+        }
+        if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
+          // TODO(herhut): Handle class initializers.
+          if (Log.ENABLED) {
+            Log.info(getClass(), "Cannot merge %s into %s due to static initializers.",
+                clazz.toSourceString(), targetClass.toSourceString());
+          }
+          continue;
+        }
+        // Guard against the case where we have two methods that may get the same signature
+        // if we replace types. This is rare, so we approximate and err on the safe side here.
+        if (new CollisionDetector(clazz.type, targetClass.type, getInvokes(), mergedClasses)
+            .mayCollide()) {
+          if (Log.ENABLED) {
+            Log.info(getClass(), "Cannot merge %s into %s due to conflict.", clazz.toSourceString(),
+                targetClass.toSourceString());
+          }
+          continue;
+        }
+        boolean merged = new ClassMerger(clazz, targetClass).merge();
+        if (Log.ENABLED) {
+          if (merged) {
+            numberOfMerges++;
+            Log.info(getClass(), "Merged class %s into %s.", clazz.toSourceString(),
+                targetClass.toSourceString());
+          } else {
+            Log.info(getClass(), "Aborted merge for class %s into %s.",
+                clazz.toSourceString(), targetClass.toSourceString());
+          }
+        }
+      }
+    }
+    if (Log.ENABLED) {
+      Log.debug(getClass(), "Merged %d classes.", numberOfMerges);
+    }
+    return renamedMembersLense.build(graphLense, application.dexItemFactory);
+  }
+
+  private class ClassMerger {
+
+    private static final String CONSTRUCTOR_NAME = "constructor";
+
+    private final DexClass source;
+    private final DexClass target;
+    private final Map<DexEncodedMethod, DexEncodedMethod> deferredRenamings = new HashMap<>();
+    private boolean abortMerge = false;
+
+    private ClassMerger(DexClass source, DexClass target) {
+      this.source = source;
+      this.target = target;
+    }
+
+    public boolean merge() {
+      // Merge the class [clazz] into [targetClass] by adding all methods to
+      // targetClass that are not currently contained.
+      // Step 1: Merge methods
+      Set<Wrapper<DexMethod>> existingMethods = new HashSet<>();
+      addAll(existingMethods, target.directMethods(), MethodSignatureEquivalence.get());
+      addAll(existingMethods, target.virtualMethods(), MethodSignatureEquivalence.get());
+      Collection<DexEncodedMethod> mergedDirectMethods = mergeItems(
+          Iterators.transform(Iterators.forArray(source.directMethods()), this::renameConstructors),
+          target.directMethods(),
+          MethodSignatureEquivalence.get(),
+          existingMethods,
+          this::renameMethod
+      );
+      Iterator<DexEncodedMethod> methods = Iterators.forArray(source.virtualMethods());
+      if (source.accessFlags.isInterface()) {
+        // If merging an interface, only merge methods that are not otherwise defined in the
+        // target class.
+        methods = Iterators.transform(methods, this::filterShadowedInterfaceMethods);
+      }
+      Collection<DexEncodedMethod> mergedVirtualMethods = mergeItems(
+          methods,
+          target.virtualMethods(),
+          MethodSignatureEquivalence.get(),
+          existingMethods,
+          this::abortOnNonAbstract);
+      if (abortMerge) {
+        return false;
+      }
+      // Step 2: Merge fields
+      Set<Wrapper<DexField>> existingFields = new HashSet<>();
+      addAll(existingFields, target.instanceFields(), FieldSignatureEquivalence.get());
+      addAll(existingFields, target.staticFields(), FieldSignatureEquivalence.get());
+      Collection<DexEncodedField> mergedStaticFields = mergeItems(
+          Iterators.forArray(source.staticFields()),
+          target.staticFields(),
+          FieldSignatureEquivalence.get(),
+          existingFields,
+          this::renameField);
+      Collection<DexEncodedField> mergedInstanceFields = mergeItems(
+          Iterators.forArray(source.instanceFields()),
+          target.instanceFields(),
+          FieldSignatureEquivalence.get(),
+          existingFields,
+          this::renameField);
+      // Step 3: Merge interfaces
+      Set<DexType> interfaces = mergeArrays(target.interfaces.values, source.interfaces.values);
+      // Now destructively update the class.
+      // Step 1: Update supertype or fix interfaces.
+      if (source.isInterface()) {
+        interfaces.remove(source.type);
+      } else {
+        assert !target.isInterface();
+        target.superType = source.superType;
+      }
+      target.interfaces = interfaces.isEmpty()
+          ? DexTypeList.empty()
+          : new DexTypeList(interfaces.toArray(new DexType[interfaces.size()]));
+      // Step 2: replace fields and methods.
+      target.directMethods = mergedDirectMethods
+          .toArray(new DexEncodedMethod[mergedDirectMethods.size()]);
+      target.virtualMethods = mergedVirtualMethods
+          .toArray(new DexEncodedMethod[mergedVirtualMethods.size()]);
+      target.staticFields = mergedStaticFields
+          .toArray(new DexEncodedField[mergedStaticFields.size()]);
+      target.instanceFields = mergedInstanceFields
+          .toArray(new DexEncodedField[mergedInstanceFields.size()]);
+      // Step 3: Unlink old class to ease tree shaking.
+      source.superType = application.dexItemFactory.objectType;
+      source.directMethods = null;
+      source.virtualMethods = null;
+      source.instanceFields = null;
+      source.staticFields = null;
+      source.interfaces = DexTypeList.empty();
+      // Step 4: Record merging.
+      mergedClasses.put(source.type, target.type);
+      // Step 5: Make deferred renamings final.
+      deferredRenamings.forEach((from, to) -> renamedMembersLense.map(from.method, to.method));
+      return true;
+    }
+
+    private DexEncodedMethod filterShadowedInterfaceMethods(DexEncodedMethod m) {
+      DexEncodedMethod actual = appInfo.lookupVirtualDefinition(target.type, m.method);
+      assert actual != null;
+      if (actual != m) {
+        // We will drop a method here, so record it as a potential renaming.
+        deferredRenamings.put(m, actual);
+        return null;
+      }
+      // We will keep the method, so the class better be abstract.
+      assert target.accessFlags.isAbstract();
+      return m;
+    }
+
+    private <T extends KeyedDexItem<S>, S extends PresortedComparable<S>> void addAll(
+        Collection<Wrapper<S>> collection, T[] items, Equivalence<S> equivalence) {
+      for (T item : items) {
+        collection.add(equivalence.wrap(item.getKey()));
+      }
+    }
+
+    private <T> Set<T> mergeArrays(T[] one, T[] other) {
+      Set<T> merged = new LinkedHashSet<>();
+      Collections.addAll(merged, one);
+      Collections.addAll(merged, other);
+      return merged;
+    }
+
+    private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> Collection<S> mergeItems(
+        Iterator<S> fromItems,
+        S[] toItems,
+        Equivalence<T> equivalence,
+        Set<Wrapper<T>> existing,
+        BiFunction<S, S, S> onConflict) {
+      HashMap<Wrapper<T>, S> methods = new HashMap<>();
+      // First add everything from the target class. These items are not preprocessed.
+      for (S item : toItems) {
+        methods.put(equivalence.wrap(item.getKey()), item);
+      }
+      // Now add the new methods, resolving shadowing.
+      addNonShadowed(fromItems, methods, equivalence, existing, onConflict);
+      return methods.values();
+    }
+
+    private <T extends PresortedComparable<T>, S extends KeyedDexItem<T>> void addNonShadowed(
+        Iterator<S> items,
+        HashMap<Wrapper<T>, S> map,
+        Equivalence<T> equivalence,
+        Set<Wrapper<T>> existing,
+        BiFunction<S, S, S> onConflict) {
+      while (items.hasNext()) {
+        S item = items.next();
+        if (item == null) {
+          // This item was filtered out by a preprocessing.
+          continue;
+        }
+        Wrapper<T> wrapped = equivalence.wrap(item.getKey());
+        if (existing.contains(wrapped)) {
+          S resolved = onConflict.apply(map.get(wrapped), item);
+          wrapped = equivalence.wrap(resolved.getKey());
+          map.put(wrapped, resolved);
+        } else {
+          map.put(wrapped, item);
+        }
+      }
+    }
+
+    private DexString makeMergedName(String nameString, DexType holder) {
+      return application.dexItemFactory
+          .createString(nameString + "$" + holder.toSourceString().replace('.', '$'));
+    }
+
+    private DexEncodedMethod abortOnNonAbstract(DexEncodedMethod existing,
+        DexEncodedMethod method) {
+      if (existing == null) {
+        // This is a conflict between a static and virtual method. Abort.
+        abortMerge = true;
+        return method;
+      }
+      // Ignore if we merge in an abstract method or if we override a bridge method that would
+      // bridge to the superclasses method.
+      if (method.accessFlags.isAbstract()) {
+        // We make a method disappear here, so record the renaming so that calls to the previous
+        // target get forwarded properly.
+        deferredRenamings.put(method, existing);
+        return existing;
+      } else if (existing.accessFlags.isBridge()) {
+        InvokeSingleTargetExtractor extractor = new InvokeSingleTargetExtractor();
+        existing.getCode().registerReachableDefinitions(extractor);
+        if (extractor.getTarget() != method.method) {
+          abortMerge = true;
+        }
+        return method;
+      } else {
+        abortMerge = true;
+        return existing;
+      }
+    }
+
+    private DexEncodedMethod renameConstructors(DexEncodedMethod method) {
+      // Only rename instance initializers.
+      if (!method.accessFlags.isConstructor() || method.accessFlags.isStatic()) {
+        return method;
+      }
+      DexType holder = method.method.holder;
+      DexEncodedMethod result = method
+          .toRenamedMethod(makeMergedName(CONSTRUCTOR_NAME, holder), application.dexItemFactory);
+      result.markForceInline();
+      deferredRenamings.put(method, result);
+      // Renamed constructors turn into ordinary private functions. They can be private, as
+      // they are only references from their direct subclass, which they were merged into.
+      result.accessFlags.unsetConstructor();
+      result.accessFlags.unsetPublic();
+      result.accessFlags.unsetProtected();
+      result.accessFlags.setPrivate();
+      return result;
+    }
+
+    private DexEncodedMethod renameMethod(DexEncodedMethod existing, DexEncodedMethod method) {
+      // We cannot handle renaming static initializers yet and constructors should have been
+      // renamed already.
+      assert !method.accessFlags.isConstructor();
+      DexType holder = method.method.holder;
+      String name = method.method.name.toSourceString();
+      DexEncodedMethod result = method
+          .toRenamedMethod(makeMergedName(name, holder), application.dexItemFactory);
+      renamedMembersLense.map(method.method, result.method);
+      return result;
+    }
+
+    private DexEncodedField renameField(DexEncodedField existing, DexEncodedField field) {
+      DexString oldName = field.field.name;
+      DexType holder = field.field.clazz;
+      DexEncodedField result = field
+          .toRenamedField(makeMergedName(oldName.toSourceString(), holder),
+              application.dexItemFactory);
+      renamedMembersLense.map(field.field, result.field);
+      return result;
+    }
+  }
+
+  private class TreeFixer {
+
+    private final Builder lense = GraphLense.builder();
+    Map<DexProto, DexProto> protoFixupCache = new IdentityHashMap<>();
+
+    private GraphLense fixupTypeReferences(GraphLense graphLense) {
+      // Globally substitute merged class types in protos and holders.
+      for (DexProgramClass clazz : appInfo.classes()) {
+        clazz.directMethods = substituteTypesIn(clazz.directMethods);
+        clazz.virtualMethods = substituteTypesIn(clazz.virtualMethods);
+        clazz.virtualMethods = removeDupes(clazz.virtualMethods);
+        clazz.staticFields = substituteTypesIn(clazz.staticFields);
+        clazz.instanceFields = substituteTypesIn(clazz.instanceFields);
+      }
+      // Record type renamings so instanceof and checkcast checks are also fixed.
+      for (DexType type : mergedClasses.keySet()) {
+        DexType fixed = fixupType(type);
+        lense.map(type, fixed);
+      }
+      return lense.build(graphLense, application.dexItemFactory);
+    }
+
+    private DexEncodedMethod[] removeDupes(DexEncodedMethod[] methods) {
+      if (methods == null) {
+        return null;
+      }
+      Map<DexMethod, DexEncodedMethod> filtered = new IdentityHashMap<>();
+      for (DexEncodedMethod method : methods) {
+        DexEncodedMethod previous = filtered.put(method.method, method);
+        if (previous != null) {
+          if (!previous.accessFlags.isBridge()) {
+            if (!method.accessFlags.isBridge()) {
+              throw new CompilationError("Class merging produced invalid result.");
+            } else {
+              filtered.put(previous.method, previous);
+            }
+          }
+        }
+      }
+      if (filtered.size() == methods.length) {
+        return methods;
+      }
+      return filtered.values().toArray(new DexEncodedMethod[filtered.size()]);
+    }
+
+    private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
+      if (methods == null) {
+        return null;
+      }
+      for (int i = 0; i < methods.length; i++) {
+        DexEncodedMethod encodedMethod = methods[i];
+        DexMethod method = encodedMethod.method;
+        DexProto newProto = getUpdatedProto(method.proto);
+        DexType newHolder = fixupType(method.holder);
+        DexMethod newMethod = application.dexItemFactory.createMethod(newHolder, newProto,
+            method.name);
+        if (newMethod != encodedMethod.method) {
+          lense.map(encodedMethod.method, newMethod);
+          methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
+        }
+      }
+      return methods;
+    }
+
+    private DexEncodedField[] substituteTypesIn(DexEncodedField[] fields) {
+      if (fields == null) {
+        return null;
+      }
+      for (int i = 0; i < fields.length; i++) {
+        DexEncodedField encodedField = fields[i];
+        DexField field = encodedField.field;
+        DexType newType = fixupType(field.type);
+        DexType newHolder = fixupType(field.clazz);
+        DexField newField = application.dexItemFactory.createField(newHolder, newType, field.name);
+        if (newField != encodedField.field) {
+          lense.map(encodedField.field, newField);
+          fields[i] = encodedField.toTypeSubstitutedField(newField);
+        }
+      }
+      return fields;
+    }
+
+    private DexProto getUpdatedProto(DexProto proto) {
+      DexProto result = protoFixupCache.get(proto);
+      if (result == null) {
+        DexType returnType = fixupType(proto.returnType);
+        DexType[] arguments = fixupTypes(proto.parameters.values);
+        result = application.dexItemFactory.createProto(returnType, arguments);
+        protoFixupCache.put(proto, result);
+      }
+      return result;
+    }
+
+    private DexType fixupType(DexType type) {
+      if (type.isArrayType()) {
+        DexType base = type.toBaseType(application.dexItemFactory);
+        DexType fixed = fixupType(base);
+        if (base == fixed) {
+          return type;
+        } else {
+          return type.replaceBaseType(fixed, application.dexItemFactory);
+        }
+      }
+      while (mergedClasses.containsKey(type)) {
+        type = mergedClasses.get(type);
+      }
+      return type;
+    }
+
+    private DexType[] fixupTypes(DexType[] types) {
+      DexType[] result = new DexType[types.length];
+      for (int i = 0; i < result.length; i++) {
+        result[i] = fixupType(types[i]);
+      }
+      return result;
+    }
+  }
+
+  private static class CollisionDetector {
+
+    private static int NOT_FOUND = 1 << (Integer.SIZE - 1);
+
+    // TODO(herhut): Maybe cache seenPositions for target classes.
+    private final Map<DexString, Int2IntMap> seenPositions = new IdentityHashMap<>();
+    private final Reference2IntMap<DexProto> targetProtoCache;
+    private final Reference2IntMap<DexProto> sourceProtoCache;
+    private final DexType source, target;
+    private final Collection<DexMethod> invokes;
+    private final Map<DexType, DexType> substituions;
+
+    private CollisionDetector(DexType source, DexType target, Collection<DexMethod> invokes,
+        Map<DexType, DexType> substitutions) {
+      this.source = source;
+      this.target = target;
+      this.invokes = invokes;
+      this.substituions = substitutions;
+      this.targetProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
+      this.targetProtoCache.defaultReturnValue(NOT_FOUND);
+      this.sourceProtoCache = new Reference2IntOpenHashMap<>(invokes.size() / 2);
+      this.sourceProtoCache.defaultReturnValue(NOT_FOUND);
+    }
+
+    boolean mayCollide() {
+      fillSeenPositions(invokes);
+      // If the type is not used in methods at all, there cannot be any conflict.
+      if (seenPositions.isEmpty()) {
+        return false;
+      }
+      for (DexMethod method : invokes) {
+        Int2IntMap positionsMap = seenPositions.get(method.name);
+        if (positionsMap != null) {
+          int arity = method.proto.parameters.values.length;
+          int previous = positionsMap.get(arity);
+          if (previous != NOT_FOUND) {
+            assert previous != 0;
+            int positions = computePositionsFor(method.proto, source, sourceProtoCache,
+                substituions);
+            if ((positions & previous) != 0) {
+              return true;
+            }
+          }
+        }
+      }
+      return false;
+    }
+
+    private void fillSeenPositions(Collection<DexMethod> invokes) {
+      for (DexMethod method : invokes) {
+        DexType[] parameters = method.proto.parameters.values;
+        int arity = parameters.length;
+        int positions = computePositionsFor(method.proto, target, targetProtoCache, substituions);
+        if (positions != 0) {
+          Int2IntMap positionsMap =
+              seenPositions.computeIfAbsent(method.name, k -> {
+                Int2IntMap result = new Int2IntOpenHashMap();
+                result.defaultReturnValue(NOT_FOUND);
+                return result;
+              });
+          int value = 0;
+          int previous = positionsMap.get(arity);
+          if (previous != NOT_FOUND) {
+            value = previous;
+          }
+          value |= positions;
+          positionsMap.put(arity, value);
+        }
+      }
+
+    }
+
+    private int computePositionsFor(DexProto proto, DexType type,
+        Reference2IntMap<DexProto> cache, Map<DexType, DexType> substitutions) {
+      int result = cache.getInt(proto);
+      if (result != NOT_FOUND) {
+        return result;
+      }
+      result = 0;
+      int bitsUsed = 0;
+      int accumulator = 0;
+      for (DexType aType : proto.parameters.values) {
+        if (substitutions != null) {
+          // Substitute the type with the already merged class to estimate what it will
+          // look like.
+          while (substitutions.containsKey(aType)) {
+            aType = substitutions.get(aType);
+          }
+        }
+        accumulator <<= 1;
+        bitsUsed++;
+        if (aType == type) {
+          accumulator |= 1;
+        }
+        // Handle overflow on 31 bit boundary.
+        if (bitsUsed == Integer.SIZE - 1) {
+          result |= accumulator;
+          accumulator = 0;
+          bitsUsed = 0;
+        }
+      }
+      // We also take the return type into account for potential conflicts.
+      DexType returnType = proto.returnType;
+      if (substitutions != null) {
+        while (substitutions.containsKey(returnType)) {
+          returnType = substitutions.get(returnType);
+        }
+      }
+      accumulator <<= 1;
+      if (returnType == type) {
+        accumulator |= 1;
+      }
+      result |= accumulator;
+      cache.put(proto, result);
+      return result;
+    }
+  }
+}
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 5f2996f..f801cdd 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -76,10 +76,10 @@
             // However, this might extend an abstract class and we might have removed the
             // corresponding methods in this class. This might happen if we only keep this
             // class around for its constants.
-            // TODO(herhut): Find alternative for abstract final classes.
-          } else {
-            clazz.accessFlags.setAbstract();
+            // For now, we remove the final flag to still be able to mark it abstract.
+            clazz.accessFlags.unsetFinal();
           }
+          clazz.accessFlags.setAbstract();
         }
         // The class is used and must be kept. Remove the unused fields and methods from
         // the class.
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
index 6d46efe..85d5c57 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -203,12 +203,16 @@
     return proguardSeeds == null ? null : proguardSeeds.getStream(closer);
   }
 
-  /** True if the package distribution resource exists. */
+  /**
+   * True if the package distribution resource exists.
+   */
   public boolean hasPackageDistribution() {
     return packageDistribution != null;
   }
 
-  /** Get the input stream of the package distribution resource if it exists. */
+  /**
+   * Get the input stream of the package distribution resource if it exists.
+   */
   public InputStream getPackageDistribution(Closer closer) throws IOException {
     return packageDistribution == null ? null : packageDistribution.getStream(closer);
   }
@@ -407,7 +411,9 @@
       return addProgramFiles(Arrays.asList(files));
     }
 
-    /** Add program file resources. */
+    /**
+     * Add program file resources.
+     */
     public Builder addProgramFiles(Collection<Path> files) throws IOException {
       for (Path file : files) {
         addFile(file, Resource.Kind.PROGRAM);
@@ -422,7 +428,9 @@
       return addClasspathFiles(Arrays.asList(files));
     }
 
-    /** Add classpath file resources. */
+    /**
+     * Add classpath file resources.
+     */
     public Builder addClasspathFiles(Collection<Path> files) throws IOException {
       for (Path file : files) {
         addFile(file, Resource.Kind.CLASSPATH);
@@ -437,7 +445,9 @@
       return addLibraryFiles(Arrays.asList(files));
     }
 
-    /** Add library file resources. */
+    /**
+     * Add library file resources.
+     */
     public Builder addLibraryFiles(Collection<Path> files) throws IOException {
       for (Path file : files) {
         addFile(file, Resource.Kind.LIBRARY);
@@ -452,7 +462,9 @@
       return addDexProgramData(Arrays.asList(data));
     }
 
-    /** Add dex program-data. */
+    /**
+     * Add dex program-data.
+     */
     public Builder addDexProgramData(Collection<byte[]> data) {
       for (byte[] datum : data) {
         dexSources.add(InternalResource.fromBytes(Resource.Kind.PROGRAM, datum));
@@ -467,7 +479,9 @@
       return addClassProgramData(Arrays.asList(data));
     }
 
-    /** Add Java-bytecode program data. */
+    /**
+     * Add Java-bytecode program data.
+     */
     public Builder addClassProgramData(Collection<byte[]> data) {
       for (byte[] datum : data) {
         classSources.add(InternalResource.fromBytes(Resource.Kind.PROGRAM, datum));
@@ -475,36 +489,48 @@
       return this;
     }
 
-    /** Set proguard-map file. */
+    /**
+     * Set proguard-map file.
+     */
     public Builder setProguardMapFile(Path file) {
       proguardMap = file == null ? null : InternalResource.fromFile(null, file);
       return this;
     }
 
-    /** Set proguard-map data. */
+    /**
+     * Set proguard-map data.
+     */
     public Builder setProguardMapData(String content) {
       return setProguardMapData(content == null ? null : content.getBytes(StandardCharsets.UTF_8));
     }
 
-    /** Set proguard-map data. */
+    /**
+     * Set proguard-map data.
+     */
     public Builder setProguardMapData(byte[] content) {
       proguardMap = content == null ? null : InternalResource.fromBytes(null, content);
       return this;
     }
 
-    /** Set proguard-seeds data. */
+    /**
+     * Set proguard-seeds data.
+     */
     public Builder setProguardSeedsData(byte[] content) {
       proguardSeeds = content == null ? null : InternalResource.fromBytes(null, content);
       return this;
     }
 
-    /** Set the package-distribution file. */
+    /**
+     * Set the package-distribution file.
+     */
     public Builder setPackageDistributionFile(Path file) {
       packageDistribution = file == null ? null : InternalResource.fromFile(null, file);
       return this;
     }
 
-    /** Set the main-dex list file. */
+    /**
+     * Set the main-dex list file.
+     */
     public Builder setMainDexListFile(Path file) {
       mainDexList = file == null ? null : InternalResource.fromFile(null, file);
       return this;
diff --git a/src/main/java/com/android/tools/r8/utils/FieldSignatureEquivalence.java b/src/main/java/com/android/tools/r8/utils/FieldSignatureEquivalence.java
new file mode 100644
index 0000000..8006e6d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FieldSignatureEquivalence.java
@@ -0,0 +1,34 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.graph.DexField;
+import com.google.common.base.Equivalence;
+
+/**
+ * Implements an equivalence on {@link DexField} that does not take the holder into account.
+ *
+ * <p>Useful when comparing method implementations by their signature only.
+ */
+public class FieldSignatureEquivalence extends Equivalence<DexField> {
+
+  private static final FieldSignatureEquivalence THEINSTANCE = new FieldSignatureEquivalence();
+
+  private FieldSignatureEquivalence() {
+  }
+
+  public static FieldSignatureEquivalence get() {
+    return THEINSTANCE;
+  }
+
+  @Override
+  protected boolean doEquivalent(DexField a, DexField b) {
+    return a.name.equals(b.name) && a.type.equals(b.type);
+  }
+
+  @Override
+  protected int doHash(DexField field) {
+    return field.name.hashCode() * 31 + field.type.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/HashMapInt.java b/src/main/java/com/android/tools/r8/utils/HashMapInt.java
deleted file mode 100644
index 7e91644..0000000
--- a/src/main/java/com/android/tools/r8/utils/HashMapInt.java
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-// Hash map based on open addressing where keys are Objects and values are basic ints.
-// Provides: put, get, and size.
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Objects;
-
-public class HashMapInt<T> extends SimpleHashMap {
-
-  private Object[] keys;
-  private int[] values;
-
-  public HashMapInt() {
-    super();
-  }
-
-  public HashMapInt(int initialCapacity) {
-    super(initialCapacity);
-  }
-
-  public HashMapInt(int initialCapacity, double loadFactor) {
-    super(initialCapacity, loadFactor);
-  }
-
-  public void put(final T key, final int value) {
-    if (key == null) {
-      throw new RuntimeException("HashMapInt does not support null as key.");
-    }
-    ensureCapacity();
-    basePut(key, value);
-  }
-
-  boolean equals(Object one, Object other) {
-    return one.equals(other);
-  }
-
-  public int get(final T key) {
-    if (key == null) {
-      throw new RuntimeException("HashMapInt does not support null as key.");
-    }
-    int count = 1;
-    int index = firstProbe(key);
-    while (!equals(key, keys[index])) {
-      if (keys[index] == null) {
-        throw new RuntimeException("HashMapInt get only works if key is present.");
-      }
-      index = nextProbe(index, count++);
-    }
-    return values[index];
-  }
-
-  int firstProbe(T key) {
-    return firstProbe(key.hashCode());
-  }
-
-  public boolean containsKey(final T key) {
-    if (key == null) {
-      throw new RuntimeException("HashMapInt does not support null as key.");
-    }
-    int count = 1;
-    for (int index = firstProbe(key); ; index = nextProbe(index, count++)) {
-      Object k = keys[index];
-      if (k == null) {
-        return false;
-      }
-      if (equals(k, key)) {
-        return true;
-      }
-    }
-  }
-
-  private void basePut(final T key, final int value) {
-    int count = 1;
-    int index = firstProbe(key);
-    while ((keys[index] != null) && (keys[index] != key)) {
-      index = nextProbe(index, count++);
-    }
-    if (keys[index] == null) {
-      keys[index] = key;
-      incrementSize();
-    }
-    values[index] = value;
-  }
-
-  @SuppressWarnings("unchecked")
-  public Iterable<Integer> values() {
-    return () -> Arrays.stream(keys).filter(Objects::nonNull).map((key) -> get((T) key)).iterator();
-  }
-
-  @SuppressWarnings("unchecked")
-  public Iterable<T> keys() {
-    return () -> (Iterator<T>) Arrays.stream(keys).filter(Objects::nonNull).iterator();
-  }
-
-  @SuppressWarnings("unchecked")
-  void resize() {
-    final Object[] oldKeys = keys;
-    final int[] oldValues = values;
-    final int oldLength = length();
-    super.resize();
-    // Repopulate.
-    for (int index = 0; index < oldLength; index++) {
-      T key = (T) oldKeys[index];
-      if (key != null) {
-        basePut(key, oldValues[index]);
-      }
-    }
-  }
-
-  void initialize(final int length, final int limit) {
-    super.initialize(length, limit);
-    keys = new Object[length];
-    values = new int[length];
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/IdentityHashMapInt.java b/src/main/java/com/android/tools/r8/utils/IdentityHashMapInt.java
deleted file mode 100644
index 66e62ac..0000000
--- a/src/main/java/com/android/tools/r8/utils/IdentityHashMapInt.java
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-public class IdentityHashMapInt<T> extends HashMapInt<T> {
-
-  public IdentityHashMapInt() {
-    super();
-  }
-
-  public IdentityHashMapInt(int initialCapacity) {
-    super(initialCapacity);
-  }
-
-  public IdentityHashMapInt(int initialCapacity, double loadFactor) {
-    super(initialCapacity, loadFactor);
-  }
-
-  @Override
-  int firstProbe(T key) {
-    return firstProbe(System.identityHashCode(key));
-  }
-
-  @Override
-  boolean equals(Object one, Object other) {
-    return one == other;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/IntHashMap.java b/src/main/java/com/android/tools/r8/utils/IntHashMap.java
deleted file mode 100644
index 6a92316..0000000
--- a/src/main/java/com/android/tools/r8/utils/IntHashMap.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-// Hash map based on open addressing where keys are basic ints and values are Objects.
-// Provides: put, get, and size.
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Objects;
-import java.util.stream.IntStream;
-
-public class IntHashMap<T> extends SimpleHashMap {
-
-  private int[] keys;
-  private Object[] values;
-
-  public IntHashMap() {
-    super();
-  }
-
-  public IntHashMap(int initialCapacity) {
-    super(initialCapacity);
-  }
-
-  public IntHashMap(int initialCapacity, double loadFactor) {
-    super(initialCapacity, loadFactor);
-  }
-
-  public void put(final int key, final T value) {
-    if (value == null) {
-      throw new RuntimeException("IntHashMap does not support null as value.");
-    }
-    ensureCapacity();
-    basePut(key, value);
-  }
-
-  public T get(final int key) {
-    int count = 1;
-    int index = firstProbe(computeHash(key));
-    // Note that unused entries in keys is 0.
-    // That means we only need to check for value != null when key == 0.
-    while ((keys[index] != key) && (values[index] != null)) {
-      index = nextProbe(index, count++);
-    }
-    assert (keys[index] == key) || (values[index] == null);
-    @SuppressWarnings("unchecked")
-    T result = (T) values[index];
-    return result;
-  }
-
-  private void basePut(final int key, final Object value) {
-    assert value != null;
-    int count = 1;
-    int index = firstProbe(computeHash(key));
-    while ((values[index] != null) && (keys[index] != key)) {
-      index = nextProbe(index, count++);
-    }
-    if (values[index] == null) {
-      keys[index] = key;
-      incrementSize();
-    }
-    values[index] = value;
-    assert value.equals(get(key));
-  }
-
-  void resize() {
-    final int[] oldKeys = keys;
-    final Object[] oldValues = values;
-    final int oldLength = length();
-    super.resize();
-    // Repopulate.
-    for (int index = 0; index < oldLength; index++) {
-      Object value = oldValues[index];
-      if (value != null) {
-        basePut(oldKeys[index], value);
-      }
-    }
-  }
-
-  void initialize(final int length, final int limit) {
-    super.initialize(length, limit);
-    keys = new int[length];
-    values = new Object[length];
-  }
-
-  @SuppressWarnings("unchecked")
-  public Iterable<T> values() {
-    return () -> (Iterator<T>) Arrays.stream(values).filter(Objects::nonNull).iterator();
-  }
-
-  public Iterable<Integer> keys() {
-    if (get(0) != null) {
-      return () -> IntStream.concat(IntStream.of(0), Arrays.stream(keys).filter(i -> i != 0))
-          .iterator();
-    }
-    return () -> Arrays.stream(keys).filter(i -> i != 0 || get(i) != null).distinct().iterator();
-  }
-
-  // Thomas Wang, Integer Hash Functions.
-  // http://www.concentric.net/~Ttwang/tech/inthash.htm
-  private static int computeHash(final int key) {
-    int hash = key;
-    hash = ~hash + (hash << 15);  // hash = (hash << 15) - hash - 1;
-    hash = hash ^ (hash >> 12);
-    hash = hash + (hash << 2);
-    hash = hash ^ (hash >> 4);
-    hash = hash * 2057;  // hash = (hash + (hash << 3)) + (hash << 11);
-    hash = hash ^ (hash >> 16);
-    return hash & 0x3fffffff;
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/utils/IntIntHashMap.java b/src/main/java/com/android/tools/r8/utils/IntIntHashMap.java
deleted file mode 100644
index 03d4911..0000000
--- a/src/main/java/com/android/tools/r8/utils/IntIntHashMap.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-// Hash map based on open addressing where keys are positive basic ints and values are basic ints.
-// Provides: put, get, and size.
-
-import java.util.Arrays;
-
-public class IntIntHashMap extends SimpleHashMap {
-
-  private final int EMPTY_KEY = -1;
-
-  private int[] keys;
-  private int[] values;
-
-  public IntIntHashMap() {
-    super();
-  }
-
-  public IntIntHashMap(int initialCapacity) {
-    super(initialCapacity);
-  }
-
-  public IntIntHashMap(int initialCapacity, double loadFactor) {
-    super(initialCapacity, loadFactor);
-  }
-
-  public void put(final int key, final int value) {
-    if (key < 0) {
-      throw new RuntimeException("IntIntHashMap does not support negative ints as key.");
-    }
-    ensureCapacity();
-    basePut(key, value);
-  }
-
-  public int get(final int key) {
-    if (key < 0) {
-      throw new RuntimeException("IntIntHashMap does not support negative ints as key.");
-    }
-    int count = 1;
-    int index = firstProbe(key);
-    while (key != keys[index]) {
-      if (keys[index] == EMPTY_KEY) {
-        throw new RuntimeException("IntIntHashMap get only works if key is present.");
-      }
-      index = nextProbe(index, count++);
-    }
-    return values[index];
-  }
-
-  public boolean containsKey(final int key) {
-    if (key < 0) {
-      throw new RuntimeException("IntIntHashMap does not support negative ints as key.");
-    }
-    int count = 1;
-    for (int index = firstProbe(key); ; index = nextProbe(index, count++)) {
-      int k = keys[index];
-      if (k == EMPTY_KEY) {
-        return false;
-      }
-      if (k == key) {
-        return true;
-      }
-    }
-  }
-
-  private void basePut(final int key, final int value) {
-    int count = 1;
-    int index = firstProbe(key);
-    while ((keys[index] != EMPTY_KEY) && (keys[index] != key)) {
-      index = nextProbe(index, count++);
-    }
-    if (keys[index] != key) {
-      incrementSize();
-      keys[index] = key;
-    }
-    values[index] = value;
-  }
-
-  void resize() {
-    final int[] oldKeys = keys;
-    final int[] oldValues = values;
-    final int oldLength = length();
-    super.resize();
-    // Repopulate.
-    for (int index = 0; index < oldLength; index++) {
-      int key = oldKeys[index];
-      if (key != EMPTY_KEY) {
-        basePut(key, oldValues[index]);
-      }
-    }
-  }
-
-  void initialize(final int length, final int limit) {
-    super.initialize(length, limit);
-    keys = new int[length];
-    Arrays.fill(keys, EMPTY_KEY);
-    values = new int[length];
-  }
-
-
-  @SuppressWarnings("unchecked")
-  public Iterable<Integer> values() {
-    return () -> Arrays.stream(keys).filter(k -> k > EMPTY_KEY).map(this::get).iterator();
-  }
-
-  @SuppressWarnings("unchecked")
-  public Iterable<Integer> keys() {
-    return () -> Arrays.stream(keys).filter(k -> k > EMPTY_KEY).iterator();
-  }
-}
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 d3b80e3..1fd72f8 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -33,6 +33,7 @@
   // Skipping optimizations.
   public boolean skipDebugInfoOpt = false;
   public boolean skipDebugLineNumberOpt = false;
+  public boolean skipClassMerging = true;
 
   public boolean lazyClasspathLoading = false;
   public boolean lazyLibraryLoading = false;
diff --git a/src/main/java/com/android/tools/r8/utils/MethodJavaSignatureEquivalence.java b/src/main/java/com/android/tools/r8/utils/MethodJavaSignatureEquivalence.java
new file mode 100644
index 0000000..1b60380
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/MethodJavaSignatureEquivalence.java
@@ -0,0 +1,36 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.google.common.base.Equivalence;
+
+/**
+ * Implements an equivalence on {@link DexMethod} that does not take the holder nor return type into
+ * account.
+ *
+ * <p>Useful when deciding whether methods shadow each other wrt. Java semantics.
+ */
+public class MethodJavaSignatureEquivalence extends Equivalence<DexMethod> {
+
+  private static final MethodJavaSignatureEquivalence THEINSTANCE
+      = new MethodJavaSignatureEquivalence();
+
+  private MethodJavaSignatureEquivalence() {
+  }
+
+  public static MethodJavaSignatureEquivalence get() {
+    return THEINSTANCE;
+  }
+
+  @Override
+  protected boolean doEquivalent(DexMethod a, DexMethod b) {
+    return a.name.equals(b.name) && a.proto.parameters.equals(b.proto.parameters);
+  }
+
+  @Override
+  protected int doHash(DexMethod method) {
+    return method.name.hashCode() * 31 + method.proto.parameters.hashCode();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/utils/MethodSignatureEquivalence.java b/src/main/java/com/android/tools/r8/utils/MethodSignatureEquivalence.java
index 86ce165..c47494a 100644
--- a/src/main/java/com/android/tools/r8/utils/MethodSignatureEquivalence.java
+++ b/src/main/java/com/android/tools/r8/utils/MethodSignatureEquivalence.java
@@ -3,13 +3,11 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.utils;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.google.common.base.Equivalence;
 
 /**
- * Implements an equivalence on {@link DexEncodedMethod} that does not take the holder into
- * account.
+ * Implements an equivalence on {@link DexMethod} that does not take the holder into account.
  *
  * <p>Useful when comparing method implementations by their signature only.
  */
diff --git a/src/test/debugTestResourcesJava8/DebugDefaultMethod.java b/src/test/debugTestResourcesJava8/DebugDefaultMethod.java
new file mode 100644
index 0000000..db216b3
--- /dev/null
+++ b/src/test/debugTestResourcesJava8/DebugDefaultMethod.java
@@ -0,0 +1,35 @@
+// 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.
+
+public class DebugDefaultMethod {
+
+  interface I {
+    default void doSomething(String msg) {
+      String name = getClass().getName();
+      System.out.println(name + ": " + msg);
+    }
+  }
+
+  static class DefaultImpl implements I {
+  }
+
+  static class OverrideImpl implements I {
+
+    @Override
+    public void doSomething(String msg) {
+      String newMsg = "OVERRIDE" + msg;
+      System.out.println(newMsg);
+    }
+  }
+
+  private static void testDefaultMethod(I i) {
+    i.doSomething("Test");
+  }
+
+  public static void main(String[] args) {
+    testDefaultMethod(new DefaultImpl());
+    testDefaultMethod(new OverrideImpl());
+  }
+
+}
diff --git a/src/test/debugTestResourcesJava8/DebugLambda.java b/src/test/debugTestResourcesJava8/DebugLambda.java
new file mode 100644
index 0000000..c3b8695
--- /dev/null
+++ b/src/test/debugTestResourcesJava8/DebugLambda.java
@@ -0,0 +1,23 @@
+// 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.
+
+public class DebugLambda {
+
+  interface I {
+    int getInt();
+  }
+
+  private static void printInt(I i) {
+    System.out.println(i.getInt());
+  }
+
+  public static void testLambda(int i, int j) {
+    printInt(() -> i + j);
+  }
+
+  public static void main(String[] args) {
+    DebugLambda.testLambda(5, 10);
+  }
+
+}
diff --git a/src/test/examples/classmerging/ClassWithConflictingMethod.java b/src/test/examples/classmerging/ClassWithConflictingMethod.java
new file mode 100644
index 0000000..b4bf9d7
--- /dev/null
+++ b/src/test/examples/classmerging/ClassWithConflictingMethod.java
@@ -0,0 +1,11 @@
+// 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 classmerging;
+
+public class ClassWithConflictingMethod {
+
+  public static int conflict(ConflictingInterface item) {
+    return 123;
+  }
+}
diff --git a/src/test/examples/classmerging/ConflictingInterface.java b/src/test/examples/classmerging/ConflictingInterface.java
new file mode 100644
index 0000000..6202e3c
--- /dev/null
+++ b/src/test/examples/classmerging/ConflictingInterface.java
@@ -0,0 +1,9 @@
+// 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 classmerging;
+
+public interface ConflictingInterface {
+
+  public String method();
+}
diff --git a/src/test/examples/classmerging/ConflictingInterfaceImpl.java b/src/test/examples/classmerging/ConflictingInterfaceImpl.java
new file mode 100644
index 0000000..e46edaf
--- /dev/null
+++ b/src/test/examples/classmerging/ConflictingInterfaceImpl.java
@@ -0,0 +1,12 @@
+// 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 classmerging;
+
+public class ConflictingInterfaceImpl implements ConflictingInterface {
+
+  @Override
+  public String method() {
+    return "ConflictingInterfaceImpl::method";
+  }
+}
diff --git a/src/test/examples/classmerging/GenericAbstractClass.java b/src/test/examples/classmerging/GenericAbstractClass.java
new file mode 100644
index 0000000..42620f6
--- /dev/null
+++ b/src/test/examples/classmerging/GenericAbstractClass.java
@@ -0,0 +1,13 @@
+// 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 classmerging;
+
+public abstract class GenericAbstractClass<T> {
+
+  public abstract T method();
+
+  public T otherMethod() {
+    return null;
+  }
+}
diff --git a/src/test/examples/classmerging/GenericAbstractClassImpl.java b/src/test/examples/classmerging/GenericAbstractClassImpl.java
new file mode 100644
index 0000000..931465e
--- /dev/null
+++ b/src/test/examples/classmerging/GenericAbstractClassImpl.java
@@ -0,0 +1,17 @@
+// 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 classmerging;
+
+public class GenericAbstractClassImpl extends GenericAbstractClass<String> {
+
+  @Override
+  public String method() {
+    return "Hello from GenericAbstractClassImpl";
+  }
+
+  @Override
+  public String otherMethod() {
+    return "otherMethod";
+  }
+}
diff --git a/src/test/examples/classmerging/GenericInterface.java b/src/test/examples/classmerging/GenericInterface.java
new file mode 100644
index 0000000..cda0b32
--- /dev/null
+++ b/src/test/examples/classmerging/GenericInterface.java
@@ -0,0 +1,9 @@
+// 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 classmerging;
+
+public interface GenericInterface<T> {
+
+  T method();
+}
diff --git a/src/test/examples/classmerging/GenericInterfaceImpl.java b/src/test/examples/classmerging/GenericInterfaceImpl.java
new file mode 100644
index 0000000..6a14107
--- /dev/null
+++ b/src/test/examples/classmerging/GenericInterfaceImpl.java
@@ -0,0 +1,12 @@
+// 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 classmerging;
+
+public class GenericInterfaceImpl implements GenericInterface<String> {
+
+  @Override
+  public String method() {
+    return "method";
+  }
+}
diff --git a/src/test/examples/classmerging/OtherClassWithConflictingMethod.java b/src/test/examples/classmerging/OtherClassWithConflictingMethod.java
new file mode 100644
index 0000000..cd7efd1
--- /dev/null
+++ b/src/test/examples/classmerging/OtherClassWithConflictingMethod.java
@@ -0,0 +1,11 @@
+// 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 classmerging;
+
+public class OtherClassWithConflictingMethod {
+
+  public static int conflict(ConflictingInterfaceImpl item) {
+    return 321;
+  }
+}
diff --git a/src/test/examples/classmerging/Outer.java b/src/test/examples/classmerging/Outer.java
new file mode 100644
index 0000000..652f794
--- /dev/null
+++ b/src/test/examples/classmerging/Outer.java
@@ -0,0 +1,26 @@
+// 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 classmerging;
+
+class Outer {
+
+  /**
+   * This class is package private to trigger the generation of bridge methods
+   * for the visibility change of methods from public subtypes.
+   */
+  class SuperClass {
+
+    public String method() {
+      return "Method in SuperClass.";
+    }
+  }
+
+  public class SubClass extends SuperClass {
+    // Intentionally left empty.
+  }
+
+  public SubClass getInstance() {
+    return new SubClass();
+  }
+}
diff --git a/src/test/examples/classmerging/SubClass.java b/src/test/examples/classmerging/SubClass.java
new file mode 100644
index 0000000..b6a9054
--- /dev/null
+++ b/src/test/examples/classmerging/SubClass.java
@@ -0,0 +1,22 @@
+// 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 classmerging;
+
+public class SubClass extends SuperClass {
+
+  private int field;
+
+  public SubClass(int field) {
+    this(field, field + 100);
+  }
+
+  public SubClass(int one, int other) {
+    super(one);
+    field = other;
+  }
+
+  public String toString() {
+    return "is " + field + " " + getField();
+  }
+}
diff --git a/src/test/examples/classmerging/SubClassThatReferencesSuperMethod.java b/src/test/examples/classmerging/SubClassThatReferencesSuperMethod.java
new file mode 100644
index 0000000..c12804c
--- /dev/null
+++ b/src/test/examples/classmerging/SubClassThatReferencesSuperMethod.java
@@ -0,0 +1,12 @@
+// 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 classmerging;
+
+public class SubClassThatReferencesSuperMethod extends SuperClassWithReferencedMethod {
+
+  @Override
+  public String referencedMethod() {
+    return "From sub: " + super.referencedMethod();
+  }
+}
diff --git a/src/test/examples/classmerging/SuperClass.java b/src/test/examples/classmerging/SuperClass.java
new file mode 100644
index 0000000..b7c9207
--- /dev/null
+++ b/src/test/examples/classmerging/SuperClass.java
@@ -0,0 +1,17 @@
+// 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 classmerging;
+
+public class SuperClass {
+
+  private final int field;
+
+  public SuperClass(int field) {
+    this.field = field;
+  }
+
+  public int getField() {
+    return field;
+  }
+}
diff --git a/src/test/examples/classmerging/SuperClassWithReferencedMethod.java b/src/test/examples/classmerging/SuperClassWithReferencedMethod.java
new file mode 100644
index 0000000..8d4e7b5
--- /dev/null
+++ b/src/test/examples/classmerging/SuperClassWithReferencedMethod.java
@@ -0,0 +1,11 @@
+// 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 classmerging;
+
+public class SuperClassWithReferencedMethod {
+
+  public String referencedMethod() {
+    return "From Super";
+  }
+}
diff --git a/src/test/examples/classmerging/Test.java b/src/test/examples/classmerging/Test.java
new file mode 100644
index 0000000..6d5c51f
--- /dev/null
+++ b/src/test/examples/classmerging/Test.java
@@ -0,0 +1,34 @@
+// 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 classmerging;
+
+public class Test {
+
+  public static void main(String... args) {
+    GenericInterface iface = new GenericInterfaceImpl();
+    callMethodOnIface(iface);
+    GenericAbstractClass clazz = new GenericAbstractClassImpl();
+    callMethodOnAbstractClass(clazz);
+    ConflictingInterfaceImpl impl = new ConflictingInterfaceImpl();
+    callMethodOnIface(impl);
+    System.out.println(new SubClassThatReferencesSuperMethod().referencedMethod());
+    System.out.println(new Outer().getInstance().method());
+    System.out.println(new SubClass(42));
+  }
+
+  private static void callMethodOnIface(GenericInterface iface) {
+    System.out.println(iface.method());
+  }
+
+  private static void callMethodOnAbstractClass(GenericAbstractClass clazz) {
+    System.out.println(clazz.method());
+    System.out.println(clazz.otherMethod());
+  }
+
+  private static void callMethodOnIface(ConflictingInterface iface) {
+    System.out.println(iface.method());
+    System.out.println(ClassWithConflictingMethod.conflict(null));
+    System.out.println(OtherClassWithConflictingMethod.conflict(null));
+  }
+}
diff --git a/src/test/examples/classmerging/keep-rules.txt b/src/test/examples/classmerging/keep-rules.txt
new file mode 100644
index 0000000..58b262b
--- /dev/null
+++ b/src/test/examples/classmerging/keep-rules.txt
@@ -0,0 +1,12 @@
+# Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class classmerging.Test {
+  public static void main(...);
+}
+
+# allow access modification to enable minification
+-allowaccessmodification
diff --git a/src/test/examplesAndroidN/interfacemethods/DefaultMethods.java b/src/test/examplesAndroidN/interfacemethods/DefaultMethods.java
index 49e792c..ef49f4e 100644
--- a/src/test/examplesAndroidN/interfacemethods/DefaultMethods.java
+++ b/src/test/examplesAndroidN/interfacemethods/DefaultMethods.java
@@ -6,7 +6,24 @@
 
 public class DefaultMethods {
 
+  interface I3 {
+    default int getValue() {
+      return 1;
+    }
+
+  }
+
+  static class C3 {
+    public int getValue() {
+      return 2;
+    }
+  }
+
+  static class C4 extends C3 implements I3 {
+  }
+
   public static void main(String[] args) {
     new C2().d1();
+    System.out.println(new C4().getValue());
   }
 }
diff --git a/src/test/examplesAndroidO/invokecustom/keep-rules.txt b/src/test/examplesAndroidO/invokecustom/keep-rules.txt
index 52fa2a7..bcefc4b 100644
--- a/src/test/examplesAndroidO/invokecustom/keep-rules.txt
+++ b/src/test/examplesAndroidO/invokecustom/keep-rules.txt
@@ -8,7 +8,7 @@
   public static void main(...);
 }
 
--keepclassmembers  class * {
+-keepclasseswithmembers  class * {
   *** targetMethodTest*(...);
 }
 
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidNTest.java
new file mode 100644
index 0000000..7fb4bcf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidNTest.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.D8Command.Builder;
+import com.android.tools.r8.errors.CompilationError;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.function.UnaryOperator;
+
+public class D8RunExamplesAndroidNTest extends RunExamplesAndroidNTest<D8Command.Builder> {
+
+  class D8TestRunner extends TestRunner {
+
+    D8TestRunner(String testName, String packageName, String mainClass) {
+      super(testName, packageName, mainClass);
+    }
+
+    @Override
+    TestRunner withMinApiLevel(int minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+    }
+
+    @Override
+    void build(Path inputFile, Path out) throws Throwable {
+      D8Command.Builder builder = D8Command.builder();
+      for (UnaryOperator<Builder> transformation : builderTransformations) {
+        builder = transformation.apply(builder);
+      }
+      builder.addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(builder.getMinApiLevel())));
+      D8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+      try {
+        ToolHelper.runD8(command, this::combinedOptionConsumer);
+      } catch (RuntimeException re) {
+        throw re instanceof CompilationError ? re : re.getCause();
+      }
+    }
+  }
+
+  @Override
+  TestRunner test(String testName, String packageName, String mainClass) {
+    return new D8TestRunner(testName, packageName, mainClass);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
index 1335970..13c38ca 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidNTest.java
@@ -4,150 +4,40 @@
 
 package com.android.tools.r8;
 
-import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
-import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.ToolHelper.DexVm;
-import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.dex.Constants;
-import com.android.tools.r8.errors.CompilationError;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.OffOrAuto;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
 import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ExecutionException;
-import java.util.function.Consumer;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
+import java.util.function.UnaryOperator;
 
-public class R8RunExamplesAndroidNTest {
+public class R8RunExamplesAndroidNTest extends RunExamplesAndroidNTest<R8Command.Builder> {
 
-  private static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR;
+  class R8TestRunner extends TestRunner {
 
-  private static Map<DexVm, List<String>> failsOn =
-      ImmutableMap.of(
-          DexVm.ART_4_4_4,
-          ImmutableList.of(
-              // Dex version not supported
-              "staticinterfacemethods", "defaultmethods"),
-          DexVm.ART_5_1_1,
-          ImmutableList.of(
-              // Dex version not supported
-              "staticinterfacemethods", "defaultmethods"),
-          DexVm.ART_6_0_1,
-          ImmutableList.of(
-              // Dex version not supported
-              "staticinterfacemethods", "defaultmethods"),
-          DexVm.ART_7_0_0,
-          ImmutableList.of(),
-          DexVm.ART_DEFAULT,
-          ImmutableList.of());
-
-  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
-  @Rule public ExpectedException thrown = ExpectedException.none();
-
-  @Test
-  public void staticInterfaceMethods() throws Throwable {
-    doTest(
-        "staticinterfacemethods",
-        "interfacemethods",
-        "StaticInterfaceMethods",
-        Constants.ANDROID_N_API,
-        options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
-  }
-
-  @Test
-  public void staticInterfaceMethodsErrorDueToMinSdk() throws Throwable {
-    thrown.expect(CompilationError.class);
-    doTest(
-        "staticinterfacemethods-error-due-to-min-sdk",
-        "interfacemethods",
-        "StaticInterfaceMethods");
-  }
-
-  @Test
-  public void defaultMethods() throws Throwable {
-    doTest(
-        "defaultmethods",
-        "interfacemethods",
-        "DefaultMethods",
-        Constants.ANDROID_N_API,
-        options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
-  }
-
-  @Test
-  public void defaultMethodsErrorDueToMinSdk() throws Throwable {
-    thrown.expect(CompilationError.class);
-    doTest("defaultmethods-error-due-to-min-sdk", "interfacemethods", "DefaultMethods");
-  }
-
-  private void doTest(String testName, String packageName, String className) throws Throwable {
-    doTest(testName, packageName, className, R8Command.builder(), options -> {});
-  }
-
-  private void doTest(
-      String testName,
-      String packageName,
-      String className,
-      int minSdk,
-      Consumer<InternalOptions> optionsConsumer)
-      throws Throwable {
-    doTest(
-        testName,
-        packageName,
-        className,
-        R8Command.builder().setMinApiLevel(minSdk),
-        optionsConsumer);
-  }
-
-  public void doTest(
-      String testName,
-      String packageName,
-      String className,
-      R8Command.Builder builder,
-      Consumer<InternalOptions> optionsConsumer)
-      throws Throwable {
-    String mainClass = packageName + "." + className;
-    Path inputFile = Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION);
-    Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
-
-    try {
-      ToolHelper.runR8(
-          builder.addProgramFiles(inputFile).setOutputPath(out).build(), optionsConsumer);
-    } catch (ExecutionException e) {
-      throw e.getCause();
+    R8TestRunner(String testName, String packageName, String mainClass) {
+      super(testName, packageName, mainClass);
     }
 
-    if (!ToolHelper.artSupported()) {
-      return;
+    @Override
+    TestRunner withMinApiLevel(int minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
     }
 
-    boolean expectedToFail = false;
-    if (failsOn.containsKey(ToolHelper.getDexVm())
-        && failsOn.get(ToolHelper.getDexVm()).contains(testName)) {
-      expectedToFail = true;
-      thrown.expect(Throwable.class);
+    @Override
+    void build(Path inputFile, Path out) throws Throwable {
+      try {
+        R8Command.Builder builder = R8Command.builder();
+        for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
+          builder = transformation.apply(builder);
+        }
+        R8Command command = builder.addProgramFiles(inputFile).setOutputPath(out).build();
+        ToolHelper.runR8(command, this::combinedOptionConsumer);
+      } catch (ExecutionException e) {
+        throw e.getCause();
+      }
     }
-    String output = ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass);
-    if (!expectedToFail) {
-      ProcessResult javaResult =
-          ToolHelper.runJava(ImmutableList.of(inputFile.toString()), mainClass);
-      assertEquals("JVM run failed", javaResult.exitCode, 0);
-      assertTrue(
-          "JVM output does not match art output.\n\tjvm: "
-              + javaResult.stdout
-              + "\n\tart: "
-              + output,
-          output.equals(javaResult.stdout));
-    }
+  }
+
+  @Override
+  TestRunner test(String testName, String packageName, String mainClass) {
+    return new R8TestRunner(testName, packageName, mainClass);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
index 3fe2173..8c5e094 100644
--- a/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunSmaliTestsTest.java
@@ -78,13 +78,14 @@
             "java.lang.NullPointerException: Attempt to read from field " +
                 "'Test Test.a' on a null object reference\n"},
         {"merge-blocks-regression", "java.lang.NullPointerException: Attempt to invoke virtual"
-            +" method 'Test Test.bW_()' on a null object reference\n"},
+            + " method 'Test Test.bW_()' on a null object reference\n"},
         {"self-is-catch-block", "100\n-1\n"},
         {"infinite-loop", ""},
         {"regression/33336471",
             "START\n0\n2\nLOOP\n1\n2\nLOOP\n2\n2\nDONE\n" +
                 "START\n0\n2\nLOOP\n1\n2\nLOOP\n2\n2\nDONE\n"},
         {"regression/33846227", ""},
+        {"illegal-invokes", "ICCE\nICCE\n"},
     });
   }
 
@@ -123,7 +124,8 @@
         thrown.expect(Throwable.class);
       }
       output =
-          ToolHelper.checkArtOutputIdentical(originalDexFile.toString(), generated, mainClass, null);
+          ToolHelper
+              .checkArtOutputIdentical(originalDexFile.toString(), generated, mainClass, null);
     }
     assertEquals(expectedOutput, output);
   }
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
new file mode 100644
index 0000000..cb28cc4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidNTest.java
@@ -0,0 +1,157 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+import static com.android.tools.r8.utils.FileUtils.ZIP_EXTENSION;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.errors.CompilationError;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.OffOrAuto;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Consumer;
+import java.util.function.UnaryOperator;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+public abstract class RunExamplesAndroidNTest<B> {
+
+  private static final String EXAMPLE_DIR = ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR;
+
+  abstract class TestRunner {
+    final String testName;
+    final String packageName;
+    final String mainClass;
+
+    final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
+    final List<UnaryOperator<B>> builderTransformations = new ArrayList<>();
+
+    TestRunner(String testName, String packageName, String mainClass) {
+      this.testName = testName;
+      this.packageName = packageName;
+      this.mainClass = mainClass;
+    }
+
+    TestRunner withOptionConsumer(Consumer<InternalOptions> consumer) {
+      optionConsumers.add(consumer);
+      return this;
+    }
+
+    TestRunner withInterfaceMethodDesugaring(OffOrAuto behavior) {
+      return withOptionConsumer(o -> o.interfaceMethodDesugaring = behavior);
+    }
+
+    TestRunner withBuilderTransformation(UnaryOperator<B> builderTransformation) {
+      builderTransformations.add(builderTransformation);
+      return this;
+    }
+
+    void combinedOptionConsumer(InternalOptions options) {
+      for (Consumer<InternalOptions> consumer : optionConsumers) {
+        consumer.accept(options);
+      }
+    }
+
+    void run() throws Throwable {
+      String qualifiedMainClass = packageName + "." + mainClass;
+      Path inputFile = Paths.get(EXAMPLE_DIR, packageName + JAR_EXTENSION);
+      Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
+
+      build(inputFile, out);
+
+      if (!ToolHelper.artSupported()) {
+        return;
+      }
+
+      boolean expectedToFail = false;
+      if (failsOn.containsKey(ToolHelper.getDexVm())
+          && failsOn.get(ToolHelper.getDexVm()).contains(testName)) {
+        expectedToFail = true;
+        thrown.expect(Throwable.class);
+      }
+      String output = ToolHelper.runArtNoVerificationErrors(out.toString(), qualifiedMainClass);
+      if (!expectedToFail) {
+        ProcessResult javaResult =
+            ToolHelper.runJava(ImmutableList.of(inputFile.toString()), qualifiedMainClass);
+        assertEquals("JVM run failed", javaResult.exitCode, 0);
+        assertTrue(
+            "JVM output does not match art output.\n\tjvm: "
+                + javaResult.stdout
+                + "\n\tart: "
+                + output,
+            output.equals(javaResult.stdout));
+      }
+    }
+
+    abstract TestRunner withMinApiLevel(int minApiLevel);
+
+    abstract void build(Path inputFile, Path out) throws Throwable;
+  }
+
+  private static Map<DexVm, List<String>> failsOn =
+      ImmutableMap.of(
+          DexVm.ART_4_4_4,
+          ImmutableList.of(),
+          DexVm.ART_5_1_1,
+          ImmutableList.of(),
+          DexVm.ART_6_0_1,
+          ImmutableList.of(),
+          DexVm.ART_7_0_0,
+          ImmutableList.of(),
+          DexVm.ART_DEFAULT,
+          ImmutableList.of());
+
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Rule public ExpectedException thrown = ExpectedException.none();
+
+  @Test
+  public void staticInterfaceMethods() throws Throwable {
+    test("staticinterfacemethods", "interfacemethods", "StaticInterfaceMethods")
+        .withMinApiLevel(Constants.ANDROID_K_API)
+        .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .run();
+  }
+
+  @Test
+  public void staticInterfaceMethodsErrorDueToMinSdk() throws Throwable {
+    thrown.expect(CompilationError.class);
+    test("staticinterfacemethods-error-due-to-min-sdk", "interfacemethods",
+        "StaticInterfaceMethods")
+        .run();
+  }
+
+  @Test
+  public void defaultMethods() throws Throwable {
+    test("defaultmethods", "interfacemethods", "DefaultMethods")
+        .withMinApiLevel(Constants.ANDROID_K_API)
+        .withInterfaceMethodDesugaring(OffOrAuto.Auto)
+        .run();
+  }
+
+  @Test
+  public void defaultMethodsErrorDueToMinSdk() throws Throwable {
+    thrown.expect(CompilationError.class);
+    test("defaultmethods-error-due-to-min-sdk", "interfacemethods",
+        "DefaultMethods")
+        .run();
+  }
+
+  abstract TestRunner test(String testName, String packageName, String mainClass);
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
new file mode 100644
index 0000000..0d0d6f0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -0,0 +1,75 @@
+package com.android.tools.r8.classmerging;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.DexInspector;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.ExecutionException;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class ClassMergingTest {
+
+  private static final Path EXAMPLE_JAR = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
+      .resolve("classmerging.jar");
+  private static final Path EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_DIR)
+      .resolve("classmerging").resolve("keep-rules.txt");
+
+  @Rule
+  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  @Before
+  public void runR8()
+      throws IOException, ProguardRuleParserException, ExecutionException, CompilationException {
+    // Disable access modification, as it otherwise is difficult to test visibility bridge methods.
+    ToolHelper.runR8(
+        R8Command.builder()
+            .setOutputPath(Paths.get(temp.getRoot().getCanonicalPath()))
+            .addProgramFiles(EXAMPLE_JAR)
+            .addProguardConfigurationFiles(EXAMPLE_KEEP)
+            .setMinification(false)
+            .build(), o -> {
+          o.allowAccessModification = false;
+          o.skipClassMerging = false;
+        });
+    inspector = new DexInspector(
+        Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
+  }
+
+  private DexInspector inspector;
+
+  @Test
+  public void testClassesHaveBeenMerged() throws IOException, ExecutionException {
+    // GenericInterface should be merged into GenericInterfaceImpl.
+    Assert.assertFalse(inspector.clazz("classmerging.GenericInterface").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
+    Assert.assertFalse(inspector.clazz("classmerging.GenericAbstractClass").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.GenericInterfaceImpl").isPresent());
+    Assert.assertFalse(inspector.clazz("classmerging.Outer$SuperClass").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.Outer$SubClass").isPresent());
+    Assert.assertFalse(inspector.clazz("classmerging.SuperClass").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.SubClass").isPresent());
+  }
+
+
+  @Test
+  public void testConflictWasDetected() throws IOException, ExecutionException {
+    Assert.assertTrue(inspector.clazz("classmerging.ConflictingInterface").isPresent());
+    Assert.assertTrue(inspector.clazz("classmerging.ConflictingInterfaceImpl").isPresent());
+  }
+
+  @Test
+  public void testSuperCallWasDetected() throws IOException, ExecutionException {
+    Assert.assertTrue(inspector.clazz("classmerging.SuperClassWithReferencedMethod").isPresent());
+    Assert
+        .assertTrue(inspector.clazz("classmerging.SubClassThatReferencesSuperMethod").isPresent());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
index 228b9b3..4131b86 100644
--- a/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
+++ b/src/test/java/com/android/tools/r8/compatdx/CompatDxTests.java
@@ -100,6 +100,11 @@
     runDexer(MainDexListTests.getTwoLargeClassesAppPath().toString());
   }
 
+  @Test
+  public void keepClassesTest() throws IOException {
+    runDexerWithOutput("out.zip", "--keep-classes", EXAMPLE_JAR_FILE1);
+  }
+
   private void runDexer(String... args) throws IOException {
     runDexerWithOutput("", args);
   }
@@ -108,12 +113,24 @@
     runDexerWithOutput(null, args);
   }
 
+  private Path getOutputD8() {
+    return temp.getRoot().toPath().resolve("d8-out");
+  }
+
+  private Path getOutputDX() {
+    return temp.getRoot().toPath().resolve("dx-out");
+  }
+
   private void runDexerWithOutput(String out, String... args) throws IOException {
     Path d8Out = null;
     Path dxOut = null;
     if (out != null) {
-      d8Out = temp.newFolder("d8-out").toPath().resolve(out);
-      dxOut = temp.newFolder("dx-out").toPath().resolve(out);
+      Path baseD8 = getOutputD8();
+      Path baseDX = getOutputDX();
+      Files.createDirectory(baseD8);
+      Files.createDirectory(baseDX);
+      d8Out = baseD8.resolve(out);
+      dxOut = baseDX.resolve(out);
       assertNotEquals(d8Out, dxOut);
     }
 
@@ -166,7 +183,8 @@
       assertTrue("Expected dx output to contain " + entry, dxContent.contains(entry));
     }
     for (String entry : dxContent) {
-      if (FileUtils.isDexFile(Paths.get(entry))) {
+      Path path = Paths.get(entry);
+      if (FileUtils.isDexFile(path) || FileUtils.isClassFile(path)) {
         assertTrue("Expected d8 output to contain " + entry, d8Content.contains(entry));
       }
     }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 0b042a9..5f9a95c 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -4,11 +4,12 @@
 package com.android.tools.r8.debug;
 
 import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.D8;
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.dex.Constants;
+import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
 import java.io.File;
 import java.nio.file.Path;
@@ -78,11 +79,14 @@
 
   private static final Path DEBUGGEE_JAR = Paths
       .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources.jar");
+  private static final Path DEBUGGEE_JAVA8_JAR = Paths
+      .get(ToolHelper.BUILD_DIR, "test", "debug_test_resources_java8.jar");
 
   @ClassRule
   public static TemporaryFolder temp = new TemporaryFolder();
   private static Path jdwpDexD8 = null;
   private static Path debuggeeDexD8 = null;
+  private static Path debuggeeJava8DexD8 = null;
 
   @Rule
   public TestName testName = new TestName();
@@ -95,7 +99,7 @@
       Path jdwpJar = ToolHelper.getJdwpTestsJarPath(minSdk);
       Path dexOutputDir = temp.newFolder("d8-jdwp-jar").toPath();
       jdwpDexD8 = dexOutputDir.resolve("classes.dex");
-      D8.run(
+      ToolHelper.runD8(
           D8Command.builder()
               .addProgramFiles(jdwpJar)
               .setOutputPath(dexOutputDir)
@@ -106,7 +110,7 @@
     {
       Path dexOutputDir = temp.newFolder("d8-debuggee-jar").toPath();
       debuggeeDexD8 = dexOutputDir.resolve("classes.dex");
-      D8.run(
+      ToolHelper.runD8(
           D8Command.builder()
               .addProgramFiles(DEBUGGEE_JAR)
               .setOutputPath(dexOutputDir)
@@ -114,6 +118,25 @@
               .setMode(CompilationMode.DEBUG)
               .build());
     }
+    {
+      Path dexOutputDir = temp.newFolder("d8-debuggee-java8-jar").toPath();
+      debuggeeJava8DexD8 = dexOutputDir.resolve("classes.dex");
+      ToolHelper.runD8(
+          D8Command.builder()
+              .addProgramFiles(DEBUGGEE_JAVA8_JAR)
+              .setOutputPath(dexOutputDir)
+              .setMinApiLevel(minSdk)
+              .setMode(CompilationMode.DEBUG)
+              .build(),
+          options -> {
+            // Enable desugaring for preN runtimes
+            options.interfaceMethodDesugaring = OffOrAuto.Auto;
+          });
+    }
+  }
+
+  protected final boolean supportsDefaultMethod() {
+    return ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()) >= Constants.ANDROID_N_API;
   }
 
   protected final void runDebugTest(String debuggeeClass, JUnit3Wrapper.Command... commands)
@@ -123,6 +146,22 @@
 
   protected final void runDebugTest(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
       throws Throwable {
+    runDebugTest(debuggeeDexD8, debuggeeClass, commands);
+  }
+
+  protected final void runDebugTestJava8(String debuggeeClass, JUnit3Wrapper.Command... commands)
+      throws Throwable {
+    runDebugTestJava8(debuggeeClass, Arrays.asList(commands));
+  }
+
+  protected final void runDebugTestJava8(String debuggeeClass, List<JUnit3Wrapper.Command> commands)
+      throws Throwable {
+    runDebugTest(debuggeeJava8DexD8, debuggeeClass, commands);
+  }
+
+  private void runDebugTest(Path debuggeeExec, String debuggeeClass,
+      List<JUnit3Wrapper.Command> commands)
+      throws Throwable {
     // Skip test due to unsupported runtime.
     Assume.assumeTrue("Skipping test " + testName.getMethodName() + " because ART is not supported",
         ToolHelper.artSupported());
@@ -131,7 +170,7 @@
             .getDexVm(), UNSUPPORTED_ART_VERSIONS.contains(ToolHelper.getDexVm()));
 
     // Run with ART.
-    String[] paths = new String[]{jdwpDexD8.toString(), debuggeeDexD8.toString()};
+    String[] paths = new String[]{jdwpDexD8.toString(), debuggeeExec.toString()};
     new JUnit3Wrapper(debuggeeClass, paths, commands).runBare();
   }
 
@@ -172,6 +211,10 @@
     return new JUnit3Wrapper.Command.StepCommand(stepDepth, stepFilter);
   }
 
+  protected final JUnit3Wrapper.Command checkLocal(String localName) {
+    return inspect(t -> t.checkLocal(localName));
+  }
+
   protected final JUnit3Wrapper.Command checkLocal(String localName, Value expectedValue) {
     return inspect(t -> t.checkLocal(localName, expectedValue));
   }
@@ -308,6 +351,18 @@
         // Capture the context of the event suspension.
         updateEventContext((EventThread) parsedEvent);
 
+        if (DEBUG_TESTS && debuggeeState.location != null) {
+          // Dump location
+          String classSig = getMirror().getClassSignature(debuggeeState.location.classID);
+          String methodName = getMirror()
+              .getMethodName(debuggeeState.location.classID, debuggeeState.location.methodID);
+          String methodSig = getMirror()
+              .getMethodSignature(debuggeeState.location.classID, debuggeeState.location.methodID);
+          System.out.println(String
+              .format("Suspended in %s#%s%s@%x", classSig, methodName, methodSig,
+                  Long.valueOf(debuggeeState.location.index)));
+        }
+
         // Handle event.
         EventHandler eh = events.get(requestID);
         assert eh != null;
@@ -346,6 +401,10 @@
         return this.location;
       }
 
+      public void checkLocal(String localName) {
+        getVariableAt(getLocation(), localName);
+      }
+
       public void checkLocal(String localName, Value expectedValue) {
         Variable localVar = getVariableAt(getLocation(), localName);
 
@@ -767,7 +826,27 @@
       public boolean skipLocation(VmMirror mirror, Location location) {
         // TODO(shertz) we also need to skip class loaders to act like IntelliJ.
         // Skip synthetic methods.
-        return isSyntheticMethod(mirror, location);
+        if (isLambdaMethod(mirror, location)) {
+          // Lambda methods are synthetic but we do want to stop there.
+          if (DEBUG_TESTS) {
+            System.out.println("NOT skipping lambda implementation method");
+          }
+          return false;
+        }
+        if (isInLambdaClass(mirror, location)) {
+          // Lambda classes must be skipped since they are only wrappers around lambda code.
+          if (DEBUG_TESTS) {
+            System.out.println("Skipping lambda class wrapper method");
+          }
+          return true;
+        }
+        if (isSyntheticMethod(mirror, location)) {
+          if (DEBUG_TESTS) {
+            System.out.println("Skipping synthetic method");
+          }
+          return true;
+        }
+        return false;
       }
 
       private static boolean isSyntheticMethod(VmMirror mirror, Location location) {
@@ -787,6 +866,16 @@
         }
         return false;
       }
+
+      private static boolean isInLambdaClass(VmMirror mirror, Location location) {
+        String classSig = mirror.getClassSignature(location.classID);
+        return classSig.contains("$$Lambda$");
+      }
+
+      private boolean isLambdaMethod(VmMirror mirror, Location location) {
+        String methodName = mirror.getMethodName(location.classID, location.methodID);
+        return methodName.startsWith("lambda$");
+      }
     }
   }
 
diff --git a/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
new file mode 100644
index 0000000..ea116a7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/DefaultMethodTest.java
@@ -0,0 +1,65 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+public class DefaultMethodTest extends DebugTestBase {
+
+  @Test
+  public void testDefaultMethod() throws Throwable {
+    String debuggeeClass = "DebugDefaultMethod";
+    String parameterName = "msg";
+    String localVariableName = "name";
+
+    List<Command> commands = new ArrayList<>();
+    commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
+    commands.add(run());
+    commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
+    commands.add(checkLine(27));
+    if (!supportsDefaultMethod()) {
+      // We desugared default method. This means we're going to step through an extra (forward)
+      // method first.
+      commands.add(stepInto());
+    }
+    commands.add(stepInto());
+    commands.add(checkLocal(parameterName));
+    commands.add(stepOver());
+    commands.add(checkLocal(parameterName));
+    commands.add(checkLocal(localVariableName));
+    // TODO(shertz) check current method name ?
+    commands.add(run());
+    commands.add(run()  /* resume after 2nd breakpoint */);
+
+    runDebugTestJava8(debuggeeClass, commands);
+  }
+
+  @Test
+  public void testOverrideDefaultMethod() throws Throwable {
+    String debuggeeClass = "DebugDefaultMethod";
+    String parameterName = "msg";
+    String localVariableName = "newMsg";
+
+    List<Command> commands = new ArrayList<>();
+    commands.add(breakpoint(debuggeeClass, "testDefaultMethod"));
+    commands.add(run());
+    commands.add(run() /* resume after 1st breakpoint */);
+    commands.add(checkMethod(debuggeeClass, "testDefaultMethod"));
+    commands.add(checkLine(27));
+    commands.add(stepInto());
+    commands.add(checkMethod("DebugDefaultMethod$OverrideImpl", "doSomething"));
+    commands.add(checkLocal(parameterName));
+    commands.add(stepOver());
+    commands.add(checkLocal(parameterName));
+    commands.add(checkLocal(localVariableName));
+    commands.add(run());
+
+    runDebugTestJava8(debuggeeClass, commands);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LambdaTest.java b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
new file mode 100644
index 0000000..467ae09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LambdaTest.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.debug;
+
+import org.junit.Test;
+
+public class LambdaTest extends DebugTestBase {
+
+  @Test
+  public void testLambdaDebugging() throws Throwable {
+    String debuggeeClass = "DebugLambda";
+    String initialMethodName = "printInt";
+    // TODO(shertz) test local variables
+    runDebugTestJava8(debuggeeClass,
+        breakpoint(debuggeeClass, initialMethodName),
+        run(),
+        checkMethod(debuggeeClass, initialMethodName),
+        checkLine(12),
+        stepInto(INTELLIJ_FILTER),
+        checkLine(16),
+        run());
+  }
+}
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 5902a6e..4b2ece0 100644
--- a/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/CompilationTestBase.java
@@ -36,17 +36,19 @@
 
   public AndroidApp runAndCheckVerification(
       CompilerUnderTest compiler,
+      CompilationMode mode,
       String referenceApk,
       String pgMap,
       String pgConf,
       String... inputs)
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
     return runAndCheckVerification(
-        compiler, referenceApk, pgMap, pgConf, Arrays.asList(inputs));
+        compiler, mode, referenceApk, pgMap, pgConf, Arrays.asList(inputs));
   }
 
   public AndroidApp runAndCheckVerification(
       CompilerUnderTest compiler,
+      CompilationMode mode,
       String referenceApk,
       String pgMap,
       String pgConf,
@@ -63,6 +65,7 @@
       if (pgConf != null) {
         builder.addProguardConfigurationFiles(Paths.get(pgConf));
       }
+      builder.setMode(mode);
       outputApp = ToolHelper.runR8(builder.build());
     } else {
       assert compiler == CompilerUnderTest.D8;
@@ -70,7 +73,7 @@
           ToolHelper.runD8(
               D8Command.builder()
                   .addProgramFiles(ListUtils.map(inputs, Paths::get))
-                  .setMode(CompilationMode.DEBUG)
+                  .setMode(mode)
                   .build());
     }
     Path out = temp.getRoot().toPath().resolve("all.zip");
diff --git a/src/test/java/com/android/tools/r8/internal/D8GMSCoreV10DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8GMSCoreV10DeployJarVerificationTest.java
new file mode 100644
index 0000000..04c1d2e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8GMSCoreV10DeployJarVerificationTest.java
@@ -0,0 +1,31 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8GMSCoreV10DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
+
+  @Test
+  public void buildDebugFromDeployJar()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    buildFromDeployJar(
+        CompilerUnderTest.D8, CompilationMode.DEBUG,
+        GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
+  }
+
+  @Test
+  public void buildReleaseFromDeployJar()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    buildFromDeployJar(
+        CompilerUnderTest.D8, CompilationMode.RELEASE,
+        GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/D8GMSCoreV9DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8GMSCoreV9DeployJarVerificationTest.java
index d5f23d0..2e076d5 100644
--- a/src/test/java/com/android/tools/r8/internal/D8GMSCoreV9DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/D8GMSCoreV9DeployJarVerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
@@ -13,8 +14,18 @@
 public class D8GMSCoreV9DeployJarVerificationTest extends GMSCoreDeployJarVerificationTest {
 
   @Test
-  public void buildFromDeployJar()
+  public void buildDebugFromDeployJar()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    buildFromDeployJar(CompilerUnderTest.D8, GMSCoreCompilationTestBase.GMSCORE_V9_DIR, true);
+    buildFromDeployJar(
+        CompilerUnderTest.D8, CompilationMode.DEBUG,
+        GMSCoreCompilationTestBase.GMSCORE_V9_DIR, true);
+  }
+
+  @Test
+  public void buildReleaseFromDeployJar()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    buildFromDeployJar(
+        CompilerUnderTest.D8, CompilationMode.RELEASE,
+        GMSCoreCompilationTestBase.GMSCORE_V9_DIR, true);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/D8YouTubeDeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/D8YouTubeDeployJarVerificationTest.java
new file mode 100644
index 0000000..5e226bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/D8YouTubeDeployJarVerificationTest.java
@@ -0,0 +1,29 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
+import com.android.tools.r8.shaking.ProguardRuleParserException;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+public class D8YouTubeDeployJarVerificationTest extends YouTubeCompilationBase {
+
+  @Test
+  public void buildDebugFromDeployJar()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        CompilerUnderTest.D8, CompilationMode.DEBUG, BASE + APK, null, null, BASE + DEPLOY_JAR);
+  }
+
+  @Test
+  public void buildReleaseFromDeployJar()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        CompilerUnderTest.D8, CompilationMode.RELEASE, BASE + APK, null, null, BASE + DEPLOY_JAR);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/GMSCoreCompilationTestBase.java b/src/test/java/com/android/tools/r8/internal/GMSCoreCompilationTestBase.java
index ca8d019..1e91e0d 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreCompilationTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreCompilationTestBase.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
@@ -30,14 +31,16 @@
   static final String DEPLOY_JAR = "GmsCore_prod_alldpi_release_all_locales_deploy.jar";
   static final String REFERENCE_APK = "noshrink_x86_GmsCore_prod_alldpi_release_unsigned.apk";
 
-  public void runR8AndCheckVerification(String version)
+  public void runR8AndCheckVerification(CompilationMode mode, String version)
       throws ProguardRuleParserException, ExecutionException, IOException, CompilationException {
-    runAndCheckVerification(CompilerUnderTest.R8, version);
+    runAndCheckVerification(CompilerUnderTest.R8, mode, version);
   }
 
-  public void runAndCheckVerification(CompilerUnderTest compiler, String version)
+  public void runAndCheckVerification(
+      CompilerUnderTest compiler, CompilationMode mode, String version)
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
     runAndCheckVerification(
-        compiler, version + GMSCORE_APK, null, null, Paths.get(version, GMSCORE_APK).toString());
+        compiler, mode, version + GMSCORE_APK, null, null,
+        Paths.get(version, GMSCORE_APK).toString());
   }
 }
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 ecc7451..0a2cdae 100644
--- a/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/GMSCoreDeployJarVerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
@@ -11,9 +12,10 @@
 
 public class GMSCoreDeployJarVerificationTest extends GMSCoreCompilationTestBase {
 
-  public void buildFromDeployJar(CompilerUnderTest compiler, String base, boolean hasReference)
+  public void buildFromDeployJar(
+      CompilerUnderTest compiler, CompilationMode mode, String base, boolean hasReference)
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
     runAndCheckVerification(
-        compiler, hasReference ? base + REFERENCE_APK : null, null, null, base + DEPLOY_JAR);
+        compiler, mode, hasReference ? base + REFERENCE_APK : null, null, null, base + DEPLOY_JAR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
index 2571490..fecc360 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreTreeShakeJarVerificationTest.java
@@ -6,6 +6,7 @@
 import static junit.framework.TestCase.assertTrue;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
@@ -18,10 +19,12 @@
 
 public class R8GMSCoreTreeShakeJarVerificationTest extends GMSCoreCompilationTestBase {
 
-  public void buildAndTreeShakeFromDeployJar(String base, boolean hasReference, int maxSize)
+  public void buildAndTreeShakeFromDeployJar(
+      CompilationMode mode, String base, boolean hasReference, int maxSize)
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
     AndroidApp app = runAndCheckVerification(
         CompilerUnderTest.R8,
+        mode,
         hasReference ? base + REFERENCE_APK : null,
         null,
         base + PG_CONF,
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 5894981..6118b3d 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10DeployJarVerificationTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
@@ -17,6 +18,8 @@
   public void buildFromDeployJar()
       // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    buildFromDeployJar(CompilerUnderTest.R8, GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
+    buildFromDeployJar(
+        CompilerUnderTest.R8, CompilationMode.RELEASE,
+        GMSCoreCompilationTestBase.GMSCORE_V10_DIR, false);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
index 5253ac2..9572825 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV10TreeShakeJarVerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -16,6 +17,7 @@
   public void buildAndTreeShakeFromDeployJar()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
     // TODO(tamaskenez): set hasReference = true when we have the noshrink file for V10
-    buildAndTreeShakeFromDeployJar(GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE);
+    buildAndTreeShakeFromDeployJar(
+        CompilationMode.RELEASE, GMSCORE_V10_DIR, false, GMSCORE_V10_MAX_SIZE);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV4VerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV4VerificationTest.java
index d7de82e..0405587 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV4VerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV4VerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -13,6 +14,6 @@
   @Test
   public void verify()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    runR8AndCheckVerification(GMSCORE_V4_DIR);
+    runR8AndCheckVerification(CompilationMode.RELEASE, GMSCORE_V4_DIR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV5VerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV5VerificationTest.java
index 6a098a8..17aab5e 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV5VerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV5VerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -13,6 +14,6 @@
   @Test
   public void verify()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    runR8AndCheckVerification(GMSCORE_V5_DIR);
+    runR8AndCheckVerification(CompilationMode.RELEASE, GMSCORE_V5_DIR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV6VerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV6VerificationTest.java
index b7ca295..d34ad90 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV6VerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV6VerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -13,6 +14,6 @@
   @Test
   public void verify()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    runR8AndCheckVerification(GMSCORE_V6_DIR);
+    runR8AndCheckVerification(CompilationMode.RELEASE, GMSCORE_V6_DIR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV7VerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV7VerificationTest.java
index f65a8e1..2f530f4 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV7VerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV7VerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -14,6 +15,6 @@
   @Test
   public void verify()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    runR8AndCheckVerification(GMSCORE_V7_DIR);
+    runR8AndCheckVerification(CompilationMode.RELEASE, GMSCORE_V7_DIR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV8VerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV8VerificationTest.java
index bc0dbe8..128bd17 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV8VerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV8VerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -14,6 +15,6 @@
   @Test
   public void verify()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    runR8AndCheckVerification(GMSCORE_V8_DIR);
+    runR8AndCheckVerification(CompilationMode.RELEASE, GMSCORE_V8_DIR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9DeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9DeployJarVerificationTest.java
index d1e6844..bc8c2b6 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9DeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9DeployJarVerificationTest.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
@@ -16,6 +17,8 @@
   @Test
   public void buildFromDeployJar()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    buildFromDeployJar(CompilerUnderTest.R8, GMSCoreCompilationTestBase.GMSCORE_V9_DIR, true);
+    buildFromDeployJar(
+        CompilerUnderTest.R8, CompilationMode.RELEASE,
+        GMSCoreCompilationTestBase.GMSCORE_V9_DIR, true);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
index 2f5ed63..78aa1a3 100644
--- a/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/R8GMSCoreV9TreeShakeJarVerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
@@ -14,6 +15,7 @@
   @Test
   public void buildAndTreeShakeFromDeployJar()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    buildAndTreeShakeFromDeployJar(GMSCORE_V9_DIR, true, GMSCORE_V9_MAX_SIZE);
+    buildAndTreeShakeFromDeployJar(
+        CompilationMode.RELEASE, GMSCORE_V9_DIR, true, GMSCORE_V9_MAX_SIZE);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
index 08b3892..3e46590 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeCompilationBase.java
@@ -4,7 +4,7 @@
 package com.android.tools.r8.internal;
 
 public abstract class YouTubeCompilationBase extends CompilationTestBase {
-  static final String BASE = "third_party/youtube/youtube.android_11.47/";
+  static final String BASE = "third_party/youtube/youtube.android_12.17/";
   static final String APK = "YouTubeRelease_unsigned.apk";
   static final String DEPLOY_JAR = "YouTubeRelease_deploy.jar";
   static final String PG_JAR = "YouTubeRelease_proguard.jar";
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeDeployJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeDeployJarVerificationTest.java
index b40ba07..7fbfb2e 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeDeployJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeDeployJarVerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
@@ -13,8 +14,16 @@
 public class YouTubeDeployJarVerificationTest extends YouTubeCompilationBase {
 
   @Test
-  public void buildFromDeployJar()
+  public void buildDebugFromDeployJar()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    runAndCheckVerification(CompilerUnderTest.R8, BASE + APK, null, null, BASE + DEPLOY_JAR);
+    runAndCheckVerification(
+        CompilerUnderTest.R8, CompilationMode.DEBUG, BASE + APK, null, null, BASE + DEPLOY_JAR);
+  }
+
+  @Test
+  public void buildReleaseFromDeployJar()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        CompilerUnderTest.R8, CompilationMode.RELEASE, BASE + APK, null, null, BASE + DEPLOY_JAR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeDexVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeDexVerificationTest.java
index 19bfe25..ba816f1 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeDexVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeDexVerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
@@ -13,8 +14,16 @@
 public class YouTubeDexVerificationTest extends YouTubeCompilationBase {
 
   @Test
-  public void buildFromDex()
+  public void buildDebugFromDex()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    runAndCheckVerification(CompilerUnderTest.R8, BASE + APK, BASE + PG_MAP, null, BASE + APK);
+    runAndCheckVerification(
+        CompilerUnderTest.R8, CompilationMode.DEBUG, BASE + APK, BASE + PG_MAP, null, BASE + APK);
+  }
+
+  @Test
+  public void buildReleaseFromDex()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        CompilerUnderTest.R8, CompilationMode.RELEASE, BASE + APK, BASE + PG_MAP, null, BASE + APK);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
index 0b1e553..4bbceeb 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeProguardJarVerificationTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.internal;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import java.io.IOException;
@@ -13,8 +14,17 @@
 public class YouTubeProguardJarVerificationTest extends YouTubeCompilationBase {
 
   @Test
-  public void buildFromProguardJar()
+  public void buildDebugFromProguardJar()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    runAndCheckVerification(CompilerUnderTest.R8, BASE + APK, BASE + PG_MAP, null, BASE + PG_JAR);
+    runAndCheckVerification(
+        CompilerUnderTest.R8, CompilationMode.DEBUG, BASE + APK, BASE + PG_MAP, null, BASE + PG_JAR);
+  }
+
+  @Test
+  public void buildReleaseFromProguardJar()
+      throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
+    runAndCheckVerification(
+        CompilerUnderTest.R8, CompilationMode.RELEASE,
+        BASE + APK, BASE + PG_MAP, null, BASE + PG_JAR);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
index c51129c..8b5b814 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeTreeShakeJarVerificationTest.java
@@ -6,26 +6,33 @@
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8RunArtTestsTest.CompilerUnderTest;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalResource;
+import com.google.common.collect.ImmutableList;
 import com.google.common.io.ByteStreams;
 import com.google.common.io.Closer;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class YouTubeTreeShakeJarVerificationTest extends YouTubeCompilationBase {
 
   @Test
-  @Ignore("b/35656577")
   public void buildAndTreeShakeFromDeployJar()
       throws ExecutionException, IOException, ProguardRuleParserException, CompilationException {
-    int maxSize = 16000000;
+    int maxSize = 20000000;
     AndroidApp app = runAndCheckVerification(
-        CompilerUnderTest.R8, BASE + APK, null, BASE + PG_CONF, BASE + DEPLOY_JAR);
+        CompilerUnderTest.R8,
+        CompilationMode.RELEASE,
+        BASE + APK,
+        null,
+        BASE + PG_CONF,
+        // Don't pass any inputs. The input will be read from the -injars in the Proguard
+        // configuration file.
+        ImmutableList.of());
     int bytes = 0;
     try (Closer closer = Closer.create()) {
       for (InternalResource dex : app.getDexProgramResources()) {
diff --git a/src/test/java/com/android/tools/r8/utils/HashMapIntTest.java b/src/test/java/com/android/tools/r8/utils/HashMapIntTest.java
deleted file mode 100644
index 5d8fffe..0000000
--- a/src/test/java/com/android/tools/r8/utils/HashMapIntTest.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import java.util.Random;
-import java.util.Set;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class HashMapIntTest {
-
-  static private String getString(int i) {
-    String SALTCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
-    StringBuilder salt = new StringBuilder();
-    Random rnd = new Random();
-    while (salt.length() < 18) {
-      int index = (int) (rnd.nextFloat() * SALTCHARS.length());
-      salt.append(SALTCHARS.charAt(index));
-    }
-    salt.append(" ");
-    salt.append(i);
-    return salt.toString();
-  }
-
-  @Test(expected = RuntimeException.class)
-  public void putInvalid() throws Exception {
-    HashMapInt<Object> map = new HashMapInt<>();
-    map.put(null, 12);
-  }
-
-  @Test
-  public void put() throws Exception {
-    HashMapInt<Object> map = new HashMapInt<>();
-    Object key = new Object();
-    int value = 0;
-    map.put(key, value);
-    Assert.assertTrue(map.containsKey(key));
-    Assert.assertFalse(map.containsKey(new Object[0]));
-    Assert.assertEquals(map.get(key), value);
-    Assert.assertEquals(map.size(), 1);
-  }
-
-  @Test
-  public void random() throws Exception {
-    final int length = 5999;
-    HashMapInt<String> map = new HashMapInt<>();
-    String[] array = new String[length];
-    for (int i = 0; i < length; i++) {
-      array[i] = getString(i);
-      map.put(array[i], i * 3);
-    }
-    Assert.assertEquals(length, map.size());
-    for (int i = 0; i < length; i++) {
-      Assert.assertEquals(map.get(array[i]), i * 3);
-    }
-    for (int i : map.values()) {
-      Assert.assertTrue(i < length * 3 && i >= 0 && i % 3 == 0);
-    }
-    Assert.assertEquals(length, Iterables.size(map.values()));
-    Set<String> items = ImmutableSet.copyOf(array);
-    for (String s : map.keys()) {
-      Assert.assertTrue(items.contains(s));
-    }
-    Assert.assertEquals(length, Iterables.size(map.keys()));
-  }
-
-  @Test
-  public void overwrite() throws Exception {
-    HashMapInt<Object> map = new HashMapInt<>();
-    Object key = new Object();
-    int value1 = 0;
-    map.put(key, value1);
-    Assert.assertEquals(map.get(key), value1);
-    int value2 = -1;
-    map.put(key, value2);
-    Assert.assertEquals(map.get(key), value2);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/IdentityHashMapIntTest.java b/src/test/java/com/android/tools/r8/utils/IdentityHashMapIntTest.java
deleted file mode 100644
index dd57960..0000000
--- a/src/test/java/com/android/tools/r8/utils/IdentityHashMapIntTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
-import java.util.Collections;
-import java.util.Set;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class IdentityHashMapIntTest {
-
-  @Test(expected = RuntimeException.class)
-  public void putInvalid() throws Exception {
-    IdentityHashMapInt<Object> map = new IdentityHashMapInt<>();
-    map.put(null, 12);
-  }
-
-  @Test
-  public void put() throws Exception {
-    IdentityHashMapInt<Object> map = new IdentityHashMapInt<>();
-    Object key = new AllEqual();
-    int value = 0;
-    map.put(key, value);
-    Assert.assertTrue(map.containsKey(key));
-    Assert.assertFalse(map.containsKey(new Object()));
-    Assert.assertFalse(map.containsKey(new AllEqual()));
-    Assert.assertEquals(map.get(key), value);
-    Assert.assertEquals(map.size(), 1);
-  }
-
-  @Test
-  public void random() throws Exception {
-    final int length = 5999;
-    IdentityHashMapInt<Object> map = new IdentityHashMapInt<>();
-    AllEqual[] array = new AllEqual[length];
-    for (int i = 0; i < length; i++) {
-      array[i] = new AllEqual();
-      map.put(array[i], i * 3);
-    }
-    Assert.assertEquals(length, map.size());
-    for (int i = 0; i < length; i++) {
-      Assert.assertEquals(map.get(array[i]), i * 3);
-    }
-    for (int i : map.values()) {
-      Assert.assertTrue(i < length * 3 && i >= 0 && i % 3 == 0);
-    }
-    Assert.assertEquals(length, Iterables.size(map.values()));
-    Set<Object> items = Sets.newIdentityHashSet();
-    Collections.addAll(items, array);
-    for (Object o : map.keys()) {
-      Assert.assertTrue(items.contains(o));
-    }
-    Assert.assertEquals(length, Iterables.size(map.keys()));
-  }
-
-  @Test
-  public void overwrite() throws Exception {
-    IdentityHashMapInt<Object> map = new IdentityHashMapInt<>();
-    Object key = new Object();
-    int value1 = 0;
-    map.put(key, value1);
-    Assert.assertEquals(map.get(key), value1);
-    int value2 = -1;
-    map.put(key, value2);
-    Assert.assertEquals(map.get(key), value2);
-  }
-
-  private static class AllEqual {
-
-    @Override
-    public boolean equals(Object o) {
-      return true;
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/IntHashMapTest.java b/src/test/java/com/android/tools/r8/utils/IntHashMapTest.java
deleted file mode 100644
index c4b4dc2..0000000
--- a/src/test/java/com/android/tools/r8/utils/IntHashMapTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import java.util.Random;
-import java.util.Set;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class IntHashMapTest {
-
-  static private String getRandomString() {
-    String SALTCHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
-    StringBuilder salt = new StringBuilder();
-    Random rnd = new Random();
-    while (salt.length() < 18) {
-      int index = (int) (rnd.nextFloat() * SALTCHARS.length());
-      salt.append(SALTCHARS.charAt(index));
-    }
-    return salt.toString();
-  }
-
-  @Test(expected = RuntimeException.class)
-  public void putInvalid() throws Exception {
-    IntHashMap<Object> map = new IntHashMap<>();
-    map.put(12, null);
-  }
-
-  @Test
-  public void put() throws Exception {
-    IntHashMap<Object> map = new IntHashMap<>();
-    int key = 1;
-    Object value = new Object();
-    map.put(key, value);
-    Assert.assertEquals(map.get(key), value);
-    Assert.assertEquals(map.size(), 1);
-    map.put(key + 1, value);
-    Assert.assertEquals(map.get(key + 1), value);
-    Assert.assertEquals(map.size(), 2);
-    Assert.assertEquals(map.get(0), null);
-    map.put(0, value);
-    Assert.assertEquals(map.get(0), value);
-  }
-
-  @Test
-  public void random() throws Exception {
-    final int length = 5999;
-    IntHashMap<String> map = new IntHashMap<>();
-    String[] array = new String[length];
-    for (int i = 0; i < length; i++) {
-      array[i] = getRandomString();
-      map.put(i, array[i]);
-    }
-    Assert.assertEquals(length, map.size());
-    for (int i = 0; i < length; i++) {
-      Assert.assertEquals(map.get(i), array[i]);
-    }
-    Set<String> items = ImmutableSet.copyOf(array);
-    for (String s : map.values()) {
-      Assert.assertTrue(items.contains(s));
-    }
-    Assert.assertEquals(length, Iterables.size(map.values()));
-    for (int i : map.keys()) {
-      Assert.assertTrue(i < length && i >= 0);
-    }
-    Assert.assertEquals(length, Iterables.size(map.keys()));
-  }
-
-  @Test
-  public void overwrite() throws Exception {
-    IntHashMap<Object> map = new IntHashMap<>();
-    int key = 1;
-    Object value1 = new Object();
-    map.put(key, value1);
-    Assert.assertEquals(map.get(key), value1);
-    Object value2 = new Object[0];
-    map.put(key, value2);
-    Assert.assertEquals(map.get(key), value2);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/utils/IntIntHashMapTest.java b/src/test/java/com/android/tools/r8/utils/IntIntHashMapTest.java
deleted file mode 100644
index d129a03..0000000
--- a/src/test/java/com/android/tools/r8/utils/IntIntHashMapTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.utils;
-
-import com.google.common.collect.Iterables;
-import java.util.Iterator;
-import java.util.LinkedHashSet;
-import java.util.Random;
-import java.util.Set;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class IntIntHashMapTest {
-
-  @Test(expected = RuntimeException.class)
-  public void putInvalid() throws Exception {
-    IntIntHashMap map = new IntIntHashMap();
-    map.put(-1, 12);
-  }
-
-  @Test
-  public void put() throws Exception {
-    IntIntHashMap map = new IntIntHashMap();
-    int key = 22;
-    int value = 0;
-    map.put(key, value);
-    Assert.assertTrue(map.containsKey(key));
-    Assert.assertFalse(map.containsKey(33));
-    Assert.assertEquals(map.get(key), value);
-    Assert.assertEquals(map.size(), 1);
-  }
-
-  @Test
-  public void random() throws Exception {
-    final int length = 5999;
-    IntIntHashMap map = new IntIntHashMap();
-    Random rnd = new Random();
-    Set<Integer> seen = new LinkedHashSet<>();
-    String[] array = new String[length];
-    for (int i = 0; i < length; i++) {
-      int next;
-      do {
-        next = rnd.nextInt(4 * length);
-      } while (seen.contains(next));
-      seen.add(next);
-      map.put(next, i * 3);
-    }
-    Assert.assertEquals(seen.size(), map.size());
-    Iterator<Integer> it = seen.iterator();
-    for (int i = 0; i < length; i++) {
-      Assert.assertEquals(map.get(it.next()), i * 3);
-    }
-    for (int i : map.values()) {
-      Assert.assertTrue(i < length * 3 && i >= 0 && i % 3 == 0);
-    }
-    Assert.assertEquals(length, Iterables.size(map.values()));
-    for (Integer key : map.keys()) {
-      Assert.assertTrue(seen.contains(key));
-    }
-    Assert.assertEquals(seen.size(), Iterables.size(map.keys()));
-  }
-
-  @Test
-  public void overwrite() throws Exception {
-    IntIntHashMap map = new IntIntHashMap();
-    int key = 42;
-    int value1 = 0;
-    map.put(key, value1);
-    Assert.assertEquals(map.get(key), value1);
-    int value2 = -1;
-    map.put(key, value2);
-    Assert.assertEquals(map.get(key), value2);
-  }
-}
diff --git a/src/test/smali/illegal-invokes/Iface.java b/src/test/smali/illegal-invokes/Iface.java
new file mode 100644
index 0000000..dd11626
--- /dev/null
+++ b/src/test/smali/illegal-invokes/Iface.java
@@ -0,0 +1,9 @@
+// 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.
+
+public class Iface {
+
+  public void bar() {
+  }
+}
diff --git a/src/test/smali/illegal-invokes/Iface.smali b/src/test/smali/illegal-invokes/Iface.smali
new file mode 100644
index 0000000..d66777c
--- /dev/null
+++ b/src/test/smali/illegal-invokes/Iface.smali
@@ -0,0 +1,10 @@
+# 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.
+
+.class interface public LIface;
+.super Ljava/lang/Object;
+
+.method public abstract bar()V
+.end method
+
diff --git a/src/test/smali/illegal-invokes/Lowest.java b/src/test/smali/illegal-invokes/Lowest.java
new file mode 100644
index 0000000..e464313
--- /dev/null
+++ b/src/test/smali/illegal-invokes/Lowest.java
@@ -0,0 +1,11 @@
+// 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.
+
+public class Lowest extends Iface implements Super {
+
+  @Override
+  public void foo() {
+
+  }
+}
diff --git a/src/test/smali/illegal-invokes/Lowest.smali b/src/test/smali/illegal-invokes/Lowest.smali
new file mode 100644
index 0000000..d6c5787
--- /dev/null
+++ b/src/test/smali/illegal-invokes/Lowest.smali
@@ -0,0 +1,12 @@
+# 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.
+
+.class public LLowest;
+.super LSuper;
+
+.method public bar()V
+    .locals 0
+    return-void
+.end method
+
diff --git a/src/test/smali/illegal-invokes/Super.java b/src/test/smali/illegal-invokes/Super.java
new file mode 100644
index 0000000..54a8714
--- /dev/null
+++ b/src/test/smali/illegal-invokes/Super.java
@@ -0,0 +1,8 @@
+// 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.
+
+public interface Super {
+
+  public void foo();
+}
diff --git a/src/test/smali/illegal-invokes/Super.smali b/src/test/smali/illegal-invokes/Super.smali
new file mode 100644
index 0000000..1ec4f0e
--- /dev/null
+++ b/src/test/smali/illegal-invokes/Super.smali
@@ -0,0 +1,13 @@
+# 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.
+
+.class public LSuper;
+.super Ljava/lang/Object;
+.implements LIface;
+
+.method public foo()V
+    .locals 0
+    return-void
+.end method
+
diff --git a/src/test/smali/illegal-invokes/Test.java b/src/test/smali/illegal-invokes/Test.java
new file mode 100644
index 0000000..8ddd18e
--- /dev/null
+++ b/src/test/smali/illegal-invokes/Test.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+public class Test {
+
+  int ignore;
+
+  public static void main(String... args) {
+    Lowest l = new Lowest();
+    callVirtualOnIface(l);
+    callIfaceOnVirtual(l);
+  }
+
+  private static void callVirtualOnIface(Iface i) {
+    try {
+      i.bar();
+    } catch (IncompatibleClassChangeError e) {
+      System.out.println("ICCE");
+    }
+  }
+
+  private static void callIfaceOnVirtual(Super s) {
+    try {
+      s.foo();
+    } catch (IncompatibleClassChangeError e) {
+      System.out.println("ICCE");
+    }
+  }
+}
diff --git a/tools/apk-masseur.py b/tools/apk-masseur.py
index b0c990f..6e3528a 100755
--- a/tools/apk-masseur.py
+++ b/tools/apk-masseur.py
@@ -24,6 +24,10 @@
   parser.add_option('--keystore',
                     help='keystore file (default ~/.android/app.keystore)',
                     default=None)
+  parser.add_option('--install',
+                    help='install the generated apk with adb options -t -r -d',
+                    default=False,
+                    action='store_true')
   (options, args) = parser.parse_args()
   if len(args) != 1:
     parser.error('Expected <apk> argument, got: ' + ' '.join(args))
@@ -102,6 +106,10 @@
     aligned_apk = align(signed_apk, temp)
     print 'Writing result to', options.out
     shutil.copyfile(aligned_apk, options.out)
+    if options.install:
+      cmd = ['adb', 'install', '-t', '-r', '-d', options.out]
+      utils.PrintCmd(cmd)
+      subprocess.check_call(cmd)
   return 0
 
 if __name__ == '__main__':
diff --git a/tools/create_art_tests.py b/tools/create_art_tests.py
new file mode 100755
index 0000000..3a83cbc
--- /dev/null
+++ b/tools/create_art_tests.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+# 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.
+
+from os import makedirs, listdir
+from os.path import join, exists, isdir
+from string import Template, upper
+from sys import exit
+from shutil import rmtree
+
+OUTPUT_DIR = "build/generated/test/java/com/android/tools/r8/art"
+TEST_DIR = "tests/art"
+TOOLCHAINS = ["dx", "jack", "none"]
+TOOLS = ["r8", "d8"]
+TEMPLATE = Template(
+"""// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.art.$testGeneratingToolchain.$compilerUnderTest;
+
+import static com.android.tools.r8.R8RunArtTestsTest.DexTool.$testGeneratingToolchainEnum;
+
+import com.android.tools.r8.R8RunArtTestsTest;
+import org.junit.Test;
+
+/**
+ * Auto-generated test for the art $name test using the $testGeneratingToolchain toolchain.
+ *
+ * DO NOT EDIT THIS FILE. EDIT THE HERE DOCUMENT TEMPLATE IN tools/create_art_tests.py INSTEAD!
+ */
+public class $testClassName extends R8RunArtTestsTest {
+
+    public $testClassName() {
+      super("$name", $testGeneratingToolchainEnum);
+    }
+
+    @Test
+    public void run$testClassName() throws Throwable {
+      // For testing with other Art VMs than the default pass the VM version as a argument to
+      // runArtTest, e.g. runArtTest(ToolHelper.ART_4_4_4).
+      runArtTest(CompilerUnderTest.$compilerUnderTestEnum);
+    }
+}
+""")
+
+def create_toolchain_dirs(toolchain):
+  toolchain_dir = join(OUTPUT_DIR, toolchain)
+  if exists(toolchain_dir):
+    rmtree(toolchain_dir)
+  makedirs(join(toolchain_dir, "d8"))
+  makedirs(join(toolchain_dir, "r8"))
+
+def write_file(toolchain, tool, class_name, contents):
+  file_name = join(OUTPUT_DIR, toolchain, tool, class_name + ".java")
+  with open(file_name, "w") as file:
+    file.write(contents)
+
+def create_tests(toolchain):
+  source_dir = join(TEST_DIR, "dx" if toolchain == "none" else toolchain)
+  dirs = [d for d in listdir(source_dir)
+          if isdir(join(source_dir, d))]
+  for dir in dirs:
+    class_name = "Art" + dir.replace("-", "_") + "Test"
+    for tool in TOOLS:
+      contents = TEMPLATE.substitute(
+          name=dir,
+          compilerUnderTestEnum=upper(tool),
+          compilerUnderTest=tool,
+          testGeneratingToolchain=toolchain,
+          testGeneratingToolchainEnum=upper(toolchain),
+          testClassName=class_name)
+      write_file(toolchain, tool, class_name, contents)
+
+
+def main():
+  for toolchain in TOOLCHAINS:
+    create_toolchain_dirs(toolchain)
+    create_tests(toolchain)
+
+if __name__ == "__main__":
+  exit(main())
diff --git a/tools/linux/dx.tar.gz.sha1 b/tools/linux/dx.tar.gz.sha1
index f13e394..92e67ce 100644
--- a/tools/linux/dx.tar.gz.sha1
+++ b/tools/linux/dx.tar.gz.sha1
@@ -1 +1 @@
-6976e6a1768527b2388b1fdda5868dfa6b80d844
\ No newline at end of file
+da8789846188590e69dbac06f4d387762e71a616
\ No newline at end of file