Merge "Remove dead code from VirtualFile"
diff --git a/.gitignore b/.gitignore
index b66a429..f743d94 100644
--- a/.gitignore
+++ b/.gitignore
@@ -60,6 +60,8 @@
 third_party/goyt.tar.gz
 third_party/ddmlib
 third_party/ddmlib.tar.gz
+third_party/core-lambda-stubs
+third_party/core-lambda-stubs.tar.gz
 src/test/jack/ub-jack
 gradle-app.setting
 gradlew
diff --git a/build.gradle b/build.gradle
index c9c415e..9b8d59b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -25,6 +25,22 @@
     }
 }
 
+ext {
+    androidSupportVersion = '25.4.0'
+    apacheCommonsVersion = '1.12'
+    asmVersion = '6.0'
+    autoValueVersion = '1.5'
+    espressoVersion = '3.0.0'
+    fastutilVersion = '7.2.0'
+    guavaVersion = '23.0'
+    joptSimpleVersion = '4.6'
+    jsonSimpleVersion = '1.1'
+    junitVersion = '4.12'
+    kotlinVersion = '1.2.0'
+    protobufVersion = '3.0.0'
+    smaliVersion = '2.2b4'
+}
+
 def errorProneConfiguration = [
     '-XepDisableAllChecks',
     // D8 want to use reference equality, thus disable the checker explicitly
@@ -192,48 +208,48 @@
 }
 
 dependencies {
-    compile 'net.sf.jopt-simple:jopt-simple:4.6'
-    compile 'com.googlecode.json-simple:json-simple:1.1'
+    compile "net.sf.jopt-simple:jopt-simple:$joptSimpleVersion"
+    compile "com.googlecode.json-simple:json-simple:$jsonSimpleVersion"
     // Include all of guava when compiling the code, but exclude annotations that we don't
     // need from the packaging.
-    compileOnly('com.google.guava:guava:23.0')
-    compile('com.google.guava:guava:23.0', {
+    compileOnly("com.google.guava:guava:$guavaVersion")
+    compile("com.google.guava:guava:$guavaVersion", {
         exclude group: 'com.google.errorprone'
         exclude group: 'com.google.code.findbugs'
         exclude group: 'com.google.j2objc'
         exclude group: 'org.codehaus.mojo'
     })
-    compile group: 'it.unimi.dsi', name: 'fastutil', version: '7.2.0'
-    compile group: 'org.ow2.asm', name: 'asm', version: '6.0'
-    compile group: 'org.ow2.asm', name: 'asm-commons', version: '6.0'
-    compile group: 'org.ow2.asm', name: 'asm-tree', version: '6.0'
-    compile group: 'org.ow2.asm', name: 'asm-analysis', version: '6.0'
-    compile group: 'org.ow2.asm', name: 'asm-util', version: '6.0'
+    compile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+    compile group: 'org.ow2.asm', name: 'asm', version: asmVersion
+    compile group: 'org.ow2.asm', name: 'asm-commons', version: asmVersion
+    compile group: 'org.ow2.asm', name: 'asm-tree', version: asmVersion
+    compile group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
+    compile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
     testCompile sourceSets.examples.output
-    testCompile 'junit:junit:4.12'
-    testCompile group: 'org.smali', name: 'smali', version: '2.2b4'
+    testCompile "junit:junit:$junitVersion"
+    testCompile group: 'org.smali', name: 'smali', version: smaliVersion
     testCompile files('third_party/jasmin/jasmin-2.4.jar')
     testCompile files('third_party/jdwp-tests/apache-harmony-jdwp-tests-host.jar')
     testCompile files('third_party/ddmlib/ddmlib.jar')
-    bsPatchCompile group: 'org.apache.commons', name: 'commons-compress', version: '1.12'
-    jctfCommonCompile 'junit:junit:4.12'
-    jctfTestsCompile 'junit:junit:4.12'
+    bsPatchCompile "org.apache.commons:commons-compress:$apacheCommonsVersion"
+    jctfCommonCompile "junit:junit:$junitVersion"
+    jctfTestsCompile "junit:junit:$junitVersion"
     jctfTestsCompile sourceSets.jctfCommon.output
-    examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: '6.0'
-    examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: '6.0'
+    examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
+    examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
     // Import Guava for @Nullable annotation
-    examplesCompile 'com.google.guava:guava:23.0'
-    examplesCompile 'com.google.protobuf:protobuf-lite:3.0.0'
-    examplesCompileOnly "com.google.auto.value:auto-value:1.5"
-    examplesRuntime 'com.google.protobuf:protobuf-lite:3.0.0'
-    supportLibs 'com.android.support:support-v4:25.4.0'
-    supportLibs 'junit:junit:4.12'
-    supportLibs 'com.android.support.test.espresso:espresso-core:3.0.0'
+    examplesCompile "com.google.guava:guava:$guavaVersion"
+    examplesCompile "com.google.protobuf:protobuf-lite:$protobufVersion"
+    examplesCompileOnly "com.google.auto.value:auto-value:$autoValueVersion"
+    examplesRuntime "com.google.protobuf:protobuf-lite:$protobufVersion"
+    supportLibs "com.android.support:support-v4:$androidSupportVersion"
+    supportLibs "junit:junit:$junitVersion"
+    supportLibs "com.android.support.test.espresso:espresso-core:$espressoVersion"
     apiUsageSampleCompile sourceSets.main.output
-    debugTestResourcesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0'
-    examplesKotlinCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0'
-    kotlinR8TestResourcesCompileOnly 'org.jetbrains.kotlin:kotlin-stdlib:1.2.0'
-    apt 'com.google.auto.value:auto-value:1.5'
+    debugTestResourcesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+    examplesKotlinCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+    kotlinR8TestResourcesCompileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
+    apt "com.google.auto.value:auto-value:$autoValueVersion"
 }
 
 configurations.bsPatchCompile.extendsFrom configurations.compile
@@ -245,11 +261,11 @@
 protobuf {
     protoc {
         // Download from repositories
-        artifact = 'com.google.protobuf:protoc:3.0.0'
+        artifact = "com.google.protobuf:protoc:$protobufVersion"
     }
     plugins {
         javalite {
-            artifact = 'com.google.protobuf:protoc-gen-javalite:3.0.0'
+            artifact = "com.google.protobuf:protoc-gen-javalite:$protobufVersion"
         }
     }
     generateProtoTasks {
@@ -292,6 +308,7 @@
                 "android_cts_baseline",
                 "shadow",
                 "ddmlib",
+                "core-lambda-stubs",
         ],
         // All dex-vms have a fixed OS of Linux, as they are only supported on Linux, and will be run in a Docker
         // container on other platforms where supported.
@@ -559,6 +576,20 @@
     }
 }
 
+task DexSplitter(type: Jar) {
+    from sourceSets.main.output
+    baseName 'dexsplitter'
+    manifest {
+      attributes 'Main-Class': 'com.android.tools.r8.dexsplitter.DexSplitter'
+    }
+    // In order to build without dependencies, pass the exclude_deps property using:
+    // gradle -Pexclude_deps CompatDx
+    if (!project.hasProperty('exclude_deps')) {
+        // Also include dependencies
+        from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
+    }
+}
+
 task CompatProguard(type: Jar) {
     from sourceSets.main.output
     baseName 'compatproguard'
@@ -1152,13 +1183,17 @@
 task buildKotlinR8TestResources {
     def examplesDir = file("src/test/kotlinR8TestResources")
     examplesDir.eachDir { dir ->
-        def name = dir.getName()
-        def taskName = "jar_kotlinR8TestResources_${name}"
-        task "${taskName}"(type: kotlin.Kotlinc) {
-            source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.kt')
-            destination = file("build/test/kotlinR8TestResources/${name}.jar")
+        kotlin.Kotlinc.KotlinTargetVersion.values().each { kotlinTargetVersion ->
+            def name = dir.getName()
+            def taskName = "jar_kotlinR8TestResources_${name}_${kotlinTargetVersion}"
+            def outputFile = "build/test/kotlinR8TestResources/${kotlinTargetVersion}/${name}.jar"
+            task "${taskName}"(type: kotlin.Kotlinc) {
+                source = fileTree(dir: file("${examplesDir}/${name}"), include: '**/*.kt')
+                destination = file(outputFile)
+                targetVersion = kotlinTargetVersion
+            }
+            dependsOn taskName
         }
-        dependsOn taskName
     }
 }
 
diff --git a/buildSrc/src/main/java/kotlin/Kotlinc.java b/buildSrc/src/main/java/kotlin/Kotlinc.java
index 261acc3..0c0a50a 100644
--- a/buildSrc/src/main/java/kotlin/Kotlinc.java
+++ b/buildSrc/src/main/java/kotlin/Kotlinc.java
@@ -19,16 +19,29 @@
 import utils.Utils;
 
 /**
- * Gradle task to compile Kotlin source files.
+ * Gradle task to compile Kotlin source files. By default the generated classes target Java 1.6.
  */
 public class Kotlinc extends DefaultTask {
 
+  enum KotlinTargetVersion {
+    JAVA_6("1.6"),
+    JAVA_8("1.8");
+
+    private final String optionName;
+
+    KotlinTargetVersion(String optionName) {
+      this.optionName = optionName;
+    }
+  }
+
   @InputFiles
   private FileTree source;
 
   @OutputFile
   private File destination;
 
+  private KotlinTargetVersion targetVersion = KotlinTargetVersion.JAVA_6;
+
   public FileTree getSource() {
     return source;
   }
@@ -45,6 +58,14 @@
     this.destination = destination;
   }
 
+  public KotlinTargetVersion getTargetVersion() {
+    return targetVersion;
+  }
+
+  public void setTargetVersion(KotlinTargetVersion targetVersion) {
+    this.targetVersion = targetVersion;
+  }
+
   @TaskAction
   public void compile() {
     getProject().exec(new Action<ExecSpec>() {
@@ -57,6 +78,7 @@
           execSpec.setExecutable(kotlincExecPath.toFile());
           execSpec.args("-include-runtime");
           execSpec.args("-nowarn");
+          execSpec.args("-jvm-target", targetVersion.optionName);
           execSpec.args("-d", destination.getCanonicalPath());
           execSpec.args(source.getFiles());
         } catch (IOException e) {
diff --git a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
index decf916..b2560ff 100644
--- a/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
+++ b/src/main/java/com/android/tools/r8/CompatProguardCommandBuilder.java
@@ -5,12 +5,14 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import java.util.List;
 
 public class CompatProguardCommandBuilder extends R8Command.Builder {
-  private static final List<String> REFLECTIONS = ImmutableList.of(
+  @VisibleForTesting
+  public static final List<String> REFLECTIONS = ImmutableList.of(
       "-identifiernamestring public class java.lang.Class {",
       "  public static java.lang.Class forName(java.lang.String);",
       "  public java.lang.reflect.Field getField(java.lang.String);",
@@ -37,6 +39,13 @@
     this(true);
   }
 
+  public CompatProguardCommandBuilder(
+      boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
+    super(forceProguardCompatibility, diagnosticsHandler);
+    setIgnoreDexInArchive(true);
+    addProguardConfiguration(REFLECTIONS, EmbeddedOrigin.INSTANCE);
+  }
+
   public CompatProguardCommandBuilder(boolean forceProguardCompatibility) {
     super(forceProguardCompatibility);
     setIgnoreDexInArchive(true);
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index a806138..a7bb291 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -148,8 +148,8 @@
       throws IOException, CompilationException {
     try {
       // Disable global optimizations.
-      options.skipMinification = true;
-      options.inlineAccessors = false;
+      options.enableMinification = false;
+      options.enableInlining = false;
       options.outline.enabled = false;
 
       Timing timing = new Timing("DX timer");
diff --git a/src/main/java/com/android/tools/r8/D8Command.java b/src/main/java/com/android/tools/r8/D8Command.java
index 0e39dae..2678c1f 100644
--- a/src/main/java/com/android/tools/r8/D8Command.java
+++ b/src/main/java/com/android/tools/r8/D8Command.java
@@ -329,22 +329,22 @@
     internal.minApiLevel = getMinApiLevel();
     internal.intermediate = intermediate;
     // Assert and fixup defaults.
-    assert !internal.skipMinification;
-    internal.skipMinification = true;
-    assert internal.useTreeShaking;
-    internal.useTreeShaking = false;
+    assert internal.enableMinification;
+    internal.enableMinification = false;
+    assert internal.enableTreeShaking;
+    internal.enableTreeShaking = false;
     assert !internal.passthroughDexCode;
     internal.passthroughDexCode = true;
 
     // Disable some of R8 optimizations.
-    assert internal.inlineAccessors;
-    internal.inlineAccessors = false;
-    assert internal.removeSwitchMaps;
-    internal.removeSwitchMaps = false;
+    assert internal.enableInlining;
+    internal.enableInlining = false;
+    assert internal.enableSwitchMapRemoval;
+    internal.enableSwitchMapRemoval = false;
     assert internal.outline.enabled;
     internal.outline.enabled = false;
-    assert internal.propagateMemberValue;
-    internal.propagateMemberValue = false;
+    assert internal.enableValuePropagation;
+    internal.enableValuePropagation = false;
 
     internal.enableDesugaring = getEnableDesugaring();
     return internal;
diff --git a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
index 57b3804..bcbddb1 100644
--- a/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
+++ b/src/main/java/com/android/tools/r8/DexFileMergerHelper.java
@@ -56,8 +56,8 @@
     options.enableDesugaring = false;
     options.enableMainDexListCheck = false;
     options.minimalMainDex = minimalMainDex;
-    options.skipMinification = true;
-    options.inlineAccessors = false;
+    options.enableMinification = false;
+    options.enableInlining = false;
     options.outline.enabled = false;
 
     ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
new file mode 100644
index 0000000..d28f93f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -0,0 +1,116 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8;
+
+import com.android.tools.r8.DexIndexedConsumer.ArchiveConsumer;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.dex.ApplicationWriter;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexApplication.Builder;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNameMapper;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.utils.FeatureClassMapping;
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ThreadUtils;
+import com.android.tools.r8.utils.Timing;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+
+public class DexSplitterHelper {
+
+  public static void run(
+      D8Command command,
+      FeatureClassMapping featureClassMapping,
+      String outputArchive,
+      String proguardMap)
+      throws IOException, CompilationException, ExecutionException {
+    InternalOptions options = command.getInternalOptions();
+    options.enableDesugaring = false;
+    options.enableMainDexListCheck = false;
+    options.minimalMainDex = false;
+    options.enableMinification = false;
+    options.enableInlining = false;
+    options.outline.enabled = false;
+
+    ExecutorService executor = ThreadUtils.getExecutorService(ThreadUtils.NOT_SPECIFIED);
+    try {
+      try {
+        Timing timing = new Timing("DexSplitter");
+        DexApplication app =
+            new ApplicationReader(command.getInputApp(), options, timing).read(null, executor);
+
+
+        ClassNameMapper mapper = null;
+        if (proguardMap != null) {
+          mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap));
+        }
+        Map<String, Builder> applications = getDistribution(app, featureClassMapping, mapper);
+        for (Entry<String, Builder> entry : applications.entrySet()) {
+          DexApplication featureApp = entry.getValue().build();
+          // We use the same factory, reset sorting.
+          featureApp.dexItemFactory.resetSortedIndices();
+          assert !options.hasMethodsFilter();
+
+          // Run d8 optimize to ensure jumbo strings are handled.
+          AppInfo appInfo = new AppInfo(featureApp);
+          featureApp = D8.optimize(featureApp, appInfo, options, timing, executor);
+          // We create a specific consumer for each split.
+          DexIndexedConsumer consumer =
+              new ArchiveConsumer(Paths.get(outputArchive + "." + entry.getKey() + ".zip"));
+          try {
+            new ApplicationWriter(
+                    featureApp,
+                    options,
+                    D8.getMarker(options),
+                    null,
+                    NamingLens.getIdentityLens(),
+                    null,
+                    null,
+                    consumer)
+                .write(executor);
+            options.printWarnings();
+          } finally {
+            consumer.finished(options.reporter);
+          }
+        }
+      } catch (ExecutionException e) {
+        R8.unwrapExecutionException(e);
+        throw new AssertionError(e); // unwrapping method should have thrown
+      } catch (FeatureMappingException e) {
+        options.reporter.error(e.getMessage());
+      } finally {
+        options.signalFinishedToProgramConsumer();
+      }
+    } finally {
+      executor.shutdown();
+    }
+  }
+
+  private static Map<String, Builder> getDistribution(
+      DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper)
+      throws FeatureMappingException {
+    Map<String, Builder> applications = new HashMap<>();
+    for (DexProgramClass clazz : app.classes()) {
+      String clazzName =
+          mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString();
+      String feature = featureClassMapping.featureForClass(clazzName);
+      Builder featureApplication = applications.get(feature);
+      if (featureApplication == null) {
+        featureApplication = DexApplication.builder(app.dexItemFactory, app.timing);
+        applications.put(feature, featureApplication);
+      }
+      featureApplication.addProgramClass(clazz);
+    }
+    return applications;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
index b27c26a..f9e0ebf 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexListCommand.java
@@ -27,6 +27,7 @@
   private final ImmutableList<ProguardConfigurationRule> mainDexKeepRules;
   private final StringConsumer mainDexListConsumer;
   private final DexItemFactory factory;
+  private final Reporter reporter;
 
   public static class Builder extends BaseCommand.Builder<GenerateMainDexListCommand, Builder> {
 
@@ -34,6 +35,14 @@
     private final List<ProguardConfigurationSource> mainDexRules = new ArrayList<>();
     private StringConsumer mainDexListConsumer = null;
 
+    private Builder() {
+    }
+
+    private Builder(DiagnosticsHandler diagnosticsHandler) {
+      super(diagnosticsHandler);
+    }
+
+
     @Override
     GenerateMainDexListCommand.Builder self() {
       return this;
@@ -106,7 +115,7 @@
       }
 
       return new GenerateMainDexListCommand(
-          factory, getAppBuilder().build(), mainDexKeepRules, mainDexListConsumer);
+          factory, getAppBuilder().build(), mainDexKeepRules, mainDexListConsumer, getReporter());
     }
   }
 
@@ -127,6 +136,10 @@
     return new GenerateMainDexListCommand.Builder();
   }
 
+  public static GenerateMainDexListCommand.Builder builder(DiagnosticsHandler diagnosticsHandler) {
+    return new GenerateMainDexListCommand.Builder(diagnosticsHandler);
+  }
+
   public static GenerateMainDexListCommand.Builder parse(String[] args) {
     GenerateMainDexListCommand.Builder builder = builder();
     parse(args, builder);
@@ -168,11 +181,13 @@
       DexItemFactory factory,
       AndroidApp inputApp,
       ImmutableList<ProguardConfigurationRule> mainDexKeepRules,
-      StringConsumer mainDexListConsumer) {
+      StringConsumer mainDexListConsumer,
+      Reporter reporter) {
     super(inputApp);
     this.factory = factory;
     this.mainDexKeepRules = mainDexKeepRules;
     this.mainDexListConsumer = mainDexListConsumer;
+    this.reporter = reporter;
   }
 
   private GenerateMainDexListCommand(boolean printHelp, boolean printVersion) {
@@ -180,18 +195,17 @@
     this.factory = new DexItemFactory();
     this.mainDexKeepRules = ImmutableList.of();
     this.mainDexListConsumer = null;
+    this.reporter = new Reporter(new DefaultDiagnosticsHandler());
   }
 
   @Override
   InternalOptions getInternalOptions() {
-    InternalOptions internal =
-        new InternalOptions(factory, new Reporter(new DefaultDiagnosticsHandler()));
+    InternalOptions internal = new InternalOptions(factory, reporter);
     internal.mainDexKeepRules = mainDexKeepRules;
     internal.mainDexListConsumer = mainDexListConsumer;
     internal.minimalMainDex = internal.debug;
-    internal.removeSwitchMaps = false;
-    internal.inlineAccessors = false;
-    internal.allowLibraryClassesToExtendProgramClasses = true;
+    internal.enableSwitchMapRemoval = false;
+    internal.enableInlining = false;
     return internal;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 345a671..906c8d8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -287,7 +287,7 @@
           out.flush();
           proguardSeedsData = bytes.toString();
         }
-        if (options.useTreeShaking) {
+        if (options.enableTreeShaking) {
           TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
           application = pruner.run();
           // Recompute the subtyping information.
@@ -329,7 +329,7 @@
       if (appInfo.hasLiveness()) {
         graphLense = new MemberRebindingAnalysis(appInfo.withLiveness(), graphLense).run();
         // Class merging requires inlining.
-        if (!options.skipClassMerging && options.inlineAccessors) {
+        if (options.enableClassMerging && options.enableInlining) {
           timing.begin("ClassMerger");
           SimpleClassMerger classMerger = new SimpleClassMerger(application,
               appInfo.withLiveness(), graphLense, timing);
@@ -400,12 +400,12 @@
 
       appInfo = new AppInfoWithSubtyping(application);
 
-      if (options.useTreeShaking || !options.skipMinification) {
+      if (options.enableTreeShaking || options.enableMinification) {
         timing.begin("Post optimization code stripping");
         try {
           Enqueuer enqueuer = new Enqueuer(appInfo, options);
           appInfo = enqueuer.traceApplication(rootSet, timing);
-          if (options.useTreeShaking) {
+          if (options.enableTreeShaking) {
             TreePruner pruner = new TreePruner(application, appInfo.withLiveness(), options);
             application = pruner.run();
             appInfo = appInfo.withLiveness()
@@ -420,7 +420,7 @@
       }
 
       // Only perform discard-checking if tree-shaking is turned on.
-      if (options.useTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
+      if (options.enableTreeShaking && !rootSet.checkDiscarded.isEmpty()) {
         new DiscardedChecker(rootSet, application, options).run();
       }
 
@@ -428,9 +428,9 @@
       // If we did not have keep rules, everything will be marked as keep, so no minification
       // will happen. Just avoid the overhead.
       NamingLens namingLens =
-          options.skipMinification
-              ? NamingLens.getIdentityLens()
-              : new Minifier(appInfo.withLiveness(), rootSet, options).run(timing);
+          options.enableMinification
+              ? new Minifier(appInfo.withLiveness(), rootSet, options).run(timing)
+              : NamingLens.getIdentityLens();
       timing.end();
 
       ProguardMapSupplier proguardMapSupplier;
diff --git a/src/main/java/com/android/tools/r8/R8Command.java b/src/main/java/com/android/tools/r8/R8Command.java
index d42df18..7004d5f 100644
--- a/src/main/java/com/android/tools/r8/R8Command.java
+++ b/src/main/java/com/android/tools/r8/R8Command.java
@@ -75,6 +75,12 @@
       this.forceProguardCompatibility = forceProguardCompatibility;
     }
 
+    Builder(boolean forceProguardCompatibility, DiagnosticsHandler diagnosticsHandler) {
+      super(diagnosticsHandler);
+      setMode(CompilationMode.RELEASE);
+      this.forceProguardCompatibility = forceProguardCompatibility;
+    }
+
     private Builder(DiagnosticsHandler diagnosticsHandler) {
       super(diagnosticsHandler);
       setMode(CompilationMode.DEBUG);
@@ -630,26 +636,17 @@
     internal.programConsumer = getProgramConsumer();
     internal.minApiLevel = getMinApiLevel();
     internal.enableDesugaring = getEnableDesugaring();
-    // -dontoptimize disables optimizations by flipping related flags.
-    if (!proguardConfiguration.isOptimizing()) {
-      internal.skipClassMerging = true;
-      internal.addNonNull = false;
-      internal.inlineAccessors = false;
-      internal.removeSwitchMaps = false;
-      internal.outline.enabled = false;
-      internal.propagateMemberValue = false;
-    }
-    assert !internal.skipMinification;
-    internal.skipMinification = !getEnableMinification();
-    assert internal.useTreeShaking;
-    internal.useTreeShaking = getEnableTreeShaking();
+    assert internal.enableMinification;
+    internal.enableMinification = getEnableMinification();
+    assert internal.enableTreeShaking;
+    internal.enableTreeShaking = getEnableTreeShaking();
     assert !internal.ignoreMissingClasses;
     internal.ignoreMissingClasses = proguardConfiguration.isIgnoreWarnings()
         // TODO(70706667): We probably only want this in Proguard compatibility mode.
         || (forceProguardCompatibility
             && !proguardConfiguration.isOptimizing()
-            && internal.skipMinification
-            && !internal.useTreeShaking);
+            && !internal.enableMinification
+            && !internal.enableTreeShaking);
 
     assert !internal.verbose;
     internal.mainDexKeepRules = mainDexKeepRules;
@@ -661,7 +658,7 @@
 
     if (internal.debug) {
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
-      internal.inlineAccessors = false;
+      internal.enableInlining = false;
       // TODO(zerny): Should we support outlining in debug mode? b/62937285
       internal.outline.enabled = false;
     }
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index cbbc619..46758e9 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "v1.1.3-dev";
+  public static final String LABEL = "v1.1.6-dev";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index bdce57dc..b413bfe 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.dex;
 
 import com.android.tools.r8.ApiLevelException;
+import com.android.tools.r8.DexIndexedConsumer;
 import com.android.tools.r8.errors.DexOverflowException;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationDirectory;
@@ -46,6 +47,7 @@
   public final String proguardSeedsData;
   public final InternalOptions options;
   public DexString markerString;
+  public DexIndexedConsumer programConsumer;
   public final ProguardMapSupplier proguardMapSupplier;
 
   private static class SortAnnotations extends MixedSectionCollection {
@@ -113,6 +115,26 @@
       NamingLens namingLens,
       String proguardSeedsData,
       ProguardMapSupplier proguardMapSupplier) {
+    this(
+        application,
+        options,
+        marker,
+        deadCode,
+        namingLens,
+        proguardSeedsData,
+        proguardMapSupplier,
+        null);
+  }
+
+  public ApplicationWriter(
+      DexApplication application,
+      InternalOptions options,
+      Marker marker,
+      String deadCode,
+      NamingLens namingLens,
+      String proguardSeedsData,
+      ProguardMapSupplier proguardMapSupplier,
+      DexIndexedConsumer consumer) {
     assert application != null;
     this.application = application;
     assert options != null;
@@ -124,6 +146,7 @@
     this.namingLens = namingLens;
     this.proguardSeedsData = proguardSeedsData;
     this.proguardMapSupplier = proguardMapSupplier;
+    this.programConsumer = consumer;
   }
 
   private Iterable<VirtualFile> distribute()
@@ -186,7 +209,13 @@
               executorService.submit(
                   () -> {
                     byte[] result = writeDexFile(mapping);
-                    if (virtualFile.getPrimaryClassDescriptor() != null) {
+                    if (programConsumer != null) {
+                      programConsumer.accept(
+                          virtualFile.getId(),
+                          result,
+                          virtualFile.getClassDescriptors(),
+                          options.reporter);
+                    } else if (virtualFile.getPrimaryClassDescriptor() != null) {
                       options
                           .getDexFilePerClassFileConsumer()
                           .accept(
diff --git a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
index baafe22..78463bc 100644
--- a/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
+++ b/src/main/java/com/android/tools/r8/dexfilemerger/DexFileMerger.java
@@ -12,6 +12,8 @@
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.OptionsParsing;
+import com.android.tools.r8.utils.OptionsParsing.ParseContext;
 import com.android.tools.r8.utils.StringDiagnostic;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -20,7 +22,6 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -79,87 +80,6 @@
     String dexPrefix = DEX_PREFIX;
   }
 
-  private static class ParseContext {
-    private final String[] args;
-    private int nextIndex = 0;
-
-    ParseContext(String[] args) {
-      this.args = args;
-    }
-
-    String head() {
-      return nextIndex < args.length ? args[nextIndex] : null;
-    }
-
-    String next() {
-      if (nextIndex < args.length) {
-        ++nextIndex;
-        return head();
-      } else {
-        throw new RuntimeException("Iterating over the end of argument list.");
-      }
-    }
-  }
-
-  /**
-   * Try parsing the switch {@code name} and zero or more non-switch args after it. Also supports
-   * the <name>=arg syntax.
-   */
-  private static List<String> tryParseMulti(ParseContext context, String name) {
-    List<String> result = null;
-    String head = context.head();
-    if (head.equals(name)) {
-      context.next();
-      result = new ArrayList<>();
-      while (context.head() != null && !context.head().startsWith("-")) {
-        result.add(context.head());
-        context.next();
-      }
-    } else if (head.startsWith(name) && head.charAt(name.length()) == '=') {
-      result = Collections.singletonList(head.substring(name.length() + 1));
-      context.next();
-    }
-    return result;
-  }
-
-  /**
-   * Try parsing the switch {@code name} and one arg after it. Also supports the <name>=arg syntax.
-   */
-  private static String tryParseSingle(ParseContext context, String name, String shortName) {
-    String head = context.head();
-    if (head.equals(name) || head.equals(shortName)) {
-      String next = context.next();
-      if (next == null) {
-        throw new RuntimeException(String.format("Missing argument for '%s'.", head));
-      }
-      context.next();
-      return next;
-    }
-
-    if (head.startsWith(name) && head.charAt(name.length()) == '=') {
-      context.next();
-      return head.substring(name.length() + 1);
-    }
-
-    return null;
-  }
-
-  /**
-   * Try parsing the switch {@code name} as a boolean switch or its negation, with a 'no' between
-   * the dashes and the word.
-   */
-  private static Boolean tryParseBoolean(ParseContext context, String name) {
-    if (context.head().equals(name)) {
-      context.next();
-      return true;
-    }
-    assert name.startsWith("--");
-    if (context.head().equals("--no" + name.substring(2))) {
-      context.next();
-      return false;
-    }
-    return null;
-  }
 
   private static Options parseArguments(String[] args) throws IOException {
     // We may have a single argument which is a parameter file path, prefixed with '@'.
@@ -192,54 +112,54 @@
       if (context.head().startsWith("@")) {
         throw new RuntimeException("A params file must be the only argument: " + context.head());
       }
-      strings = tryParseMulti(context, "--input");
+      strings = OptionsParsing.tryParseMulti(context, "--input");
       if (strings != null) {
         options.inputArchives.addAll(strings);
         continue;
       }
-      string = tryParseSingle(context, "--output", "-o");
+      string = OptionsParsing.tryParseSingle(context, "--output", "-o");
       if (string != null) {
         options.outputArchive = string;
         continue;
       }
-      string = tryParseSingle(context, "--multidex", null);
+      string = OptionsParsing.tryParseSingle(context, "--multidex", null);
       if (string != null) {
         options.multidexMode = MultidexStrategy.valueOf(string.toUpperCase());
         continue;
       }
-      string = tryParseSingle(context, "--main-dex-list", null);
+      string = OptionsParsing.tryParseSingle(context, "--main-dex-list", null);
       if (string != null) {
         options.mainDexListFile = string;
         continue;
       }
-      b = tryParseBoolean(context, "--minimal-main-dex");
+      b = OptionsParsing.tryParseBoolean(context, "--minimal-main-dex");
       if (b != null) {
         options.minimalMainDex = b;
         continue;
       }
-      b = tryParseBoolean(context, "--verbose");
+      b = OptionsParsing.tryParseBoolean(context, "--verbose");
       if (b != null) {
         options.verbose = b;
         continue;
       }
-      string = tryParseSingle(context, "--max-bytes-wasted-per-file", null);
+      string = OptionsParsing.tryParseSingle(context, "--max-bytes-wasted-per-file", null);
       if (string != null) {
         System.err.println("Warning: '--max-bytes-wasted-per-file' is ignored.");
         continue;
       }
-      string = tryParseSingle(context, "--set-max-idx-number", null);
+      string = OptionsParsing.tryParseSingle(context, "--set-max-idx-number", null);
       if (string != null) {
         System.err.println("Warning: The '--set-max-idx-number' option is ignored.");
         continue;
       }
-      b = tryParseBoolean(context, "--forceJumbo");
+      b = OptionsParsing.tryParseBoolean(context, "--forceJumbo");
       if (b != null) {
         System.err.println(
             "Warning: '--forceJumbo' can be safely omitted. Strings will only use "
                 + "jumbo-string indexing if necessary.");
         continue;
       }
-      string = tryParseSingle(context, "--dex_prefix", null);
+      string = OptionsParsing.tryParseSingle(context, "--dex_prefix", null);
       if (string != null) {
         options.dexPrefix = string;
         continue;
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
new file mode 100644
index 0000000..20cf4ee
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.dexsplitter;
+
+import com.android.tools.r8.CompilationException;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.DexSplitterHelper;
+import com.android.tools.r8.ResourceException;
+import com.android.tools.r8.utils.FeatureClassMapping;
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.android.tools.r8.utils.OptionsParsing;
+import com.android.tools.r8.utils.OptionsParsing.ParseContext;
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+public class DexSplitter {
+
+  private static final String DEFAULT_OUTPUT_ARCHIVE_FILENAME = "split";
+
+  private static final boolean PRINT_ARGS = false;
+
+  private static class Options {
+    List<String> inputArchives = new ArrayList<>();
+    List<String> featureJars = new ArrayList<>();
+    String splitBaseName = DEFAULT_OUTPUT_ARCHIVE_FILENAME;
+    String featureSplitMapping;
+    String proguardMap;
+  }
+
+  private static Options parseArguments(String[] args) throws IOException {
+    Options options = new Options();
+    ParseContext context = new ParseContext(args);
+    while (context.head() != null) {
+      List<String> input = OptionsParsing.tryParseMulti(context, "--input");
+      if (input != null) {
+        options.inputArchives.addAll(input);
+        continue;
+      }
+      List<String> featureJars = OptionsParsing.tryParseMulti(context, "--feature-jar");
+      if (featureJars != null) {
+        options.featureJars.addAll(featureJars);
+        continue;
+      }
+      String output = OptionsParsing.tryParseSingle(context, "--output", "-o");
+      if (output != null) {
+        options.splitBaseName = output;
+        continue;
+      }
+      String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
+      if (proguardMap != null) {
+        options.proguardMap = proguardMap;
+        continue;
+      }
+      String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
+      if (featureSplit != null) {
+        options.featureSplitMapping = featureSplit;
+        continue;
+      }
+      throw new RuntimeException(String.format("Unknown options: '%s'.", context.head()));
+    }
+    return options;
+  }
+
+  private static FeatureClassMapping createFeatureClassMapping(Options options)
+      throws IOException, FeatureMappingException, ResourceException {
+    if (options.featureSplitMapping != null) {
+      return FeatureClassMapping.fromSpecification(Paths.get(options.featureSplitMapping));
+    }
+    assert !options.featureJars.isEmpty();
+    return FeatureClassMapping.fromJarFiles(options.featureJars);
+  }
+
+  public static void run(String[] args)
+      throws CompilationFailedException, IOException, CompilationException, ExecutionException,
+          ResourceException, FeatureMappingException {
+    Options options = parseArguments(args);
+    if (options.inputArchives.isEmpty()) {
+      throw new RuntimeException("Need at least one --input");
+    }
+    if (options.featureSplitMapping == null && options.featureJars.isEmpty()) {
+      throw new RuntimeException("You must supply a feature split mapping or feature jars");
+    }
+    if (options.featureSplitMapping != null && !options.featureJars.isEmpty()) {
+      throw new RuntimeException("You can't supply both a feature split mapping and feature jars");
+    }
+
+    D8Command.Builder builder = D8Command.builder();
+    for (String s : options.inputArchives) {
+      builder.addProgramFiles(Paths.get(s));
+    }
+    // We set the actual consumer on the ApplicationWriter when we have calculated the distribution
+    // since we don't yet know the distribution.
+    builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
+
+    FeatureClassMapping featureClassMapping = createFeatureClassMapping(options);
+
+    DexSplitterHelper.run(
+        builder.build(), featureClassMapping, options.splitBaseName, options.proguardMap);
+  }
+
+  public static void main(String[] args) {
+    try {
+      if (PRINT_ARGS) {
+        printArgs(args);
+      }
+      run(args);
+    } catch (CompilationFailedException
+        | IOException
+        | CompilationException
+        | ExecutionException
+        | ResourceException
+        | FeatureMappingException e) {
+      System.err.println("Splitting failed: " + e.getMessage());
+      System.exit(1);
+    }
+  }
+
+  private static void printArgs(String[] args) {
+    System.err.printf("r8.DexSplitter");
+    for (String s : args) {
+      System.err.printf(" %s", s);
+    }
+    System.err.println("");
+  }
+}
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 612d280..2ba0890 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -108,11 +108,17 @@
    */
   public DexEncodedMethod lookupSuperTarget(DexMethod method,
       DexType invocationContext) {
+    // Make sure we are not chasing NotFoundError.
+    ResolutionResult resolutionResult = resolveMethod(method.holder, method);
+    if (resolutionResult.asListOfTargets().isEmpty()) {
+      return null;
+    }
+    // Then, resume on the search, but this time, starting from the holder of the caller.
     DexClass contextClass = definitionFor(invocationContext);
     if (contextClass == null || contextClass.superType == null) {
       return null;
     }
-    ResolutionResult resolutionResult = resolveMethod(contextClass.superType, method);
+    resolutionResult = resolveMethod(contextClass.superType, method);
     DexEncodedMethod target = resolutionResult.asSingleTarget();
     return target == null || !target.isStaticMethod() ? target : null;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index dba4842..35108bd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -213,8 +213,12 @@
 
     @Override
     public void collectIndexedItems(IndexedItemCollection collection) {
-      name.collectIndexedItems(collection);
-      type.collectIndexedItems(collection);
+      if (name != null) {
+        name.collectIndexedItems(collection);
+      }
+      if (type != null) {
+        type.collectIndexedItems(collection);
+      }
       if (signature != null) {
         signature.collectIndexedItems(collection);
       }
@@ -234,8 +238,8 @@
     public int hashCode() {
       return Constants.DBG_START_LOCAL
           + registerNum * 7
-          + name.hashCode() * 13
-          + type.hashCode() * 17
+          + Objects.hashCode(name) * 13
+          + Objects.hashCode(type) * 17
           + Objects.hashCode(signature) * 19;
     }
 
@@ -248,10 +252,10 @@
       if (registerNum != o.registerNum) {
         return false;
       }
-      if (!name.equals(o.name)) {
+      if (!Objects.equals(name, o.name)) {
         return false;
       }
-      if (!type.equals(o.type)) {
+      if (!Objects.equals(type, o.type)) {
         return false;
       }
       return Objects.equals(signature, o.signature);
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 8aa0a38..a1e8821 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -609,9 +609,8 @@
       return new OptimizationInfo(this);
     }
 
-    private void markCheckNullReceiverBeforeAnySideEffect() {
-      assert !checksNullReceiverBeforeAnySideEffect;
-      checksNullReceiverBeforeAnySideEffect = true;
+    private void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
+      checksNullReceiverBeforeAnySideEffect = mark;
     }
   }
 
@@ -659,8 +658,8 @@
     ensureMutableOI().markUseIdentifierNameString();
   }
 
-  synchronized public void markCheckNullReceiverBeforeAnySideEffect() {
-    ensureMutableOI().markCheckNullReceiverBeforeAnySideEffect();
+  synchronized public void markCheckNullReceiverBeforeAnySideEffect(boolean mark) {
+    ensureMutableOI().markCheckNullReceiverBeforeAnySideEffect(mark);
   }
 
   public OptimizationInfo getOptimizationInfo() {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d674ccb..b1a5781 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -194,6 +194,7 @@
   public final LongMethods longMethods = new LongMethods();
   public final ThrowableMethods throwableMethods = new ThrowableMethods();
   public final ClassMethods classMethods = new ClassMethods();
+  public final Kotlin kotlin = new Kotlin();
 
   // Dex system annotations.
   // See https://source.android.com/devices/tech/dalvik/dex-format.html#system-annotation
@@ -334,6 +335,24 @@
     }
   }
 
+  public class Kotlin {
+    private Kotlin() {
+    }
+
+    public final Intrinsics intrinsics = new Intrinsics();
+
+    // kotlin.jvm.internal.Intrinsics class
+    public class Intrinsics {
+      private Intrinsics() {
+      }
+
+      public final DexType type = createType(createString("Lkotlin/jvm/internal/Intrinsics;"));
+      public final DexMethod throwParameterIsNullException =
+          createMethod(type, createProto(voidType, stringType), "throwParameterIsNullException");
+      public final DexMethod throwNpe = createMethod(type, createProto(voidType), "throwNpe");
+    }
+  }
+
   private static <T extends DexItem> T canonicalize(ConcurrentHashMap<T, T> map, T item) {
     assert item != null;
     assert !DexItemFactory.isInternalSentinel(item);
diff --git a/src/main/java/com/android/tools/r8/graph/DexMethod.java b/src/main/java/com/android/tools/r8/graph/DexMethod.java
index 2fe0796..621878f 100644
--- a/src/main/java/com/android/tools/r8/graph/DexMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexMethod.java
@@ -6,6 +6,8 @@
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.naming.NamingLens;
+import com.google.common.collect.Maps;
+import java.util.Map;
 
 public class DexMethod extends Descriptor<DexEncodedMethod, DexMethod>
     implements PresortedComparable<DexMethod> {
@@ -15,7 +17,7 @@
   public final DexString name;
 
   // Caches used during processing.
-  private DexEncodedMethod singleTargetCache;
+  private Map<DexType, DexEncodedMethod> singleTargetCache;
 
   DexMethod(DexType holder, DexProto proto, DexString name) {
     this.holder = holder;
@@ -151,16 +153,20 @@
     return builder.toString();
   }
 
-  synchronized public void setSingleVirtualMethodCache(DexEncodedMethod method) {
-    singleTargetCache = method == null ? DexEncodedMethod.SENTINEL : method;
+  synchronized public void setSingleVirtualMethodCache(
+      DexType receiverType, DexEncodedMethod method) {
+    if (singleTargetCache == null) {
+      singleTargetCache = Maps.newIdentityHashMap();
+    }
+    singleTargetCache.put(receiverType, method);
   }
 
-  synchronized public boolean isSingleVirtualMethodCached() {
-    return singleTargetCache != null;
+  synchronized public boolean isSingleVirtualMethodCached(DexType receiverType) {
+    return singleTargetCache != null && singleTargetCache.containsKey(receiverType);
   }
 
-  synchronized public DexEncodedMethod getSingleVirtualMethodCache() {
-    assert isSingleVirtualMethodCached();
-    return singleTargetCache == DexEncodedMethod.SENTINEL ? null : singleTargetCache;
+  synchronized public DexEncodedMethod getSingleVirtualMethodCache(DexType receiverType) {
+    assert isSingleVirtualMethodCached(receiverType);
+    return singleTargetCache.get(receiverType);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
index bd459b5..2e30a5d 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ArrayTypeLatticeElement.java
@@ -57,17 +57,6 @@
   }
 
   @Override
-  public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
-    if (castType.getNumberOfLeadingSquareBrackets() == getNesting()) {
-      DexType base = castType.toBaseType(appInfo.dexItemFactory);
-      if (getArrayBaseType(appInfo.dexItemFactory).isSubtypeOf(base, appInfo)) {
-        return this;
-      }
-    }
-    return super.checkCast(appInfo, castType);
-  }
-
-  @Override
   public String toString() {
     return isNullableString() + arrayType.toString();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
index 5e19e83..f5b2f6e 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/ClassTypeLatticeElement.java
@@ -45,14 +45,6 @@
   }
 
   @Override
-  public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
-    if (classType.isSubtypeOf(castType, appInfo)) {
-      return this;
-    }
-    return fromDexType(castType, isNullable());
-  }
-
-  @Override
   public String toString() {
     return isNullableString() + classType.toString();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
index 3c3f0e9..ca9ac77 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/NullLatticeElement.java
@@ -11,6 +11,11 @@
   }
 
   @Override
+  public boolean mustBeNull() {
+    return true;
+  }
+
+  @Override
   TypeLatticeElement asNullable() {
     return this;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
index dddd435..4138b8c 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeAnalysis.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.Value;
 import com.google.common.annotations.VisibleForTesting;
@@ -34,10 +35,11 @@
   public TypeAnalysis(AppInfo appInfo, DexEncodedMethod encodedMethod, IRCode code) {
     this.appInfo = appInfo;
     this.encodedMethod = encodedMethod;
-    updateBlocks(code.topologicallySortedBlocks());
+    analyzeBlocks(code.topologicallySortedBlocks());
   }
 
-  public void updateBlocks(List<BasicBlock> blocks) {
+  @Override
+  public void analyzeBlocks(List<BasicBlock> blocks) {
     assert worklist.isEmpty();
     worklist.addAll(blocks);
     while (!worklist.isEmpty()) {
@@ -83,6 +85,12 @@
         }
       }
     }
+    for (Phi phi : block.getPhis()) {
+      TypeLatticeElement phiType = computePhiType(phi);
+      if (!getLatticeElement(phi).equals(phiType)) {
+        updateTypeOfValue(phi, phiType);
+      }
+    }
   }
 
   private void registerAsUserOfValue(Value value, BasicBlock block, Set<Value> seenPhis) {
@@ -124,15 +132,37 @@
   }
 
   @Override
-  public DexType getObjectType(Value value) {
-    DexType type = appInfo.dexItemFactory.objectType;
-    TypeLatticeElement l = getLatticeElement(value);
-    if (l.isClassTypeLatticeElement()) {
-      type = l.asClassTypeLatticeElement().getClassType();
-    } else if (l.isArrayTypeLatticeElement()) {
-      type = l.asArrayTypeLatticeElement().getArrayType();
+  public DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke) {
+    DexType receiverType = invoke.getInvokedMethod().getHolder();
+    TypeLatticeElement lattice = getLatticeElement(invoke.getReceiver());
+    if (lattice.isClassTypeLatticeElement()) {
+      DexType refinedType = lattice.asClassTypeLatticeElement().getClassType();
+      if (refinedType.isSubtypeOf(receiverType, appInfo)) {
+        return refinedType;
+      }
     }
-    return type;
+    return receiverType;
+  }
+
+  private static final TypeEnvironment DEFAULT_ENVIRONMENT = new TypeEnvironment() {
+    @Override
+    public TypeLatticeElement getLatticeElement(Value value) {
+      return Top.getInstance();
+    }
+
+    @Override
+    public DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke) {
+      return invoke.getInvokedMethod().holder;
+    }
+
+    @Override
+    public void analyzeBlocks(List<BasicBlock> blocks) {
+    }
+  };
+
+  // TODO(b/72693244): By annotating type lattice to value, remove the default type env at all.
+  public static TypeEnvironment getDefaultTypeEnvironment() {
+    return DEFAULT_ENVIRONMENT;
   }
 
   @VisibleForTesting
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
index e996012..890af8a 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeEnvironment.java
@@ -4,9 +4,13 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.Value;
+import java.util.List;
 
 public interface TypeEnvironment {
   TypeLatticeElement getLatticeElement(Value value);
-  DexType getObjectType(Value value);
+  DexType getRefinedReceiverType(InvokeMethodWithReceiver invoke);
+  void analyzeBlocks(List<BasicBlock> blocks);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
index 2a26571..5523abb 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/type/TypeLatticeElement.java
@@ -25,6 +25,10 @@
     return isNullable;
   }
 
+  public boolean mustBeNull() {
+    return false;
+  }
+
   /**
    * Defines how to join with null or switch to nullable lattice element.
    *
@@ -138,13 +142,43 @@
 
   public static TypeLatticeElement join(AppInfo appInfo, Stream<TypeLatticeElement> types) {
     BinaryOperator<TypeLatticeElement> joiner = joiner(appInfo);
-    return types.reduce(Bottom.getInstance(), joiner::apply, joiner::apply);
+    return types.reduce(Bottom.getInstance(), joiner, joiner);
+  }
+
+  /**
+   * Determines the strict partial order of the given {@link TypeLatticeElement}s.
+   *
+   * @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
+   * @param l1 subject {@link TypeLatticeElement}
+   * @param l2 expected to be *strict* bigger than {@param l1}
+   * @return {@code true} if {@param l1} is strictly less than {@param l2}.
+   */
+  public static boolean strictlyLessThan(
+      AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
+    if (l1.equals(l2)) {
+      return false;
+    }
+    TypeLatticeElement lub = join(appInfo, Stream.of(l1, l2));
+    return !l1.equals(lub) && l2.equals(lub);
+  }
+
+  /**
+   * Determines the partial order of the given {@link TypeLatticeElement}s.
+   *
+   * @param appInfo {@link AppInfo} to compute the least upper bound of {@link TypeLatticeElement}
+   * @param l1 subject {@link TypeLatticeElement}
+   * @param l2 expected to be bigger than or equal to {@param l1}
+   * @return {@code true} if {@param l1} is less than or equal to {@param l2}.
+   */
+  public static boolean lessThanOrEqual(
+      AppInfo appInfo, TypeLatticeElement l1, TypeLatticeElement l2) {
+    return l1.equals(l2) || strictlyLessThan(appInfo, l1, l2);
   }
 
   /**
    * Represents a type that can be everything.
    *
-   * @return true if the corresponding {@link Value} could be any kinds.
+   * @return {@code true} if the corresponding {@link Value} could be any kinds.
    */
   public boolean isTop() {
     return false;
@@ -153,7 +187,7 @@
   /**
    * Represents an empty type.
    *
-   * @return true if the type of corresponding {@link Value} is not determined yet.
+   * @return {@code true} if the type of corresponding {@link Value} is not determined yet.
    */
   boolean isBottom() {
     return false;
@@ -212,7 +246,15 @@
   }
 
   public TypeLatticeElement checkCast(AppInfo appInfo, DexType castType) {
-    return fromDexType(castType, isNullable());
+    TypeLatticeElement castTypeLattice = fromDexType(castType, isNullable());
+    // Special case: casting null.
+    if (mustBeNull()) {
+      return castTypeLattice;
+    }
+    if (lessThanOrEqual(appInfo, this, castTypeLattice)) {
+      return this;
+    }
+    return castTypeLattice;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 8fe89bb..8d5857a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -182,6 +182,14 @@
     }
   }
 
+  public void swapSuccessors(BasicBlock a, BasicBlock b) {
+    assert a != b;
+    int aIndex = successors.indexOf(a);
+    int bIndex = successors.indexOf(b);
+    assert aIndex >= 0 && bIndex >= 0;
+    swapSuccessorsByIndex(aIndex, bIndex);
+  }
+
   public void swapSuccessorsByIndex(int index1, int index2) {
     assert index1 != index2;
     if (hasCatchHandlers()) {
@@ -392,6 +400,10 @@
     return instructions;
   }
 
+  public boolean isEmpty() {
+    return instructions.isEmpty();
+  }
+
   public Instruction entry() {
     return instructions.get(0);
   }
@@ -1049,20 +1061,25 @@
     return hare;
   }
 
-  public boolean isSimpleAlwaysThrowingPath() {
+  public boolean isSimpleAlwaysThrowingPath(boolean failOnMissingPosition) {
     // See Floyd's cycle-finding algorithm for reference.
     BasicBlock hare = this;
     BasicBlock tortuous = this;
     boolean advance = false;
     while (true) {
       List<BasicBlock> normalSuccessors = hare.getNormalSuccessors();
-      if (normalSuccessors.size() == 0) {
-        return hare.exit().isThrow();
-      }
       if (normalSuccessors.size() > 1) {
         return false;
       }
 
+      if (failOnMissingPosition && hasThrowingInstructionWithoutPosition(hare)) {
+        return false;
+      }
+
+      if (normalSuccessors.size() == 0) {
+        return hare.exit().isThrow();
+      }
+
       hare = normalSuccessors.get(0);
       tortuous = advance ? tortuous.getNormalSuccessors().get(0) : tortuous;
       advance = !advance;
@@ -1072,6 +1089,15 @@
     }
   }
 
+  private boolean hasThrowingInstructionWithoutPosition(BasicBlock hare) {
+    for (Instruction instruction : hare.instructions) {
+      if (instruction.instructionTypeCanThrow() && instruction.getPosition().isNone()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public Position getPosition() {
     BasicBlock block = endOfGotoChain();
     return block != null ? block.entry().getPosition() : Position.none();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Invoke.java b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
index d3ccf16..7f24908 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Invoke.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Invoke.java
@@ -10,14 +10,12 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItem;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.DexBuilder;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import java.util.List;
 import java.util.function.Function;
 
@@ -230,10 +228,6 @@
     return "Invoke-" + getTypeString();
   }
 
-  // This method is used for inlining and/or other optimizations, such as value propagation.
-  // It returns the target method iff this invoke has only one target.
-  abstract public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo);
-
   @Override
   public boolean isInvoke() {
     return true;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index 0622639..38d83ed 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.code.InvokeCustomRange;
 import com.android.tools.r8.graph.DexCallSite;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -47,12 +46,6 @@
   }
 
   @Override
-  public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
-    // Target method can not be known at compile time.
-    return null;
-  }
-
-  @Override
   public void buildDex(DexBuilder builder) {
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index f4ab435..60aee95 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -34,8 +35,10 @@
   }
 
   @Override
-  public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
-    return appInfo.lookupSingleInterfaceTarget(getInvokedMethod());
+  public DexEncodedMethod computeSingleTarget(
+      AppInfoWithLiveness appInfo, TypeEnvironment typeEnvironment, DexType invocationContext) {
+    DexType refinedReceiverType = typeEnvironment.getRefinedReceiverType(this);
+    return appInfo.lookupSingleInterfaceTarget(getInvokedMethod(), refinedReceiverType);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index c9a63d0..eb58c4d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -10,6 +10,8 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
@@ -60,12 +62,28 @@
     return this;
   }
 
+  // TODO(jsjeon): merge lookupSingleTarget and computeSingleTarget.
   public abstract DexEncodedMethod lookupSingleTarget(AppInfoWithLiveness appInfo,
       DexType invocationContext);
 
   public abstract Collection<DexEncodedMethod> lookupTargets(AppInfoWithSubtyping appInfo,
       DexType invocationContext);
 
+  // This method is used for inlining and/or other optimizations, such as value propagation.
+  // It returns the target method iff this invoke has only one target.
+  public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
+    // TODO(jsjeon): revisit all usage of this method and pass proper invocation context.
+    return computeSingleTarget(appInfo, TypeAnalysis.getDefaultTypeEnvironment(), null);
+  }
+
+  // TODO(b/72693244): By annotating type lattice to value, avoid passing type env.
+  public DexEncodedMethod computeSingleTarget(
+      AppInfoWithLiveness appInfo, TypeEnvironment typeEnvironment, DexType invocationContext) {
+    // In subclasses, e.g., invoke-virtual or invoke-super, use a narrower receiver type by using
+    // receiver type and type environment or invocation context---where the current invoke is.
+    return lookupSingleTarget(appInfo, appInfo.dexItemFactory.objectType);
+  }
+
   @Override
   public abstract Constraint inliningConstraint(AppInfoWithLiveness info,
       DexType invocationContext);
@@ -120,7 +138,7 @@
     return result;
   }
 
-  public abstract InlineAction computeInlining(InliningOracle decider);
+  public abstract InlineAction computeInlining(InliningOracle decider, DexType invocationContext);
 
   @Override
   public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
index 3118308..95b3f45 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethodWithReceiver.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.InliningOracle;
 import java.util.List;
@@ -29,8 +30,8 @@
   }
 
   @Override
-  public final InlineAction computeInlining(InliningOracle decider) {
-    return decider.computeForInvokeWithReceiver(this);
+  public final InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+    return decider.computeForInvokeWithReceiver(this, invocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
index 07c7a8f..f3d41e2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMultiNewArray.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.cf.code.CfMultiANewArray;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -57,11 +56,6 @@
   }
 
   @Override
-  public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
-    return null;
-  }
-
-  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokeMultiNewArray()) {
       return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index aa04347..41e4ccf 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -9,7 +9,6 @@
 import com.android.tools.r8.code.FilledNewArrayRange;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -53,11 +52,6 @@
   }
 
   @Override
-  public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
-    return null;
-  }
-
-  @Override
   public void buildDex(DexBuilder builder) {
     com.android.tools.r8.code.Instruction instruction;
     int argumentRegisters = requiredArgumentRegisters();
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index cc81752..c0e1a30 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -116,7 +116,7 @@
   }
 
   @Override
-  public InlineAction computeInlining(InliningOracle decider) {
-    return decider.computeForInvokePolymorpic(this);
+  public InlineAction computeInlining(InliningOracle decider, DexType invocationContext) {
+    return decider.computeForInvokePolymorpic(this, invocationContext);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 638a335..1867d14 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -103,8 +103,8 @@
   }
 
   @Override
-  public InlineAction computeInlining(InliningOracle decider) {
-    return decider.computeForInvokeStatic(this);
+  public InlineAction computeInlining(InliningOracle decider, DexType inocationContext) {
+    return decider.computeForInvokeStatic(this, inocationContext);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index f6719b8..d3ef914 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -4,10 +4,12 @@
 package com.android.tools.r8.ir.code;
 
 import com.android.tools.r8.code.InvokeSuperRange;
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
@@ -32,9 +34,17 @@
   }
 
   @Override
-  public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
-    // TODO(b/70707023) Use lookupSuperTarget here once fixed.
-    return null;
+  public DexEncodedMethod computeSingleTarget(
+      AppInfoWithLiveness appInfo, TypeEnvironment typeEnvironment, DexType invocationContext) {
+    if (invocationContext == null) {
+      return null;
+    }
+    try {
+      return appInfo.lookupSuperTarget(getInvokedMethod(), invocationContext);
+    } catch (CompilationError ce) {
+      // In case of illegal invoke-super, ignore inlining/optimizing it by returning null here.
+      return null;
+    }
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index e3ffea8..8a41c4e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.conversion.CfBuilder;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -34,8 +35,10 @@
   }
 
   @Override
-  public DexEncodedMethod computeSingleTarget(AppInfoWithLiveness appInfo) {
-    return appInfo.lookupSingleVirtualTarget(getInvokedMethod());
+  public DexEncodedMethod computeSingleTarget(
+      AppInfoWithLiveness appInfo, TypeEnvironment typeEnvironment, DexType invocationContext) {
+    DexType refinedReceiverType = typeEnvironment.getRefinedReceiverType(this);
+    return appInfo.lookupSingleVirtualTarget(getInvokedMethod(), refinedReceiverType);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index a6fd739..c6fdfc2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -27,6 +27,11 @@
     this.type = type;
   }
 
+  @Override
+  public String toString() {
+    return super.toString() + " " + type.toString();
+  }
+
   public Value dest() {
     return outValue;
   }
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 3b989da..bb4b629 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
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
@@ -33,7 +34,7 @@
 import com.android.tools.r8.ir.optimize.Inliner;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.ir.optimize.MemberValuePropagation;
-import com.android.tools.r8.ir.optimize.NonNullMarker;
+import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.ir.optimize.Outliner;
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -81,7 +82,7 @@
   private final CodeRewriter codeRewriter;
   private final MemberValuePropagation memberValuePropagation;
   private final LensCodeRewriter lensCodeRewriter;
-  private final NonNullMarker nonNullMarker;
+  private final NonNullTracker nonNullTracker;
   private final Inliner inliner;
   private final ProtoLitePruner protoLiteRewriter;
   private final IdentifierNameStringMarker identifierNameStringMarker;
@@ -112,17 +113,18 @@
             ? new InterfaceMethodRewriter(this, options) : null;
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasLiveness();
-      this.nonNullMarker = new NonNullMarker();
+      this.nonNullTracker = new NonNullTracker();
       this.inliner = new Inliner(appInfo.withLiveness(), graphLense, options);
       this.outliner = new Outliner(appInfo.withLiveness(), options);
       this.memberValuePropagation =
-          options.propagateMemberValue ? new MemberValuePropagation(appInfo.withLiveness()) : null;
+          options.enableValuePropagation ?
+              new MemberValuePropagation(appInfo.withLiveness()) : null;
       this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
       if (appInfo.hasLiveness()) {
         // When disabling the pruner here, also disable the ProtoLiteExtension in R8.java.
         this.protoLiteRewriter =
             options.forceProguardCompatibility ? null : new ProtoLitePruner(appInfo.withLiveness());
-        if (!appInfo.withLiveness().identifierNameStrings.isEmpty() && !options.skipMinification) {
+        if (!appInfo.withLiveness().identifierNameStrings.isEmpty() && options.enableMinification) {
           this.identifierNameStringMarker = new IdentifierNameStringMarker(appInfo.withLiveness());
         } else {
           this.identifierNameStringMarker = null;
@@ -132,7 +134,7 @@
         this.identifierNameStringMarker = null;
       }
     } else {
-      this.nonNullMarker = null;
+      this.nonNullTracker = null;
       this.inliner = null;
       this.outliner = null;
       this.memberValuePropagation = null;
@@ -377,7 +379,9 @@
 
     // Second inlining pass for dealing with double inline callers.
     if (inliner != null) {
-      inliner.processDoubleInlineCallers(this, ignoreOptimizationFeedback);
+      // Use direct feedback still, since methods after inlining may
+      // change their status or other properties.
+      inliner.processDoubleInlineCallers(this, directFeedback);
     }
 
     synthesizeLambdaClasses(builder);
@@ -554,25 +558,26 @@
     if (memberValuePropagation != null) {
       memberValuePropagation.rewriteWithConstantValues(code, method.method.holder);
     }
-    if (options.removeSwitchMaps && appInfo.hasLiveness()) {
+    if (options.enableSwitchMapRemoval && appInfo.hasLiveness()) {
       codeRewriter.removeSwitchMaps(code);
     }
     if (options.disableAssertions) {
       codeRewriter.disableAssertions(code);
     }
-    if (options.addNonNull && nonNullMarker != null) {
-      nonNullMarker.addNonNull(code);
+    if (options.enableNonNullTracking && nonNullTracker != null) {
+      nonNullTracker.addNonNull(code);
       assert code.isConsistentSSA();
     }
-    TypeAnalysis typeAnalysis = new TypeAnalysis(appInfo, method, code);
-    if (options.inlineAccessors && inliner != null) {
+    TypeEnvironment typeEnvironment = TypeAnalysis.getDefaultTypeEnvironment();
+    if (options.enableInlining && inliner != null) {
+      typeEnvironment = new TypeAnalysis(appInfo, method, code);
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       assert !options.debug;
       inliner.performInlining(
-          method, code, typeAnalysis, isProcessedConcurrently, callSiteInformation);
+          method, code, typeEnvironment, isProcessedConcurrently, callSiteInformation);
     }
     // TODO(b/69962188): MethodDevirtualizer can perform optimizations using type analysis.
-    codeRewriter.removeCasts(code, typeAnalysis);
+    codeRewriter.removeCasts(code, typeEnvironment);
     codeRewriter.rewriteLongCompareAndRequireNonNull(code, options);
     codeRewriter.commonSubexpressionElimination(code);
     codeRewriter.simplifyArrayConstruction(code);
@@ -581,9 +586,9 @@
     new SparseConditionalConstantPropagation(code).run();
     codeRewriter.rewriteSwitch(code);
     codeRewriter.processMethodsNeverReturningNormally(code);
-    codeRewriter.simplifyIf(code, typeAnalysis);
-    if (options.addNonNull && nonNullMarker != null) {
-      nonNullMarker.cleanupNonNull(code);
+    codeRewriter.simplifyIf(code, typeEnvironment);
+    if (options.enableNonNullTracking && nonNullTracker != null) {
+      nonNullTracker.cleanupNonNull(code);
       assert code.isConsistentSSA();
     }
     if (!options.debug) {
@@ -671,7 +676,7 @@
   private void markProcessed(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     // After all the optimizations have take place, we compute whether method should be inlinedex.
     Constraint state;
-    if (!options.inlineAccessors || inliner == null) {
+    if (!options.enableInlining || inliner == null) {
       state = Constraint.NEVER;
     } else {
       state = inliner.computeInliningConstraint(code, method);
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 9cd430a..cdb419f 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
@@ -30,6 +30,7 @@
 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.NewInstance;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
@@ -196,7 +197,15 @@
                 newArrayEmpty.size(), newType);
             iterator.replaceCurrentInstruction(newNewArray);
           }
-        }
+        } else if (current.isNewInstance()) {
+            NewInstance newInstance= current.asNewInstance();
+            DexType newClazz = graphLense.lookupType(newInstance.clazz, method);
+            if (newClazz != newInstance.clazz) {
+              NewInstance newNewInstance =
+                  new NewInstance(newClazz, makeOutValue(newInstance, code));
+              iterator.replaceCurrentInstruction(newNewInstance);
+            }
+          }
       }
     }
     assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
index 267a93f..1b29233 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedback.java
@@ -13,5 +13,5 @@
   void methodNeverReturnsNull(DexEncodedMethod method);
   void methodNeverReturnsNormally(DexEncodedMethod method);
   void markProcessed(DexEncodedMethod method, Constraint state);
-  void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method);
+  void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark);
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
index 93e48cc..1fd3a2f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackDirect.java
@@ -35,7 +35,7 @@
   }
 
   @Override
-  public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method) {
-    method.markCheckNullReceiverBeforeAnySideEffect();
+  public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {
+    method.markCheckNullReceiverBeforeAnySideEffect(mark);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
index 5a6bd66..e409818 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/OptimizationFeedbackIgnore.java
@@ -25,5 +25,5 @@
   public void markProcessed(DexEncodedMethod method, Constraint state) {}
 
   @Override
-  public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method) {}
+  public void markCheckNullReceiverBeforeAnySideEffect(DexEncodedMethod method, boolean mark) {}
 }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index bb29d3d..05a4d56 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -302,7 +302,9 @@
     assert implMethod.holder == accessedFrom;
     assert descriptor.targetFoundInClass(accessedFrom);
     assert descriptor.getAccessibility() != null;
-    assert descriptor.getAccessibility().isPrivate();
+    // When coming from javac these are also private, but we don't assert that, as the
+    // accessibility could have been modified (e.g. due to -allowaccessmodification).
+
     assert descriptor.getAccessibility().isSynthetic();
 
     if (implHandle.type.isInvokeStatic()) {
@@ -493,9 +495,9 @@
           // TODO(ager): Should we give the new first parameter an actual name? Maybe 'this'?
           encodedMethod.accessFlags.setStatic();
           encodedMethod.accessFlags.unsetPrivate();
-          if (implMethodHolder.isInterface()) {
-            encodedMethod.accessFlags.setPublic();
-          }
+          // Always make the method public to provide access when r8 minification is allowed to move
+          // the lambda class accessing this method to another package (-allowaccessmodification).
+          encodedMethod.accessFlags.setPublic();
           DexCode dexCode = newMethod.getCode().asDexCode();
           dexCode.setDebugInfo(dexCode.debugInfoWithAdditionalFirstParameter(null));
           assert (dexCode.getDebugInfo() == null)
@@ -522,11 +524,11 @@
       DexProgramClass accessorClass = programDefinitionFor(callTarget.holder);
       assert accessorClass != null;
 
+      // Always make the method public to provide access when r8 minification is allowed to move
+      // the lambda class accessing this method to another package (-allowaccessmodification).
       MethodAccessFlags accessorFlags =
           MethodAccessFlags.fromSharedAccessFlags(
-              Constants.ACC_SYNTHETIC
-                  | Constants.ACC_STATIC
-                  | (accessorClass.isInterface() ? Constants.ACC_PUBLIC : 0),
+              Constants.ACC_SYNTHETIC | Constants.ACC_STATIC | Constants.ACC_PUBLIC,
               false);
       DexEncodedMethod accessorEncodedMethod = new DexEncodedMethod(
           callTarget, accessorFlags, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(),
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 910de36..769228d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -145,6 +145,15 @@
     return true;
   }
 
+  private static boolean isFallthroughBlock(BasicBlock block) {
+    for (BasicBlock pred : block.getPredecessors()) {
+      if (pred.exit().fallthroughBlock() == block) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private static void collapsTrivialGoto(
       IRCode code, BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove) {
 
@@ -156,21 +165,18 @@
     BasicBlock target = block.endOfGotoChain();
 
     boolean needed = false;
+
     if (target == null) {
       // This implies we are in a loop of GOTOs. In that case, we will iteratively remove each
       // trivial GOTO one-by-one until the above base case (one block targeting itself) is left.
       target = block.exit().asGoto().getTarget();
-    } else if (target != nextBlock) {
+    }
+
+    if (target != nextBlock) {
       // Not targeting the fallthrough block, determine if we need this goto. We need it if
       // a fallthrough can hit this block. That is the case if the block is the entry block
       // or if one of the predecessors fall through to the block.
-      needed = code.blocks.get(0) == block;
-      for (BasicBlock pred : block.getPredecessors()) {
-        if (pred.exit().fallthroughBlock() == block) {
-          needed = true;
-          break;
-        }
-      }
+      needed = code.blocks.get(0) == block || isFallthroughBlock(block);
     }
 
     if (!needed) {
@@ -707,9 +713,8 @@
       DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     if (!method.isStaticMethod()) {
       final Value receiver = code.getThis();
-      if (receiver.isUsed() && allPathsCheckNullReceiverBeforeSideEffect(code, receiver)) {
-        feedback.markCheckNullReceiverBeforeAnySideEffect(method);
-      }
+      feedback.markCheckNullReceiverBeforeAnySideEffect(method,
+          receiver.isUsed() && allPathsCheckNullReceiverBeforeSideEffect(code, receiver));
     }
   }
 
@@ -1118,27 +1123,37 @@
         continue;
       }
 
-      DexType inType = typeEnvironment.getObjectType(inValue);
-      DexType outType = typeEnvironment.getObjectType(outValue);
-      // Be careful about a down-cast verification error:
-      // A < B < C
-      // c = ...        // Even though we know c is of type A,
-      // a' = (B) c;    // (this could be removed, since chained below.)
-      // a'' = (A) a';  // this should remain for runtime verification.
-      if (outType.isStrictSubtypeOf(inType, appInfo)) {
-        continue;
-      }
-      // 1) Trivial cast.
-      //   A a = ...
-      //   A a' = (A) a;
-      // 2) Up-cast: we already have finer type info.
-      //   A < B
-      //   A a = ...
-      //   B b = (B) a;
-      if (inType.isSubtypeOf(castType, appInfo)) {
-        needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
-        outValue.replaceUsers(inValue);
-        it.removeOrReplaceByDebugLocalRead();
+      TypeLatticeElement inTypeLattice = typeEnvironment.getLatticeElement(inValue);
+      if (!inTypeLattice.isTop()) {
+        TypeLatticeElement outTypeLattice = typeEnvironment.getLatticeElement(outValue);
+        TypeLatticeElement castTypeLattice =
+            TypeLatticeElement.fromDexType(castType, inTypeLattice.isNullable());
+        // Special case: null cast, e.g., getMethod(..., (Class[]) null);
+        // This cast should be kept no matter what.
+        if (inTypeLattice.mustBeNull()) {
+          assert outTypeLattice.equals(castTypeLattice);
+          continue;
+        }
+        // 1) Trivial cast.
+        //   A a = ...
+        //   A a' = (A) a;
+        // 2) Up-cast: we already have finer type info.
+        //   A < B
+        //   A a = ...
+        //   B b = (B) a;
+        if (TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, castTypeLattice)) {
+          assert outTypeLattice.equals(inTypeLattice);
+          needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
+          outValue.replaceUsers(inValue);
+          it.removeOrReplaceByDebugLocalRead();
+          continue;
+        }
+        // Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
+        // A < B < C
+        // c = ...        // Even though we know c is of type A,
+        // a' = (B) c;    // (this could be removed, since chained below.)
+        // a'' = (A) a';  // this should remain for runtime verification.
+        assert outTypeLattice.equals(castTypeLattice);
       }
     }
     // ... v1
@@ -1419,7 +1434,7 @@
           // and the dominator or the original block has catch handlers.
           continue;
         }
-        if (!dominator.isSimpleAlwaysThrowingPath()) {
+        if (!dominator.isSimpleAlwaysThrowingPath(false)) {
           // Only move string constants into blocks being part of simple
           // always throwing path.
           continue;
@@ -1859,11 +1874,16 @@
 
   public void simplifyIf(IRCode code, TypeEnvironment typeEnvironment) {
     int color = code.reserveMarkingColor();
+    boolean ifBranchFlipped = false;
     for (BasicBlock block : code.blocks) {
       if (block.isMarked(color)) {
         continue;
       }
       if (block.exit().isIf()) {
+        // Flip then/else branches if needed.
+        if (flipIfBranchesIfNeeded(block, code.hasDebugPositions)) {
+          ifBranchFlipped = true;
+        }
         // First rewrite zero comparison.
         rewriteIfWithConstZero(block);
 
@@ -1875,62 +1895,64 @@
         If theIf = block.exit().asIf();
         List<Value> inValues = theIf.inValues();
 
-        int cond;
-
         if (inValues.get(0).isConstNumber()
             && (theIf.isZeroTest() || inValues.get(1).isConstNumber())) {
           // Zero test with a constant of comparison between between two constants.
           if (theIf.isZeroTest()) {
-            cond = inValues.get(0).getConstInstruction().asConstNumber().getIntValue();
+            int cond = inValues.get(0).getConstInstruction().asConstNumber().getIntValue();
+            simplifyIfWithKnownCondition(code, block, theIf, cond, color);
           } else {
             long left = (long) inValues.get(0).getConstInstruction().asConstNumber().getIntValue();
             long right = (long) inValues.get(1).getConstInstruction().asConstNumber().getIntValue();
-            cond = Long.signum(left - right);
+            simplifyIfWithKnownCondition(code, block, theIf, Long.signum(left - right), color);
           }
         } else if (inValues.get(0).hasValueRange()
             && (theIf.isZeroTest() || inValues.get(1).hasValueRange())) {
           // Zero test with a value range, or comparison between between two values,
           // each with a value ranges.
           if (theIf.isZeroTest()) {
-            if (inValues.get(0).isValueInRange(0)) {
-              // Zero in in the range - can't determine the comparison.
-              continue;
+            if (!inValues.get(0).isValueInRange(0)) {
+              int cond = Long.signum(inValues.get(0).getValueRange().getMin());
+              simplifyIfWithKnownCondition(code, block, theIf, cond, color);
             }
-            cond = Long.signum(inValues.get(0).getValueRange().getMin());
           } else {
             LongInterval leftRange = inValues.get(0).getValueRange();
             LongInterval rightRange = inValues.get(1).getValueRange();
-            if (leftRange.overlapsWith(rightRange)) {
-              // Ranges overlap - can't determine the comparison.
-              continue;
+            if (!leftRange.overlapsWith(rightRange)) {
+              int cond = Long.signum(leftRange.getMin() - rightRange.getMin());
+              simplifyIfWithKnownCondition(code, block, theIf, cond, color);
             }
-            // There is no overlap.
-            cond = Long.signum(leftRange.getMin() - rightRange.getMin());
           }
         } else if (theIf.isZeroTest() && !inValues.get(0).isConstNumber()) {
-          // TODO(b/72693244): annotate type lattice to value
-          TypeLatticeElement l = typeEnvironment.getLatticeElement(inValues.get(0));
-          if (!l.isPrimitive() && !l.isNullable()) {
-            // Any non-zero value should work.
-            cond = 1;
+          if (inValues.get(0).isNeverNull()) {
+            simplifyIfWithKnownCondition(code, block, theIf, 1, color);
           } else {
-            // We are still not sure.
-            continue;
+            // TODO(b/72693244): annotate type lattice to value
+            TypeLatticeElement l = typeEnvironment.getLatticeElement(inValues.get(0));
+            if (!l.isPrimitive() && !l.isNullable()) {
+              // Any non-zero value should work.
+              simplifyIfWithKnownCondition(code, block, theIf, 1, color);
+            }
           }
-        } else {
-          continue;
         }
-        BasicBlock target = theIf.targetFromCondition(cond);
-        BasicBlock deadTarget =
-            target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
-        rewriteIfToGoto(code, block, theIf, target, deadTarget, color);
       }
     }
     code.removeMarkedBlocks(color);
     code.returnMarkingColor(color);
+    if (ifBranchFlipped) {
+      code.traceBlocks();
+    }
     assert code.isConsistentSSA();
   }
 
+  private void simplifyIfWithKnownCondition(
+      IRCode code, BasicBlock block, If theIf, int cond, int markingColor) {
+    BasicBlock target = theIf.targetFromCondition(cond);
+    BasicBlock deadTarget =
+        target == theIf.getTrueTarget() ? theIf.fallthroughBlock() : theIf.getTrueTarget();
+    rewriteIfToGoto(code, block, theIf, target, deadTarget, markingColor);
+  }
+
   // Find all method invocations that never returns normally, split the block
   // after each such invoke instruction and follow it with a block throwing a
   // null value (which should result in NPE). Note that this throw is not
@@ -1951,12 +1973,12 @@
       InstructionListIterator insnIterator = block.listIterator();
       while (insnIterator.hasNext()) {
         Instruction insn = insnIterator.next();
-        if (!insn.isInvoke()) {
+        if (!insn.isInvokeMethod()) {
           continue;
         }
 
         DexEncodedMethod singleTarget =
-            insn.asInvoke().computeSingleTarget(appInfoWithLiveness);
+            insn.asInvokeMethod().computeSingleTarget(appInfoWithLiveness);
         if (singleTarget == null || !singleTarget.getOptimizationInfo().neverReturnsNormally()) {
           continue;
         }
@@ -2185,6 +2207,28 @@
     }
   }
 
+  private boolean flipIfBranchesIfNeeded(BasicBlock block, boolean failOnMissingPosition) {
+    If theIf = block.exit().asIf();
+    BasicBlock trueTarget = theIf.getTrueTarget();
+    BasicBlock fallthrough = theIf.fallthroughBlock();
+    assert trueTarget != fallthrough;
+
+    if (!fallthrough.isSimpleAlwaysThrowingPath(failOnMissingPosition) ||
+        trueTarget.isSimpleAlwaysThrowingPath(failOnMissingPosition)) {
+      return false;
+    }
+
+    // In case fall-through block always trows there is a good chance that it
+    // is created for error checks and 'trueTarget' represents most more common
+    // non-error case. Flipping the if in this case may result in faster code
+    // on older Android versions.
+    List<Value> inValues = theIf.inValues();
+    If newIf = new If(theIf.getType().inverted(), inValues);
+    block.replaceLastInstruction(newIf);
+    block.swapSuccessors(trueTarget, fallthrough);
+    return true;
+  }
+
   public void rewriteLongCompareAndRequireNonNull(IRCode code, InternalOptions options) {
     if (options.canUseLongCompareAndObjectsNonNull()) {
       return;
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 abf73b2..da28ce0 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
@@ -13,7 +13,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
-import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
+import com.android.tools.r8.ir.analysis.type.TypeEnvironment;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -53,10 +53,22 @@
   private final Set<DexEncodedMethod> doubleInlineSelectedTargets = Sets.newIdentityHashSet();
   private final Map<DexEncodedMethod, DexEncodedMethod> doubleInlineeCandidates = new HashMap<>();
 
+  private final Set<DexMethod> blackList = Sets.newIdentityHashSet();
+
   public Inliner(AppInfoWithLiveness appInfo, GraphLense graphLense, InternalOptions options) {
     this.appInfo = appInfo;
     this.graphLense = graphLense;
     this.options = options;
+    fillInBlackList(appInfo);
+  }
+
+  private void fillInBlackList(AppInfoWithLiveness appInfo) {
+    blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException);
+    blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwNpe);
+  }
+
+  public boolean isBlackListed(DexMethod method) {
+    return blackList.contains(method);
   }
 
   private Constraint instructionAllowedForInlining(
@@ -105,6 +117,12 @@
     return target.isSamePackage(context);
   }
 
+  synchronized boolean isDoubleInliningTarget(
+      CallSiteInformation callSiteInformation, DexEncodedMethod candidate) {
+    return callSiteInformation.hasDoubleCallSite(candidate)
+        || doubleInlineSelectedTargets.contains(candidate);
+  }
+
   synchronized DexEncodedMethod doubleInlining(DexEncodedMethod method,
       DexEncodedMethod target) {
     if (!applyDoubleInlining) {
@@ -191,6 +209,14 @@
 
     public static Constraint classIsVisible(DexType context, DexType clazz,
         AppInfoWithSubtyping appInfo) {
+      if (clazz.isArrayType()) {
+        return classIsVisible(context, clazz.toArrayElementType(appInfo.dexItemFactory), appInfo);
+      }
+
+      if (clazz.isPrimitiveType()) {
+        return ALWAYS;
+      }
+
       DexClass definition = appInfo.definitionFor(clazz);
       return definition == null ? NEVER
           : deriveConstraint(context, clazz, definition.accessFlags, appInfo);
@@ -243,8 +269,8 @@
       if (!target.isProcessed()) {
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
       }
-      if (options.addNonNull) {
-        new NonNullMarker().addNonNull(code);
+      if (options.enableNonNullTracking) {
+        new NonNullTracker().addNonNull(code);
       }
       return code;
     }
@@ -339,7 +365,7 @@
   public void performInlining(
       DexEncodedMethod method,
       IRCode code,
-      TypeAnalysis typeAnalysis,
+      TypeEnvironment typeEnvironment,
       Predicate<DexEncodedMethod> isProcessedConcurrently,
       CallSiteInformation callSiteInformation)
       throws ApiLevelException {
@@ -349,7 +375,7 @@
       return;
     }
     InliningOracle oracle = new InliningOracle(
-        this, method, typeAnalysis, callSiteInformation, isProcessedConcurrently);
+        this, method, typeEnvironment, callSiteInformation, isProcessedConcurrently);
 
     List<BasicBlock> blocksToRemove = new ArrayList<>();
     ListIterator<BasicBlock> blockIterator = code.listIterator();
@@ -363,7 +389,7 @@
         Instruction current = iterator.next();
         if (current.isInvokeMethod()) {
           InvokeMethod invoke = current.asInvokeMethod();
-          InlineAction result = invoke.computeInlining(oracle);
+          InlineAction result = invoke.computeInlining(oracle, method.method.holder);
           if (result != null) {
             DexEncodedMethod target = result.target;
             Position invokePosition = invoke.getPosition();
@@ -390,7 +416,7 @@
                   Log.verbose(getClass(), "Forcing extra inline on " + target.toSourceString());
                 }
                 performInlining(
-                    target, inlinee, typeAnalysis, isProcessedConcurrently, callSiteInformation);
+                    target, inlinee, typeEnvironment, isProcessedConcurrently, callSiteInformation);
               }
               // Make sure constructor inlining is legal.
               assert !target.isClassInitializer();
@@ -413,13 +439,19 @@
               if (instruction_allowance >= 0 || result.ignoreInstructionBudget()) {
                 iterator.inlineInvoke(code, inlinee, blockIterator, blocksToRemove, downcast);
                 // Update type env for inlined blocks.
-                typeAnalysis.updateBlocks(inlinee.topologicallySortedBlocks());
+                typeEnvironment.analyzeBlocks(inlinee.topologicallySortedBlocks());
                 // TODO(b/69964136): need a test where refined env in inlinee affects the caller.
-              }
-              // If we inlined the invoke from a bridge method, it is no longer a bridge method.
-              if (method.accessFlags.isBridge()) {
-                method.accessFlags.unsetSynthetic();
-                method.accessFlags.unsetBridge();
+
+                // If we inlined the invoke from a bridge method, it is no longer a bridge method.
+                if (method.accessFlags.isBridge()) {
+                  method.accessFlags.unsetSynthetic();
+                  method.accessFlags.unsetBridge();
+                }
+
+                // Record that the current method uses identifier name string if the inlinee did so.
+                if (target.getOptimizationInfo().useIdentifierNameString()) {
+                  method.markUseIdentifierNameString();
+                }
               }
             }
           }
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 de39887..0e3228c 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
@@ -53,8 +53,9 @@
     }
   }
 
-  private DexEncodedMethod validateCandidate(InvokeMethod invoke) {
-    DexEncodedMethod candidate = invoke.computeSingleTarget(inliner.appInfo);
+  private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
+    DexEncodedMethod candidate =
+        invoke.computeSingleTarget(inliner.appInfo, typeEnvironment, invocationContext);
     if ((candidate == null)
         || (candidate.getCode() == null)
         || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
@@ -129,7 +130,7 @@
 
   private synchronized boolean isDoubleInliningTarget(DexEncodedMethod candidate) {
     // 10 is found from measuring.
-    return callSiteInformation.hasDoubleCallSite(candidate)
+    return inliner.isDoubleInliningTarget(callSiteInformation, candidate)
         && candidate.getCode().isDexCode()
         && (candidate.getCode().asDexCode().instructions.length <= 10);
   }
@@ -202,9 +203,10 @@
     return true;
   }
 
-  public InlineAction computeForInvokeWithReceiver(InvokeMethodWithReceiver invoke) {
-    DexEncodedMethod candidate = validateCandidate(invoke);
-    if (candidate == null) {
+  public InlineAction computeForInvokeWithReceiver(
+      InvokeMethodWithReceiver invoke, DexType invocationContext) {
+    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+    if (candidate == null || inliner.isBlackListed(candidate.method)) {
       return null;
     }
 
@@ -242,9 +244,9 @@
     return new InlineAction(candidate, invoke, reason);
   }
 
-  public InlineAction computeForInvokeStatic(InvokeStatic invoke) {
-    DexEncodedMethod candidate = validateCandidate(invoke);
-    if (candidate == null) {
+  public InlineAction computeForInvokeStatic(InvokeStatic invoke, DexType invocationContext) {
+    DexEncodedMethod candidate = validateCandidate(invoke, invocationContext);
+    if (candidate == null || inliner.isBlackListed(candidate.method)) {
       return null;
     }
 
@@ -276,7 +278,8 @@
     return new InlineAction(candidate, invoke, reason);
   }
 
-  public InlineAction computeForInvokePolymorpic(InvokePolymorphic invoke) {
+  public InlineAction computeForInvokePolymorpic(
+      InvokePolymorphic invoke, DexType invocationContext) {
     // TODO: No inlining of invoke polymorphic for now.
     if (info != null) {
       info.exclude(invoke, "inlining through invoke signature polymorpic is not supported");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullMarker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullMarker.java
deleted file mode 100644
index 1054343..0000000
--- a/src/main/java/com/android/tools/r8/ir/optimize/NonNullMarker.java
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize;
-
-import com.android.tools.r8.errors.Unreachable;
-import com.android.tools.r8.ir.code.BasicBlock;
-import com.android.tools.r8.ir.code.DominatorTree;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.InstructionListIterator;
-import com.android.tools.r8.ir.code.NonNull;
-import com.android.tools.r8.ir.code.Phi;
-import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.code.ValueType;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.Sets;
-import java.util.ListIterator;
-import java.util.Set;
-
-public class NonNullMarker {
-
-  public NonNullMarker() {
-  }
-
-  @VisibleForTesting
-  static boolean throwsOnNullInput(Instruction instruction) {
-    return (instruction.isInvokeMethodWithReceiver() && !instruction.isInvokeDirect())
-        || instruction.isInstanceGet()
-        || instruction.isInstancePut()
-        || instruction.isArrayGet()
-        || instruction.isArrayPut()
-        || instruction.isMonitor();
-  }
-
-  private Value getNonNullInput(Instruction instruction) {
-    if (instruction.isInvokeMethodWithReceiver()) {
-      return instruction.asInvokeMethodWithReceiver().getReceiver();
-    } else if (instruction.isInstanceGet()) {
-      return instruction.asInstanceGet().object();
-    } else if (instruction.isInstancePut()) {
-      return instruction.asInstancePut().object();
-    } else if (instruction.isArrayGet()) {
-      return instruction.asArrayGet().array();
-    } else if (instruction.isArrayPut()) {
-      return instruction.asArrayPut().array();
-    } else if (instruction.isMonitor()) {
-      return instruction.asMonitor().object();
-    }
-    throw new Unreachable("Should conform to throwsOnNullInput.");
-  }
-
-  public void addNonNull(IRCode code) {
-    ListIterator<BasicBlock> blocks = code.blocks.listIterator();
-    while (blocks.hasNext()) {
-      BasicBlock block = blocks.next();
-      InstructionListIterator iterator = block.listIterator();
-      while (iterator.hasNext()) {
-        Instruction current = iterator.next();
-        if (!throwsOnNullInput(current)) {
-          continue;
-        }
-        Value knownToBeNonNullValue = getNonNullInput(current);
-        // Avoid adding redundant non-null instruction.
-        if (knownToBeNonNullValue.isNeverNull()) {
-          // Otherwise, we will have something like:
-          // non_null_rcv <- non-null(rcv)
-          // ...
-          // another_rcv <- non-null(non_null_rcv)
-          continue;
-        }
-        // First, if the current block has catch handler, split into two blocks, e.g.,
-        //
-        // ...x
-        // invoke(rcv, ...)
-        // ...y
-        //
-        //   ~>
-        //
-        // ...x
-        // invoke(rcv, ...)
-        // goto A
-        //
-        // A: ...y // nonNullSuccessor
-        //
-        BasicBlock nonNullSuccessor =
-            block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
-        // Next, add non-null fake IR, e.g.,
-        // ...x
-        // invoke(rcv, ...)
-        // goto A
-        // ...
-        // A: non_null_rcv <- non-null(rcv)
-        // ...y
-        Value nonNullValue =
-            code.createValue(ValueType.OBJECT, knownToBeNonNullValue.getLocalInfo());
-        NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
-        nonNull.setPosition(current.getPosition());
-        if (block.hasCatchHandlers()) {
-          // If we split, add non-null IR on top of the new split block.
-          nonNullSuccessor.listIterator().add(nonNull);
-        } else {
-          // Otherwise, just add it to the current position of the iterator.
-          iterator.add(nonNull);
-        }
-        // Then, replace all users of the original value that are dominated by either the current
-        // block or the new split-off block. Since NPE can be explicitly caught, nullness should be
-        // propagated through dominance.
-        Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
-        Set<Phi> phiUsers = knownToBeNonNullValue.uniquePhiUsers();
-        Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
-        Set<Phi> dominatedPhiUsers = Sets.newIdentityHashSet();
-        DominatorTree dominatorTree = new DominatorTree(code);
-        for (BasicBlock dominatee : dominatorTree.dominatedBlocks(nonNullSuccessor)) {
-          boolean passNonNullOrDontCare = dominatee != nonNullSuccessor;
-          InstructionListIterator dominateeIterator = dominatee.listIterator();
-          while (dominateeIterator.hasNext()) {
-            Instruction potentialUser = dominateeIterator.next();
-            // Avoid replacing values in instructions that happen *before* the current non-null.
-            if (!passNonNullOrDontCare) {
-              passNonNullOrDontCare = potentialUser == nonNull;
-            }
-            // Avoid replacing values in instructions we are referring.
-            if (potentialUser == current || potentialUser == nonNull) {
-              continue;
-            }
-            if (passNonNullOrDontCare && users.contains(potentialUser)) {
-              dominatedUsers.add(potentialUser);
-            }
-          }
-          for (Phi phi : dominatee.getPhis()) {
-            if (phiUsers.contains(phi)) {
-              dominatedPhiUsers.add(phi);
-            }
-          }
-        }
-        knownToBeNonNullValue.replaceSelectiveUsers(
-            nonNullValue, dominatedUsers, dominatedPhiUsers);
-      }
-    }
-  }
-
-  public void cleanupNonNull(IRCode code) {
-    InstructionIterator it = code.instructionIterator();
-    boolean needToCheckTrivialPhis = false;
-    while (it.hasNext()) {
-      Instruction instruction = it.next();
-      // non_null_rcv <- non-null(rcv)  // deleted
-      // ...
-      // non_null_rcv#foo
-      //
-      //  ~>
-      //
-      // rcv#foo
-      if (instruction.isNonNull()) {
-        NonNull nonNull = instruction.asNonNull();
-        Value src = nonNull.src();
-        Value dest = nonNull.dest();
-        needToCheckTrivialPhis = needToCheckTrivialPhis || dest.uniquePhiUsers().size() != 0;
-        dest.replaceUsers(src);
-        it.remove();
-      }
-    }
-    // non-null might introduce a phi, e.g.,
-    // non_null_rcv <- non-null(rcv)
-    // ...
-    // v <- phi(rcv, non_null_rcv)
-    //
-    // Cleaning up that non-null may result in a trivial phi:
-    // v <- phi(rcv, rcv)
-    if (needToCheckTrivialPhis) {
-      code.removeAllTrivialPhis();
-    }
-  }
-
-}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
new file mode 100644
index 0000000..9f15bc2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NonNullTracker.java
@@ -0,0 +1,245 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.DominatorTree;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.NonNull;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.Sets;
+import java.util.ListIterator;
+import java.util.Set;
+
+public class NonNullTracker {
+
+  public NonNullTracker() {
+  }
+
+  @VisibleForTesting
+  static boolean throwsOnNullInput(Instruction instruction) {
+    return (instruction.isInvokeMethodWithReceiver() && !instruction.isInvokeDirect())
+        || instruction.isInstanceGet()
+        || instruction.isInstancePut()
+        || instruction.isArrayGet()
+        || instruction.isArrayPut()
+        || instruction.isArrayLength()
+        || instruction.isMonitor();
+  }
+
+  private Value getNonNullInput(Instruction instruction) {
+    if (instruction.isInvokeMethodWithReceiver()) {
+      return instruction.asInvokeMethodWithReceiver().getReceiver();
+    } else if (instruction.isInstanceGet()) {
+      return instruction.asInstanceGet().object();
+    } else if (instruction.isInstancePut()) {
+      return instruction.asInstancePut().object();
+    } else if (instruction.isArrayGet()) {
+      return instruction.asArrayGet().array();
+    } else if (instruction.isArrayPut()) {
+      return instruction.asArrayPut().array();
+    } else if (instruction.isArrayLength()) {
+      return instruction.asArrayLength().array();
+    } else if (instruction.isMonitor()) {
+      return instruction.asMonitor().object();
+    }
+    throw new Unreachable("Should conform to throwsOnNullInput.");
+  }
+
+  public void addNonNull(IRCode code) {
+    ListIterator<BasicBlock> blocks = code.blocks.listIterator();
+    while (blocks.hasNext()) {
+      BasicBlock block = blocks.next();
+      // Add non-null after instructions that implicitly indicate receiver/array is not null.
+      InstructionListIterator iterator = block.listIterator();
+      while (iterator.hasNext()) {
+        Instruction current = iterator.next();
+        if (!throwsOnNullInput(current)) {
+          continue;
+        }
+        Value knownToBeNonNullValue = getNonNullInput(current);
+        // Avoid adding redundant non-null instruction.
+        if (knownToBeNonNullValue.isNeverNull()) {
+          // Otherwise, we will have something like:
+          // non_null_rcv <- non-null(rcv)
+          // ...
+          // another_rcv <- non-null(non_null_rcv)
+          continue;
+        }
+        // First, if the current block has catch handler, split into two blocks, e.g.,
+        //
+        // ...x
+        // invoke(rcv, ...)
+        // ...y
+        //
+        //   ~>
+        //
+        // ...x
+        // invoke(rcv, ...)
+        // goto A
+        //
+        // A: ...y // blockWithNonNullInstruction
+        //
+        BasicBlock blockWithNonNullInstruction =
+            block.hasCatchHandlers() ? iterator.split(code, blocks) : block;
+        // Next, add non-null fake IR, e.g.,
+        // ...x
+        // invoke(rcv, ...)
+        // goto A
+        // ...
+        // A: non_null_rcv <- non-null(rcv)
+        // ...y
+        Value nonNullValue =
+            code.createValue(ValueType.OBJECT, knownToBeNonNullValue.getLocalInfo());
+        NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
+        nonNull.setPosition(current.getPosition());
+        if (blockWithNonNullInstruction !=  block) {
+          // If we split, add non-null IR on top of the new split block.
+          blockWithNonNullInstruction.listIterator().add(nonNull);
+        } else {
+          // Otherwise, just add it to the current block at the position of the iterator.
+          iterator.add(nonNull);
+        }
+        // Then, replace all users of the original value that are dominated by either the current
+        // block or the new split-off block. Since NPE can be explicitly caught, nullness should be
+        // propagated through dominance.
+        Set<Instruction> users = knownToBeNonNullValue.uniqueUsers();
+        Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+        Set<Phi> dominatedPhiUsers = Sets.newIdentityHashSet();
+        DominatorTree dominatorTree = new DominatorTree(code);
+        Set<BasicBlock> dominatedBlocks = Sets.newIdentityHashSet();
+        for (BasicBlock dominatee : dominatorTree.dominatedBlocks(blockWithNonNullInstruction)) {
+          dominatedBlocks.add(dominatee);
+          InstructionListIterator dominateeIterator = dominatee.listIterator();
+          if (dominatee == blockWithNonNullInstruction) {
+            // In the block with the inserted non null instruction, skip instructions up to and
+            // including the newly inserted instruction.
+            dominateeIterator.nextUntil(instruction -> instruction == nonNull);
+          }
+          while (dominateeIterator.hasNext()) {
+            Instruction potentialUser = dominateeIterator.next();
+            assert potentialUser != nonNull;
+            if (users.contains(potentialUser)) {
+              dominatedUsers.add(potentialUser);
+            }
+          }
+        }
+        for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
+          if (dominatedBlocks.contains(user.getBlock())) {
+            dominatedPhiUsers.add(user);
+          }
+        }
+        knownToBeNonNullValue.replaceSelectiveUsers(
+            nonNullValue, dominatedUsers, dominatedPhiUsers);
+      }
+
+      // Add non-null on top of the successor block if the current block ends with a null check.
+      if (block.exit().isIf() && block.exit().asIf().isZeroTest()) {
+        // if v EQ blockX
+        // ... (fallthrough)
+        // blockX: ...
+        //
+        //   ~>
+        //
+        // if v EQ blockX
+        // non_null_value <- non-null(v)
+        // ...
+        // blockX: ...
+        //
+        // or
+        //
+        // if v NE blockY
+        // ...
+        // blockY: ...
+        //
+        //   ~>
+        //
+        // blockY: non_null_value <- non-null(v)
+        // ...
+        If theIf = block.exit().asIf();
+        Value knownToBeNonNullValue = theIf.inValues().get(0);
+        // Avoid adding redundant non-null instruction.
+        if (!knownToBeNonNullValue.isNeverNull()) {
+          BasicBlock target = theIf.targetFromCondition(1L);
+          // Ignore uncommon empty blocks.
+          if (!target.isEmpty()) {
+            DominatorTree dominatorTree = new DominatorTree(code);
+            // Make sure there are no paths to the target block without passing the current block.
+            if (dominatorTree.dominatedBy(target, block)) {
+              // Collect users of the original value that are dominated by the target block.
+              Set<Instruction> dominatedUsers = Sets.newIdentityHashSet();
+              Set<Phi> dominatedPhiUsers = Sets.newIdentityHashSet();
+              Set<BasicBlock> dominatedBlocks =
+                  Sets.newHashSet(dominatorTree.dominatedBlocks(target));
+              for (Instruction user : knownToBeNonNullValue.uniqueUsers()) {
+                if (dominatedBlocks.contains(user.getBlock())) {
+                  dominatedUsers.add(user);
+                }
+              }
+              for (Phi user : knownToBeNonNullValue.uniquePhiUsers()) {
+                if (dominatedBlocks.contains(user.getBlock())) {
+                  dominatedPhiUsers.add(user);
+                }
+              }
+              // Avoid adding a non-null for the value without meaningful users.
+              if (!dominatedUsers.isEmpty() && !dominatedPhiUsers.isEmpty()) {
+                Value nonNullValue = code.createValue(
+                    knownToBeNonNullValue.outType(), knownToBeNonNullValue.getLocalInfo());
+                NonNull nonNull = new NonNull(nonNullValue, knownToBeNonNullValue);
+                InstructionListIterator targetIterator = target.listIterator();
+                nonNull.setPosition(targetIterator.next().getPosition());
+                targetIterator.previous();
+                targetIterator.add(nonNull);
+                knownToBeNonNullValue.replaceSelectiveUsers(
+                    nonNullValue, dominatedUsers, dominatedPhiUsers);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  public void cleanupNonNull(IRCode code) {
+    InstructionIterator it = code.instructionIterator();
+    boolean needToCheckTrivialPhis = false;
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      // non_null_rcv <- non-null(rcv)  // deleted
+      // ...
+      // non_null_rcv#foo
+      //
+      //  ~>
+      //
+      // rcv#foo
+      if (instruction.isNonNull()) {
+        NonNull nonNull = instruction.asNonNull();
+        Value src = nonNull.src();
+        Value dest = nonNull.dest();
+        needToCheckTrivialPhis = needToCheckTrivialPhis || dest.uniquePhiUsers().size() != 0;
+        dest.replaceUsers(src);
+        it.remove();
+      }
+    }
+    // non-null might introduce a phi, e.g.,
+    // non_null_rcv <- non-null(rcv)
+    // ...
+    // v <- phi(rcv, non_null_rcv)
+    //
+    // Cleaning up that non-null may result in a trivial phi:
+    // v <- phi(rcv, rcv)
+    if (needToCheckTrivialPhis) {
+      code.removeAllTrivialPhis();
+    }
+  }
+
+}
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 7af0451..24def66 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
@@ -73,6 +73,7 @@
 
   public static final int REGISTER_CANDIDATE_NOT_FOUND = -1;
   public static final int MIN_CONSTANT_FREE_FOR_POSITIONS = 5;
+  private static final int RESERVED_MOVE_EXCEPTION_REGISTER = 0;
 
   private enum ArgumentReuseMode {
     ALLOW_ARGUMENT_REUSE,
@@ -141,6 +142,9 @@
   // List of intervals that no register has been allocated to sorted by first live range.
   protected PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
 
+  // List of intervals for the result of move-exception instructions.
+  private List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
+
   // The first register used for parallel moves. After register allocation the parallel move
   // temporary registers are [firstParallelMoveTemporary, maxRegisterNumber].
   private int firstParallelMoveTemporary = NO_REGISTER;
@@ -153,6 +157,11 @@
   // register.
   private boolean hasDedicatedMoveExceptionRegister = false;
 
+  private int getMoveExceptionRegister() {
+    return numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS +
+        RESERVED_MOVE_EXCEPTION_REGISTER;
+  }
+
   public LinearScanRegisterAllocator(IRCode code, InternalOptions options) {
     this.code = code;
     this.options = options;
@@ -663,6 +672,7 @@
     active.clear();
     inactive.clear();
     unhandled.clear();
+    moveExceptionIntervals.clear();
     for (LiveIntervals intervals : liveIntervals) {
       intervals.clearRegisterAssignment();
     }
@@ -752,7 +762,6 @@
       // Force all move exception ranges to start out with the exception in a fixed register. Split
       // their live ranges which will force another register if used.
       int moveExceptionRegister = NO_REGISTER;
-      List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
       boolean overlappingMoveExceptionIntervals = false;
       for (BasicBlock block : code.blocks) {
         for (Instruction instruction : block.getInstructions()) {
@@ -761,8 +770,12 @@
             Value exceptionValue = instruction.outValue();
             LiveIntervals intervals = exceptionValue.getLiveIntervals();
             unhandled.remove(intervals);
+            moveExceptionIntervals.add(intervals);
             if (moveExceptionRegister == NO_REGISTER) {
+              assert RESERVED_MOVE_EXCEPTION_REGISTER == 0;
               moveExceptionRegister = getFreeConsecutiveRegisters(1);
+              assert moveExceptionRegister == getMoveExceptionRegister();
+              assert !freeRegisters.contains(moveExceptionRegister);
             }
             intervals.setRegister(moveExceptionRegister);
             if (!overlappingMoveExceptionIntervals) {
@@ -770,7 +783,6 @@
                 overlappingMoveExceptionIntervals |= other.overlaps(intervals);
               }
             }
-            moveExceptionIntervals.add(intervals);
           }
         }
       }
@@ -966,8 +978,14 @@
       }
       current = current.getNextConsecutive();
     }
-    // Select registers.
     current = unhandledInterval.getStartOfConsecutive();
+    // Exclude move exception register if the first interval overlaps a move exception interval.
+    if (overlapsMoveExceptionInterval(current) &&
+        freeRegisters.remove(getMoveExceptionRegister())) {
+      assert RESERVED_MOVE_EXCEPTION_REGISTER == 0;
+      excludedRegisters.add(getMoveExceptionRegister());
+    }
+    // Select registers.
     int numberOfRegister = current.numberOfConsecutiveRegisters();
     int firstRegister = getFreeConsecutiveRegisters(numberOfRegister);
     for (int i = 0; i < numberOfRegister; i++) {
@@ -1124,6 +1142,54 @@
     return false;
   }
 
+  // Intervals overlap a move exception interval if one of the splits of the intervals does.
+  // Since spill and restore moves are always put after the move exception we cannot give
+  // a non-move exception interval the same register as a move exception instruction.
+  //
+  // For example:
+  //
+  // B0:
+  //   const v0, 0
+  //   invoke throwing_method v0 (catch handler B2)
+  //   goto B1
+  // B1:
+  //   ...
+  // B2:
+  //   move-exception v1
+  //   invoke method v0
+  //   return
+  //
+  // During register allocation we could split the const number intervals into multiple
+  // parts. We have to avoid assigning the same register to v1 and and v0 in B0 even
+  // if v0 has a different register in B2. That is because the spill/restore move when
+  // transitioning from B0 to B2 has to be after the move-exception instruction.
+  //
+  // Assuming that v0 has register 0 in B0 and register 4 in B2 and v1 has register 0 in B2
+  // we would generate the following incorrect code:
+  //
+  // B0:
+  //   const r0, 0
+  //   invoke throwing_method r0 (catch handler B2)
+  //   goto B1
+  // B1:
+  //   ...
+  // B2:
+  //   move-exception r0
+  //   move r4, r0  // Whoops.
+  //   invoke method r4
+  //   return
+  private boolean overlapsMoveExceptionInterval(LiveIntervals intervals) {
+    if (!hasDedicatedMoveExceptionRegister) {
+      return false;
+    }
+    for (LiveIntervals moveExceptionInterval : moveExceptionIntervals) {
+      if (intervals.anySplitOverlaps(moveExceptionInterval)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   private boolean allocateSingleInterval(LiveIntervals unhandledInterval, ArgumentReuseMode mode) {
     int registerConstraint = unhandledInterval.getRegisterLimit();
     assert registerConstraint <= Constants.U16BIT_MAX;
@@ -1182,8 +1248,8 @@
     // register. If we cannot find a free valid register for the move exception value we have no
     // place to put a spill move (because the move exception instruction has to be the
     // first instruction in the handler block).
-    if (hasDedicatedMoveExceptionRegister) {
-      int moveExceptionRegister = numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS;
+    if (overlapsMoveExceptionInterval(unhandledInterval)) {
+      int moveExceptionRegister = getMoveExceptionRegister();
       if (moveExceptionRegister <= registerConstraint) {
         freePositions.set(moveExceptionRegister, 0);
       }
@@ -1530,8 +1596,8 @@
     }
 
     // Disallow reuse of the move exception register if we have reserved one.
-    if (hasDedicatedMoveExceptionRegister) {
-      usePositions.set(numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS, 0);
+    if (overlapsMoveExceptionInterval(unhandledInterval)) {
+      usePositions.set(getMoveExceptionRegister(), 0);
     }
 
     // Treat active linked argument intervals as pinned. They cannot be given another register
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 b6d8b79..2bbdaf9 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
@@ -325,6 +325,19 @@
     return nextOverlap(other) != -1;
   }
 
+  public boolean anySplitOverlaps(LiveIntervals other) {
+    LiveIntervals parent = getSplitParent();
+    if (parent.overlaps(other)) {
+      return true;
+    }
+    for (LiveIntervals child : parent.getSplitChildren()) {
+      if (child.overlaps(other)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public int nextOverlap(LiveIntervals other) {
     Iterator<LiveRange> it = other.ranges.iterator();
     LiveRange otherRange = it.next();
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 4181f7e..03c4c33 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -37,7 +37,7 @@
   }
 
   public NamingLens run(Timing timing) {
-    assert !options.skipMinification;
+    assert options.enableMinification;
     timing.begin("MinifyClasses");
     Map<DexType, DexString> classRenaming =
         new ClassNameMinifier(appInfo, rootSet, options).computeRenaming(timing);
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index 3fad0d4..521229e 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexClass;
@@ -11,7 +10,6 @@
 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.DexItem;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
@@ -133,15 +131,12 @@
       DexClass clazz = appInfo.definitionFor(from);
       if (clazz == null) return;
 
-      final Set<DexItem> membersNotMapped = Sets.newIdentityHashSet();
       final Set<MemberNaming> appliedMemberNaming = Sets.newIdentityHashSet();
       clazz.forEachField(encodedField -> {
         MemberNaming memberNaming = classNaming.lookupByOriginalItem(encodedField.field);
         if (memberNaming != null) {
           appliedMemberNaming.add(memberNaming);
           applyFieldMapping(encodedField.field, memberNaming);
-        } else if (clazz.isLibraryClass()) {
-          membersNotMapped.add(encodedField.field);
         }
       });
 
@@ -150,26 +145,9 @@
         if (memberNaming != null) {
           appliedMemberNaming.add(memberNaming);
           applyMethodMapping(encodedMethod.method, memberNaming);
-        } else if (clazz.isLibraryClass()) {
-          // <clinit> mapping could be omitted.
-          if (encodedMethod.isClassInitializer()) {
-            return;
-          }
-          membersNotMapped.add(encodedMethod.method);
         }
       });
 
-      // The presence of members that are not mapped indicates incomplete mappings for libraries.
-      if (!membersNotMapped.isEmpty()) {
-        StringBuilder builder = new StringBuilder();
-        builder.append("Incomplete mappings:");
-        for (DexItem member : membersNotMapped) {
-          builder.append(System.lineSeparator()).append("  ").append(member);
-        }
-        builder.append(System.lineSeparator());
-        throw new CompilationError(builder.toString(), clazz.origin);
-      }
-
       // We need to handle a lib class that extends another lib class where some members are not
       // overridden, resulting in absence of definitions. References to those members need to be
       // redirected via lense as well.
diff --git a/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java b/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
index 1313665..0a6c84a 100644
--- a/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
+++ b/src/main/java/com/android/tools/r8/origin/ArchiveEntryOrigin.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.origin;
 
-import com.android.tools.r8.origin.Origin;
-
 /**
  * Origin representing an entry in an archive.
  */
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
index bd2ad76..871812a 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationRemover.java
@@ -92,6 +92,10 @@
       annotationType = annotationType.toBaseType(appInfo.dexItemFactory);
     }
     DexClass definition = appInfo.definitionFor(annotationType);
+    // TODO(73102187): How to handle annotations without definition.
+    if (options.enableTreeShaking && definition == null) {
+      return false;
+    }
     return definition == null || definition.isLibraryClass()
         || appInfo.liveTypes.contains(annotationType);
   }
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 1d32bde..ead6bb7 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -42,9 +42,11 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.Timing;
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Queues;
 import com.google.common.collect.Sets;
@@ -79,6 +81,7 @@
  * field descriptions for details.
  */
 public class Enqueuer {
+  private boolean tracingMainDex = false;
 
   private final AppInfoWithSubtyping appInfo;
   private final InternalOptions options;
@@ -500,13 +503,19 @@
   }
 
   private void ensureFromLibraryOrThrow(DexType type, DexType context) {
+    if (tracingMainDex) {
+      // b/72312389: android.jar contains parts of JUnit and most developers include JUnit in
+      // their programs. This leads to library classes extending program classes. When tracing
+      // main dex lists we allow this.
+      return;
+    }
+
     DexClass holder = appInfo.definitionFor(type);
     if (holder != null && !holder.isLibraryClass()) {
       Diagnostic message = new StringDiagnostic("Library class " + context.toSourceString()
           + (holder.isInterface() ? " implements " : " extends ")
           + "program class " + type.toSourceString());
-      if (options.forceProguardCompatibility
-          || options.allowLibraryClassesToExtendProgramClasses) {
+      if (options.forceProguardCompatibility) {
         options.reporter.warning(message);
       } else {
         options.reporter.error(message);
@@ -945,6 +954,7 @@
   }
 
   public Set<DexType> traceMainDex(RootSet rootSet, Timing timing) {
+    this.tracingMainDex = true;
     this.rootSet = rootSet;
     // Translate the result of root-set computation into enqueuer actions.
     enqueueRootItems(rootSet.noShrinking);
@@ -1762,39 +1772,54 @@
      * For mapping invoke virtual instruction to single target method.
      */
     public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method) {
+      return lookupSingleVirtualTarget(method, method.holder);
+    }
+
+    public DexEncodedMethod lookupSingleVirtualTarget(
+        DexMethod method, DexType refinedReceiverType) {
       // This implements the logic from
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
       assert method != null;
+      assert refinedReceiverType.isSubtypeOf(method.holder, this);
       DexClass holder = definitionFor(method.holder);
-      if ((holder == null) || holder.isLibraryClass() || holder.isInterface()) {
+      if (holder == null || holder.isLibraryClass() || holder.isInterface()) {
         return null;
       }
-      if (method.isSingleVirtualMethodCached()) {
-        return method.getSingleVirtualMethodCache();
+      boolean refinedReceiverIsStrictSubType = refinedReceiverType != method.holder;
+      DexClass refinedHolder =
+          refinedReceiverIsStrictSubType ? definitionFor(refinedReceiverType) : holder;
+      assert refinedHolder != null;
+      assert !refinedHolder.isLibraryClass();
+      if (method.isSingleVirtualMethodCached(refinedReceiverType)) {
+        return method.getSingleVirtualMethodCache(refinedReceiverType);
       }
       // For kept types we cannot ensure a single target.
       if (pinnedItems.contains(method.holder)) {
-        method.setSingleVirtualMethodCache(null);
+        method.setSingleVirtualMethodCache(refinedReceiverType, null);
         return null;
       }
-      // First get the target for receiver type method.type.
+      // First get the target for the holder type.
       ResolutionResult topMethod = resolveMethod(method.holder, method);
       // We might hit none or multiple targets. Both make this fail at runtime.
       if (!topMethod.hasSingleTarget() || !topMethod.asSingleTarget().isVirtualMethod()) {
-        method.setSingleVirtualMethodCache(null);
+        method.setSingleVirtualMethodCache(refinedReceiverType, null);
         return null;
       }
+      // Now, resolve the target with the refined receiver type.
+      if (refinedReceiverIsStrictSubType) {
+        topMethod = resolveMethod(refinedReceiverType, method);
+      }
       DexEncodedMethod topSingleTarget = topMethod.asSingleTarget();
       DexClass topHolder = definitionFor(topSingleTarget.method.holder);
       // We need to know whether the top method is from an interface, as that would allow it to be
       // shadowed by a default method from an interface further down.
       boolean topIsFromInterface = topHolder.isInterface();
       // Now look at all subtypes and search for overrides.
-      DexEncodedMethod result = findSingleTargetFromSubtypes(method.holder, method,
-          topSingleTarget, !holder.accessFlags.isAbstract(), topIsFromInterface);
+      DexEncodedMethod result = findSingleTargetFromSubtypes(refinedReceiverType, method,
+          topSingleTarget, !refinedHolder.accessFlags.isAbstract(), topIsFromInterface);
       // Map the failure case of SENTINEL to null.
       result = result == DexEncodedMethod.SENTINEL ? null : result;
-      method.setSingleVirtualMethodCache(result);
+      method.setSingleVirtualMethodCache(refinedReceiverType, result);
       return result;
     }
 
@@ -1886,6 +1911,11 @@
     }
 
     public DexEncodedMethod lookupSingleInterfaceTarget(DexMethod method) {
+      return lookupSingleInterfaceTarget(method, method.holder);
+    }
+
+    public DexEncodedMethod lookupSingleInterfaceTarget(
+        DexMethod method, DexType refinedReceiverType) {
       DexClass holder = definitionFor(method.holder);
       if ((holder == null) || holder.isLibraryClass() || !holder.accessFlags.isInterface()) {
         return null;
@@ -1903,7 +1933,12 @@
       DexEncodedMethod result = null;
       // The loop will ignore abstract classes that are not kept as they should not be a target
       // at runtime.
-      for (DexType type : subtypes(method.holder)) {
+      Iterable<DexType> subTypesToExplore =
+          refinedReceiverType == method.holder
+              ? subtypes(method.holder)
+              : Iterables.concat(
+                  ImmutableList.of(refinedReceiverType), subtypes(refinedReceiverType));
+      for (DexType type : subTypesToExplore) {
         if (pinnedItems.contains(type)) {
           // For kept classes we cannot ensure a single target.
           return null;
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 9a57b07..27a6405 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -41,8 +41,7 @@
 
   public DexApplication run() {
     application.timing.begin("Pruning application...");
-    if (options.debugKeepRules && !options.skipMinification) {
-
+    if (options.debugKeepRules && options.enableMinification) {
       options.reporter.info(
           new StringDiagnostic(
               "Debugging keep rules on a minified build might yield broken builds, as "
diff --git a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
index 1d424bb..cccbb4b 100644
--- a/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DescriptorUtils.java
@@ -92,16 +92,29 @@
   }
 
   /**
-   * Determine the given {@param typeName} is valid java type name or not.
+   * Determine the given {@param typeName} is a valid jvms binary name or not (jvms 4.2.1).
    *
-   * @param typeName the java type name
-   * @return true if and only if the given type name is valid java type
+   * @param typeName the jvms binary name
+   * @return true if and only if the given type name is valid jvms binary name
    */
   public static boolean isValidJavaType(String typeName) {
-    return typeName.length() > 0
-        && Character.isJavaIdentifierStart(typeName.charAt(0))
-        && typeName.substring(1).chars().allMatch(
-            ch -> Character.isJavaIdentifierPart(ch) || ch == JAVA_PACKAGE_SEPARATOR);
+    if (typeName.length() == 0) {
+      return false;
+    }
+    char last = 0;
+    for (int i = 0; i < typeName.length(); i++) {
+      char c = typeName.charAt(i);
+      if (c == ';' ||
+          c == '[' ||
+          c == '/') {
+        return false;
+      }
+      if (c == '.' && (i == 0 || last == '.')) {
+        return false;
+      }
+      last = c;
+    }
+    return true;
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
new file mode 100644
index 0000000..ebb692f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/FeatureClassMapping.java
@@ -0,0 +1,174 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import com.android.tools.r8.ArchiveClassFileProvider;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Provides a mappings of classes to modules. The structure of the input file is as follows:
+ * packageOrClass:module
+ *
+ * <p>Lines with a # prefix are ignored.
+ *
+ * <p>We will do most specific matching, i.e., com.google.foobar.*:feature2 com.google.*:base will
+ * put everything in the com.google namespace into base, except classes in com.google.foobar that
+ * will go to feature2. Class based mappings takes precedence over packages (since they are more
+ * specific): com.google.A:feature2 com.google.*:base Puts A into feature2, and all other classes
+ * from com.google into base.
+ *
+ * <p>Note that this format does not allow specifying inter-module dependencies, this is simply a
+ * placement tool.
+ */
+public class FeatureClassMapping {
+
+  HashMap<String, String> parsedRules = new HashMap<>(); // Already parsed rules.
+
+  HashSet<FeaturePredicate> mappings = new HashSet<>();
+  Path mappingFile;
+
+  static final String COMMENT = "#";
+  static final String SEPARATOR = ":";
+
+  public static FeatureClassMapping fromSpecification(Path file)
+      throws FeatureMappingException, IOException {
+    FeatureClassMapping mapping = new FeatureClassMapping();
+    List<String> lines = FileUtils.readAllLines(file);
+    for (int i = 0; i < lines.size(); i++) {
+      String line = lines.get(i);
+      mapping.parseAndAdd(line, i);
+    }
+    return mapping;
+  }
+
+  public static FeatureClassMapping fromJarFiles(List<String> jarFiles)
+      throws FeatureMappingException, IOException {
+    FeatureClassMapping mapping = new FeatureClassMapping();
+    for (String jar : jarFiles) {
+      Path jarPath = Paths.get(jar);
+      String featureName = jarPath.getFileName().toString();
+      if (featureName.endsWith(".jar") || featureName.endsWith(".zip")) {
+        featureName = featureName.substring(0, featureName.length() - 4);
+      }
+
+      ArchiveClassFileProvider provider = new ArchiveClassFileProvider(jarPath);
+      for (String javaDescriptor : provider.getClassDescriptors()) {
+          String javaType = DescriptorUtils.descriptorToJavaType(javaDescriptor);
+          mapping.addMapping(javaType, featureName);
+      }
+    }
+    return mapping;
+  }
+
+  private FeatureClassMapping() {}
+
+  public void addMapping(String clazz, String feature) throws FeatureMappingException {
+    addRule(clazz, feature, 0);
+  }
+
+  FeatureClassMapping(List<String> lines) throws FeatureMappingException {
+    for (int i = 0; i < lines.size(); i++) {
+      String line = lines.get(i);
+      parseAndAdd(line, i);
+    }
+  }
+
+  public String featureForClass(String clazz) throws FeatureMappingException {
+    // Todo(ricow): improve performance (e.g., direct lookup of class predicates through hashmap).
+    FeaturePredicate bestMatch = null;
+    for (FeaturePredicate mapping : mappings) {
+      if (mapping.match(clazz)) {
+        if (bestMatch == null || bestMatch.predicate.length() < mapping.predicate.length()) {
+          bestMatch = mapping;
+        }
+      }
+    }
+    if (bestMatch == null) {
+      throw new FeatureMappingException("Class: " + clazz + " is not mapped to any feature");
+    }
+    return bestMatch.feature;
+  }
+
+  private void parseAndAdd(String line, int lineNumber) throws FeatureMappingException {
+    if (line.startsWith(COMMENT)) {
+      return; // Ignore comments
+    }
+    if (line.isEmpty()) {
+      return; // Ignore blank lines
+    }
+
+    if (!line.contains(SEPARATOR)) {
+      error("Mapping lines must contain a " + SEPARATOR, lineNumber);
+    }
+    String[] values = line.split(SEPARATOR);
+    if (values.length != 2) {
+      error("Mapping lines can only contain one " + SEPARATOR, lineNumber);
+    }
+
+    String predicate = values[0];
+    String feature = values[1];
+    addRule(predicate, feature, lineNumber);
+  }
+
+  private void addRule(String predicate, String feature, int lineNumber)
+      throws FeatureMappingException {
+    if (parsedRules.containsKey(predicate)) {
+      if (!parsedRules.get(predicate).equals(feature)) {
+        error("Redefinition of predicate " + predicate + "not allowed", lineNumber);
+      }
+      return; // Already have this rule.
+    }
+    parsedRules.put(predicate, feature);
+    FeaturePredicate featurePredicate = new FeaturePredicate(predicate, feature);
+    mappings.add(featurePredicate);
+  }
+
+  private void error(String error, int line) throws FeatureMappingException {
+    throw new FeatureMappingException(
+        "Invalid mappings specification: " + error + "\n in file " + mappingFile + ":" + line);
+  }
+
+  public static class FeatureMappingException extends Exception {
+    FeatureMappingException(String message) {
+      super(message);
+    }
+  }
+
+  /** A feature predicate can either be a wildcard or class predicate. */
+  private static class FeaturePredicate {
+    private static Pattern identifier = Pattern.compile("[A-Za-z_\\-][A-Za-z0-9_$\\-]*");
+    final String predicate;
+    final String feature;
+    // False implies class predicate.
+    final boolean isWildcard;
+
+    FeaturePredicate(String predicate, String feature) throws FeatureMappingException {
+      isWildcard = predicate.endsWith(".*");
+      if (isWildcard) {
+        this.predicate = predicate.substring(0, predicate.length() - 3);
+      } else {
+        this.predicate = predicate;
+      }
+      if (!DescriptorUtils.isValidJavaType(this.predicate)) {
+        throw new FeatureMappingException(this.predicate + " is not a valid identifier");
+      }
+      this.feature = feature;
+    }
+
+    boolean match(String className) {
+      if (isWildcard) {
+        return className.startsWith(predicate);
+      } else {
+        // We also put inner classes into the same feature.
+        return className.startsWith(predicate);
+      }
+    }
+  }
+}
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 1c32575..8c3c6ed 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -65,6 +65,15 @@
     this.reporter = reporter;
     this.proguardConfiguration = proguardConfiguration;
     itemFactory = proguardConfiguration.getDexItemFactory();
+    // -dontoptimize disables optimizations by flipping related flags.
+    if (!proguardConfiguration.isOptimizing()) {
+      enableClassMerging = false;
+      enableNonNullTracking = false;
+      enableInlining = false;
+      enableSwitchMapRemoval = false;
+      outline.enabled = false;
+      enableValuePropagation = false;
+    }
   }
 
   public boolean printTimes = false;
@@ -73,12 +82,12 @@
   public boolean passthroughDexCode = false;
 
   // Optimization-related flags. These should conform to -dontoptimize.
-  public boolean skipClassMerging = true;
-  public boolean addNonNull = true;
-  public boolean inlineAccessors = true;
-  public boolean removeSwitchMaps = true;
+  public boolean enableClassMerging = false;
+  public boolean enableNonNullTracking = true;
+  public boolean enableInlining = true;
+  public boolean enableSwitchMapRemoval = true;
   public final OutlineOptions outline = new OutlineOptions();
-  public boolean propagateMemberValue = true;
+  public boolean enableValuePropagation = true;
 
   // Number of threads to use while processing the dex files.
   public int numberOfThreads = ThreadUtils.NOT_SPECIFIED;
@@ -162,21 +171,17 @@
   // to disable the check that the build makes sense for multi-dexing.
   public boolean enableMainDexListCheck = true;
 
-  public boolean useTreeShaking = true;
+  public boolean enableTreeShaking = true;
 
   public boolean printCfg = false;
   public String printCfgFile;
   public boolean ignoreMissingClasses = false;
   // EXPERIMENTAL flag to get behaviour as close to Proguard as possible.
   public boolean forceProguardCompatibility = false;
-  public boolean skipMinification = false;
+  public boolean enableMinification = true;
   public boolean disableAssertions = true;
   public boolean debugKeepRules = false;
 
-  // TODO(72312389): android.jar contains parts of JUnit and most developers include JUnit in
-  // their programs, which can lead to library classes extending program classes.
-  public boolean allowLibraryClassesToExtendProgramClasses = false;
-
   public boolean debug = false;
   public final TestingOptions testing = new TestingOptions();
 
diff --git a/src/main/java/com/android/tools/r8/utils/OptionsParsing.java b/src/main/java/com/android/tools/r8/utils/OptionsParsing.java
new file mode 100644
index 0000000..ece6bb2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/utils/OptionsParsing.java
@@ -0,0 +1,93 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class OptionsParsing {
+
+  /**
+   * Try parsing the switch {@code name} and zero or more non-switch args after it. Also supports
+   * the <name>=arg syntax.
+   */
+  public static List<String> tryParseMulti(ParseContext context, String name) {
+    List<String> result = null;
+    String head = context.head();
+    if (head.equals(name)) {
+      context.next();
+      result = new ArrayList<>();
+      while (context.head() != null && !context.head().startsWith("-")) {
+        result.add(context.head());
+        context.next();
+      }
+    } else if (head.startsWith(name) && head.charAt(name.length()) == '=') {
+      result = Collections.singletonList(head.substring(name.length() + 1));
+      context.next();
+    }
+    return result;
+  }
+
+  /**
+   * Try parsing the switch {@code name} and one arg after it. Also supports the <name>=arg syntax.
+   */
+  public static String tryParseSingle(ParseContext context, String name, String shortName) {
+    String head = context.head();
+    if (head.equals(name) || head.equals(shortName)) {
+      String next = context.next();
+      if (next == null) {
+        throw new RuntimeException(String.format("Missing argument for '%s'.", head));
+      }
+      context.next();
+      return next;
+    }
+
+    if (head.startsWith(name) && head.charAt(name.length()) == '=') {
+      context.next();
+      return head.substring(name.length() + 1);
+    }
+
+    return null;
+  }
+
+  /**
+   * Try parsing the switch {@code name} as a boolean switch or its negation, with a 'no' between
+   * the dashes and the word.
+   */
+  public static Boolean tryParseBoolean(ParseContext context, String name) {
+    if (context.head().equals(name)) {
+      context.next();
+      return true;
+    }
+    assert name.startsWith("--");
+    if (context.head().equals("--no" + name.substring(2))) {
+      context.next();
+      return false;
+    }
+    return null;
+  }
+
+  public static class ParseContext {
+    private final String[] args;
+    private int nextIndex = 0;
+
+    public ParseContext(String[] args) {
+      this.args = args;
+    }
+
+    public String head() {
+      return nextIndex < args.length ? args[nextIndex] : null;
+    }
+
+    public String next() {
+      if (nextIndex < args.length) {
+        ++nextIndex;
+        return head();
+      } else {
+        throw new RuntimeException("Iterating over the end of argument list.");
+      }
+    }
+  }
+}
diff --git a/src/test/examples/adaptclassstrings/keep-rules-1.txt b/src/test/examples/adaptclassstrings/keep-rules-1.txt
index 72562ed..21ab3c8a 100644
--- a/src/test/examples/adaptclassstrings/keep-rules-1.txt
+++ b/src/test/examples/adaptclassstrings/keep-rules-1.txt
@@ -9,4 +9,3 @@
 }
 
 -dontshrink
--dontoptimize
diff --git a/src/test/examples/adaptclassstrings/keep-rules-2.txt b/src/test/examples/adaptclassstrings/keep-rules-2.txt
index fc8be9d..b06eebf 100644
--- a/src/test/examples/adaptclassstrings/keep-rules-2.txt
+++ b/src/test/examples/adaptclassstrings/keep-rules-2.txt
@@ -9,6 +9,5 @@
 }
 
 -dontshrink
--dontoptimize
 
 -adaptclassstrings *.*A
diff --git a/src/test/examples/atomicfieldupdater/keep-rules.txt b/src/test/examples/atomicfieldupdater/keep-rules.txt
index 3b2d2ef..44c60f5 100644
--- a/src/test/examples/atomicfieldupdater/keep-rules.txt
+++ b/src/test/examples/atomicfieldupdater/keep-rules.txt
@@ -10,7 +10,6 @@
 -keep,allowobfuscation class atomicfieldupdater.A
 
 -dontshrink
--dontoptimize
 
 # This will be added to CompatProguard by default.
 # We are testing whether R8 shows the same behavior.
diff --git a/src/test/examples/dexsplitsample/Class1.java b/src/test/examples/dexsplitsample/Class1.java
new file mode 100644
index 0000000..c0cd25f
--- /dev/null
+++ b/src/test/examples/dexsplitsample/Class1.java
@@ -0,0 +1,15 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package dexsplitsample;
+
+public class Class1 {
+  public static void main(String[] args) {
+    System.out.println("Class1");
+  }
+
+  protected String getClass1String() {
+    return "Class1String";
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/examples/dexsplitsample/Class2.java
similarity index 62%
copy from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
copy to src/test/examples/dexsplitsample/Class2.java
index 09e94f7..39d73b8 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/examples/dexsplitsample/Class2.java
@@ -1,8 +1,11 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
 
-public class FieldAccessTest {
-  String fld;
+package dexsplitsample;
+
+public class Class2 {
+  public static void main(String[] args) {
+    System.out.println("Class2");
+  }
 }
diff --git a/src/test/examples/dexsplitsample/Class3.java b/src/test/examples/dexsplitsample/Class3.java
new file mode 100644
index 0000000..98469a5
--- /dev/null
+++ b/src/test/examples/dexsplitsample/Class3.java
@@ -0,0 +1,24 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package dexsplitsample;
+
+public class Class3 extends Class1 {
+  public static void main(String[] args) {
+    Class3 clazz = new Class3();
+    if (clazz.getClass1String() != "Class1String") {
+      throw new RuntimeException("Can't call method from super");
+    }
+    if (!new InnerClass().success()) {
+      throw new RuntimeException("Can't call method on inner class");
+    }
+    System.out.println("Class3");
+  }
+
+  private static class InnerClass {
+    public boolean success() {
+      return true;
+    }
+  }
+}
diff --git a/src/test/examples/forname/keep-rules.txt b/src/test/examples/forname/keep-rules.txt
index 34a498b..4131646 100644
--- a/src/test/examples/forname/keep-rules.txt
+++ b/src/test/examples/forname/keep-rules.txt
@@ -10,7 +10,6 @@
 -keep,allowobfuscation class forname.A
 
 -dontshrink
--dontoptimize
 
 # This will be added to CompatProguard by default.
 # We are testing whether R8 shows the same behavior.
diff --git a/src/test/examples/getmembers/A.java b/src/test/examples/getmembers/A.java
index 7a231c0..b13a28c 100644
--- a/src/test/examples/getmembers/A.java
+++ b/src/test/examples/getmembers/A.java
@@ -8,4 +8,7 @@
   String bar(String s) {
     return foo + s;
   }
+  public static String baz() {
+    return foo;
+  }
 }
diff --git a/src/test/examples/getmembers/B.java b/src/test/examples/getmembers/B.java
new file mode 100644
index 0000000..d29d0a2
--- /dev/null
+++ b/src/test/examples/getmembers/B.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package getmembers;
+
+import java.lang.reflect.Method;
+
+public class B {
+
+  private String toBeInlined() throws Exception {
+    Method baz = A.class.getMethod("baz", (Class[]) null);
+    assert baz != null;
+    String bazResult = (String) baz.invoke(null, null);
+    assert bazResult.startsWith("foo");
+    return bazResult;
+  }
+
+  synchronized static String inliner() throws Exception {
+    B self = new B();
+    return self.toBeInlined();
+  }
+
+}
diff --git a/src/test/examples/getmembers/Main.java b/src/test/examples/getmembers/Main.java
index ee82c05..7d26710 100644
--- a/src/test/examples/getmembers/Main.java
+++ b/src/test/examples/getmembers/Main.java
@@ -12,11 +12,17 @@
     Field foo = a.getDeclaredField("foo");
     assert foo != null;
     assert foo.get(null).equals("foo");
+    System.out.println(foo.get(null));
 
     Method bar = a.getDeclaredMethod("bar", new Class[] { String.class });
     assert bar != null;
     A instanceA = new A();
-    String barResult = (String) bar.invoke(instanceA);
+    String barResult = (String) bar.invoke(instanceA, "bar");
     assert barResult.startsWith("foo");
+    System.out.println(barResult);
+
+    String bazResult = B.inliner();
+    assert bazResult.startsWith("foo");
+    System.out.println(bazResult);
   }
 }
diff --git a/src/test/examples/getmembers/keep-rules.txt b/src/test/examples/getmembers/keep-rules.txt
index 23f8d6c..68c4a5d 100644
--- a/src/test/examples/getmembers/keep-rules.txt
+++ b/src/test/examples/getmembers/keep-rules.txt
@@ -7,10 +7,13 @@
   public static void main(...);
 }
 
--keep,allowobfuscation class getmembers.A
+-keep,allowobfuscation class getmembers.A {
+  <methods>;
+}
 
--dontshrink
--dontoptimize
+-alwaysinline class getmembers.B {
+  private String toBeInlined();
+}
 
 # This will be added to CompatProguard by default.
 # We are testing whether R8 shows the same behavior.
diff --git a/src/test/examples/identifiernamestring/keep-rules-1.txt b/src/test/examples/identifiernamestring/keep-rules-1.txt
index bc18a3f..682999f 100644
--- a/src/test/examples/identifiernamestring/keep-rules-1.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-1.txt
@@ -10,4 +10,3 @@
 -keepnames class identifiernamestring.A
 
 -dontshrink
--dontoptimize
diff --git a/src/test/examples/identifiernamestring/keep-rules-2.txt b/src/test/examples/identifiernamestring/keep-rules-2.txt
index 87584e1..74af143 100644
--- a/src/test/examples/identifiernamestring/keep-rules-2.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-2.txt
@@ -10,7 +10,6 @@
 -keepnames class identifiernamestring.A
 
 -dontshrink
--dontoptimize
 
 -identifiernamestring class * {
   @identifiernamestring.IdentifierNameString *;
diff --git a/src/test/examples/identifiernamestring/keep-rules-3.txt b/src/test/examples/identifiernamestring/keep-rules-3.txt
index 5ac8b93..aa23772 100644
--- a/src/test/examples/identifiernamestring/keep-rules-3.txt
+++ b/src/test/examples/identifiernamestring/keep-rules-3.txt
@@ -10,7 +10,6 @@
 -keepnames class identifiernamestring.A
 
 -dontshrink
--dontoptimize
 
 -identifiernamestring class * {
   static java.lang.reflect.Field *(java.lang.Class, java.lang.String);
diff --git a/src/test/examples/naming001/keep-rules-106.txt b/src/test/examples/naming001/keep-rules-106.txt
new file mode 100644
index 0000000..ca320eb
--- /dev/null
+++ b/src/test/examples/naming001/keep-rules-106.txt
@@ -0,0 +1,5 @@
+# Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+-applymapping mapping-106.txt
\ No newline at end of file
diff --git a/src/test/examples/naming001/mapping-106.txt b/src/test/examples/naming001/mapping-106.txt
new file mode 100644
index 0000000..1b3606d
--- /dev/null
+++ b/src/test/examples/naming001/mapping-106.txt
@@ -0,0 +1 @@
+naming001.E -> a.a:
\ No newline at end of file
diff --git a/src/test/examplesAndroidApi/instrumentationtest/InstrumentationTest.java b/src/test/examplesAndroidApi/instrumentationtest/InstrumentationTest.java
new file mode 100644
index 0000000..5541ccd
--- /dev/null
+++ b/src/test/examplesAndroidApi/instrumentationtest/InstrumentationTest.java
@@ -0,0 +1,21 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package instrumentationtest;
+
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+
+public class InstrumentationTest extends InstrumentationTestCase {
+  Context context;
+
+  public void setUp() throws Exception {
+    super.setUp();
+    context = getInstrumentation().getContext();
+    assertNotNull(context);
+  }
+
+  public void testSomething() {
+    assertEquals(false, true);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 562abaf..db26fe6 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -12,19 +12,11 @@
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.io.ByteStreams;
-import java.io.File;
 import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.StandardOpenOption;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipOutputStream;
 import org.junit.Assert;
 
 public class AsmTestBase extends TestBase {
@@ -41,9 +33,10 @@
         exceptionClass);
   }
 
-  protected void ensureSameOutput(String main, int apiLevel, byte[]... classes) throws Exception {
+  protected void ensureSameOutput(String main, AndroidApiLevel apiLevel, byte[]... classes)
+      throws Exception {
     AndroidApp app = buildAndroidApp(classes);
-    Consumer<InternalOptions> setMinApiLevel = o -> o.minApiLevel = apiLevel;
+    Consumer<InternalOptions> setMinApiLevel = o -> o.minApiLevel = apiLevel.getLevel();
     ProcessResult javaResult = runOnJava(main, classes);
     ProcessResult d8Result = runOnArtRaw(compileWithD8(app, setMinApiLevel), main);
     ProcessResult r8Result = runOnArtRaw(compileWithR8(app, setMinApiLevel), main);
@@ -105,20 +98,6 @@
     ensureSameOutput(main, mergedApp, classes);
   }
 
-  protected byte[] asBytes(Class clazz) throws IOException {
-    return ByteStreams
-        .toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
-  }
-
-  protected AndroidApp buildAndroidApp(byte[]... classes) throws IOException {
-    AndroidApp.Builder builder = AndroidApp.builder();
-    for (byte[] clazz : classes) {
-      builder.addClassProgramData(clazz, Origin.unknown());
-    }
-    builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.N.getLevel()));
-    return builder.build();
-  }
-
   protected static AndroidApp readClassesAndAsmDump(List<Class> classes, List<byte[]> asmClasses)
       throws IOException {
     AndroidApp.Builder builder = AndroidApp.builder();
@@ -136,32 +115,6 @@
     assertTrue(result.stderr, result.stderr.contains(exception.getCanonicalName()));
   }
 
-  protected ProcessResult runOnJava(String main, byte[]... classes) throws IOException {
-    Path file = writeToZip(classes);
-    return ToolHelper.runJavaNoVerify(file, main);
-  }
-
-  private Path writeToZip(byte[]... classes) throws IOException {
-    DumpLoader dumpLoader = new DumpLoader();
-    File result = temp.newFile();
-    try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(result.toPath(),
-        StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
-      for (byte[] clazz : classes) {
-        String name = dumpLoader.loadClass(clazz).getTypeName();
-        ZipEntry zipEntry = new ZipEntry(DescriptorUtils.getPathFromJavaType(name));
-        zipEntry.setSize(clazz.length);
-        out.putNextEntry(zipEntry);
-        out.write(clazz);
-        out.closeEntry();
-      }
-    }
-    return result.toPath();
-  }
-
-  protected static Class loadClassFromDump(byte[] dump) {
-    return new DumpLoader().loadClass(dump);
-  }
-
   @FunctionalInterface
   protected interface AsmDump {
 
@@ -170,7 +123,7 @@
 
   protected static Class loadClassFromAsmClass(AsmDump asmDump) {
     try {
-      return new DumpLoader().loadClass(asmDump.dump());
+      return loadClassFromDump(asmDump.dump());
     } catch (Exception e) {
       throw new ClassFormatError(e.toString());
     }
@@ -183,17 +136,4 @@
       throw new ClassFormatError(e.toString());
     }
   }
-
-  protected static byte[] getBytesFromJavaClass(Class clazz) throws IOException {
-    return Files.readAllBytes(ToolHelper.getClassFileForTestClass(clazz));
-  }
-
-  private static class DumpLoader extends ClassLoader {
-
-    @SuppressWarnings("deprecation")
-    public Class loadClass(byte[] clazz) {
-      return defineClass(clazz, 0, clazz.length);
-    }
-  }
-
 }
diff --git a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
index 4cde5d9..6d0ef5a 100644
--- a/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/D8ApiBinaryCompatibilityTests.java
@@ -65,7 +65,8 @@
                     "--main-dex-list",
                     mainDexList.toString(),
                     "--lib",
-                    ToolHelper.getAndroidJar(minApiLevel).toString(),
+                    ToolHelper.getAndroidJar(
+                        AndroidApiLevel.getAndroidApiLevel(minApiLevel)).toString(),
                     "--classpath",
                     lib1.toString(),
                     "--classpath",
diff --git a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
index 05d303c..caa1d48 100644
--- a/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8IncrementalRunExamplesAndroidOTest.java
@@ -14,6 +14,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.desugar.InterfaceMethodRewriter;
 import com.android.tools.r8.ir.desugar.LambdaRewriter;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.OffOrAuto;
@@ -49,8 +50,8 @@
     }
 
     @Override
-    D8IncrementalTestRunner withMinApiLevel(int minApiLevel) {
-      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+    D8IncrementalTestRunner withMinApiLevel(AndroidApiLevel minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel.getLevel()));
     }
 
     @Override
@@ -175,8 +176,8 @@
       } else {
         throw new Unreachable("Unexpected output mode " + outputMode);
       }
-      addLibraryReference(builder, ToolHelper
-          .getAndroidJar(androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion));
+      addLibraryReference(builder, ToolHelper.getAndroidJar(
+          androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
       try {
         return ToolHelper.runD8(builder, this::combinedOptionConsumer);
       } catch (Unimplemented | CompilationError | InternalCompilerError re) {
@@ -324,7 +325,7 @@
 
   @Override
   protected Path buildDexThroughIntermediate(String packageName, Path input, OutputMode outputMode,
-      int minApi, String... mainDexClasses) throws Throwable {
+      AndroidApiLevel minApi, String... mainDexClasses) throws Throwable {
     // tests using this should already been skipped.
     throw new Unreachable();
   }
diff --git a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
index 832a781..dc4a9b4 100644
--- a/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8LazyRunExamplesAndroidOTest.java
@@ -66,7 +66,7 @@
 
   @Test
   public void dexPerClassFileWithDesugaringAndFolderClasspath() throws Throwable {
-    int minAPILevel = AndroidApiLevel.K.getLevel();
+    AndroidApiLevel minAPILevel = AndroidApiLevel.K;
     Path inputFile =
         Paths.get(ToolHelper.EXAMPLES_ANDROID_N_BUILD_DIR, "interfacemethods" + JAR_EXTENSION);
     Path tmpClassesDir = temp.newFolder().toPath();
@@ -78,7 +78,7 @@
     {
       D8Command.Builder command =
           D8Command.builder()
-              .setMinApiLevel(minAPILevel)
+              .setMinApiLevel(minAPILevel.getLevel())
               .addLibraryFiles(androidJar)
               .addProgramFiles(inputFile);
 
@@ -98,7 +98,7 @@
     for (Path classFile : individualClassFiles) {
       D8Command.Builder builder =
           D8Command.builder()
-              .setMinApiLevel(minAPILevel)
+              .setMinApiLevel(minAPILevel.getLevel())
               .addLibraryFiles(androidJar)
               .addClasspathFiles(tmpClassesDir)
               .addProgramFiles(classFile);
@@ -111,7 +111,7 @@
               });
       individalDexes.add(individualResult.getDexProgramResourcesForTesting().get(0));
     }
-    AndroidApp mergedResult = mergeDexResources(minAPILevel, individalDexes);
+    AndroidApp mergedResult = mergeDexResources(minAPILevel.getLevel(), individalDexes);
 
     assertTrue(Arrays.equals(
         readResource(fullBuildResult.getDexProgramResourcesForTesting().get(0)),
diff --git a/src/test/java/com/android/tools/r8/D8NonLazyRunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8NonLazyRunExamplesAndroidOTest.java
index 905d209..e16ecd1 100644
--- a/src/test/java/com/android/tools/r8/D8NonLazyRunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8NonLazyRunExamplesAndroidOTest.java
@@ -22,8 +22,8 @@
 
     @Override
     void addLibraryReference(D8Command.Builder builder, Path location) throws IOException {
-      builder.addLibraryFiles(ToolHelper
-          .getAndroidJar(androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion));
+      builder.addLibraryFiles(ToolHelper.getAndroidJar(
+          androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
     }
 
     @Override
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
index edb73f7..b5802f4 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidOTest.java
@@ -27,8 +27,8 @@
     }
 
     @Override
-    D8TestRunner withMinApiLevel(int minApiLevel) {
-      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+    D8TestRunner withMinApiLevel(AndroidApiLevel minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel.getLevel()));
     }
 
     D8TestRunner withClasspath(Path... classpath) {
@@ -43,7 +43,7 @@
       }
       builder.addLibraryFiles(
           ToolHelper.getAndroidJar(
-              androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion));
+              androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
       builder.addProgramFiles(inputFile);
       try {
         ToolHelper.runD8(builder, this::combinedOptionConsumer);
@@ -70,7 +70,7 @@
     D8TestRunner lib1 =
         test("testDefaultInInterfaceWithoutDesugaring", "desugaringwithmissingclasslib1", "N/A")
             .withInterfaceMethodDesugaring(OffOrAuto.Off)
-            .withMinApiLevel(AndroidApiLevel.K.getLevel());
+            .withMinApiLevel(AndroidApiLevel.K);
     try  {
       lib1.build();
 
@@ -87,7 +87,7 @@
     D8TestRunner lib1 =
         test("desugaringwithmissingclasslib1", "desugaringwithmissingclasslib1", "N/A")
             .withInterfaceMethodDesugaring(OffOrAuto.Auto)
-            .withMinApiLevel(AndroidApiLevel.K.getLevel());
+            .withMinApiLevel(AndroidApiLevel.K);
     lib1.build();
 
     // lib2: interface B extends A { default String foo() { return "B"; } }
@@ -96,7 +96,7 @@
         test("desugaringwithmissingclasslib2", "desugaringwithmissingclasslib2", "N/A")
             .withInterfaceMethodDesugaring(OffOrAuto.Auto)
             .withClasspath(lib1.getInputJar())
-            .withMinApiLevel(AndroidApiLevel.K.getLevel());
+            .withMinApiLevel(AndroidApiLevel.K);
     lib2.build();
 
     // test: class ImplementMethodsWithDefault implements A, B {} should get its foo implementation
@@ -107,7 +107,7 @@
         test("desugaringwithmissingclasstest1", "desugaringwithmissingclasstest1", "N/A")
             .withInterfaceMethodDesugaring(OffOrAuto.Auto)
             .withClasspath(lib1.getInputJar())
-            .withMinApiLevel(AndroidApiLevel.K.getLevel());
+            .withMinApiLevel(AndroidApiLevel.K);
     test.build();
 
     // TODO check compilation warnings are correctly reported
@@ -116,7 +116,7 @@
 
   @Test
   public void testMissingInterfaceDesugared2AndroidK() throws Throwable {
-    int minApi = AndroidApiLevel.K.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.K;
 
     // lib1: interface A { default String foo() { return "A"; } }
     D8TestRunner lib1 =
@@ -170,7 +170,7 @@
 
   @Test
   public void testMissingInterfaceDesugared2AndroidO() throws Throwable {
-    int minApi = AndroidApiLevel.O.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.O;
     // lib1: interface A { default String foo() { return "A"; } }
     D8TestRunner lib1 =
         test("desugaringwithmissingclasslib1", "desugaringwithmissingclasslib1", "N/A")
@@ -218,7 +218,7 @@
   @Test
   public void testCallToMissingSuperInterfaceDesugaredAndroidK() throws Throwable {
 
-    int minApi = AndroidApiLevel.K.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.K;
     // lib1: interface A { default String foo() { return "A"; } }
     D8TestRunner lib1 =
         test("desugaringwithmissingclasslib1", "desugaringwithmissingclasslib1", "N/A")
@@ -271,7 +271,7 @@
 
   @Test
   public void testCallToMissingSuperInterfaceDesugaredAndroidO() throws Throwable {
-    int minApi = AndroidApiLevel.O.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.O;
     // lib1: interface A { default String foo() { return "A"; } }
     D8TestRunner lib1 =
         test("desugaringwithmissingclasslib1", "desugaringwithmissingclasslib1", "N/A")
@@ -318,7 +318,7 @@
 
   @Test
   public void testMissingSuperDesugaredAndroidK() throws Throwable {
-    int minApi = AndroidApiLevel.K.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.K;
 
     // lib1: interface A { default String foo() { return "A"; } }
     D8TestRunner lib1 =
@@ -366,7 +366,7 @@
 
   @Test
   public void testMissingSuperDesugaredAndroidO() throws Throwable {
-    int minApi = AndroidApiLevel.O.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.O;
 
     // lib1: interface A { default String foo() { return "A"; } }
     D8TestRunner lib1 =
@@ -416,7 +416,7 @@
 
   @Test
   public void testMissingSuperDesugaredWithProgramCrossImplementationAndroidK() throws Throwable {
-    int minApi = AndroidApiLevel.K.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.K;
 
     // lib1: interface A { default String foo() { return "A"; } }
     //       interface A2 { default String foo2() { return "A2"; } }
@@ -461,7 +461,7 @@
 
   @Test
   public void testMissingSuperDesugaredWithClasspathCrossImplementationAndroidK() throws Throwable {
-    int minApi = AndroidApiLevel.K.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.K;
 
     // lib1: interface A { default String foo() { return "A"; } }
     //       interface A2 { default String foo2() { return "A2"; } }
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
index b8e9675..0ae485a 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesAndroidPTest.java
@@ -37,7 +37,7 @@
       }
       // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
       builder
-          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel()))
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O))
           .addProgramFiles(inputFile)
           .setOutput(out, OutputMode.DexIndexed);
       try {
diff --git a/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java
index 71ffde2..ece7f31 100644
--- a/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/D8RunExamplesJava9Test.java
@@ -37,7 +37,7 @@
       }
       // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
       builder
-          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()))
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P))
           .addProgramFiles(inputFile)
           .setOutput(out, OutputMode.DexIndexed);
       try {
diff --git a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
index 8e8ca36..6533e08 100644
--- a/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ProguardMapMarkerTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertFalse;
 
 import com.android.tools.r8.naming.ProguardMapSupplier;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.VersionProperties;
 import java.nio.file.Paths;
 import java.util.HashSet;
@@ -17,15 +18,15 @@
 public class ProguardMapMarkerTest {
   @Test
   public void proguardMapMarkerTest24() throws CompilationFailedException {
-    proguardMapMarkerTest(24);
+    proguardMapMarkerTest(AndroidApiLevel.N);
   }
 
   @Test
   public void proguardMapMarkerTest26() throws CompilationFailedException {
-    proguardMapMarkerTest(26);
+    proguardMapMarkerTest(AndroidApiLevel.O);
   }
 
-  private void proguardMapMarkerTest(int minApiLevel) throws CompilationFailedException {
+  private void proguardMapMarkerTest(AndroidApiLevel minApiLevel) throws CompilationFailedException {
     String classFile = ToolHelper.EXAMPLES_BUILD_DIR + "classes/trivial/Trivial.class";
     R8.run(
         R8Command.builder()
@@ -43,10 +44,10 @@
                   public void finished(DiagnosticsHandler handler) {}
                 })
             .addLibraryFiles(ToolHelper.getAndroidJar(minApiLevel))
-            .setMinApiLevel(minApiLevel)
+            .setMinApiLevel(minApiLevel.getLevel())
             .setProguardMapConsumer(
                 (proguardMap, handler) -> {
-                  verifyMarkers(proguardMap, minApiLevel);
+                  verifyMarkers(proguardMap, minApiLevel.getLevel());
                 })
             .build());
   }
diff --git a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
index 59536de..28fe5dc 100644
--- a/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
+++ b/src/test/java/com/android/tools/r8/R8ApiBinaryCompatibilityTests.java
@@ -22,7 +22,7 @@
 
   static final Path JAR = Paths.get("tests", "r8_api_usage_sample.jar");
   static final String MAIN = "com.android.tools.apiusagesample.R8ApiUsageSample";
-  static final int MIN_API = AndroidApiLevel.K.getLevel();
+  static final AndroidApiLevel MIN_API = AndroidApiLevel.K;
 
   @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
@@ -54,7 +54,7 @@
                     "--output",
                     temp.newFolder().toString(),
                     "--min-api",
-                    Integer.toString(MIN_API),
+                    Integer.toString(MIN_API.getLevel()),
                     "--pg-conf",
                     pgConf.toString(),
                     "--main-dex-rules",
diff --git a/src/test/java/com/android/tools/r8/R8IgnoreMissingClassesTest.java b/src/test/java/com/android/tools/r8/R8IgnoreMissingClassesTest.java
index 697158a..83bc302 100644
--- a/src/test/java/com/android/tools/r8/R8IgnoreMissingClassesTest.java
+++ b/src/test/java/com/android/tools/r8/R8IgnoreMissingClassesTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8;
 
 import com.android.tools.r8.origin.EmbeddedOrigin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -11,14 +12,14 @@
 
 public class R8IgnoreMissingClassesTest {
 
-  private static final int MIN_API = 26;
+  private static final AndroidApiLevel MIN_API = AndroidApiLevel.O;
   private static final Path EXAMPLE = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR, "usestdlib.jar");
   private static final Path LIBRARY = ToolHelper.getAndroidJar(MIN_API);
 
   private R8Command.Builder config() {
     return R8Command.builder()
         .addProgramFiles(EXAMPLE)
-        .setMinApiLevel(MIN_API)
+        .setMinApiLevel(MIN_API.getLevel())
         .setProgramConsumer(DexIndexedConsumer.emptyConsumer());
   }
 
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 2278d67..32c0175 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -105,46 +105,46 @@
       "third_party/gradle/gradle/lib/plugins/hamcrest-core-1.3.jar";
 
   // Test that required to set min-api to a specific value.
-  private static Map<String, Integer> needMinSdkVersion =
-      new ImmutableMap.Builder<String, Integer>()
+  private static Map<String, AndroidApiLevel> needMinSdkVersion =
+      new ImmutableMap.Builder<String, AndroidApiLevel>()
           // Android O
-          .put("952-invoke-custom", AndroidApiLevel.O.getLevel())
-          .put("952-invoke-custom-kinds", AndroidApiLevel.O.getLevel())
-          .put("953-invoke-polymorphic-compiler", AndroidApiLevel.O.getLevel())
-          .put("957-methodhandle-transforms", AndroidApiLevel.O.getLevel())
-          .put("958-methodhandle-stackframe", AndroidApiLevel.O.getLevel())
-          .put("959-invoke-polymorphic-accessors", AndroidApiLevel.O.getLevel())
-          .put("979-const-method-handle", AndroidApiLevel.P.getLevel())
-          .put("990-method-handle-and-mr", AndroidApiLevel.O.getLevel())
+          .put("952-invoke-custom", AndroidApiLevel.O)
+          .put("952-invoke-custom-kinds", AndroidApiLevel.O)
+          .put("953-invoke-polymorphic-compiler", AndroidApiLevel.O)
+          .put("957-methodhandle-transforms", AndroidApiLevel.O)
+          .put("958-methodhandle-stackframe", AndroidApiLevel.O)
+          .put("959-invoke-polymorphic-accessors", AndroidApiLevel.O)
+          .put("979-const-method-handle", AndroidApiLevel.P)
+          .put("990-method-handle-and-mr", AndroidApiLevel.O)
           // Test intentionally asserts presence of bridge default methods desugar removes.
-          .put("044-proxy", AndroidApiLevel.N.getLevel())
+          .put("044-proxy", AndroidApiLevel.N)
           // Test intentionally asserts absence of default interface method in a class.
-          .put("048-reflect-v8", AndroidApiLevel.N.getLevel())
+          .put("048-reflect-v8", AndroidApiLevel.N)
           // Uses default interface methods.
-          .put("162-method-resolution", AndroidApiLevel.N.getLevel())
-          .put("616-cha-interface-default", AndroidApiLevel.N.getLevel())
-          .put("1910-transform-with-default", AndroidApiLevel.N.getLevel())
+          .put("162-method-resolution", AndroidApiLevel.N)
+          .put("616-cha-interface-default", AndroidApiLevel.N)
+          .put("1910-transform-with-default", AndroidApiLevel.N)
           // Interface initializer is not triggered after desugaring.
-          .put("962-iface-static", AndroidApiLevel.N.getLevel())
+          .put("962-iface-static", AndroidApiLevel.N)
           // Interface initializer is not triggered after desugaring.
-          .put("964-default-iface-init-gen", AndroidApiLevel.N.getLevel())
+          .put("964-default-iface-init-gen", AndroidApiLevel.N)
           // AbstractMethodError (for method not implemented in class) instead of
           // IncompatibleClassChangeError (for conflict of default interface methods).
-          .put("968-default-partial-compile-gen", AndroidApiLevel.N.getLevel())
+          .put("968-default-partial-compile-gen", AndroidApiLevel.N)
           // NoClassDefFoundError (for companion class) instead of NoSuchMethodError.
-          .put("970-iface-super-resolution-gen", AndroidApiLevel.N.getLevel())
+          .put("970-iface-super-resolution-gen", AndroidApiLevel.N)
           // NoClassDefFoundError (for companion class) instead of AbstractMethodError.
-          .put("971-iface-super", AndroidApiLevel.N.getLevel())
+          .put("971-iface-super", AndroidApiLevel.N)
           // Test for miranda methods is not relevant for desugaring scenario.
-          .put("972-default-imt-collision", AndroidApiLevel.N.getLevel())
+          .put("972-default-imt-collision", AndroidApiLevel.N)
           // Uses default interface methods.
-          .put("972-iface-super-multidex", AndroidApiLevel.N.getLevel())
+          .put("972-iface-super-multidex", AndroidApiLevel.N)
           // java.util.Objects is missing and test has default methods.
-          .put("973-default-multidex", AndroidApiLevel.N.getLevel())
+          .put("973-default-multidex", AndroidApiLevel.N)
           // a.klass.that.does.not.Exist is missing and test has default methods.
-          .put("974-verify-interface-super", AndroidApiLevel.N.getLevel())
+          .put("974-verify-interface-super", AndroidApiLevel.N)
           // Desugaring of interface private methods is not yet supported.
-          .put("975-iface-private", AndroidApiLevel.N.getLevel())
+          .put("975-iface-private", AndroidApiLevel.N)
           .build();
 
   // Tests that timeout when run with Art.
@@ -1390,13 +1390,13 @@
                 .setMode(mode)
                 .addProgramFiles(ListUtils.map(fileNames, Paths::get))
                 .setOutput(Paths.get(resultPath), OutputMode.DexIndexed);
-        Integer minSdkVersion = needMinSdkVersion.get(name);
+        AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
         if (minSdkVersion != null) {
-          builder.setMinApiLevel(minSdkVersion);
+          builder.setMinApiLevel(minSdkVersion.getLevel());
           builder.addLibraryFiles(ToolHelper.getAndroidJar(minSdkVersion));
         } else {
           builder
-              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault().getLevel()));
+              .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.getDefault()));
         }
         D8.run(builder.build());
         break;
@@ -1409,12 +1409,12 @@
                   .setOutput(Paths.get(resultPath), OutputMode.DexIndexed);
           // Add program files directly to the underlying app to avoid errors on DEX inputs.
           ToolHelper.getAppBuilder(builder).addProgramFiles(ListUtils.map(fileNames, Paths::get));
-          Integer minSdkVersion = needMinSdkVersion.get(name);
+          AndroidApiLevel minSdkVersion = needMinSdkVersion.get(name);
           if (minSdkVersion != null) {
-            builder.setMinApiLevel(minSdkVersion);
+            builder.setMinApiLevel(minSdkVersion.getLevel());
             ToolHelper.addFilteredAndroidJar(builder, minSdkVersion);
           } else {
-            ToolHelper.addFilteredAndroidJar(builder, AndroidApiLevel.getDefault().getLevel());
+            ToolHelper.addFilteredAndroidJar(builder, AndroidApiLevel.getDefault());
           }
           if (keepRulesFile != null) {
             builder.addProguardConfigurationFiles(Paths.get(keepRulesFile));
@@ -1424,7 +1424,7 @@
               builder.build(),
               options -> {
                 if (disableInlining) {
-                  options.inlineAccessors = false;
+                  options.enableInlining = false;
                 }
                 options.lineNumberOptimization = LineNumberOptimization.OFF;
                 // Some tests actually rely on missing classes for what they test.
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
index 751c34e..b6b9ba8 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidOTest.java
@@ -48,7 +48,7 @@
   @Test
   public void invokeCustomWithShrinking() throws Throwable {
     test("invokecustom-with-shrinking", "invokecustom", "InvokeCustom")
-        .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .withMinApiLevel(AndroidApiLevel.O)
         .withBuilderTransformation(builder ->
             builder.addProguardConfigurationFiles(
                 Paths.get(ToolHelper.EXAMPLES_ANDROID_O_DIR, "invokecustom/keep-rules.txt")))
@@ -62,8 +62,8 @@
     }
 
     @Override
-    R8TestRunner withMinApiLevel(int minApiLevel) {
-      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel));
+    R8TestRunner withMinApiLevel(AndroidApiLevel minApiLevel) {
+      return withBuilderTransformation(builder -> builder.setMinApiLevel(minApiLevel.getLevel()));
     }
 
     @Override
@@ -72,8 +72,8 @@
       for (UnaryOperator<R8Command.Builder> transformation : builderTransformations) {
         builder = transformation.apply(builder);
       }
-      builder.addLibraryFiles(ToolHelper
-          .getAndroidJar(androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion));
+      builder.addLibraryFiles(ToolHelper.getAndroidJar(
+          androidJarVersion == null ? builder.getMinApiLevel() : androidJarVersion.getLevel()));
       R8Command command = builder.addProgramFiles(inputFile).build();
       ToolHelper.runR8(command, this::combinedOptionConsumer);
     }
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
index becfa6d..f4665a9 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesAndroidPTest.java
@@ -72,7 +72,7 @@
         builder = transformation.apply(builder);
       }
       // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
-      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O.getLevel()));
+      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O));
       R8Command command =
           builder.addProgramFiles(inputFile).setOutput(out, OutputMode.DexIndexed).build();
       ToolHelper.runR8(command, this::combinedOptionConsumer);
diff --git a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
index 1a307a5..670e6be 100644
--- a/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
+++ b/src/test/java/com/android/tools/r8/R8RunExamplesJava9Test.java
@@ -28,7 +28,7 @@
         builder = transformation.apply(builder);
       }
       // TODO(mikaelpeltier) Add new android.jar build from aosp and use it
-      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P.getLevel()));
+      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
       R8Command command =
           builder.addProgramFiles(inputFile).setOutput(out, OutputMode.DexIndexed).build();
       ToolHelper.runR8(command, this::combinedOptionConsumer);
diff --git a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
index fa507b0..1bbcfdc 100644
--- a/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
+++ b/src/test/java/com/android/tools/r8/RunExamplesAndroidOTest.java
@@ -57,7 +57,7 @@
     final String packageName;
     final String mainClass;
 
-    Integer androidJarVersion = null;
+    AndroidApiLevel androidJarVersion = null;
 
     final List<Consumer<InternalOptions>> optionConsumers = new ArrayList<>();
     final List<Consumer<DexInspector>> dexInspectorChecks = new ArrayList<>();
@@ -162,9 +162,9 @@
       execute(testName, qualifiedMainClass, new Path[]{inputFile}, new Path[]{out});
     }
 
-    abstract C withMinApiLevel(int minApiLevel);
+    abstract C withMinApiLevel(AndroidApiLevel minApiLevel);
 
-    C withAndroidJar(int androidJarVersion) {
+    C withAndroidJar(AndroidApiLevel androidJarVersion) {
       assert this.androidJarVersion == null;
       this.androidJarVersion = androidJarVersion;
       return self();
@@ -291,56 +291,56 @@
   @Test
   public void stringConcat() throws Throwable {
     test("stringconcat", "stringconcat", "StringConcat")
-        .withMinApiLevel(AndroidApiLevel.K.getLevel())
+        .withMinApiLevel(AndroidApiLevel.K)
         .run();
   }
 
   @Test
   public void invokeCustom() throws Throwable {
     test("invokecustom", "invokecustom", "InvokeCustom")
-        .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .withMinApiLevel(AndroidApiLevel.O)
         .run();
   }
 
   @Test
   public void invokeCustom2() throws Throwable {
     test("invokecustom2", "invokecustom2", "InvokeCustom")
-        .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .withMinApiLevel(AndroidApiLevel.O)
         .run();
   }
 
   @Test
   public void invokeCustomErrorDueToMinSdk() throws Throwable {
     test("invokecustom-error-due-to-min-sdk", "invokecustom", "InvokeCustom")
-        .withMinApiLevel(25)
+        .withMinApiLevel(AndroidApiLevel.N_MR1)
         .run();
   }
 
   @Test
   public void invokePolymorphic() throws Throwable {
     test("invokepolymorphic", "invokepolymorphic", "InvokePolymorphic")
-        .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .withMinApiLevel(AndroidApiLevel.O)
         .run();
   }
 
   @Test
   public void invokePolymorphicErrorDueToMinSdk() throws Throwable {
     test("invokepolymorphic-error-due-to-min-sdk", "invokepolymorphic", "InvokePolymorphic")
-        .withMinApiLevel(25)
+        .withMinApiLevel(AndroidApiLevel.N_MR1)
         .run();
   }
 
   @Test
   public void lambdaDesugaring() throws Throwable {
     test("lambdadesugaring", "lambdadesugaring", "LambdaDesugaring")
-        .withMinApiLevel(AndroidApiLevel.K.getLevel())
+        .withMinApiLevel(AndroidApiLevel.K)
         .run();
   }
 
   @Test
   public void lambdaDesugaringNPlus() throws Throwable {
     test("lambdadesugaringnplus", "lambdadesugaringnplus", "LambdasWithStaticAndDefaultMethods")
-        .withMinApiLevel(AndroidApiLevel.K.getLevel())
+        .withMinApiLevel(AndroidApiLevel.K)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .run();
   }
@@ -348,8 +348,8 @@
   @Test
   public void desugarDefaultMethodInAndroidJar25() throws Throwable {
     test("DefaultMethodInAndroidJar25", "desugaringwithandroidjar25", "DefaultMethodInAndroidJar25")
-        .withMinApiLevel(AndroidApiLevel.K.getLevel())
-        .withAndroidJar(AndroidApiLevel.O.getLevel())
+        .withMinApiLevel(AndroidApiLevel.K)
+        .withAndroidJar(AndroidApiLevel.O)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .run();
   }
@@ -357,8 +357,8 @@
   @Test
   public void desugarStaticMethodInAndroidJar25() throws Throwable {
     test("StaticMethodInAndroidJar25", "desugaringwithandroidjar25", "StaticMethodInAndroidJar25")
-        .withMinApiLevel(AndroidApiLevel.K.getLevel())
-        .withAndroidJar(AndroidApiLevel.O.getLevel())
+        .withMinApiLevel(AndroidApiLevel.K)
+        .withAndroidJar(AndroidApiLevel.O)
         .withInterfaceMethodDesugaring(OffOrAuto.Auto)
         .run();
   }
@@ -366,14 +366,14 @@
   @Test
   public void lambdaDesugaringValueAdjustments() throws Throwable {
     test("lambdadesugaring-value-adjustments", "lambdadesugaring", "ValueAdjustments")
-        .withMinApiLevel(AndroidApiLevel.K.getLevel())
+        .withMinApiLevel(AndroidApiLevel.K)
         .run();
   }
 
   @Test
   public void paramNames() throws Throwable {
     test("paramnames", "paramnames", "ParameterNames")
-        .withMinApiLevel(AndroidApiLevel.O.getLevel())
+        .withMinApiLevel(AndroidApiLevel.O)
         .run();
   }
 
@@ -469,7 +469,7 @@
       int expectedMainDexListSize,
       String... mainDexClasses)
       throws Throwable {
-    int minApi = AndroidApiLevel.K.getLevel();
+    AndroidApiLevel minApi = AndroidApiLevel.K;
 
     // Full build, will be used as reference.
     TestRunner<?> full =
@@ -514,7 +514,7 @@
       String packageName,
       Path input,
       OutputMode outputMode,
-      int minApi,
+      AndroidApiLevel minApi,
       String... mainDexClasses)
       throws Throwable {
     Path intermediateDex =
@@ -522,7 +522,7 @@
     // Build intermediate with D8.
     D8Command.Builder command = D8Command.builder()
         .setOutput(intermediateDex, outputMode)
-        .setMinApiLevel(minApi)
+        .setMinApiLevel(minApi.getLevel())
         .addLibraryFiles(ToolHelper.getAndroidJar(minApi))
         .setIntermediate(true)
         .addProgramFiles(input);
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index cf13f41..77f9ad9 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -7,6 +7,7 @@
 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.code.Instruction;
 import com.android.tools.r8.graph.DexCode;
@@ -14,6 +15,7 @@
 import com.android.tools.r8.graph.SmaliWriter;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.DescriptorUtils;
@@ -32,6 +34,7 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
@@ -43,6 +46,7 @@
 import java.util.jar.JarOutputStream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
 import org.junit.Rule;
 import org.junit.rules.TemporaryFolder;
 
@@ -61,6 +65,18 @@
   }
 
   /**
+   * Build an AndroidApp with the specified test classes as byte array.
+   */
+  protected AndroidApp buildAndroidApp(byte[]... classes) throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    for (byte[] clazz : classes) {
+      builder.addClassProgramData(clazz, Origin.unknown());
+    }
+    builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.N.getLevel()));
+    return builder.build();
+  }
+
+  /**
    * Build an AndroidApp with the specified test classes.
    */
   protected static AndroidApp readClasses(Class... classes) throws IOException {
@@ -95,6 +111,22 @@
     return builder.build();
   }
 
+  protected static AndroidApp readClassesAndAndriodJar(List<Class> programClasses)
+      throws IOException {
+    return readClassesAndAndriodJar(programClasses, ToolHelper.getMinApiLevelForDexVm());
+  }
+
+  protected static AndroidApp readClassesAndAndriodJar(
+      List<Class> programClasses, AndroidApiLevel androidLibrary)
+      throws IOException {
+    AndroidApp.Builder builder = AndroidApp.builder();
+    for (Class clazz : programClasses) {
+      builder.addProgramFiles(ToolHelper.getClassFileForTestClass(clazz));
+    }
+    builder.addLibraryFiles(ToolHelper.getAndroidJar(androidLibrary));
+    return builder.build();
+  }
+
   /**
    * Create a temporary JAR file containing the specified test classes.
    */
@@ -348,12 +380,20 @@
   }
 
   /**
+   * Run application on the specified version of Art with the specified main class.
+   */
+  protected ProcessResult runOnArtRaw(AndroidApp app, String mainClass, DexVm version)
+      throws IOException {
+    Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
+    app.writeToZip(out, OutputMode.DexIndexed);
+    return ToolHelper.runArtRaw(ImmutableList.of(out.toString()), mainClass, null, version);
+  }
+
+  /**
    * Run application on Art with the specified main class.
    */
   protected ProcessResult runOnArtRaw(AndroidApp app, String mainClass) throws IOException {
-    Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
-    app.writeToZip(out, OutputMode.DexIndexed);
-    return ToolHelper.runArtRaw(ImmutableList.of(out.toString()), mainClass, null);
+    return runOnArtRaw(app, mainClass, null);
   }
 
   /**
@@ -387,7 +427,17 @@
   /**
    * Run application on Art with the specified main class and provided arguments.
    */
-  protected String runOnArt(AndroidApp app, String mainClass, List<String> args) throws IOException {
+  protected String runOnArt(AndroidApp app, String mainClass, List<String> args)
+      throws IOException {
+    return runOnArt(app, mainClass, args, null);
+  }
+
+  /**
+   * Run application on Art with the specified main class, provided arguments, and specified VM
+   * version.
+   */
+  protected String runOnArt(AndroidApp app, String mainClass, List<String> args, DexVm dexVm)
+      throws IOException {
     Path out = File.createTempFile("junit", ".zip", temp.getRoot()).toPath();
     app.writeToZip(out, OutputMode.DexIndexed);
     return ToolHelper.runArtNoVerificationErrors(
@@ -397,7 +447,8 @@
           for (String arg : args) {
             builder.appendProgramArgument(arg);
           }
-        });
+        },
+        dexVm);
   }
 
   /**
@@ -429,6 +480,38 @@
     return result.stdout;
   }
 
+  protected ProcessResult runOnJava(String main, byte[]... classes) throws IOException {
+    Path file = writeToZip(classes);
+    return ToolHelper.runJavaNoVerify(file, main);
+  }
+
+  private Path writeToZip(byte[]... classes) throws IOException {
+    File result = temp.newFile();
+    try (ZipOutputStream out = new ZipOutputStream(Files.newOutputStream(result.toPath(),
+        StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
+      for (byte[] clazz : classes) {
+        String name = loadClassFromDump(clazz).getTypeName();
+        ZipEntry zipEntry = new ZipEntry(DescriptorUtils.getPathFromJavaType(name));
+        zipEntry.setSize(clazz.length);
+        out.putNextEntry(zipEntry);
+        out.write(clazz);
+        out.closeEntry();
+      }
+    }
+    return result.toPath();
+  }
+
+  protected static Class loadClassFromDump(byte[] dump) {
+    return new DumpLoader().loadClass(dump);
+  }
+
+  private static class DumpLoader extends ClassLoader {
+
+    @SuppressWarnings("deprecation")
+    public Class loadClass(byte[] clazz) {
+      return defineClass(clazz, 0, clazz.length);
+    }
+  }
 
   /**
    * Disassemble the content of an application. Only works for an application with only dex code.
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 7883df8..cc325e2 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -88,7 +88,7 @@
   public static final String DEFAULT_PROGUARD_MAP_FILE = "proguard.map";
 
   private static final String ANDROID_JAR_PATTERN = "third_party/android_jar/lib-v%d/android.jar";
-  private static final int DEFAULT_MIN_SDK = AndroidApiLevel.I.getLevel();
+  private static final AndroidApiLevel DEFAULT_MIN_SDK = AndroidApiLevel.I;
 
   private static final String PROGUARD = "third_party/proguard/proguard5.2.1/bin/proguard.sh";
 
@@ -440,6 +440,10 @@
   private static final String ANGLER_DIR = TOOLS + "/linux/art/product/angler";
   private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
 
+  public static byte[] getClassAsBytes(Class clazz) throws IOException {
+    return ByteStreams.toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
+  }
+
   public static String getArtDir(DexVm version) {
     String dir = ART_DIRS.get(version);
     if (dir == null) {
@@ -479,38 +483,49 @@
   }
 
   public static Path getDefaultAndroidJar() {
-    return getAndroidJar(AndroidApiLevel.getDefault().getLevel());
+    return getAndroidJar(AndroidApiLevel.getDefault());
   }
 
-  public static Path getAndroidJar(int minSdkVersion) {
-    if (minSdkVersion == AndroidApiLevel.P.getLevel()) {
+  public static Path getAndroidJar(int apiLevel) {
+    return getAndroidJar(AndroidApiLevel.getAndroidApiLevel(apiLevel));
+  }
+
+  public static Path getAndroidJar(AndroidApiLevel apiLevel) {
+    if (apiLevel == AndroidApiLevel.P) {
       // TODO(mikaelpeltier) Android P does not yet have his android.jar use the O version
-      minSdkVersion = AndroidApiLevel.O.getLevel();
+      apiLevel = AndroidApiLevel.O;
     }
     String jar = String.format(
         ANDROID_JAR_PATTERN,
-        minSdkVersion == AndroidApiLevel.getDefault().getLevel() ? DEFAULT_MIN_SDK : minSdkVersion);
+        (apiLevel == AndroidApiLevel.getDefault() ? DEFAULT_MIN_SDK : apiLevel).getLevel());
     assert Files.exists(Paths.get(jar))
-        : "Expected android jar to exist for API level " + minSdkVersion;
+        : "Expected android jar to exist for API level " + apiLevel;
     return Paths.get(jar);
   }
 
-  public static Path getJdwpTestsCfJarPath(int minSdk) {
-    if (minSdk >= AndroidApiLevel.N.getLevel()) {
+  public static Path getJdwpTestsCfJarPath(AndroidApiLevel minSdk) {
+    if (minSdk.getLevel() >= AndroidApiLevel.N.getLevel()) {
       return Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-host.jar");
     } else {
       return Paths.get(ToolHelper.BUILD_DIR, "libs", "jdwp-tests-preN.jar");
     }
   }
 
-  public static Path getJdwpTestsDexJarPath(int minSdk) {
-    if (minSdk >= AndroidApiLevel.N.getLevel()) {
+  public static Path getJdwpTestsDexJarPath(AndroidApiLevel minSdk) {
+    if (minSdk.getLevel() >= AndroidApiLevel.N.getLevel()) {
       return Paths.get("third_party", "jdwp-tests", "apache-harmony-jdwp-tests-hostdex.jar");
     } else {
       return Paths.get(ToolHelper.BUILD_DIR, "libs", "jdwp-tests-preN-dex.jar");
     }
   }
 
+  /**
+   * Get the junit jar bundled with the framework.
+   */
+  public static Path getFrameworkJunitJarPath(DexVm version) {
+    return Paths.get(getArtDir(version), "framework", "junit.jar");
+  }
+
   static class RetainedTemporaryFolder extends TemporaryFolder {
 
     RetainedTemporaryFolder(java.io.File parentFolder) {
@@ -582,20 +597,24 @@
     }
   }
 
-  public static int getMinApiLevelForDexVm(DexVm dexVm) {
+  public static AndroidApiLevel getMinApiLevelForDexVm() {
+    return getMinApiLevelForDexVm(ToolHelper.getDexVm());
+  }
+
+  public static AndroidApiLevel getMinApiLevelForDexVm(DexVm dexVm) {
     switch (dexVm.version) {
       case DEFAULT:
-        return AndroidApiLevel.O.getLevel();
+        return AndroidApiLevel.O;
       case V7_0_0:
-        return AndroidApiLevel.N.getLevel();
+        return AndroidApiLevel.N;
       case V6_0_1:
-        return AndroidApiLevel.M.getLevel();
+        return AndroidApiLevel.M;
       case V5_1_1:
-        return AndroidApiLevel.L_MR1.getLevel();
+        return AndroidApiLevel.L_MR1;
       case V4_4_4:
-        return AndroidApiLevel.K.getLevel();
+        return AndroidApiLevel.K;
       case V4_0_4:
-        return AndroidApiLevel.I_MR1.getLevel();
+        return AndroidApiLevel.I_MR1;
       default:
         throw new Unreachable("Missing min api level for dex vm " + dexVm);
     }
@@ -769,7 +788,7 @@
       // Add the android library matching the minsdk. We filter out junit and testing classes
       // from the android jar to avoid duplicate classes in art and jctf tests.
       AndroidApp.Builder builder = AndroidApp.builder(app);
-      addFilteredAndroidJar(builder, command.getMinApiLevel());
+      addFilteredAndroidJar(builder, AndroidApiLevel.getAndroidApiLevel(command.getMinApiLevel()));
       app = builder.build();
     }
     InternalOptions options = command.getInternalOptions();
@@ -781,15 +800,15 @@
     return compatSink.build();
   }
 
-  public static void addFilteredAndroidJar(BaseCommand.Builder builder, int minSdkVersion)
+  public static void addFilteredAndroidJar(BaseCommand.Builder builder, AndroidApiLevel apiLevel)
       throws IOException {
-    addFilteredAndroidJar(getAppBuilder(builder), minSdkVersion);
+    addFilteredAndroidJar(getAppBuilder(builder), apiLevel);
   }
 
-  public static void addFilteredAndroidJar(AndroidApp.Builder builder, int minSdkVersion)
+  public static void addFilteredAndroidJar(AndroidApp.Builder builder, AndroidApiLevel apiLevel)
       throws IOException {
     builder.addFilteredLibraryArchives(Collections.singletonList(
-        new FilteredClassPath(getAndroidJar(minSdkVersion),
+        new FilteredClassPath(getAndroidJar(apiLevel),
             ImmutableList.of("!junit/**", "!android/test/**"))));
   }
 
@@ -1226,4 +1245,19 @@
         options,
         null);
   }
+
+  public enum KotlinTargetVersion {
+    JAVA_6("JAVA_6"),
+    JAVA_8("JAVA_8");
+
+    private final String folderName;
+
+    KotlinTargetVersion(String folderName) {
+      this.folderName = folderName;
+    }
+
+    public String getFolderName() {
+      return folderName;
+    }
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/VmTestRunner.java b/src/test/java/com/android/tools/r8/VmTestRunner.java
index f377543..978cbe7 100644
--- a/src/test/java/com/android/tools/r8/VmTestRunner.java
+++ b/src/test/java/com/android/tools/r8/VmTestRunner.java
@@ -19,6 +19,16 @@
 public class VmTestRunner extends BlockJUnit4ClassRunner {
 
   /**
+   * Ignores the test for all VM versions up to {@link #value()}.
+   */
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target({ElementType.METHOD, ElementType.TYPE})
+  public @interface IgnoreIfVmOlderThan {
+
+    DexVm.Version value();
+  }
+
+  /**
    * Ignores the test for all VM versions up to and includion {@link #value()}.
    */
   @Retention(RetentionPolicy.RUNTIME)
@@ -65,6 +75,12 @@
       return true;
     }
     DexVm.Version currentVersion = ToolHelper.getDexVm().getVersion();
+    IgnoreIfVmOlderThan ignoreIfVmOlderThan =
+        child.getAnnotation(IgnoreIfVmOlderThan.class);
+    if (ignoreIfVmOlderThan != null
+        && !currentVersion.isAtLeast(ignoreIfVmOlderThan.value())) {
+      return true;
+    }
     IgnoreIfVmOlderOrEqualThan ignoreIfVmOlderOrEqualThan =
         child.getAnnotation(IgnoreIfVmOlderOrEqualThan.class);
     if (ignoreIfVmOlderOrEqualThan != null
diff --git a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
index 18e17f9..34b92b0 100644
--- a/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
+++ b/src/test/java/com/android/tools/r8/accessrelaxation/AccessRelaxationTest.java
@@ -25,7 +25,7 @@
     R8Command.Builder builder = R8Command.builder();
     builder.addProgramFiles(ToolHelper.getClassFilesForTestPackage(A.class.getPackage()));
     builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
-    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
+    builder.setMinApiLevel(ToolHelper.getMinApiLevelForDexVm().getLevel());
 
     // Note: we use '-checkdiscard' to indirectly check that the access relaxation is
     // done which leads to inlining of all pB*** methods so they are removed. Without
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
index 585b949..55ff3a2 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/KeepNonVisibilityBridgeMethodsTest.java
@@ -39,7 +39,9 @@
         SimpleDataAdapter.class.getMethod("registerObserver", DataAdapter.Observer.class);
     MethodSubject subject = inspector.method(registerObserver);
     assertTrue(subject.isPresent());
-    assertTrue(subject.isBridge());
+    // The method is there, but it might be unmarked as a bridge if
+    // another method is inlined into it.
+    // assertTrue(subject.isBridge());
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java
index 4d80056..44cbe28 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/Main.java
@@ -7,11 +7,21 @@
 // Reduced test case from code where removal of bridge methods caused failure.
 public class Main {
 
-  public static void registerObserver(DataAdapter dataAdapter) {
-    dataAdapter.registerObserver(null);
+  private static class DataAdapterObserver implements DataAdapter.Observer {
+  }
+
+  private static class ObservableListObserver implements ObservableList.Observer {
+  }
+
+  static void registerObserver(DataAdapter dataAdapter) {
+    dataAdapter.registerObserver(new DataAdapterObserver());
   }
 
   public static void main(String[] args) {
     registerObserver(new SimpleDataAdapter());
+
+    // To prevent SimpleObservableList#registerObserver from being inlined.
+    SimpleObservableList<ObservableListObserver> originalImpl = new SimpleObservableList<>();
+    originalImpl.registerObserver(new ObservableListObserver());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
index 5fa667b..5af0ff2 100644
--- a/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
+++ b/src/test/java/com/android/tools/r8/bridgeremoval/bridgestokeep/SimpleObservableList.java
@@ -5,11 +5,17 @@
 package com.android.tools.r8.bridgeremoval.bridgestokeep;
 
 import com.android.tools.r8.bridgeremoval.bridgestokeep.ObservableList.Observer;
+import java.util.List;
 
 public class SimpleObservableList<O extends Observer>
     implements ObservableList<O> {
 
+  private List<O> observers;
+
   @Override
   public void registerObserver(O observer) {
+    if (observer != null && observers != null && !observers.contains(observer)) {
+      observers.add(observer);
+    }
   }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
index a56c0c8..1938c46 100644
--- a/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
+++ b/src/test/java/com/android/tools/r8/checkdiscarded/CheckDiscardedTest.java
@@ -36,7 +36,7 @@
   }
 
   private void noInlining(InternalOptions options) {
-    options.inlineAccessors = false;
+    options.enableInlining = false;
   }
 
   private String checkDiscardRule(boolean member, Class annotation) {
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 52066a6..e256ae4 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -38,7 +38,7 @@
   public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
 
   private void configure(InternalOptions options) {
-    options.skipClassMerging = false;
+    options.enableClassMerging = true;
   }
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java
new file mode 100644
index 0000000..76fcd19f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class BreakInOneLineFinallyTest {
+  int i = 0;
+
+  void foo() {
+    try { bar(); }
+    finally { baz(); } // Java will fail to break here on exceptional exit.
+  }
+
+  int bar() {
+    if (i++ % 2 == 0) {
+      System.out.println("bar return " + i);
+      return i;
+    }
+    System.out.println("bar throw " + i);
+    throw new RuntimeException("" + i);
+  }
+
+  void baz() {
+    System.out.println("baz");
+  }
+
+  public static void main(String[] args) {
+    BreakInOneLineFinallyTest test = new BreakInOneLineFinallyTest();
+    test.foo();
+    try {
+      test.foo();
+    } catch (RuntimeException e) {
+      System.out.println("Caught expected exception: " + e.getMessage());
+      return;
+    }
+    throw new RuntimeException("Test failed...");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java
new file mode 100644
index 0000000..6d7d1a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BreakInOneLineFinallyTestRunner extends DebugTestBase {
+
+  private static final Class CLASS = BreakInOneLineFinallyTest.class;
+  private static final String FILE = CLASS.getSimpleName() + ".java";
+  private static final String NAME = CLASS.getCanonicalName();
+
+  private final DebugTestConfig config;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<Object[]> setup() {
+    DelayedDebugTestConfig cf =
+            temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+    DelayedDebugTestConfig d8 =
+            temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+    return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+  }
+
+  public BreakInOneLineFinallyTestRunner(String name, DelayedDebugTestConfig config) {
+    this.config = config.getConfig(temp);
+  }
+
+  @Test
+  public void testHitBreakpointOnNormalAndExceptionalFlow() throws Throwable {
+    Assume.assumeFalse(
+        "b/72933440 : JavaC doesn't duplicate line-table entries when duplicating finally blocks",
+        config instanceof D8DebugTestConfig);
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "foo", 11),
+        run(),
+        checkLine(FILE, 11), // hit finally on normal flow
+        breakpoint(NAME, "main", 34), // can't hit the exceptional block :-(
+        run(),
+        checkLine(FILE, 34),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java
new file mode 100644
index 0000000..eec9d13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class BreakInTwoLinesFinallyTest {
+  int i = 0;
+
+  void foo() {
+    try { bar(); }
+    finally {
+      baz();
+    }
+  }
+
+  int bar() {
+    if (i++ % 2 == 0) {
+      System.out.println("bar return " + i);
+      return i;
+    }
+    System.out.println("bar throw " + i);
+    throw new RuntimeException("" + i);
+  }
+
+  void baz() {
+    System.out.println("baz");
+  }
+
+  public static void main(String[] args) {
+    BreakInTwoLinesFinallyTest test = new BreakInTwoLinesFinallyTest();
+    test.foo();
+    try {
+      test.foo();
+    } catch (RuntimeException e) {
+      System.out.println("Caught expected exception: " + e.getMessage());
+      return;
+    }
+    throw new RuntimeException("Test failed...");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java
new file mode 100644
index 0000000..7691a2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BreakInTwoLinesFinallyTestRunner extends DebugTestBase {
+
+  private static final Class CLASS = BreakInTwoLinesFinallyTest.class;
+  private static final String FILE = CLASS.getSimpleName() + ".java";
+  private static final String NAME = CLASS.getCanonicalName();
+
+  private final DebugTestConfig config;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<Object[]> setup() {
+    DelayedDebugTestConfig cf =
+            temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+    DelayedDebugTestConfig d8 =
+            temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+    return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+  }
+
+  public BreakInTwoLinesFinallyTestRunner(String name, DelayedDebugTestConfig config) {
+    this.config = config.getConfig(temp);
+  }
+
+  @Test
+  public void testHitBreakpointOnNormalAndExceptionalFlow() throws Throwable {
+    Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_6_0_1_HOST));
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "foo", 12),
+        run(),
+        checkLine(FILE, 12), // hit finally on normal flow
+        run(),
+        checkLine(FILE, 12), // hit finally on exceptional flow
+        breakpoint(NAME, "main", 36),
+        run(),
+        checkLine(FILE, 36),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
index 6049b7d..e8fa240 100644
--- a/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/CfDebugTestConfig.java
@@ -13,8 +13,7 @@
 /** Base test configuration with CF version of JDWP. */
 public class CfDebugTestConfig extends DebugTestConfig {
 
-  public static final Path JDWP_JAR =
-      ToolHelper.getJdwpTestsCfJarPath(AndroidApiLevel.N.getLevel());
+  public static final Path JDWP_JAR = ToolHelper.getJdwpTestsCfJarPath(AndroidApiLevel.N);
 
   public CfDebugTestConfig() {
     this(Collections.emptyList());
diff --git a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
index d535f72..1f13006 100644
--- a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
@@ -7,10 +7,11 @@
 import com.android.tools.r8.D8Command;
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import java.util.function.Consumer;
@@ -21,12 +22,12 @@
 
   public static AndroidApp d8Compile(List<Path> paths, Consumer<InternalOptions> optionsConsumer) {
     try {
-      int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+      AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
       D8Command.Builder builder = D8Command.builder();
       return ToolHelper.runD8(
           builder
               .addProgramFiles(paths)
-              .setMinApiLevel(minSdk)
+              .setMinApiLevel(minSdk.getLevel())
               .setMode(CompilationMode.DEBUG)
               .addLibraryFiles(ToolHelper.getAndroidJar(minSdk)),
           optionsConsumer);
@@ -35,6 +36,14 @@
     }
   }
 
+  public D8DebugTestConfig compileAndAddClasses(TemporaryFolder temp, Class... classes) {
+    return compileAndAddClasses(temp, Arrays.asList(classes));
+  }
+
+  public D8DebugTestConfig compileAndAddClasses(TemporaryFolder temp, List<Class> classes) {
+    return compileAndAdd(temp, ListUtils.map(classes, ToolHelper::getClassFileForTestClass), null);
+  }
+
   public D8DebugTestConfig compileAndAdd(TemporaryFolder temp, Path... paths) {
     return compileAndAdd(temp, Arrays.asList(paths), null);
   }
diff --git a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
index 3b7e4e8..6432530 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugInfoWhenInliningTest.java
@@ -8,10 +8,10 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.debug.DebugTestBase.JUnit3Wrapper.Command;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -33,7 +33,7 @@
       boolean writeProguardMap)
       throws Exception {
     assert outputMode == OutputMode.DexIndexed || outputMode == OutputMode.ClassFile;
-    int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
     String prefix = outputMode == OutputMode.ClassFile ? "cf" : "dex";
     Path outdir = temp.newFolder().toPath();
     Path outjar = outdir.resolve(prefix + "_r8_compiled.jar");
@@ -41,7 +41,7 @@
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(DEBUGGEE_JAR)
-            .setMinApiLevel(minSdk)
+            .setMinApiLevel(minSdk.getLevel())
             .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
             .setMode(CompilationMode.RELEASE)
             .setOutput(outjar, outputMode);
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 94d5666..cbfcca6 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -115,7 +115,7 @@
 
   protected static final boolean supportsDefaultMethod(DebugTestConfig config) {
     return config.isCfRuntime()
-        || ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()) >= AndroidApiLevel.N.getLevel();
+        || ToolHelper.getMinApiLevelForDexVm().getLevel() >= AndroidApiLevel.N.getLevel();
   }
 
   protected final void runDebugTest(
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index aff3708..556e17f 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -37,12 +37,14 @@
     return paths;
   }
 
-  public void addPaths(Path... paths) {
+  public DebugTestConfig addPaths(Path... paths) {
     addPaths(Arrays.asList(paths));
+    return this;
   }
 
-  public void addPaths(List<Path> paths) {
+  public DebugTestConfig addPaths(List<Path> paths) {
     this.paths.addAll(paths);
+    return this;
   }
 
   /** Proguard map that the debuggee has been translated according to, null if not present. */
@@ -50,8 +52,9 @@
     return proguardMap;
   }
 
-  public void setProguardMap(Path proguardMap) {
+  public DebugTestConfig setProguardMap(Path proguardMap) {
     this.proguardMap = proguardMap;
+    return this;
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
index 27e3d30..babf170 100644
--- a/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DexDebugTestConfig.java
@@ -13,7 +13,7 @@
 public class DexDebugTestConfig extends DebugTestConfig {
 
   public static final Path JDWP_DEX_JAR =
-      ToolHelper.getJdwpTestsDexJarPath(ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
+      ToolHelper.getJdwpTestsDexJarPath(ToolHelper.getMinApiLevelForDexVm());
 
   public DexDebugTestConfig() {
     this(Collections.emptyList());
diff --git a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
index d7e4d25..d944a75 100644
--- a/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LineNumberOptimizationTest.java
@@ -7,9 +7,9 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.Test;
 
 /** Tests source file and line numbers on inlined methods. */
@@ -31,14 +31,14 @@
       boolean writeProguardMap,
       boolean dontOptimizeByEnablingDebug)
       throws Exception {
-    int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
     Path outdir = temp.newFolder().toPath();
     Path outjar = outdir.resolve("r8_compiled.jar");
     Path proguardMapPath = writeProguardMap ? outdir.resolve("proguard.map") : null;
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(DEBUGGEE_JAR)
-            .setMinApiLevel(minSdk)
+            .setMinApiLevel(minSdk.getLevel())
             .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
             .setMode(dontOptimizeByEnablingDebug ? CompilationMode.DEBUG : CompilationMode.RELEASE)
             .setOutput(outjar, OutputMode.DexIndexed);
@@ -51,7 +51,7 @@
           if (!dontOptimizeByEnablingDebug) {
             options.lineNumberOptimization = lineNumberOptimization;
           }
-          options.inlineAccessors = false;
+          options.enableInlining = false;
         });
     DebugTestConfig config = new D8DebugTestConfig();
     config.addPaths(outjar);
diff --git a/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java
new file mode 100644
index 0000000..9abc732
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+public class LocalChangeOnSameLineTest {
+  int i = 0;
+
+  int bar() {
+    System.out.println("bar call " + ++i);
+    return i;
+  }
+
+  void foo() {
+    { int x = bar(); int y = bar(); }
+    { int x = bar(); int y = bar(); }
+  }
+
+  public static void main(String[] args) {
+    new LocalChangeOnSameLineTest().foo();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java
new file mode 100644
index 0000000..b588590
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LocalChangeOnSameLineTestRunner extends DebugTestBase {
+
+  private static final Class CLASS = LocalChangeOnSameLineTest.class;
+  private static final String FILE = CLASS.getSimpleName() + ".java";
+  private static final String NAME = CLASS.getCanonicalName();
+
+  private final DebugTestConfig config;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static Collection<Object[]> setup() {
+    DelayedDebugTestConfig cf =
+            temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+    DelayedDebugTestConfig d8 =
+            temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+    return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+  }
+
+  public LocalChangeOnSameLineTestRunner(String name, DelayedDebugTestConfig config) {
+    this.config = config.getConfig(temp);
+  }
+
+  /** Test that only hit the break point at line 15 once. */
+  @Test
+  public void testHitBreakpointOnce() throws Throwable {
+    Assume.assumeFalse("b/72933440 : invalid line info table", config instanceof D8DebugTestConfig);
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "foo", 15),
+        run(),
+        checkLine(FILE, 15),
+        breakpoint(NAME, "main", 21),
+        run(),
+        checkLine(FILE, 21),
+        run());
+  }
+
+  /** Test that locals are correct in the frame of foo each time we break in bar. */
+  @Test
+  public void testLocalsOnBreakpoint() throws Throwable {
+    runDebugTest(
+        config,
+        NAME,
+        breakpoint(NAME, "bar"),
+        run(),
+        checkLine(FILE, 10),
+        inspect(t -> t.getFrame(1).checkNoLocal("x")),
+        run(),
+        checkLine(FILE, 10),
+        inspect(t -> t.getFrame(1).checkLocal("x")),
+        run(),
+        checkLine(FILE, 10),
+        inspect(t -> t.getFrame(1).checkNoLocal("x")),
+        run(),
+        checkLine(FILE, 10),
+        inspect(t -> t.getFrame(1).checkLocal("x")),
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/MinificationTest.java b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
index 7bf989c..5f63038 100644
--- a/src/test/java/com/android/tools/r8/debug/MinificationTest.java
+++ b/src/test/java/com/android/tools/r8/debug/MinificationTest.java
@@ -9,10 +9,10 @@
 import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -68,14 +68,14 @@
       proguardConfigurations = builder.build();
     }
 
-    int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
     Path dexOutputDir = temp.newFolder().toPath();
     Path proguardMap = writeProguardMap ? dexOutputDir.resolve("proguard.map") : null;
     R8Command.Builder builder =
         R8Command.builder()
             .addProgramFiles(DEBUGGEE_JAR)
             .setOutput(dexOutputDir, OutputMode.DexIndexed)
-            .setMinApiLevel(minSdk)
+            .setMinApiLevel(minSdk.getLevel())
             .setMode(CompilationMode.DEBUG)
             .addLibraryFiles(ToolHelper.getAndroidJar(minSdk));
     if (proguardMap != null) {
diff --git a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
index d5bbe18..4916e1d 100644
--- a/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/R8CfDebugTestResourcesConfig.java
@@ -8,10 +8,10 @@
 import com.android.tools.r8.R8;
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import java.nio.file.Path;
-import java.nio.file.Paths;
 import org.junit.rules.TemporaryFolder;
 
 // Shared test configuration for R8/CF compiled resources from the "debugTestResources" target.
@@ -21,11 +21,11 @@
 
   private static synchronized AndroidApp getCompiledResources() throws Throwable {
     if (compiledResources == null) {
-      int minApi = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+      AndroidApiLevel minApi = ToolHelper.getMinApiLevelForDexVm();
       AndroidAppConsumers sink = new AndroidAppConsumers();
       R8.run(
           R8Command.builder()
-              .setMinApiLevel(minApi)
+              .setMinApiLevel(minApi.getLevel())
               .setMode(CompilationMode.DEBUG)
               .addProgramFiles(DebugTestBase.DEBUGGEE_JAR)
               .setProgramConsumer(sink.wrapClassFileConsumer(null))
diff --git a/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java b/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
index ab5ce01..97047e8 100644
--- a/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/BasicTestDependenciesDesugaringTest.java
@@ -85,7 +85,7 @@
         D8Command.builder()
             .addClasspathFiles(classpath)
             .addProgramFiles(toCompile)
-            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()))
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
             .setMinApiLevel(AndroidApiLevel.K.getLevel()),
         options -> options.interfaceMethodDesugaring = OffOrAuto.Auto);
   }
@@ -99,7 +99,7 @@
         D8Command.builder()
             .addClasspathFiles(classpath)
             .addProgramFiles(toCompile)
-            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()))
+            .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
             .setMinApiLevel(AndroidApiLevel.K.getLevel()),
         options -> options.interfaceMethodDesugaring = OffOrAuto.Off);
   }
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
new file mode 100644
index 0000000..750a5c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -0,0 +1,252 @@
+// 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.dexsplitter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.D8;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class DexSplitterTests {
+
+  private static final String CLASS_DIR = ToolHelper.EXAMPLES_BUILD_DIR + "classes/dexsplitsample";
+  private static final String CLASS1_CLASS = CLASS_DIR + "/Class1.class";
+  private static final String CLASS2_CLASS = CLASS_DIR + "/Class2.class";
+  private static final String CLASS3_CLASS = CLASS_DIR + "/Class3.class";
+  private static final String CLASS3_INNER_CLASS = CLASS_DIR + "/Class3$InnerClass.class";
+
+  @Rule public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
+
+  /**
+   * To test the file splitting we have 3 classes that we distribute like this: Class1 -> base
+   * Class2 -> feature1 Class3 -> feature1
+   *
+   * <p>Class1 and Class2 works independently of each other, but Class3 extends Class1, and
+   * therefore can't run without the base being loaded.
+   */
+  @Test
+  public void splitFilesNoObfuscation() throws CompilationFailedException, IOException {
+    // Initial normal compile to create dex files.
+    Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+    D8.run(
+        D8Command.builder()
+            .setOutput(inputDex, OutputMode.DexIndexed)
+            .addProgramFiles(Paths.get(CLASS1_CLASS))
+            .addProgramFiles(Paths.get(CLASS2_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .build());
+
+    Path outputDex = temp.getRoot().toPath().resolve("output");
+    Path splitSpec = createSplitSpec();
+
+    DexSplitter.main(
+        new String[] {
+          "--input", inputDex.toString(),
+          "--output", outputDex.toString(),
+          "--feature-splits", splitSpec.toString()
+        });
+
+
+    Path base = outputDex.getParent().resolve("output.base.zip");
+    Path feature = outputDex.getParent().resolve("output.feature1.zip");
+    validateUnobfuscatedOutput(base, feature);
+  }
+
+  private void validateUnobfuscatedOutput(Path base, Path feature) throws IOException {
+    // Both classes should still work if we give all dex files to the system.
+    for (String className : new String[] {"Class1", "Class2", "Class3"}) {
+      ArtCommandBuilder builder = new ArtCommandBuilder();
+      builder.appendClasspath(base.toString());
+      builder.appendClasspath(feature.toString());
+      builder.setMainClass("dexsplitsample." + className);
+      String out = ToolHelper.runArt(builder);
+      assertEquals(out, className + "\n");
+    }
+    // Individual classes should also work from the individual files.
+    String className = "Class1";
+    ArtCommandBuilder builder = new ArtCommandBuilder();
+    builder.appendClasspath(base.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    String out = ToolHelper.runArt(builder);
+    assertEquals(out, className + "\n");
+
+    className = "Class2";
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    out = ToolHelper.runArt(builder);
+    assertEquals(out, className + "\n");
+
+    className = "Class3";
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass("dexsplitsample." + className);
+    try {
+      ToolHelper.runArt(builder);
+      assertFalse(true);
+    } catch (AssertionError assertionError) {
+      // We expect this to throw since base is not in the path and Class3 depends on Class1
+    }
+  }
+
+  private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
+    Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
+    try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
+      out.write(
+          "dexsplitsample.Class1:base\n"
+              + "dexsplitsample.Class2:feature1\n"
+              + "dexsplitsample.Class3:feature1");
+    }
+    return splitSpec;
+  }
+
+  private List<String> getProguardConf() {
+    return ImmutableList.of(
+        "-keep class dexsplitsample.Class3 {",
+        "  public static void main(java.lang.String[]);",
+        "}");
+  }
+
+  @Test
+  public void splitFilesFromJar() throws IOException, CompilationFailedException {
+    // Initial normal compile to create dex files.
+    Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+    D8.run(
+        D8Command.builder()
+            .setOutput(inputDex, OutputMode.DexIndexed)
+            .addProgramFiles(Paths.get(CLASS1_CLASS))
+            .addProgramFiles(Paths.get(CLASS2_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .build());
+
+    Path outputDex = temp.getRoot().toPath().resolve("output");
+    Path baseJar = temp.getRoot().toPath().resolve("base.jar");
+    Path featureJar = temp.getRoot().toPath().resolve("feature1.jar");
+    ZipOutputStream baseStream = new ZipOutputStream(Files.newOutputStream(baseJar));
+    String name = "dexsplitsample/Class1.class";
+    baseStream.putNextEntry(new ZipEntry(name));
+    baseStream.write(Files.readAllBytes(Paths.get(CLASS1_CLASS)));
+    baseStream.closeEntry();
+    baseStream.close();
+
+    ZipOutputStream featureStream = new ZipOutputStream(Files.newOutputStream(featureJar));
+    name = "dexsplitsample/Class2.class";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS2_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class3.class";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_CLASS)));
+    featureStream.closeEntry();
+    name = "dexsplitsample/Class3$InnerClass.class";
+    featureStream.putNextEntry(new ZipEntry(name));
+    featureStream.write(Files.readAllBytes(Paths.get(CLASS3_INNER_CLASS)));
+    featureStream.closeEntry();
+    featureStream.close();
+
+    DexSplitter.main(
+        new String[] {
+          "--input",
+          inputDex.toString(),
+          "--output",
+          outputDex.toString(),
+          "--feature-jar",
+          baseJar.toString(),
+          "--feature-jar",
+          featureJar.toString()
+        });
+    Path base = outputDex.getParent().resolve("output.base.zip");
+    Path feature = outputDex.getParent().resolve("output.feature1.zip");
+    validateUnobfuscatedOutput(base, feature);
+  }
+
+  @Test
+  public void splitFilesObfuscation()
+      throws CompilationFailedException, IOException, ExecutionException {
+    // Initial normal compile to create dex files.
+    Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+    Path proguardMap = temp.getRoot().toPath().resolve("proguard.map");
+
+    R8.run(
+        R8Command.builder()
+            .setOutput(inputDex, OutputMode.DexIndexed)
+            .addProgramFiles(Paths.get(CLASS1_CLASS))
+            .addProgramFiles(Paths.get(CLASS2_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_CLASS))
+            .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+            .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+            .setProguardMapOutputPath(proguardMap)
+            .addProguardConfiguration(getProguardConf(), null)
+            .build());
+
+    Path outputDex = temp.getRoot().toPath().resolve("output");
+    Path splitSpec = createSplitSpec();
+
+    DexSplitter.main(
+        new String[] {
+          "--input", inputDex.toString(),
+          "--output", outputDex.toString(),
+          "--feature-splits", splitSpec.toString(),
+          "--proguard-map", proguardMap.toString()
+        });
+
+    Path base = outputDex.getParent().resolve("output.base.zip");
+    Path feature = outputDex.getParent().resolve("output.feature1.zip");
+    String class3 = "dexsplitsample.Class3";
+    // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
+    // class1 which is in base.
+    ArtCommandBuilder builder = new ArtCommandBuilder();
+    builder.appendClasspath(base.toString());
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass(class3);
+    String out = ToolHelper.runArt(builder);
+    assertEquals(out, "Class3\n");
+
+    // Class1 should not be in the feature, it should still be in base.
+    builder = new ArtCommandBuilder();
+    builder.appendClasspath(feature.toString());
+    builder.setMainClass(class3);
+    try {
+      ToolHelper.runArt(builder);
+      assertFalse(true);
+    } catch (AssertionError assertionError) {
+      // We expect this to throw since base is not in the path and Class3 depends on Class1.
+    }
+
+    // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
+    // shaken away.
+    DexInspector inspector = new DexInspector(base, proguardMap.toString());
+    ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
+    assertTrue(subject.isPresent());
+    assertTrue(subject.isRenamed());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 7b1cf3e..5bf5a36 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.VmTestRunner;
 import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
@@ -26,24 +27,24 @@
   @IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
   public void testInvokeSuperTargets() throws Exception {
     ensureSameOutput(MainClass.class.getCanonicalName(),
-        asBytes(MainClass.class),
-        asBytes(Consumer.class),
-        asBytes(Super.class),
-        asBytes(SubLevel1.class),
-        asBytes(SubLevel2.class),
+        ToolHelper.getClassAsBytes(MainClass.class),
+        ToolHelper.getClassAsBytes(Consumer.class),
+        ToolHelper.getClassAsBytes(Super.class),
+        ToolHelper.getClassAsBytes(SubLevel1.class),
+        ToolHelper.getClassAsBytes(SubLevel2.class),
         InvokerClassDump.dump(),
-        asBytes(SubclassOfInvokerClass.class));
+        ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
   }
 
   @Test
   public void testInvokeSuperTargetsNonVerifying() throws Exception {
     ensureR8FailsWithCompilationError(MainClassFailing.class.getCanonicalName(),
-        asBytes(MainClassFailing.class),
-        asBytes(Consumer.class),
-        asBytes(Super.class),
-        asBytes(SubLevel1.class),
-        asBytes(SubLevel2.class),
+        ToolHelper.getClassAsBytes(MainClassFailing.class),
+        ToolHelper.getClassAsBytes(Consumer.class),
+        ToolHelper.getClassAsBytes(Super.class),
+        ToolHelper.getClassAsBytes(SubLevel1.class),
+        ToolHelper.getClassAsBytes(SubLevel2.class),
         InvokerClassFailingDump.dump(),
-        asBytes(SubclassOfInvokerClass.class));
+        ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 39cf369..5150223 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -7,7 +7,8 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.dex.ApplicationReader;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.DexApplication;
@@ -24,12 +25,14 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.NonNull;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.nonnull.NonNullAfterArrayAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterFieldAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterInvoke;
-import com.android.tools.r8.ir.optimize.NonNullMarker;
+import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
+import com.android.tools.r8.ir.optimize.NonNullTracker;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Timing;
@@ -38,7 +41,7 @@
 import java.util.function.BiConsumer;
 import org.junit.Test;
 
-public class NullabilityTest extends AsmTestBase {
+public class NullabilityTest extends TestBase {
   private static final InternalOptions TEST_OPTIONS = new InternalOptions();
 
   private void buildAndTest(
@@ -47,7 +50,7 @@
       boolean npeCaught,
       BiConsumer<AppInfo, TypeAnalysis> inspector)
       throws Exception {
-    AndroidApp app = buildAndroidApp(asBytes(mainClass));
+    AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
     DexApplication dexApplication =
         new ApplicationReader(app, TEST_OPTIONS, new Timing("NullabilityTest.appReader"))
             .read().toDirect();
@@ -55,8 +58,8 @@
     DexInspector dexInspector = new DexInspector(appInfo.app);
     DexEncodedMethod foo = dexInspector.clazz(mainClass.getName()).method(signature).getMethod();
     IRCode irCode = foo.buildIR(TEST_OPTIONS);
-    NonNullMarker nonNullMarker = new NonNullMarker();
-    nonNullMarker.addNonNull(irCode);
+    NonNullTracker nonNullTracker = new NonNullTracker();
+    nonNullTracker.addNonNull(irCode);
     TypeAnalysis analysis = new TypeAnalysis(appInfo, foo, irCode);
     inspector.accept(appInfo, analysis);
     verifyLastInvoke(irCode, analysis, npeCaught);
@@ -112,7 +115,7 @@
     buildAndTest(NonNullAfterInvoke.class, signature, false, (appInfo, typeAnalysis) -> {
       DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
       DexType mainClass = appInfo.dexItemFactory.createType(
-          "Lcom/android/tools/r8/ir/nonnull/NonNullAfterInvoke;");
+          DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           InvokeVirtual.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
           NonNull.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
@@ -128,7 +131,7 @@
     buildAndTest(NonNullAfterInvoke.class, signature, true, (appInfo, typeAnalysis) -> {
       DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
       DexType mainClass = appInfo.dexItemFactory.createType(
-          "Lcom/android/tools/r8/ir/nonnull/NonNullAfterInvoke;");
+          DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           InvokeVirtual.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
           NonNull.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, false),
@@ -144,7 +147,7 @@
     buildAndTest(NonNullAfterArrayAccess.class, signature, false, (appInfo, typeAnalysis) -> {
       DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
       DexType mainClass = appInfo.dexItemFactory.createType(
-          "Lcom/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess;");
+          DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           // An element inside a non-null array could be null.
           ArrayGet.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
@@ -170,7 +173,7 @@
     buildAndTest(NonNullAfterArrayAccess.class, signature, true, (appInfo, typeAnalysis) -> {
       DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
       DexType mainClass = appInfo.dexItemFactory.createType(
-          "Lcom/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess;");
+          DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           // An element inside a non-null array could be null.
           ArrayGet.class, new ClassTypeLatticeElement(appInfo.dexItemFactory.stringType, true),
@@ -192,13 +195,13 @@
   @Test
   public void nonNullAfterSafeFieldAccess() throws Exception {
     MethodSignature signature = new MethodSignature("foo", "int",
-        new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+        new String[]{FieldAccessTest.class.getCanonicalName()});
     buildAndTest(NonNullAfterFieldAccess.class, signature, false, (appInfo, typeAnalysis) -> {
       DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
       DexType mainClass = appInfo.dexItemFactory.createType(
-          "Lcom/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess;");
+          DescriptorUtils.javaTypeToDescriptor(NonNullAfterFieldAccess.class.getCanonicalName()));
       DexType testClass = appInfo.dexItemFactory.createType(
-          "Lcom/android/tools/r8/ir/nonnull/FieldAccessTest;");
+          DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           Argument.class, new ClassTypeLatticeElement(testClass, true),
           NonNull.class, new ClassTypeLatticeElement(testClass, false),
@@ -212,13 +215,13 @@
   @Test
   public void stillNullAfterExceptionCatch_iget() throws Exception {
     MethodSignature signature = new MethodSignature("bar", "int",
-        new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+        new String[]{FieldAccessTest.class.getCanonicalName()});
     buildAndTest(NonNullAfterFieldAccess.class, signature, true, (appInfo, typeAnalysis) -> {
       DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
       DexType mainClass = appInfo.dexItemFactory.createType(
-          "Lcom/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess;");
+          DescriptorUtils.javaTypeToDescriptor(NonNullAfterFieldAccess.class.getCanonicalName()));
       DexType testClass = appInfo.dexItemFactory.createType(
-          "Lcom/android/tools/r8/ir/nonnull/FieldAccessTest;");
+          DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
       Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
           Argument.class, new ClassTypeLatticeElement(testClass, true),
           NonNull.class, new ClassTypeLatticeElement(testClass, false),
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 117c1e1..118a75a 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.analysis.type;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import com.android.tools.r8.ToolHelper;
@@ -20,6 +21,9 @@
 import org.junit.Test;
 
 public class TypeLatticeTest {
+  private static final String IO_EXCEPTION = "Ljava/io/IOException;";
+  private static final String NOT_FOUND = "Ljava/io/FileNotFoundException;";
+  private static final String INTERRUPT = "Ljava/io/InterruptedIOException;";
 
   private static DexItemFactory factory;
   private static AppInfoWithSubtyping appInfo;
@@ -58,7 +62,15 @@
 
   private TypeLatticeElement join(TypeLatticeElement... elements) {
     assertTrue(elements.length > 1);
-    return Arrays.stream(elements).reduce(TypeLatticeElement.joiner(appInfo)).get();
+    return TypeLatticeElement.join(appInfo, Arrays.stream(elements));
+  }
+
+  private boolean strictlyLessThan(TypeLatticeElement l1, TypeLatticeElement l2) {
+    return TypeLatticeElement.strictlyLessThan(appInfo, l1, l2);
+  }
+
+  private boolean lessThanOrEqual(TypeLatticeElement l1, TypeLatticeElement l2) {
+    return TypeLatticeElement.lessThanOrEqual(appInfo, l1, l2);
   }
 
   @Test
@@ -97,10 +109,10 @@
   @Test
   public void joinToNonJavaLangObject() {
     assertEquals(
-        element(factory.createType("Ljava/io/IOException;")),
+        element(factory.createType(IO_EXCEPTION)),
         join(
-            element(factory.createType("Ljava/io/FileNotFoundException;")),
-            element(factory.createType("Ljava/io/InterruptedIOException;"))));
+            element(factory.createType(NOT_FOUND)),
+            element(factory.createType(INTERRUPT))));
   }
 
   @Test
@@ -192,4 +204,51 @@
             array(3, factory.stringType),
             array(4, factory.stringType)));
   }
+
+  @Test
+  public void testPartialOrders() {
+    assertTrue(lessThanOrEqual(
+        element(factory.objectType),
+        element(factory.objectType)));
+    assertFalse(strictlyLessThan(
+        element(factory.objectType),
+        element(factory.objectType)));
+
+    assertTrue(strictlyLessThan(
+        element(factory.createType(NOT_FOUND)),
+        element(factory.createType(IO_EXCEPTION))));
+    assertTrue(strictlyLessThan(
+        element(factory.createType(INTERRUPT)),
+        element(factory.createType(IO_EXCEPTION))));
+    assertFalse(lessThanOrEqual(
+        element(factory.createType(NOT_FOUND)),
+        element(factory.createType(INTERRUPT))));
+    assertFalse(lessThanOrEqual(
+        element(factory.createType(INTERRUPT)),
+        element(factory.createType(NOT_FOUND))));
+
+    assertTrue(strictlyLessThan(
+        array(1, factory.stringType),
+        array(1, factory.objectType)));
+    assertFalse(lessThanOrEqual(
+        array(1, factory.stringType),
+        array(2, factory.objectType)));
+    assertTrue(strictlyLessThan(
+        array(2, factory.stringType),
+        array(1, factory.objectType)));
+
+    assertFalse(lessThanOrEqual(
+        array(3, factory.stringType),
+        array(4, factory.stringType)));
+    assertFalse(lessThanOrEqual(
+        array(4, factory.stringType),
+        array(3, factory.stringType)));
+
+    assertTrue(strictlyLessThan(
+        array(2, factory.objectType),
+        array(1, factory.objectType)));
+    assertTrue(strictlyLessThan(
+        NullLatticeElement.getInstance(),
+        array(1, factory.classType)));
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
index f133d7e..e438efb 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/CheckCastRemovalTest.java
@@ -5,9 +5,18 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.code.AgetObject;
+import com.android.tools.r8.code.AputObject;
 import com.android.tools.r8.code.CheckCast;
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.ConstString;
+import com.android.tools.r8.code.IgetObject;
 import com.android.tools.r8.code.InvokeDirect;
+import com.android.tools.r8.code.InvokeVirtual;
+import com.android.tools.r8.code.NewArray;
 import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.code.ReturnVoid;
 import com.android.tools.r8.graph.DexCode;
@@ -28,6 +37,7 @@
   public void exactMatch() throws Exception {
     JasminBuilder builder = new JasminBuilder();
     ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+    classBuilder.addDefaultConstructor();
     MethodSignature main = classBuilder.addMainMethod(
         ".limit stack 3",
         ".limit locals 1",
@@ -39,7 +49,6 @@
 
     List<String> pgConfigs = ImmutableList.of(
         "-keep class " + CLASS_NAME + " { *; }",
-        "-dontoptimize",
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
@@ -50,15 +59,20 @@
         NewInstance.class,
         InvokeDirect.class,
         ReturnVoid.class));
+
+    checkRuntime(builder, app, CLASS_NAME);
   }
 
   @Test
   public void upCasts() throws Exception {
     JasminBuilder builder = new JasminBuilder();
     // A < B < C
-    builder.addClass("C");
-    builder.addClass("B", "C");
-    builder.addClass("A", "B");
+    ClassBuilder c = builder.addClass("C");
+    c.addDefaultConstructor();
+    ClassBuilder b = builder.addClass("B", "C");
+    b.addDefaultConstructor();
+    ClassBuilder a = builder.addClass("A", "B");
+    a.addDefaultConstructor();
     ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
     MethodSignature main = classBuilder.addMainMethod(
         ".limit stack 3",
@@ -75,7 +89,6 @@
         "-keep class A { *; }",
         "-keep class B { *; }",
         "-keep class C { *; }",
-        "-dontoptimize",
         "-dontshrink");
     AndroidApp app = compileWithR8(builder, pgConfigs, null);
 
@@ -86,15 +99,20 @@
         NewInstance.class,
         InvokeDirect.class,
         ReturnVoid.class));
+
+    checkRuntime(builder, app, CLASS_NAME);
   }
 
   @Test
   public void downCasts() throws Exception {
     JasminBuilder builder = new JasminBuilder();
     // C < B < A
-    builder.addClass("A");
-    builder.addClass("B", "A");
-    builder.addClass("C", "B");
+    ClassBuilder a = builder.addClass("A");
+    a.addDefaultConstructor();
+    ClassBuilder b = builder.addClass("B", "A");
+    b.addDefaultConstructor();
+    ClassBuilder c = builder.addClass("C", "B");
+    c.addDefaultConstructor();
     ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
     MethodSignature main = classBuilder.addMainMethod(
         ".limit stack 3",
@@ -126,5 +144,99 @@
         ReturnVoid.class));
     CheckCast cast = (CheckCast) code.instructions[2];
     assertEquals("C", cast.getType().toString());
+
+    checkRuntimeException(builder, app, CLASS_NAME, "ClassCastException");
+  }
+
+  @Test
+  public void bothUpAndDowncast() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+    MethodSignature main = classBuilder.addMainMethod(
+        ".limit stack 4",
+        ".limit locals 1",
+        "iconst_1",
+        "anewarray java/lang/String", // args parameter
+        "dup",
+        "iconst_0",
+        "ldc \"a string\"",
+        "aastore",
+        "checkcast [Ljava/lang/Object;",  // This upcast can be removed.
+        "iconst_0",
+        "aaload",
+        "checkcast java/lang/String",  // Then, this downcast can be removed, too.
+        "invokevirtual java/lang/String/length()I",
+        "return");
+    // That is, both checkcasts should be removed together or kept together.
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-keep class " + CLASS_NAME + " { *; }",
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexEncodedMethod method = getMethod(app, CLASS_NAME, main);
+    assertNotNull(method);
+
+    DexCode code = method.getCode().asDexCode();
+    checkInstructions(code, ImmutableList.of(
+        Const4.class,
+        NewArray.class,
+        ConstString.class,
+        Const4.class,
+        AputObject.class,
+        AgetObject.class,
+        InvokeVirtual.class,
+        ReturnVoid.class));
+
+    checkRuntime(builder, app, CLASS_NAME);
+  }
+
+  @Test
+  public void nullCast() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+    classBuilder.addField("public", "fld", "Ljava/lang/String;", null);
+    MethodSignature main = classBuilder.addMainMethod(
+        ".limit stack 3",
+        ".limit locals 1",
+        "aconst_null",
+        "checkcast Example", // Should be kept
+        "getfield Example.fld Ljava/lang/String;",
+        "return");
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-keep class " + CLASS_NAME + " { *; }",
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexEncodedMethod method = getMethod(app, CLASS_NAME, main);
+    assertNotNull(method);
+
+    checkInstructions(method.getCode().asDexCode(), ImmutableList.of(
+        Const4.class,
+        CheckCast.class,
+        IgetObject.class,
+        ReturnVoid.class));
+
+    checkRuntimeException(builder, app, CLASS_NAME, "NullPointerException");
+  }
+
+  private void checkRuntime(JasminBuilder builder, AndroidApp app, String className)
+      throws Exception {
+    String normalOutput = runOnJava(builder, className);
+    String dexOptimizedOutput = runOnArt(app, className);
+    assertEquals(normalOutput, dexOptimizedOutput);
+  }
+
+  private void checkRuntimeException(
+      JasminBuilder builder, AndroidApp app, String className, String exceptionName)
+      throws Exception {
+    ProcessResult javaOutput = runOnJavaRaw(builder, className);
+    assertEquals(1, javaOutput.exitCode);
+    assertTrue(javaOutput.stderr.contains(exceptionName));
+
+    ProcessResult artOutput = runOnArtRaw(app, className);
+    assertEquals(1, artOutput.exitCode);
+    assertTrue(artOutput.stderr.contains(exceptionName));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
deleted file mode 100644
index 036c6da..0000000
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.optimize;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.dex.ApplicationReader;
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InstancePut;
-import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
-import com.android.tools.r8.ir.code.NonNull;
-import com.android.tools.r8.ir.nonnull.NonNullAfterArrayAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterFieldAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterInvoke;
-import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.utils.AndroidApp;
-import com.android.tools.r8.utils.DexInspector;
-import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.Timing;
-import java.util.function.Consumer;
-import org.junit.Test;
-
-public class NonNullMarkerTest extends AsmTestBase {
-  private static final InternalOptions TEST_OPTIONS = new InternalOptions();
-
-  private void buildAndTest(
-      Class<?> testClass,
-      MethodSignature signature,
-      int expectedNumberOfNonNull,
-      Consumer<IRCode> testAugmentedIRCode)
-      throws Exception {
-    AndroidApp app = buildAndroidApp(asBytes(testClass));
-    DexApplication dexApplication =
-        new ApplicationReader(app, TEST_OPTIONS, new Timing("NonNullMarkerTest.appReader"))
-            .read().toDirect();
-    AppInfo appInfo = new AppInfo(dexApplication);
-    DexInspector dexInspector = new DexInspector(appInfo.app);
-    DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
-    IRCode irCode = foo.buildIR(TEST_OPTIONS);
-    checkCountOfNonNull(irCode, 0);
-
-    NonNullMarker nonNullMarker = new NonNullMarker();
-
-    nonNullMarker.addNonNull(irCode);
-    assertTrue(irCode.isConsistentSSA());
-    checkCountOfNonNull(irCode, expectedNumberOfNonNull);
-
-    if (testAugmentedIRCode != null) {
-      testAugmentedIRCode.accept(irCode);
-    }
-
-    nonNullMarker.cleanupNonNull(irCode);
-    assertTrue(irCode.isConsistentSSA());
-    checkCountOfNonNull(irCode, 0);
-  }
-
-  private static void checkCountOfNonNull(IRCode code, int expectedOccurrences) {
-    int count = 0;
-    Instruction prev = null, curr = null;
-    InstructionIterator it = code.instructionIterator();
-    while (it.hasNext()) {
-      prev = curr != null && !curr.isGoto() ? curr : prev;
-      curr = it.next();
-      if (curr.isNonNull()) {
-        // Make sure non-null is added to the right place.
-        assertTrue(prev == null || NonNullMarker.throwsOnNullInput(prev));
-        count++;
-      }
-    }
-    assertEquals(count, expectedOccurrences);
-  }
-
-  @Test
-  public void nonNullAfterSafeInvokes() throws Exception {
-    MethodSignature signature =
-        new MethodSignature("foo", "int", new String[]{"java.lang.String"});
-    buildAndTest(NonNullAfterInvoke.class, signature, 1, null);
-  }
-
-  @Test
-  public void nonNullAfterSafeArrayAccess() throws Exception {
-    MethodSignature signature =
-        new MethodSignature("foo", "int", new String[]{"java.lang.String[]"});
-    buildAndTest(NonNullAfterArrayAccess.class, signature, 1, null);
-  }
-
-  @Test
-  public void nonNullAfterSafeFieldAccess() throws Exception {
-    MethodSignature signature = new MethodSignature("foo", "int",
-        new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
-    buildAndTest(NonNullAfterFieldAccess.class, signature, 1, null);
-  }
-
-  @Test
-  public void avoidRedundantNonNull() throws Exception {
-    MethodSignature signature = new MethodSignature("foo2", "int",
-        new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
-    buildAndTest(NonNullAfterFieldAccess.class, signature, 1, ircode -> {
-      // There are two InstancePut instructions of interest.
-      int count = 0;
-      InstructionIterator it = ircode.instructionIterator();
-      while (it.hasNext()) {
-        Instruction instruction = it.nextUntil(Instruction::isInstancePut);
-        if (instruction == null) {
-          break;
-        }
-        InstancePut iput = instruction.asInstancePut();
-        if (count == 0) {
-          // First one in the very first line: its value should not be replaced by NonNullMarker
-          // because this instruction will happen _before_ non-null.
-          assertFalse(iput.value().definition.isNonNull());
-        } else if (count == 1) {
-          // Second one after a safe invocation, which should use the value added by NonNullMarker.
-          assertTrue(iput.object().definition.isNonNull());
-        }
-        count++;
-      }
-      assertEquals(2, count);
-    });
-  }
-
-}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
new file mode 100644
index 0000000..646cfaf
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -0,0 +1,190 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.InstancePut;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
+import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterNullCheck;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.Timing;
+import java.util.function.Consumer;
+import org.junit.Test;
+
+public class NonNullTrackerTest extends TestBase {
+  private static final InternalOptions TEST_OPTIONS = new InternalOptions();
+
+  private void buildAndTest(
+      Class<?> testClass,
+      MethodSignature signature,
+      int expectedNumberOfNonNull,
+      Consumer<IRCode> testAugmentedIRCode)
+      throws Exception {
+    AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
+    DexApplication dexApplication =
+        new ApplicationReader(app, TEST_OPTIONS, new Timing("NonNullMarkerTest.appReader"))
+            .read().toDirect();
+    AppInfo appInfo = new AppInfo(dexApplication);
+    DexInspector dexInspector = new DexInspector(appInfo.app);
+    DexEncodedMethod foo = dexInspector.clazz(testClass.getName()).method(signature).getMethod();
+    IRCode irCode = foo.buildIR(TEST_OPTIONS);
+    checkCountOfNonNull(irCode, 0);
+
+    NonNullTracker nonNullTracker = new NonNullTracker();
+
+    nonNullTracker.addNonNull(irCode);
+    assertTrue(irCode.isConsistentSSA());
+    checkCountOfNonNull(irCode, expectedNumberOfNonNull);
+
+    if (testAugmentedIRCode != null) {
+      testAugmentedIRCode.accept(irCode);
+    }
+
+    nonNullTracker.cleanupNonNull(irCode);
+    assertTrue(irCode.isConsistentSSA());
+    checkCountOfNonNull(irCode, 0);
+  }
+
+  private static void checkCountOfNonNull(IRCode code, int expectedOccurrences) {
+    int count = 0;
+    Instruction prev = null, curr = null;
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      prev = curr != null && !curr.isGoto() ? curr : prev;
+      curr = it.next();
+      if (curr.isNonNull()) {
+        // Make sure non-null is added to the right place.
+        assertTrue(prev == null
+            || NonNullTracker.throwsOnNullInput(prev)
+            || (prev.isIf() && prev.asIf().isZeroTest())
+            || !curr.getBlock().getPredecessors().contains(prev.getBlock()));
+        count++;
+      }
+    }
+    assertEquals(expectedOccurrences, count);
+  }
+
+  private void checkInvokeGetsNonNullReceiver(IRCode irCode) {
+    checkInvokeReceiver(irCode, true);
+  }
+
+  private void checkInvokeGetsNullReceiver(IRCode irCode) {
+    checkInvokeReceiver(irCode, false);
+  }
+
+  private void checkInvokeReceiver(IRCode irCode, boolean isNotNull) {
+    InstructionIterator it = irCode.instructionIterator();
+    boolean metInvokeWithReceiver = false;
+    while (it.hasNext()) {
+      Instruction instruction = it.nextUntil(Instruction::isInvokeMethodWithReceiver);
+      if (instruction == null) {
+        break;
+      }
+      InvokeMethodWithReceiver invoke = instruction.asInvokeMethodWithReceiver();
+      if (invoke.isInvokeDirect()
+          || !invoke.getInvokedMethod().name.toString().contains("hashCode")) {
+        continue;
+      }
+      metInvokeWithReceiver = true;
+      if (isNotNull) {
+        assertTrue(invoke.getReceiver().isNeverNull()
+            || invoke.getReceiver().definition.isArgument());
+      } else {
+        assertFalse(invoke.getReceiver().isNeverNull());
+      }
+    }
+    assertTrue(metInvokeWithReceiver);
+  }
+
+  @Test
+  public void nonNullAfterSafeInvokes() throws Exception {
+    MethodSignature foo =
+        new MethodSignature("foo", "int", new String[]{"java.lang.String"});
+    buildAndTest(NonNullAfterInvoke.class, foo, 1, this::checkInvokeGetsNonNullReceiver);
+    MethodSignature bar =
+        new MethodSignature("bar", "int", new String[]{"java.lang.String"});
+    buildAndTest(NonNullAfterInvoke.class, bar, 2, this::checkInvokeGetsNullReceiver);
+  }
+
+  @Test
+  public void nonNullAfterSafeArrayAccess() throws Exception {
+    MethodSignature foo =
+        new MethodSignature("foo", "int", new String[]{"java.lang.String[]"});
+    buildAndTest(NonNullAfterArrayAccess.class, foo, 1, null);
+  }
+
+  @Test
+  public void nonNullAfterSafeArrayLength() throws Exception {
+    MethodSignature signature =
+        new MethodSignature("arrayLength", "int", new String[]{"java.lang.String[]"});
+    buildAndTest(NonNullAfterArrayAccess.class, signature, 1, null);
+  }
+
+  @Test
+  public void nonNullAfterSafeFieldAccess() throws Exception {
+    MethodSignature foo = new MethodSignature("foo", "int",
+        new String[]{FieldAccessTest.class.getCanonicalName()});
+    buildAndTest(NonNullAfterFieldAccess.class, foo, 1, null);
+  }
+
+  @Test
+  public void avoidRedundantNonNull() throws Exception {
+    MethodSignature signature = new MethodSignature("foo2", "int",
+        new String[]{FieldAccessTest.class.getCanonicalName()});
+    buildAndTest(NonNullAfterFieldAccess.class, signature, 1, ircode -> {
+      // There are two InstancePut instructions of interest.
+      int count = 0;
+      InstructionIterator it = ircode.instructionIterator();
+      while (it.hasNext()) {
+        Instruction instruction = it.nextUntil(Instruction::isInstancePut);
+        if (instruction == null) {
+          break;
+        }
+        InstancePut iput = instruction.asInstancePut();
+        if (count == 0) {
+          // First one in the very first line: its value should not be replaced by NonNullMarker
+          // because this instruction will happen _before_ non-null.
+          assertFalse(iput.value().definition.isNonNull());
+        } else if (count == 1) {
+          // Second one after a safe invocation, which should use the value added by NonNullMarker.
+          assertTrue(iput.object().definition.isNonNull());
+        }
+        count++;
+      }
+      assertEquals(2, count);
+    });
+  }
+
+  @Test
+  public void nonNullAfterNullCheck() throws Exception {
+    MethodSignature foo =
+        new MethodSignature("foo", "int", new String[]{"java.lang.String"});
+    buildAndTest(NonNullAfterNullCheck.class, foo, 1, this::checkInvokeGetsNonNullReceiver);
+    MethodSignature bar =
+        new MethodSignature("bar", "int", new String[]{"java.lang.String"});
+    buildAndTest(NonNullAfterNullCheck.class, bar, 1, this::checkInvokeGetsNonNullReceiver);
+    MethodSignature baz =
+        new MethodSignature("baz", "int", new String[]{"java.lang.String"});
+    buildAndTest(NonNullAfterNullCheck.class, baz, 1, this::checkInvokeGetsNullReceiver);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index f282b42..9c03ee7 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -100,7 +100,7 @@
             .build();
     // TODO(62048823): Enable minification.
     ToolHelper.runR8(command, o -> {
-      o.skipMinification = true;
+      o.enableMinification = false;
     });
     String artOutput = ToolHelper.runArtNoVerificationErrors(out + "/classes.dex",
         "inlining.Inlining");
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
index c72cdcb..92e43d0 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
@@ -5,14 +5,16 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.code.Format21t;
 import com.android.tools.r8.code.Format22t;
 import com.android.tools.r8.code.Instruction;
 import com.android.tools.r8.graph.DexEncodedMethod;
-import com.android.tools.r8.ir.nonnull.NonNullAfterArrayAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterFieldAccess;
-import com.android.tools.r8.ir.nonnull.NonNullAfterInvoke;
+import com.android.tools.r8.ir.optimize.nonnull.FieldAccessTest;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterArrayAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterFieldAccess;
+import com.android.tools.r8.ir.optimize.nonnull.NonNullAfterInvoke;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
@@ -21,15 +23,15 @@
 import java.util.List;
 import org.junit.Test;
 
-public class SimplifyIfNotNullTest extends AsmTestBase {
+public class SimplifyIfNotNullTest extends TestBase {
   private static boolean isIf(Instruction instruction) {
     return instruction instanceof Format21t || instruction instanceof Format22t;
   }
 
   private void buildAndTest(Class<?> testClass, List<MethodSignature> signatures) throws Exception {
-    AndroidApp app = buildAndroidApp(asBytes(testClass));
+    AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
     AndroidApp r8Result = compileWithR8(
-        app, keepMainProguardConfiguration(testClass), o -> o.inlineAccessors = false);
+        app, keepMainProguardConfiguration(testClass), o -> o.enableInlining = false);
     DexInspector dexInspector = new DexInspector(r8Result);
     for (MethodSignature signature : signatures) {
       DexEncodedMethod method =
@@ -61,11 +63,11 @@
   @Test
   public void nonNullAfterSafeFieldAccess() throws Exception {
     MethodSignature foo = new MethodSignature("foo", "int",
-        new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+        new String[]{FieldAccessTest.class.getCanonicalName()});
     MethodSignature bar = new MethodSignature("bar", "int",
-        new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+        new String[]{FieldAccessTest.class.getCanonicalName()});
     MethodSignature foo2 = new MethodSignature("foo2", "int",
-        new String[]{"com.android.tools.r8.ir.nonnull.FieldAccessTest"});
+        new String[]{FieldAccessTest.class.getCanonicalName()});
     buildAndTest(NonNullAfterFieldAccess.class, ImmutableList.of(foo, bar, foo2));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
index 8697917..699ebb4 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/TrivialGotoEliminationTest.java
@@ -3,9 +3,16 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.optimize;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.ir.code.Goto;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.If;
+import com.android.tools.r8.ir.code.If.Type;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
@@ -57,9 +64,76 @@
     // throw.
     IRCode code = new IRCode(null, blocks, new ValueNumberGenerator(), false);
     CodeRewriter.collapsTrivialGotos(null, code);
-    assert code.blocks.get(0).isTrivialGoto();
-    assert blocks.contains(block0);
-    assert blocks.contains(block1);
-    assert blocks.contains(block2);
+    assertTrue(code.blocks.get(0).isTrivialGoto());
+    assertTrue(blocks.contains(block0));
+    assertTrue(blocks.contains(block1));
+    assertTrue(blocks.contains(block2));
+  }
+
+  @Test
+  public void trivialGotoLoopAsFallthrough() {
+    // Setup block structure:
+    // block0:
+    //   v0 <- argument
+    //   if ne v0 block2
+    //
+    // block1:
+    //   goto block3
+    //
+    // block2:
+    //   return
+    //
+    // block3:
+    //   goto block3
+    BasicBlock block2 = new BasicBlock();
+    block2.setNumber(2);
+    Instruction ret = new Return();
+    ret.setPosition(Position.none());
+    block2.add(ret);
+    block2.setFilledForTesting();
+
+    BasicBlock block3 = new BasicBlock();
+    block3.setNumber(3);
+    Instruction instruction = new Goto();
+    instruction.setPosition(Position.none());
+    block3.add(instruction);
+    block3.setFilledForTesting();
+    block3.getSuccessors().add(block3);
+
+    BasicBlock block1 = BasicBlock.createGotoBlock(1);
+    block1.getSuccessors().add(block3);
+    block1.setFilledForTesting();
+
+    BasicBlock block0 = new BasicBlock();
+    block0.setNumber(0);
+    Value value = new Value(0, ValueType.OBJECT, null);
+    instruction = new Argument(value);
+    instruction.setPosition(Position.none());
+    block0.add(instruction);
+    instruction = new If(Type.EQ, value);
+    instruction.setPosition(Position.none());
+    block0.add(instruction);
+    block0.getSuccessors().add(block2);
+    block0.getSuccessors().add(block1);
+    block0.setFilledForTesting();
+
+    block1.getPredecessors().add(block0);
+    block2.getPredecessors().add(block0);
+    block3.getPredecessors().add(block1);
+    block3.getPredecessors().add(block3);
+
+    LinkedList<BasicBlock> blocks = new LinkedList<>();
+    blocks.add(block0);
+    blocks.add(block1);
+    blocks.add(block2);
+    blocks.add(block3);
+    // Check that the goto in block0 remains. There was a bug in the trivial goto elimination
+    // that ended up removing that goto changing the code to start with the unreachable
+    // throw.
+    IRCode code = new IRCode(null, blocks, new ValueNumberGenerator(), false);
+    CodeRewriter.collapsTrivialGotos(null, code);
+    assertTrue(block0.getInstructions().get(1).isIf());
+    assertEquals(block1, block0.getInstructions().get(1).asIf().fallthroughBlock());
+    assertTrue(blocks.containsAll(ImmutableList.of(block0, block1, block2, block3)));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/FieldAccessTest.java
similarity index 83%
rename from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
rename to src/test/java/com/android/tools/r8/ir/optimize/nonnull/FieldAccessTest.java
index 09e94f7..dec12d5 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/FieldAccessTest.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
+package com.android.tools.r8.ir.optimize.nonnull;
 
 public class FieldAccessTest {
   String fld;
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterArrayAccess.java
similarity index 76%
rename from src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess.java
rename to src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterArrayAccess.java
index 902d577..92cedea 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterArrayAccess.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterArrayAccess.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
+package com.android.tools.r8.ir.optimize.nonnull;
 
 public class NonNullAfterArrayAccess {
 
@@ -26,10 +26,19 @@
     return arg.hashCode();
   }
 
+  public static int arrayLength(String[] arg) {
+    int length = arg.length;
+    if (arg == null) {
+      throw new AssertionError("arg is not null.");
+    }
+    return arg.hashCode() + length;
+  }
+
   public static void main(String[] args) {
     String[] nonNullArgs = new String[1];
     nonNullArgs[0] = "non-null";
     foo(nonNullArgs);
     bar(nonNullArgs);
+    arrayLength(nonNullArgs);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterFieldAccess.java
similarity index 96%
rename from src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess.java
rename to src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterFieldAccess.java
index 73ee6ea..3b7e950 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterFieldAccess.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterFieldAccess.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
+package com.android.tools.r8.ir.optimize.nonnull;
 
 public class NonNullAfterFieldAccess {
 
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterInvoke.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterInvoke.java
similarity index 94%
rename from src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterInvoke.java
rename to src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterInvoke.java
index a2c472a..8aab7fe 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/NonNullAfterInvoke.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterInvoke.java
@@ -1,7 +1,7 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
+package com.android.tools.r8.ir.optimize.nonnull;
 
 public class NonNullAfterInvoke {
 
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterNullCheck.java b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterNullCheck.java
new file mode 100644
index 0000000..d943339
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/nonnull/NonNullAfterNullCheck.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.optimize.nonnull;
+
+public class NonNullAfterNullCheck {
+
+  public static int foo(String arg) {
+    if (arg != null) {
+      return arg.contains("null") ? arg.hashCode() : 0;
+    }
+    return -1;
+  }
+
+  public static int bar(String arg) {
+    if (arg == null) {
+      return -1;
+    } else {
+      return arg.contains("null") ? arg.hashCode() : 0;
+    }
+  }
+
+  public static int baz(String arg) {
+    if (arg != null) {
+      if (arg == null) {
+        throw new AssertionError("Unreachable.");
+      }
+    }
+    // not dominated by null check.
+    return arg.hashCode();
+  }
+
+  public static void main(String[] args) {
+    foo("non-null");
+    bar("non-null");
+    baz("non-null");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress72758525.java b/src/test/java/com/android/tools/r8/jasmin/Regress72758525.java
new file mode 100644
index 0000000..97331c8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress72758525.java
@@ -0,0 +1,61 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.jasmin;
+
+import com.android.tools.r8.jasmin.JasminBuilder.ClassFileVersion;
+import org.junit.Test;
+
+public class Regress72758525 extends JasminTestBase {
+
+  private JasminBuilder buildClass() {
+    JasminBuilder builder = new JasminBuilder(ClassFileVersion.JDK_1_4);
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+    clazz.addDefaultConstructor();
+
+    clazz.addMainMethod(
+        ".limit stack 25",
+        ".limit locals 1",
+        "aload 0",
+        "dup",
+        "lconst_0",
+        "dconst_1",
+        "fconst_0",
+        "lconst_1",
+        "iconst_5",
+        "fconst_1",
+        "dconst_1",
+        "new Test",
+        "dup",
+        "invokespecial Test/<init>()V",
+        "lconst_0",
+        "new java/lang/Object",
+        "dup",
+        "invokespecial java/lang/Object/<init>()V",
+        "iconst_m1",
+        "dup2",
+        "dup2_x2",
+        "L0:",
+        "ineg",
+        "new java/lang/Object",
+        "dup",
+        "invokespecial java/lang/Object/<init>()V",
+        "dup2_x2",
+        "pop2",
+        "pop",
+        "aload 0",
+        "ifnull L0",
+        "i2f",
+        "invokestatic java/lang/Float/isNaN(F)Z",
+        "return");
+    return builder;
+  }
+
+  @Test
+  public void test() throws Exception {
+    JasminBuilder builder = buildClass();
+    runOnArtD8(builder, "Test");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
index 1349830..af8382e 100644
--- a/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
+++ b/src/test/java/com/android/tools/r8/jdwp/RunJdwpTests.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
 import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.io.File;
@@ -270,7 +271,7 @@
   @BeforeClass
   public static void compileLibraries() throws Exception {
     // Selects appropriate jar according to min api level for the selected runtime.
-    int minApi = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+    AndroidApiLevel minApi = ToolHelper.getMinApiLevelForDexVm();
     Path jdwpTestsJar = ToolHelper.getJdwpTestsCfJarPath(minApi);
     Path classPath = ToolHelper.getClassPathForTests();
     Path testPath = classPath.resolve(Paths.get("com","android", "tools", "r8", "jdwp"));
@@ -285,7 +286,7 @@
             .addProgramFiles(jdwpTestsJar)
             .addProgramFiles(extraTestResources)
             .setOutput(d8Out.toPath(), OutputMode.DexIndexed)
-            .setMinApiLevel(minApi)
+            .setMinApiLevel(minApi.getLevel())
             .setMode(CompilationMode.DEBUG)
             .build());
   }
diff --git a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
index d79d099..9f520a2 100644
--- a/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
+++ b/src/test/java/com/android/tools/r8/kotlin/AbstractR8KotlinTestBase.java
@@ -13,6 +13,7 @@
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
@@ -21,25 +22,35 @@
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableList.Builder;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Arrays;
-import java.util.List;
+import java.util.Collection;
 import java.util.Map;
 import org.junit.Assume;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
 
-// TODO(shertz) also run with backend 1.8
+@RunWith(Parameterized.class)
 public abstract class AbstractR8KotlinTestBase extends TestBase {
 
-  public static final String KOTLIN_R8_TEST_RESOURCES_BUILD_DIR =
-      ToolHelper.TESTS_BUILD_DIR + "/kotlinR8TestResources";
-
-  protected final boolean allowAccessModification;
-
-  protected AbstractR8KotlinTestBase(boolean allowAccessModification) {
-    this.allowAccessModification = allowAccessModification;
+  @Parameters(name = "{0}_{1}")
+  public static Collection<Object[]> data() {
+    ImmutableList.Builder<Object[]> builder = new Builder<>();
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      builder.add(new Object[]{Boolean.TRUE, targetVersion});
+      builder.add(new Object[]{Boolean.FALSE, targetVersion});
+    }
+    return builder.build();
   }
 
+  @Parameter(0) public boolean allowAccessModification;
+  @Parameter(1) public KotlinTargetVersion targetVersion;
+
   protected static void checkMethodIsInvokedAtLeastOnce(DexCode dexCode,
       MethodSignature... methodSignatures) {
     for (MethodSignature methodSignature : methodSignatures) {
@@ -90,17 +101,16 @@
     return classSubject;
   }
 
-  private static MethodSubject checkMethod(ClassSubject classSubject, String methodName,
-      String methodReturnType, List<String> methodParameterTypes, boolean isPresent) {
-    return checkMethod(classSubject,
-        new MethodSignature(methodName, methodReturnType, methodParameterTypes), isPresent);
-  }
-
   protected static MethodSubject checkMethodIsPresent(ClassSubject classSubject,
       MethodSignature methodSignature) {
     return checkMethod(classSubject, methodSignature, true);
   }
 
+  protected static MethodSubject checkMethodIsAbsent(ClassSubject classSubject,
+      MethodSignature methodSignature) {
+    return checkMethod(classSubject, methodSignature, false);
+  }
+
   protected static MethodSubject checkMethod(ClassSubject classSubject,
       MethodSignature methodSignature, boolean isPresent) {
     MethodSubject methodSubject = classSubject.method(methodSignature);
@@ -121,21 +131,6 @@
     return code.asDexCode();
   }
 
-  private static DexCode extractCodeFor(DexInspector dexInspector, String className,
-      String methodName,
-      String methodReturnType, List<String> methodParameterTypes) {
-    ClassSubject classSubject = checkClassExists(dexInspector, className);
-    MethodSubject methodSubject = checkMethodIsPresent(classSubject, methodName, methodReturnType,
-        methodParameterTypes);
-    return getDexCode(methodSubject);
-  }
-
-  protected static MethodSubject checkMethodIsPresent(ClassSubject classSubject, String methodName,
-      String methodReturnType,
-      List<String> methodParameterTypes) {
-    return checkMethod(classSubject, methodName, methodReturnType, methodParameterTypes, true);
-  }
-
   private String buildProguardRules(String mainClass) {
     ProguardRulesBuilder proguardRules = new ProguardRulesBuilder();
     proguardRules.appendWithLineSeparator(keepMainProguardConfiguration(mainClass));
@@ -160,8 +155,7 @@
       AndroidAppInspector inspector) throws Exception {
     Assume.assumeTrue(ToolHelper.artSupported());
 
-    Path jarFile =
-        Paths.get(KOTLIN_R8_TEST_RESOURCES_BUILD_DIR, folder + FileUtils.JAR_EXTENSION);
+    Path jarFile = getJarFile(folder);
 
     String proguardRules = buildProguardRules(mainClass);
     if (extraProguardRules != null) {
@@ -193,6 +187,11 @@
     inspector.inspectApp(app);
   }
 
+  private Path getJarFile(String folder) {
+    return Paths.get(ToolHelper.TESTS_BUILD_DIR, "kotlinR8TestResources",
+        targetVersion.getFolderName(), folder + FileUtils.JAR_EXTENSION);
+  }
+
   @FunctionalInterface
   interface AndroidAppInspector {
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java b/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
index 602c992..87ffcef 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinClass.java
@@ -63,7 +63,15 @@
 
   public MemberNaming.MethodSignature getGetterForProperty(String name) {
     String type = getProperty(name).type;
-    String getterName = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
+    String getterName;
+    if (name.length() > 2 && name.startsWith("is")
+        && (name.charAt(2) == '_' || Character.isUpperCase(name.charAt(2)))) {
+      // Getter for property "isAbc" is "isAbc".
+      getterName = name;
+    } else {
+      // Getter for property "abc" is "getAbc".
+      getterName = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
+    }
     return new MemberNaming.MethodSignature(getterName, type, Collections.emptyList());
   }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
index 4bd61e1..379579f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinDataClassTest.java
@@ -9,17 +9,9 @@
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.Map;
 import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
-@RunWith(Parameterized.class)
 public class R8KotlinDataClassTest extends AbstractR8KotlinTestBase {
 
   private static final KotlinDataClass TEST_DATA_CLASS = new KotlinDataClass("dataclass.Person")
@@ -39,15 +31,6 @@
   private static final MethodSignature COPY_DEFAULT_METHOD =
       TEST_DATA_CLASS.getCopyDefaultSignature();
 
-  public R8KotlinDataClassTest(boolean allowAccessModification) {
-    super(allowAccessModification);
-  }
-
-  @Parameters(name = "{0}")
-  public static Collection<Object> data() {
-    return ImmutableList.of(Boolean.TRUE, Boolean.FALSE);
-  }
-
   @Test
   public void test_dataclass_gettersOnly() throws Exception {
     final String mainClassName = "dataclass.MainGettersOnlyKt";
@@ -60,17 +43,16 @@
 
       // Getters should be removed after inlining, which is possible only if access is relaxed.
       final boolean areGetterPresent = !allowAccessModification;
+      checkMethod(dataClass, NAME_GETTER_METHOD, areGetterPresent);
+      checkMethod(dataClass, AGE_GETTER_METHOD, areGetterPresent);
 
-      Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
-          .put(NAME_GETTER_METHOD, areGetterPresent)
-          .put(AGE_GETTER_METHOD, areGetterPresent)
-          // ComponentN and copy methods are not used.
-          .put(COMPONENT1_METHOD, false)
-          .put(COMPONENT2_METHOD, false)
-          .put(COPY_METHOD, false)
-          .put(COPY_DEFAULT_METHOD, false)
-          .build();
-      checkMethodsPresence(dataClass, presenceMap);
+      // No use of componentN functions.
+      checkMethodIsAbsent(dataClass, COMPONENT1_METHOD);
+      checkMethodIsAbsent(dataClass, COMPONENT2_METHOD);
+
+      // No use of copy functions.
+      checkMethodIsAbsent(dataClass, COPY_METHOD);
+      checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
 
       ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
       MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
@@ -97,17 +79,16 @@
       // ComponentN functions should be removed after inlining, which is possible only if access
       // is relaxed.
       final boolean areComponentMethodsPresent = !allowAccessModification;
+      checkMethod(dataClass, COMPONENT1_METHOD, areComponentMethodsPresent);
+      checkMethod(dataClass, COMPONENT2_METHOD, areComponentMethodsPresent);
 
-      Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
-          .put(NAME_GETTER_METHOD, false)
-          .put(AGE_GETTER_METHOD, false)
-          // ComponentN and copy methods are not used.
-          .put(COMPONENT1_METHOD, areComponentMethodsPresent)
-          .put(COMPONENT2_METHOD, areComponentMethodsPresent)
-          .put(COPY_METHOD, false)
-          .put(COPY_DEFAULT_METHOD, false)
-          .build();
-      checkMethodsPresence(dataClass, presenceMap);
+      // No use of getter.
+      checkMethodIsAbsent(dataClass, NAME_GETTER_METHOD);
+      checkMethodIsAbsent(dataClass, AGE_GETTER_METHOD);
+
+      // No use of copy functions.
+      checkMethodIsAbsent(dataClass, COPY_METHOD);
+      checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
 
       ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
       MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
@@ -131,17 +112,18 @@
       ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
 
       boolean component2IsPresent = !allowAccessModification;
+      checkMethod(dataClass, COMPONENT2_METHOD, component2IsPresent);
 
-      Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
-          .put(NAME_GETTER_METHOD, false)
-          .put(AGE_GETTER_METHOD, false)
-          // ComponentN and copy methods are not used.
-          .put(COMPONENT1_METHOD, false)
-          .put(COMPONENT2_METHOD, component2IsPresent)
-          .put(COPY_METHOD, false)
-          .put(COPY_DEFAULT_METHOD, false)
-          .build();
-      checkMethodsPresence(dataClass, presenceMap);
+      // Function component1 is not used.
+      checkMethodIsAbsent(dataClass, COMPONENT1_METHOD);
+
+      // No use of getter.
+      checkMethodIsAbsent(dataClass, NAME_GETTER_METHOD);
+      checkMethodIsAbsent(dataClass, AGE_GETTER_METHOD);
+
+      // No use of copy functions.
+      checkMethodIsAbsent(dataClass, COPY_METHOD);
+      checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
 
       ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
       MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
@@ -155,26 +137,23 @@
   }
 
   @Test
-  public void test_dataclass_copy() throws Exception {
-    final String mainClassName = "dataclass.MainCopyKt";
-    runTest("dataclass", mainClassName, (app) -> {
+  public void test_dataclass_copyIsRemovedIfNotUsed() throws Exception {
+    final String mainClassName = "dataclass.MainComponentOnlyKt";
+    final MethodSignature testMethodSignature =
+        new MethodSignature("testDataClassCopy", "void", Collections.emptyList());
+    final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
+    runTest("dataclass", mainClassName, extraRules, (app) -> {
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
 
-      // Copy method is small enough that it is always inlined.
-      final boolean copyMethodIsPresent = false;
-
-      Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
-          .put(COPY_METHOD, copyMethodIsPresent)
-          .put(COPY_DEFAULT_METHOD, false)
-          .build();
-      checkMethodsPresence(dataClass, presenceMap);
+      checkMethodIsAbsent(dataClass, COPY_METHOD);
+      checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
     });
   }
 
   @Test
-  public void test_dataclass_copyDefault() throws Exception {
-    final String mainClassName = "dataclass.MainCopyWithDefaultKt";
+  public void test_dataclass_copyDefaultIsRemovedIfNotUsed() throws Exception {
+    final String mainClassName = "dataclass.MainCopyKt";
     final MethodSignature testMethodSignature =
         new MethodSignature("testDataClassCopyWithDefault", "void", Collections.emptyList());
     final String extraRules = keepClassMethod(mainClassName, testMethodSignature);
@@ -182,30 +161,7 @@
       DexInspector dexInspector = new DexInspector(app);
       ClassSubject dataClass = checkClassExists(dexInspector, TEST_DATA_CLASS.getClassName());
 
-      // Copy$default method is inlined if access is relaxed.
-      final boolean copyDefaultMethodIsPresent = !allowAccessModification;
-
-      // copy$default is a wrapper around copy to deal with default values. If it's inlined thus
-      // copy is inlined as well.
-      final boolean copyMethodIsPresent = copyDefaultMethodIsPresent;
-
-      Map<MethodSignature, Boolean> presenceMap = ImmutableMap.<MethodSignature, Boolean>builder()
-          .put(COPY_DEFAULT_METHOD, copyDefaultMethodIsPresent)
-          .put(COPY_METHOD, copyMethodIsPresent)
-          .build();
-      checkMethodsPresence(dataClass, presenceMap);
-
-      if (copyDefaultMethodIsPresent) {
-        ClassSubject classSubject = checkClassExists(dexInspector, mainClassName);
-        MethodSubject testMethod = checkMethodIsPresent(classSubject, testMethodSignature);
-        DexCode dexCode = getDexCode(testMethod);
-        checkMethodIsInvokedAtLeastOnce(dexCode, COPY_DEFAULT_METHOD);
-      }
-      if (copyMethodIsPresent) {
-        MethodSubject testMethod = checkMethod(dataClass, COPY_DEFAULT_METHOD, true);
-        DexCode dexCode = getDexCode(testMethod);
-        checkMethodIsInvokedAtLeastOnce(dexCode, COPY_METHOD);
-      }
+      checkMethodIsAbsent(dataClass, COPY_DEFAULT_METHOD);
     });
   }
 
diff --git a/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
new file mode 100644
index 0000000..b059382
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/R8KotlinIntrinsicsTest.java
@@ -0,0 +1,45 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import java.util.Collections;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class R8KotlinIntrinsicsTest extends AbstractR8KotlinTestBase {
+
+  private static final KotlinDataClass KOTLIN_INTRINSICS_CLASS =
+      new KotlinDataClass("kotlin.jvm.internal.Intrinsics");
+
+  @Test
+  public void testParameterNullCheckIsInlined() throws Exception {
+    final String extraRules = keepClassMethod("intrinsics.IntrinsicsKt",
+        new MethodSignature("expectsNonNullParameters",
+            "java.lang.String", Lists.newArrayList("java.lang.String", "java.lang.String")));
+
+    runTest("intrinsics", "intrinsics.IntrinsicsKt", extraRules, (app) -> {
+      DexInspector dexInspector = new DexInspector(app);
+      ClassSubject intrinsicsClass = checkClassExists(
+          dexInspector, KOTLIN_INTRINSICS_CLASS.getClassName());
+
+      checkMethodsPresence(intrinsicsClass,
+          ImmutableMap.<MethodSignature, Boolean>builder()
+              .put(new MethodSignature("throwParameterIsNullException",
+                      "void", Collections.singletonList("java.lang.String")),
+                  true)
+              .put(new MethodSignature("checkParameterIsNotNull",
+                      "void", Lists.newArrayList("java.lang.Object", "java.lang.String")),
+                  allowAccessModification ? false /* should be inlined*/ : true)
+              .build());
+    });
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
index e33d785..36ad4c9 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexTracingTest.java
@@ -47,7 +47,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-1.txt"),
-        AndroidApiLevel.I.getLevel());
+        AndroidApiLevel.I);
   }
 
   @Test
@@ -58,7 +58,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "main-dex-rules-2.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex001", "ref-list-2.txt"),
-        AndroidApiLevel.I.getLevel());
+        AndroidApiLevel.I);
   }
 
   @Test
@@ -69,7 +69,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex002", "ref-list-1.txt"),
-        AndroidApiLevel.I.getLevel());
+        AndroidApiLevel.I);
   }
 
   @Test
@@ -80,7 +80,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex003", "ref-list-1.txt"),
-        AndroidApiLevel.I.getLevel());
+        AndroidApiLevel.I);
   }
 
   @Test
@@ -91,7 +91,7 @@
         EXAMPLE_O_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex", "main-dex-rules.txt"),
         Paths.get(EXAMPLE_O_SRC_DIR, "multidex004", "ref-list-1.txt"),
-        AndroidApiLevel.I.getLevel());
+        AndroidApiLevel.I);
   }
 
   @Test
@@ -131,7 +131,7 @@
         EXAMPLE_BUILD_DIR,
         Paths.get(EXAMPLE_SRC_DIR, "multidex005", "main-dex-rules-" + variant + ".txt"),
         Paths.get(EXAMPLE_SRC_DIR, "multidex005", "ref-list-" + variant + ".txt"),
-        AndroidApiLevel.I.getLevel());
+        AndroidApiLevel.I);
   }
 
   private void doTest(
@@ -140,7 +140,7 @@
       String buildDir,
       Path mainDexRules,
       Path expectedMainDexList,
-      int minSdk)
+      AndroidApiLevel minSdk)
       throws Throwable {
     doTest(
         testName,
@@ -150,7 +150,7 @@
         expectedMainDexList,
         minSdk,
         (options) -> {
-          options.inlineAccessors = false;
+          options.enableInlining = false;
         });
   }
 
@@ -160,7 +160,7 @@
       String buildDir,
       Path mainDexRules,
       Path expectedMainDexList,
-      int minSdk,
+      AndroidApiLevel minSdk,
       Consumer<InternalOptions> optionsConsumer)
       throws Throwable {
     Path out = temp.getRoot().toPath().resolve(testName + ZIP_EXTENSION);
@@ -170,7 +170,7 @@
       // Build main-dex list using GenerateMainDexList and test the output from run.
       GenerateMainDexListCommand.Builder mdlCommandBuilder = GenerateMainDexListCommand.builder();
       GenerateMainDexListCommand mdlCommand = mdlCommandBuilder
-          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()))
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
           .addProgramFiles(inputJar)
           .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
           .addMainDexRulesFiles(mainDexRules)
@@ -189,7 +189,7 @@
       final Box mainDexListOutput = new Box();
       mdlCommandBuilder = GenerateMainDexListCommand.builder();
       mdlCommand = mdlCommandBuilder
-          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()))
+          .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.K))
           .addProgramFiles(inputJar)
           .addProgramFiles(Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
           .addMainDexRulesFiles(mainDexRules)
@@ -207,7 +207,7 @@
       R8Command.Builder r8CommandBuilder = R8Command.builder();
       R8Command command =
           r8CommandBuilder
-              .setMinApiLevel(minSdk)
+              .setMinApiLevel(minSdk.getLevel())
               .addProgramFiles(inputJar)
               .addProgramFiles(
                   Paths.get(EXAMPLE_BUILD_DIR, "multidexfakeframeworks" + JAR_EXTENSION))
diff --git a/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
new file mode 100644
index 0000000..0f1359e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/maindexlist/b72312389/B72312389.java
@@ -0,0 +1,153 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.maindexlist.b72312389;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.BaseCommand;
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.DiagnosticsHandler;
+import com.android.tools.r8.GenerateMainDexList;
+import com.android.tools.r8.GenerateMainDexListCommand;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DexInspector;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B72312389 extends TestBase {
+  // Build a app with a class extending InstrumentationTestCase and including both the junit
+  // and the Android library.
+  private void buildInstrumentationTestCaseApplication(BaseCommand.Builder builder) {
+    builder
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.O))
+        .addProgramFiles(
+            Paths.get("build", "test", "examplesAndroidApi",
+                "classes", "instrumentationtest", "InstrumentationTest.class"))
+        .addProgramFiles(ToolHelper.getFrameworkJunitJarPath(DexVm.ART_7_0_0_HOST));
+  }
+
+  private List<String> keepInstrumentationTestCaseRules = ImmutableList.of(
+      "-keep class instrumentationtest.InstrumentationTest {",
+      "  *;",
+      "}");
+
+  @Test
+  public void testGenerateMainDexList() throws Exception {
+    CollectingDiagnosticHandler diagnostics = new CollectingDiagnosticHandler();
+    GenerateMainDexListCommand.Builder builder = GenerateMainDexListCommand.builder(diagnostics);
+    buildInstrumentationTestCaseApplication(builder);
+    GenerateMainDexListCommand command = builder
+        .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
+        .build();
+    List<String> mainDexList = GenerateMainDexList.run(command);
+    assertTrue(mainDexList.contains("junit/framework/TestCase.class"));
+    diagnostics.assertEmpty();
+  }
+
+  private static class StringBox {
+    String content;
+  }
+
+  @Test
+  public void testR8ForceProguardCompatibility() throws Exception {
+    StringBox mainDexList = new StringBox();
+    // Build a app with a class extending InstrumentationTestCase and including both the junit
+    // and the Android library.
+    CollectingDiagnosticHandler diagnostics = new CollectingDiagnosticHandler();
+    R8Command.Builder builder = new CompatProguardCommandBuilder(true, diagnostics);
+    buildInstrumentationTestCaseApplication(builder);
+    R8Command command = builder
+        .setMinApiLevel(AndroidApiLevel.K.getLevel())
+        // TODO(72793900): This should not be required.
+        .addProguardConfiguration(ImmutableList.of("-keep class ** { *; }"), Origin.unknown())
+        .addProguardConfiguration(ImmutableList.of("-dontobfuscate"), Origin.unknown())
+        .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
+        .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+        .setMainDexListConsumer(
+            (string, handler) -> mainDexList.content = string)
+        .build();
+    DexInspector inspector = new DexInspector(ToolHelper.runR8(command));
+    assertTrue(inspector.clazz("instrumentationtest.InstrumentationTest").isPresent());
+    assertTrue(mainDexList.content.contains("junit/framework/TestCase.class"));
+    // TODO(72794301): Two copies of this message is a bit over the top.
+    assertEquals(2,
+        diagnostics.countLibraryClassExtensdProgramClassWarnings(
+            "android.test.InstrumentationTestCase", "junit.framework.TestCase"));
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    CollectingDiagnosticHandler diagnostics = new CollectingDiagnosticHandler();
+    R8Command.Builder builder = R8Command.builder(diagnostics);
+    buildInstrumentationTestCaseApplication(builder);
+    R8Command command = builder
+        .addMainDexRules(keepInstrumentationTestCaseRules, Origin.unknown())
+        .setProgramConsumer(DexIndexedConsumer.emptyConsumer())
+        .build();
+    try {
+      R8.run(command);
+      fail();
+    } catch (CompilationFailedException e) {
+      // Expected, as library class extending program class is an error for R8.
+    }
+  }
+
+  private static class CollectingDiagnosticHandler implements DiagnosticsHandler {
+    private final List<Diagnostic> infos = new ArrayList<>();
+    private final List<Diagnostic> warnings = new ArrayList<>();
+    private final List<Diagnostic> errors = new ArrayList<>();
+
+    @Override
+    public void info(Diagnostic info) {
+      infos.add(info);
+    }
+
+    @Override
+    public void warning(Diagnostic warning) {
+      warnings.add(warning);
+    }
+
+    @Override
+    public void error(Diagnostic error) {
+      errors.add(error);
+    }
+
+    public void assertEmpty() {
+      assertEquals(0, errors.size());
+      assertEquals(0, warnings.size());
+      assertEquals(0, infos.size());
+    }
+
+    private boolean isLibraryClassExtensdProgramClassWarnings(
+        String libraryClass, String programClass, Diagnostic diagnostic) {
+      return diagnostic.getDiagnosticMessage().equals(
+          "Library class "+ libraryClass + " extends program class " + programClass);
+    }
+
+    public long countLibraryClassExtensdProgramClassWarnings(
+        String libraryClass, String programClass) {
+      return warnings.stream()
+          .filter(diagnostics ->
+              isLibraryClassExtensdProgramClassWarnings(libraryClass, programClass, diagnostics))
+          .count();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
index 9b0b566..e2dfb6d 100644
--- a/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
+++ b/src/test/java/com/android/tools/r8/memberrebinding/MemberRebindingTest.java
@@ -84,7 +84,7 @@
         .addLibraryFiles(JAR_LIBRARIES)
         .setMinApiLevel(minApiLevel);
     ToolHelper.getAppBuilder(builder).addProgramFiles(programFile);
-    ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = false);
+    ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
   }
 
   private static boolean coolInvokes(InstructionSubject instruction) {
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
index 9dbfb13..490162a 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/MoveStringConstantsTest.java
@@ -52,21 +52,39 @@
 
     // Check the instruction used for testInlinedIntoVoidMethod
     MethodSubject methodThrowToBeInlined =
-        clazz.method("void", "foo", ImmutableList.of("java.lang.String"));
+        clazz.method("void", "foo", ImmutableList.of(
+            "java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String"));
     assertTrue(methodThrowToBeInlined.isPresent());
     validateSequence(methodThrowToBeInlined.iterateInstructions(),
-        InstructionSubject::isIfNez,
+        // 'if' with "foo#1" is flipped.
+        InstructionSubject::isIfEqz,
+
+        // 'if' with "foo#2" is removed along with the constant.
+
+        // 'if' with "foo#3" is removed so now we have unconditional call.
+        insn -> insn.isConstString("StringConstants::foo#3"),
+        InstructionSubject::isInvokeStatic,
+        InstructionSubject::isThrow,
+
+        // 'if's with "foo#4" and "foo#5" are flipped, but their throwing branches
+        // are not moved to the end of the code (area for improvement?).
+        insn -> insn.isConstString("StringConstants::foo#4"),
+        InstructionSubject::isIfEqz, // Flipped if
+        InstructionSubject::isGoto, // Jump around throwing branch.
+        InstructionSubject::isInvokeStatic, // Throwing branch.
+        InstructionSubject::isThrow,
+
+        insn -> insn.isConstString("StringConstants::foo#5"),
+        InstructionSubject::isIfEqz, // Flipped if
+        InstructionSubject::isReturnVoid, // Final return statement.
+        InstructionSubject::isInvokeStatic, // Throwing branch.
+        InstructionSubject::isThrow,
+
+        // After 'if' with "foo#1" flipped, always throwing branch
+        // moved here along with the constant.
         insn -> insn.isConstString("StringConstants::foo#1"),
-        // Below two are removed by optimization: non-null argument "".
-        // InstructionSubject::isIfNez,
-        // insn -> insn.isConstString("StringConstants::foo#2"),
-        // InstructionSubject::isIfNez, 'removed by optimization'
-        insn -> insn.isConstString("StringConstants::foo#3")
-        // Below four are removed, since a safe call of arg.length() indicates arg is not null.
-        // insn -> insn.isConstString("StringConstants::foo#4"),
-        // InstructionSubject::isIfNez,
-        // insn -> insn.isConstString("StringConstants::foo#5"),
-        // InstructionSubject::isIfNez
+        InstructionSubject::isInvokeStatic,
+        InstructionSubject::isThrow
     );
   }
 
diff --git a/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java b/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
index b801804..bf13fc3 100644
--- a/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
+++ b/src/test/java/com/android/tools/r8/movestringconstants/TestClass.java
@@ -5,19 +5,19 @@
 package com.android.tools.r8.movestringconstants;
 
 public class TestClass {
-  static void foo(String arg) {
-    Utils.check(arg, "StringConstants::foo#1");
+  static void foo(String arg1, String arg2, String arg3, String arg4) {
+    Utils.check(arg1, "StringConstants::foo#1");
     Utils.check("", "StringConstants::foo#2");
-    if (arg.length() == 12345) {
+    if (arg2.length() == 12345) {
       Utils.check(null, "StringConstants::foo#3");
     }
     try {
-      Utils.check(arg, "StringConstants::foo#4");
+      Utils.check(arg3, "StringConstants::foo#4");
     } catch (Exception e) {
       System.out.println(e.getMessage());
     }
     try {
-      Utils.check(arg, "StringConstants::foo#5");
+      Utils.check(arg4, "StringConstants::foo#5");
     } finally {
       System.out.println("finally");
     }
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
index 1631a52..deb90e4 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -15,6 +15,8 @@
 import com.android.tools.r8.R8Command;
 import com.android.tools.r8.StringConsumer;
 import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.NewInstance;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -219,13 +221,40 @@
         main.iterateInstructions(InstructionSubject::isInvoke);
     // mapping-105 simply includes: naming001.D#keep -> peek
     // naming001.E extends D, hence its keep() should be renamed to peek as well.
-    // Skip E#<init>
-    iterator.next();
+    // Check E#<init> is not renamed.
+    InvokeInstructionSubject init = iterator.next();
+    assertEquals("Lnaming001/E;-><init>()V", init.invokedMethod().toSmaliString());
     // E#keep() should be replaced with peek by applying the map.
     InvokeInstructionSubject m = iterator.next();
     assertEquals("peek", m.invokedMethod().name.toSourceString());
-    // E could be renamed randomly, though.
-    assertNotEquals("naming001.E", m.holder().toString());
+    // D must not be renamed
+    assertEquals("naming001.D", m.holder().toString());
+  }
+
+  @Test
+  public void test_naming001_rule106() throws Exception {
+    // keep rules just to rename E
+    Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-106.txt");
+    Path proguardMap = out.resolve(MAPPING);
+    AndroidApp outputApp =
+        runR8(
+            ToolHelper.addProguardConfigurationConsumer(
+                getCommandForApps(out, flag, NAMING001_JAR).setDisableMinification(true),
+                pgConfig -> {
+                  pgConfig.setPrintMapping(true);
+                  pgConfig.setPrintMappingFile(proguardMap);
+                })
+                .build());
+
+    // Make sure the given proguard map is indeed applied.
+    DexInspector inspector = new DexInspector(outputApp);
+    MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+
+    // naming001.E is renamed to a.a, so first instruction must be: new-instance La/a;
+    Instruction[] instructions = main.getMethod().getCode().asDexCode().instructions;
+    assertTrue(instructions[0] instanceof NewInstance);
+    NewInstance newInstance = (NewInstance) instructions[0];
+    assertEquals( "La/a;", newInstance.getType().toSmaliString());
   }
 
   @Test
@@ -264,7 +293,7 @@
       throws ProguardRuleParserException, ExecutionException, CompilationException, IOException {
     return ToolHelper.runR8(command, options -> {
       // Disable inlining to make this test not depend on inlining decisions.
-      options.inlineAccessors = false;
+      options.enableInlining = false;
     });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
index 0d1ca64..fa39257 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierMinifierTest.java
@@ -11,6 +11,7 @@
 
 import com.android.tools.r8.OutputMode;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.TestBase;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.ConstStringJumbo;
@@ -18,6 +19,7 @@
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexValue.DexValueString;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -43,15 +45,12 @@
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class IdentifierMinifierTest {
+public class IdentifierMinifierTest extends TestBase {
 
   private final String appFileName;
   private final List<String> keepRulesFiles;
   private final Consumer<DexInspector> inspection;
 
-  @Rule
-  public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
   public IdentifierMinifierTest(
       String test,
       List<String> keepRulesFiles,
@@ -61,30 +60,28 @@
     this.inspection = inspection;
   }
 
+  private AndroidApp processedApp;
+
   @Before
   public void generateR8ProcessedApp() throws Exception {
     Path out = temp.getRoot().toPath();
     R8Command.Builder builder =
         ToolHelper.addProguardConfigurationConsumer(
-                R8Command.builder(),
-                pgConfig -> {
-                  pgConfig.setPrintMapping(true);
-                  pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
-                })
+            R8Command.builder(),
+            pgConfig -> {
+              pgConfig.setPrintMapping(true);
+              pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+            })
             .setOutput(out, OutputMode.DexIndexed)
             .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
             .addLibraryFiles(ToolHelper.getDefaultAndroidJar());
     ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(appFileName));
-    ToolHelper.runR8(builder.build());
+    processedApp = ToolHelper.runR8(builder.build(), o -> o.debug = false);
   }
 
   @Test
   public void identiferMinifierTest() throws Exception {
-    Path out = temp.getRoot().toPath();
-    DexInspector dexInspector =
-        new DexInspector(
-            out.resolve("classes.dex"),
-            out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE).toString());
+    DexInspector dexInspector = new DexInspector(processedApp);
     inspection.accept(dexInspector);
   }
 
@@ -189,6 +186,13 @@
     Set<Instruction> constStringInstructions =
         getRenamedMemberIdentifierConstStrings(a, mainCode.asDexCode().instructions);
     assertEquals(2, constStringInstructions.size());
+
+    ClassSubject b = inspector.clazz("getmembers.B");
+    MethodSubject inliner = b.method("java.lang.String", "inliner", ImmutableList.of());
+    Code inlinerCode = inliner.getMethod().getCode();
+    constStringInstructions =
+        getRenamedMemberIdentifierConstStrings(a, inlinerCode.asDexCode().instructions);
+    assertEquals(1, constStringInstructions.size());
   }
 
   // Without -identifiernamestring
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index 62c1bec..b0e1131 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -60,12 +60,13 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        ConstString.class,
+        IputObject.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[1];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[2] instanceof IputObject);
-    assertTrue(code.instructions[3] instanceof ReturnVoid);
   }
 
   @Test
@@ -92,17 +93,18 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof SgetObject);
-    assertTrue(code.instructions[2] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        SgetObject.class,
+        ConstString.class,
+        InvokeVirtual.class,
+        ConstString.class,
+        IputObject.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[2];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[3] instanceof InvokeVirtual);
-    assertTrue(code.instructions[4] instanceof ConstString);
     constString = (ConstString) code.instructions[4];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[5] instanceof IputObject);
-    assertTrue(code.instructions[6] instanceof ReturnVoid);
   }
 
   @Test
@@ -131,17 +133,18 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof SgetObject);
-    assertTrue(code.instructions[2] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        SgetObject.class,
+        ConstString.class,
+        InvokeVirtual.class,
+        ConstString.class,
+        IputObject.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[2];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[3] instanceof InvokeVirtual);
-    assertTrue(code.instructions[4] instanceof ConstString);
     constString = (ConstString) code.instructions[4];
     assertNotEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[5] instanceof IputObject);
-    assertTrue(code.instructions[6] instanceof ReturnVoid);
   }
 
   @Test
@@ -165,11 +168,12 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        ConstString.class,
+        SputObject.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[0];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[1] instanceof SputObject);
-    assertTrue(code.instructions[2] instanceof ReturnVoid);
   }
 
   @Test
@@ -195,16 +199,17 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof SgetObject);
-    assertTrue(code.instructions[1] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        SgetObject.class,
+        ConstString.class,
+        InvokeVirtual.class,
+        ConstString.class,
+        SputObject.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[1];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[2] instanceof InvokeVirtual);
-    assertTrue(code.instructions[3] instanceof ConstString);
     constString = (ConstString) code.instructions[3];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[4] instanceof SputObject);
-    assertTrue(code.instructions[5] instanceof ReturnVoid);
   }
 
   @Test
@@ -232,16 +237,17 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof SgetObject);
-    assertTrue(code.instructions[1] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        SgetObject.class,
+        ConstString.class,
+        InvokeVirtual.class,
+        ConstString.class,
+        SputObject.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[1];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[2] instanceof InvokeVirtual);
-    assertTrue(code.instructions[3] instanceof ConstString);
     constString = (ConstString) code.instructions[3];
     assertNotEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[4] instanceof SputObject);
-    assertTrue(code.instructions[5] instanceof ReturnVoid);
   }
 
   @Test
@@ -252,8 +258,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
-        "-dontshrink",
-        "-dontoptimize");
+        "-dontshrink");
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -275,8 +280,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep class " + CLASS_NAME + " { static java.lang.String sClassName; }",
         "-keep,allowobfuscation class " + BOO,
-        "-dontshrink",
-        "-dontoptimize");
+        "-dontshrink");
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -300,8 +304,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sFieldName; }",
         "-keep class " + CLASS_NAME + " { static java.lang.String sFieldName; }",
         "-keep,allowobfuscation class " + BOO + " { <fields>; }",
-        "-dontshrink",
-        "-dontoptimize");
+        "-dontshrink");
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -325,8 +328,7 @@
         "-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sMethodName; }",
         "-keep class " + CLASS_NAME + " { static java.lang.String sMethodName; }",
         "-keep,allowobfuscation class " + BOO + " { <methods>; }",
-        "-dontshrink",
-        "-dontoptimize");
+        "-dontshrink");
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -356,8 +358,7 @@
 
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
-        "-keep class " + CLASS_NAME,
-        "-dontoptimize");
+        "-keep class " + CLASS_NAME);
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -366,15 +367,16 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        ConstString.class,
+        ConstString.class,
+        InvokeStatic.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[1];
     assertEquals("Mixed/form.Boo", constString.getString().toString());
-    assertTrue(code.instructions[2] instanceof ConstString);
     constString = (ConstString) code.instructions[2];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[3] instanceof InvokeStatic);
-    assertTrue(code.instructions[4] instanceof ReturnVoid);
   }
 
   @Test
@@ -396,8 +398,7 @@
 
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
-        "-keep class " + CLASS_NAME,
-        "-dontoptimize");
+        "-keep class " + CLASS_NAME);
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -406,17 +407,18 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof SgetObject);
-    assertTrue(code.instructions[2] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        SgetObject.class,
+        ConstString.class,
+        InvokeVirtual.class,
+        ConstString.class,
+        InvokeStatic.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[2];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[3] instanceof InvokeVirtual);
-    assertTrue(code.instructions[4] instanceof ConstString);
     constString = (ConstString) code.instructions[4];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[5] instanceof InvokeStatic);
-    assertTrue(code.instructions[6] instanceof ReturnVoid);
   }
 
   @Test
@@ -440,8 +442,7 @@
     List<String> pgConfigs = ImmutableList.of(
         "-identifiernamestring class " + CLASS_NAME + " { static void foo(...); }",
         "-keep class " + CLASS_NAME,
-        "-keep,allowobfuscation class " + BOO,
-        "-dontoptimize");
+        "-keep,allowobfuscation class " + BOO);
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -450,17 +451,18 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof SgetObject);
-    assertTrue(code.instructions[2] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        SgetObject.class,
+        ConstString.class,
+        InvokeVirtual.class,
+        ConstString.class,
+        InvokeStatic.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[2];
     assertEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[3] instanceof InvokeVirtual);
-    assertTrue(code.instructions[4] instanceof ConstString);
     constString = (ConstString) code.instructions[4];
     assertNotEquals(BOO, constString.getString().toString());
-    assertTrue(code.instructions[5] instanceof InvokeStatic);
-    assertTrue(code.instructions[6] instanceof ReturnVoid);
   }
 
   @Test
@@ -492,8 +494,7 @@
             + "  static java.lang.reflect.Field *(java.lang.Class,java.lang.String);\n"
             + "}",
         "-keep class " + CLASS_NAME,
-        "-keep class R { *; }",
-        "-dontoptimize");
+        "-keep class R { *; }");
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -502,13 +503,14 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof ConstClass);
-    assertTrue(code.instructions[2] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        ConstClass.class,
+        ConstString.class,
+        InvokeStatic.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[2];
     assertEquals("foo", constString.getString().toString());
-    assertTrue(code.instructions[3] instanceof InvokeStatic);
-    assertTrue(code.instructions[4] instanceof ReturnVoid);
   }
 
   @Test
@@ -540,8 +542,7 @@
             + "  static java.lang.reflect.Field *(java.lang.Class,java.lang.String);\n"
             + "}",
         "-keep class " + CLASS_NAME,
-        "-keep,allowobfuscation class R { *; }",
-        "-dontoptimize");
+        "-keep,allowobfuscation class R { *; }");
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -550,13 +551,14 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof ConstClass);
-    assertTrue(code.instructions[2] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        ConstClass.class,
+        ConstString.class,
+        InvokeStatic.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[2];
     assertNotEquals("foo", constString.getString().toString());
-    assertTrue(code.instructions[3] instanceof InvokeStatic);
-    assertTrue(code.instructions[4] instanceof ReturnVoid);
   }
 
   @Test
@@ -595,8 +597,7 @@
             + "    *(java.lang.Class,java.lang.String,java.lang.Class[]);\n"
             + "}",
         "-keep class " + CLASS_NAME,
-        "-keep class R { *; }",
-        "-dontoptimize");
+        "-keep class R { *; }");
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -605,17 +606,18 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof ConstClass);
-    assertTrue(code.instructions[2] instanceof Const4);
-    assertTrue(code.instructions[3] instanceof NewArray);
-    assertTrue(code.instructions[4] instanceof Const4);
-    assertTrue(code.instructions[5] instanceof AputObject);
-    assertTrue(code.instructions[6] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        ConstClass.class,
+        Const4.class,
+        NewArray.class,
+        Const4.class,
+        AputObject.class,
+        ConstString.class,
+        InvokeStatic.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[6];
     assertEquals("foo", constString.getString().toString());
-    assertTrue(code.instructions[7] instanceof InvokeStatic);
-    assertTrue(code.instructions[8] instanceof ReturnVoid);
   }
 
   @Test
@@ -654,8 +656,7 @@
             + "    *(java.lang.Class,java.lang.String,java.lang.Class[]);\n"
             + "}",
         "-keep class " + CLASS_NAME,
-        "-keep,allowobfuscation class R { *; }",
-        "-dontoptimize");
+        "-keep,allowobfuscation class R { *; }");
     DexInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
 
     ClassSubject clazz = inspector.clazz(CLASS_NAME);
@@ -664,17 +665,18 @@
     assertNotNull(method);
 
     DexCode code = method.getCode().asDexCode();
-    assertTrue(code.instructions[0] instanceof InvokeDirect);
-    assertTrue(code.instructions[1] instanceof ConstClass);
-    assertTrue(code.instructions[2] instanceof Const4);
-    assertTrue(code.instructions[3] instanceof NewArray);
-    assertTrue(code.instructions[4] instanceof Const4);
-    assertTrue(code.instructions[5] instanceof AputObject);
-    assertTrue(code.instructions[6] instanceof ConstString);
+    checkInstructions(code, ImmutableList.of(
+        InvokeDirect.class,
+        ConstClass.class,
+        Const4.class,
+        NewArray.class,
+        Const4.class,
+        AputObject.class,
+        ConstString.class,
+        InvokeStatic.class,
+        ReturnVoid.class));
     ConstString constString = (ConstString) code.instructions[6];
     assertNotEquals("foo", constString.getString().toString());
-    assertTrue(code.instructions[7] instanceof InvokeStatic);
-    assertTrue(code.instructions[8] instanceof ReturnVoid);
   }
 
   private DexInspector getInspectorAfterRunR8(
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
index 84dd3b7..ca86a53 100644
--- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
+++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileDebugTest.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.debug.DebugTestBase;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.debug.DexDebugTestConfig;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.google.common.collect.ImmutableList;
 import java.nio.file.Path;
 import org.junit.BeforeClass;
@@ -26,7 +27,7 @@
 
   @BeforeClass
   public static void initDebuggeePath() throws Exception {
-    int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
+    AndroidApiLevel minSdk = ToolHelper.getMinApiLevelForDexVm();
     Path outdir = temp.newFolder().toPath();
     Path outjar = outdir.resolve("r8_compiled.jar");
     Path proguardMapPath = outdir.resolve("proguard.map");
@@ -39,7 +40,7 @@
                       ImmutableList.of("SourceFile", "LineNumberTable"));
                 })
             .addProgramFiles(DEBUGGEE_JAR)
-            .setMinApiLevel(minSdk)
+            .setMinApiLevel(minSdk.getLevel())
             .addLibraryFiles(ToolHelper.getAndroidJar(minSdk))
             .setMode(CompilationMode.DEBUG)
             .setOutput(outjar, OutputMode.DexIndexed)
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
new file mode 100644
index 0000000..b96c17a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/B72391662.java
@@ -0,0 +1,56 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b72391662;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.VmTestRunner;
+import com.android.tools.r8.VmTestRunner.IgnoreIfVmOlderThan;
+import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageSuper;
+import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import joptsimple.internal.Strings;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(VmTestRunner.class)
+public class B72391662 extends TestBase {
+
+  private void doTest(boolean allowAccessModification, boolean minify) throws Exception {
+    Class mainClass = TestMain.class;
+    List<String> config = ImmutableList.of(
+        allowAccessModification ?"-allowaccessmodification" : "",
+        !minify ? "-dontobfuscate" : "",
+        "-keep class " + mainClass.getCanonicalName() + " {",
+        "  public void main(java.lang.String[]);",
+        "}",
+        "-keep class " + TestClass.class.getCanonicalName() + " {",
+        "  *;",
+        "}",
+        "-keep class " + OtherPackageTestClass.class.getCanonicalName() + " {",
+        "  *;",
+        "}"
+    );
+
+    AndroidApp app = readClassesAndAndriodJar(ImmutableList.of(
+        mainClass, Interface.class, Super.class, TestClass.class,
+        OtherPackageSuper.class, OtherPackageTestClass.class));
+    app = compileWithR8(app, Strings.join(config, System.lineSeparator()));
+    assertEquals("123451234567\nABC\n", runOnArt(app, mainClass.getCanonicalName()));
+  }
+
+  @Test
+  @IgnoreIfVmOlderThan(Version.V7_0_0)
+  public void test() throws Exception {
+    doTest(true, true);
+    doTest(true, false);
+    doTest(false, true);
+    doTest(false, false);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/b72391662/Interface.java
similarity index 69%
copy from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
copy to src/test/java/com/android/tools/r8/naming/b72391662/Interface.java
index 09e94f7..803466e 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/Interface.java
@@ -1,8 +1,9 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
 
-public class FieldAccessTest {
-  String fld;
+package com.android.tools.r8.naming.b72391662;
+
+public interface Interface {
+  int getValue();
 }
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/b72391662/Super.java
similarity index 66%
copy from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
copy to src/test/java/com/android/tools/r8/naming/b72391662/Super.java
index 09e94f7..acb891d 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/Super.java
@@ -1,8 +1,11 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
 
-public class FieldAccessTest {
-  String fld;
+package com.android.tools.r8.naming.b72391662;
+
+public class Super {
+  int returnFive() {
+    return 5;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
new file mode 100644
index 0000000..88efc09
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/TestClass.java
@@ -0,0 +1,44 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b72391662;
+
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+
+public class TestClass extends Super implements Interface {
+  private final int value;
+
+  TestClass() {
+    this.value = 3;
+  }
+
+  public int getValue() {
+    return value;
+  }
+
+  static String staticMethod() {
+    return "1";
+  }
+
+  String instanceMethod() {
+    return "2";
+  }
+
+  private int y(IntSupplier x) {
+    return x.getAsInt();
+  }
+  int x() {
+    return y(super::returnFive);
+  }
+
+  Object supplyNull() {
+    System.out.print("A");
+    return null;
+  }
+
+  Object useSupplier(Supplier<Object> a) {
+    return a.get();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/TestMain.java b/src/test/java/com/android/tools/r8/naming/b72391662/TestMain.java
new file mode 100644
index 0000000..4da4b87
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/TestMain.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b72391662;
+
+import com.android.tools.r8.naming.b72391662.subpackage.OtherPackageTestClass;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
+public class TestMain extends Super {
+  private Object supplyNull() {
+    System.out.print("C");
+    return null;
+  }
+
+  private Object useSupplier(Supplier<Object> a) {
+    return a.get();
+  }
+
+  private static void printString(Supplier<String> stringSupplier) {
+    System.out.print(stringSupplier.get());
+  }
+
+  private static void printInteger(Integer i) {
+    System.out.print(i);
+  }
+
+  private static void printValue(Supplier<Interface> s) {
+    System.out.print(s.get().getValue());
+  }
+
+  private static void printNewArrayLength(IntFunction<Interface[]> s) {
+    System.out.print(s.apply(4).length);
+  }
+
+  public static void main(String[] args) {
+    // Test with an instance in this package.
+    TestClass instanceInThisPackage = new TestClass();
+    printString(TestClass::staticMethod);
+    printString(instanceInThisPackage::instanceMethod);
+    printValue(TestClass::new);
+    printNewArrayLength(TestClass[]::new);
+    printInteger(instanceInThisPackage.x());
+
+    // Test with an instance in another package.
+    OtherPackageTestClass instanceInOtherPackage = new OtherPackageTestClass();
+    printString(OtherPackageTestClass::staticMethod);
+    printString(instanceInOtherPackage::instanceMethod);
+    printValue(OtherPackageTestClass::new);
+    printNewArrayLength(OtherPackageTestClass[]::new);
+    printInteger(instanceInOtherPackage.x());
+
+    Function<Integer, Integer> lambda = x -> x + 2;
+    printInteger(lambda.apply(4));
+
+    List<Integer> list =  new ArrayList<>();
+    list.add(5);
+    list.forEach(e -> { System.out.println(e + 2);});
+
+    instanceInThisPackage.useSupplier(instanceInThisPackage::supplyNull);
+    instanceInOtherPackage.useSupplier(instanceInOtherPackage::supplyNull);
+    TestMain instanceOfThisClass = new TestMain();
+    instanceOfThisClass.useSupplier(instanceOfThisClass::supplyNull);
+
+    System.out.println("");
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java b/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageSuper.java
similarity index 61%
copy from src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
copy to src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageSuper.java
index 09e94f7..01aee8a 100644
--- a/src/test/java/com/android/tools/r8/ir/nonnull/FieldAccessTest.java
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageSuper.java
@@ -1,8 +1,11 @@
 // Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.ir.nonnull;
 
-public class FieldAccessTest {
-  String fld;
+package com.android.tools.r8.naming.b72391662.subpackage;
+
+public class OtherPackageSuper {
+  int returnFive() {
+    return 5;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageTestClass.java b/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageTestClass.java
new file mode 100644
index 0000000..0476a6a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/b72391662/subpackage/OtherPackageTestClass.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.naming.b72391662.subpackage;
+
+import com.android.tools.r8.naming.b72391662.Interface;
+import java.util.function.IntSupplier;
+import java.util.function.Supplier;
+
+public class OtherPackageTestClass extends OtherPackageSuper implements Interface{
+  public final int value;
+
+  public OtherPackageTestClass() {
+    this.value = 3;
+  }
+
+  public int getValue() {
+    return value;
+  }
+
+  public static String staticMethod() {
+    return "1";
+  }
+
+  public String instanceMethod() {
+    return "2";
+  }
+
+  private int y(IntSupplier x) {
+    return x.getAsInt();
+  }
+
+  public int x() {
+    return y(super::returnFive);
+  }
+
+  public Object supplyNull() {
+    System.out.print("B");
+    return null;
+  }
+
+  public Object useSupplier(Supplier<Object> a) {
+    return a.get();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/A.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/A.java
new file mode 100644
index 0000000..96b8a06
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/A.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+public class A {
+  public volatile int f1;
+  volatile Object f2;
+  public volatile B f3;
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java
new file mode 100644
index 0000000..6660196
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/B.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+public class B {
+  volatile int f1 = 8;
+  volatile Object f2 = "d8";
+  volatile String f3 = "r8";
+
+  public int getF1() {
+    return f1;
+  }
+
+  public Object getF2() {
+    return f2;
+  }
+
+  public String getF3() {
+    return f3;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldResolution.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldResolution.java
new file mode 100644
index 0000000..26f5df6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldResolution.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import java.lang.reflect.Field;
+import java.util.Random;
+
+public class FieldResolution {
+  public static void main(String[] args) throws Exception {
+    A a = new A();
+    B b = new B();
+
+    Field f3 = A.class.getField("f3");
+    f3.set(a, b);
+    assert a.f3 != null;
+    assert a.f3 == b;
+
+    Field f1 = A.class.getField("f1");
+    Random random = new Random();
+    int next = random.nextInt();
+    f1.set(a, next);
+    a.f3.f1 = next;
+    int diff = a.f1 - b.f1;
+    System.out.println("diff: " + diff);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldUpdater.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldUpdater.java
new file mode 100644
index 0000000..8f5011c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/FieldUpdater.java
@@ -0,0 +1,37 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+public class FieldUpdater {
+  @SuppressWarnings("unchecked")
+  public static void main(String[] args) throws Exception {
+    A a = new A();
+    B b = new B();
+    AtomicReferenceFieldUpdater f3Updater =
+        AtomicReferenceFieldUpdater.newUpdater(A.class, B.class, "f3");
+    f3Updater.set(a, b);
+    AtomicReferenceFieldUpdater f2Updater =
+        AtomicReferenceFieldUpdater.newUpdater(A.class, Object.class, "f2");
+    f2Updater.set(a, b);
+    assert a.f2 instanceof B;
+    assert a.f2 == a.f3;
+    ((B) a.f2).f2 = a;
+    assert b.f2 instanceof A;
+    assert ((A) b.f2).f2 == b;
+
+    Random random = new Random();
+    int next = random.nextInt();
+    AtomicIntegerFieldUpdater f1Updater =
+        AtomicIntegerFieldUpdater.newUpdater(A.class, "f1");
+    f1Updater.set(a, next);
+    B viaF3 = a.f3;
+    viaF3.f1 = next;
+    int diff = viaF3.f1 - a.f1;
+    System.out.println("diff: " + diff);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/MethodResolution.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/MethodResolution.java
new file mode 100644
index 0000000..c791031
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/MethodResolution.java
@@ -0,0 +1,25 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import java.lang.reflect.Method;
+
+public class MethodResolution {
+  public static void main(String[] args) throws Exception {
+    B b = new B();
+
+    int originalF1 = b.getF1();
+    Method getF1 = B.class.getMethod("getF1", (Class[]) null);
+    int diff = ((Integer) getF1.invoke(b)) - originalF1;
+    System.out.println("diff: " + diff);
+
+    Object originalF2 = b.getF2();
+    Method getF2 = B.class.getMethod("getF2", (Class[]) null);
+    System.out.println(originalF2 + " v.s. " + getF2.invoke(b));
+
+    String originalF3 = b.getF3();
+    Method getF3 = B.class.getMethod("getF3", (Class[]) null);
+    System.out.println(originalF3 + " v.s. " + getF3.invoke(b));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
new file mode 100644
index 0000000..dce8880
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/OverloadAggressivelyTest.java
@@ -0,0 +1,203 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.CompatProguardCommandBuilder;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Kind;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.graph.DexEncodedField;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class OverloadAggressivelyTest extends TestBase {
+  private final DexVm dexVm;
+  private final boolean overloadaggressively;
+
+  public OverloadAggressivelyTest(DexVm dexVm, boolean overloadaggressively) {
+    this.dexVm = dexVm;
+    this.overloadaggressively = overloadaggressively;
+  }
+
+  @Parameters(name = "vm: {0}, overloadaggressively: {1}")
+  public static Collection<Object[]> data() {
+    List<Object[]> testCases = new ArrayList<>();
+    for (DexVm version : DexVm.values()) {
+      if (version.getKind() == Kind.HOST) {
+        testCases.add(new Object[]{version, true});
+        testCases.add(new Object[]{version, false});
+      }
+    }
+    return testCases;
+  }
+
+  private AndroidApp runR8(AndroidApp app, Class main, Path out) throws Exception {
+     R8Command command =
+        ToolHelper.addProguardConfigurationConsumer(
+            ToolHelper.prepareR8CommandBuilder(app),
+            pgConfig -> {
+              pgConfig.setPrintMapping(true);
+              pgConfig.setPrintMappingFile(out.resolve(ToolHelper.DEFAULT_PROGUARD_MAP_FILE));
+            })
+        .addProguardConfiguration(
+            ImmutableList.copyOf(Iterables.concat(ImmutableList.of(
+                keepMainProguardConfiguration(main),
+                overloadaggressively ? "-overloadaggressively" : ""),
+                CompatProguardCommandBuilder.REFLECTIONS)),
+            Origin.unknown())
+        .setOutput(out, OutputMode.DexIndexed)
+        .build();
+    return ToolHelper.runR8(command, o -> o.enableInlining = false);
+  }
+
+  @Test
+  public void fieldUpdater() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(FieldUpdater.class),
+        ToolHelper.getClassAsBytes(A.class),
+        ToolHelper.getClassAsBytes(B.class)
+    };
+    AndroidApp originalApp = buildAndroidApp(classes);
+    Path out = temp.getRoot().toPath();
+    AndroidApp processedApp = runR8(originalApp, FieldUpdater.class, out);
+
+    DexInspector dexInspector = new DexInspector(processedApp);
+    ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
+    DexEncodedField f1 = a.field("int", "f1").getField();
+    assertNotNull(f1);
+    DexEncodedField f2 = a.field("java.lang.Object", "f2").getField();
+    assertNotNull(f2);
+    // TODO(b/72858955): due to the potential reflective access, they should have different names
+    //   by R8's improved reflective access detection or via keep rules.
+    assertEquals(overloadaggressively, f1.field.name == f2.field.name);
+    DexEncodedField f3 = a.field(B.class.getCanonicalName(), "f3").getField();
+    assertNotNull(f3);
+    // TODO(b/72858955): ditto
+    assertEquals(overloadaggressively, f1.field.name == f3.field.name);
+    // TODO(b/72858955): ditto
+    assertEquals(overloadaggressively, f2.field.name == f3.field.name);
+
+    String main = FieldUpdater.class.getCanonicalName();
+    ProcessResult javaOutput = runOnJava(main, classes);
+    assertEquals(0, javaOutput.exitCode);
+    ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+    // TODO(b/72858955): eventually, R8 should avoid this field resolution conflict.
+    if (overloadaggressively) {
+      assertNotEquals(0, artOutput.exitCode);
+      assertTrue(artOutput.stderr.contains("ClassCastException"));
+    } else {
+      assertEquals(0, artOutput.exitCode);
+      assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+      // ART may dump its own debugging info through stderr.
+      // assertEquals(javaOutput.stderr.trim(), artOutput.stderr.trim());
+    }
+  }
+
+  @Test
+  public void fieldResolution() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(FieldResolution.class),
+        ToolHelper.getClassAsBytes(A.class),
+        ToolHelper.getClassAsBytes(B.class)
+    };
+    AndroidApp originalApp = buildAndroidApp(classes);
+    Path out = temp.getRoot().toPath();
+    AndroidApp processedApp = runR8(originalApp, FieldResolution.class, out);
+
+    DexInspector dexInspector = new DexInspector(processedApp);
+    ClassSubject a = dexInspector.clazz(A.class.getCanonicalName());
+    DexEncodedField f1 = a.field("int", "f1").getField();
+    assertNotNull(f1);
+    DexEncodedField f3 = a.field(B.class.getCanonicalName(), "f3").getField();
+    assertNotNull(f3);
+    // TODO(b/72858955): due to the potential reflective access, they should have different names
+    //   by R8's improved reflective access detection or via keep rules.
+    assertEquals(overloadaggressively, f1.field.name == f3.field.name);
+
+    String main = FieldResolution.class.getCanonicalName();
+    ProcessResult javaOutput = runOnJava(main, classes);
+    assertEquals(0, javaOutput.exitCode);
+    ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+    // TODO(b/72858955): R8 should avoid field resolution conflict even w/ -overloadaggressively.
+    if (overloadaggressively) {
+      assertNotEquals(0, artOutput.exitCode);
+      assertTrue(artOutput.stderr.contains("IllegalArgumentException"));
+    } else {
+      assertEquals(0, artOutput.exitCode);
+      assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+      // ART may dump its own debugging info through stderr.
+      // assertEquals(javaOutput.stderr.trim(), artOutput.stderr.trim());
+    }
+  }
+
+  @Test
+  public void methodResolution() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    byte[][] classes = {
+        ToolHelper.getClassAsBytes(MethodResolution.class),
+        ToolHelper.getClassAsBytes(B.class)
+    };
+    AndroidApp originalApp = buildAndroidApp(classes);
+    Path out = temp.getRoot().toPath();
+    AndroidApp processedApp = runR8(originalApp, MethodResolution.class, out);
+
+    DexInspector dexInspector = new DexInspector(processedApp);
+    ClassSubject b = dexInspector.clazz(B.class.getCanonicalName());
+    DexEncodedMethod m1 =
+        b.method("int", "getF1", ImmutableList.of()).getMethod();
+    assertNotNull(m1);
+    DexEncodedMethod m2 =
+        b.method("java.lang.Object", "getF2", ImmutableList.of()).getMethod();
+    // TODO(b/72858955): due to the potential reflective access, they should have different names.
+    assertEquals(overloadaggressively, m1.method.name == m2.method.name);
+    DexEncodedMethod m3 =
+        b.method("java.lang.String", "getF3", ImmutableList.of()).getMethod();
+    assertNotNull(m3);
+    // TODO(b/72858955): ditto
+    assertEquals(overloadaggressively, m1.method.name == m3.method.name);
+    // TODO(b/72858955): ditto
+    assertEquals(overloadaggressively, m2.method.name == m3.method.name);
+
+    String main = MethodResolution.class.getCanonicalName();
+    ProcessResult javaOutput = runOnJava(main, classes);
+    assertEquals(0, javaOutput.exitCode);
+    ProcessResult artOutput = runOnArtRaw(processedApp, main, dexVm);
+    // TODO(b/72858955): R8 should avoid method resolution conflict even w/ -overloadaggressively.
+    if (overloadaggressively) {
+      assertEquals(0, artOutput.exitCode);
+      assertNotEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+    } else {
+      assertEquals(0, artOutput.exitCode);
+      assertEquals(javaOutput.stdout.trim(), artOutput.stdout.trim());
+      // ART may dump its own debugging info through stderr.
+      // assertEquals(javaOutput.stderr.trim(), artOutput.stderr.trim());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
new file mode 100644
index 0000000..66d76b7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/overloadaggressively/ValidNameConflictTest.java
@@ -0,0 +1,370 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.naming.overloadaggressively;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Kind;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.FieldSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ValidNameConflictTest extends JasminTestBase {
+  private final String CLASS_NAME = "Example";
+  private final String MSG = "You are seeing undefined behavior.";
+
+  private final String REFLECTIONS =
+      "-identifiernamestring public class java.lang.Class {\n"
+          + "  public java.lang.reflect.Field getField(java.lang.String);\n"
+          + "  public java.lang.reflect.Method getMethod(java.lang.String, java.lang.Class[]);"
+          + "}";
+
+  private final DexVm dexVm;
+
+  public ValidNameConflictTest(DexVm dexVm) {
+    this.dexVm = dexVm;
+  }
+
+  @Parameters(name = "vm: {0}")
+  public static Collection<Object> data() {
+    List<Object> testCases = new ArrayList<>();
+    for (DexVm version : DexVm.values()) {
+      if (version.getKind() == Kind.HOST) {
+        testCases.add(version);
+      }
+    }
+    return testCases;
+  }
+
+  private JasminBuilder buildFieldNameConflictClassFile() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+    classBuilder.addStaticField("same", "Ljava/lang/String;", "\"" + MSG + "\"");
+    classBuilder.addStaticField("same", "Ljava/lang/Object;", null);
+    classBuilder.addMainMethod(
+        ".limit stack 3",
+        ".limit locals 1",
+        "ldc Example",
+        "ldc \"same\"",
+        "invokevirtual java/lang/Class/getField(Ljava/lang/String;)Ljava/lang/reflect/Field;",
+        "astore_0",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "aload_0",
+        "aconst_null",
+        "invokevirtual java/lang/reflect/Field/get(Ljava/lang/Object;)Ljava/lang/Object;",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+        "return");
+    return builder;
+  }
+
+  @Test
+  public void remainFieldNameConflictDueToKeepRules() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    JasminBuilder builder = buildFieldNameConflictClassFile();
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+    assertEquals(0, javaOutput.exitCode);
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-keep public class " + CLASS_NAME + " {\n"
+            + "  public static void main(java.lang.String[]);\n"
+            + "  static <fields>;"
+            + "}\n"
+            + "-printmapping\n",
+        REFLECTIONS,
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexInspector dexInspector = new DexInspector(app);
+    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    FieldSubject f1 = clazz.field("java.lang.String", "same");
+    assertTrue(f1.isPresent());
+    assertFalse(f1.isRenamed());
+    FieldSubject f2 = clazz.field("java.lang.Object", "same");
+    assertTrue(f2.isPresent());
+    assertFalse(f2.isRenamed());
+    assertEquals(f1.getField().field.name, f2.getField().field.name);
+
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    assertEquals(0, artOutput.exitCode);
+    // With reserved *same* names, it is not guaranteed to have same output.
+    // assertEquals(javaOutput.stdout, artOutput.stdout);
+  }
+
+
+  @Test
+  public void remainFieldNameConflictWithUseUniqueClassMemberNames() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    JasminBuilder builder = buildFieldNameConflictClassFile();
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+    assertEquals(0, javaOutput.exitCode);
+
+    List<String> pgConfigs = ImmutableList.of(
+        keepMainProguardConfiguration(CLASS_NAME),
+        REFLECTIONS,
+        "-useuniqueclassmembernames",
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexInspector dexInspector = new DexInspector(app);
+    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    FieldSubject f1 = clazz.field("java.lang.String", "same");
+    assertTrue(f1.isPresent());
+    assertTrue(f1.isRenamed());
+    FieldSubject f2 = clazz.field("java.lang.Object", "same");
+    assertTrue(f2.isPresent());
+    assertTrue(f2.isRenamed());
+    // TODO(b/73149686): -useuniqueclassmembernames for field minification is buggy.
+    // assertEquals(f1.getField().field.name, f2.getField().field.name);
+
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    assertEquals(0, artOutput.exitCode);
+    // TODO(b/73149686): with reserved *same* names, it is not guaranteed to have same output.
+    assertEquals(javaOutput.stdout, artOutput.stdout);
+  }
+
+  @Test
+  public void resolveFieldNameConflictWithoutAnyOption() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    JasminBuilder builder = buildFieldNameConflictClassFile();
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+    assertEquals(0, javaOutput.exitCode);
+
+    List<String> pgConfigs = ImmutableList.of(
+        keepMainProguardConfiguration(CLASS_NAME),
+        REFLECTIONS,
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexInspector dexInspector = new DexInspector(app);
+    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    FieldSubject f1 = clazz.field("java.lang.String", "same");
+    assertTrue(f1.isPresent());
+    assertTrue(f1.isRenamed());
+    FieldSubject f2 = clazz.field("java.lang.Object", "same");
+    assertTrue(f2.isPresent());
+    assertTrue(f2.isRenamed());
+    assertNotEquals(f1.getField().field.name, f2.getField().field.name);
+
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    assertEquals(0, artOutput.exitCode);
+    assertEquals(javaOutput.stdout, artOutput.stdout);
+  }
+
+  @Test
+  public void resolveFieldNameConflictEvenWithOverloadAggressively() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    JasminBuilder builder = buildFieldNameConflictClassFile();
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+    assertEquals(0, javaOutput.exitCode);
+
+    List<String> pgConfigs = ImmutableList.of(
+        keepMainProguardConfiguration(CLASS_NAME),
+        REFLECTIONS,
+        "-overloadaggressively",
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexInspector dexInspector = new DexInspector(app);
+    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    FieldSubject f1 = clazz.field("java.lang.String", "same");
+    assertTrue(f1.isPresent());
+    assertTrue(f1.isRenamed());
+    FieldSubject f2 = clazz.field("java.lang.Object", "same");
+    assertTrue(f2.isPresent());
+    assertTrue(f2.isRenamed());
+    // TODO(b/72858955): R8 should resolve this field name conflict.
+    assertEquals(f1.getField().field.name, f2.getField().field.name);
+
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    assertEquals(0, artOutput.exitCode);
+    // TODO(b/72858955): distinct names will make the output be same.
+    // assertEquals(javaOutput.stdout, artOutput.stdout);
+  }
+
+  private JasminBuilder buildMethodNameConflictClassFile() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+    classBuilder.addStaticMethod("same", ImmutableList.of(), "Ljava/lang/String;",
+        "ldc \"" + MSG + "\"",
+        "areturn");
+    classBuilder.addStaticMethod("same", ImmutableList.of(), "Ljava/lang/Object;",
+        "aconst_null",
+        "areturn");
+    classBuilder.addMainMethod(
+        ".limit stack 3",
+        ".limit locals 1",
+        "ldc Example",
+        "ldc \"same\"",
+        "aconst_null",
+        "checkcast [Ljava/lang/Class;",
+        "invokevirtual java/lang/Class/getMethod"
+            + "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;",
+        "astore_0",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "aload_0",
+        "aconst_null",
+        "aconst_null",
+        "checkcast [Ljava/lang/Object;",
+        "invokevirtual java/lang/reflect/Method/invoke"
+            + "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;",
+        "invokevirtual java/io/PrintStream/print(Ljava/lang/Object;)V",
+        "return");
+    return builder;
+  }
+
+  @Test
+  public void remainMethodNameConflictDueToKeepRules() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    JasminBuilder builder = buildMethodNameConflictClassFile();
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+    assertEquals(0, javaOutput.exitCode);
+
+    List<String> pgConfigs = ImmutableList.of(
+        "-keep public class " + CLASS_NAME + " {\n"
+            + "  public static void main(java.lang.String[]);\n"
+            + "  static <methods>;"
+            + "}\n"
+            + "-printmapping\n",
+        REFLECTIONS,
+        "-useuniqueclassmembernames",
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexInspector dexInspector = new DexInspector(app);
+    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
+    assertTrue(m1.isPresent());
+    assertFalse(m1.isRenamed());
+    MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
+    assertTrue(m2.isPresent());
+    assertFalse(m2.isRenamed());
+    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    assertEquals(0, artOutput.exitCode);
+    // With name conflict, it is not guaranteed to get the same output.
+    // assertEquals(javaOutput.stdout, artOutput.stdout);
+  }
+
+  @Test
+  public void remainMethodNameConflictWithUseUniqueClassMemberNames() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    JasminBuilder builder = buildMethodNameConflictClassFile();
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+    assertEquals(0, javaOutput.exitCode);
+
+    List<String> pgConfigs = ImmutableList.of(
+        keepMainProguardConfiguration(CLASS_NAME),
+        REFLECTIONS,
+        "-useuniqueclassmembernames",
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexInspector dexInspector = new DexInspector(app);
+    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
+    assertTrue(m1.isPresent());
+    assertTrue(m1.isRenamed());
+    MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
+    assertTrue(m2.isPresent());
+    assertTrue(m2.isRenamed());
+    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    assertEquals(0, artOutput.exitCode);
+    // With name conflict, it is not guaranteed to get the same output.
+    // assertEquals(javaOutput.stdout, artOutput.stdout);
+  }
+
+  @Test
+  public void resolveMethodNameConflictWithoutAnyOption() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    JasminBuilder builder = buildMethodNameConflictClassFile();
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+    assertEquals(0, javaOutput.exitCode);
+
+    List<String> pgConfigs = ImmutableList.of(
+        keepMainProguardConfiguration(CLASS_NAME),
+        REFLECTIONS,
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexInspector dexInspector = new DexInspector(app);
+    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
+    assertTrue(m1.isPresent());
+    assertTrue(m1.isRenamed());
+    MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
+    assertTrue(m2.isPresent());
+    assertTrue(m2.isRenamed());
+    // TODO(b/73149686): R8 should be able to fix this conflict w/o -overloadaggressively.
+    // assertNotEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    assertEquals(0, artOutput.exitCode);
+    // TODO(b/73149686): distinct names will output the same results.
+    // assertEquals(javaOutput.stdout, artOutput.stdout);
+  }
+
+  @Test
+  public void resolveMethodNameConflictEvenWithOverloadAggressively() throws Exception {
+    Assume.assumeTrue(ToolHelper.artSupported());
+    JasminBuilder builder = buildMethodNameConflictClassFile();
+    ProcessResult javaOutput = runOnJavaNoVerifyRaw(builder, CLASS_NAME);
+    assertEquals(0, javaOutput.exitCode);
+
+    List<String> pgConfigs = ImmutableList.of(
+        keepMainProguardConfiguration(CLASS_NAME),
+        REFLECTIONS,
+        "-overloadaggressively",
+        "-dontshrink");
+    AndroidApp app = compileWithR8(builder, pgConfigs, null);
+
+    DexInspector dexInspector = new DexInspector(app);
+    ClassSubject clazz = dexInspector.clazz(CLASS_NAME);
+    assertTrue(clazz.isPresent());
+    MethodSubject m1 = clazz.method("java.lang.String", "same", ImmutableList.of());
+    assertTrue(m1.isPresent());
+    assertTrue(m1.isRenamed());
+    MethodSubject m2 = clazz.method("java.lang.Object", "same", ImmutableList.of());
+    assertTrue(m2.isPresent());
+    assertTrue(m2.isRenamed());
+    // TODO(b/73149686): R8 should be able to fix this conflict even w/ -overloadaggressively.
+    assertEquals(m1.getMethod().method.name, m2.getMethod().method.name);
+
+    ProcessResult artOutput = runOnArtRaw(app, CLASS_NAME, dexVm);
+    assertEquals(0, artOutput.exitCode);
+    // TODO(b/73149686): distinct names will output the same results.
+    // assertEquals(javaOutput.stdout, artOutput.stdout);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java b/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
index 0553fe4..f1d846e 100644
--- a/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
+++ b/src/test/java/com/android/tools/r8/regress/b69906048/Regress69906048Test.java
@@ -16,7 +16,7 @@
   public void buildWithD8AndRunWithDalvikOrArt() throws Exception {
     AndroidApp androidApp = compileWithR8(
         ImmutableList.of(ClassWithAnnotations.class, AnAnnotation.class),
-        options -> options.minApiLevel = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()));
+        options -> options.minApiLevel = ToolHelper.getMinApiLevelForDexVm().getLevel());
     String result = runOnArt(androidApp, ClassWithAnnotations.class);
     Assert.assertEquals("@" + AnAnnotation.class.getCanonicalName() + "()", result);
   }
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index 3725a24..cf2be59 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -46,10 +46,10 @@
     List<byte[]> allBytes = new ArrayList<>();
     allBytes.addAll(ASM_CLASSES);
     for (Class clazz : CLASSES) {
-      allBytes.add(getBytesFromJavaClass(clazz));
+      allBytes.add(ToolHelper.getClassAsBytes(clazz));
     }
     ensureSameOutput(Main.class.getCanonicalName(),
-        ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()),
+        ToolHelper.getMinApiLevelForDexVm(),
         allBytes.toArray(new byte[allBytes.size()][]));
   }
 }
diff --git a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
index 3c79bd3..3c724c0 100644
--- a/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
+++ b/src/test/java/com/android/tools/r8/rewrite/assertions/RemoveAssertionsTest.java
@@ -26,7 +26,7 @@
     AndroidApp app = compileWithR8(
         ImmutableList.of(testClass),
         keepMainProguardConfiguration(testClass, true, false),
-        options -> options.inlineAccessors = false);
+        options -> options.enableInlining = false);
     DexInspector x = new DexInspector(app);
 
     ClassSubject clazz = x.clazz(ClassWithAssertions.class);
diff --git a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
index 94e7669..cce5a96 100644
--- a/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/PrintUsageTest.java
@@ -79,7 +79,7 @@
             .build();
     ToolHelper.runR8(command, options -> {
       // Disable inlining to make this test not depend on inlining decisions.
-      options.inlineAccessors = false;
+      options.enableInlining = false;
     });
   }
 
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
index fdb35fb..cba8686 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingSpecificTest.java
@@ -94,7 +94,7 @@
         .addProguardConfigurationFiles(keepRules, printMapping);
     ToolHelper.getAppBuilder(builder).addProgramFiles(originalDex);
     // Turn off inlining, as we want the mapping that is printed to be stable.
-    ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = false);
+    ToolHelper.runR8(builder.build(), options -> options.enableInlining = false);
 
     Path outputmapping = out.resolve("mapping.txt");
     // Remove comments.
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index 732f397..fe1f1eb 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -145,7 +145,7 @@
             .addProguardConfigurationFiles(ListUtils.map(keepRulesFiles, Paths::get))
             .addLibraryFiles(JAR_LIBRARIES);
     ToolHelper.getAppBuilder(builder).addProgramFiles(Paths.get(programFile));
-    ToolHelper.runR8(builder.build(), options -> options.inlineAccessors = inline);
+    ToolHelper.runR8(builder.build(), options -> options.enableInlining = inline);
   }
 
   public static void shaking1HasNoClassUnused(DexInspector inspector) {
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
index 402e639..64ea9b4 100644
--- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java
+++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -49,7 +49,7 @@
   private Consumer<InternalOptions> configureOptions(Consumer<OutlineOptions> optionsConsumer) {
     return options -> {
       // Disable inlining to make sure that code looks as expected.
-      options.inlineAccessors = false;
+      options.enableInlining = false;
       // Also apply outline options.
       optionsConsumer.accept(options.outline);
     };
diff --git a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
index 2dec26c..dae717e 100644
--- a/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
+++ b/src/test/java/com/android/tools/r8/smali/RemoveWriteOfUnusedFieldsTest.java
@@ -59,7 +59,7 @@
         compileWithR8(
             AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build(),
             keepMainProguardConfiguration("Test"),
-            options -> options.inlineAccessors = false);
+            options -> options.enableInlining = false);
 
     DexInspector inspector = new DexInspector(app);
     MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
@@ -111,7 +111,7 @@
         compileWithR8(
             AndroidApp.builder().addDexProgramData(builder.compile(), Origin.unknown()).build(),
             keepMainProguardConfiguration("Test"),
-            options -> options.inlineAccessors = false);
+            options -> options.enableInlining = false);
 
     DexInspector inspector = new DexInspector(app);
     MethodSubject method = inspector.clazz("Test").method("void", "test", ImmutableList.of());
diff --git a/src/test/java/com/android/tools/r8/utils/DexInspector.java b/src/test/java/com/android/tools/r8/utils/DexInspector.java
index 7db0d6c..95ecd4f 100644
--- a/src/test/java/com/android/tools/r8/utils/DexInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/DexInspector.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.code.Const4;
 import com.android.tools.r8.code.ConstString;
 import com.android.tools.r8.code.Goto;
+import com.android.tools.r8.code.IfEqz;
 import com.android.tools.r8.code.IfNez;
 import com.android.tools.r8.code.Iget;
 import com.android.tools.r8.code.IgetBoolean;
@@ -755,9 +756,10 @@
     @Override
     public MethodSignature getOriginalSignature() {
       MethodSignature signature = getFinalSignature();
-      return clazz.naming != null ?
-          (MethodSignature) clazz.naming.lookup(signature).getOriginalSignature() :
-          signature;
+      MemberNaming memberNaming = clazz.naming != null ? clazz.naming.lookup(signature) : null;
+      return memberNaming != null
+          ? (MethodSignature) memberNaming.getOriginalSignature()
+          : signature;
     }
 
     @Override
@@ -878,9 +880,10 @@
     @Override
     public FieldSignature getOriginalSignature() {
       FieldSignature signature = getFinalSignature();
-      return clazz.naming != null ?
-          (FieldSignature) clazz.naming.lookup(signature).getOriginalSignature() :
-          signature;
+      MemberNaming memberNaming = clazz.naming != null ? clazz.naming.lookup(signature) : null;
+      return memberNaming != null
+          ? (FieldSignature) memberNaming.getOriginalSignature()
+          : signature;
     }
 
     @Override
@@ -999,6 +1002,10 @@
       return instruction instanceof IfNez;
     }
 
+    boolean isIfEqz(Instruction instruction) {
+      return instruction instanceof IfEqz;
+    }
+
     boolean isFieldAccess(Instruction instruction) {
       return isInstanceGet(instruction)
           || isInstancePut(instruction)
@@ -1109,6 +1116,10 @@
       return factory.isIfNez(instruction);
     }
 
+    public boolean isIfEqz() {
+      return factory.isIfEqz(instruction);
+    }
+
     public boolean isReturnVoid() {
       return factory.isReturnVoid(instruction);
     }
diff --git a/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
new file mode 100644
index 0000000..3e759d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/FeatureClassMappingTest.java
@@ -0,0 +1,83 @@
+// 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 static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+
+import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+
+public class FeatureClassMappingTest {
+
+  @Test
+  public void testSimpleParse() throws Exception {
+
+    List<String> lines =
+        ImmutableList.of(
+            "# Comment, don't care about contents: even more ::::",
+            "com.google.base:base",
+            "", // Empty lines allowed
+            "com.google.feature1:feature1",
+            "com.google.feature1:feature1", // Multiple definitions of the same predicate allowed.
+            "com.google$:feature1",
+            "_com.google:feature21",
+            "com.google.*:feature32");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+  }
+
+  private void ensureThrowsMappingException(List<String> lines) {
+    try {
+      new FeatureClassMapping(lines);
+      assertFalse(true);
+    } catch (FeatureMappingException e) {
+      // Expected
+    }
+  }
+
+  private void ensureThrowsMappingException(String string) {
+    ensureThrowsMappingException(ImmutableList.of(string));
+  }
+
+  @Test
+  public void testLookup() throws Exception {
+    List<String> lines =
+        ImmutableList.of(
+            "com.google.Base:base",
+            "",
+            "com.google.Feature1:feature1",
+            "com.google.Feature1:feature1", // Multiple definitions of the same predicate allowed.
+            "com.google.different.*:feature1",
+            "_com.Google:feature21",
+            "com.google.bas42.*:feature42");
+    FeatureClassMapping mapping = new FeatureClassMapping(lines);
+    assertEquals(mapping.featureForClass("com.google.Feature1"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.different.Feature1"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.different.Foobar"), "feature1");
+    assertEquals(mapping.featureForClass("com.google.Base"), "base");
+    assertEquals(mapping.featureForClass("com.google.bas42.foo.bar.bar.Foo"), "feature42");
+    assertEquals(mapping.featureForClass("com.google.bas42.f$o$o$.bar43.bar.Foo"), "feature42");
+    assertEquals(mapping.featureForClass("_com.Google"), "feature21");
+  }
+
+  @Test
+  public void testWrongLines() throws Exception {
+    // No colon.
+    ensureThrowsMappingException("foo");
+    ensureThrowsMappingException("com.google.base");
+    // Two colons.
+    ensureThrowsMappingException(ImmutableList.of("a:b:c"));
+
+    // Empty identifier.
+    ensureThrowsMappingException("com..google:feature1");
+
+    // Ambiguous redefinition
+    ensureThrowsMappingException(
+        ImmutableList.of("com.google.foo:feature1", "com.google.foo:feature2"));
+    ensureThrowsMappingException(
+        ImmutableList.of("com.google.foo.*:feature1", "com.google.foo.*:feature2"));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
index b14ef8a..fb4fe70 100644
--- a/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
+++ b/src/test/java/com/android/tools/r8/utils/GenerateMainDexListCommandTest.java
@@ -61,7 +61,7 @@
 
   private void addAndroidJarsToCommandLine(List<String> args) {
     args.add("--lib");
-    args.add(ToolHelper.getAndroidJar(AndroidApiLevel.K.getLevel()).toAbsolutePath().toString());
+    args.add(ToolHelper.getAndroidJar(AndroidApiLevel.K).toAbsolutePath().toString());
   }
 
   // Add the jars used in the com.android.tools.r8.maindexlist.MainDexTracingTest test.
diff --git a/src/test/kotlinR8TestResources/dataclass/MainCopy.kt b/src/test/kotlinR8TestResources/dataclass/MainCopy.kt
index 2e4f047..2644302 100644
--- a/src/test/kotlinR8TestResources/dataclass/MainCopy.kt
+++ b/src/test/kotlinR8TestResources/dataclass/MainCopy.kt
@@ -16,7 +16,8 @@
 
 fun testDataClassCopy() {
     val albert = Person("Albert", 28)
-    val olderJonas = albert.copy("Jonas", albert.age + 10)
+    val youngerJonas = albert.copy("Jonas", albert.age - 10)
+    val olderJonas = youngerJonas.copy("Jonas", albert.age + 20)
     println("Name: ${olderJonas.name}")
     println("Age: ${olderJonas.age}")
 }
\ No newline at end of file
diff --git a/src/test/kotlinR8TestResources/dataclass/MainCopyWithDefault.kt b/src/test/kotlinR8TestResources/dataclass/MainCopyWithDefault.kt
deleted file mode 100644
index 2df19f7..0000000
--- a/src/test/kotlinR8TestResources/dataclass/MainCopyWithDefault.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package dataclass
-
-/**
- * This is an example of copying an instance of a data class Person using its copy method and
- * relying on default values for some of its properties. Therefore the compiler will generate an
- * invoke to copy$default method which is a wrapper around copy and deals with default values.
- *
- * See https://kotlinlang.org/docs/reference/data-classes.html#copying.
- */
-fun main(args: Array<String>) {
-    testDataClassCopyWithDefault()
-}
-
-fun testDataClassCopyWithDefault() {
-    val albert = Person("Albert", 28)
-    // We don't pass a 'name', thus we copy the property value of the receiver. This will result
-    // in calling the copy$default method instead of the copy method.
-    val olderAlbert = albert.copy(age = albert.age + 10)
-    println("Name: ${olderAlbert.name}")
-    println("Age: ${olderAlbert.age}")
-}
\ No newline at end of file
diff --git a/src/test/kotlinR8TestResources/intrinsics/Intrinsics.kt b/src/test/kotlinR8TestResources/intrinsics/Intrinsics.kt
new file mode 100644
index 0000000..90d26bf
--- /dev/null
+++ b/src/test/kotlinR8TestResources/intrinsics/Intrinsics.kt
@@ -0,0 +1,32 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package intrinsics
+
+import java.lang.reflect.InvocationTargetException
+
+fun main(args: Array<String>) {
+    testParameterNullCheck()
+}
+
+fun expectsNonNullParameters(a: String, b: String): String = a + b
+
+fun testParameterNullCheck() {
+    println("> ${expectsNonNullParameters("pre", "post")} <")
+
+    val intrinsics = Class.forName("intrinsics.IntrinsicsKt")
+    val method = intrinsics.getMethod(
+            "expectsNonNullParameters", String::class.java, String::class.java)
+
+    println("> ${method.invoke(null, "pre", "post")} <")
+
+    try {
+        println("> ${method.invoke(null, "pre", null)} <")
+    } catch (e: InvocationTargetException) {
+        println("> exception: ${e.targetException::javaClass} <")
+        return
+    }
+    throw AssertionError()
+}
+
diff --git a/third_party/core-lambda-stubs.tar.gz.sha1 b/third_party/core-lambda-stubs.tar.gz.sha1
new file mode 100644
index 0000000..3b8961a
--- /dev/null
+++ b/third_party/core-lambda-stubs.tar.gz.sha1
@@ -0,0 +1 @@
+23dd799b1df85a68110bf06f672177d553b0682c
\ No newline at end of file
diff --git a/tools/archive.py b/tools/archive.py
index 360a0ee..815e416 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -113,6 +113,14 @@
       print('Uploading %s to %s' % (tagged_jar, destination))
       utils.upload_file_to_cloud_storage(tagged_jar, destination)
       print('File available at: %s' % GetUrl(version, file_name, is_master))
+    # Upload extracted maven directory for easy testing in studio.
+    zip_ref = zipfile.ZipFile(utils.MAVEN_ZIP, 'r')
+    zip_ref.extractall(temp)
+    zip_ref.close()
+    utils.upload_dir_to_cloud_storage(
+        os.path.join(temp, 'com'),
+        GetUploadDestination(version, 'com', is_master))
+    print('Maven repo root available at: %s' % GetUrl(version, '', is_master))
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index cc045a2..47bf7d2 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -210,8 +210,7 @@
   # Create directory structure for this version.
   version = determine_version()
   with utils.TempDir() as tmp_dir:
-    version_dir = join(
-        tmp_dir, 'com', 'google', 'android', 'tools', 'r8', version, 'r8')
+    version_dir = join(tmp_dir, 'com', 'android', 'tools', 'r8', version)
     makedirs(version_dir)
     # Write the pom file.
     pom_file = join(version_dir, 'r8-' + version + '.pom')
diff --git a/tools/test.py b/tools/test.py
index 58fc4eb..ed99c1d 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -85,7 +85,7 @@
   upload_dir = os.path.join(utils.REPO_ROOT, 'build', 'reports', 'tests')
   u_dir = uuid.uuid4()
   destination = 'gs://%s/%s' % (BUCKET, u_dir)
-  utils.upload_dir_to_cloud_storage(upload_dir, destination)
+  utils.upload_dir_to_cloud_storage(upload_dir, destination, is_html=True)
   url = 'http://storage.googleapis.com/%s/%s/test/index.html' % (BUCKET, u_dir)
   print 'Test results available at: %s' % url
   print '@@@STEP_LINK@Test failures@%s@@@' % url
@@ -166,4 +166,3 @@
   else:
     notify.notify("Tests passed.")
   sys.exit(return_code)
-
diff --git a/tools/update_prebuilds_in_android.py b/tools/update_prebuilds_in_android.py
index d6e96a3..7635ba6 100755
--- a/tools/update_prebuilds_in_android.py
+++ b/tools/update_prebuilds_in_android.py
@@ -53,7 +53,9 @@
 def download_target(root, url, target):
   download_path = os.path.join(root, target)
   print 'Downloading: ' + url + ' -> ' + download_path
-  urllib.urlretrieve(url, download_path)
+  result = urllib.urlretrieve(url, download_path)
+  if 'X-GUploader-Request-Result: success' not in str(result[1]):
+    raise IOError('Failed to download ' + url)
 
 def Main():
   args = parse_arguments()
diff --git a/tools/utils.py b/tools/utils.py
index 90e084b..8716f65 100644
--- a/tools/utils.py
+++ b/tools/utils.py
@@ -73,10 +73,12 @@
     if not os.path.isdir(path):
         raise
 
-def upload_dir_to_cloud_storage(directory, destination):
+def upload_dir_to_cloud_storage(directory, destination, is_html=False):
   # Upload and make the content encoding right for viewing directly
-  cmd = ['gsutil.py', 'cp', '-z', 'html', '-a',
-         'public-read', '-R', directory, destination]
+  cmd = ['gsutil.py', 'cp']
+  if is_html:
+    cmd += ['-z', 'html']
+  cmd += ['-a', 'public-read', '-R', directory, destination]
   PrintCmd(cmd)
   subprocess.check_call(cmd)
 
@@ -204,8 +206,7 @@
     print('{}-{}(CodeSize): {}'
         .format(prefix, segment_name, size))
 
-# ensure that java version is 1.8.*-internal,
-# as opposed to e.g. 1.7* or 1.8.*-google-v7
+# Ensure that we are not benchmarking with a google jvm.
 def check_java_version():
   cmd= ['java', '-version']
   output = subprocess.check_output(cmd, stderr = subprocess.STDOUT)
@@ -214,10 +215,9 @@
     raise Exception("Can't check java version: no version string in output"
         " of 'java -version': '{}'".format(output))
   version = m.groups(0)[0]
-  m = re.search('1[.]8[.].*-internal', version)
-  if m is None:
-    raise Exception("Incorrect java version, expected: '1.8.*-internal',"
-        " actual: {}".format(version))
+  m = re.search('google', version)
+  if m is not None:
+    raise Exception("Do not use google JVM for benchmarking: " + version)
 
 def verify_with_dex2oat(dex_file):